From 6c7b18b32d87c2a835f7e5c48faac4a8ad44668b Mon Sep 17 00:00:00 2001 From: piaip Date: Thu, 20 Mar 2008 11:33:49 +0000 Subject: - (internal/exp) first draft of new layout git-svn-id: http://opensvn.csie.org/pttbbs/branches/piaip.newlayout@4013 63ad8ddf-47c3-0310-b6dd-a9e9d9715204 --- README | 24 +- blog/INSTALL | 79 - blog/blog.pl | 483 -- blog/builddb.pl | 247 - blog/index.pl | 17 - cacheserver/Makefile | 30 - cacheserver/README | 1 - cacheserver/authserver.c | 236 - cacheserver/friend.cpp | 350 - cacheserver/utmpserver.c | 222 - cacheserver/utmpserver2.c | 290 - cacheserver/utmpserver3.c | 341 - cacheserver/utmpsync.c | 27 - common/bbs/Makefile | 24 + common/bbs/log.c | 0 common/bbs/money.c | 37 + common/bbs/string.c | 1 + common/sys/Makefile | 24 + common/sys/file.c | 297 + common/sys/lock.c | 23 + common/sys/log.c | 43 + common/sys/net.c | 114 + common/sys/sort.c | 10 + common/sys/string.c | 345 + common/sys/time.c | 116 + console/Makefile | 136 + console/admin.c | 1172 ++++ console/aids.c | 290 + console/alloc.c | 254 + console/announce.c | 1558 +++++ console/args.c | 73 + console/assess.c | 268 + console/bbs.c | 3986 +++++++++++ console/bbslua.c | 1801 +++++ console/bbsluaext.c | 83 + console/board.c | 1960 ++++++ console/brc.c | 596 ++ console/cache.c | 1215 ++++ console/cal.c | 630 ++ console/calendar.c | 362 + console/card.c | 672 ++ console/chat.c | 597 ++ console/chc.c | 1012 +++ console/chc_tab.c | 106 + console/chess.c | 1762 +++++ console/chicken.c | 1138 +++ console/convert.c | 123 + console/crypt.c | 647 ++ console/dark.c | 565 ++ console/edit.c | 3886 ++++++++++ console/emaildb.c | 244 + console/fav.c | 1320 ++++ console/file.c | 186 + console/friend.c | 572 ++ console/gamble.c | 388 + console/go.c | 1118 +++ console/gomo.c | 590 ++ console/guess.c | 369 + console/io.c | 1050 +++ console/kaede.c | 165 + console/lovepaper.c | 114 + console/mail.c | 2092 ++++++ console/mbbsd.c | 1832 +++++ console/menu.c | 718 ++ console/merge.c | 237 + console/more.c | 77 + console/name.c | 1067 +++ console/osdep.c | 643 ++ console/othello.c | 577 ++ console/passwd.c | 171 + console/pfterm.c | 2502 +++++++ console/pmore.c | 3732 ++++++++++ console/random.c | 711 ++ console/read.c | 1371 ++++ console/record.c | 666 ++ console/register.c | 3040 ++++++++ console/reversi.c | 513 ++ console/screen.c | 819 +++ console/stuff.c | 828 +++ console/syspost.c | 146 + console/talk.c | 3654 ++++++++++ console/telnet.c | 338 + console/term.c | 118 + console/time.c | 20 + console/topsong.c | 72 + console/user.c | 1452 ++++ console/var.c | 628 ++ console/vice.c | 153 + console/vote.c | 1088 +++ console/voteboard.c | 400 ++ console/xyz.c | 338 + daemon/cached/Makefile | 30 + daemon/cached/README | 1 + daemon/cached/authserver.c | 236 + daemon/cached/friend.cpp | 350 + daemon/cached/utmpserver.c | 222 + daemon/cached/utmpserver2.c | 290 + daemon/cached/utmpserver3.c | 341 + daemon/cached/utmpsync.c | 27 + daemon/innbbsd/COPYRIGHT.nocem | 11 + daemon/innbbsd/Makefile | 74 + daemon/innbbsd/antisplam.h | 25 + daemon/innbbsd/bbslib.c | 773 ++ daemon/innbbsd/bbslib.h | 62 + daemon/innbbsd/bbslink.c | 1809 +++++ daemon/innbbsd/bbsnnrp.c | 1247 ++++ daemon/innbbsd/clibrary.h | 142 + daemon/innbbsd/closeonexec.c | 69 + daemon/innbbsd/connectsock.c | 464 ++ daemon/innbbsd/ctlinnbbsd.c | 167 + daemon/innbbsd/daemon.c | 177 + daemon/innbbsd/daemon.h | 54 + daemon/innbbsd/dbz.c | 1885 +++++ daemon/innbbsd/dbz.h | 36 + daemon/innbbsd/dbztool.c | 97 + daemon/innbbsd/echobbslib.c | 777 ++ daemon/innbbsd/externs.h | 88 + daemon/innbbsd/file.c | 205 + daemon/innbbsd/his.c | 477 ++ daemon/innbbsd/his.h | 77 + daemon/innbbsd/innbbsconf.h | 186 + daemon/innbbsd/innbbsd.c | 794 +++ daemon/innbbsd/innbbsd.h | 9 + daemon/innbbsd/inncheck.pl | 47 + daemon/innbbsd/inndchannel.c | 681 ++ daemon/innbbsd/inntobbs.c | 342 + daemon/innbbsd/inntobbs.h | 39 + daemon/innbbsd/mkhistory.c | 18 + daemon/innbbsd/nntp.h | 141 + daemon/innbbsd/nocem.c | 636 ++ daemon/innbbsd/nocem.h | 57 + daemon/innbbsd/pmain.c | 64 + daemon/innbbsd/port.c | 35 + daemon/innbbsd/receive_article.c | 1125 +++ daemon/innbbsd/rfc931.c | 147 + daemon/innbbsd/str_decode.c | 291 + innbbsd/COPYRIGHT.nocem | 11 - innbbsd/Makefile | 74 - innbbsd/antisplam.h | 25 - innbbsd/bbslib.c | 773 -- innbbsd/bbslib.h | 62 - innbbsd/bbslink.c | 1809 ----- innbbsd/bbsnnrp.c | 1247 ---- innbbsd/clibrary.h | 142 - innbbsd/closeonexec.c | 69 - innbbsd/connectsock.c | 464 -- innbbsd/ctlinnbbsd.c | 167 - innbbsd/daemon.c | 177 - innbbsd/daemon.h | 54 - innbbsd/dbz.c | 1885 ----- innbbsd/dbz.h | 36 - innbbsd/dbztool.c | 97 - innbbsd/echobbslib.c | 777 -- innbbsd/externs.h | 88 - innbbsd/file.c | 205 - innbbsd/his.c | 477 -- innbbsd/his.h | 77 - innbbsd/innbbsconf.h | 186 - innbbsd/innbbsd.c | 794 --- innbbsd/innbbsd.h | 9 - innbbsd/inncheck.pl | 47 - innbbsd/inndchannel.c | 681 -- innbbsd/inntobbs.c | 342 - innbbsd/inntobbs.h | 39 - innbbsd/mkhistory.c | 18 - innbbsd/nntp.h | 141 - innbbsd/nocem.c | 636 -- innbbsd/nocem.h | 57 - innbbsd/pmain.c | 64 - innbbsd/port.c | 35 - innbbsd/receive_article.c | 1125 --- innbbsd/rfc931.c | 147 - innbbsd/str_decode.c | 291 - mbbsd/Makefile | 136 - mbbsd/admin.c | 1172 ---- mbbsd/aids.c | 290 - mbbsd/alloc.c | 254 - mbbsd/announce.c | 1558 ----- mbbsd/args.c | 73 - mbbsd/assess.c | 268 - mbbsd/bbs.c | 3986 ----------- mbbsd/bbslua.c | 1801 ----- mbbsd/bbsluaext.c | 83 - mbbsd/board.c | 1960 ------ mbbsd/brc.c | 596 -- mbbsd/cache.c | 1215 ---- mbbsd/cal.c | 630 -- mbbsd/calendar.c | 362 - mbbsd/card.c | 672 -- mbbsd/chat.c | 597 -- mbbsd/chc.c | 1012 --- mbbsd/chc_tab.c | 106 - mbbsd/chess.c | 1762 ----- mbbsd/chicken.c | 1138 --- mbbsd/convert.c | 123 - mbbsd/crypt.c | 647 -- mbbsd/dark.c | 565 -- mbbsd/edit.c | 3886 ---------- mbbsd/emaildb.c | 244 - mbbsd/fav.c | 1320 ---- mbbsd/file.c | 186 - mbbsd/friend.c | 572 -- mbbsd/gamble.c | 388 - mbbsd/go.c | 1118 --- mbbsd/gomo.c | 590 -- mbbsd/guess.c | 369 - mbbsd/io.c | 1050 --- mbbsd/kaede.c | 165 - mbbsd/lovepaper.c | 114 - mbbsd/mail.c | 2092 ------ mbbsd/mbbsd.c | 1832 ----- mbbsd/menu.c | 718 -- mbbsd/merge.c | 237 - mbbsd/more.c | 77 - mbbsd/name.c | 1067 --- mbbsd/osdep.c | 643 -- mbbsd/othello.c | 577 -- mbbsd/passwd.c | 171 - mbbsd/pfterm.c | 2502 ------- mbbsd/pmore.c | 3732 ---------- mbbsd/random.c | 711 -- mbbsd/read.c | 1371 ---- mbbsd/record.c | 666 -- mbbsd/register.c | 3040 -------- mbbsd/reversi.c | 513 -- mbbsd/screen.c | 819 --- mbbsd/stuff.c | 828 --- mbbsd/syspost.c | 146 - mbbsd/talk.c | 3654 ---------- mbbsd/telnet.c | 338 - mbbsd/term.c | 118 - mbbsd/time.c | 20 - mbbsd/topsong.c | 72 - mbbsd/user.c | 1452 ---- mbbsd/var.c | 628 -- mbbsd/vice.c | 153 - mbbsd/vote.c | 1088 --- mbbsd/voteboard.c | 400 -- mbbsd/xyz.c | 338 - src/libbbs/Makefile | 24 - src/libbbs/log.c | 0 src/libbbs/money.c | 37 - src/libbbs/string.c | 1 - src/libbbsutil/Makefile | 24 - src/libbbsutil/file.c | 297 - src/libbbsutil/lock.c | 23 - src/libbbsutil/log.c | 43 - src/libbbsutil/net.c | 114 - src/libbbsutil/sort.c | 10 - src/libbbsutil/string.c | 345 - src/libbbsutil/time.c | 116 - staticweb/INSTALL | 43 - staticweb/article.html | 18 - staticweb/b2g.pm | 13990 ------------------------------------- staticweb/banner.html | 10 - staticweb/dir.html | 43 - staticweb/header.html | 15 - staticweb/index.html | 47 - staticweb/index.pl | 99 - staticweb/man.pl | 145 - staticweb/manbuilder.pl | 105 - staticweb/search.html | 36 - staticweb/styles.css | 20 - web/blog/INSTALL | 79 + web/blog/blog.pl | 483 ++ web/blog/builddb.pl | 247 + web/blog/index.pl | 17 + web/static/INSTALL | 43 + web/static/article.html | 18 + web/static/b2g.pm | 13990 +++++++++++++++++++++++++++++++++++++ web/static/banner.html | 10 + web/static/dir.html | 43 + web/static/header.html | 15 + web/static/index.html | 47 + web/static/index.pl | 99 + web/static/man.pl | 145 + web/static/manbuilder.pl | 105 + web/static/search.html | 36 + web/static/styles.css | 20 + 279 files changed, 92287 insertions(+), 92271 deletions(-) delete mode 100644 blog/INSTALL delete mode 100755 blog/blog.pl delete mode 100755 blog/builddb.pl delete mode 100755 blog/index.pl delete mode 100644 cacheserver/Makefile delete mode 100644 cacheserver/README delete mode 100644 cacheserver/authserver.c delete mode 100644 cacheserver/friend.cpp delete mode 100644 cacheserver/utmpserver.c delete mode 100644 cacheserver/utmpserver2.c delete mode 100644 cacheserver/utmpserver3.c delete mode 100644 cacheserver/utmpsync.c create mode 100644 common/bbs/Makefile create mode 100644 common/bbs/log.c create mode 100644 common/bbs/money.c create mode 100644 common/bbs/string.c create mode 100644 common/sys/Makefile create mode 100644 common/sys/file.c create mode 100644 common/sys/lock.c create mode 100644 common/sys/log.c create mode 100644 common/sys/net.c create mode 100644 common/sys/sort.c create mode 100644 common/sys/string.c create mode 100644 common/sys/time.c create mode 100644 console/Makefile create mode 100644 console/admin.c create mode 100644 console/aids.c create mode 100644 console/alloc.c create mode 100644 console/announce.c create mode 100644 console/args.c create mode 100644 console/assess.c create mode 100644 console/bbs.c create mode 100644 console/bbslua.c create mode 100644 console/bbsluaext.c create mode 100644 console/board.c create mode 100644 console/brc.c create mode 100644 console/cache.c create mode 100644 console/cal.c create mode 100644 console/calendar.c create mode 100644 console/card.c create mode 100644 console/chat.c create mode 100644 console/chc.c create mode 100644 console/chc_tab.c create mode 100644 console/chess.c create mode 100644 console/chicken.c create mode 100644 console/convert.c create mode 100644 console/crypt.c create mode 100644 console/dark.c create mode 100644 console/edit.c create mode 100644 console/emaildb.c create mode 100644 console/fav.c create mode 100644 console/file.c create mode 100644 console/friend.c create mode 100644 console/gamble.c create mode 100644 console/go.c create mode 100644 console/gomo.c create mode 100644 console/guess.c create mode 100644 console/io.c create mode 100644 console/kaede.c create mode 100644 console/lovepaper.c create mode 100644 console/mail.c create mode 100644 console/mbbsd.c create mode 100644 console/menu.c create mode 100644 console/merge.c create mode 100644 console/more.c create mode 100644 console/name.c create mode 100644 console/osdep.c create mode 100644 console/othello.c create mode 100644 console/passwd.c create mode 100644 console/pfterm.c create mode 100644 console/pmore.c create mode 100644 console/random.c create mode 100644 console/read.c create mode 100644 console/record.c create mode 100644 console/register.c create mode 100644 console/reversi.c create mode 100644 console/screen.c create mode 100644 console/stuff.c create mode 100644 console/syspost.c create mode 100644 console/talk.c create mode 100644 console/telnet.c create mode 100644 console/term.c create mode 100644 console/time.c create mode 100644 console/topsong.c create mode 100644 console/user.c create mode 100644 console/var.c create mode 100644 console/vice.c create mode 100644 console/vote.c create mode 100644 console/voteboard.c create mode 100644 console/xyz.c create mode 100644 daemon/cached/Makefile create mode 100644 daemon/cached/README create mode 100644 daemon/cached/authserver.c create mode 100644 daemon/cached/friend.cpp create mode 100644 daemon/cached/utmpserver.c create mode 100644 daemon/cached/utmpserver2.c create mode 100644 daemon/cached/utmpserver3.c create mode 100644 daemon/cached/utmpsync.c create mode 100644 daemon/innbbsd/COPYRIGHT.nocem create mode 100644 daemon/innbbsd/Makefile create mode 100644 daemon/innbbsd/antisplam.h create mode 100644 daemon/innbbsd/bbslib.c create mode 100644 daemon/innbbsd/bbslib.h create mode 100644 daemon/innbbsd/bbslink.c create mode 100644 daemon/innbbsd/bbsnnrp.c create mode 100644 daemon/innbbsd/clibrary.h create mode 100644 daemon/innbbsd/closeonexec.c create mode 100644 daemon/innbbsd/connectsock.c create mode 100644 daemon/innbbsd/ctlinnbbsd.c create mode 100644 daemon/innbbsd/daemon.c create mode 100644 daemon/innbbsd/daemon.h create mode 100644 daemon/innbbsd/dbz.c create mode 100644 daemon/innbbsd/dbz.h create mode 100644 daemon/innbbsd/dbztool.c create mode 100644 daemon/innbbsd/echobbslib.c create mode 100644 daemon/innbbsd/externs.h create mode 100644 daemon/innbbsd/file.c create mode 100644 daemon/innbbsd/his.c create mode 100644 daemon/innbbsd/his.h create mode 100644 daemon/innbbsd/innbbsconf.h create mode 100644 daemon/innbbsd/innbbsd.c create mode 100644 daemon/innbbsd/innbbsd.h create mode 100644 daemon/innbbsd/inncheck.pl create mode 100644 daemon/innbbsd/inndchannel.c create mode 100644 daemon/innbbsd/inntobbs.c create mode 100644 daemon/innbbsd/inntobbs.h create mode 100644 daemon/innbbsd/mkhistory.c create mode 100644 daemon/innbbsd/nntp.h create mode 100644 daemon/innbbsd/nocem.c create mode 100644 daemon/innbbsd/nocem.h create mode 100644 daemon/innbbsd/pmain.c create mode 100644 daemon/innbbsd/port.c create mode 100644 daemon/innbbsd/receive_article.c create mode 100644 daemon/innbbsd/rfc931.c create mode 100644 daemon/innbbsd/str_decode.c delete mode 100644 innbbsd/COPYRIGHT.nocem delete mode 100644 innbbsd/Makefile delete mode 100644 innbbsd/antisplam.h delete mode 100644 innbbsd/bbslib.c delete mode 100644 innbbsd/bbslib.h delete mode 100644 innbbsd/bbslink.c delete mode 100644 innbbsd/bbsnnrp.c delete mode 100644 innbbsd/clibrary.h delete mode 100644 innbbsd/closeonexec.c delete mode 100644 innbbsd/connectsock.c delete mode 100644 innbbsd/ctlinnbbsd.c delete mode 100644 innbbsd/daemon.c delete mode 100644 innbbsd/daemon.h delete mode 100644 innbbsd/dbz.c delete mode 100644 innbbsd/dbz.h delete mode 100644 innbbsd/dbztool.c delete mode 100644 innbbsd/echobbslib.c delete mode 100644 innbbsd/externs.h delete mode 100644 innbbsd/file.c delete mode 100644 innbbsd/his.c delete mode 100644 innbbsd/his.h delete mode 100644 innbbsd/innbbsconf.h delete mode 100644 innbbsd/innbbsd.c delete mode 100644 innbbsd/innbbsd.h delete mode 100644 innbbsd/inncheck.pl delete mode 100644 innbbsd/inndchannel.c delete mode 100644 innbbsd/inntobbs.c delete mode 100644 innbbsd/inntobbs.h delete mode 100644 innbbsd/mkhistory.c delete mode 100644 innbbsd/nntp.h delete mode 100644 innbbsd/nocem.c delete mode 100644 innbbsd/nocem.h delete mode 100644 innbbsd/pmain.c delete mode 100644 innbbsd/port.c delete mode 100644 innbbsd/receive_article.c delete mode 100644 innbbsd/rfc931.c delete mode 100644 innbbsd/str_decode.c delete mode 100644 mbbsd/Makefile delete mode 100644 mbbsd/admin.c delete mode 100644 mbbsd/aids.c delete mode 100644 mbbsd/alloc.c delete mode 100644 mbbsd/announce.c delete mode 100644 mbbsd/args.c delete mode 100644 mbbsd/assess.c delete mode 100644 mbbsd/bbs.c delete mode 100644 mbbsd/bbslua.c delete mode 100644 mbbsd/bbsluaext.c delete mode 100644 mbbsd/board.c delete mode 100644 mbbsd/brc.c delete mode 100644 mbbsd/cache.c delete mode 100644 mbbsd/cal.c delete mode 100644 mbbsd/calendar.c delete mode 100644 mbbsd/card.c delete mode 100644 mbbsd/chat.c delete mode 100644 mbbsd/chc.c delete mode 100644 mbbsd/chc_tab.c delete mode 100644 mbbsd/chess.c delete mode 100644 mbbsd/chicken.c delete mode 100644 mbbsd/convert.c delete mode 100644 mbbsd/crypt.c delete mode 100644 mbbsd/dark.c delete mode 100644 mbbsd/edit.c delete mode 100644 mbbsd/emaildb.c delete mode 100644 mbbsd/fav.c delete mode 100644 mbbsd/file.c delete mode 100644 mbbsd/friend.c delete mode 100644 mbbsd/gamble.c delete mode 100644 mbbsd/go.c delete mode 100644 mbbsd/gomo.c delete mode 100644 mbbsd/guess.c delete mode 100644 mbbsd/io.c delete mode 100644 mbbsd/kaede.c delete mode 100644 mbbsd/lovepaper.c delete mode 100644 mbbsd/mail.c delete mode 100644 mbbsd/mbbsd.c delete mode 100644 mbbsd/menu.c delete mode 100644 mbbsd/merge.c delete mode 100644 mbbsd/more.c delete mode 100644 mbbsd/name.c delete mode 100644 mbbsd/osdep.c delete mode 100644 mbbsd/othello.c delete mode 100644 mbbsd/passwd.c delete mode 100644 mbbsd/pfterm.c delete mode 100644 mbbsd/pmore.c delete mode 100644 mbbsd/random.c delete mode 100644 mbbsd/read.c delete mode 100644 mbbsd/record.c delete mode 100644 mbbsd/register.c delete mode 100644 mbbsd/reversi.c delete mode 100644 mbbsd/screen.c delete mode 100644 mbbsd/stuff.c delete mode 100644 mbbsd/syspost.c delete mode 100644 mbbsd/talk.c delete mode 100644 mbbsd/telnet.c delete mode 100644 mbbsd/term.c delete mode 100644 mbbsd/time.c delete mode 100644 mbbsd/topsong.c delete mode 100644 mbbsd/user.c delete mode 100644 mbbsd/var.c delete mode 100644 mbbsd/vice.c delete mode 100644 mbbsd/vote.c delete mode 100644 mbbsd/voteboard.c delete mode 100644 mbbsd/xyz.c delete mode 100644 src/libbbs/Makefile delete mode 100644 src/libbbs/log.c delete mode 100644 src/libbbs/money.c delete mode 100644 src/libbbs/string.c delete mode 100644 src/libbbsutil/Makefile delete mode 100644 src/libbbsutil/file.c delete mode 100644 src/libbbsutil/lock.c delete mode 100644 src/libbbsutil/log.c delete mode 100644 src/libbbsutil/net.c delete mode 100644 src/libbbsutil/sort.c delete mode 100644 src/libbbsutil/string.c delete mode 100644 src/libbbsutil/time.c delete mode 100644 staticweb/INSTALL delete mode 100644 staticweb/article.html delete mode 100644 staticweb/b2g.pm delete mode 100644 staticweb/banner.html delete mode 100644 staticweb/dir.html delete mode 100644 staticweb/header.html delete mode 100644 staticweb/index.html delete mode 100755 staticweb/index.pl delete mode 100755 staticweb/man.pl delete mode 100755 staticweb/manbuilder.pl delete mode 100644 staticweb/search.html delete mode 100644 staticweb/styles.css create mode 100644 web/blog/INSTALL create mode 100755 web/blog/blog.pl create mode 100755 web/blog/builddb.pl create mode 100755 web/blog/index.pl create mode 100644 web/static/INSTALL create mode 100644 web/static/article.html create mode 100644 web/static/b2g.pm create mode 100644 web/static/banner.html create mode 100644 web/static/dir.html create mode 100644 web/static/header.html create mode 100644 web/static/index.html create mode 100755 web/static/index.pl create mode 100755 web/static/man.pl create mode 100755 web/static/manbuilder.pl create mode 100644 web/static/search.html create mode 100644 web/static/styles.css diff --git a/README b/README index f1e640be..c9f3edcb 100644 --- a/README +++ b/README @@ -24,7 +24,23 @@ $Id$ sample/ 範例 crontab 提供 bbs執行時須透過 crontab 定時跑的設定 - blog/ PttBLOG - include/ include 檔 - innbbsd/ 轉信 - mbbsd/ bbs 主程式 + include/ 共享的 include 檔 + + common/ 共通程式庫 + sys/ 系統或其它底層相關 + bbs/ 有 BBS 邏輯的部份 + + web/ WWW Based Clients + blog/ PttBlog + static/ 靜態網頁輸出 + + console/ Console Mode Client (mbbsd 傳統界面) + + daemon/ + innbbsd/ 轉信 + cached/ 線上記錄伺服器 + xchatd 聊天室伺服器 + + util/ 其它管理輔助程式 + + upgrade/ 系統昇級相關區 diff --git a/blog/INSTALL b/blog/INSTALL deleted file mode 100644 index 1f216292..00000000 --- a/blog/INSTALL +++ /dev/null @@ -1,79 +0,0 @@ -這篇文章在描述怎麼架設 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 - 在中間, 加上這兩行: - 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 * - - ServerName blog.ptt2.cc - DocumentRoot /home/bbs/blog/web - - Options ExecCGI - - - -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/blog/blog.pl b/blog/blog.pl deleted file mode 100755 index 5362f4b5..00000000 --- a/blog/blog.pl +++ /dev/null @@ -1,483 +0,0 @@ -#!/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 = ("\n". - "\n". - "\n"); - $c .= ("\n") - foreach( ['Sunday', 'Sun'], ['Monday', 'Mon'], - ['Tuesday', 'Tue'], ['Wednesday', 'Wed'], - ['Thursday', 'Thu'], ['Friday', 'Fri'], - ['Saturday', 'Sat'] ); - - $week = Day_of_Week($y, $m, 1); - $c .= "\n\n"; - - if( $week == 7 ){ - $week = 0; - } - else{ - $c .= ("\n") - foreach( 1..$week ); - } - foreach( 1..31 ){ - last if( !check_date($y, $m, $_) ); - $c .= "\n" if( $newtr ); - $c .= "\n"; - if( ++$week == 7 ){ - $c .= "\n\n"; - $week = 0; - $newtr = 1; - } - else{ - $newtr = 0; - } - } - - $c .= "\n" if( !$newtr ); - $c .= "
$emonth[$m] $y
[0]\" align=\"center\">". - "$_->[1]
". - " 
"; - - $t = packdate($y, $m, $_); - if( !$article{"$t.title"} ){ - $c .= "$_"; - } - else{ - my $link = $attr{"$fn.loadCalendar"}; - $link =~ s/\[\% key \%\]/$t/g; - $c .= "$_"; - } - - $c .= "
\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 => '批踢踢部落格 ', - 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 "
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 "$ostr";
-}
-
-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/
\n/gs; - } - elsif( /^strict$/i ){ - $c =~ s/\/>/gs; - $c =~ s/\"/"/gs; - $c =~ s/\'/'/gs; -# $c =~ s/ / /gs; - } - elsif( /^ubb$/i ){ - $c =~ s|\[url\](.*?)\[/url\]|$1|gsi; - $c =~ s|\[url=(.*?)\](.*?)\[/url\]|$2|gsi; - $c =~ s|\[email\](.*?)\[/email\]|$1|gsi; - $c =~ s|\[b\](.*?)\[/b\]|$1|gsi; - $c =~ s|\[i\](.*?)\[/i\]|$1|gsi; - $c =~ s|\[img\](.*?)\[/img\]|(null)|gsi; - } - elsif( /^wiki$/i ){ - my $t; - $c =~ s|\[(http://\S+) (.*?)\]| \[$2\] |gi; - $c =~ s|([^\>\"])(http://\S+\.(:?jpg\|gif\|png\|bmp))|$1$2|gsi; - $c =~ s|([^\>\"])(http://\S+)|$1$2|gsi; - $c =~ s|\(\((.*?)\)\)|utf8dump($1, $th{wikibase})|gsie; - $c =~ s|^\-{4,}$|
|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/blog/builddb.pl b/blog/builddb.pl deleted file mode 100755 index 9c805d90..00000000 --- a/blog/builddb.pl +++ /dev/null @@ -1,247 +0,0 @@ -#!/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/$outdir/$fn"; - - if( $c =~ m|(.*?)\n\s*\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/blog/index.pl b/blog/index.pl deleted file mode 100755 index b30ed178..00000000 --- a/blog/index.pl +++ /dev/null @@ -1,17 +0,0 @@ -#!/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/cacheserver/Makefile b/cacheserver/Makefile deleted file mode 100644 index bdff3f08..00000000 --- a/cacheserver/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -# $Id$ -SRCROOT= .. -.include "$(SRCROOT)/pttbbs.mk" - -PROGRAMS= utmpserver utmpsync utmpserver2 utmpserver3 authserver -UTILDIR= $(SRCROOT)/util -UTILOBJ= $(UTILDIR)/util_stuff.o $(UTILDIR)/util_var.o $(UTILDIR)/util_file.o $(UTILDIR)/util_cache.o $(UTILDIR)/util_passwd.o $(UTILDIR)/util_record.o $(UTILDIR)/util_osdep.o $(UTILDIR)/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/cacheserver/README b/cacheserver/README deleted file mode 100644 index 3bfeddbc..00000000 --- a/cacheserver/README +++ /dev/null @@ -1 +0,0 @@ -這是一個測試的東西. 除非確定你知道這個程式在幹什麼, 否則請不理會這個目錄 :P diff --git a/cacheserver/authserver.c b/cacheserver/authserver.c deleted file mode 100644 index 5fbba03a..00000000 --- a/cacheserver/authserver.c +++ /dev/null @@ -1,236 +0,0 @@ -/* $Id$ */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include - -#include - -#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/cacheserver/friend.cpp b/cacheserver/friend.cpp deleted file mode 100644 index e62a7198..00000000 --- a/cacheserver/friend.cpp +++ /dev/null @@ -1,350 +0,0 @@ -#define NDEBUG -#include -#include - -// 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 -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] T* freelist::list[8][KEEP]; -template int freelist::tail[8]={0}; - -template -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(room0); - 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::free(base, room); - base=0; - room=0; - } else { - S origroom=room; - if(room==0) - room=MIN_ROOM; - while(roomsize) room=S(room/2); - if(room!=origroom || base==0) { - //base=(T*)realloc(base, sizeof(T)*room); - T* tmp=freelist::alloc(room); - assert(tmp); - if(n>0) - memcpy(tmp, base, sizeof(T)*n); - if(base!=0) - freelist::free(base, origroom); - base=tmp; - } - assert(base); - } - } -}; - -template -struct RelationList: public myvector { - RelationList() :myvector() {} - void add(Uid me, Uid him) { - RelationList& 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& 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; -}; - -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& backlist(Uid him); -}; -struct Likeby: public Relation { - Likeby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {} - static RelationList& backlist(Uid him); -}; -struct Hate: public Relation { - Hate(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {} - static RelationList& backlist(Uid him); -}; -struct Hateby: public Relation { - Hateby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {} - static RelationList& backlist(Uid him); -}; - - -struct Utmp { - Utmp() { - for(int i=0; i utmplist; - - RelationList like; - RelationList hate; - RelationList likeby; - RelationList 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& Like::backlist(Uid him) { return userlist.users[him].likeby; } -RelationList& Likeby::backlist(Uid him) { return userlist.users[him].like; } -RelationList& Hate::backlist(Uid him) { return userlist.users[him].hateby; } -RelationList& 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 work; - for(int i=0; i0) { - int newn=1; - for(int i=1; i - -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/cacheserver/utmpserver2.c b/cacheserver/utmpserver2.c deleted file mode 100644 index 12c3a299..00000000 --- a/cacheserver/utmpserver2.c +++ /dev/null @@ -1,290 +0,0 @@ -/* $Id$ */ -#include -#include - -#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; i30 || - 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 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/cacheserver/utmpserver3.c b/cacheserver/utmpserver3.c deleted file mode 100644 index 0e1eef02..00000000 --- a/cacheserver/utmpserver3.c +++ /dev/null @@ -1,341 +0,0 @@ -/* $Id$ */ -#include -#include -#include -#include - -#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; i30 || - 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; ievb, 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/cacheserver/utmpsync.c b/cacheserver/utmpsync.c deleted file mode 100644 index 69d1c623..00000000 --- a/cacheserver/utmpsync.c +++ /dev/null @@ -1,27 +0,0 @@ -/* $Id$ */ -#include "bbs.h" -#include - -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/common/bbs/Makefile b/common/bbs/Makefile new file mode 100644 index 00000000..b6eef8e7 --- /dev/null +++ b/common/bbs/Makefile @@ -0,0 +1,24 @@ + +SRCROOT= ../.. +.include "$(SRCROOT)/pttbbs.mk" + +CFLAGS+= -I$(SRCROOT)/include + +OBJS= log.o string.o money.o +TARGET= libbbs.a + + +.SUFFIXES: .c .o +.c.o: + $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c + +all: $(TARGET) + +install: + +$(TARGET): $(OBJS) + $(AR) cru $@ $(OBJS) + ranlib $@ + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/common/bbs/log.c b/common/bbs/log.c new file mode 100644 index 00000000..e69de29b diff --git a/common/bbs/money.c b/common/bbs/money.c new file mode 100644 index 00000000..a6d54127 --- /dev/null +++ b/common/bbs/money.c @@ -0,0 +1,37 @@ +#include + +#include + +/* 計算贈與稅 */ +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; +} + +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]; +} + diff --git a/common/bbs/string.c b/common/bbs/string.c new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/common/bbs/string.c @@ -0,0 +1 @@ + diff --git a/common/sys/Makefile b/common/sys/Makefile new file mode 100644 index 00000000..99cd6143 --- /dev/null +++ b/common/sys/Makefile @@ -0,0 +1,24 @@ + +SRCROOT= ../.. +.include "$(SRCROOT)/pttbbs.mk" + +CFLAGS+= -I$(SRCROOT)/include + +OBJS= file.o lock.o log.o net.o sort.o string.o time.o +TARGET= libbbsutil.a + + +.SUFFIXES: .c .o +.c.o: + $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c + +all: $(TARGET) + +install: + +$(TARGET): $(OBJS) + $(AR) cru $@ $(OBJS) + ranlib $@ + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/common/sys/file.c b/common/sys/file.c new file mode 100644 index 00000000..21313283 --- /dev/null +++ b/common/sys/file.c @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libbbsutil.h" + + +/* ----------------------------------------------------- */ +/* 檔案檢查函數:檔案、目錄、屬於 */ +/* ----------------------------------------------------- */ + +/** + * 傳回 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 的 ctime + * @param fname + */ +time4_t +dashc(const char *fname) +{ + struct stat st; + + if (!stat(fname, &st)) + return st.st_ctime; + 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 +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 + +int copy_file_to_dir(const char *src, const char *dst) +{ + char buf[PATH_MAX]; + char *slash; + if ((slash = rindex(src, '/')) == NULL) + snprintf(buf, sizeof(buf), "%s/%s", dst, src); + else + snprintf(buf, sizeof(buf), "%s/%s", dst, slash); + return copy_file_to_file(src, buf); +} + +int copy_dir_to_dir(const char *src, const char *dst) +{ + DIR *dir; + struct dirent *entry; + struct stat st; + char buf[PATH_MAX], buf2[PATH_MAX]; + + 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, sizeof(buf), "%s/%s", src, entry->d_name); + snprintf(buf2, sizeof(buf2), "%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 +Rename(const char *src, const char *dst) +{ + if (rename(src, dst) == 0) + return 0; + if (!strchr(src, ';') && !strchr(dst, ';')) + { + pid_t pid = fork(); + if (pid == 0) + execl("/bin/mv", "mv", "-f", src, dst, (char *)NULL); + else if (pid > 0) + { + int status = -1; + waitpid(pid, &status, 0); + return WEXITSTATUS(status) == 0 ? 0 : -1; + } + else + return -1; + } + 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;} + // flock(dst, LOCK_SH); + + if(off > 0) + lseek(fi, (off_t)off, SEEK_SET); + + while((bytes=read(fi, buf, sizeof(buf)))>0) + { + write(fo, buf, bytes); + } + // flock(dst, LOCK_UN); + close(fo); + close(fi); + return 0; +} + +/** + * @param src file + * @param dst file + * @return 0 if success + */ +int +Link(const char *src, const char *dst) +{ + if (symlink(src, dst) == 0) + return 0; + + return Copy(src, dst); +} + diff --git a/common/sys/lock.c b/common/sys/lock.c new file mode 100644 index 00000000..4b28bab1 --- /dev/null +++ b/common/sys/lock.c @@ -0,0 +1,23 @@ +#include +#include +#include + +/** + * lock fd + * @param mode F_WRLCK, F_UNLCK + */ +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); +} + diff --git a/common/sys/log.c b/common/sys/log.c new file mode 100644 index 00000000..9fe3d514 --- /dev/null +++ b/common/sys/log.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +#include "libbbsutil.h" + +int +log_filef(const char *fn, int log_flag, const char *fmt,...) +{ + char msg[256]; + + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + return log_file(fn, log_flag, msg); +} + +int +log_file(const char *fn, int log_flag, const char *msg) +{ + int fd; + int flag = O_APPEND | O_WRONLY; + int mode = 0664; + + if (log_flag & LOG_CREAT) + flag |= O_CREAT; + + fd = open(fn, flag, mode); + if( fd < 0 ) + return -1; + + if( write(fd, msg, strlen(msg)) < 0 ){ + close(fd); + return -1; + } + close(fd); + return 0; +} + diff --git a/common/sys/net.c b/common/sys/net.c new file mode 100644 index 00000000..60208a65 --- /dev/null +++ b/common/sys/net.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libbbsutil.h" + +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; +} + +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; +} diff --git a/common/sys/sort.c b/common/sys/sort.c new file mode 100644 index 00000000..edecc0a8 --- /dev/null +++ b/common/sys/sort.c @@ -0,0 +1,10 @@ + +int cmp_int(const void *a, const void *b) +{ + return *(int*)a - *(int*)b; +} + +int cmp_int_desc(const void * a, const void * b) +{ + return cmp_int(b, a); +} diff --git a/common/sys/string.c b/common/sys/string.c new file mode 100644 index 00000000..b2e7612e --- /dev/null +++ b/common/sys/string.c @@ -0,0 +1,345 @@ +#include +#include +#include +#include "fnv_hash.h" + +#include "ansi.h" +#include "libbbsutil.h" + +#define CHAR_LOWER(c) ((c >= 'A' && c <= 'Z') ? c|32 : c) +/* ----------------------------------------------------- */ +/* 字串轉換檢查函數 */ +/* ----------------------------------------------------- */ +/** + * 將字串 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++; + } +} + +int +strip_blank(char *cbuf, char *buf) +{ + for (; *buf; buf++) + if (*buf != ' ') + *cbuf++ = *buf; + *cbuf = 0; + return 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, +}; +/** + * 根據 mode 來 strip 字串 src,並把結果存到 dst + * @param dst + * @param src + * @param mode enum {STRIP_ALL = 0, ONLY_COLOR, NO_RELOAD}; + * STRIP_ALL: 全部吃掉 + * ONLY_COLOR: 吃掉所有跟顏色無關的 (ESC[*m) + * NO_RELOAD: 不 strip (?) + * @return strip 後的長度 + */ +int +strip_ansi(char *dst, const char *src, enum STRIP_FLAG mode) +{ + register int count = 0; +#define isEscapeParam(X) (EscapeFlag[(int)(X)] & 1) +#define isEscapeCommand(X) (EscapeFlag[(int)(X)] & 2) + + for(; *src; ++src) + if( *src != ESC_CHR ){ + if( dst ) + *dst++ = *src; + ++count; + }else{ + const char* p = src + 1; + if( *p != '[' ){ + ++src; + if(*src=='\0') break; + continue; + } + while(isEscapeParam(*++p)); + if( (mode == NO_RELOAD && isEscapeCommand(*p)) || + (mode == ONLY_COLOR && *p == 'm' )){ + register int len = p - src + 1; + if( dst ){ + strncpy(dst, src, len); + dst += len; + } + count += len; + } + src = p; + if(*src=='\0') break; + } + if( dst ) + *dst = 0; + return count; +} + +int +strlen_noansi(const char *s) +{ + // XXX this is almost identical to + // strip_ansi(NULL, s, STRIP_ALL) + register int count = 0, mode = 0; + + if (!s || !*s) + return 0; + + for (; *s; ++s) + { + // 0 - no ansi, 1 - [, 2 - param+cmd + switch (mode) + { + case 0: + if (*s == ESC_CHR) + mode = 1; + else + count ++; + break; + + case 1: + if (*s == '[') + mode = 2; + else + mode = 0; // unknown command + break; + + case 2: + if (isEscapeParam(*s)) + continue; + else if (isEscapeCommand(*s)) + mode = 0; + else + mode = 0; + break; + } + } + return count; +} + +void +strip_nonebig5(unsigned char *str, int maxlen) +{ + int i; + int len=0; + for(i=0;i= 0x80) { + // DBCS lead. + isInDBCS = 1; + } else { + // normal character. + } + } + + if(len) *len = l; + return (oldl != l) ? 1 : 0; +} + +/* ----------------------------------------------------- */ +/* 字串檢查函數:英文、數字、檔名、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 (!isalnum(*p3) && !strchr("@[]-._", *p3)) /* 只允許 alnum 或這些符號 */ + return 1; + p1 = p2 + (*p2 ? 1 : 0); + } + return 0; +} + +/* + * return 1 if /^[0-9]+$/ + * 0 else, 含空字串 + */ +int is_number(const char *p) +{ + if (*p == '\0') + return 0; + + for(; *p; p++) { + if (*p < '0' || '9' < *p) + return 0; + } + return 1; +} + +unsigned +StringHash(const char *s) +{ + return fnv1a_32_strcase(s, FNV1_32_INIT); +} + +/* 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= 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; +} + diff --git a/common/sys/time.c b/common/sys/time.c new file mode 100644 index 00000000..2e0dbdd1 --- /dev/null +++ b/common/sys/time.c @@ -0,0 +1,116 @@ +#include +#include +#include "libbbsutil.h" + +static char cdate_buffer[32]; + +/** + * 閏年 + */ +int is_leap_year(int year) +{ + return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); +} + +/** + * 給日期求星座 + * + * @return 1..12 + */ +int getHoroscope(int m, int d) +{ + if (m > 12 || m < 1) + return 1; + + // 摩羯 水瓶 雙魚 牡羊 金牛 雙子 巨蟹 獅子 處女 天秤 天蠍 射手 + 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; +} + +/** + * 23+1 bytes, "12/31/2007 00:00:00 Mon\0" + */ +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; +} + +/** + * 19+1 bytes, "12/31/2007 00:00:00\0" + */ +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; +} + +/** + * 10+1 bytes, "12/31/2007\0" + */ +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; +} + +#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 + +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/console/Makefile b/console/Makefile new file mode 100644 index 00000000..a33301c6 --- /dev/null +++ b/console/Makefile @@ -0,0 +1,136 @@ +# $Id$ + +SRCROOT= .. +.include "$(SRCROOT)/pttbbs.mk" + +####################################################################### +# common modules +####################################################################### + +PROG= mbbsd +CHESSOBJS= chc.o chc_tab.o chess.o go.o gomo.o dark.o reversi.o +GAMEOBJS = card.o guess.o chicken.o othello.o +SYSOBJS = args.o crypt.o osdep.o +COREOBJS = bbs.o announce.o read.o board.o cache.o brc.o mail.o record.o fav.o +ACCOBJS = user.o register.o passwd.o +TALKOBJS = talk.o chat.o friend.o +NETOBJS = mbbsd.o io.o term.o +UTILOBJS = stuff.o file.o kaede.o convert.o name.o +PLUGOBJS = lovepaper.o calendar.o topsong.o vice.o +OBJS= admin.o assess.o cal.o edit.o menu.o more.o gamble.o \ + xyz.o syspost.o vote.o var.o voteboard.o \ + pmore.o telnet.o \ + $(COREOBJS) $(ACCOBJS) $(NETOBJS) $(TALKOBJS) $(UTILOBJS) \ + $(SYSOBJS) $(PLUGOBJS) $(CHESSOBJS) $(GAMEOBJS) + +####################################################################### +# conditional configurations and optional modules +####################################################################### + +.if !defined(WITHOUT_BLOG) && defined(WITH_BLOG) +CFLAGS+= -DBLOG +LDFLAGS+= -L/usr/local/lib/mysql +LIBS+= -lmysqlclient +.endif + +.if !defined(WITHOUT_LOG_CRAWLER) && defined(WITH_LOG_CRAWLER) +CFLAGS+= -DLOG_CRAWLER +.endif + +.if !defined(WITHOUT_EMAILDB) && defined(WITH_EMAILDB) +OBJS+= emaildb.o +CFLAGS+= -DUSE_EMAILDB +LIBS+= -lsqlite3 +.endif + +.if !defined(WITHOUT_BBSLUA_USAGE) && defined(WITH_BBSLUA_USAGE) +CFLAGS+= -DBBSLUA_USAGE +.endif + +.if !defined(WITHOUT_BBSLUA) && defined(WITH_BBSLUA) +OBJS+= bbslua.o bbsluaext.o +CFLAGS+= -DUSE_BBSLUA +# MODIFY THESE ENVIRONMENT SETTINGS TO FIT YOUR CONFIGURATION +CFLAGS+= -I/usr/include/lua5.1 +CFLAGS_FreeBSD += -I/usr/local/include/lua51 +LDFLAGS_FreeBSD+= -L/usr/local/lib/lua51 +# modify the lib name below to fit your configuration +# usually you'd try "-llua" instead of "-llua5.1". +LIBS+= -llua5.1 -lm +#LIBS+= -llua -lm +.endif + +.if !defined(WITHOUT_PFTERM) && defined(WITH_PFTERM) +OBJS+= pfterm.o +CFLAGS+= -DUSE_PFTERM +#CFLAGS+= -DDBG_OUTRPT +.else +OBJS+= screen.o +.endif + +####################################################################### +# special library (DIET) configuration +####################################################################### + +.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 +LIBS+= $(SRCROOT)/src/libbbsutil/libbbsutil.a \ + $(SRCROOT)/src/libbbs/libbbs.a + +####################################################################### +# Make Rules +####################################################################### + +.SUFFIXES: .c .o +.c.o: $(SRCROOT)/include/var.h + $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) + +$(PROG): $(OBJS) + sh $(SRCROOT)/util/newvers.sh + $(DIETCC) $(CC) $(LDFLAGS) -o $(PROG) $(OBJS) $(LIBS) $(EXT_LIBS) vers.c + +$(SRCROOT)/include/var.h: var.c + perl $(SRCROOT)/util/parsevar.pl < var.c > $(SRCROOT)/include/var.h + +$(SRCROOT)/include/banip.h: $(SRCROOT)/util/banip.pl + perl $(SRCROOT)/util/banip.pl > $@ + +mbbsd.o: mbbsd.c $(SRCROOT)/include/var.h $(SRCROOT)/include/banip.h + $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $< + +initemaildb: emaildb.c + $(CC) -DINIT_MAIN $(CFLAGS) $(LDFLAGS) -o initemaildb emaildb.c $(LIBS) + +ctags: + ctags *.c ../include/*.h ../src/libbbs/*.c ../src/libbbsutil/*.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/console/admin.c b/console/admin.c new file mode 100644 index 00000000..d0efa3e1 --- /dev/null +++ b/console/admin.c @@ -0,0 +1,1172 @@ +/* $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 unum = 0; + FILE *fp1 = fopen(passwdfile, "r"); + char friendfile[128]="", key[22], *keymatch; + int keytype = 0; + char isCurrentPwd = 0; + + isCurrentPwd = (strcmp(passwdfile, FN_PASSWD) == 0) ? 1 : 0; + assert(fp1); + clear(); + if (!mode) + { + getdata(0, 0, "請輸入id :", key, sizeof(key), DOECHO); + } else { + // improved search + getdata(0, 0, "搜尋哪種欄位?" + "([0]全部 1.ID 2.姓名 3.暱稱 4.地址 5.email 6.IP 7.認證/電話) ", + key, 2, DOECHO); + if (isascii(key[0]) && isdigit(key[0])) + keytype = key[0] - '0'; + if (keytype < 0 || keytype > 7) + keytype = 0; + getdata(0, 0, "請輸入關鍵字: ", key, sizeof(key), DOECHO); + } + + if(!key[0]) { + fclose(fp1); + return 0; + } + while ((fread(&user, sizeof(user), 1, fp1)) > 0 && unum++ < MAX_USERS) { + + // skip empty records + if (!user.userid[0]) + continue; + + if (!(unum & 0xFF)) { + move(1, 0); + prints("第 [%d] 筆資料\n", unum); + refresh(); + } + + keymatch = NULL; + + if (!mode) + { + // only verify id + if (!strcasecmp(user.userid, key)) + keymatch = user.userid; + } else { + // search by keytype + if ((!keytype || keytype == 1) && + strcasestr(user.userid, key)) + keymatch = user.userid; + else if ((!keytype || keytype == 2) && + strcasestr(user.realname, key)) + keymatch = user.realname; + else if ((!keytype || keytype == 3) && + strcasestr(user.nickname, key)) + keymatch = user.nickname; + else if ((!keytype || keytype == 4) && + strcasestr(user.address, key)) + keymatch = user.address; + else if ((!keytype || keytype == 5) && + strcasestr(user.email, key)) + keymatch = user.email; + else if ((!keytype || keytype == 6) && + strcasestr(user.lasthost, key)) + keymatch = user.lasthost; + else if ((!keytype || keytype == 7) && + strcasestr(user.justify, key)) + keymatch = user.justify; + } + + if(keymatch) { + move(1, 0); + prints("第 [%d] 筆資料\n", unum); + refresh(); + + user_display(&user, 1); + // user_display does not have linefeed in tail. + + if (isCurrentPwd && HasUserPerm(PERM_ACCOUNTS)) + uinfo_query(&user, 1, unum); + else + outs("\n"); + + 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-1brdname); +} + +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/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(GLOBAL_SECURITY, bh.title, "請注意刪除的合法性", "[系統安全局]"); + assert(0<=bid-1 && bid-1 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 %s\n" + "板主: %s => %s\n", + bh.brdname, newbh.brdname, bh.BM, newbh.BM); + post_msg(GLOBAL_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 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-1number, 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人:%d"MONEYNAME"幣)", 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人:%d"MONEYNAME"幣)", count, total_money); + strcat(reason, buf); + + } + + fclose(fp2); + + sprintf(buf, "%s 紅包機: %s", cuser.userid, reason); + post_file(GLOBAL_SECURITY, buf, "etc/givemoney.log", "[紅包機報告]"); + pressanykey(); + return FULLUPDATE; +} diff --git a/console/aids.c b/console/aids.c new file mode 100644 index 00000000..56d19378 --- /dev/null +++ b/console/aids.c @@ -0,0 +1,290 @@ +/* $Id$ */ +#include "bbs.h" + +#error "Not complete yet" + +aidu_t fn2aidu(char *fn) +{ + aidu_t aidu = 0; + aidu_t type = 0; + aidu_t v1 = 0; + aidu_t v2 = 0; + char *sp = fn; + + if(fn == NULL) + return 0; + + switch(*(sp ++)) + { + case 'M': + type = 0; + break; + case 'G': + type = 1; + break; + default: + return 0; + break; + } + + if(*(sp ++) != '.') + return 0; + v1 = strtoul(sp, &sp, 10); + if(sp == NULL) + return 0; + if(*sp != '.' || *(sp + 1) != 'A') + return 0; + sp += 2; + if(*(sp ++) == '.') + { + v2 = strtoul(sp, &sp, 16); + if(sp == NULL) + return 0; + } + aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffff) << 12) | (v2 & 0xfff); + + return aidu; +} + +/* IMPORTANT: + * size of buf must be at least 8+1 bytes + */ +char *aidu2aidc(char *buf, aidu_t aidu) +{ + const char aidu2aidc_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"; + const int aidu2aidc_tablesize = sizeof(aidu2aidc_table) - 1; + char *sp = buf + 8; + aidu_t v; + + *(sp --) = '\0'; + while(sp >= buf) + { + /* FIXME: 能保證 aidu2aidc_tablesize 是 2 的冪次的話, + 這裡可以改用 bitwise operation 做 */ + v = aidu % aidu2aidc_tablesize; + aidu = aidu / aidu2aidc_tablesize; + *(sp --) = aidu2aidc_table[v]; + } + return buf; +} + +/* IMPORTANT: + * size of fn must be at least FNLEN bytes + */ +char *aidu2fn(char *fn, aidu_t aidu) +{ + aidu_t type = ((aidu >> 44) & 0xf); + aidu_t v1 = ((aidu >> 12) & 0xffffffff); + aidu_t v2 = (aidu & 0xfff); + + if(fn == NULL) + return NULL; + snprintf(fn, FNLEN, "%c.%d.A.%03X", ((type == 0) ? 'M' : 'G'), (unsigned int)v1, (unsigned int)v2); + return fn; +} + +aidu_t aidc2aidu(char *aidc) +{ + char *sp = aidc; + aidu_t aidu = 0; + + if(aidc == NULL) + return 0; + + while(*sp != '\0' && /* ignore trailing spaces */ *sp != ' ') + { + aidu_t v = 0; + /* FIXME: 查表法會不會比較快? */ + if(*sp >= '0' && *sp <= '9') + v = *sp - '0'; + else if(*sp >= 'A' && *sp <= 'Z') + v = *sp - 'A' + 10; + else if(*sp >= 'a' && *sp <= 'z') + v = *sp - 'a' + 36; + else if(*sp == '-') + v = 62; + else if(*sp == '_') + v = 63; + else if(*sp == '@') + break; + else + return 0; + aidu <<= 6; + aidu |= (v & 0x3f); + sp ++; + } + + return aidu; +} + +int search_aidu_in_bfile(char *bfile, aidu_t aidu) +{ + char fn[FNLEN]; + int fd; + fileheader_t fhs[64]; + int len, i; + int pos = 0; + int found = 0; + int lastpos = 0; + + if(aidu2fn(fn, aidu) == NULL) + return -1; + if((fd = open(bfile, O_RDONLY, 0)) < 0) + return -1; + + while(!found && (len = read(fd, fhs, sizeof(fhs))) > 0) + { + len /= sizeof(fileheader_t); + for(i = 0; i < len; i ++) + { + int l; + if(strcmp(fhs[i].filename, fn) == 0 || + ((aidu & 0xfff) == 0 && (l = strlen(fhs[i].filename)) > 6 && + strncmp(fhs[i].filename, fn, l) == 0)) + { + if(fhs[i].filemode & FILE_BOTTOM) + { + lastpos = pos; + } + else + { + found = 1; + break; + } + } + pos ++; + } + } + close(fd); + + return (found ? pos : (lastpos ? lastpos : -1)); +} + +SearchAIDResult_t search_aidu_in_board(char *bname, aidu_t aidu) +{ + SearchAIDResult_t r = {AIDR_BOARD, -1}: + int n = -1; + char dirfile[PATHLEN]; + + { + char bf[FNLEN]; + + snprintf(bf, FNLEN, "%s.bottom", FN_DIR); + setbfile(dirfile, bname, bf); + if((n = search_aidu_in_bfile(dirfile, aidu)) >= 0) + { + r.where = AIDR_BOTTOM; + r.n = n; + } + } + if(r.n < 0) + { + setbfile(dirfile, bname, FN_DIR); + if((n = search_aidu_in_bfile(dirfile, aidu)) >= 0) + { + r.where = AIDR_BOARD; + r.n = n; + } + } + if(r.n < 0) + { + setbfile(dirfile, bname, fn_mandex); + if((n = search_aidu_in_bfile(dirfile, aidu)) >= 0) + { + r.where = AIDR_DIGEST; + r.n = n; + } + } + return r; +} + +SearchAIDResult_t do_search_aid(void) +{ + SearchAIDResult_t r = {AIDR_BOARD, -1}; + char aidc[100]; + char bname[IDLEN + 1] = ""; + aidu_t aidu = 0; + char *sp; + char *sp2; + char *emsg = NULL; + + if(!getdata(b_lines, 0, "搜尋" AID_DISPLAYNAME ": #", aidc, 15 + IDLEN, LCECHO)) + { + move(b_lines, 0); + clrtorol(); + r.n = -1; + return r; + } + + if(currstat == RMAIL) + { + move(21, 0); + clrtobot(); + move(22, 0); + prints("此狀態下無法搜尋" AID_DISPLAYNAME); + pressanykey(); + r.n = -1; + return r; + } + + sp = aidc; + while(*sp == ' ') + sp ++; + while(*sp == '#') + sp ++; + aidu = aidc2aidu(sp); + if((sp2 = strchr(sp, '@')) != NULL) + { + // assert(sizeof(bname) > IDLEN); + strlcpy(bname, sp2 + 1, IDLEN+1); + *sp2 = '\0'; + } + else + bname[0] = '\0'; + + if(aidu > 0) + { + if(bname[0] != '\0') + { + if(!HasBoardPerm_bn(bname)) + return FULLUPDATE; + r = search_aidu_in_board(bname, aidu); + if(r.n >= 0) + { + if(enter_board(bname) < 0) + { + r.n = -1; + emsg = "錯誤:無法進入指定的看板 %s"; + } + } + } + else + { + r = search_aidu_in_board(currboard, aidu); + } + } + + if(r.n < 0) + { + if(aidu == 0) + emsg = "不合法的" AID_DISPLAYNAME ",請確定輸入是正確的"; + else if(emsg == NULL) + { + if(bname[0] != '\0') + emsg = "看板 %s 內找不到這個" AID_DISPLAYNAME ",可能是文章已經消失,或是找錯看板了"; + else + emsg = "找不到這個" AID_DISPLAYNAME ",可能是文章已經消失,或是找錯看板了"; + } + move(21, 0); + clrtoeol(); + move(22, 0); + prints(emsg, bname); + pressanykey(); + r.n = -1; + return r; + } + else + { + return r; + } +} diff --git a/console/alloc.c b/console/alloc.c new file mode 100644 index 00000000..de676ce4 --- /dev/null +++ b/console/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 +#include +#include + +#include +#include +#include +#include +#include + +#include /* 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<>__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;inext=(((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 (sizesize=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 (sizesize!=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/console/announce.c b/console/announce.c new file mode 100644 index 00000000..fb7db427 --- /dev/null +++ b/console/announce.c @@ -0,0 +1,1558 @@ +/* $Id$ */ +#include "bbs.h" + +// XXX piaip 2007/12/29 +// 最近發現很多 code 都死在 announce +// 因為進來要看 lastlevel 而非 currbid +// user 可能一進 BBS 直殺郵件->mail_cite->進精華區 +// 於是就爆炸 +// 同理 currboard 也不該用 +// 請改用 me.bid (注意 me.bid 可能為 0, 表示進來的非看板。) +// +// XXX 9999 麻煩想個方式改掉 + +// for max file size limitation here, see edit.c +#define MAX_FILE_SIZE (32768*1024) + +/* 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 int +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 + + // if len < 0, the directory is not valid anymore. + if (len < 0) + return 0; + + if (len < pm->header_size) + bzero(&pm->header[len], FHSZ * (pm->header_size - len)); + return 1; +} + +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 int +a_showmenu(menu_t * pm) +{ + char *title, *editor; + int n; + fileheader_t *item; + time4_t dtime; + + showtitle("精華文章", pm->mtitle); + prints(" " ANSI_COLOR(1;36) "編號 標 題%56s" ANSI_COLOR(0), + "編 選 日 期"); + + if (!pm->num) + { + outs("\n 《精華區》尚在吸取天地間的日月精華中... :)"); + } + else + { + char buf[PATHLEN]; + + // determine if path is valid. + if (!a_loadname(pm)) + return 0; + + 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); + } + } + + 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); + } + return 1; +} + +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; + + if (!a_loadname(pm)) + return pm->now; + } + 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 + +static void +a_newitem(menu_t * pm, int mode) +{ + char *mesg[3] = { + "[新增文章] 請輸入標題:", /* ADDITEM */ + "[新增目錄] 請輸入標題:", /* ADDGROUP */ + }; + + char fpath[PATHLEN]; + fileheader_t item; + + strlcpy(fpath, pm->path, sizeof(fpath)); + if (strlen(pm->path) + FNLEN*2 >= PATHLEN) + return; + + 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; + } + + 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: + { + int edflags = 0; +# ifdef GLOBAL_BBSMOVIE + if (pm && pm->bid && + strcmp(getbcache(pm->bid)->brdname, + GLOBAL_BBSMOVIE) == 0) + { + edflags |= EDITFLAG_UPLOAD; + edflags |= EDITFLAG_ALLOWLARGE; + } +# endif // GLOBAL_BBSMOVIE + if (vedit2(fpath, 0, NULL, edflags) == -1) { + unlink(fpath); + pressanykey(); + return; + } + } + break; + case ADDGROUP: + // do nothing + 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(); + off_t sz; + + if (!dashf(cq->copyfile)) { + vmsg("目錄不得附加於檔案後!"); + return; + } + + snprintf(fname, sizeof(fname), "%s/%s", pm->path, + pm->header[pm->now - pm->page].filename); + + if (!dashf(fname)) { + vmsg("檔案不得附加於此!"); + return; + } + + sz = dashs(fname); + if (sz >= MAX_FILE_SIZE) + { + vmsg("檔案已超過最大限制,無法再附加"); + return; + } + + 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' || !(fp = fopen(fname, "a+"))) + return; + + if (!(fin = fopen(cq->copyfile, "r"))) { + fclose(fp); + return; + } + + 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); + fclose(fp); + cq->copyfile[0] = '\0'; + } +} + +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[PATHLEN], buf[PATHLEN]; + + if (TagBoard == 0){ + sethomedir(dirname, cuser.userid); + } + else{ + bh = getbcache(TagBoard); + setbdir(dirname, bh->brdname); + } + tagnum = TagNum; + + // prevent if anything wrong + if (tagnum > MAXTAGS || tagnum < 0) + { + vmsg("內部錯誤。請把你剛剛進行的完整步驟貼到 " + GLOBAL_BUGREPORT " 板。"); + return ans; + } + + if (tagnum < 1) + return ans; + + /* since we use different tag features, + * copyqueue is not required/used. */ + copyqueue_reset(); + + while (tagnum-- > 0) { + memset(&fhdr, 0, sizeof(fhdr)); + EnumTagFhdr(&fhdr, dirname, ent++); + + // XXX many process crashed here as fhdr.filename[0] == 0 + // let's workaround it? or trace? + // if (!fhdr->filename[0]) + // continue; + + if (!fhdr.filename[0]) + { + grayout(0, b_lines-2, GRAYOUT_DARK); + move(b_lines-1, 0); clrtobot(); + prints("第 %d 項處理發生錯誤。 請把你剛剛進行的完整步驟貼到 " + GLOBAL_BUGREPORT " 板。\n", ent); + vmsg("忽略錯誤並繼續進行。"); + continue; + } + + 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/" FN_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/" FN_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]; + /* board friend only effact when in board reading mode */ + if (me->level >= MANAGER) + return 1; + if (fhdr->filemode & FILE_BM) + return 0; + if (fhdr->filemode & FILE_HIDE) + { + if (currstat == ANNOUNCE || + !is_hidden_board_friend(me->bid, currutmp->uid)) + return 0; + } + return 1; +} +int +a_menu(const char *maintitle, const char *path, + int lastlevel, int lastbid, + char *trans_buffer) +{ + static char Fexit; // 用來跳出 recursion + menu_t me; + char fname[PATHLEN]; + int ch, returnvalue = FULLUPDATE; + + // prevent deep resursive directories + if (strlen(path) + FNLEN >= PATHLEN) + { + // it is not save to enter such directory. + return returnvalue; + } + + if(trans_buffer) + trans_buffer[0] = '\0'; + + memset(&me, 0, sizeof(me)); + 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); + me.bid = lastbid; + + /* 精華區-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)); + if (!a_showmenu(&me)) + { + // some directories are invalid, restart! + Fexit = 1; + break; + } + } + 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) { + int edflags = 0; + *quote_file = 0; + +# ifdef GLOBAL_BBSMOVIE + if (me.bid && strcmp(getbcache(me.bid)->brdname, + GLOBAL_BBSMOVIE) == 0) + { + edflags |= EDITFLAG_UPLOAD; + edflags |= EDITFLAG_ALLOWLARGE; + } +# endif // GLOBAL_BBSMOVIE + + if (vedit2(fname, NA, NULL, edflags) != -1) { + char fpath[PATHLEN]; + fileheader_t fhdr; + strlcpy(fpath, path, sizeof(fpath)); + stampfile(fpath, &fhdr); + unlink(fpath); + strlcpy(fhdr.filename, + me.header[me.now - me.page].filename, + sizeof(fhdr.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) && + (me.bid==0 || !is_BM_cache(me.bid)) && 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_filef(FN_USSONG, LOG_CREAT, "%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, me.bid, 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; + + } + + if (me.level >= MANAGER) { + 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; +#ifdef BLOG + case 'b': + if (me.bid) + { + char genbuf[128]; + char *bname = getbcache(me.bid)->brdname; + snprintf(genbuf, sizeof(genbuf), + "bin/builddb.pl -f -n %d %s", me.now, bname); + system(genbuf); + vmsg("資料更新完成"); + } + me.page = 9999; + break; + + case 'B': + if (me.bid && me.bid == currbid) + { + BlogMain(me.now); + }; + me.page = 9999; + break; +#endif + } + + 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 '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), + 0, + NULL); + return 0; +} + +#ifdef BLOG +#include +void BlogMain(int num) +{ + int oldmode = currutmp->mode; + char genbuf[128], exit = 0; + + // WARNING: 要確認 currboard/currbid 已正確設定才能用此API。 + + //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, "/" FN_DIR); + append_record(adir, &item, FHSZ); + + snprintf(buf, sizeof(buf), + "cp -R etc/Blog.Default/" FN_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/console/args.c b/console/args.c new file mode 100644 index 00000000..bbd6be21 --- /dev/null +++ b/console/args.c @@ -0,0 +1,73 @@ +/* $Id$ */ +#include "bbs.h" +#ifdef HAVE_SETPROCTITLE + +void +initsetproctitle(int argc, char **argv, char **envp) +{ +} + +#else + + +static char **Argv = NULL; /* pointer to argument vector */ +static int argv_size; /* 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) + argv_size = envp[i - 1] + strlen(envp[i - 1]) - Argv[0]; + else + argv_size = argv[argc - 1] + strlen(argv[argc - 1]) - Argv[0]; +} + +static void +do_setproctitle(const char *cmdline) +{ + int len; + + len = strlen(cmdline) + 1; // +1 for '\0' + if(len > argv_size - 2) // 2 ?? + len = argv_size - 2; + memset(Argv[0], 0, argv_size); + 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/console/assess.c b/console/assess.c new file mode 100644 index 00000000..4d63a8d8 --- /dev/null +++ b/console/assess.c @@ -0,0 +1,268 @@ +/* $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 + +// how long is AID? see read.c... +#ifndef AIDC_LEN +#define AIDC_LEN (20) +#endif // AIDC_LEN + +// #define MAXGP (100) +#define MAXGP (SALE_MAXVALUE) + +int +u_fixgoodpost(void) +{ + char endinput = 0; + unsigned int newgp = 0; + int bid; + char bname[IDLEN+1]; + char xaidc[AIDC_LEN+1]; + + aidu_t gpaids[MAXGP+1]; + int gpbids[MAXGP+1]; + int cgps = 0; + + clear(); + stand_title("自動優文修正程式"); + + outs("開始修正優文之前,有些功\課要麻煩您先查好:\n\n" + "請先找到你所有的優文文章的看板與" AID_DISPLAYNAME "\n" + AID_DISPLAYNAME "的查詢方法是在該篇文章前面按下大寫 Q 。\n" + "查好後請把這些資料放在手邊,等下會請您輸入。\n" + "另外,若有多重登入請先關閉其它連線。\n" + "\n"); + outs("如果一切都準備好了,請按下 y 開始,或其它任意鍵跳出。\n\n"); + if (getans("優文的資料都查好了嗎?") != 'y') + { + vmsg("跳出修正程式。"); + return 0; + } + while (!endinput && newgp < MAXGP) + { + int y, x = 0; + boardheader_t *bh = NULL; + + move(1, 0); clrtobot(); + outs("請依序輸入優文資訊,全部完成後按 ENTER 即可停止。\n"); + + move(b_lines-2, 0); clrtobot(); + prints("目前已確認優文數目: %d" ANSI_RESET "\n\n", newgp); + + if (!getdata(5, 0, "請輸入優文文章所在看板名稱: ", + bname, sizeof(bname), DOECHO)) + { + move(5, 0); + if (getans(ANSI_COLOR(1;33)"確定全部輸入完成了嗎? " + ANSI_RESET "[y/N]: ") != 'y') + continue; + endinput = 1; break; + } + move (6, 0); + outs("確認看板... "); + if (bname[0] == '\0' || !(bid = getbnum(bname))) + { + outs(ANSI_COLOR(1;31) "看板不存在!"); + vmsg("請重新輸入。"); + continue; + } + assert(0<=bid-1 && bid-1brdname, sizeof(bname)); + prints("已找到看板 --> %s\n", bname); + getyx(&y, &x); + + // loop AID query + while (newgp < MAXGP) + { + int n; + int fd; + char dirfile[PATHLEN]; + char *sp; + aidu_t aidu = 0; + fileheader_t fh; + + move(y, 0); clrtobot(); + move(b_lines-2, 0); clrtobot(); + prints("目前已確認優文數目: %d" ANSI_RESET "\n\n", newgp); + + if (getdata(y, 0, "請輸入" AID_DISPLAYNAME ": #", + xaidc, AIDC_LEN, DOECHO) == 0) + break; + + sp = xaidc; + while(*sp == ' ') sp ++; + if(*sp == '#') sp ++; + + if((aidu = aidc2aidu(sp)) <= 0) + { + outs(ANSI_COLOR(1;31) AID_DISPLAYNAME "格式不正確!"); + vmsg("請重新輸入。"); + continue; + } + + // check repeated input of same board+AID. + for (n = 0; n < cgps; n++) + { + if (gpaids[n] == aidu && gpbids[n] == bid) + { + vmsg("您已輸入過此優文了,請重新輸入。"); + aidu = 0; + break; + } + } + + if (aidu <= 0) + continue; + + // find aidu in board + n = -1; + // see read.c, search .bottom first. + if (n < 0) + { + outs("搜尋置底文章..."); + setbfile(dirfile, bname, FN_DIR ".bottom"); + n = search_aidu(dirfile, aidu); + } + if (n < 0) { + // search board + outs("未找到。\n搜尋看板文章.."); + setbfile(dirfile, bname, FN_DIR); + n = search_aidu(dirfile, aidu); + } + if (n < 0) + { + // search digest + outs("未找到。\n搜尋文摘.."); + setbfile(dirfile, currboard, fn_mandex); + n = search_aidu(dirfile, aidu); + } + if (n < 0) + { + // search failed... + outs("未找到\n" ANSI_COLOR(1;31) "找不到文章!"); + vmsg("請確認後重新輸入。"); + continue; + } + + // found something + fd = open(dirfile, O_RDONLY); + if (fd < 0) + { + outs(ANSI_COLOR(1;31) "系統錯誤。 請稍候再重試。\n"); + vmsg("若持續發生請至" GLOBAL_BUGREPORT "報告。"); + continue; + } + + lseek(fd, n*sizeof(fileheader_t), SEEK_SET); + memset(&fh, 0, sizeof(fh)); + read(fd, &fh, sizeof(fh)); + outs("\n開始核對資料...\n"); + n = 1; + if (strcmp(fh.owner, cuser.userid) != 0) + n = 0; + prints("作者: %s (%s)\n", fh.owner, n ? "正確" : + ANSI_COLOR(1;31) "錯誤" ANSI_RESET); + if (!(fh.filemode & FILE_MARKED)) + n = 0; + prints("M文: %s\n", (fh.filemode & FILE_MARKED) ? "正確" : + ANSI_COLOR(1;31) "錯誤" ANSI_RESET); + prints("推薦: %d\n", fh.recommend); + close(fd); + if (!n) + { + vmsg("輸入的文章並非優文,請重新輸入。"); + continue; + } + n = fh.recommend / 10; + prints("計算優文數值: %+d\n", n); + + if (n > 0) + { + // log new data + newgp += n; + gpaids[cgps] = aidu; + gpbids[cgps] = bid; + cgps ++; + } + + clrtobot(); + + + vmsg("優文已確認。若要輸入其它看板文章請在AID欄空白按 ENTER"); + } + vmsgf("%s 看板輸入完成。", bname); + } + if (newgp > MAXGP) + newgp = MAXGP; + if (newgp <= cuser.goodpost) + { + vmsg("確認優文數目未高於已有優文數,不調整。"); + } else { + log_filef("log/fixgoodpost.log", LOG_CREAT, + "%s %s 自動修正優文數: 由 %d 變為 %d\n", Cdate(&now), cuser.userid, + cuser.goodpost, newgp); + cuser.goodpost = newgp; + // update passwd file here? + passwd_force_update(ALERT_PWD_GOODPOST); + passwd_update(usernum, &cuser); + vmsgf("更新優文數目為%d。", newgp); + } + + return 0; +} + +#endif diff --git a/console/bbs.c b/console/bbs.c new file mode 100644 index 00000000..83cdb5c0 --- /dev/null +++ b/console/bbs.c @@ -0,0 +1,3986 @@ +/* $Id$ */ +#include "bbs.h" + +#ifdef EDITPOST_SMARTMERGE + +#include "fnv_hash.h" +#define SMHASHLEN (64/8) + +#endif // EDITPOST_SMARTMERGE + +#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); +static int view_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln); + +#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, FN_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; +} + +// lite weight version to update dir files +static int +modify_dir_lite( + const char *direct, int ent, const char *fhdr_name, + time4_t modified, const char *title, char recommend) +{ + // since we want to do 'modification'... + int fd; + off_t sz = dashs(direct); + fileheader_t fhdr; + + // TODO lock? + // PttLock(fd, offset, size, F_WRLCK); + // write(fd, rptr, size); + // PttLock(fd, offset, size, F_UNLCK); + + // prevent black holes + if (sz < sizeof(fileheader_t) * (ent) || + (fd = open(direct, O_RDWR)) < 0 ) + return -1; + + // also check if fhdr->filename is same. + // let sz = base offset + sz = (sizeof(fileheader_t) * (ent-1)); + if (lseek(fd, sz, SEEK_SET) < 0 || + read(fd, &fhdr, sizeof(fhdr)) != sizeof(fhdr) || + strcmp(fhdr.filename, fhdr_name) != 0) + { + close(fd); + return -1; + } + + // update records + if (modified > 0) + fhdr.modified = modified; + + if (title && *title) + strcpy(fhdr.title, title); + + if (recommend) + { + recommend += fhdr.recommend; + if (recommend > MAX_RECOMMENDS) recommend = MAX_RECOMMENDS; + else if (recommend < -MAX_RECOMMENDS) recommend = -MAX_RECOMMENDS; + fhdr.recommend = recommend; + } + + if (lseek(fd, sz, SEEK_SET) >= 0) + write(fd, &fhdr, sizeof(fhdr)); + + close(fd); + + return 0; +} + +static void +check_locked(fileheader_t *fhdr) +{ + boardheader_t *bp = NULL; + + if (currstat == RMAIL) + return; + if (!currboard[0] || currbid <= 0) + return; + bp = getbcache(currbid); + if (!bp) + return; + if (!(fhdr->filemode & FILE_SOLVED)) + return; + if (!(fhdr->filemode & FILE_MARKED)) + return; + syncnow(); + bp->SRexpire = now; +} + +/* 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_filef("etc/illegal_money", LOG_CREAT, + 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, BBSMNAME "系統警察", + "Cross-post", "罰單處份"); + cuser.userlevel |= PERM_VIOLATELAW; + cuser.timeviolatelaw = now; + cuser.vl_count++; + mail_id(cuser.userid, "Cross-Post罰單", + "etc/crosspost.txt", BBSMNAME "警察部隊"); + 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); + prints("這是你第 %d 次違法 必須繳出 %d $Ptt\n\n", + cuser.vl_count, cuser.vl_count * 1000); + 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] != 'y') + { + move(15, 0); + outs( ANSI_COLOR(1;31) "不想付錢嗎? 還是不知道要按 y ?\n" + "請養成看清楚系統訊息的好習慣。\n" + "等你想通了再來吧!! 我相信你不會知錯不改的~~~" ANSI_RESET); + pressanykey(); + return 0; + } + + //Ptt:check one more time + reload_money(); + if (cuser.money < (int)cuser.vl_count * 1000) + { + log_filef("log/violation", LOG_CREAT, + "%24.24s %s pay-violation error: race-conditionn hack?\n", + ctime4(&now), cuser.userid); + vmsg("錢怎麼忽然不夠了? 試圖欺騙系統被查到將砍帳號!"); + return 0; + } + + demoney(-1000 * cuser.vl_count); + cuser.userlevel &= (~PERM_VIOLATELAW); + // force overriding alerts + if(currutmp) + currutmp->alerts &= ~ALERT_PWD_PERM; + passwd_update(usernum, &cuser); + sendalert(cuser.userid, ALERT_PWD_PERM); + log_filef("log/violation", LOG_CREAT, + "%24.24s %s pay-violation: $%d complete.\n", + ctime4(&now), cuser.userid, (int)cuser.vl_count*1000); + + vmsg("罰單已付,請盡速重新登入。"); + 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) && + !is_hidden_board_friend((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; + } +} + +int IsFreeBoardName(const char *brdname) +{ + if (strcmp(currboard, GLOBAL_TEST) == 0) + return 1; + if (strcmp(currboard, ALLPOST) == 0) + return 1; + return 0; +} + +/* 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_DIGEST) + return 0; + + 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-1perm_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); +} + +int CheckPostRestriction(int bid) +{ + boardheader_t *bp; + if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) + return 1; + assert(0<=bid-1 && bid-1 (now - (time4_t)bp->post_limit_regtime * 2592000)) + return 0; + if (cuser.numlogins / 10 < (unsigned int)bp->post_limit_logins) + return 0; + if (cuser.numposts / 10 < (unsigned int)bp->post_limit_posts) + return 0; + if (cuser.badpost > (255 - (unsigned int)bp->post_limit_badpost)) + return 0; + + return 1; +} + +static void +readtitle(void) +{ + boardheader_t *bp; + char *brd_title; + + assert(0<=currbid-1 && currbid-1bvote != 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-1bcache[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]; + char *typeattr = ""; + char isunread = 0, oisunread = 0; + + oisunread = isunread = + brc_unread(currbid, ent->filename, ent->modified); + + // modified tag + if (isunread == 2) + { + // ignore unread, if user doesn't want to show it. + if (cuser.uflag & NO_MODMARK_FLAG) + { + oisunread = isunread = 0; + } + // if user wants colored marks, use 'read' marks + else if (cuser.uflag & COLORED_MODMARK) + { + isunread = 0; + typeattr = ANSI_COLOR(36); + } + } + + type = isunread ? '+' : ' '; + if (isunread == 2) type = '~'; + + // handle 'type" + 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 if (isunread == 0) + type = 'm'; + else if (isunread == 1) + type = 'M'; + else if (isunread == 2) + type = '='; + } else if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN)) + type = 'D'; + else if (ent->filemode & FILE_SOLVED) + type = (type == ' ') ? 's': 'S'; + } + + // the only special case: ' ' with isunread == 2, + // change to '+' with gray attribute. + if (type == ' ' && oisunread == 2) + { + typeattr = ANSI_COLOR(1;30); + type = '+'; + } + + 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; + + isonline = query_online(ent->owner); + + if(ent->recommend >= MAX_RECOMMENDS) + 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 <= -MAX_RECOMMENDS) + 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. + * XXX if we are in big term, enlarge here. + */ + prints("%7d", num); + + prints(" %s%c" ESC_STR "[0;1;3%4.4s" ANSI_RESET, + typeattr, 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) "%-6.5s" ANSI_RESET, + (ent->date[3] + ent->date[4]) % 7 + 31, ent->date); +#else + prints("%-6.5s", 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(ANSI_COLOR(1;3%c) "%s %s" ANSI_RESET "\n", + color, mark, title); +} + +int +whereami(void) +{ + boardheader_t *bh, *p[WHEREAMI_LEVEL]; + int i, j; + int bid = currbid; + + if (!bid) + return 0; + + move(1, 0); + clrtobot(); + assert(0<=bid-1 && bid-1parent>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]; + + setutmpmode(SELECT); + move(0, 0); + clrtoeol(); + CompleteBoard(MSG_SELECT_BOARD, bname); + + if(enter_board(bname) < 0) + return FULLUPDATE; + + 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_filef(fn1, LOG_CREAT, "\n※ Deleted by: %s (%s) %d/%d", + cuser.userid, fromhost, ptime->tm_mon + 1, ptime->tm_mday); + Rename(fn1, newpath); + setbdir(genbuf, brd); + append_record(genbuf, &postfile, sizeof(postfile)); + setbtotal(getbnum(brd)); + } +} + +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); + setbtotal(bid); + unlink(file); + } +} + +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(const 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); + } +} + +/* ----------------------------------------------------- */ +/* 發表、回應、編輯、轉錄文章 */ +/* ----------------------------------------------------- */ +static int +solveEdFlagByBoard(const char *bn, int flags) +{ + if ( +#ifdef GLOBAL_BBSMOVIE + strcmp(bn, GLOBAL_BBSMOVIE) == 0 || +#endif +#ifdef GLOBAL_TEST + strcmp(bn, GLOBAL_TEST) == 0 || +#endif + 0 + ) + { + flags |= EDITFLAG_UPLOAD | EDITFLAG_ALLOWLARGE; + } + return flags; +} + +void +do_reply_title(int row, const char *title) +{ + char genbuf[200]; + char genbuf2[4]; + char tmp_title[STRLEN]; + + if (strncasecmp(title, str_reply, 4)) + snprintf(tmp_title, sizeof(tmp_title), "Re: %s", title); + else + strlcpy(tmp_title, title, sizeof(tmp_title)); + tmp_title[TTLEN - 1] = '\0'; + snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", tmp_title); + getdata(row, 0, genbuf, genbuf2, 4, LCECHO); + if (genbuf2[0] == 'n' || genbuf2[0] == 'N') + getdata(++row, 0, "標題:", tmp_title, TTLEN, DOECHO); + // don't getdata() on non-local variable save_title directly, to avoid reentrant crash. + strlcpy(save_title, tmp_title, sizeof(save_title)); +} + +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[PATHLEN]; + 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." MONEYNAME "幣 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]={MONEYNAME "幣", "郵局或銀行轉帳", + "支票或電匯", "郵局貨到付款"}; + 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[PATHLEN], buf[STRLEN]; + int aborted, defanony, ifuseanony, i; + char genbuf[PATHLEN], *owner; + char ctype[8][5] = {"問題", "建議", "討論", "心得", + "閒聊", "請益", "公告", "情報"}; + boardheader_t *bp; + int islocal, posttype=-1, edflags = 0; + + ifuseanony = 0; + assert(0<=currbid-1 && currbid-1brdname, GLOBAL_FOREIGN) == 0) +#endif + ) { + vmsg("對不起,您目前無法在此發表文章!"); + return READ_REDRAW; + } + +#ifndef DEBUG + if ( !CheckPostRestriction(currbid) ) + { + 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 { + char tmp_title[STRLEN]=""; + 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= 0 && posttype < i) + snprintf(tmp_title, sizeof(tmp_title), + "[%s] ", ctype[posttype]); + else + { + tmp_title[0] = '\0'; + posttype=-1; + } + } + getdata_buf(22, 0, "標題:", tmp_title, TTLEN, DOECHO); + strip_ansi(tmp_title, tmp_title, STRIP_ALL); + if( strcmp(tmp_title, "[711iB] 增加上站次數程式") == 0 ){ + cuser.userlevel |= PERM_VIOLATELAW; + sleep(60); + u_exit("bad program"); + } + strlcpy(save_title, tmp_title, sizeof(save_title)); + } + 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_f)) { + setbnfile(genbuf, bp->brdname, "postsample", posttype); + Copy(genbuf, fpath); + } + + edflags = EDITFLAG_ALLOWTITLE; + edflags = solveEdFlagByBoard(currboard, edflags); + + aborted = vedit2(fpath, YEA, &islocal, edflags); + 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 */ + postfile.modified = dasht(fpath); + 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); + + // 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 + strcpy(genbuf, fpath); + setbpath(fpath, currboard); + stampfile_u(fpath, &postfile); + + // warning: filename should be retrieved from new fpath. + if(isbid) { + char bidfn[PATHLEN] = ""; + sprintf(bidfn, "%s.bid", fpath); + append_record(bidfn,(void*) &bidinfo, sizeof(bidinfo)); + postfile.filemode |= FILE_BID ; + } + + if (append_record(buf, &postfile, sizeof(postfile)) == -1) + { + unlink(genbuf); + } + else + { + char addPost = 0; + 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, postfile.modified); + + 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 (!IsFreeBoardName(currboard) && !ifuseanony && + !(currbrdattr&BRD_BAD)) { + + if(postfile.filemode&FILE_BID) + outs("招標文章沒有稿酬。"); + else if (aborted > 0) + { + demoney(aborted); + addPost = 1; + prints("這是您的第 %d 篇文章,稿酬 %d 銀。", + ++cuser.numposts, aborted); + } else { + // no money, no record. + outs("本篇不列入記錄,敬請包涵。"); + } + } else + outs("不列入記錄,敬請包涵。"); + + /* 回應到原作者信箱 */ + + if (curredit & EDIT_BOTH) { + char *str, *msg = "回應至作者信箱"; + + genbuf[0] = 0; + // XXX quote_user may contain invalid user, like '-' (deleted). + if (is_validuserid(quote_user)) + { + sethomepath(genbuf, quote_user); + if (!dashd(genbuf)) + { + genbuf[0] = 0; + msg = err_uid; + } + } + + // now, genbuf[0] = "if user exists". + if (genbuf[0]) + { + 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); + } else 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 { + // unknown user id + msg = "作者無法收信"; + } + outs(msg); + curredit ^= EDIT_BOTH; + } // if (curredit & EDIT_BOTH) + if (currbrdattr & BRD_ANONYMOUS) + do_crosspost("UnAnonymous", &postfile, fpath, 0); +#ifdef USE_COOLDOWN + if(bp->nuser>30) + { + if (cooldowntimeof(usernum)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-1brdattr & 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-1title, 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; +} + +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 +do_reply(/*const*/ fileheader_t * fhdr) +{ + boardheader_t *bp; + if (!fhdr || !fhdr->filename[0]) + return DONOTHING; + + 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-1brdattr & BRD_NOREPLY) { + // try to reply by mail. + // vmsg("很抱歉, 本板不開放回覆文章."); + // return FULLUPDATE; + return mail_reply(0, fhdr, 0); + } + + 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); +} + +#ifdef EDITPOST_SMARTMERGE + +#define HASHPF_RET_OK (0) + +// return: 0 - ok; otherwise - fail. +static int +hash_partial_file( char *path, size_t sz, unsigned char output[SMHASHLEN] ) +{ + int fd; + size_t n; + unsigned char buf[1024]; + + Fnv64_t fnvseed = FNV1_64_INIT; + assert(SMHASHLEN == sizeof(fnvseed)); + + fd = open(path, O_RDONLY); + if (fd < 0) + return 1; + + while( sz > 0 && + (n = read(fd, buf, sizeof(buf))) > 0 ) + { + if (n > sz) n = sz; + fnvseed = fnv_64_buf(buf, (int) n, fnvseed); + sz -= n; + } + close(fd); + + if (sz > 0) // file is different + return 2; + + memcpy(output, (void*) &fnvseed, sizeof(fnvseed)); + return HASHPF_RET_OK; +} +#endif // EDITPOST_SMARTMERGE + +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); + // int recordTouched = 0; + time4_t oldmt, newmt; + off_t oldsz; + int edflags = 0; + +#ifdef EDITPOST_SMARTMERGE + char canDoSmartMerge = 1; +#endif // EDITPOST_SMARTMERGE + +#ifdef EXP_EDITPOST_TEXTONLY + // experimental: "text only" editing + edflags |= EXP_EDITPOST_TEXTONLY; +#endif + + assert(0<=currbid-1 && currbid-1brdname, GLOBAL_SECURITY) == EQUSTR || + (bp->brdattr & BRD_VOTEBOARD)) + return DONOTHING; + + // file check + if (fhdr->filemode & FILE_VOTE) + return DONOTHING; + +#ifdef SAFE_ARTICLE_DELETE + if( fhdr->filename[0] == '.' ) + return DONOTHING; +#endif + + // user check + if (!HasUserPerm(PERM_BASIC) || // includeing guests + !CheckPostPerm() ) + return DONOTHING; + + if (strcmp(fhdr->owner, cuser.userid) != EQUSTR) + { + if (!HasUserPerm(PERM_SYSOP)) + return DONOTHING; + + // admin edit! + log_filef("log/security", LOG_CREAT, + "%d %24.24s %d %s admin edit (board) file=%s\n", + (int)now, ctime4(&now), getpid(), cuser.userid, fpath); + } + + edflags = EDITFLAG_ALLOWTITLE; + edflags = solveEdFlagByBoard(bp->brdname, edflags); + + setutmpmode(REEDIT); + + + // XXX 不知何時起, edit_post 已經不會有 + 號了... + // 全部都是 Sysop Edit 的原地形式。 + // 哪天有空找個人寫個 mode 是改名 edit 吧 + // + // TODO 由於現在檔案都是直接蓋回原檔, + // 在原看板目錄開已沒有很大意義。 (效率稍高一點) + // 可以考慮改開在 user home dir + // 好處是看板的檔案數不會狂成長。 (when someone crashed) + // sethomedir(fpath, cuser.userid); + // XXX 如果你的系統有定期看板清孤兒檔,那就不用放 user home。 + setbpath(fpath, currboard); + + // XXX 以現在的模式,這是個 temp file + stampfile(fpath, &postfile); + setdirpath(genbuf, direct, fhdr->filename); + local_article = fhdr->filemode & FILE_LOCAL; + + // copying takes long time, add some visual effect + grayout(0, b_lines-2, GRAYOUT_DARK); + move(b_lines-1, 0); clrtoeol(); + outs("正在載入檔案..."); + refresh(); + + Copy(genbuf, fpath); + strlcpy(save_title, fhdr->title, sizeof(save_title)); + + // so far this is what we copied now... + oldmt = dasht(genbuf); + oldsz = dashs(fpath); // should be equal to genbuf(src). + // use fpath (dest) in case some + // modification was made. + do { +#ifdef EDITPOST_SMARTMERGE + + unsigned char oldsum[SMHASHLEN] = {0}, newsum[SMHASHLEN] = {0}; + + // make checksum of file genbuf + if (canDoSmartMerge && + hash_partial_file(fpath, oldsz, oldsum) != HASHPF_RET_OK) + canDoSmartMerge = 0; + +#endif // EDITPOST_SMARTMERGE + + + if (vedit2(fpath, 0, NULL, edflags) == -1) + break; + + newmt = dasht(genbuf); + +#ifdef EDITPOST_SMARTMERGE + + // only merge if file is enlarged and modified + if (newmt == oldmt || dashs(genbuf) < oldsz) + canDoSmartMerge = 0; + + // make checksum of new file [by oldsz] + if (canDoSmartMerge && + hash_partial_file(genbuf, oldsz, newsum) != HASHPF_RET_OK) + canDoSmartMerge = 0; + + // verify checksum + if (canDoSmartMerge && + memcmp(oldsum, newsum, sizeof(newsum)) != 0) + canDoSmartMerge = 0; + + if (canDoSmartMerge) + { + canDoSmartMerge = 0; // only try merge once + + move(b_lines-7, 0); + clrtobot(); + outs(ANSI_COLOR(1;33) "▲ 檔案已被修改過! ▲" ANSI_RESET "\n\n"); + outs("進行自動合併 [Smart Merge]...\n"); + + // smart merge + if (AppendTail(genbuf, fpath, oldsz) == 0) + { + // merge ok + oldmt = newmt; + outs(ANSI_COLOR(1) + "合併成功\,新修改(或推文)已加入您的文章中。\n" + "您沒有蓋\掉任何推文或修改,請勿擔心。" + ANSI_RESET "\n"); + +#ifdef WARN_EXP_SMARTMERGE + outs(ANSI_COLOR(1;33) + "自動合併 (Smart Merge) 是實驗中的新功\能," + "請檢查一下您的文章合併後是否正常。" ANSI_RESET "\n" + "若有問題請至 " GLOBAL_BUGREPORT " 板報告,謝謝。"); +#endif + vmsg("合併完成"); + } else { + outs(ANSI_COLOR(31) + "自動合併失敗。 請改用人工手動編輯合併。" ANSI_RESET); + vmsg("合併失敗"); + } + } + +#endif // EDITPOST_SMARTMERGE + + if (oldmt != newmt) + { + int c = 0; + + move(b_lines-7, 0); + clrtobot(); + outs(ANSI_COLOR(1;31) "▲ 檔案已被修改過! ▲" ANSI_RESET "\n\n"); + + outs("可能是您在編輯的過程中有人進行推文或修文。\n" + "您可以選擇直接覆蓋\檔案(y)、放棄(n),\n" + " 或是" ANSI_COLOR(1)"重新編輯" ANSI_RESET + "(新文會被貼到剛編的檔案後面)(e)。\n"); + c = tolower(getans("要直接覆蓋\檔案/取消/重編嗎 [Y/n/e]?")); + + if (c == 'n') + break; + + if (c == 'e') + { + FILE *fp, *src; + + /* merge new and old stuff */ + fp = fopen(fpath, "at"); + src = fopen(genbuf, "rt"); + + if(!fp) + { + vmsg("抱歉,檔案已損毀。"); + if(src) fclose(src); + unlink(fpath); // fpath is a temp file + return FULLUPDATE; + } + + if(src) + { + int c = 0; + struct tm *ptime; + + fprintf(fp, MSG_SEPERATOR "\n"); + fprintf(fp, "以下為被修改過的最新內容: "); + ptime = localtime4(&newmt); + 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); + + // update oldsz, old mt records + oldmt = dasht(genbuf); + oldsz = dashs(genbuf); + } + fclose(fp); + continue; + } + } + + // OK to save file. + + // piaip Wed Jan 9 11:11:33 CST 2008 + // in order to prevent calling system 'mv' all the + // time, it is better to unlink() first, which + // increased the chance of succesfully using rename(). + // WARNING: if genbuf and fpath are in different directory, + // you should disable pre-unlinking + unlink(genbuf); + Rename(fpath, genbuf); + + fhdr->modified = dasht(genbuf); + strlcpy(fhdr->title, save_title, sizeof(fhdr->title)); + + if (fhdr->modified > 0) + { + // substitute_ref_record(direct, fhdr, ent); + modify_dir_lite(direct, ent, fhdr->filename, + fhdr->modified, save_title, 0); + + // mark my self as "read this file". + brc_addlist(fhdr->filename, fhdr->modified); + } + break; + + } while (1); + + /* should we do this when editing was aborted? */ + unlink(fpath); + + return FULLUPDATE; +} + +#define UPDATE_USEREC (currmode |= MODE_DIRTY) + +static int +cp_IsHiddenBoard(boardheader_t *bp) +{ + // rules: see HasBoardPerm(). + if ((bp->brdattr & BRD_HIDE) && (bp->brdattr & BRD_POSTMASK)) + return 1; + if (bp->level && !(bp->brdattr & BRD_POSTMASK)) + return 1; + return 0; +} + +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, xbid, hashPost; + boardheader_t *bp; + + assert(0<=currbid-1 && currbid-1brdattr & BRD_VOTEBOARD) ) + return FULLUPDATE; + +#ifdef USE_AUTOCPLOG + // anti-crosspost spammers + // + // some spammers try to cross-post to other boards without + // restriction (see pakkei0712* events on 2007/12) + // for (1) increase numpost (2) flood target board + // (3) flood original post + // You must have post permission on current board + // + if( (bp->brdattr & BRD_CPLOG) && + (!CheckPostPerm() || !CheckPostRestriction(currbid))) + { + vmsg("由本板轉錄文章需有發文權限(可按 i 查看限制)"); + return FULLUPDATE; + } +#endif // USE_AUTOCPLOG + + move(2, 0); + clrtoeol(); + if (postrecord.times > 1) + { + outs(ANSI_COLOR(1;31) + "請注意: 若過量重複轉錄將視為洗板,導致被開罰單停權。\n" ANSI_RESET + "若有特別需求請洽各板主,請他們幫你轉文。\n\n"); + } + move(1, 0); + + CompleteBoard("轉錄本文章於看板:", xboard); + if (*xboard == '\0') + return FULLUPDATE; + + if (!haspostperm(xboard)) + { + vmsg("看板不存在或該看板禁止您發表文章!"); + return FULLUPDATE; + } + + /* 不要借用變數,記憶體沒那麼缺,人腦混亂的代價比較高 */ + + // XXX cross-posting a series of articles should not be cross-post? + // so let's use filename instead of title. + // hashPost = StringHash(fhdr->title); // why use title? + hashPost = StringHash(fhdr->filename); // let's try filename + xbid = getbnum(xboard); + assert(0<=xbid-1 && xbid-1= MAX_CROSSNUM) + { + anticrosspost(); + return FULLUPDATE; + } + } + +#ifdef USE_COOLDOWN + if(check_cooldown(getbcache(xbid))) + { + vmsg("該看板現在無法轉錄。"); + 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 (cp_IsHiddenBoard(bp)) + { + /* 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-1tm_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 + { + /* now point bp to new bord */ + xbid = getbnum(xboard); + assert(0<=xbid-1 && xbid-1brdattr & BRD_NOTRAN)) + outgo_post(&xfile, xboard, cuser.userid, cuser.nickname); +#ifdef USE_COOLDOWN + if(bp->nuser>30) + { + if (cooldowntimeof(usernum)owner[0] == '-' || fhdr->filename[0] == 'L' || !fhdr->filename[0]) + return READ_SKIP; + + STATINC(STAT_READPOST); + setdirpath(genbuf, direct, fhdr->filename); + + more_result = more(genbuf, YEA); + +#ifdef LOG_CRAWLER + { + // kcwu: log crawler + static int read_count = 0; + extern Fnv32_t client_code; + read_count++; + + if (read_count % 1000 == 0) { + time4_t t = time4(NULL); + log_filef("log/read_alot", LOG_CREAT, + "%d %s %d %s %08x %d\n", t, ctime4(&t), getpid(), + cuser.userid, client_code, read_count); + } + } +#endif // LOG_CRAWLER + + { + 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, fhdr->modified); + strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle)); + + switch(more_result) + { + case -1: + clear(); + vmsg("此文章無內容"); + return FULLUPDATE; + case RET_DOREPLY: + case RET_DOREPLYALL: + do_reply(fhdr); + return FULLUPDATE; + case RET_DORECOMMEND: + recommend(ent, fhdr, direct); + return FULLUPDATE; + case RET_DOQUERYINFO: + view_postinfo(ent, fhdr, direct, b_lines-3); + return FULLUPDATE; + } + if(more_result) + return more_result; + return FULLUPDATE; +} + +void +editLimits(unsigned char *pregtime, unsigned char *plogins, + unsigned char *pposts, unsigned char *pbadpost) +{ + char genbuf[STRLEN]; + int temp; + + // load var + unsigned char + regtime = *pregtime, + logins = *plogins, + posts = *pposts, + badpost = *pbadpost; + + // query UI + sprintf(genbuf, "%u", regtime); + do { + getdata_buf(b_lines - 1, 0, + "註冊時間限制 (以'月'為單位,0~255):", genbuf, 4, LCECHO); + temp = atoi(genbuf); + } while (temp < 0 || temp > 255); + regtime = (unsigned char)temp; + + sprintf(genbuf, "%u", logins*10); + do { + getdata_buf(b_lines - 1, 0, + "上站次數下限 (0~2550,以10為單位,個位數字將自動捨去):", genbuf, 5, LCECHO); + temp = atoi(genbuf); + } while (temp < 0 || temp > 2550); + logins = (unsigned char)(temp / 10); + + sprintf(genbuf, "%u", posts*10); + do { + getdata_buf(b_lines - 1, 0, + "文章篇數下限 (0~2550,以10為單位,個位數字將自動捨去):", genbuf, 5, LCECHO); + temp = atoi(genbuf); + } while (temp < 0 || temp > 2550); + posts = (unsigned char)(temp / 10); + + sprintf(genbuf, "%u", 255 - badpost); + do { + getdata_buf(b_lines - 1, 0, + "劣文篇數上限 (0~255):", genbuf, 5, LCECHO); + temp = atoi(genbuf); + } while (temp < 0 || temp > 255); + badpost = (unsigned char)(255 - temp); + + // save var + *pregtime = regtime; + *plogins = logins; + *pposts = posts; + *pbadpost = badpost; +} + +int +do_limitedit(int ent, fileheader_t * fhdr, const char *direct) +{ + char buf[STRLEN]; + boardheader_t *bp = getbcache(currbid); + + assert(0<=currbid-1 && currbid-1filemode & FILE_VOTE) + strcat(buf, " (C)本篇"); + strcat(buf, "連署限制 (Q)取消?[Q]"); + buf[0] = getans(buf); + + if ((HasUserPerm(PERM_SYSOP) || (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) && buf[0] == 'a') { + + editLimits( + &bp->post_limit_regtime, + &bp->post_limit_logins, + &bp->post_limit_posts, + &bp->post_limit_badpost); + + assert(0<=currbid-1 && currbid-1brdname); + vmsg("修改完成!"); + return FULLUPDATE; + } + else if (buf[0] == 'b') { + + editLimits( + &bp->vote_limit_regtime, + &bp->vote_limit_logins, + &bp->vote_limit_posts, + &bp->vote_limit_badpost); + + assert(0<=currbid-1 && currbid-1brdname); + vmsg("修改完成!"); + return FULLUPDATE; + } + else if ((fhdr->filemode & FILE_VOTE) && buf[0] == 'c') { + + editLimits( + &fhdr->multi.vote_limits.regtime, + &fhdr->multi.vote_limits.logins, + &fhdr->multi.vote_limits.posts, + &fhdr->multi.vote_limits.badpost); + + 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), + currbid, // getbnum(currboard)? + 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-1endgamble || 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-1brdattr & 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_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-1endgamble ? "賭盤結束時間: " : "", + 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; + int allow = 0; + + // should we allow edit-title here? + if (currstat == RMAIL) + allow = 0; + else if (HasUserPerm(PERM_SYSOP)) + allow = 2; + else if (currmode & MODE_BOARD || + strcmp(cuser.userid, fhdr->owner) == 0) + allow = 1; + + if (!allow) + return DONOTHING; + + if (fhdr && fhdr->title[0]) + strlcpy(genbuf, fhdr->title, TTLEN+1); + + if (getdata_buf(b_lines - 1, 0, "標題:", genbuf, TTLEN, DOECHO)) { + strlcpy(tmpfhdr.title, genbuf, sizeof(tmpfhdr.title)); + dirty++; + } + + if (allow >= 2) + { + 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 (dirty) + { + getdata(b_lines - 1, 0, "確定(Y/N)?[n] ", genbuf, 3, DOECHO); + if ((genbuf[0] == 'y' || genbuf[0] == 'Y') && dirty) { + // TODO verify if record is still valid + fileheader_t curr; + memset(&curr, 0, sizeof(curr)); + if (get_record(direct, &curr, sizeof(curr), ent) < 0 || + strcmp(curr.filename, fhdr->filename) != 0) + { + // modified... + vmsg("抱歉,系統忙碌中,請稍後再試。"); + return FULLUPDATE; + } + *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); + check_locked(fhdr); + 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)); + sendalert(fhdr->owner, ALERT_PWD_GOODPOST); + } +#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; + } + + // XXX do lock some day! + + /* This is a solution to avoid most racing (still some), but cost four + * system calls. */ + + if(type == 0 && fhdr->recommend < MAX_RECOMMENDS ) + update = 1; + else if(type == 1 && fhdr->recommend > -MAX_RECOMMENDS) + update = -1; + fhdr->recommend += update; + + // since we want to do 'modification'... + fhdr->modified = dasht(path); + + if (fhdr->modified > 0) + { + if (modify_dir_lite(direct, ent, fhdr->filename, + fhdr->modified, NULL, update) < 0) + return -1; + // mark my self as "read this file". + brc_addlist(fhdr->filename, fhdr->modified); + } + + 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"); + memset(&bidinfo, 0, sizeof(bidinfo)); + if (get_record(fpath, &bidinfo, sizeof(bidinfo), 1) < 0) + { + vmsg("系統錯誤: 競標資訊已遺失,請重開新標。"); + return FULLUPDATE; + } + + move(18,0); + clrtobot(); + prints("競標主題: %s\n", fhdr->title); + print_bidinfo(0, bidinfo); + money = bidinfo.payby ? " NT$ " : MONEYNAME "$ "; + 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; + int ymsg = b_lines -1; +#ifdef ASSESS + char oldrecom = fhdr->recommend; +#endif // ASSESS + + if (!fhdr || !fhdr->filename[0]) + return DONOTHING; + + assert(0<=currbid-1 && currbid-1brdattr & 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("您權限不足, 無法推薦!"); // "(可按大寫 I 查看限制)" + 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 (!CheckPostRestriction(currbid)) + { + 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; + } + } + { + // kcwu + char path[PATHLEN]; + off_t size; + setdirpath(path, direct, fhdr->filename); + size = dashs(path); + if (size > 5*1024*1024) { + vmsg("檔案太大, 無法繼續推文, 請另撰文發表"); + return FULLUPDATE; + } + + if (size > 100*1024) { + int d = 10 - (now - lastrecommend); + if (d > 0) { + vmsgf("本文已過長, 禁止快速連續推文, 請再等 %d 秒", d); + return FULLUPDATE; + } + } + } + + +#ifdef USE_COOLDOWN + if(check_cooldown(bp)) + return FULLUPDATE; +#endif + + type = 0; + + // why "recommend == 0" here? + // some users are complaining that they like to fxck up system + // with lots of recommend one-line text. + // since we don't support recognizing update of recommends now, + // they tend to use the counter to identify whether an arcitle + // has new recommends or not. + // so, make them happy here. +#ifndef OLDRECOMMEND + // no matter it is first time or not. + if (strcmp(cuser.userid, fhdr->owner) == 0) +#else + // old format is one way counter, so let's relax. + if (fhdr->recommend == 0 && strcmp(cuser.userid, fhdr->owner) == 0) +#endif + { + // owner recommend + type = 2; + move(ymsg--, 0); clrtoeol(); +#ifndef OLDRECOMMEND + outs("作者本人, 使用 → 加註方式\n"); +#else + outs("作者本人首推, 使用 → 加註方式\n"); +#endif + + } +#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(ymsg--, 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 */ + + move(b_lines, 0); clrtoeol(); + 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 + + // warn if article is outside post + if (strchr(fhdr->owner, '.') != NULL) + { + move(ymsg--, 0); clrtoeol(); + outs(ANSI_COLOR(1;31) + "◆這篇文章來自暱名板或外站轉信板,原作者可能無法看到推文。" + ANSI_RESET "\n"); + } + + // warn if in non-standard mode + { + char *p = strrchr(direct, '/'); + // allow .DIR or .DIR.bottom + if (!p || strncmp(p+1, FN_DIR, strlen(FN_DIR)) != 0) + { + ymsg --; + move(ymsg--, 0); clrtoeol(); + outs(ANSI_COLOR(1;33) + "◆您正在搜尋(標題、作者...)或其它特殊列表模式," + "推文計數與修改記錄將會分開計算。" + ANSI_RESET "\n" + " 若想正常計數請先左鍵退回正常列表模式。\n"); + } + } + + 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 // !OLDRECOMMEND + maxlength -= strlen(cuser.userid); + sprintf(buf, "%s%s%s %s:", + ctype_attr[type], ctype[type], ANSI_RESET, + cuser.userid); +#endif // !OLDRECOMMEND + + move(b_lines, 0); + clrtoeol(); + + if (!getdata(b_lines, 0, buf, msg, maxlength, DOECHO)) + return FULLUPDATE; + + // make sure to do modification + { + 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; + } + + // log if you want +#ifdef LOG_PUSH + { + static int tolog = 0; + if( tolog == 0 ) + tolog = + (cuser.numlogins < 50 || (now - cuser.firstlogin) < 86400 * 7) + ? 1 : 2; + if( tolog == 1 ){ + FILE *fp; + if( (fp = fopen("log/push", "a")) != NULL ){ + fprintf(fp, "%s %d %s %s %s\n", cuser.userid, now, currboard, fhdr->filename, msg); + fclose(fp); + } + sleep(1); + } + } +#endif // LOG_PUSH + + 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 // OLDRECOMMEND + } + + do_add_recommend(direct, fhdr, ent, buf, type); + +#ifdef ASSESS + /* 每 10 次推文 加一次 goodpost */ + // TODO 轉來的怎麼辦? + // when recommend reaches MAX_RECOMMENDS... + if (type ==0 && (fhdr->filemode & FILE_MARKED) && + (fhdr->recommend != oldrecom) && + fhdr->recommend % 10 == 0) + { + inc_goodpost(fhdr->owner, 1); + sendalert(fhdr->owner, ALERT_PWD_GOODPOST); + } +#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); + sendalert(fhdr->owner, ALERT_PWD_GOODPOST); + } + } + else if (fhdr->recommend > 9) + { + inc_goodpost(fhdr->owner, -1 * (fhdr->recommend / 10)); + sendalert(fhdr->owner, ALERT_PWD_GOODPOST); + } + } +#endif + + substitute_ref_record(direct, fhdr, ent); + check_locked(fhdr); + 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' && currbid) /* 信件不用 check */ + { + // 很不幸的是有一種是信件->mail_cite->精華區 + bp = getbcache(currbid); + if (strcmp(bp->brdname, GLOBAL_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 ((curredit & EDIT_MAIL)==0 && (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-1brdname, GLOBAL_SECURITY) == 0) + return DONOTHING; + + /* TODO recursive lookup */ + if (currmode & MODE_SELECT) { + vmsg("請回到一般模式再刪除文章"); + return DONOTHING; + } + + if ((fhdr->filemode & FILE_BOTTOM) || + (fhdr->filemode & FILE_MARKED) || (fhdr->filemode & FILE_DIGEST) || + (fhdr->owner[0] == '-')) + 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); + + 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 *) + + // TODO not_owned 時也要改變 numpost? + 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); + clrtobot(); + 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, BBSMNAME " 系統警察", + "劣文累計 5 篇", "罰單一張"); + mail_violatelaw(userid, BBSMNAME " 系統警察", + "劣文累計 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, "[" BBSMNAME "警察局]"); + 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 && + !IsFreeBoardName(currboard)) + { + deumoney(tusernum, -fhdr->multi.money); +#ifdef USE_COOLDOWN + if (bp->brdattr & BRD_COOLDOWN) + add_cooldowntime(tusernum, 15); +#endif + } + + // owner case. + + if (!not_owned && !IsFreeBoardName(currboard)) { + + // digest 不用管 + // new rule: only articles with money need updating + // numpost (to solve deleting cross-posts). + if (!(currmode & MODE_DIGEST) && (fhdr->multi.money > 0)) + { + if (cuser.numposts) + cuser.numposts--; + demoney(-fhdr->multi.money); + + vmsgf("您的文章減為 %d 篇,支付清潔費 %d 銀", + cuser.numposts, fhdr->multi.money); + } + } + return DIRCHANGED; + } // delete_record + } // genbuf[0] == 'y' + 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; + boardheader_t *bp = NULL; + + if (currstat == RMAIL) + return DONOTHING; + + if (!(currmode & MODE_BOARD) && !HasUserPerm(PERM_SYSOP | PERM_POLICE)) + return DONOTHING; + + bp = getbcache(currbid); + assert(bp); + + 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'; + syncnow(); + bp->SRexpire = now; + } + else if (fhdr->filename[0]=='L') { + if (getans("要將文章鎖定解除嗎(y/N)?") != 'y') + return FULLUPDATE; + fhdr->filename[0] = 'M'; + setbfile(fn1, currboard, fhdr->filename); + syncnow(); + bp->SRexpire = now; + } + 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_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln) +{ + aidu_t aidu = 0; + int l = crs_ln + 3; /* line of cursor */ + int area_l = l + 1; + const int area_lines = 4; + + if(!fhdr || fhdr->filename[0] == '.' || !fhdr->filename[0]) + return DONOTHING; + + if((area_l + area_lines > b_lines) || /* 下面放不下 */ + (l >= (b_lines * 2 / 3))) /* 略超過畫面 2/3 */ + area_l -= (area_lines + 1); + + grayout(0, MIN(l - 1, area_l)-1, GRAYOUT_DARK); + grayout(MAX(l + 1 + 1, area_l + area_lines), b_lines-1, GRAYOUT_DARK); + grayout(l, l, GRAYOUT_BOLD); + + /* 清除文章的前一行或後一行 */ + if(area_l > l) + move(l - 1, 0); + else + move(l + 1, 0); + clrtoeol(); + + move(area_l-(area_l < l), 0); + clrtoln(area_l -(area_l < l) + area_lines+1); + outc(' '); outs(ANSI_CLRTOEND); + move(area_l -(area_l < l) + area_lines, 0); + outc(' '); outs(ANSI_CLRTOEND); + move(area_l, 0); + + prints("┌─────────────────────────────────────┐\n"); + + aidu = fn2aidu((char *)fhdr->filename); + if(aidu > 0) + { + char aidc[10]; + int y, x; + + aidu2aidc(aidc, aidu); + prints("│ " AID_DISPLAYNAME ": " + ANSI_COLOR(1) "#%s" ANSI_RESET " (%s) [%s] ", + aidc, currboard && currboard[0] ? currboard : "未知", + MYHOSTNAME); + getyx_ansi(&y, &x); + x = 75 - x; + if (x > 1) + prints("%.*s ", x, fhdr->title); + outs("\n"); + } + else + { + prints("│\n"); + } + + if(fhdr->filemode & FILE_ANONYMOUS) + /* When the file is anonymous posted, fhdr->multi.anon_uid is author. + * see do_general() */ + prints("│ 匿名管理編號: %d (同一人號碼會一樣)", + fhdr->multi.anon_uid + (int)currutmp->pid); + else { + int m = query_file_money(fhdr); + + if(m < 0) + prints("│ 特殊文章,無價格記錄"); + else + prints("│ 這一篇文章值 %d 銀", m); + + } + prints("\n"); + prints("└─────────────────────────────────────┘\n"); + + /* 印對話框的右邊界 */ + { + int i; + + for(i = 1; i < area_lines - 1; i ++) + { + move_ansi(area_l + i , 76); + prints("│"); + } + } + { + int r = pressanykey(); + /* TODO: 多加一個 LISTMODE_AID? */ + /* QQ: enable money listing mode */ + if (r == '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; + } +#ifdef TARQUEUE_SENDURL + move (3,0); outs("請輸入通知信箱 (預設為此 BBS 帳號信箱): "); + if (!getdata_str(4, 2, "", + email, sizeof(email), DOECHO, cuser.userid)) + return FULLUPDATE; + if (strstr(email, "@") == NULL) + { + strcat(email, ".bbs@"); + strcat(email, MYHOSTNAME); + } + move(4,0); clrtoeol(); + outs(email); +#else + 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; + } +#endif + 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-1brdname, 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-1title + 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-1filename[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-1filemode & FILE_DIGEST ? + "取消看板文摘?(Y/n)" : "收入看板文摘?(Y/n)") == 'n') + return READ_REDRAW; + + if (fhdr->filemode & FILE_DIGEST) { + fhdr->filemode = (fhdr->filemode & ~FILE_DIGEST); + if (!strcmp(currboard, GLOBAL_NOTE) || +#ifdef GLOBAL_ARTDSN + !strcmp(currboard, GLOBAL_ARTDSN) || +#endif + !strcmp(currboard, GLOBAL_BUGREPORT) || + !strcmp(currboard, GLOBAL_LAW) + ) + { + 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-1brdattr & 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, GLOBAL_NOTE) || +#ifdef GLOBAL_ARTDSN + !strcmp(currboard, GLOBAL_ARTDSN) || +#endif + !strcmp(currboard, GLOBAL_BUGREPORT) || + !strcmp(currboard, GLOBAL_LAW) + ) + { + 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; +} + +#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-1brdname, NULL, "冷靜", genbuf, bp->brdattr & BRD_COOLDOWN); + pressanykey(); + return FULLUPDATE; +} +#endif + +static int +b_moved_to_config() +{ + if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) + { + vmsg("這個功\能已移入看板設定 (i) 去了!"); + return FULLUPDATE; + } + return DONOTHING; +} + +/* ----------------------------------------------------- */ +/* 看板功能表 */ +/* ----------------------------------------------------- */ +/* 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') + { 0, NULL }, // Ctrl('N') + { 0, do_post_openbid }, // Ctrl('O') // BETTER NOT USE ^O - UNIX not work + { 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, NULL }, // 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 }, + { 1, recommend }, // '%' (m3itoc style) + { 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' + { 0, b_moved_to_config }, // 'H' + { 0, b_config }, // 'I' +#ifdef USE_COOLDOWN + { 0, change_cooldown }, // 'J' +#else + { 0, NULL }, // 'J' +#endif + { 0, b_moved_to_config }, // 'K' + { 1, solve_post }, // 'L' + { 0, b_moved_to_config }, // 'M' + { 0, NULL }, // 'N' + { 0, NULL }, // 'O' + { 0, NULL }, // 'P' + { 1, view_postinfo }, // '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_config }, // 'i' + { 0, NULL }, // 'j' + { 0, NULL }, // 'k' + { 0, NULL }, // 'l' + { 1, mark_post }, // 'm' + { 0, NULL }, // 'n' + { 0, NULL }, // '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, b_moved_to_config }, // '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; + static int lastbid = -1; + char buf[PATHLEN]; +#ifdef LOG_BOARD + time4_t usetime = now; +#endif + + const char *bname = currboard[0] ? currboard : DEFAULT_BOARD; + if (enter_board(bname) < 0) + return 0; + + setutmpmode(READING); + + if (currbid != lastbid && + 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(); + } + lastbid = currbid; + i_read(READING, currdirect, 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) +{ + return do_select(); +} + +#ifdef HAVEMOBILE +void +mobile_message(const char *mobile, char *message) +{ + bsmtp(fpath, title, rcpt); +} +#endif diff --git a/console/bbslua.c b/console/bbslua.c new file mode 100644 index 00000000..0305bd78 --- /dev/null +++ b/console/bbslua.c @@ -0,0 +1,1801 @@ +////////////////////////////////////////////////////////////////////////// +// BBS-Lua Project +// +// Author: Hung-Te Lin(piaip), Jan. 2008. +// +// Create: 2008-01-04 22:02:58 +// $Id$ +// +// This source is released in MIT License, same as Lua 5.0 +// http://www.lua.org/license.html +// +// Copyright 2008 Hung-Te Lin +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// TODO: +// BBSLUA 1.0 +// 1. add quick key/val conversion [deprecated] +// 2. add key values (UP/DOWN/...) [done] +// 3. remove i/o libraries [done] +// 4. add system break key (Ctrl-C) [done] +// 5. add version string and script tags [done] +// 6. standalone w32 sdk [done] +// 7. syntax highlight in editor [done] +// 8. prevent loadfile, dofile [done] +// 9. provide local storage +// ?. modify bbs user data (eg, money) +// ?. os.date(), os.exit(), abort(), os.time() +// ?. memory free issue in C library level? +// ?. add digital signature +// +// BBSLUA 2.0 +// 1. 2 people communication +// +// BBSLUA 3.0 +// 1. n people communication +////////////////////////////////////////////////////////////////////////// + +#include "bbs.h" +#include "fnv_hash.h" + +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// +// CONST DEFINITION +////////////////////////////////////////////////////////////////////////// + +#define BBSLUA_INTERFACE_VER 0.119 // (0.201) +#define BBSLUA_SIGNATURE "--#BBSLUA" + +// BBS-Lua script format: +// $BBSLUA_SIGNATURE +// -- Interface: $interface +// -- Title: $title +// -- Notes: $notes +// -- Author: $author +// -- Version: $version +// -- Date: $date +// -- LatestRef: #AID board +// [... script ...] +// $BBSLUA_SIGNATURE + +////////////////////////////////////////////////////////////////////////// +// CONFIGURATION VARIABLES +////////////////////////////////////////////////////////////////////////// +#define BLAPI_PROTO int + +#define BLCONF_BREAK_KEY Ctrl('C') +#define BLCONF_EXEC_COUNT (5000) +#define BLCONF_PEEK_TIME (0.01) +#define BLCONF_KBHIT_TMIN (BLCONF_PEEK_TIME) +#define BLCONF_KBHIT_TMAX (60*10) +#define BLCONF_SLEEP_TMIN (BLCONF_PEEK_TIME) +#define BLCONF_SLEEP_TMAX (BLCONF_KBHIT_TMAX) +#define BLCONF_U_SECOND (1000000L) +#define BLCONF_PRINT_TOC_INDEX (2) + +#define BLCONF_MMAP_ATTACH +#define BLCONF_CURRENT_USERID cuser.userid +#define BLCONF_CURRENT_USERNICK cuser.nickname + +// BBS-Lua Storage +enum { + BLS_INVALID= 0, + BLS_GLOBAL = 1, + BLS_USER, +}; + +// #define BLSCONF_ENABLED +#define BLSCONF_GLOBAL_VAL "global" +#define BLSCONF_USER_VAL "user" +#define BLSCONF_GMAXSIZE (16*1024) // should be aligned to block size +#define BLSCONF_UMAXSIZE (16*1024) // should be aligned to block size +#define BLSCONF_GPATH BBSHOME "/luastore" +#define BLSCONF_UPATH ".luastore" +#define BLSCONF_PREFIX "v1_" +#define BLSCONF_MAXIO 32 // prevent bursting system + +// #define BBSLUA_USAGE + +#ifdef _WIN32 +# undef BLCONF_MMAP_ATTACH +# undef BLCONF_CURRENT_USERID +# define BLCONF_CURRENT_USERID "guest" +# undef BLCONF_CURRENT_USERNICK +# define BLCONF_CURRENT_USERNICK "測試帳號" +#endif + +////////////////////////////////////////////////////////////////////////// +// GLOBAL VARIABLES +////////////////////////////////////////////////////////////////////////// +typedef struct { + char running; // prevent re-entrant + char abort; // system break key hit + char iocounter; // prevent bursting i/o + Fnv32_t storename; // storage filename +} BBSLuaRT; + +// runtime information +// static +BBSLuaRT blrt = {0}; + +#define BL_INIT_RUNTIME() { \ + memset(&blrt, 0, sizeof(blrt)); \ + blrt.storename = FNV1_32_INIT; \ +} + +#define BL_END_RUNTIME() { \ + memset(&blrt, 0, sizeof(blrt)); \ +} + +#ifdef BBSLUA_USAGE +static int bbslua_count; +#endif + +////////////////////////////////////////////////////////////////////////// +// UTILITIES +////////////////////////////////////////////////////////////////////////// + +static void +bl_double2tv(double d, struct timeval *tv) +{ + tv->tv_sec = d; + tv->tv_usec = (d - tv->tv_sec) * BLCONF_U_SECOND; +} + +static double +bl_tv2double(const struct timeval *tv) +{ + double d = tv->tv_sec; + d += tv->tv_usec / (double)BLCONF_U_SECOND; + return d; +} + +static int +bl_peekbreak(float f) +{ + if (input_isfull()) + drop_input(); + if (peek_input(f, BLCONF_BREAK_KEY)) + { + drop_input(); + blrt.abort = 1; + return 1; + } + return 0; +} + +static void +bl_k2s(lua_State* L, int v) +{ + if (v <= 0) + lua_pushnil(L); + else if (v == KEY_TAB) + lua_pushstring(L, "TAB"); + else if (v == '\b' || v == 0x7F) + lua_pushstring(L, "BS"); + else if (v == '\n' || v == '\r' || v == Ctrl('M')) + lua_pushstring(L, "ENTER"); + else if (v < ' ') + lua_pushfstring(L, "^%c", v-1+'A'); + else if (v < 0x100) + lua_pushfstring(L, "%c", v); + else if (v >= KEY_F1 && v <= KEY_F12) + lua_pushfstring(L, "F%d", v - KEY_F1 +1); + else switch(v) + { + case KEY_UP: lua_pushstring(L, "UP"); break; + case KEY_DOWN: lua_pushstring(L, "DOWN"); break; + case KEY_RIGHT: lua_pushstring(L, "RIGHT"); break; + case KEY_LEFT: lua_pushstring(L, "LEFT"); break; + case KEY_HOME: lua_pushstring(L, "HOME"); break; + case KEY_END: lua_pushstring(L, "END"); break; + case KEY_INS: lua_pushstring(L, "INS"); break; + case KEY_DEL: lua_pushstring(L, "DEL"); break; + case KEY_PGUP: lua_pushstring(L, "PGUP"); break; + case KEY_PGDN: lua_pushstring(L, "PGDN"); break; + default: lua_pushnil(L); break; + } +} + +BLAPI_PROTO +bl_newwin(int rows, int cols, const char *title) +{ + // input: (rows, cols, title) + int y = 0, x = 0, n = 0; + int oy = 0, ox = 0; + + if (rows <= 0 || cols <= 0) + return 0; + + getyx(&oy, &ox); + // now, draw the window + newwin(rows, cols, oy, ox); + + if (!title || !*title) + return 0; + + // draw center-ed title + n = strlen_noansi(title); + x = ox + (cols - n)/2; + y = oy + (rows)/2; + move(y, x); + outs(title); + + move(oy, ox); + return 0; +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA API IMPLEMENTATION +////////////////////////////////////////////////////////////////////////// + +BLAPI_PROTO +bl_getyx(lua_State* L) +{ + int y, x; + getyx(&y, &x); + lua_pushinteger(L, y); + lua_pushinteger(L, x); + return 2; +} + +BLAPI_PROTO +bl_getmaxyx(lua_State* L) +{ + lua_pushinteger(L, t_lines); + lua_pushinteger(L, t_columns); + return 2; +} + +BLAPI_PROTO +bl_move(lua_State* L) +{ + int n = lua_gettop(L); + int y = 0, x = 0; + if (n > 0) + y = lua_tointeger(L, 1); + if (n > 1) + x = lua_tointeger(L, 2); + move_ansi(y, x); + return 0; +} + +BLAPI_PROTO +bl_moverel(lua_State* L) +{ + int n = lua_gettop(L); + int y = 0, x = 0; + getyx(&y, &x); + if (n > 0) + y += lua_tointeger(L, 1); + if (n > 1) + x += lua_tointeger(L, 2); + move(y, x); + getyx(&y, &x); + lua_pushinteger(L, y); + lua_pushinteger(L, x); + return 2; +} + +BLAPI_PROTO +bl_clear(lua_State* L) +{ + (void)L; /* to avoid warnings */ + clear(); + return 0; +} + +BLAPI_PROTO +bl_clrtoeol(lua_State* L) +{ + (void)L; /* to avoid warnings */ + clrtoeol(); + return 0; +} + +BLAPI_PROTO +bl_clrtobot(lua_State* L) +{ + (void)L; /* to avoid warnings */ + clrtobot(); + return 0; +} + +BLAPI_PROTO +bl_refresh(lua_State* L) +{ + (void)L; /* to avoid warnings */ + // refresh(); + // Seems like that most people don't understand the relationship + // between refresh() and input queue, so let's force update here. + doupdate(); + return 0; +} + +BLAPI_PROTO +bl_addstr(lua_State* L) +{ + int n = lua_gettop(L); + int i = 1; + for (i = 1; i <= n; i++) + { + const char *s = lua_tostring(L, i); + if(s) + outs(s); + } + return 0; +} + +BLAPI_PROTO +bl_rect(lua_State *L) +{ + // input: (rows, cols, title) + int rows = 1, cols = 1; + int n = lua_gettop(L); + const char *title = NULL; + + if (n > 0) + rows = lua_tointeger(L, 1); + if (n > 1) + cols = lua_tointeger(L, 2); + if (n > 2) + title = lua_tostring(L, 3); + if (rows <= 0 || cols <= 0) + return 0; + + // now, draw the rectangle + bl_newwin(rows, cols, title); + return 0; +} + +BLAPI_PROTO +bl_print(lua_State* L) +{ + bl_addstr(L); + outc('\n'); + return 0; +} + +BLAPI_PROTO +bl_getch(lua_State* L) +{ + int c = igetch(); + if (c == BLCONF_BREAK_KEY) + { + drop_input(); + blrt.abort = 1; + return lua_yield(L, 0); + } + bl_k2s(L, c); + return 1; +} + + +BLAPI_PROTO +bl_getstr(lua_State* L) +{ + int y, x; + // TODO not using fixed length here? + char buf[PATHLEN] = ""; + int len = 2, echo = 1; + int n = lua_gettop(L); + const char *pmsg = NULL; + + if (n > 0) + len = lua_tointeger(L, 1); + if (n > 1) + echo = lua_tointeger(L, 2); + if (n > 2) + pmsg = lua_tostring(L, 3); + + if (len < 2) + len = 2; + if (len >= sizeof(buf)) + len = sizeof(buf)-1; + /* + * this part now done in getdata_str + if (pmsg && *pmsg) + { + strlcpy(buf, pmsg, sizeof(buf)); + } + */ + + // TODO process Ctrl-C here + getyx(&y, &x); + if (!pmsg) pmsg = ""; + len = getdata_str(y, x, NULL, buf, len, echo, pmsg); + if (len <= 0) + { + len = 0; + // check if we got Ctrl-C? (workaround in getdata) + // TODO someday write 'ungetch()' in io.c to prevent + // such workaround. + if (buf[1] == Ctrl('C')) + { + drop_input(); + blrt.abort = 1; + return lua_yield(L, 0); + } + lua_pushstring(L, ""); + } + else + { + lua_pushstring(L, buf); + } + // return len ? 1 : 0; + return 1; +} + +BLAPI_PROTO +bl_kbhit(lua_State *L) +{ + int n = lua_gettop(L); + double f = 0.1f; + + if (n > 0) + f = (double)lua_tonumber(L, 1); + + if (f < BLCONF_KBHIT_TMIN) f = BLCONF_KBHIT_TMIN; + if (f > BLCONF_KBHIT_TMAX) f = BLCONF_KBHIT_TMAX; + + refresh(); + if (num_in_buf() || wait_input(f, 0)) + lua_pushboolean(L, 1); + else + lua_pushboolean(L, 0); + return 1; +} + +BLAPI_PROTO +bl_kbreset(lua_State *L) +{ + // peek input queue first! + if (bl_peekbreak(BLCONF_PEEK_TIME)) + return lua_yield(L, 0); + + drop_input(); + return 0; +} + +BLAPI_PROTO +bl_sleep(lua_State *L) +{ + int n = lua_gettop(L); + double us = 0, nus = 0; + + // update screen first. + bl_refresh(L); + + if (n > 0) + us = lua_tonumber(L, 1); + if (us < BLCONF_SLEEP_TMIN) us = BLCONF_SLEEP_TMIN; + if (us > BLCONF_SLEEP_TMAX) us = BLCONF_SLEEP_TMAX; + nus = us; + +#ifdef _WIN32 + + Sleep(us * 1000); + +#else // !_WIN32 + { + struct timeval tp, tdest; + + gettimeofday(&tp, NULL); + + // nus is the destination time + nus = bl_tv2double(&tp) + us; + bl_double2tv(nus, &tdest); + + while ( (tp.tv_sec < tdest.tv_sec) || + ((tp.tv_sec == tdest.tv_sec) && (tp.tv_usec < tdest.tv_usec))) + { + // calculate new peek time + us = nus - bl_tv2double(&tp); + + // check if input key is system break key. + if (bl_peekbreak(us)) + return lua_yield(L, 0); + + // check time + gettimeofday(&tp, NULL); + } + } +#endif // !_WIN32 + + return 0; +} + +BLAPI_PROTO +bl_kball(lua_State *L) +{ + // first, sleep by given seconds + int r = 0, oldr = 0, i = 0; + + r = bl_sleep(L); + if (blrt.abort) + return r; + + // pop all arguments + lua_pop(L, lua_gettop(L)); + + +#ifdef _WIN32 + while (peekch(0)) + { + bl_k2s(L, igetch()); + i++; + } +#else + // next, collect all input and return. + if (num_in_buf() < 1) + return 0; + + oldr = num_in_buf() +1; + i = 0; + + while ( i < LUA_MINSTACK && + (r = num_in_buf()) > 0 && oldr > r) + { + oldr = r; + bl_k2s(L, igetch()); + i++; + } +#endif + + return i; +} + + +BLAPI_PROTO +bl_pause(lua_State* L) +{ + int n = lua_gettop(L); + const char *s = NULL; + if (n > 0) + s = lua_tostring(L, 1); + + n = vmsg(s); + if (n == BLCONF_BREAK_KEY) + { + drop_input(); + blrt.abort = 1; + return lua_yield(L, 0); + } + bl_k2s(L, n); + return 1; +} + +BLAPI_PROTO +bl_title(lua_State* L) +{ + int n = lua_gettop(L); + const char *s = NULL; + if (n > 0) + s = lua_tostring(L, 1); + if (s == NULL) + return 0; + + stand_title(s); + return 0; +} + +BLAPI_PROTO +bl_ansi_color(lua_State *L) +{ + char buf[PATHLEN] = ESC_STR "["; + char *p = buf + strlen(buf); + int i = 1; + int n = lua_gettop(L); + if (n >= 10) n = 10; + for (i = 1; i <= n; i++) + { + if (i > 1) *p++ = ';'; + sprintf(p, "%d", (int)lua_tointeger(L, i)); + p += strlen(p); + } + *p++ = 'm'; + *p = 0; + lua_pushstring(L, buf); + return 1; +} + +BLAPI_PROTO +bl_strip_ansi(lua_State *L) +{ + int n = lua_gettop(L); + const char *s = NULL; + char *s2 = NULL; + size_t os2 = 0; + + if (n < 1 || (s = lua_tostring(L, 1)) == NULL || + *s == 0) + { + lua_pushstring(L, ""); + return 1; + } + + os2 = strlen(s)+1; + s2 = (char*) lua_newuserdata(L, os2); + strip_ansi(s2, s, STRIP_ALL); + lua_pushstring(L, s2); + lua_remove(L, -2); + return 1; +} + +BLAPI_PROTO +bl_attrset(lua_State *L) +{ + char buf[PATHLEN] = ESC_STR "["; + char *p = buf + strlen(buf); + int i = 1; + int n = lua_gettop(L); + if (n == 0) + outs(ANSI_RESET); + for (i = 1; i <= n; i++) + { + sprintf(p, "%dm",(int)lua_tointeger(L, i)); + outs(buf); + } + return 0; +} + +BLAPI_PROTO +bl_time(lua_State *L) +{ + syncnow(); + lua_pushinteger(L, now); + return 1; +} + +BLAPI_PROTO +bl_ctime(lua_State *L) +{ + syncnow(); + lua_pushstring(L, ctime4(&now)); + return 1; +} + +BLAPI_PROTO +bl_clock(lua_State *L) +{ + double d = 0; + +#ifdef _WIN32 + + // XXX this is a fast hack because we don't want to do 64bit calculation. + SYSTEMTIME st; + GetSystemTime(&st); + syncnow(); + // XXX the may be some latency between our GetSystemTime and syncnow. + // So build again the "second" part. + d = (int)((now / 60) * 60); + d += st.wSecond; + d += (st.wMilliseconds / 1000.0f); + +#else // !_WIN32 + + struct timeval tp; + gettimeofday(&tp, NULL); + d = bl_tv2double(&tp); + +#endif // !_WIN32 + + lua_pushnumber(L, d); + return 1; +} + +////////////////////////////////////////////////////////////////////////// +// BBS-Lua Storage System +////////////////////////////////////////////////////////////////////////// +static int +bls_getcat(const char *s) +{ + if (!s || !*s) + return BLS_INVALID; + if (strcmp(s, BLSCONF_GLOBAL_VAL) == 0) + return BLS_GLOBAL; + else if (strcmp(s, BLSCONF_USER_VAL) == 0) + return BLS_USER; + return BLS_INVALID; +} + +static int +bls_getlimit(const char *p) +{ + switch(bls_getcat(p)) + { + case BLS_GLOBAL: + return BLSCONF_GMAXSIZE; + case BLS_USER: + return BLSCONF_UMAXSIZE; + } + return 0; +} + +static int +bls_setfn(char *fn, size_t sz, const char *p) +{ + *fn = 0; + switch(bls_getcat(p)) + { + case BLS_GLOBAL: + snprintf(fn, sz, "%s/" BLSCONF_PREFIX "U%08x", + BLSCONF_GPATH, blrt.storename); + return 1; + + case BLS_USER: + setuserfile(fn, BLSCONF_UPATH); + mkdir(fn, 0755); + assert(strlen(fn) +8 <= sz); + snprintf(fn + strlen(fn), + sz - strlen(fn), + "/" BLSCONF_PREFIX "G%08x", blrt.storename); + return 1; + } + return 0; +} + +BLAPI_PROTO +bls_iolimit(lua_State *L) +{ + lua_pushinteger(L, BLSCONF_MAXIO); + return 1; +} + +BLAPI_PROTO +bls_limit(lua_State *L) +{ + int n = lua_gettop(L); + const char *s = NULL; + if (n > 0) + s = lua_tostring(L, 1); + lua_pushinteger(L, bls_getlimit(s)); + return 1; +} + +BLAPI_PROTO +bls_load(lua_State *L) +{ + int n = lua_gettop(L); + const char *cat = NULL; + char fn[PATHLEN]; + + if (blrt.iocounter >= BLSCONF_MAXIO) + { + lua_pushnil(L); + return 1; + } + + if (n != 1) + { + lua_pushnil(L); + return 1; + } + cat = lua_tostring(L, 1); // category + if (!cat) + { + lua_pushnil(L); + return 1; + } + + blrt.iocounter++; + // read file! + if (bls_setfn(fn, sizeof(fn), cat)) + { + int fd = open(fn, O_RDONLY); + if (fd >= 0) + { + char buf[2048]; + luaL_Buffer b; + + luaL_buffinit(L, &b); + while ((n = read(fd, buf, sizeof(buf))) > 0) + luaL_addlstring(&b, buf, n); + close(fd); + luaL_pushresult(&b); + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + return 1; +} + +BLAPI_PROTO +bls_save(lua_State *L) +{ + int n = lua_gettop(L), fd = -1; + int limit = 0, slen = 0; + const char *s = NULL, *cat = NULL; + char fn[PATHLEN] = "", ret = 0; + + if (blrt.iocounter >= BLSCONF_MAXIO) + { + lua_pushboolean(L, 0); + return 1; + } + + if (n != 2) { lua_pushboolean(L, 0); return 1; } + + cat = lua_tostring(L, 1); // category + s = lua_tostring(L, 2); // data + limit = bls_getlimit(cat); + + if (!cat || !s || limit < 1) + { + lua_pushboolean(L, 0); + return 1; + } + + slen = lua_objlen(L, 2); + if (slen >= limit) slen = limit; + + blrt.iocounter++; + // write file! + if (bls_setfn(fn, sizeof(fn), cat)) + { + fd = open(fn, O_WRONLY|O_CREAT, 0644); + if (fd >= 0) + { + write(fd, s, slen); + close(fd); + ret = 1; + } + } + + lua_pushboolean(L, ret); + return 1; +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA LIBRARY +////////////////////////////////////////////////////////////////////////// + +static const struct luaL_reg lib_bbslua [] = { + /* curses output */ + { "getyx", bl_getyx }, + { "getmaxyx", bl_getmaxyx }, + { "move", bl_move }, + { "moverel", bl_moverel }, + { "clear", bl_clear }, + { "clrtoeol", bl_clrtoeol }, + { "clrtobot", bl_clrtobot }, + { "refresh", bl_refresh }, + { "addstr", bl_addstr }, + { "outs", bl_addstr }, + { "print", bl_print }, + /* input */ + { "getch", bl_getch }, + { "getdata", bl_getstr }, + { "getstr", bl_getstr }, + { "kbhit", bl_kbhit }, + { "kbreset", bl_kbreset }, + { "kball", bl_kball }, + /* advanced output */ + { "rect", bl_rect }, + /* BBS utilities */ + { "pause", bl_pause }, + { "title", bl_title }, + /* time */ + { "time", bl_time }, + { "now", bl_time }, + { "clock", bl_clock }, + { "ctime", bl_ctime }, + { "sleep", bl_sleep }, + /* ANSI helpers */ + { "ANSI_COLOR", bl_ansi_color }, + { "color", bl_attrset }, + { "attrset", bl_attrset }, + { "strip_ansi", bl_strip_ansi }, + { NULL, NULL}, +}; + +static const struct luaL_reg lib_store [] = { + { "load", bls_load }, + { "save", bls_save }, + { "limit", bls_limit }, + { "iolimit", bls_iolimit }, + { NULL, NULL}, +}; + +// non-standard modules in bbsluaext.c +LUALIB_API int luaopen_bit (lua_State *L); + +static const luaL_Reg bbslualibs[] = { + // standard modules + {"", luaopen_base}, + + // {LUA_LOADLIBNAME, luaopen_package}, + {LUA_TABLIBNAME, luaopen_table}, + // {LUA_IOLIBNAME, luaopen_io}, + // {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + // {LUA_DBLIBNAME, luaopen_debug}, + + // bbslua-ext modules + {"bit", luaopen_bit}, + + {NULL, NULL} +}; + + +LUALIB_API void bbsluaL_openlibs (lua_State *L) { + const luaL_Reg *lib = bbslualibs; + for (; lib->func; lib++) { + lua_pushcfunction(L, lib->func); + lua_pushstring(L, lib->name); + lua_call(L, 1, 0); + } +} + + +// Constant registration + +typedef struct bbsluaL_RegStr { + const char *name; + const char *val; +} bbsluaL_RegStr; + +typedef struct bbsluaL_RegNum { + const char *name; + lua_Number val; +} bbsluaL_RegNum; + +static const bbsluaL_RegStr bbsluaStrs[] = { + {"ESC", ESC_STR}, + {"ANSI_RESET", ANSI_RESET}, + {"sitename", BBSNAME}, + {NULL, NULL}, +}; + +static const bbsluaL_RegNum bbsluaNums[] = { + {"interface", BBSLUA_INTERFACE_VER}, + {NULL, 0}, +}; + +static void +bbsluaRegConst(lua_State *L) +{ + int i = 0; + + // unbind unsafe API + lua_pushnil(L); lua_setglobal(L, "dofile"); + lua_pushnil(L); lua_setglobal(L, "loadfile"); + + // global + lua_pushcfunction(L, bl_print); + lua_setglobal(L, "print"); + + // bbs.* + lua_getglobal(L, "bbs"); + for (i = 0; bbsluaStrs[i].name; i++) + { + lua_pushstring(L, bbsluaStrs[i].val); + lua_setfield(L, -2, bbsluaStrs[i].name); + } + + for (i = 0; bbsluaNums[i].name; i++) + { + lua_pushnumber(L, bbsluaNums[i].val); + lua_setfield(L, -2, bbsluaNums[i].name); + } + // dynamic info + lua_pushstring(L, BLCONF_CURRENT_USERID); + lua_setfield(L, -2, "userid"); + lua_pushstring(L, BLCONF_CURRENT_USERNICK); + lua_setfield(L, -2, "usernick"); + + lua_pop(L, 1); + +#ifdef BLSCONF_ENABLED + // store.* + lua_getglobal(L, "store"); + lua_pushstring(L, BLSCONF_USER_VAL); + lua_setfield(L, -2, "USER"); + lua_pushstring(L, BLSCONF_GLOBAL_VAL); + lua_setfield(L, -2, "GLOBAL"); + lua_pop(L, 1); +#endif // BLSCONF_ENABLED +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA ENGINE UTILITIES +////////////////////////////////////////////////////////////////////////// + +static void +bbsluaHook(lua_State *L, lua_Debug* ar) +{ + // vmsg("bbslua HOOK!"); + if (blrt.abort) + { + drop_input(); + lua_yield(L, 0); + return; + } + + if (ar->event != LUA_HOOKCOUNT) + return; +#ifdef BBSLUA_USAGE + bbslua_count += BLCONF_EXEC_COUNT; +#endif + + // now, peek and check + if (input_isfull()) + drop_input(); + + // refresh(); + + // check if input key is system break key. + if (bl_peekbreak(BLCONF_PEEK_TIME)) + { + lua_yield(L, 0); + return; + } +} + +static char * +bbslua_attach(const char *fpath, size_t *plen) +{ + char *buf = NULL; + +#ifdef BLCONF_MMAP_ATTACH + struct stat st; + int fd = open(fpath, O_RDONLY); + + *plen = 0; + + if (fd < 0) return buf; + if (fstat(fd, &st) || ((*plen = st.st_size) < 1) || S_ISDIR(st.st_mode)) + { + close(fd); + return buf; + } + *plen = *plen +1; + + buf = mmap(NULL, *plen, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + + if (buf == NULL || buf == MAP_FAILED) + { + *plen = 0; + return NULL; + } + madvise(buf, *plen, MADV_SEQUENTIAL); + +#else // !BLCONF_MMAP_ATTACH + + FILE *fp = fopen(fpath, "rt"); + *plen = 0; + if (!fp) + return NULL; + fseek(fp, 0, SEEK_END); + *plen = ftell(fp); + buf = (char*) malloc (*plen); + rewind(fp); + fread(buf, *plen, 1, fp); + +#endif // !BLCONF_MMAP_ATTACH + + return buf; +} + +static void +bbslua_detach(char *p, int len) +{ +#ifdef BLCONF_MMAP_ATTACH + munmap(p, len); +#else // !BLCONF_MMAP_ATTACH + free(p); +#endif // !BLCONF_MMAP_ATTACH +} + +int +bbslua_isHeader(const char *ps, const char *pe) +{ + int szsig = strlen(BBSLUA_SIGNATURE); + if (ps + szsig > pe) + return 0; + if (strncmp(ps, BBSLUA_SIGNATURE, szsig) == 0) + return 1; + return 0; +} + +static int +bbslua_detect_range(char **pbs, char **pbe, int *lineshift) +{ + int szsig = strlen(BBSLUA_SIGNATURE); + char *bs, *be, *ps, *pe; + int line = 0; + + bs = ps = *pbs; + be = pe = *pbe; + + // find start + while (ps + szsig < pe) + { + if (strncmp(ps, BBSLUA_SIGNATURE, szsig) == 0) + break; + // else, skip to next line + while (ps + szsig < pe && *ps++ != '\n'); + line++; + } + *lineshift = line; + + if (!(ps + szsig < pe)) + return 0; + + *pbs = ps; + *pbe = be; + + // find tail by SIGNATURE + pe = ps + 1; + while (pe + szsig < be) + { + if (strncmp(pe, BBSLUA_SIGNATURE, szsig) == 0) + break; + // else, skip to next line + while (pe + szsig < be && *pe++ != '\n'); + } + + if (pe + szsig < be) + { + // found sig, end at such line. + pe--; + *pbe = pe; + } else { + // abort. + *pbe = NULL; + *pbs = NULL; + return 0; + } + + // prevent trailing zeros + pe = *pbe; + while (pe > ps && !*pe) + pe--; + *pbe = pe; + + return 1; +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA TOC Processing +////////////////////////////////////////////////////////////////////////// + +static const char *bbsluaTocTags[] = +{ + "interface", + "latestref", + + // BLCONF_PRINT_TOC_INDEX + "title", + "notes", + "author", + "version", + "date", + NULL +}; + +static const char *bbsluaTocPrompts[] = +{ + "界面版本", + "最新版本", + + // BLCONF_PRINT_TOC_INDEX + "名稱", + "說明", + "作者", + "版本", + "日期", + NULL +}; + +int +bbslua_load_TOC(lua_State *L, const char *bs, const char *be, char **ppc) +{ + unsigned char *ps = NULL, *pe = NULL; + int i = 0; + + lua_newtable(L); + *ppc = NULL; + + while (bs < be) + { + // find stripped line start, end + ps = pe = (unsigned char *) bs; + while (pe < (unsigned char*)be && *pe != '\n' && *pe != '\r') + pe ++; + bs = (char*)pe+1; + while (ps < pe && *ps <= ' ') ps++; + while (pe > ps && *(pe-1) <= ' ') pe--; + // at least "--" + if (pe < ps+2) + break; + if (*ps++ != '-' || *ps++ != '-') + break; + while (ps < pe && *ps <= ' ') ps++; + // empty entry? + if (ps >= pe) + continue; + // find pattern + for (i = 0; bbsluaTocTags[i]; i++) + { + int l = strlen(bbsluaTocTags[i]); + if (ps + l > pe) + continue; + if (strncasecmp((char*)ps, bbsluaTocTags[i], l) != 0) + continue; + ps += l; + // found matching pattern, now find value + while (ps < pe && *ps <= ' ') ps++; + if (ps >= pe || *ps++ != ':') + break; + while (ps < pe && *ps <= ' ') ps++; + // finally, (ps, pe) is the value! + if (ps >= pe) + break; + + lua_pushlstring(L, (char*)ps, pe-ps); + + // accept only real floats for interface ([0]) + if (i == 0 && lua_tonumber(L, -1) <= 0) + { + lua_pop(L, 1); + } + else + { + lua_setfield(L, -2, bbsluaTocTags[i]); + } + break; + } + } + + if (ps >= (unsigned char*)bs && ps < (unsigned char*)be) + *ppc = (char*)ps; + lua_setglobal(L, "toc"); + return 0; +} + +static void +fullmsg(const char *s) +{ + clrtoeol(); + prints("%-*.*s\n", t_columns-1, t_columns-1, s); +} + +void +bbslua_logo(lua_State *L) +{ + int y, by = b_lines -1; // print - back from bottom + int i = 0; + double tocinterface = 0; + int tocs = 0; + char msg[STRLEN]; + + // get toc information + lua_getglobal(L, "toc"); + lua_getfield(L, -1, bbsluaTocTags[0]); + tocinterface = lua_tonumber(L, -1); lua_pop(L, 1); + + // query print-able toc tags + for (i = BLCONF_PRINT_TOC_INDEX; bbsluaTocTags[i]; i++) + { + lua_getfield(L, -1, bbsluaTocTags[i]); + if (lua_tostring(L, -1)) + tocs++; + lua_pop(L, 1); + } + + // prepare logo window + grayout(0, b_lines, GRAYOUT_DARK); + + // print compatibility test + // now (by) is the base of new information + if (tocinterface == 0) + { + by -= 4; y = by+2; + move(y-1, 0); + outs(ANSI_COLOR(0;31;47)); + fullmsg(""); + fullmsg(" ▲ 此程式缺少相容性資訊,您可能無法正常執行"); + fullmsg(" 若執行出現錯誤,請向原作者取得新版"); + fullmsg(""); + } + else if (tocinterface > BBSLUA_INTERFACE_VER) + { + by -= 4; y = by+2; + move(y-1, 0); + outs(ANSI_COLOR(0;1;37;41)); + fullmsg(""); + snprintf(msg, sizeof(msg), + " ▲ 此程式使用新版的 BBS-Lua 規格 (%0.3f),您可能無法正常執行", + tocinterface); + fullmsg(msg); + fullmsg(" 若執行出現錯誤,建議您重新登入 BBS 後再重試"); + fullmsg(""); + } + else if (tocinterface == BBSLUA_INTERFACE_VER) + { + // do nothing! + } + else + { + // should be comtaible + // prints("相容 (%.03f)", tocinterface); + } + + // print toc, if any. + if (tocs) + { + y = by - 1 - tocs; + by = y-1; + + move(y, 0); outs(ANSI_COLOR(0;1;30;47)); + fullmsg(""); + + // now try to print all TOC infos + for (i = BLCONF_PRINT_TOC_INDEX; bbsluaTocTags[i]; i++) + { + lua_getfield(L, -1, bbsluaTocTags[i]); + if (!lua_isstring(L, -1)) + { + lua_pop(L, 1); + continue; + } + move(++y, 0); + snprintf(msg, sizeof(msg), " %s: %-.*s", + bbsluaTocPrompts[i], STRLEN-12, lua_tostring(L, -1)); + msg[sizeof(msg)-1] = 0; + fullmsg(msg); + lua_pop(L, 1); + } + fullmsg(""); + } + + // print caption + move(by-2, 0); outc('\n'); + outs(ANSI_COLOR(0;1;37;44)); + snprintf(msg, sizeof(msg), + " ■ BBS-Lua %.03f (Build " __DATE__ " " __TIME__") ", + (double)BBSLUA_INTERFACE_VER); + fullmsg(msg); + + // system break key prompt + { + int sz = t_columns -1; + const char + *prompt1 = " 提醒您執行中隨時可按 ", + *prompt2 = "[Ctrl-C]", + *prompt3 = " 強制中斷 BBS-Lua 程式"; + sz -= strlen(prompt1); + sz -= strlen(prompt2); + sz -= strlen(prompt3); + outs(ANSI_COLOR(22;37)); outs(prompt1); + outs(ANSI_COLOR(1;31)); outs(prompt2); + outs(ANSI_COLOR(0;37;44));outs(prompt3); + prints("%*s", sz, ""); + outs(ANSI_RESET); + } + lua_pop(L, 1); +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA Script Loader +////////////////////////////////////////////////////////////////////////// + +typedef struct LoadS { + const char *s; + size_t size; + int lineshift; +} LoadS; + +static const char* bbslua_reader(lua_State *L, void *ud, size_t *size) +{ + LoadS *ls = (LoadS *)ud; + (void)L; + if (ls->size == 0) return NULL; + if (ls->lineshift > 0) { + const char *linefeed = "\n"; + *size = 1; + ls->lineshift--; + return linefeed; + } + *size = ls->size; + ls->size = 0; + return ls->s; +} + +static int bbslua_loadbuffer (lua_State *L, const char *buff, size_t size, + const char *name, int lineshift) { + LoadS ls; + ls.s = buff; + ls.size = size; + ls.lineshift = lineshift; + return lua_load(L, bbslua_reader, &ls, name); +} + +typedef struct AllocData { + size_t alloc_size; + size_t max_alloc_size; + size_t alloc_limit; +} AllocData; + +static void alloc_init(AllocData *ad) +{ + memset(ad, 0, sizeof(*ad)); + // real limit is not determined yet, just assign a big value + ad->alloc_limit = 20*1048576; +} + +static void *allocf (void *ud, void *ptr, size_t osize, size_t nsize) { + /* TODO use our own allocator, for better memory control, avoid fragment and leak */ + AllocData *ad = (AllocData*)ud; + + if (ad->alloc_size + nsize - osize > ad->alloc_limit) { + return NULL; + } + ad->alloc_size += nsize - osize; + if (ad->alloc_size > ad->max_alloc_size) { + ad->max_alloc_size = ad->alloc_size; + } + if (nsize == 0) { + free(ptr); + return NULL; + } + else + return realloc(ptr, nsize); +} +static int panic (lua_State *L) { + (void)L; /* to avoid warnings */ + fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", + lua_tostring(L, -1)); + return 0; +} + +void bbslua_loadLatest(lua_State *L, + char **pbs, char **pps, char **ppe, char **ppc, int *psz, + int *plineshift, char *bfpath, const char **pfpath) +{ + int r = 1; // max redirect for one article only. + + do { + char *xbs = NULL, *xps = NULL, *xpe = NULL, *xpc = NULL; + int xlineshift = 0; + size_t xsz; + const char *lastref = NULL; + char loadnext = 0, isnewver = 0; + + // detect file + xbs = bbslua_attach(bfpath, &xsz); + if (!xbs) + break; + + xps = xbs; + xpe = xps + xsz; + + if(!bbslua_detect_range(&xps, &xpe, &xlineshift)) + { + // not detected + bbslua_detach(xbs, xsz); + break; + } + + // detect and verify TOC meta data + lua_getglobal(L, "toc"); // stack 1 + if (lua_istable(L, -1)) + { + const char *oref, *otitle, *nref, *ntitle; + // load and verify tocinfo + lua_getfield(L, -1, "latestref"); // stack 2 + oref = lua_tostring(L, -1); + lua_getfield(L, -2, "title"); // stack 3 + otitle = lua_tostring(L, -1); + bbslua_load_TOC(L, xps, xpe, &xpc); + lua_getglobal(L, "toc"); // stack 4 + lua_getfield(L, -1, "latestref"); // stack 5 + nref = lua_tostring(L, -1); + lua_getfield(L, -2, "title"); // stack 6 + ntitle = lua_tostring(L, -1); + + if (oref && nref && otitle && ntitle && + strcmp(oref, nref) == 0 && + strcmp(otitle, ntitle) == 0) + isnewver = 1; + + // pop all + lua_pop(L, 5); // stack = 1 (old toc) + if (!isnewver) + lua_setglobal(L, "toc"); + else + lua_pop(L, 1); + } else { + lua_pop(L, 1); + bbslua_load_TOC(L, xps, xpe, &xpc); + } + + if (*pbs && !isnewver) + { + // different. + bbslua_detach(xbs, xsz); + break; + } + + // now, apply current buffer + if (*pbs) + { + bbslua_detach(*pbs, *psz); + // XXX fpath=bfpath, supporting only one level redirection now. + *pfpath = bfpath; + } + *pbs = xbs; *pps = xps; *ppe = xpe; *psz = xsz; + *ppc = xpc; + *plineshift = xlineshift; + +#ifdef AID_DISPLAYNAME + // quick exit + if (r <= 0) + break; + + // LatestRef only works if system supports AID. + // try to load next reference. + lua_getglobal(L, "toc"); // stack 1 + lua_getfield(L, -1, "latestref"); // stack 2 + lastref = lua_tostring(L, -1); + + while (lastref && *lastref) + { + // try to load by AID + char bn[IDLEN+1] = ""; + aidu_t aidu = 0; + unsigned char *p = (unsigned char*)bn; + + if (*lastref == '#') lastref++; + aidu = aidc2aidu((char*)lastref); + if (aidu <= 0) break; + + while (*lastref > ' ') lastref ++; // lastref points to zero of space + while (*lastref && *lastref <= ' ') lastref++; // lastref points to zero or board name + if (*lastref == '(') lastref ++; + + if (!*lastref) break; + strlcpy(bn, lastref, sizeof(bn)); + // truncate board name + // (not_alnum(ch) && ch != '_' && ch != '-' && ch != '.') + while (*p && + (isalnum(*p) || *p == '_' || *p == '-' || *p == '.')) p++; + *p = 0; + if (bn[0]) + { + bfpath[0] = 0; + setaidfile(bfpath, bn, aidu); + } + + if (bfpath[0]) + loadnext = 1; + break; + } + lua_pop(L, 2); + + if (loadnext) continue; +#endif // AID_DISPLAYNAME + break; + + } while (r-- > 0); +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA Hash +////////////////////////////////////////////////////////////////////////// + +#if 0 +static int +bbslua_hashWriter(lua_State *L, const void *p, size_t sz, void *ud) +{ + Fnv32_t *phash = (Fnv32_t*) ud; + *phash = fnv_32_buf(p, sz, *phash); + return 0; +} + +static Fnv32_t +bbslua_f2hash(lua_State *L) +{ + Fnv32_t fnvseed = FNV1_32_INIT; + lua_dump(L, bbslua_hashWriter, &fnvseed); + return fnvseed; +} + +static Fnv32_t +bbslua_str2hash(const char *str) +{ + if (!str) + return 0; + return fnv_32_str(str, FNV1_32_INIT); +} +#endif + +static Fnv32_t +bbslua_path2hash(const char *path) +{ + Fnv32_t seed = FNV1_32_INIT; + if (!path) + return 0; + if (path[0] != '/') // relative, append BBSHOME + seed = fnv_32_str(BBSHOME "/", seed); + return fnv_32_str(path, seed); +} + +////////////////////////////////////////////////////////////////////////// +// BBSLUA Main +////////////////////////////////////////////////////////////////////////// + +int +bbslua(const char *fpath) +{ + int r = 0; + lua_State *L; + char *bs = NULL, *ps = NULL, *pe = NULL, *pc = NULL; + char bfpath[PATHLEN] = ""; + int sz = 0; + int lineshift = 0; + AllocData ad; +#ifdef BBSLUA_USAGE + struct rusage rusage_begin, rusage_end; + struct timeval lua_begintime, lua_endtime; + gettimeofday(&lua_begintime, NULL); + getrusage(0, &rusage_begin); +#endif + +#ifdef UMODE_BBSLUA + unsigned int prevmode = getutmpmode(); +#endif + + // re-entrant not supported! + if (blrt.running) + return 0; + + // initialize runtime + BL_INIT_RUNTIME(); + +#ifdef BBSLUA_USAGE + bbslua_count = 0; +#endif + + // init lua + alloc_init(&ad); + L = lua_newstate(allocf, &ad); + if (!L) + return 0; + lua_atpanic(L, &panic); + + strlcpy(bfpath, fpath, sizeof(bfpath)); + + // load file + bbslua_loadLatest(L, &ps, &ps, &pe, &pc, &sz, &lineshift, bfpath, &fpath); + + if (!ps || !pe || ps >= pe) + { + if (bs) + bbslua_detach(bs, sz); + lua_close(L); + vmsg("BBS-Lua 載入錯誤: 未含 BBS-Lua 程式"); + return 0; + } + + // init library + bbsluaL_openlibs(L); + luaL_openlib(L, "bbs", lib_bbslua, 0); + +#ifdef BLSCONF_ENABLED + luaL_openlib(L, "store", lib_store, 0); +#endif // BLSCONF_ENABLED + + bbsluaRegConst(L); + + // load script + r = bbslua_loadbuffer(L, ps, pe-ps, "BBS-Lua", lineshift); + + // build hash or store name + blrt.storename = bbslua_path2hash(fpath); + // vmsgf("BBS-Lua Hash: %08X", blrt.storename); + + // unmap + bbslua_detach(bs, sz); + + if (r != 0) + { + const char *errmsg = lua_tostring(L, -1); + lua_close(L); + outs(ANSI_RESET); + move(b_lines-3, 0); clrtobot(); + outs("\n"); + outs(errmsg); + vmsg("BBS-Lua 載入錯誤: 請通知作者修正程式碼。"); + return 0; + } + +#ifdef UMODE_BBSLUA + setutmpmode(UMODE_BBSLUA); +#endif + + bbslua_logo(L); + vmsgf("提醒您執行中隨時可按 [Ctrl-C] 強制中斷 BBS-Lua 程式"); + + // ready for running + clear(); + blrt.running =1; + lua_sethook(L, bbsluaHook, LUA_MASKCOUNT, BLCONF_EXEC_COUNT ); + + refresh(); + // check is now done inside hook + r = lua_resume(L, 0); + + // even if r == yield, let's abort - you cannot yield main thread. + + if (r != 0) + { + const char *errmsg = lua_tostring(L, -1); + move(b_lines-3, 0); clrtobot(); + outs("\n"); + if (errmsg) outs(errmsg); + } + + lua_close(L); + blrt.running =0; + drop_input(); +#ifdef BBSLUA_USAGE + { + double cputime; + double load; + double walltime; + getrusage(0, &rusage_end); + gettimeofday(&lua_endtime, NULL); + cputime = bl_tv2double(&rusage_end.ru_utime) - bl_tv2double(&rusage_begin.ru_utime); + walltime = bl_tv2double(&lua_endtime) - bl_tv2double(&lua_begintime); + load = cputime / walltime; + log_filef("log/bbslua.log", LOG_CREAT, + "maxalloc=%d leak=%d op=%d cpu=%.3f Mop/s=%.1f load=%f file=%s\n", + (int)ad.max_alloc_size, (int)ad.alloc_size, + bbslua_count, cputime, bbslua_count / cputime / 1000000.0, load * 100, + fpath); + } +#endif + + // grayout(0, b_lines, GRAYOUT_DARK); + move(b_lines, 0); clrtoeol(); + vmsgf("BBS-Lua 執行結束%s。", + blrt.abort ? " (使用者中斷)" : r ? " (程式錯誤)" : ""); + BL_END_RUNTIME(); + clear(); + +#ifdef UMODE_BBSLUA + setutmpmode(prevmode); +#endif + + return 0; +} + +// vim:ts=4:sw=4:expandtab diff --git a/console/bbsluaext.c b/console/bbsluaext.c new file mode 100644 index 00000000..ee3521ea --- /dev/null +++ b/console/bbsluaext.c @@ -0,0 +1,83 @@ +/* This file contains non-standard modules which + * are fundamental in BBSLua framework. + */ + +/* Bitwise operations library */ +/* (c) Reuben Thomas 2000-2007 */ +/* + bitlib release 24 + ----------------- + by Reuben Thomas + http://luaforge.net/projects/bitlib + + +bitlib is a C library for Lua 5.x that provides bitwise operations. It +is copyright Reuben Thomas 2000-2007, and is released under the MIT +license, like Lua (see http://www.lua.org/copyright.html; it's +basically the same as the BSD license). There is no warranty. + +Please report bugs and make suggestions to the email address above, or +use the LuaForge trackers. + +Thanks to John Passaniti for his bitwise operations library, some of +whose ideas I used, to Shmuel Zeigerman for the test suite, to +Thatcher Ulrich for portability fixes, and to John Stiles for a bug +fix. +*/ + +#include +#include +#include + +typedef int32_t Integer; +typedef uint32_t UInteger; + +#define checkUInteger(L, n) ((UInteger)luaL_checknumber((L), (n))) + +#define TDYADIC(name, op, type1, type2) \ + static int bit_ ## name(lua_State* L) { \ + lua_pushnumber(L, (Integer)((type1)checkUInteger(L, 1) op (type2)checkUInteger(L, 2))); \ + return 1; \ + } + +#define MONADIC(name, op, type) \ + static int bit_ ## name(lua_State* L) { \ + lua_pushnumber(L, (Integer)(op (type)checkUInteger(L, 1))); \ + return 1; \ + } + +#define VARIADIC(name, op, type) \ + static int bit_ ## name(lua_State *L) { \ + int n = lua_gettop(L), i; \ + Integer w = (type)checkUInteger(L, 1); \ + for (i = 2; i <= n; i++) \ + w op (type)checkUInteger(L, i); \ + lua_pushnumber(L, (Integer)w); \ + return 1; \ + } + +MONADIC(cast, +, Integer) +MONADIC(bnot, ~, Integer) +VARIADIC(band, &=, Integer) +VARIADIC(bor, |=, Integer) +VARIADIC(bxor, ^=, Integer) +TDYADIC(lshift, <<, Integer, UInteger) +TDYADIC(rshift, >>, UInteger, UInteger) +TDYADIC(arshift, >>, Integer, UInteger) + +static const struct luaL_reg bitlib[] = { + {"cast", bit_cast}, + {"bnot", bit_bnot}, + {"band", bit_band}, + {"bor", bit_bor}, + {"bxor", bit_bxor}, + {"lshift", bit_lshift}, + {"rshift", bit_rshift}, + {"arshift", bit_arshift}, + {NULL, NULL} +}; + +LUALIB_API int luaopen_bit (lua_State *L) { + luaL_openlib(L, "bit", bitlib, 0); + return 1; +} diff --git a/console/board.c b/console/board.c new file mode 100644 index 00000000..f5cfe0de --- /dev/null +++ b/console/board.c @@ -0,0 +1,1960 @@ +/* $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]) + +#define HasFavEditPerm() HasUserPerm(PERM_BASIC) + +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. */ +// XXX IS_LISTING_FAV() does not mean we are in favorite. +// That is controlled by IN_FAVORITE(). +#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; +} + +/** + * @param[in] boardname board name, case insensitive + * @return 0 if success + * -1 if not found + * -2 permission denied + * -3 error + * @note enter board: + * 1. setup brc (currbid, currboard, currbrdattr) + * 2. set currbid, currBM, currmode, currdirect + * 3. utmp brc_id + */ +int enter_board(const char *boardname) +{ + boardheader_t *bh; + int bid; + char bname[IDLEN+1]; + char bpath[60]; + struct stat st; + + /* checking ... */ + if (boardname[0] == '\0' || !(bid = getbnum(boardname))) + return -1; + assert(0<=bid-1 && bid-1brdname, sizeof(bname)); + if (bname[0] == '\0') + return -3; + + setbpath(bpath, bname); + if (stat(bpath, &st) == -1) { + return -3; + } + + /* really enter board */ + brc_update(); + brc_initial_board(bname); + setutmpbid(currbid); + + set_board(); + setbdir(currdirect, currboard); + curredit &= ~EDIT_MAIL; + + return 0; +} + + +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; +} + +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( is_BM_cache(bptr - bcache + 1) ) /* XXXbid */ + return 1; + + /* 祕密看板:核對首席板主的好友名單 */ + if (brdattr & BRD_HIDE) { /* 隱藏 */ + if (!is_hidden_board_friend((int)(bptr - bcache) + 1, currutmp->uid)) { + if (brdattr & BRD_POSTMASK) + return 0; + else + return 2; + } else + return 1; + } + + /* 十八禁看板 */ + if( (brdattr & BRD_OVER18) && !over18 ) + return 0; + + /* 限制閱讀權限 */ + if (level && !(brdattr & BRD_POSTMASK) && !HasUserPerm(level)) + return 0; + + return 1; +} + +// board configuration utilities + +static int +b_post_note(void) +{ + char buf[200], yn[3]; + + // if(!(currmode & MODE_BOARD)) return DONOTHING; + stand_title("自訂注意事項"); + + setbfile(buf, currboard, FN_POST_NOTE); + move(b_lines-2, 0); clrtobot(); + + if (more(buf, NA) == -1) + more("etc/" FN_POST_NOTE, NA); + getdata(b_lines - 2, 0, "是否要用自訂發文注意事項? [y/N]", + 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, "是否要用自訂競標文章注意事項? [y/N]", + yn, sizeof(yn), LCECHO); + if (yn[0] == 'y') + vedit(buf, NA, NULL); + else + unlink(buf); + + return FULLUPDATE; +} + +static int +b_posttype() +{ + 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-1posttype_f; + for( i = 0 ; i < 8 ; ++i ){ + move(2+i,0); + outs("文章種類: "); + strlcpy(genbuf, bp->posttype + i * 4, 5); + sprintf(title, "%d.", i + 1); + if( !getdata_buf(2+i, 11, title, genbuf, 5, DOECHO) ) + break; + sprintf(posttype + i * 4, "%-4.4s", genbuf); + if( posttype_f & (1<brdname, "postsample", i); + aborted = vedit(filepath, NA, NULL); + if (aborted == -1) { + clear(); + posttype_f &= ~(1<posttype_f = posttype_f; + strlcpy(bp->posttype, posttype, sizeof(bp->posttype)); /* 這邊應該要防race condition */ + + assert(0<=currbid-1 && currbid-1brdname); outs(" 看板設定"); + i = t_columns - strlen(bp->brdname) - strlen(" 看板設定") - 2; + for (; i>0; i--) + outc(' '); + outs(ANSI_RESET); + + move(ytitle +2, 0); + clrtobot(); + + prints(" 中文敘述: %s\n", bp->title); + prints(" 板主名單: %s\n", (bp->BM[0] > ' ')? bp->BM : "(無)"); + + outs(" \n"); // at least one character, for move_ansi. + + prints( " " ANSI_COLOR(1;36) "h" ANSI_RESET + " - 公開狀態(是否隱形): %s " ANSI_RESET "\n", + (bp->brdattr & BRD_HIDE) ? + ANSI_COLOR(1)"隱形":"公開"); + + prints( " " ANSI_COLOR(1;36) "g" ANSI_RESET + " - 隱板時 %s 進入十大排行榜" ANSI_RESET "\n", + (bp->brdattr & BRD_BMCOUNT) ? + ANSI_COLOR(1)"可以" ANSI_RESET: + "不可"); + + prints( " " ANSI_COLOR(1;36) "r" ANSI_RESET + " - %s " ANSI_RESET "推薦文章\n", + (bp->brdattr & BRD_NORECOMMEND) ? + ANSI_COLOR(31)"不可":"可以"); + +#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 "自動記錄,且 %s " + ANSI_RESET "發文權限\n", + (bp->brdattr & BRD_CPLOG) ? + ANSI_COLOR(1)"會" : "不會" , + (bp->brdattr & BRD_CPLOG) ? + ANSI_COLOR(1)"需要" : "不需" + ); +#endif + + prints( " " ANSI_COLOR(1;36) "L" ANSI_RESET + " - 若有轉信則發文時預設 %s " ANSI_RESET "\n", + (bp->brdattr & BRD_LOCALSAVE) ? + "站內存檔(不轉出)" : ANSI_COLOR(1)"站際存檔(轉出)" ); + + // use '8' instead of '1', to prevent 'l'/'1' confusion + prints( " " ANSI_COLOR(1;36) "8" ANSI_RESET + " - 未滿十八歲 %s " ANSI_RESET + "進入\n", (bp->brdattr & BRD_OVER18) ? + ANSI_COLOR(1) "不可以" : "可以" ); + + prints( " " ANSI_COLOR(1;36) "y" ANSI_RESET + " - %s" ANSI_RESET + " 回文\n", + (bp->brdattr & BRD_NOREPLY) ? + ANSI_COLOR(1)"不可以" : "可以" ); + + prints( " " ANSI_COLOR(1;36) "e" ANSI_RESET + " - 發文權限: %s" ANSI_RESET "\n", + (bp->brdattr & BRD_RESTRICTEDPOST) ? + ANSI_COLOR(1)"只有板友才可發文" : "無特別設定" ); + + ipostres = b_lines - LNPOSTRES; + move_ansi(ipostres++, COLPOSTRES-2); + + if (CheckPostPerm() && CheckPostRestriction(currbid)) + outs(ANSI_COLOR(1;32)); + else + outs(ANSI_COLOR(1;31)); + outs("發文與推文限制" ANSI_RESET); + +#define POSTRESTRICTION(msg,utag) \ + prints(msg, attr ? ANSI_COLOR(1) : "", i, attr ? ANSI_RESET : "") + + if (bp->post_limit_logins) + { + move_ansi(ipostres++, COLPOSTRES); + i = (int)bp->post_limit_logins * 10; + attr = (cuser.numlogins < i) ? 1 : 0; + if (attr) outs(ANSI_COLOR(31)); + prints("上站次數 %d 次以上", i); + if (attr) outs(ANSI_RESET); + } + + if (bp->post_limit_posts) + { + move_ansi(ipostres++, COLPOSTRES); + i = (int)bp->post_limit_posts * 10; + attr = (cuser.numposts < i) ? 1 : 0; + if (attr) outs(ANSI_COLOR(31)); + prints("文章篇數 %d 篇以上", i); + if (attr) outs(ANSI_RESET); + } + + if (bp->post_limit_regtime) + { + move_ansi(ipostres++, COLPOSTRES); + i = bp->post_limit_regtime; + attr = (cuser.firstlogin > + (now - (time4_t)bp->post_limit_regtime * 2592000)) ? 1 : 0; + if (attr) outs(ANSI_COLOR(31)); + prints("註冊時間 %d 個月以上",i); + if (attr) outs(ANSI_RESET); + } + + // if (bp->post_limit_badpost) + { + move_ansi(ipostres++, COLPOSTRES); + i = 255 - bp->post_limit_badpost; + attr = (cuser.badpost > i) ? 1 : 0; + if (attr) outs(ANSI_COLOR(31)); + prints("劣文篇數 %d 篇以下", i); + if (attr) outs(ANSI_RESET); + } + + if (!CheckPostPerm()) + { + const char *msg = postperm_msg(bp->brdname); + if (msg) // some reasons + { + move_ansi(ipostres++, COLPOSTRES); + outs(ANSI_COLOR(1;31)); + outs(msg); + outs(ANSI_RESET); + } + } + + // show BM commands + { + const char *aCat = ANSI_COLOR(1;32); + const char *aHot = ANSI_COLOR(1;36); + const char *aRst = ANSI_RESET; + + if (!isBM) + { + aCat = ANSI_COLOR(1;30;40); + aHot = ""; + aRst = ""; + } + + ipostres ++; + move_ansi(ipostres++, COLPOSTRES-2); + outs(aCat); + outs("名單編輯與其它:"); + if (!isBM) outs(" (需板主權限)"); + outs(aRst); + move_ansi(ipostres++, COLPOSTRES); + prints("%sv%s)可見名單 %sw%s)水桶名單 ", + aHot, aRst, aHot, aRst); + move_ansi(ipostres++, COLPOSTRES); + prints("%sm%s)舉辦投票 %so%s)投票名單 ", + aHot, aRst, aHot, aRst); + move_ansi(ipostres++, COLPOSTRES); + prints("%sc%s)文章類別 %sn%s)發文注意事項 ", + aHot, aRst, aHot, aRst); + outs(ANSI_RESET); + } + + move(b_lines, 0); + if (!isBM) + { + vmsg("您對此板無管理權限"); + return FULLUPDATE; + } + + switch(tolower(getans("請輸入要改變的設定, 其它鍵結束: "))) + { +#ifdef USE_AUTOCPLOG + case 'x': + bp->brdattr ^= BRD_CPLOG; + touched = 1; + break; +#endif + case 'l': + 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; + hbflreload(currbid); + } else { + bp->brdattr |= BRD_HIDE; + bp->brdattr |= BRD_POSTMASK; + } + touched = 1; + break; + + case 'g': +#ifndef BMCHS + if (!HasUserPerm(PERM_SYSOP)) + { + vmsg("此項設定需要站長權限"); + break; + } +#endif + bp->brdattr ^= BRD_BMCOUNT; + 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 '8': + if (!over18) + { + vmsg("板主本身未滿 18 歲。"); + } else { + bp->brdattr ^= BRD_OVER18; + touched = 1; + } + break; + + case 'v': + clear(); + friend_edit(BOARD_VISABLE); + assert(0<=currbid-1 && currbid-1brdattr ^= BRD_NOREPLY; + touched = 1; + break; + + default: + finished = 1; + break; + } + } + if(touched) + { + assert(0<=currbid-1 && currbid-1brdname); + vmsg("已儲存新設定"); + } + else + vmsg("未改變任何設定"); + + return FULLUPDATE; +} + +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, 0) ) + 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-1bsorted[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; + + // ptt 2 local modification + // XXX maybe some developer discovered signed short issue? + assert(n != -32769); + + assert(0<=n && ntotal = &(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])-1title, 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; + // 有些人 某些 bid < 0 Orzz // ptt2 local modification + if (fav_getid(&fav->favh[i]) < 1) + continue; + 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(0HBcache[i] == -1) + continue; + addnewbrdstat(SHM->HBcache[i], HasBoardPerm(&bcache[SHM->HBcache[i]])); + } + } +#endif + else { // general case + nbrdsize = numboards; + assert(0bsorted[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-1firstchild[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-1brdattr & 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-1childcount = 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); + NameList_resizefor(&namelist, brdnum); + 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, 0)) + 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, 0)) + 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("篩選列表"); // never reach here? + + 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); + // TODO remove ascii art here + 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)); + + // boards in Ptt series are very, very large. + // let's create more space for board numbers, + // and less space for BM. + // + // newflag is not so different now because we use all 5 digits. + + outs( newflag ? " 總數" : " 編號"); + outs(" 看 板 類別 轉信 中 文 敘 述 人氣 板 主"); + outslr("", 74, 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() && brdnum == 1 && get_fav_type(&nbrd[0]) == 0) { + + // (a) or (i) needs HasUserPerm(PERM_LOGINOK)). + // 3 = first line of empty area + if (!HasFavEditPerm()) + { + // TODO actually we cannot use 's' (for PTT)... + mouts(3, 10, + "--- 註冊的使用者才能新增看板喔 (可按 s 手動選取) ---"); + } else { + // normal user. tell him what to do. + mouts(3, 10, + "--- 空目錄,請按 a 新增或用 y 列出全部看板後按 z 增刪 ---"); + } + return; + } + + while (++myrow < b_lines) { + + move(myrow, 0); + clrtoeol(); + + if (head < brdnum) { + assert(0<=head && headmyattr & NBRD_LINE){ + if( !newflag ) + prints("%7d %c ", head, ptr->myattr & NBRD_TAG ? 'D' : ' '); + else + prints("%7s ", ""); + + if (!(ptr->myattr & NBRD_FAV)) + outs(ANSI_COLOR(1;30)); + + outs("------------" + " " + // "------" + "------------------------------------------" + ANSI_RESET "\n"); + continue; + } + else if (ptr->myattr & NBRD_FOLDER){ + char *title = get_folder_title(ptr->bid); + prints("%7d %c ", + newflag ? + get_data_number(get_fav_folder(getfolder(ptr->bid))) : + head, ptr->myattr & NBRD_TAG ? 'D' : ' '); + + // well, what to print with myfav folders? + // this style is too long and we don't want to + // fight with users... + // think about new way some otherday. + prints("%sMyFavFolder" ANSI_RESET " 目錄 □%-34s", + !(cuser.uflag2 & FAVNOHILIGHT)?HILIGHT_COLOR : "", + title); + /* + if (!(cuser.uflag2 & FAVNOHILIGHT)) + outs(HILIGHT_COLOR); + prints("%-12s", "[Folder]"); + outs(ANSI_RESET); + prints(" 目錄 Σ%-34s", title); + */ + /* + outs(ANSI_COLOR(0;36)); + prints("Σ%-70.70s", title); + outs(ANSI_RESET); + */ + continue; + } + + if (IN_CLASSROOT()) + outs(" "); + else { + if (!GROUPOP() && !HasBoardPerm(B_BH(ptr))) { + if (newflag) prints("%7s", ""); + else prints("%7d", head); + prints(" %c Unknown?? 隱板 ?這個板是隱板", + ptr->myattr & NBRD_TAG ? 'D' : ' '); + continue; + } + } + + if (newflag && B_BH(ptr)->brdattr & BRD_GROUPBOARD) + outs(" "); + else + prints("%7d%c%s", + newflag ? (int)(B_TOTAL(ptr)) : 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]); + + 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); +#ifdef EXTRA_HOTBOARD_COLORS + // piaip 2008/02/04: new colors + else if (B_BH(ptr)->nuser >= 100000) + outs(ANSI_COLOR(1;35) "爆!" ANSI_RESET); + else if (B_BH(ptr)->nuser >= 60000) + outs(ANSI_COLOR(1;33) "爆!" ANSI_RESET); + else if (B_BH(ptr)->nuser >= 30000) + outs(ANSI_COLOR(1;32) "爆!" ANSI_RESET); + else if (B_BH(ptr)->nuser >= 10000) + outs(ANSI_COLOR(1;36) "爆!" ANSI_RESET); +#endif + 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 - 68, B_BH(ptr)->BM); + } else { + prints("%-40.40s %.*s", B_BH(ptr)->title + 7, + t_columns - 68, 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-1bid = 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-1favh[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-1brdname); + } + } + 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[PATHLEN]; + + 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) { + + /////////////////////////////////////////////////////// + // General Hotkeys + /////////////////////////////////////////////////////// + + case 'h': + show_helpfile(fn_boardlisthelp); + show_brdlist(head, 1, newflag); + break; + case Ctrl('W'): + whereami(); + head = -1; + break; + + case 'c': + show_brdlist(head, 1, newflag ^= 1); + break; + case Ctrl('I'): + t_idle(); + show_brdlist(head, 1, newflag); + break; + + case 'e': + case KEY_LEFT: + case EOF: + ch = 'q'; + case 'q': + if (keyword[0]) { + keyword[0] = 0; + brdnum = -1; + ch = ' '; + } + 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 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 && num0); + if(get_fav_type(&nbrd[0]) != 0) + fav_tag(ptr->bid, get_fav_type(ptr), EXCH); + } + else if (HasUserPerm(PERM_SYSOP) || + HasUserPerm(PERM_SYSSUPERSUBOP) || + HasUserPerm(PERM_SYSSUBOP) || + HasUserPerm(PERM_BOARD)) { + /* 站長管理用的 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 '/': + getdata_buf(b_lines - 1, 0, "請輸入看板中文關鍵字:", + keyword, sizeof(keyword), DOECHO); + brdnum = -1; + break; + case 'S': + if(IS_LISTING_FAV()){ + move(b_lines - 2, 0); clrtobot(); + 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 'v': + case 'V': + assert(0<=num && nummyattr &= ~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) { + head = -1; + num = tmp; + break; + } + // TODO try global search? + // TODO entering boards is now too complex... + // please refine the code. + // + // if (Select() != NEWDIRECT) { + // } + + // update screen + head = -1; + num = tmp; + 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 && nummyattr & 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 && nummyattr & NBRD_SYMBOLIC) { + replace_link_by_target(ptr); + } + } + + assert(0<=ptr->bid-1 && ptr->bid-1brdattr & 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; + } + } + break; + /////////////////////////////////////////////////////// + // MyFav Functionality (Require PERM_BASIC) + /////////////////////////////////////////////////////// + case 'y': + if (HasFavEditPerm() && !(IN_CLASS())) { + if (get_current_fav() != NULL || !IS_LISTING_FAV()){ + yank_flag ^= 1; /* FAV <=> BRD */ + } + brdnum = -1; + } + break; + case Ctrl('D'): + if (HasFavEditPerm()) { + if (getans("刪除所有標記[N]?") == 'y'){ + fav_remove_all_tagged_item(); + brdnum = -1; + } + } + break; + case Ctrl('A'): + if (HasFavEditPerm()) { + fav_add_all_tagged_item(); + brdnum = -1; + } + break; + case Ctrl('T'): + if (HasFavEditPerm()) { + 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()) { + // TODO XXX why need symlink here? Can we remove it? + if (make_symbolic_link_interactively(class_bid) < 0) + break; + brdnum = -1; + head = 9999; + } + else if (HasFavEditPerm() && 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 'd': // why don't we enable 'd'? + case 'z': + case 'm': + if (HasFavEditPerm()) { + assert(0<=num && nummyattr & 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 (ch != 'd') // 'd' only deletes something. + { + if (fav_add_board(ptr->bid) == NULL) + vmsg("你的最愛太多了啦 真花心"); + else + ptr->myattr |= NBRD_FAV; + } + } + brdnum = -1; + head = 9999; + } + break; + case 'M': + if (HasFavEditPerm()){ + if (IN_FAVORITE() && IS_LISTING_FAV()){ + imovefav(num); + brdnum = -1; + head = 9999; + } + } + break; + case 'g': + if (HasFavEditPerm() && 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 && numattr |= 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 BRC/fav once per 10 minutes */ + if (now - last_save_fav_and_brc > 10 * 60) { + fav_save(); + brc_finalize(); + + last_save_fav_and_brc = now; + } + break; + + /////////////////////////////////////////////////////// + // Administrator Only + /////////////////////////////////////////////////////// + + case 'F': + case 'f': + if (HasUserPerm(PERM_SYSOP)) { + getbcache(class_bid)->firstchild[cuser.uflag & BRDSORT_FLAG ? 1 : 0] = 0; + brdnum = -1; + } + break; + case 'D': + if (HasUserPerm(PERM_SYSOP) || + (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) { + assert(0<=num && nummyattr & NBRD_SYMBOLIC) { + if (getans("確定刪除連結?[N/y]") == 'y') + delete_symbolic_link(getbcache(ptr->bid), ptr->bid); + } + brdnum = -1; + } + break; + case 'E': + if (HasUserPerm(PERM_SYSOP | PERM_BOARD) || GROUPOP()) { + assert(0<=num && numbrdname); + 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; + + } + } 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/console/brc.c b/console/brc.c new file mode 100644 index 00000000..4317dfd1 --- /dev/null +++ b/console/brc.c @@ -0,0 +1,596 @@ +/* $Id$ */ +#include "bbs.h" + +/** + * 關於本檔案的細節,請見 docs/brc.txt。 + * v3: add last modified time for comment system. double max size. + * original time_t as 'create'. + */ + +// WARNING: Check ../pttbbs.conf, you may have overide these value there +// TODO MAXSIZE may be better smaller to fit into memory page. +#ifndef BRC_MAXNUM +#define BRC_MAXSIZE 49152 /* Effective size of brc rc file, 8192 * 3 * 2 */ +#define BRC_MAXNUM 80 /* Upper bound of brc_num, size of brc_list */ +#endif + +#define BRC_BLOCKSIZE 1024 + +// Note: BRC v3 should already support MAX_BOARD > 65535 and BRC_MAXSIZE > 65535, +// but not widely tested yet. +#if MAX_BOARD > 65535 || BRC_MAXSIZE > 65535 +#error Max number of boards or BRC_MAXSIZE cannot fit in unsigned short, \ +please rewrite brc.c (v2) +#endif + +typedef uint32_t brcbid_t; +typedef uint16_t brcnbrd_t; + +typedef struct { + time4_t create; + time4_t modified; +} brc_rec; + +/* old brc rc file form: + * board_name 15 bytes + * brc_num 1 byte, binary integer + * brc_list brc_num * sizeof(brc_rec) bytes */ + +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 brc_rec brc_list[BRC_MAXNUM]; + +static char * const fn_brc2= ".brc2"; +static char * const fn_brc = ".brc3"; + +/** + * 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(brc_rec); /* 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 brc_rec * +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 (brc_rec*)(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 brc_rec *list) +{ + char * tmp; + if (num > 0 && list[0].create > 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(brc_rec); + if (tmp <= endp) + memcpy(ptr, list, num * sizeof(brc_rec)); /* 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 brc_rec* 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].create < 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(brc_rec); + 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(brc_rec); + 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(brc_rec)); + 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); + } +} + +void +read_brc2(void) +{ + char brcfile[STRLEN]; + int fd; + size_t sz2 = 0, sz3 = 0; + char *cvt = NULL, *cvthead = NULL; + + // brc v2 is using 16 bit for brcbid_t and brcnbrd_t. + uint16_t bid2, num2; + + brcbid_t bid; + brcnbrd_t num; + time4_t create; + brc_rec rec; + + setuserfile(brcfile, fn_brc2); + + if ((fd = open(brcfile, O_RDONLY)) == -1) + return; + + sz2 = dashs(brcfile); + sz3 = sz2 * 2; // max double size + + cvthead = cvt = malloc (sz3); + memset(cvthead, 0, sz3); + // now calculate real sz3 + + while (read(fd, &bid2, sizeof(bid2)) > 0) + { + if (read(fd, &num2, sizeof(num2)) < 1) + break; + + bid = bid2; + num = num2; + + // some brc v2 contains bad structure. + // check pointer here. + if (cvt + sizeof(brcbid_t) + sizeof(brcnbrd_t) - cvthead >= sz3) + break; + + *(brcbid_t*) cvt = bid; cvt += sizeof(brcbid_t); + *(brcnbrd_t*)cvt = num; cvt += sizeof(brcnbrd_t); + + // some brc v2 contains bad structure. + // check pointer here. + for (; num > 0 && (cvt + sizeof(brc_rec) - cvthead) <= sz3 ; num--) + { + if (read(fd, &create, sizeof(create)) < 1) + break; + + rec.create = create; + rec.modified = create; + + *(brc_rec*)cvt = rec; cvt += sizeof(brc_rec); + } + } + close(fd); + + // now cvthead is ready for v3. + sz3 = cvt - cvthead; + brc_get_buf(sz3); + // new size maybe smaller, check brc_alloc instead + if (sz3 > brc_alloc) + sz3 = brc_alloc; + brc_size = sz3; + memcpy(brc_buf, cvthead, sz3); + + free(cvthead); +} + +inline static void +read_brc_buf(void) +{ + char brcfile[STRLEN]; + int fd; + struct stat brcstat; + + if (brc_buf != NULL) + return; + + brc_size = 0; + setuserfile(brcfile, fn_brc); + + if ((fd = open(brcfile, O_RDONLY)) == -1) + { + read_brc2(); + return; + } + + fstat(fd, &brcstat); + brc_get_buf(brcstat.st_size); + brc_size = read(fd, brc_buf, brc_alloc); + close(fd); +} + +/* 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, brc_rec *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(brc_rec)); + return *num; + } + list[0].create = *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 brc_list[n].create) { + if (brc_num < BRC_MAXNUM) + brc_num++; + /* insert frec into brc_list */ + for (i = brc_num - 1; --i >= n; brc_list[i + 1] = brc_list[i]); + brc_list[n] = frec; + brc_changed = 1; + return; + } + } +} + +// return: +// 0 - read +// 1 - unread (by create) +// 2 - unread (by modified) +int +brc_unread_time(int bid, time4_t ftime, time4_t modified) +{ + int i; + int bnum; + const brc_rec *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].create) + return 1; + else if (ftime == blist[i].create) + { + time4_t brcm = blist[i].modified; + if (modified == 0 || brcm == 0) + return 0; + + // bad case... seems like that someone is making -1. + if (modified == (time4_t)-1 || brcm == (time4_t)-1) + return 0; + + // one case is, some special file headers (ex, + // always bottom) may cause issue. They share create + // time (filename) and apply different modify time. + // so let's back to 'greater'. + return modified > brcm ? 2 : 0; + } + } + return 0; +} + +int +brc_unread(int bid, const char *fname, time4_t modified) +{ + int ftime; + + ftime = atoi(&fname[2]); /* this will get the time of the file created */ + + return brc_unread_time(bid, ftime, modified); +} diff --git a/console/cache.c b/console/cache.c new file mode 100644 index 00000000..5a30c24f --- /dev/null +++ b/console/cache.c @@ -0,0 +1,1215 @@ +/* $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<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_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_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) + ;/* 指到第一筆 */ + // piaip Tue Jan 8 09:28:03 CST 2008 + // many people bugged about that their utmp have invalid + // entry on record. + // we found them caused by crash process (DEBUGSLEEPING) which + // may occupy utmp entries even after process was killed. + // because the memory is invalid, it is not safe for those process + // to wipe their utmp entry. it should be done by some external + // daemon. + // however, let's make a little workaround here... + for (; unum > 0 && i >= 0 && ulist[i] >= 0 && + SHM->uinfo[ulist[i]].uid == uid; unum--, i++) + { + if (SHM->uinfo[ulist[i]].mode == DEBUGSLEEPING) + unum ++; + } + if (unum == 0 && i > 0 && ulist[i-1] >= 0 && + SHM->uinfo[ulist[i-1]].uid == uid) + return &SHM->uinfo[ulist[i-1]]; + /* + 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); + } +} + +unsigned int +getutmpmode(void) +{ + if (currutmp) + return currutmp->mode; + return currstat; +} +#endif + +/* + * section - board cache + */ +void touchbtotal(int bid) { + assert(0<=bid-1 && bid-1total[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/" FN_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 && bidBbusystate || 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-1brdname[0]) return; + setbfile(fname, bh->brdname, FN_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-1brdname, FN_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-1total[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-1Bnumber - 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; +} + +const char * +postperm_msg(const char *bname) +{ + register int i; + char buf[PATHLEN]; + boardheader_t *bp = NULL; + + setbfile(buf, bname, fn_water); + if (belong(buf, cuser.userid)) + return "使用者水桶中"; + + if (!strcasecmp(bname, DEFAULT_BOARD)) + return NULL; + + if (!(i = getbnum(bname))) + return "看板不存在"; + + assert(0<=i-1 && i-1brdattr & BRD_GUESTPOST) + return NULL; + + if (!HasUserPerm(PERM_POST)) + return "無發文權限"; + + /* 秘密看板特別處理 */ + if (bp->brdattr & BRD_HIDE) + return NULL; + else if (bp->brdattr & BRD_RESTRICTEDPOST && + !is_hidden_board_friend(i, usernum)) + return "看板限制發文"; + + if (HasUserPerm(PERM_VIOLATELAW) && (bp->level & PERM_VIOLATELAW)) + return NULL; + else if (HasUserPerm(PERM_VIOLATELAW)) + return "罰單未繳"; + + if (!(bp->level & ~PERM_POST)) + return NULL; + if (!HasUserPerm(bp->level & ~PERM_POST)) + return "未達看板要求權限"; + return NULL; +} + +int +haspostperm(const char *bname) +{ + return postperm_msg(bname) == NULL ? 1 : 0; +} + +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-1BM, 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-1uid == 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 */ +/*-------------------------------------------------------*/ +int +filter_aggressive(const char*s) +{ + if ( + /* + strstr(s, "此處放較不適當的爭議性字句") != NULL || + */ + 0 + ) + return 1; + return 0; +} + +int +filter_dirtywords(const char*s) +{ + if ( + strstr(s, "幹你娘") != NULL || + 0) + return 1; + return 0; +} + +#define AGGRESSIVE_FN ".aggressive" +static char drop_aggressive = 0; + +void +load_aggressive_state() +{ + if (dashf(AGGRESSIVE_FN)) + drop_aggressive = 1; + else + drop_aggressive = 0; +} + +void +set_aggressive_state(int s) +{ + FILE *fp = NULL; + if (s) + { + fp = fopen(AGGRESSIVE_FN, "wb"); + fclose(fp); + } else { + remove(AGGRESSIVE_FN); + } +} + +/* 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, aggid, rawid; + + SHM->Pbusystate = 1; + SHM->last_film = 0; + bzero(SHM->notes, sizeof(SHM->notes)); + setapath(pbuf, GLOBAL_NOTE); + setadir(buf, pbuf); + + load_aggressive_state(); + id = aggid = rawid = 0; // effective count, aggressive count, total (raw) count + + if ((fp = fopen(buf, "r"))) { + // .DIR loop + while (fread(&item, sizeof(item), 1, fp)) { + + int chkagg = 0; // should we check aggressive? + + if (item.title[3] != '<' || item.title[8] != '>') + continue; + +#ifdef GLOBAL_NOTE_AGGCHKDIR + // TODO aggressive: only count '<點歌>' section + if (strcmp(item.title+3, GLOBAL_NOTE_AGGCHKDIR) == 0) + chkagg = 1; +#endif + + snprintf(buf, sizeof(buf), "%s/%s/" FN_DIR, + pbuf, item.filename); + + if (!(fp1 = fopen(buf, "r"))) + continue; + + // file loop + 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; + rawid ++; + + // filtering + if (filter_dirtywords(SHM->notes[id])) + { + memset(SHM->notes[id], 0, sizeof(SHM->notes[0])); + rawid --; + } + else if (chkagg && filter_aggressive(SHM->notes[id])) + { + aggid++; + // handle aggressive notes by last detemined state + if (drop_aggressive) + memset(SHM->notes[id], 0, sizeof(SHM->notes[0])); + else + id++; +#ifdef _BBS_UTIL_C_ + // Debug purpose + // printf("found aggressive: %s\n", buf); +#endif + } + else + { + id++; + } + + fclose(fp2); + if (id >= MAX_MOVIE) + break; + + } // end of file loop + fclose(fp1); + + if (id >= MAX_MOVIE) + break; + } // end of .DIR loop + fclose(fp); + + // decide next aggressive state + if (rawid && aggid*3 >= rawid) // if aggressive exceed 1/3 + set_aggressive_state(1); + else + set_aggressive_state(0); + +#ifdef _BBS_UTIL_C_ + printf("id(%d)/agg(%d)/raw(%d)\n", + id, aggid, rawid); +#endif + } + 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) { + strcpy(SHM->home_desc[SHM->home_num], "雲深不知處"); + } + else { + strlcpy(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-1hbfl[bid-1], hbfl, sizeof(hbfl)); +} + +/* 是否通過板友測試. 如果在板友名單中的話傳回 1, 否則為 0 */ +int +is_hidden_board_friend(int bid, int uid) +{ + int i; + + assert(0<=bid-1 && bid-1hbfl[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 1; + } + return 0; +} + +#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/console/cal.c b/console/cal.c new file mode 100644 index 00000000..1f6f9d9d --- /dev/null +++ b/console/cal.c @@ -0,0 +1,630 @@ +/* $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" + +/* Heat:發票 */ +int +vice(int money, const char *item) +{ + char buf[128]; + unsigned int viceserial = (currutmp->lastact % 10000) * 10000 + + random() % 10000; + + // new logic: do not send useless vice tickets + demoney(-money); + if (money < VICE_MIN) + return 0; + + setuserfile(buf, VICE_NEW); + log_filef(buf, LOG_CREAT, "%8.8d\n", viceserial); + snprintf(buf, sizeof(buf), + "%s 花了$%d 編號[%08d]", item, money, viceserial); + mail_id(cuser.userid, buf, "etc/vice.txt", BBSMNAME "經濟部"); + 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\n", cuser.userid); + outs(ANSI_COLOR(1) "注意點歌內容請勿涉及謾罵 人身攻擊 猥褻" + "公然侮辱 誹謗\n" + "若有上述違規情形,站方將保留決定是否公開播放的權利\n" + "如不同意請按 (3) 離開。" ANSI_RESET "\n"); + getdata(18, 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, 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(19, 0, "點歌者(可匿名): ", sender, sizeof(sender), DOECHO, cuser.userid); + getdata(20, 0, "點給(可匿名): ", receiver, sizeof(receiver), DOECHO); + + getdata_str(21, 0, "想要要對他(她)說..:", say, + sizeof(say), DOECHO, "我愛妳.."); + snprintf(save_title, sizeof(save_title), + "%s:%s", sender, say); + getdata_str(22, 0, "寄到誰的信箱(真實 ID 或 E-mail)?", + address, sizeof(address), LCECHO, receiver); + vmsg("接著要選歌囉..進入歌本好好的選一首歌吧..^o^"); + a_menu("點歌歌本", SONGBOOK, 0, 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_filef("etc/osong.log", LOG_CREAT, "id: %-12s ◇ %s 點給 %s : \"%s\", 轉寄至 %s\n", cuser.userid, sender, receiver, say, address); + + if (append_record(OSONGPATH "/" FN_DIR, &mail, sizeof(mail)) != -1) { + cuser.lastsong = now; + /* Jaky 超過 MAX_MOVIE 首歌就開始砍 */ + nsongs = get_num_records(OSONGPATH "/" FN_DIR, sizeof(mail)); + if (nsongs > MAX_MOVIE) { + // XXX race condition + delete_range(OSONGPATH "/" FN_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) +{ + char tmp_from[sizeof(currutmp->from)]; + if (getans("確定要改故鄉?[y/N]") != 'y') + return 0; + reload_money(); + if (cuser.money < 49) + return 0; + if (getdata_buf(b_lines - 1, 0, "請輸入新故鄉:", + tmp_from, sizeof(tmp_from), DOECHO)) { + vice(49, "更改故鄉"); + strlcpy(currutmp->from, tmp_from, sizeof(currutmp->from)); + 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); + + // no need to create default prompt. + // and people usually come this this by accident... + getdata(b_lines - 2, 0, buf, ans, sizeof(ans), LCECHO); + + 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) + { + vmsg("你的錢不夠。"); + return 0; + } + + if (vmsgf("你想購買 %d 封信箱 (要花 %d 元), 確定嗎?[y/N] ", + n, n*1000) != 'y') + return 0; + + vice(n * 1000, "購買信箱"); + inmailbox(n); + vmsgf("已購買信箱。新容量上限: %d", cuser.exmailbox); + 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 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_filef(FN_MONEY, LOG_CREAT, "%-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]")); + if (money < 50) { + usleep(2000000); + } else if (money < 200) { + usleep(500000); + } else { + usleep(100000); + } + return 0; + } + return -1; +} + +int +p_give(void) +{ + give_money_ui(NULL); + return -1; +} + +int +give_money_ui(const char *userid) +{ + int uid; + char id[IDLEN + 1], money_buf[20]; + char passbuf[PASSLEN]; + int m = 0, tries = 3, skipauth = 0; + static time4_t lastauth = 0; + + // TODO prevent macros, we should check something here, + // like user pw/id/... + clear(); + stand_title("給予金錢"); + if (!userid || !*userid) + usercomplete("這位幸運兒的id: ", id); + else { + strlcpy(id, userid, sizeof(id)); + prints("這位幸運兒的id: %s\n", id); + } + move(2, 0); clrtobot(); + + if (!id[0] || !strcasecmp(cuser.userid, id)) + { + vmsg("交易取消!"); + return -1; + } + if (!getdata(2, 0, "要給他多少錢呢: ", money_buf, 7, LCECHO) || + ((m = atoi(money_buf)) <= 0)) + { + vmsg("交易取消!"); + return -1; + } + if ((uid = searchuser(id, id)) == 0) { + vmsg("查無此人!"); + return -1; + } + move(4, 0); + prints("交易內容: %s 將給予 %s : %d 元 (要再扣稅金 %d 元)\n", + cuser.userid, id, m, give_tax(m)); + + if (now - lastauth >= 15*60) // valid through 15 minutes + { + outs(ANSI_COLOR(1;31) "為了避免誤按或是惡意詐騙," + "在完成交易前要重新確認您的身份。" ANSI_RESET); + } else { + outs("你的認證尚未過期,可暫時跳過密碼認證程序。\n"); + // auth is valid. + if (getans("確定進行交易嗎? (y/N): ") == 'y') + skipauth = 1; + else + tries = -1; + } + + while (!skipauth && tries-- > 0) + { + getdata(6, 0, MSG_PASSWD, + passbuf, sizeof(passbuf), NOECHO); + passbuf[8] = '\0'; + if (checkpasswd(cuser.passwd, passbuf)) + { + lastauth = now; + break; + } + if (tries > 0) + vmsgf("密碼錯誤,還有 %d 次機會。", tries); + } + if (tries < 0) + { + vmsg("交易取消!"); + return -1; + } + // vmsg("準備交易。"); + // return -1; + return do_give_money(id, uid, m); +} + +void +resolve_over18(void) +{ + /* get local time */ + struct tm ptime = *localtime4(&now); + + over18 = 0; + /* check if over18 */ + // 照實歲計算,沒生日的當作未滿 18 + if (cuser.year < 1 || cuser.month < 1) + over18 = 0; + else if( (ptime.tm_year - cuser.year) > 18) + over18 = 1; + else if (ptime.tm_year - cuser.year < 18) + over18 = 0; + else if ((ptime.tm_mon+1) > cuser.month) + over18 = 1; + else if ((ptime.tm_mon+1) < cuser.month) + over18 = 0; + else if (ptime.tm_mday >= cuser.day ) + over18 = 1; +} + +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)); + +#ifdef REPORT_PIAIP_MODULES + outs("\n" ANSI_COLOR(1;30) + "Modules powered by piaip:\n" + "\ttelnet protocol, ALOHA fixer, BRC v3\n" +#if defined(USE_PIAIP_MORE) || defined(USE_PMORE) + "\tpmore (piaip's more) 2007 w/Movie\n" +#endif +#ifdef HAVE_GRAYOUT + "\tGrayout Advanced Control 淡入淡出特效系統\n" +#endif +#ifdef EDITPOST_SMARTMERGE + "\tSmart Merge 修文自動合併\n" +#endif +#ifdef EXP_EDIT_UPLOAD + "\t(EXP) Editor Uploader 長文上傳\n" +#endif +#if defined(USE_PFTERM) + "\t(EXP) pfterm (piaip's flat terminal, Perfect Term)\n" +#endif +#if defined(USE_BBSLUA) + "\t(EXP) BBS-Lua\n" +#endif + ANSI_RESET + ); +#endif // REPORT_PIAIP_MODULES + + 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: %u KB, " +#endif +#ifdef __linux__ + "VmData: %d KB, VmStk: %d KB, " +#endif + "idrss: %d KB, isrss: %d KB\n", +#ifdef IA32 + ((unsigned 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", + (long int)ru.ru_utime.tv_sec, + (long int)ru.ru_utime.tv_usec, + (long int)ru.ru_stime.tv_sec, + (long int)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/console/calendar.c b/console/calendar.c new file mode 100644 index 00000000..b87c774a --- /dev/null +++ b/console/calendar.c @@ -0,0 +1,362 @@ +/* $Id$ */ +#include "bbs.h" + +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}; + + assert(1<=m && m<=12); + return leap && m == 2 ? 29 : day[m - 1]; +} + +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, is_leap_year(y)); + return w; +} + +/** + * return 1 if date is invalid + */ +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); if (!y) return 1; + m = strtok_r(NULL, "/", &strtok_pos);if (!m) return 1; + d = strtok_r(NULL, "", &strtok_pos); if (!d) return 1; + + *year = atoi(y); + *month = atoi(m); + *day = atoi(d); + if (*year < 1 || *month < 1 || *month > 12 || + *day < 1 || *day > MonthDay(*month, is_leap_year(*year))) + return 1; + return 0; +} + +/** + * return 1 if date is invalid + */ +static int +ParseEventDate(const char *date, event_t * t) +{ + int retval = ParseDate(date, &t->year, &t->month, &t->day); + if (retval) + return retval; + 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, is_leap_year(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 +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); + clrtoln(b_lines); + move(3, 0); + prints("行事曆格式說明:\n編輯時以一行為單位,如:\n\n# 井號開頭的是註解\n2006/05/04 red 上批踢踢!\n\n其中的 red 是指表示的顏色。"); + pressanykey(); + } + return 0; +} + +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); + i = vmsg("請按 e 編輯行事曆,或其它任意鍵離開。"); + i = tolower(((unsigned char)i) & 0xFF); + if (i == 'e') + { + u_editcalendar(); + } + return 0; +} + diff --git a/console/card.c b/console/card.c new file mode 100644 index 00000000..5f5a1530 --- /dev/null +++ b/console/card.c @@ -0,0 +1,672 @@ +/* $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 元, 現在要分組(加收 %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 元, 還要加牌嗎? [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("好厲害唷! 六張牌還沒爆! 加 %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("呵呵...電腦爆掉了! 你贏了! 可得 %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("平局,退回 %d 元!", JACK); + return 0; + } + if(card_alls_upper(cpu)=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("好厲害唷! 過五關嘍! 加 %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("呵呵...電腦爆掉了! 你贏了! 可得 %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]; + int times = 0; + + setutmpmode(TENHALF); + while (1) { + reload_money(); + if (cuser.money < TEN_HALF) { + outs("您的錢不夠唷!去多發表些有意義的文章再來~~~"); + return 0; + } + + if (times++ % 5 == 0) + { + move(b_lines-2, 0); clrtoeol(); + outs(ANSI_COLOR(1;31) + "警告: 本遊戲由 PttGames 看板評鑑為極度黑店,請小心!" ANSI_RESET); + } + + 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/console/chat.c b/console/chat.c new file mode 100644 index 00000000..6623fbc2 --- /dev/null +++ b/console/chat.c @@ -0,0 +1,597 @@ +/* $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 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 ", "邀請 加入談天室"); + chathelp("[/k]ick ", "將 踢出談天室"); + chathelp("[/o]p ", "將 Op 的權力轉移給 "); + chathelp("[/t]opic ", "換個話題"); + chathelp("[/w]all", "廣播 (站長專用)"); + } else { + // chathelp("[/.]help", "chicken 鬥雞用指令"); + chathelp(" /help op", "談天室管理員專用指令"); + chathelp("[//]help", "MUD-like 社交動詞"); + chathelp("[/a]ct ", "做一個動作"); + chathelp("[/b]ye [msg]", "道別"); + chathelp("[/c]lear", "清除螢幕"); + chathelp("[/j]oin ", "建立或加入談天室"); + chathelp("[/l]ist [room]", "列出談天室使用者"); + chathelp("[/m]sg ", "跟 說悄悄話"); + chathelp("[/n]ick ", "將談天代號換成 "); + chathelp("[/p]ager", "切換呼叫器"); + chathelp("[/q]uery ", "查詢網友"); + chathelp("[/r]oom ", "列出一般談天室"); + chathelp("[/w]ho", "列出本談天室使用者"); + chathelp(" /whoin ", "列出談天室 的使用者"); + chathelp(" /ignore ", "忽略指定使用者的訊息"); + chathelp(" /unignore ", "停止忽略指定使用者的訊息"); + } +} + +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); +} + +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; +static time4_t lastEnter = 0; + +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; + } + + syncnow(); + +#ifdef CHAT_GAPMINS + if ((now - lastEnter)/60 < CHAT_GAPMINS) + { + vmsg("您才剛離開聊天室,裡面正在整理中。請稍後再試。"); + return 0; + } +#endif + +#ifdef CHAT_REGDAYS + if ((now - cuser.firstlogin)/86400 < CHAT_REGDAYS) + { + int i = CHAT_REGDAYS - (now-cuser.firstlogin)/86400 +1; + vmsgf("您還不夠資深喔 (再等 %d 天吧)", i); + return 0; + } +#endif + + 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); + vmsg("系統錯誤。"); + 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(); + } + syncnow(); + lastEnter = now; + + 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, 56); + outs(" /h 查詢指令 /b 離開 "); + 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) { + +#ifdef EXP_ANTIFLOOD + // prevent flooding */ + static time4_t lasttime = 0; + static int flood = 0; + + /* // debug anti flodding + move(b_lines-3, 0); clrtoeol(); + prints("lasttime=%d, now=%d, flood=%d\n", + lasttime, now, flood); + refresh(); + */ + syncnow(); + if (now - lasttime < 3 ) + { + // 3 秒內洗半面是不行的 ((25-5)/2) + if( ++flood > 10 ){ + // flush all input! + drop_input(); + while (wait_input(1, 0)) + { + if (num_in_buf()) + drop_input(); + else + tty_read((unsigned char*)inbuf, sizeof(inbuf)); + } + drop_input(); + vmsg("請勿大量剪貼或造成洗板面的效果。"); + // log? + sleep(2); + continue; + } + } else { + lasttime = now; + flood = 0; + } +#endif // anti-flood + + 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; + + scr_dump(&old_screen); + add_io(0, 0); + t_idle(); + scr_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/console/chc.c b/console/chc.c new file mode 100644 index 00000000..602fc37e --- /dev/null +++ b/console/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\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("\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 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("\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 = NULL; + 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/console/chc_tab.c b/console/chc_tab.c new file mode 100644 index 00000000..4bed88ab --- /dev/null +++ b/console/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/console/chess.c b/console/chess.c new file mode 100644 index 00000000..a86ca766 --- /dev/null +++ b/console/chess.c @@ -0,0 +1,1762 @@ +/* $Id$ */ +#include "bbs.h" +#include "chess.h" +#include + +#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 || !CurrentPlayingGameInfo) + return; + + 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) +{ + char a = 0; + if (info->mode == CHESS_MODE_VERSUS && info->myturn == 0 && + info->constants->log_board) { + ChessGenLogGlobal(info, result); + } + + a = getans((cuser.uflag & DEFBACKUP_FLAG) ? + "是否將棋譜寄回信箱? [Y/n]" : + "是否將棋譜寄回信箱? [y/N]"); + + if (TOBACKUP(a)) + 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) { + scr_dump(&oldscreen); + ChessPlay(info); + scr_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/console/chicken.c b/console/chicken.c new file mode 100644 index 00000000..4de2bb85 --- /dev/null +++ b/console/chicken.c @@ -0,0 +1,1138 @@ +/* $Id$ */ +#include "bbs.h" + +// TODO pull chicken out of userec. +// remove chickenpk. + +#define NUM_KINDS 15 /* 有多少種動物 */ +#define CHICKENLOG "etc/chicken" + +// enable if you want to run live upgrade +// #define CHICKEN_LIVE_UPGRADE + +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}, +}; + +static void time_diff(chicken_t * thechicken); +static int isdeadth(const chicken_t * thechicken, chicken_t *mychicken); + +chicken_t * load_live_chicken(const char *uid) +{ + char fn[PATHLEN]; + int fd = 0; + chicken_t *p = NULL; + + if (!uid || !uid[0]) return NULL; + sethomefile(fn, uid, FN_CHICKEN); + if (!dashf(fn)) return NULL; + fd = open(fn, O_RDWR); + if (fd < 0) return NULL; + + // now fd is valie. open and mmap. + p = mmap(NULL, sizeof(chicken_t), PROT_READ|PROT_WRITE, MAP_SHARED, + fd, 0); + close(fd); + return p; +} + +int load_chicken(const char *uid, chicken_t *mychicken) +{ + char fn[PATHLEN]; + int fd = 0; + + memset(mychicken, 0, sizeof(chicken_t)); + if (!uid || !uid[0]) return 0; + sethomefile(fn, uid, FN_CHICKEN); + if (!dashf(fn)) return 0; + fd = open(fn, O_RDONLY); + if (fd < 0) return 0; + if (read(fd, mychicken, sizeof(chicken_t)) > 0 && mychicken->name[0]) + return 1; + return 0; +} + +void free_live_chicken(chicken_t *p) +{ + if (!p) return; + munmap(p, sizeof(chicken_t)); +} + +void +chicken_query(const char *userid) +{ + chicken_t xchicken; + +#ifdef CHICKEN_LIVE_UPGRADE + // live update + vmsg("PTT 系統進行更新,本週暫停開放寵物查詢。"); + return; +#endif + + if (!load_chicken(userid, &xchicken)) + { + move(1, 0); + clrtobot(); + prints("\n\n%s 並沒有養寵物..", userid); + } else { + time_diff(&xchicken); + if (!isdeadth(&xchicken, NULL)) + { + show_chicken_data(&xchicken, NULL); + prints("\n\n以上是 %s 的寵物資料..", userid); + } else { + move(1, 0); + clrtobot(); + prints("\n\n%s 的寵物死掉了...", userid); + } + } + + pressanykey(); +} + +static int +new_chicken(void) +{ + chicken_t mychicken; + int price, i; + int fd; + char fn[PATHLEN]; + + memset(&mychicken, 0, sizeof(chicken_t)); + + clear(); + move(2, 0); + outs("歡迎光臨 " ANSI_COLOR(33) "◎" ANSI_COLOR(37;44) " " + BBSMNAME "寵物市場 " 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 (n)就可人$100 (m)蘿莉 $77\n" + "[0]不想買了 $0\n"); + i = getans("請選擇你要養的動物:"); + + // since (o) is confusing to some people, we alias 'm' to 'o'. + if (i == 'm') i = 'o'; + + // (m, l) were political person. + // do not make them in a BBS system... + if (i == 'm' || i == 'l') + return 0; + + i -= 'a'; + if (i < 0 || i > NUM_KINDS - 1) + return 0; + + mychicken.type = i; + + price = egg_price[(int)mychicken.type]; + reload_money(); + if (cuser.money < price) { + vmsgf("錢不夠買蛋蛋,蛋蛋要 %d 元", price); + return 0; + } + + while (strlen(mychicken.name) < 3) + { + getdata(8, 0, "幫牠取個好名字:", mychicken.name, + sizeof(mychicken.name), DOECHO); + } + + 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; + + reload_money(); + if (cuser.money < price) + { + vmsg("錢不夠了。"); + return 0; + } + vice(price, "寵物蛋"); + + // flush it + setuserfile(fn, FN_CHICKEN); + fd = open(fn, O_WRONLY|O_CREAT, 0666); + if (fd < 0) + { + vmsg("系統錯誤: 無法建立資料,請至 " GLOBAL_BUGREPORT " 報告。"); + return 0; + } + + write(fd, &mychicken, sizeof(chicken_t)); + close(fd); + + // log data + log_filef(CHICKENLOG, LOG_CREAT, + 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)); + 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" + +static void +show_chicken_picture(const char *fpath) +{ + show_file(fpath, 5, 14, SHOWFILE_ALLOW_ALL); +} + +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 ? BBSMNAME2 "鬥雞場" : BBSMNAME2 "養雞場", 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_chicken_picture(buf); + + 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(chicken_t *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_chicken_picture(CHICKEN_PIC "/eat"); + pressanykey(); + } +} + +static void +ch_clean(chicken_t *mychicken) +{ + mychicken->clean = 0; + mychicken->tiredstrong += + time_change[(int)mychicken->type][TIREDSTRONG] / 3; + show_chicken_picture(CHICKEN_PIC "/clean"); + pressanykey(); +} + +static void +ch_guess(chicken_t *mychicken) +{ + char *guess[3] = {"剪刀", "石頭", "布"}, me, ch, win; + + 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(chicken_t *mychicken) +{ + mychicken->book += time_change[(int)mychicken->type][BOOK]; + mychicken->tiredstrong += time_change[(int)mychicken->type][TIREDSTRONG]; + show_chicken_picture(CHICKEN_PIC "/read"); + pressanykey(); +} + +static void +ch_kiss(chicken_t *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_chicken_picture(CHICKEN_PIC "/kiss"); + pressanykey(); +} + +static void +ch_hit(chicken_t *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_chicken_picture(CHICKEN_PIC "/hit"); + 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_chicken_picture(picture); + pressanykey(); + } else { + vmsg("現金不夠 !!!"); + } + usleep(100000); // sleep 0.1s +} + +static void +ch_eatoo(chicken_t *mychicken) +{ + if (mychicken->oo > 0) { + mychicken->oo--; + mychicken->tiredstrong = 0; + if (mychicken->happy > 5) + mychicken->happy -= 5; + show_chicken_picture(CHICKEN_PIC "/oo"); + pressanykey(); + } +} + +static void +ch_eatmedicine(chicken_t *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_chicken_picture(CHICKEN_PIC "/medicine"); + pressanykey(); + } +} + +static void +ch_kill(chicken_t *mychicken) +{ + int ans; + + ans = getans("棄養要被罰 100 元, 是否要棄養?(y/N)"); + if (ans == 'y') { + + vice(100, "棄養寵物費"); + more(CHICKEN_PIC "/deadth", YEA); + log_filef(CHICKENLOG, LOG_CREAT, + 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 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; + } +} + +/* 依時間變動的資料 */ +static 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(chicken_t *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) +{ + 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_filef(CHICKENLOG, LOG_CREAT, + 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; + } + 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; +} + +static int +isdeadth(const chicken_t * thechicken, chicken_t *mychicken) +{ + int i; + + if (!(i = deadtype(thechicken, mychicken))) + return 0; + return showdeadth(i); +} + +static void +ch_changename(chicken_t *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_filef(CHICKENLOG, LOG_CREAT, + 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) +{ + 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) "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(mychicken); + check_sick(mychicken); + break; + case '2': + ch_eat(mychicken); + check_sick(mychicken); + break; + case '3': + ch_guess(mychicken); + check_sick(mychicken); + break; + case '4': + ch_book(mychicken); + check_sick(mychicken); + break; + case '5': + ch_kiss(mychicken); + break; + case '6': + ch_hit(mychicken); + check_sick(mychicken); + break; + case '7': + ch_buyitem(food_price[(int)mychicken->type], CHICKEN_PIC "/food", + &mychicken->food, 1); + break; + case '8': + ch_eatoo(mychicken); + break; + case '9': + ch_eatmedicine(mychicken); + 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(mychicken); + break; + case 'K': + case 'k': + ch_kill(mychicken); + return 0; + 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]; + int money = price + (random() % price); + price *= 2; + // money is a little less than 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); + 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) { + 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; + thechicken->tiredstrong = 0; + thechicken->weight = thechicken->hp; + // thechicken->lastvisit = now; // really need so? + vice(money, "靈界守衛"); + snprintf(buf, sizeof(buf), + ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) + " OK了 記得餵他點東西 不然可能失效。" + "今天心情好,拿你$%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; + return 0; +} + +void +chicken_toggle_death(const char *uid) +{ + chicken_t *mychicken = load_live_chicken(uid); + +#ifdef CHICKEN_LIVE_UPGRADE + // live update + vmsg("PTT 系統進行更新,本週暫停開放寵物設定。"); + return; +#endif + + if (!uid) + return; + if (!mychicken) + { + vmsgf("%s 沒養寵物。", uid); + } + else if (mychicken->name[0]) + { + mychicken->name[0] = 0; + vmsgf("%s 的寵物被殺死了", uid); + } + else + { + strlcpy(mychicken->name, "[死]", sizeof(mychicken->name)); + vmsgf("%s 的寵物復活了", uid); + } + free_live_chicken(mychicken); +} + +#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0 + +#ifdef CHICKEN_LIVE_UPGRADE +static void +chicken_live_upgrade() +{ + char fn[PATHLEN]; + FILE *fp = NULL; + setuserfile(fn, FN_CHICKEN); + + if (dashf(fn)) + return; + + if (!cuser.old_chicken.name[0] && + !cuser.old_chicken.cbirth && + !cuser.old_chicken.hp_max) + return; + + // write to data. + fp = fopen(fn, "wb"); + fwrite(&cuser.old_chicken, sizeof(chicken_t), 1, fp); + fclose(fp); +#if 0 // enable if you want logs + log_filef("log/chicken_live_upgrade", LOG_CREAT, + "%s upgrade chicken at %s", + cuser.userid, ctime4(&now)); +#endif +} +#endif // CHICKEN_LIVE_UPGRADE + +int +chicken_main(void) +{ + int age; + chicken_t *mychicken = load_live_chicken(cuser.userid); + +#ifdef CHICKEN_LIVE_UPGRADE + if (mychicken == NULL) + { + chicken_live_upgrade(); + mychicken = load_live_chicken(cuser.userid); + } +#endif + + lockreturn0(CHICKEN, LOCK_MULTI); + if (mychicken && !mychicken->name[0]) + { + // possible for recovery + recover_chicken(mychicken); + } + if (!mychicken || !mychicken->name[0]) + { + free_live_chicken(mychicken); + mychicken = NULL; + + // create new? + if (new_chicken()) + mychicken = load_live_chicken(cuser.userid); + + // exit if still no valid data. + if (!mychicken || !mychicken->name[0]) + { + unlockutmpmode(); + free_live_chicken(mychicken); + return 0; + } + } + assert(mychicken); + age = ((now - mychicken->cbirth) / (60 * 60 * 24)); + do { + time_diff(mychicken); + if (isdeadth(mychicken, mychicken)) + break; + show_chicken_data(mychicken, NULL); + } while (select_menu(age, mychicken)); + unlockutmpmode(); + free_live_chicken(mychicken); + return 0; +} + +#ifdef USE_CHICKEN_PK +int +chickenpk(int fd) +{ + chicken_t *mychicken = load_live_chicken(cuser.userid); + chicken_t *ochicken = load_live_chicken(currutmp->mateid); + + char mateid[IDLEN + 1], data[200], buf[200]; + int ch = 0; + + userinfo_t *uin = &SHM->uinfo[currutmp->destuip]; + int r, attmax, i, datac, catched = 0, + count = 0; + + lockreturn0(CHICKEN, LOCK_MULTI); + /* 把對手的id用local buffer記住 */ + strlcpy(mateid, currutmp->mateid, sizeof(mateid)); + + if (!mychicken || !ochicken || + !ochicken->name[0] || !mychicken->name[0]) { + free_live_chicken(mychicken); + free_live_chicken(ochicken); + 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(); + 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, mychicken)) { + 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; + 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, mychicken)); + unlockutmpmode(); + free_live_chicken(mychicken); + free_live_chicken(ochicken); + return 0; +} +#endif // USE_CHICKEN_PK diff --git a/console/convert.c b/console/convert.c new file mode 100644 index 00000000..e5d0f07b --- /dev/null +++ b/console/convert.c @@ -0,0 +1,123 @@ +/* $Id$ */ +#include "bbs.h" + +#ifdef CONVERT + +extern unsigned char *gb2big(unsigned char *, int *, int); +extern unsigned char *big2gb(unsigned char *, int *, int); +extern unsigned char *utf8_uni(unsigned char *, int *, int); +extern unsigned char *uni_utf8(unsigned char *, int *, int); +extern unsigned char *uni2big(unsigned char *, int *, int); +extern 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; + +// enable this in case some day we want to detect +// current type. but right now disable for less memory cost +// int bbs_convert_type = CONV_NORMAL; + +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; + } + // bbs_convert_type = which; +} + +#endif diff --git a/console/crypt.c b/console/crypt.c new file mode 100644 index 00000000..7283c8a6 --- /dev/null +++ b/console/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. (non-const 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 +#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>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 + * 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/console/dark.c b/console/dark.c new file mode 100644 index 00000000..f19184dc --- /dev/null +++ b/console/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/console/edit.c b/console/edit.c new file mode 100644 index 00000000..c6b3a544 --- /dev/null +++ b/console/edit.c @@ -0,0 +1,3886 @@ +/* $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) + * + * FIXME 20071201 piaip + * block selection 不知何時已變為 line level 而非 character level 了, + * 這樣也比較好寫,所以把 blockpnt 拿掉吧! + * + * 20071230 piaip + * BBSmovie 有人作出了 1.9G 的檔案, 看來要分 hard limit 跟 soft limit + * [第 7426572/7426572 頁 (100%) 目前顯示: 第 163384551~163384573 行] + * 當日調查 BBSmovie 看板與精華區,平均檔案皆在 5M 以下 + * 最大的為 16M 的 Haruhi OP (avi 轉檔 with massive ANSI) + * [第 2953/2953 頁 (100%) 目前顯示: 第 64942~64964 行] + * 另外互動迷宮的大小為 + * [第 1408/1408 頁 (100%) 目前顯示: 第 30940~30962 行] + * 是以定義: + * 32M 為 size limit + * 1M 為 line limit + * 又,忽然發現之前 totaln 之類都是 short... 所以 65536 就夠了? + * 後註: 似乎是用 announce 的 append 作出來的,有看到 > --- <- mark。 + */ +#include "bbs.h" + +#define EDIT_SIZE_LIMIT (32768*1024) +#define EDIT_LINE_LIMIT (65530) // (1048576) + +#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 + * 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; + + int currln; /* current line of the article. */ + short currpnt; /* current column of the article. */ + int totaln; /* total lines of the article. */ + int 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; + int blockln; /* the row 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 *sitesig_string; + char *(*substr_fp) (); + + char synparser; // syntax parser + + 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); + if (curr_buf->sitesig_string != NULL) + free(curr_buf->sitesig_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); + } +} + +/** + * 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+B1next)) + 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, off_t offSig) +{ + char buf[WRAPMARGIN + 2]; + int indent_mode0 = curr_buf->indent_mode; + size_t szread = 0; + + assert(fp); + curr_buf->indent_mode = 0; + while (fgets(buf, sizeof(buf), fp)) + { + szread += strlen(buf); + if (offSig < 0 || szread <= offSig) + { + insert_string(buf); + } + else + { + // this is the site sig + break; + } + } + 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 (curr_buf->totaln >= EDIT_LINE_LIMIT) + { + vmsg("檔案已超過最大限制,無法再讀入暫存檔。"); + return; + } + + 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, -1); + 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; + off_t sz = 0; + + 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 (ans[0] != 'w') // 'a' + { + sz = dashs(fp_tmpbuf); + if (sz > EDIT_SIZE_LIMIT) + { + vmsg("暫存檔已超過大小限制,無法再附加。"); + 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[512]; + + 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, sizeof(buf), 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, sizeof(buf), inf) && buf[0] != '\n'); + /* FIXME by MH: + 如果 header 到內文中間沒有空行分隔,會造成 All 以外的模式 + 都引不到內文。 + */ + + if (op == 'a') + while (fgets(buf, sizeof(buf), inf)) { + insert_char(':'); + insert_char(' '); + quote_strip_ansi_inline((unsigned char *)buf); + insert_string(buf); + } + else if (op == 'r') + while (fgets(buf, sizeof(buf), 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, sizeof(buf), inf) && (!strncmp(buf, "※ ", 3))); + while (fgets(buf, sizeof(buf), 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; +} + +/* 檔案處理:讀檔、存檔、標題、簽名檔 */ +off_t loadsitesig(const char *fname); + +static void +read_file(const char *fpath, int splitSig) +{ + FILE *fp; + off_t offSig = -1; + + if (splitSig) + offSig = loadsitesig(fpath); + + 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, offSig); + 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)); +} + +off_t +loadsitesig(const char *fname) +{ + int fd = 0; + off_t sz = 0, ret = -1; + char *start, *sp; + + sz = dashs(fname); + if (sz < 1) + return -1; + fd = open(fname, O_RDONLY); + if (fd < 0) + return -1; + start = (char*)mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); + if (start) + { + sp = start + sz - 4 - 1; // 4 = \n--\n + while (sp > start) + { + if ((*sp == '\n' && strncmp(sp, "\n--\n", 4) == 0) || + (*sp == '\r' && strncmp(sp, "\r--\r", 4) == 0) ) + { + size_t szSig = sz - (sp-start+1); + ret = sp - start + 1; + // allocate string + curr_buf->sitesig_string = (char*) malloc (szSig + 1); + if (curr_buf->sitesig_string) + { + memcpy(curr_buf->sitesig_string, sp+1, szSig); + curr_buf->sitesig_string[szSig] = 0; + } + break; + } + sp --; + } + munmap(start, sz); + } + + close(fd); + return ret; +} + +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 +} + +#ifdef EXP_EDIT_UPLOAD +static void upload_file(void); +#endif // EXP_EDIT_UPLOAD + +static int +write_file(char *fpath, int saveheader, int *islocal, char *mytitle, int upload, int chtitle) +{ + 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("檔案處理"); + move(1,0); + +#ifdef EDIT_UPLOAD_ALLOWALL + upload = 1; +#endif // EDIT_UPLOAD_ALLOWALL + + // common trail + + if (currstat == SMAIL) + outs("[S]儲存"); + else if (local_article) + outs("[L]站內信件 (S)儲存"); + else + outs("[S]儲存 (L)站內信件"); + +#ifdef EXP_EDIT_UPLOAD + if (upload) + outs(" (U)上傳資料"); +#endif // EXP_EDIT_UPLOAD + + if (chtitle) + outs(" (T)改標題"); + + outs(" (A)放棄 (E)繼續 (R/W/D)讀寫刪暫存檔"); + + getdata(2, 0, "確定要儲存檔案嗎? ", ans, 2, LCECHO); + + // avoid lots pots + sleep(1); + + switch (ans[0]) { + case 'a': + outs("文章" ANSI_COLOR(1) " 沒有 " ANSI_RESET "存入"); + aborted = -1; + break; + case 'e': + return KEEP_EDITING; +#ifdef EXP_EDIT_UPLOAD + case 'u': + if (upload) + upload_file(); + return KEEP_EDITING; +#endif // EXP_EDIT_UPLOAD + case 'r': + read_tmpbuf(-1); + return KEEP_EDITING; + case 'w': + write_tmpbuf(); + return KEEP_EDITING; + case 'd': + erase_tmpbuf(); + return KEEP_EDITING; + case 't': + if (!chtitle) + return KEEP_EDITING; + 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; + + // what if currbid == 0? add currstat checking. + if (currstat == POSTING && + postrecord.times > MAX_CROSSNUM-1 && + !is_hidden_board_friend(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) + return aborted; + + if (islocal) + *islocal = local_article; + + if (curr_buf->sitesig_string) + fprintf(fp, curr_buf->sitesig_string); + + if (currstat == POSTING || currstat == SMAIL) + { + addsignature(fp, curr_buf->ifuseanony); + } + else if (currstat == REEDIT) + { +#ifndef ALL_REEDIT_LOG + // why force signature in SYSOP board? + if(strcmp(currboard, GLOBAL_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 0; +} + +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; + } +} + +#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+"))) { + + 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; + textline_t *p; + + if (!has_block_selection()) + return; + + setup_block_begin_end(&begin, &end); + + // the block region is (currln, block) or (blockln, currln). + + if (curr_buf->currln > curr_buf->blockln) { + // case (blockln, currln) + // piaip 2007/1201 在這裡原有 offset-by-one issue + // 如果又遇到,請檢查這附近。 + curr_buf->curr_window_line -= (curr_buf->currln - curr_buf->blockln); + + 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); + } else { + // case (currln, blockln) + } + + // adjust buffer after delete + 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; + } + + // adjust current line + 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--; + } + + // remove buffer + 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->blockline = curr_buf->currline; +} + +enum { + EOATTR_NORMAL = 0x00, + EOATTR_SELECTED = 0x01, // selected (reverse) + EOATTR_MOVIECODE= 0x02, // pmore movie + EOATTR_BBSLUA = 0x04, // BBS Lua (header) + EOATTR_COMMENT = 0x08, // comment syntax + +}; + +static const char *luaKeywords[] = { + "and", "break", "do", "else", "elseif", + "end", "for", "if", "in", "not", "or", + "repeat","return","then","until","while", + NULL +}; + +static const char *luaDataKeywords[] = { + "false", "function", "local", "nil", "true", + NULL +}; + +static const char *luaFunctions[] = { + "assert", "print", "tonumber", "tostring", "type", + NULL +}; + +static const char *luaMath[] = { + "abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "deg", + "exp", "floor", "fmod", "frexp", "ldexp", "log", "log10", "max", "min", + "modf", "pi", "pow", "rad", "random", "randomseed", "sin", "sinh", + "sqrt", "tan", "tanh", + NULL +}; + +static const char *luaTable[] = { + "concat", "insert", "maxn", "remove", "sort", + NULL +}; + +static const char *luaString[] = { + "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", + "lower", "match", "rep", "reverse", "sub", "upper", NULL +}; + +static const char *luaBbs[] = { + "ANSI_COLOR", "ANSI_RESET", "ESC", "addstr", "clear", "clock", + "clrtobot", "clrtoeol", "color", "ctime", "getch","getdata", + "getmaxyx", "getstr", "getyx", "interface", "kball", "kbhit", "kbreset", + "move", "moverel", "now", "outs", "pause", "print", "rect", "refresh", + "setattr", "sitename", "sleep", "strip_ansi", "time", "title", + "userid", "usernick", + NULL +}; + +static const char *luaToc[] = { + "author", "date", "interface", "latestref", + "notes", "title", "version", + NULL +}; + +static const char *luaBit[] = { + "arshift", "band", "bnot", "bor", "bxor", "cast", "lshift", "rshift", + NULL +}; + +static const char *luaStore[] = { + "USER", "GLOBAL", "iolimit", "limit", "load", "save", + NULL +}; + +static const char *luaLibs[] = { + "bbs", "bit", "math", "store", "string", "table", "toc", + NULL +}; +static const char**luaLibAPI[] = { + luaBbs, luaBit, luaMath, luaStore, luaString, luaTable, luaToc, + NULL +}; + +int synLuaKeyword(const char *text, int n, char *wlen) +{ + int i = 0; + const char **tbl = NULL; + if (*text >= 'A' && *text <= 'Z') + { + // normal identifier + while (n-- > 0 && (isalnum(*text) || *text == '_')) + { + text++; + (*wlen) ++; + } + return 0; + } + if (*text >= '0' && *text <= '9') + { + // digits + while (n-- > 0 && (isdigit(*text) || *text == '.' || *text == 'x')) + { + text++; + (*wlen) ++; + } + return 5; + } + if (*text == '#') + { + text++; + (*wlen) ++; + // length of identifier + while (n-- > 0 && (isalnum(*text) || *text == '_')) + { + text++; + (*wlen) ++; + } + return -2; + } + + // ignore non-identifiers + if (!(*text >= 'a' && *text <= 'z')) + return 0; + + // 1st, try keywords + for (i = 0; luaKeywords[i] && *text >= *luaKeywords[i]; i++) + { + int l = strlen(luaKeywords[i]); + if (n < l) + continue; + if (isalnum(text[l])) + continue; + if (strncmp(text, luaKeywords[i], l) == 0) + { + *wlen = l; + return 3; + } + } + for (i = 0; luaDataKeywords[i] && *text >= *luaDataKeywords[i]; i++) + { + int l = strlen(luaDataKeywords[i]); + if (n < l) + continue; + if (isalnum(text[l])) + continue; + if (strncmp(text, luaDataKeywords[i], l) == 0) + { + *wlen = l; + return 2; + } + } + for (i = 0; luaFunctions[i] && *text >= *luaFunctions[i]; i++) + { + int l = strlen(luaFunctions[i]); + if (n < l) + continue; + if (isalnum(text[l])) + continue; + if (strncmp(text, luaFunctions[i], l) == 0) + { + *wlen = l; + return 6; + } + } + for (i = 0; luaLibs[i]; i++) + { + int l = strlen(luaLibs[i]); + if (n < l) + continue; + if (text[l] != '.' && text[l] != ':') + continue; + if (strncmp(text, luaLibs[i], l) == 0) + { + *wlen = l+1; + text += l; text ++; + n -= l; n--; + break; + } + } + + tbl = luaLibAPI[i]; + if (!tbl) + { + // calcualte wlen + while (n-- > 0 && (isalnum(*text) || *text == '_')) + { + text++; + (*wlen) ++; + } + return 0; + } + + for (i = 0; tbl[i]; i++) + { + int l = strlen(tbl[i]); + if (n < l) + continue; + if (isalnum(text[l])) + continue; + if (strncmp(text, tbl[i], l) == 0) + { + *wlen += l; + return 6; + } + } + // luaLib. only + return -6; +} + +/** + * Just like outs, but print out '*' instead of 27(decimal) in the given string. + * + * FIXME column could not start from 0 + */ + +static void +edit_outs_attr_n(const char *text, int n, int attr) +{ + int column = 0; + register unsigned char inAnsi = 0; + register unsigned char ch; + int doReset = 0; + const char *reset = ANSI_RESET; + + // syntax attributes + char fComment = 0, + fSingleQuote = 0, + fDoubleQuote = 0, + fSquareQuote = 0, + fWord = 0; + +#ifdef COLORED_SELECTION + if ((attr & EOATTR_SELECTED) && + (attr & ~EOATTR_SELECTED)) + { + reset = ANSI_COLOR(0;7;36); + doReset = 1; + outs(reset); + } + else +#endif // if not defined, color by priority - selection first + if (attr & EOATTR_SELECTED) + { + reset = ANSI_COLOR(0;7); + doReset = 1; + outs(reset); + } + else if (attr & EOATTR_MOVIECODE) + { + reset = ANSI_COLOR(0;36); + doReset = 1; + outs(reset); + } + else if (attr & EOATTR_BBSLUA) + { + reset = ANSI_COLOR(0;1;31); + doReset = 1; + outs(reset); + } + else if (attr & EOATTR_COMMENT) + { + reset = ANSI_COLOR(0;1;34); + doReset = 1; + outs(reset); + } + +#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 == 1) + { + if(ch == ESC_CHR) + outc('*'); + else + { + outc(ch); + + if(!ANSI_IN_ESCAPE(ch)) + { + inAnsi = 0; + outs(reset); + } + } + + } + else if(ch == ESC_CHR) + { + inAnsi = 1; +#ifdef DBCSAWARE + if(isDBCS == 1) + { + isDBCS = 2; + outs(ANSI_COLOR(1;33) "?"); + outs(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) "?"); + outs(reset); + isDBCS = 0; + continue; + } + else + if(IS_BIG5_HI(ch)) + { + isDBCS = 1; + // peak next char + if(n > 0 && *text == ESC_CHR) + continue; + } +#endif + // Lua Parser! + if (!attr && curr_buf->synparser && !fComment) + { + // syntax highlight! + if (fSquareQuote) { + if (ch == ']' && n > 0 && *(text) == ']') + { + fSquareQuote = 0; + doReset = 0; + // directly print quotes + outc(ch); outc(ch); + text++, n--; + outs(ANSI_RESET); + continue; + } + } else if (fSingleQuote) { + if (ch == '\'') + { + fSingleQuote = 0; + doReset = 0; + // directly print quotes + outc(ch); + outs(ANSI_RESET); + continue; + } + } else if (fDoubleQuote) { + if (ch == '"') + { + fDoubleQuote = 0; + doReset = 0; + // directly print quotes + outc(ch); + outs(ANSI_RESET); + continue; + } + } else if (ch == '-' && n > 0 && *(text) == '-') { + fComment = 1; + doReset = 1; + outs(ANSI_COLOR(0;1;34)); + } else if (ch == '[' && n > 0 && *(text) == '[') { + fSquareQuote = 1; + doReset = 1; + fWord = 0; + outs(ANSI_COLOR(1;35)); + } else if (ch == '\'' || ch == '"') { + if (ch == '"') + fDoubleQuote = 1; + else + fSingleQuote = 1; + doReset = 1; + fWord = 0; + outs(ANSI_COLOR(1;35)); + } else { + // normal words + if (fWord) + { + // inside a word. + if (--fWord <= 0){ + fWord = 0; + doReset = 0; + outc(ch); + outs(ANSI_RESET); + continue; + } + } else if (isalnum(tolower(ch)) || ch == '#') { + char attr[] = ANSI_COLOR(0;1;37); + int x = synLuaKeyword(text-1, n+1, &fWord); + if (fWord > 0) + fWord --; + if (x != 0) + { + // sorry, fixed string here. + // 7 = *[0;1;3? + if (x<0) { attr[4] = '0'; x= -x; } + attr[7] = '0' + x; + prints(attr); + doReset = 1; + } + if (!fWord) + { + outc(ch); + outs(ANSI_RESET); + doReset = 0; + continue; + } + } + } + } + outc(ch); + } + } + + // this must be ANSI_RESET, not "reset". + if(inAnsi || doReset) + outs(ANSI_RESET); +} + +static void +edit_outs_attr(const char *text, int attr) +{ + edit_outs_attr_n(text, scr_cols, attr); +} + +static void +edit_ansi_outs_n(const char *str, int n, int attr) +{ + 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('*'); + } else { + outc(c); + } + } +} + +static void +edit_ansi_outs(const char *str, int attr) +{ + return edit_ansi_outs_n(str, strlen(str), attr); +} + +// old compatible API +void +edit_outs(const char *text) +{ + edit_outs_attr(text, 0); +} + +void +edit_outs_n(const char *text, int n) +{ + edit_outs_attr_n(text, n, 0); +} + + +#define PMORE_USE_ASCII_MOVIE // disable this if you don't enable ascii movie + +#ifdef PMORE_USE_ASCII_MOVIE +// pmore movie header support +unsigned char * + mf_movieFrameHeader(unsigned char *p, unsigned char *end); + +#endif // PMORE_USE_ASCII_MOVIE + +static int +detect_attr(const char *ps, size_t len) +{ + int attr = 0; + +#ifdef PMORE_USE_ASCII_MOVIE + if (mf_movieFrameHeader((unsigned char*)ps, (unsigned char*)ps+len)) + attr |= EOATTR_MOVIECODE; +#endif +#ifdef USE_BBSLUA + if (bbslua_isHeader(ps, ps + len)) + { + attr |= EOATTR_BBSLUA; + if (!curr_buf->synparser) + { + curr_buf->synparser = 1; + // if you need indent, toggle by hotkey. + // enabling indent by default may cause trouble to copy pasters + // curr_buf->indent_mode = 1; + } + } +#endif + return attr; +} + +static inline void +display_textline_internal(textline_t *p, int i) +{ + short tmp; + void (*output)(const char *, int) = edit_outs_attr; + void (*output_n)(const char *, int, int)= edit_outs_attr_n; + + int attr = EOATTR_NORMAL; + + move(i, 0); + clrtoeol(); + + if (!p) { + outc('~'); + outs(ANSI_CLRTOEND); + return; + } + + if (curr_buf->ansimode) { + output = edit_ansi_outs; + output_n = edit_ansi_outs_n; + } + + tmp = curr_buf->currln - curr_buf->curr_window_line + i; + + // parse attribute of line + + // selected attribute? + 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)); // remove me when EOATTR is ready... + attr |= EOATTR_SELECTED; + } + + attr |= detect_attr(p->data, p->len); + +#ifdef DBCSAWARE + if(mbcs_mode && curr_buf->edit_margin > 0) + { + if(curr_buf->edit_margin >= p->len) + { + (*output)("", attr); + } 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, attr); + } + + } else +#endif + (*output)((curr_buf->edit_margin < p->len) ? + &p->data[curr_buf->edit_margin] : "", attr); + + if (attr) + outs(ANSI_RESET); + + // workaround poor terminal + outs(ANSI_CLRTOEND); +} + +static void +refresh_window(void) +{ + register textline_t *p; + register int i; + + for (p = curr_buf->top_of_win, i = 0; i < b_lines; i++) { + display_textline_internal(p, i); + + 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; +} + +#ifdef EXP_EDIT_UPLOAD + +static void +upload_file(void) +{ + size_t szdata = 0; + int c = 1; + char promptmsg = 0; + + clear(); + block_cancel(); + stand_title("上傳文字檔案"); + move(3,0); + outs("利用本服務您可以上傳較大的文字檔 (但不計入稿費)。\n" + "\n" + "上傳期間您打的字暫時不會出現在螢幕上,除了 Ctrl-U 會被轉換為 ANSI \n" + "控制碼的 ESC 外,其它特殊鍵一律沒有作用。\n" + "\n" + "請在您的電腦本機端複製好內容後貼上即可開始傳送。\n"); + + do { + if (!num_in_buf()) + { + move(10, 0); clrtobot(); + prints("\n\n資料接收中... %u 位元組。\n", (unsigned int)szdata); + outs(ANSI_COLOR(1) + "◆全部完成後按下 End 或 ^X/^Q/^C 即可回到編輯畫面。" + ANSI_RESET "\n"); + promptmsg = 0; + } + + c = igetch(); + if (c < 0x100 && isprint2(c)) + { + insert_char(c); + szdata ++; + } + else if (c == Ctrl('U') || c == ESC_CHR) + { + insert_char(ESC_CHR); + szdata ++; + } + else if (c == Ctrl('I')) + { + insert_tab(); + szdata ++; + } + else if (c == '\r' || c == '\n') + { + split(curr_buf->currline, curr_buf->currpnt); + curr_buf->oldcurrline = curr_buf->currline; + szdata ++; + promptmsg = 1; + } + + if (!promptmsg) + promptmsg = (szdata && szdata % 1024 == 0); + + // all other keys are ignored. + } while (c != KEY_END && c != Ctrl('X') && + c != Ctrl('C') && c != Ctrl('Q') && + curr_buf->totaln <= EDIT_LINE_LIMIT && + szdata <= EDIT_SIZE_LIMIT); + + move(12, 0); + prints("傳送結束: 收到 %u 位元組。", (unsigned int)szdata); + vmsgf("回到編輯畫面"); +} + +#endif // EXP_EDIT_UPLOAD + + +/* 編輯處理:主程式、鍵盤處理 */ +int +vedit2(char *fpath, int saveheader, int *islocal, int flags) +{ + 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, (flags & EDITFLAG_TEXTONLY) ? 1 : 0); + } + + 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, BBSMNAME "系統警察", + "用機器人發表文章", "強制離站"); + 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, + (flags & EDITFLAG_UPLOAD) ? 1 : 0, + (flags & EDITFLAG_ALLOWTITLE) ? 1 : 0); + 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 */ + grayout(0, b_lines-1, GRAYOUT_DARK); + ch = vmsg("結束但不儲存 [y/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; + 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': + block_cancel(); + if (curr_buf->totaln >= EDIT_LINE_LIMIT) + { + vmsg("檔案已超過最大限制,無法再增加行數。"); + break; + } + +#ifdef MAX_EDIT_LINE + if(curr_buf->totaln == + ((flags & EDITFLAG_ALLOWLARGE) ? + MAX_EDIT_LINE_LARGE : MAX_EDIT_LINE)) + { + vmsg("已到達最大行數限制。"); + 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), + 0, + 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 Ctrl('O'): // better not use ^O - UNIX not sending. + case KEY_INS: /* Toggle insert/overwrite */ + 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 */ + block_cancel(); + 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 */ + block_cancel(); + 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 */ + block_cancel(); + 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 + { + int attr = EOATTR_NORMAL; + attr |= detect_attr(curr_buf->currline->data, curr_buf->currline->len); + edit_outs_attr(&curr_buf->currline->data[curr_buf->edit_margin], attr); + } + outs(ANSI_RESET ANSI_CLRTOEND); + edit_msg(); + } + } /* redraw */ + } /* main event loop */ + + exit_edit_buffer(); +} + +int +vedit(char *fpath, int saveheader, int *islocal) +{ + return vedit2(fpath, saveheader, islocal, EDITFLAG_ALLOWTITLE); +} + +/* vim:sw=4:nofoldenable + */ diff --git a/console/emaildb.c b/console/emaildb.c new file mode 100644 index 00000000..fa057102 --- /dev/null +++ b/console/emaildb.c @@ -0,0 +1,244 @@ +/* $Id$ */ +#include +#include "bbs.h" + +#define EMAILDB_PATH BBSHOME "/emaildb.db" + +// define FORK model to minimize memory usage. +#define FORK_MODEL + +static int emaildb_open(sqlite3 **Db) { + int rc; + + if ((rc = sqlite3_open(EMAILDB_PATH, Db)) != SQLITE_OK) + return rc; + + // create table if it doesn't exist + rc = sqlite3_exec(*Db, "CREATE TABLE IF NOT EXISTS emaildb (userid TEXT, email TEXT, PRIMARY KEY (userid));" + "CREATE INDEX IF NOT EXISTS email ON emaildb (email);", + NULL, NULL, NULL); + + return rc; +} + +#ifndef INIT_MAIN +int emaildb_check_email(char * email, int email_len) +{ + int count = -1; + pid_t pid = -1; + sqlite3 *Db = NULL; + sqlite3_stmt *Stmt = NULL; + +#ifdef FORK_MODEL + switch((pid = fork())) + { + case -1: // error + break; + + case 0: // child + break; + + default: + waitpid(pid, &count, 0); + count = WEXITSTATUS(count); + // vmsgf(ANSI_RESET "found %d emails", count); + return count; + } +#endif + + if (emaildb_open(&Db) != SQLITE_OK) + goto end; + + if (sqlite3_prepare(Db, "SELECT userid FROM emaildb WHERE email LIKE lower(?);", + -1, &Stmt, NULL) != SQLITE_OK) + goto end; + + if (sqlite3_bind_text(Stmt, 1, email, email_len, SQLITE_STATIC) != SQLITE_OK) + goto end; + + count = 0; + while (sqlite3_step(Stmt) == SQLITE_ROW) { + char *result; + userec_t u; + + if ((result = (char*)sqlite3_column_text(Stmt, 0)) == NULL) + break; + + // ignore my self, because I may be the one going to + // use mail. + if (strcasecmp(result, cuser.userid) == 0) + continue; + + // force update + u.email[0] = 0; + + if (getuser(result, &u)) + if (strcasecmp(email, u.email) == 0) + count++; + } + +end: + if (Stmt != NULL) + if (sqlite3_finalize(Stmt) != SQLITE_OK) + count = -1; + + if (Db != NULL) + sqlite3_close(Db); + + if (pid == 0) + exit(count); + + return count; +} +#endif + +int emaildb_update_email(char * userid, int userid_len, char * email, int email_len) +{ + int ret = -1; + pid_t pid = -1; + +#ifdef FORK_MODEL + switch((pid = fork())) + { + case -1: // error + break; + + case 0: // child + break; + + default: + waitpid(pid, &ret, 0); + ret = WEXITSTATUS(ret); + return ret; + } +#endif + + sqlite3 *Db = NULL; + sqlite3_stmt *Stmt = NULL; + + if (emaildb_open(&Db) != SQLITE_OK) + goto end; + + if (sqlite3_prepare(Db, "REPLACE INTO emaildb (userid, email) VALUES (lower(?),lower(?));", + -1, &Stmt, NULL) != SQLITE_OK) + goto end; + + if (sqlite3_bind_text(Stmt, 1, userid, userid_len, SQLITE_STATIC) != SQLITE_OK) + goto end; + + if (sqlite3_bind_text(Stmt, 2, email, email_len, SQLITE_STATIC) != SQLITE_OK) + goto end; + + if (sqlite3_step(Stmt) == SQLITE_DONE) + ret = 0; + +end: + if (Stmt != NULL) + sqlite3_finalize(Stmt); + if (Db != NULL) + sqlite3_close(Db); + + if (pid == 0) + exit(ret); + + return ret; +} + +#ifdef INIT_MAIN + +// standalone initialize builder + +#define TRANSCATION_PERIOD (4096) +int main() +{ + int fd = 0; + userec_t xuser; + off_t sz = 0, i = 0, valids = 0; + sqlite3 *Db = NULL; + sqlite3_stmt *Stmt = NULL, *tranStart = NULL, *tranEnd = NULL; + + // init passwd + sz = dashs(FN_PASSWD); + fd = open(FN_PASSWD, O_RDONLY); + if (fd < 0 || sz <= 0) + { + fprintf(stderr, "cannot open ~/.PASSWDS.\n"); + return 0; + } + sz /= sizeof(userec_t); + + // init emaildb + if (emaildb_open(&Db) != SQLITE_OK) + { + fprintf(stderr, "cannot initialize emaildb.\n"); + return 0; + } + + if (sqlite3_prepare(Db, "REPLACE INTO emaildb (userid, email) VALUES (lower(?),lower(?));", + -1, &Stmt, NULL) != SQLITE_OK || + sqlite3_prepare(Db, "BEGIN TRANSACTION;", -1, &tranStart, NULL) != SQLITE_OK || + sqlite3_prepare(Db, "COMMIT;", -1, &tranEnd, NULL) != SQLITE_OK) + { + fprintf(stderr, "SQLite 3 internal error.\n"); + return 0; + } + + sqlite3_step(tranStart); + sqlite3_reset(tranStart); + while (read(fd, &xuser, sizeof(xuser)) == sizeof(xuser)) + { + i++; + // got a record + if (strlen(xuser.userid) < 2 || strlen(xuser.userid) > IDLEN) + continue; + if (strlen(xuser.email) < 5) + continue; + + if (sqlite3_bind_text(Stmt, 1, xuser.userid, strlen(xuser.userid), + SQLITE_STATIC) != SQLITE_OK) + { + fprintf(stderr, "\ncannot prepare userid param.\n"); + break; + } + if (sqlite3_bind_text(Stmt, 2, xuser.email, strlen(xuser.email), + SQLITE_STATIC) != SQLITE_OK) + { + fprintf(stderr, "\ncannot prepare email param.\n"); + break; + } + + if (sqlite3_step(Stmt) != SQLITE_DONE) + { + fprintf(stderr, "\ncannot execute statement.\n"); + break; + } + sqlite3_reset(Stmt); + + valids ++; + if (valids % 10 == 0) + fprintf(stderr, "%d/%d (valid: %d)\r", + (int)i, (int)sz, (int)valids); + if (valids % TRANSCATION_PERIOD == 0) + { + sqlite3_step(tranEnd); + sqlite3_step(tranStart); + sqlite3_reset(tranEnd); + sqlite3_reset(tranStart); + } + } + + if (valids % TRANSCATION_PERIOD) + sqlite3_step(tranEnd); + + if (Stmt != NULL) + sqlite3_finalize(Stmt); + + if (Db != NULL) + sqlite3_close(Db); + + close(fd); + return 0; +} +#endif + +// vim: sw=4 diff --git a/console/fav.c b/console/fav.c new file mode 100644 index 00000000..8a6c1e2f --- /dev/null +++ b/console/fav.c @@ -0,0 +1,1320 @@ +/* $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 cmd: FALSE: unset, TRUE: set, EXCH: opposite + */ +void set_attr(fav_type_t *ft, int bit, char cmd){ + if (ft == NULL) + return; + if (cmd == EXCH) + ft->attr ^= bit; + else if (cmd == TRUE) + ft->attr |= bit; + else + ft->attr &= ~bit; + dirty = 1; +} + +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-1bid - 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-1bid - 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 出我的最愛。 + * TODO create default fav, and add SYSOP/PttNewHand (see reginit_fav) + */ +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_number = 0; + 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-1DataTail; 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-1attr; +} + +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); +} + +// create defaults for new user +void reginit_fav(void) +{ + int bid = 0; + + fav_load(); // for creating root + +#ifdef GLOBAL_NEWBIE + bid = getbnum(GLOBAL_NEWBIE); + if (bid > 0) fav_add_board(bid); +#endif + +#ifdef GLOBAL_TEST + bid = getbnum(GLOBAL_TEST); + if (bid > 0) fav_add_board(bid); +#endif + +#ifdef GLOBAL_ASKBOARD + bid = getbnum(GLOBAL_ASKBOARD); + if (bid > 0) fav_add_board(bid); +#endif + +#ifdef GLOBAL_SYSOP + bid = getbnum(GLOBAL_SYSOP); + if (bid > 0) fav_add_board(bid); +#endif + + fav_save(); +} + +#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 + +// vim:ts=8:sw=4 diff --git a/console/file.c b/console/file.c new file mode 100644 index 00000000..3b657acc --- /dev/null +++ b/console/file.c @@ -0,0 +1,186 @@ +/* $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; +} + +/** + * 將 "$key\n" append 到檔案 file 後端 + * @param file 要被 append 的檔 + * @param key 沒有換行的字串 + * @return 成功傳回 0,失敗傳回 -1。 + */ +int file_append_record(const char *file, const char *key) +{ + FILE *fp; + if (!key || !*key) return -1; + if ((fp = fopen(file, "a")) == NULL) + return -1; + flock(fileno(fp), LOCK_EX); + fputs(key, fp); + fputs("\n", fp); + flock(fileno(fp), LOCK_UN); + fclose(fp); + return 0; +} + +/** + * 傳回檔案 file 中 key 所在行數 + */ +int file_find_record(const char *file, const char *key) +{ + FILE *fp; + char buf[STRLEN], *ptr; + int i = 0; + + if ((fp = fopen(file, "r")) == NULL) + return 0; + + while (fgets(buf, STRLEN, fp)) { + char *strtok_pos; + i++; + if ((ptr = strtok_r(buf, str_space, &strtok_pos)) && !strcasecmp(ptr, key)) { + fclose(fp); + return i; + } + } + fclose(fp); + return 0; +} + +/** + * 傳回檔案 file 中是否有 key + */ +int file_exist_record(const char *file, const char *key) +{ + return file_find_record(file, key) > 0 ? 1 : 0; +} + +/** + * 刪除檔案 file 中以 string 開頭的行 + * @param file 要處理的檔案 + * @param string 尋找的 key name + * @param case_sensitive 是否要處理大小寫 + * @return 成功傳回 0,失敗傳回 -1。 + */ +int +file_delete_record(const char *file, const char *string, int case_sensitive) +{ + // TODO nfp 用 tmpfile() 比較好? 不過 Rename 會變慢... + FILE *fp = NULL, *nfp = NULL; + char fnew[PATHLEN]; + char buf[STRLEN + 1]; + int ret = -1, i = 0; + const size_t toklen = strlen(string); + + if (!toklen) + return 0; + + do { + snprintf(fnew, sizeof(fnew), "%s.%3.3X", file, (unsigned int)(random() & 0xFFF)); + if (access(fnew, 0) != 0) + break; + } while (i++ < 10); // max tries = 10 + + if (access(fnew, 0) == 0) return -1; // cannot create temp file. + + i = 0; + if ((fp = fopen(file, "r")) && (nfp = fopen(fnew, "w"))) { + while (fgets(buf, sizeof(buf), fp)) + { + size_t klen = strcspn(buf, str_space); + if (toklen == klen) + { + if (((case_sensitive && strncmp(buf, string, toklen) == 0) || + (!case_sensitive && strncasecmp(buf, string, toklen) == 0))) + { + // found line. skip it. + i++; + continue; + } + } + // other wise, keep the line. + fputs(buf, nfp); + } + fclose(nfp); nfp = NULL; + if (i > 0) + { + if(Rename(fnew, file) < 0) + ret = -1; + else + ret = 0; + } else { + unlink(fnew); + ret = 0; + } + } + if(fp) + fclose(fp); + if(nfp) + fclose(nfp); + return ret; +} + +/** + * 對每一筆 record 做 func 這件事。 + * @param file + * @param func 處理每筆 record 的 handler,為一 function pointer。 + * 第一個參數是檔案中的一行,第二個參數為 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/console/friend.c b/console/friend.c new file mode 100644 index 00000000..4fbc0be3 --- /dev/null +++ b/console/friend.c @@ -0,0 +1,572 @@ +/* $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; + char myboard[IDLEN+1] = ""; + int boardChanged = 0; + + setfriendfile(fpath, type); + + if (currboard && *currboard) + strcpy(myboard, currboard); + + 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]); + } + if (HasUserPerm(PERM_SYSOP)) + outs(" (S) 選擇其他看板的特別名單"); + + getdata(11, 0, "請選擇 或 直接[Enter] 放棄:", buf, 3, LCECHO); + if (!buf[0]) + return; + + if (HasUserPerm(PERM_SYSOP) && buf[0] == 's') + { + Select(); + boardChanged = 1; + } + + j = buf[0] - '1'; + if (j >= type) + j++; + if (!(HasUserPerm(PERM_SYSOP) || currmode & MODE_BOARD) && j >= 5) + { + if (boardChanged) + enter_board(myboard); + 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); + } + if (boardChanged) + enter_board(myboard); +} + +static int +delete_friend_from_file(const char *file, const char *string, int case_sensitive) +{ + FILE *fp = NULL, *nfp = NULL; + char fnew[PATHLEN]; + char genbuf[STRLEN + 1]; + int ret = 0; + + 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); + else + ret = 1; + } + Rename(fnew, file); + } + if(fp) + fclose(fp); + if(nfp) + fclose(nfp); + return ret; +} + +void +friend_delete(const char *uident, int type) +{ + char fn[STRLEN]; + 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[PATHLEN]; + sethomefile(fn, uident, "aloha"); + delete_friend_from_file(fn, thefriend, 0); +} + +void +friend_delete_all(const char *uident, int type) +{ + char buf[PATHLEN], line[PATHLEN]; + 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), cmp_int); + 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-1perm_reload = now; + assert(0<=currbid-1 && currbid-1brdname); + } + friend_load(0); + } +} + +int +t_override(void) +{ + friend_edit(FRIEND_OVERRIDE); + return 0; +} + +int +t_reject(void) +{ + friend_edit(FRIEND_REJECT); + return 0; +} + +int +t_fix_aloha() +{ + char xid[IDLEN+1] = ""; + char fn[PATHLEN] = ""; + + clear(); + stand_title("修正上站通知"); + + outs("這是用來修正某些使用者遇到錯誤的上站通知的問題。\n" + ANSI_COLOR(1) "如果你沒遇到此類問題可直接離開。" ANSI_RESET "\n\n" + "▼如果你遇到有人沒在你的上站通知名單內但又會丟上站通知水球給你,\n" + " 請輸入他的 ID。\n"); + + move(7, 0); + usercomplete("有誰不在你的通知名單內但又會送上站通知水球給您呢? ", xid); + + if (!xid[0]) + { + vmsg("修正結束。"); + return 0; + } + + // check by xid + move(9, 0); + outs("檢查中...\n"); + + // xid in my override list? + setuserfile(fn, "alohaed"); + if (belong(fn, xid)) + { + prints(ANSI_COLOR(1;32) "[%s] 確實在你的上站通知名單內。" + "請編輯 [上站通知名單]。" ANSI_RESET "\n", xid); + vmsg("不需修正。"); + return 0; + } + + sethomefile(fn, xid, "aloha"); + if (delete_friend_from_file(fn, cuser.userid, 0)) + { + outs(ANSI_COLOR(1;33) "已找到錯誤並修復完成。" ANSI_RESET "\n"); + } else { + outs(ANSI_COLOR(1;31) "找不到錯誤... 打錯 ID 了?" ANSI_RESET "\n"); + } + + vmsg("若上站通知錯誤仍持續發生請通知站方處理。"); + return 0; +} + diff --git a/console/gamble.c b/console/gamble.c new file mode 100644 index 00000000..2f3e2dd7 --- /dev/null +++ b/console/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(BBSMNAME "賭盤", 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, SHOWFILE_ALLOW_ALL); + 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("無法準備開獎... 請至 " GLOBAL_BUGREPORT " 報告並附上板名。"); + 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 " MONEYNAME "幣 \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 枚" MONEYNAME "幣\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 枚" MONEYNAME "幣\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", BBSMNAME "賭場"); + } + 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(GLOBAL_SECURITY, buf + 7, outcome, "[馬路探子]"); + + setbfile(buf, bh->brdname, FN_TICKET_RECORD); + unlink(buf); + + setbfile(buf, bh->brdname, FN_TICKET_USER); + post_file(GLOBAL_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/console/go.c b/console/go.c new file mode 100644 index 00000000..44cf9cc8 --- /dev/null +++ b/console/go.c @@ -0,0 +1,1118 @@ +/* $Id$ */ + +#include "bbs.h" +#include + +#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, 2, 1, + 1, 1, 1, 1, 2, + 1, 1, 1, 1, 1, + 2, 1, 1, 3 }; + + 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 = + &BoardPic[BoardPicIndex[board_line] * 4]; + 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; + char buf[ANSILINELEN] = ""; + int i, x, y; + 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; + } + + getyx(&y, &x); + for (i = 1; i <= 22; i++) + { + move(i, 0); + inansistr(buf, sizeof(buf)-1); + fprintf(fp, "%s\n", buf); + } + move(y, x); + + 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\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\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, "\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/console/gomo.c b/console/gomo.c new file mode 100644 index 00000000..90f72c63 --- /dev/null +++ b/console/gomo.c @@ -0,0 +1,590 @@ +/* $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, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 3 }; + + 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 = + &BoardPic[BoardPicIndex[board_line] * 4]; + 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) +{ + char buf[ANSILINELEN] = ""; + const int nStep = info->history.used; + int i, x, y; + + getyx(&y, &x); + for (i = 1; i <= 18; i++) + { + move(i, 0); + inansistr(buf, sizeof(buf)-1); + fprintf(fp, "%s\n", buf); + } + move(y, x); + + fprintf(fp, "\n"); + fprintf(fp, "按 z 可進入打譜模式\n"); + fprintf(fp, "\n"); + + fprintf(fp, "\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("\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("\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/console/guess.c b/console/guess.c new file mode 100644 index 00000000..c5408b88 --- /dev/null +++ b/console/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 " MONEYNAME "$", 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 " MONEYNAME "$", 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 " MONEYNAME "$"); + return 1; + } + if ((money = get_money()) == 0) + return 1; + vice(money, "猜數字"); + + Diff_Random(answer); + move(2, 0); + clrtoeol(); + prints("您下注 :%d " MONEYNAME "$", money); + + getdata_str(4, 0, "您要和電腦比賽嗎? [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 " MONEYNAME "$\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 " MONEYNAME "$\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 " MONEYNAME "$\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 " MONEYNAME "$\n", + cuser.userid, count, money); + fclose(file); + } + } else { + outs("五次猜出來, 還你本錢吧"); + move(18, 35); + clrtoeol(); + prints("你拿回了%d " MONEYNAME "$\n", money); + if ((file = fopen(LOGPASS, "a"))) { + fprintf(file, "id: %s, 第%d次猜中, 拿回了本錢 %d " MONEYNAME "$\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 " MONEYNAME "$\n", cuser.userid, money); + fclose(file); + } + unlockutmpmode(); + pressanykey(); + return 1; +} diff --git a/console/io.c b/console/io.c new file mode 100644 index 00000000..ae78bbcd --- /dev/null +++ b/console/io.c @@ -0,0 +1,1050 @@ +/* $Id$ */ +#include "bbs.h" + +//kcwu: 80x24 一般使用者名單 1.9k, 含 header 2.4k +// 一般文章推文頁約 2590 bytes +#define OBUFSIZE 3072 +#define IBUFSIZE 128 + +/* realXbuf is Xbuf+3 because hz convert library requires buf[-2]. */ +#define CVTGAP (3) + +#ifdef DEBUG +#define register +#endif + +static unsigned char real_outbuf[OBUFSIZE + CVTGAP*2] = " ", + real_inbuf [IBUFSIZE + CVTGAP*2] = " "; + +// use defines instead - it is discovered that sometimes the input/output buffer was overflow, +// without knowing why. +// static unsigned char *outbuf = real_outbuf + 3, *inbuf = real_inbuf + 3; +#define inbuf (real_inbuf +CVTGAP) +#define outbuf (real_outbuf+CVTGAP) + +static int obufsize = 0, ibufsize = 0; +static int icurrchar = 0; + +#ifdef DBG_OUTRPT +// output counter +static unsigned long szTotalOutput = 0, szLastOutput = 0; +extern unsigned char fakeEscape; +unsigned char fakeEscape = 0; + +unsigned char fakeEscFilter(unsigned char c) +{ + if (!fakeEscape) return c; + if (c == ESC_CHR) return '*'; + else if (c == '\n') return 'N'; + else if (c == '\r') return 'R'; + else if (c == '\b') return 'B'; + else if (c == '\t') return 'I'; + return c; +} +#endif // DBG_OUTRPT + +/* ----------------------------------------------------- */ +/* 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; + } + +#ifdef DBG_OUTRPT + // if (0) + { + static char xbuf[128]; + sprintf(xbuf, ESC_STR "[s" ESC_STR "[H" " [%lu/%lu] " ESC_STR "[u", + szLastOutput, szTotalOutput); + write(1, xbuf, strlen(xbuf)); + szLastOutput = 0; + } +#endif // DBG_OUTRPT + + fsync(1); +} + +void +output(const char *s, int len) +{ +#ifdef DBG_OUTRPT + int i = 0; + if (fakeEscape) + for (i = 0; i < obufsize; i++) + outbuf[i] = fakeEscFilter(outbuf[i]); + + szTotalOutput += len; + szLastOutput += len; +#endif // DBG_OUTRPT + + /* Invalid if len >= OBUFSIZE */ + assert(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) +{ + +#ifdef DBG_OUTRPT + c = fakeEscFilter(c); + szTotalOutput ++; + szLastOutput ++; +#endif // DBG_OUTRPT + + 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) +{ + if (ibufsize <= icurrchar) + return 0; + return ibufsize - icurrchar; +} + +int +input_isfull(void) +{ + return ibufsize >= IBUFSIZE; +} + +/* + * 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){ + syncnow(); + return I_TIMEOUT; + } + + if (i_newfd && FD_ISSET(i_newfd, &readfds)){ + syncnow(); + 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 +#ifdef DBG_OUTRPT + // if (0) + { + static char xbuf[128]; + sprintf(xbuf, ESC_STR "[s" ESC_STR "[2;1H [%ld] " + ESC_STR "[u", len); + write(1, xbuf, strlen(xbuf)); + fsync(1); + } +#endif // DBG_OUTRPT + + } while (len <= 0); + + ibufsize = len; + icurrchar = 0; + } + + if (currutmp) { + syncnow(); + /* 3 秒內超過兩 byte 才算 active, anti-antiidle. + * 不過方向鍵等組合鍵不止 1 byte */ + if (now - lastact < 3) + currutmp->lastact = now; + lastact = now; + } + + // CR LF are treated as one. + if (inbuf[icurrchar] == Ctrl('M')) + { + if (++icurrchar < ibufsize && + inbuf[icurrchar] == Ctrl('J')) + icurrchar ++; + return Ctrl('M'); + } + 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 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 == '~') + { + // vt220 style + if (last >= '1' && last <= '6') + return KEY_HOME + (last - '1'); + // else, unknown. + return KEY_UNKNOWN; + } + else if (last == '1') + { + if (ch >= '1' && ch <= '6') + { + // use num_in_buf() to prevent waiting keys + if (num_in_buf() && dogetch() == '~') /* must be '~' */ + return KEY_F1 + ch - '1'; + } + else if (ch >= '7' && ch <= '9') + { + // use num_in_buf() to prevent waiting keys + if (num_in_buf() && dogetch() == '~') /* must be '~' */ + return KEY_F6 + ch - '7'; + } + return KEY_UNKNOWN; + } + else if (last == '2') + { + if (ch >= '0' && ch <= '4') + { + // use inbuf() to prevent waiting keys + if (num_in_buf() && dogetch() == '~') /* hope you are '~' */ + return KEY_F9 + ch - '0'; + } + return KEY_UNKNOWN; + } + // if fall here, then escape sequence is broken. + 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'): + redrawwin(); + refresh(); + 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; + + scr_dump(&old_screen); + my_newfd = i_newfd; + i_newfd = 0; + + t_users(); + + i_newfd = my_newfd; + roll = oldroll; + scr_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; + + scr_dump(&old_screen); + + my_newfd = i_newfd; + i_newfd = 0; + my_write2(); + scr_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; + scr_dump(&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; + + /* 還原螢幕 */ + scr_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; + + // try to do this in getch() level. +#if 0 + case Ctrl('J'): /* Ptt 把 \n 拿掉 */ +#ifdef PLAY_ANGEL + /* Seams some telnet client still send CR LF when changing lines. + CallAngel(); + */ +#endif + continue; +#endif + + 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 bIgnoreBuf) +{ + int sel = 0; + fd_set readfds; + struct timeval tv, *ptv = &tv; + + if(!bIgnoreBuf && num_in_buf() > 0) + return 1; + + FD_ZERO(&readfds); + FD_SET(0, &readfds); + + 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(!bIgnoreBuf && 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; +} + +void +drop_input(void) +{ + icurrchar = ibufsize = 0; +} + +int +peek_input(float f, int c) +{ + int i = 0; + assert (c > 0 && c < ' '); // only ^x keys are safe to be detected. + // other keys may fall into escape sequence. + + if (wait_input(f, 1) && (IBUFSIZE > ibufsize)) + { + int len = tty_read(inbuf + ibufsize, IBUFSIZE - ibufsize); +#ifdef CONVERT + if(len > 0) + len = input_wrapper(inbuf+ibufsize, len); +#endif + if (len > 0) + ibufsize += len; + } + for (i = icurrchar; i < ibufsize; i++) + { + if (inbuf[i] == c) + return 1; + } + return 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, lprompt = 0; + int cx = col, cy = 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 (prompt) + { + lprompt = strlen_noansi(prompt); + cx += lprompt; + } + + if(line == b_lines-msg_occupied) + occupy_msg=1, msg_occupied ++; + + // workaround poor terminal + move_ansi(line, col); + getyx(&line, &col); + clrtoeol(); + + // (line, col) are real starting address + + if (!echo) { + if (prompt) outs(prompt); + len--; + clen = 0; + while ((ch = igetch()) != '\r') { + if (ch == Ctrl('C')) + { + // abort + clen = 0; + if (len > 1) + buf[1] = ch; // workaround for BBS-Lua + break; + } + if (ch == '\177' || ch == Ctrl('H')) { + if (!clen) { + bell(); + continue; + } + clen--; + continue; + } + if (ch>=0x100 || !isprint(ch)) { + bell(); + continue; + } + if (clen >= len) { + bell(); + 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); + + while (1) { + // refresh from prompt + move(line, col); outc(' '); move(line, col); clrtoeol(); + if (prompt) outs(prompt); + + outs(ANSI_COLOR(7)); + outs(buf); + for(i=clen; i<=len; i++) + outc(' '); + outs(ANSI_RESET); + move(cy, cx + currchar); + + if ((ch = igetch()) == '\r') + break; + + if (ch == Ctrl('C')) + { + // abort + clen = currchar = 0; + if (len > 1) + buf[1] = ch; // workaround for BBS-Lua + break; + } + + switch (ch) { + case Ctrl('A'): + case KEY_HOME: + currchar = 0; + break; + + case Ctrl('E'): + case KEY_END: + currchar = clen; + break; + + case KEY_UNKNOWN: + break; + + case KEY_LEFT: + if (currchar <= 0) + break; + --currchar; +#ifdef DBCSAWARE + if(currchar > 0 && ISDBCSAWARE() && + getDBCSstatus((unsigned char*)buf, currchar) == DBCS_TRAILING) + currchar --; +#endif + break; + + case KEY_RIGHT: + if (!buf[currchar]) + break; + ++currchar; +#ifdef DBCSAWARE + if(buf[currchar] && ISDBCSAWARE() && + getDBCSstatus((unsigned char*)buf, currchar) == DBCS_TRAILING) + currchar++; +#endif + break; + + case Ctrl('Y'): + currchar = 0; + case Ctrl('K'): + /* we shoud be able to avoid DBCS issues in ^K mode */ + buf[currchar] = '\0'; + clen = currchar; + break; + + 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); + clen = currchar = strlen(buf); + break; + + case '\177': + case Ctrl('H'): + if (!currchar) + break; +#ifdef DBCSAWARE + if (ISDBCSAWARE() && getDBCSstatus((unsigned char*)buf, + currchar-1) == DBCS_TRAILING) + { + memmove(buf+currchar-1, buf+currchar, clen-currchar+1); + currchar--, clen--; + } +#endif + memmove(buf+currchar-1, buf+currchar, clen-currchar+1); + currchar--, clen--; + break; + + case Ctrl('D'): + case KEY_DEL: + if (!buf[currchar]) + break; +#ifdef DBCSAWARE + if (ISDBCSAWARE() && buf[currchar+1] && getDBCSstatus( + (unsigned char*)buf, currchar+1) == DBCS_TRAILING) + { + memmove(buf+currchar, buf+currchar+1, clen-currchar); + clen --; + } +#endif + memmove(buf+currchar, buf+currchar+1, clen-currchar); + clen --; + break; + + default: + if (echo == NUMECHO && !isdigit(ch)) + { + bell(); + break; + } + if (isprint2(ch) && clen < len && cx + 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; + currchar++; + clen++; + } + break; + } /* end case */ + assert(0<=clen); + } /* end while */ + buf[clen] = '\0'; + + 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(line+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/console/kaede.c b/console/kaede.c new file mode 100644 index 00000000..b3d9c0ae --- /dev/null +++ b/console/kaede.c @@ -0,0 +1,165 @@ +/* $Id$ */ +#include "bbs.h" + +// TODO move stuff to libbbs(or util)/string.c, ... +// this file can be removed (or not?) + +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] ){ + + // secure content + + case 't': // current time + strlcpy(strbuf+w, Cdate(&now), size-w); + w += strlen(strbuf+w); + break; + case 'u': // current online users + w += snprintf(&strbuf[w], size - w, + "%d", SHM->UTMPnumber); + break; + + // insecure content + + case 's': // current user id + strlcpy(strbuf+w, cuser.userid, size-w); + w += strlen(strbuf+w); + break; + case 'n': // current user nickname + strlcpy(strbuf+w, cuser.nickname, size-w); + w += strlen(strbuf+w); + break; + case 'l': // current user logins + w += snprintf(&strbuf[w], size - w, + "%d", cuser.numlogins); + break; + case 'p': // current user posts + w += snprintf(&strbuf[w], size - w, + "%d", cuser.numposts); + 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; +} + +// utility from screen.c +void +outs_n(const char *str, int n) +{ + while (*str && n--) { + outc(*str++); + } +} + +// XXX left-right (for large term) +// TODO someday please add ANSI detection version +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) +{ + int y, x; + getyx(&y, &x); + while (*str && line) { + if (*str == '\n') + { + move(++y, 0); + line--; + } else + { + outc(*str); + } + 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); +} + +// vim:ts=4 diff --git a/console/lovepaper.c b/console/lovepaper.c new file mode 100644 index 00000000..50c643bb --- /dev/null +++ b/console/lovepaper.c @@ -0,0 +1,114 @@ +/* $Id$ */ +#include "bbs.h" +#define DATA "etc/lovepaper.dat" + +int +x_love(void) +{ + char buf1[200], 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, "主 題:", title, + sizeof(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); + strlcpy(save_title, title, sizeof(save_title)); + curredit |= EDIT_MAIL; + if (vedit(path, YEA, NULL) == -1) { + curredit &= ~EDIT_MAIL; + unlink(path); + clear(); + outs("\n\n 放棄寄情書\n"); + pressanykey(); + return -2; + } + curredit &= ~EDIT_MAIL; + 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; + sendalert(receiver, ALERT_NEW_MAIL); + hold_mail(buf1, receiver); + return 1; + } else { + vmsg("本站目前無情書資料庫,請向站長反應。"); + } + fclose(fpo); + return 0; +} diff --git a/console/mail.c b/console/mail.c new file mode 100644 index 00000000..38e19619 --- /dev/null +++ b/console/mail.c @@ -0,0 +1,2092 @@ +/* $Id$ */ +#include "bbs.h" +static int mailkeep = 0, mailsum = 0; +static int mailsumlimit = 0, mailmaxkeep = 0; +static char currmaildir[32]; +static char msg_cc[] = ANSI_COLOR(32) "[群組名單]" ANSI_RESET "\n"; +static char listfile[] = "list.0"; + +// check only 20 mails (one page) is enough. +// #define NEWMAIL_CHECK_RANGE (1) +// checking only 1 mail works more like brc style. +#define NEWMAIL_CHECK_RANGE (5) + +enum SHOWMAIL_MODES { + SHOWMAIL_NORM = 0, + SHOWMAIL_SUM, + SHOWMAIL_RANGE, +}; +static int showmail_mode = SHOWMAIL_NORM; + +int +setforward(void) +{ + char buf[80], ip[50] = "", yn[4]; + FILE *fp; + int flIdiotSent2Self = 0; + int oidlen = strlen(cuser.userid); + + sethomepath(buf, cuser.userid); + strcat(buf, "/.forward"); + if ((fp = fopen(buf, "r"))) { + fscanf(fp, "%" toSTR(sizeof(ip)) "s", ip); + fclose(fp); + } + getdata_buf(b_lines - 1, 0, "請輸入自動轉寄的Email: ", + ip, sizeof(ip), DOECHO); + + /* anti idiots */ + if (strncasecmp(ip, cuser.userid, oidlen) == 0) + { + int addrlen = strlen(ip); + if( addrlen == oidlen || + (addrlen > oidlen && + strcasecmp(ip + oidlen, str_mail_address) == 0)) + flIdiotSent2Self = 1; + } + + if (ip[0] && ip[0] != ' ' && !flIdiotSent2Self) { + getdata(b_lines, 0, "確定開啟自動轉信功\能?(Y/n)", yn, sizeof(yn), + LCECHO); + if (yn[0] != 'n' && (fp = fopen(buf, "w"))) { + fputs(ip, fp); + fclose(fp); + vmsg("設定完成!"); + return 0; + } + } + unlink(buf); + if(flIdiotSent2Self) + vmsg("自動轉寄是不會設定給自己的,想取消用空白就可以了。"); + else + vmsg("取消自動轉信!"); + return 0; +} + +int +toggle_showmail_mode(void) +{ + showmail_mode ++; + showmail_mode %= SHOWMAIL_RANGE; + return FULLUPDATE; +} + +int +built_mail_index(void) +{ + char genbuf[128]; + + move(b_lines - 4, 0); + outs("本功\能只在信箱檔毀損時使用," ANSI_COLOR(1;33) "無法" ANSI_RESET "救回被刪除的信件。\n" + "除非您清楚這個功\能的作用,否則" ANSI_COLOR(1;33) "請不要使用" ANSI_RESET "。\n" + "警告:任意的使用將導致" ANSI_COLOR(1;33) "不可預期的結果" ANSI_RESET "!\n"); + getdata(b_lines - 1, 0, + "確定重建信箱?(y/N)", genbuf, 3, + LCECHO); + if (genbuf[0] != 'y') + return 0; + + snprintf(genbuf, sizeof(genbuf), + BBSHOME "/bin/buildir " BBSHOME "/home/%c/%s > /dev/null", + cuser.userid[0], cuser.userid); + mouts(b_lines - 1, 0, ANSI_COLOR(1;31) "已經處理完畢!! 諸多不便 敬請原諒~" ANSI_RESET); + system(genbuf); + pressanykey(); + return 0; +} + +int +sendalert(const char *userid, int alert) +{ + userinfo_t *uentp = NULL; + int n, tuid, i; + + if ((tuid = searchuser(userid, NULL)) == 0) + return -1; + + n = count_logins(tuid, 0); + for (i = 1; i <= n; i++) + if ((uentp = (userinfo_t *) search_ulistn(tuid, i))) + uentp->alerts |= alert; + return 0; +} + +int +mail_muser(userec_t muser, const char *title, const char *filename) +{ + return mail_id(muser.userid, title, filename, cuser.userid); +} + +int +mail_id(const char *id, const char *title, const char *src, const char *owner) +{ + fileheader_t mhdr; + char dst[128], dirf[128]; + sethomepath(dst, id); + if (stampfile(dst, &mhdr)) + return 0; + strlcpy(mhdr.owner, owner, sizeof(mhdr.owner)); + strlcpy(mhdr.title, title, sizeof(mhdr.title)); + mhdr.filemode = 0; + Copy(src, dst); + + sethomedir(dirf, id); + append_record_forward(dirf, &mhdr, sizeof(mhdr), id); + sendalert(id, ALERT_NEW_MAIL); + return 0; +} + +int +invalidaddr(const char *addr) +{ +#ifdef DEBUG_FWDADDRERR + const char *origaddr = addr; + char errmsg[PATHLEN]; +#endif + + if (*addr == '\0') + return 1; /* blank */ + + while (*addr) { +#ifdef DEBUG_FWDADDRERR + if (not_alnum(*addr) && !strchr("[].@-_+", *addr)) + { + int c = (*addr) & 0xff; + clear(); + move(2,0); + outs( + "您輸入的位址錯誤 (address error)。 \n\n" + "由於最近許\多人反應打入正確的位址(id或email)後系統會判斷錯誤\n" + "但檢查不出原因,所以我們需要正確的錯誤回報。\n\n" + "如果你確實打錯了,請直接略過下面的說明。\n" + "如果你認為你輸入的位址確實是對的,請把下面的訊息複製起來\n" + "並貼到 " GLOBAL_BUGREPORT " 板。本站為造成不便深感抱歉。\n\n" + ANSI_COLOR(1;33)); + sprintf(errmsg, "原始輸入位址: [%s]\n" + "錯誤位置: 第 %d 字元: 0x%02X [ %c ]\n", + origaddr, (int)(addr - origaddr+1), c, c); + outs(errmsg); + outs(ANSI_RESET); + vmsg("請按任意鍵繼續"); + clear(); + return 1; + } +#else + if (not_alnum(*addr) && !strchr("[].@-_", *addr)) + return 1; +#endif + addr++; + } + return 0; +} + +int +m_internet(void) +{ + char receiver[60]; + char title[STRLEN]; + + getdata(20, 0, "收信人:", receiver, sizeof(receiver), DOECHO); + trim(receiver); + if (strchr(receiver, '@') && !invalidaddr(receiver) && + getdata(21, 0, "主 題:", title, sizeof(title), DOECHO)) + do_send(receiver, title); + else { + vmsg("收信人或主題不正確,請重新選取指令"); + } + return 0; +} + +void +m_init(void) +{ + sethomedir(currmaildir, cuser.userid); +} + +static void +loadmailusage(void) +{ + mailkeep=get_num_records(currmaildir,sizeof(fileheader_t)); + mailsum =get_sum_records(currmaildir, sizeof(fileheader_t)); +} + +void +setupmailusage(void) +{ // Ptt: get_sum_records is a bad function + int max_keepmail = MAX_KEEPMAIL; +#ifdef PLAY_ANGEL + if (HasUserPerm(PERM_SYSSUPERSUBOP | PERM_ANGEL)) +#else + if (HasUserPerm(PERM_SYSSUPERSUBOP)) +#endif + { + mailsumlimit = 900; + max_keepmail = 700; + } + else if (HasUserPerm(PERM_SYSSUBOP | PERM_ACCTREG | PERM_PRG | + PERM_ACTION | PERM_PAINT)) { + mailsumlimit = 700; + max_keepmail = 500; + } else if (HasUserPerm(PERM_BM)) { + mailsumlimit = 500; + max_keepmail = 300; + } else if (HasUserPerm(PERM_LOGINOK)) + mailsumlimit = 200; + else + mailsumlimit = 50; + mailsumlimit += (cuser.exmailbox + ADD_EXMAILBOX) * 10; + mailmaxkeep = max_keepmail + cuser.exmailbox; + loadmailusage(); +} + +#define MAILBOX_LIM_OK 0 +#define MAILBOX_LIM_KEEP 1 +#define MAILBOX_LIM_SUM 2 +static int +chk_mailbox_limit(void) +{ + if (HasUserPerm(PERM_SYSOP) || HasUserPerm(PERM_MAILLIMIT)) + return MAILBOX_LIM_OK; + + if (!mailkeep) + setupmailusage(); + + if (mailkeep > mailmaxkeep) + return MAILBOX_LIM_KEEP; + if (mailsum > mailsumlimit) + return MAILBOX_LIM_SUM; + return MAILBOX_LIM_OK; +} + +int +chkmailbox(void) +{ + m_init(); + + switch (chk_mailbox_limit()) { + case MAILBOX_LIM_KEEP: + bell(); + bell(); + vmsgf("您保存信件數目 %d 超出上限 %d, 請整理", mailkeep, mailmaxkeep); + return mailkeep; + + case MAILBOX_LIM_SUM: + bell(); + bell(); + vmsgf("信箱容量(大小,非件數) %d 超出上限 %d, " + "請砍過長的水球記錄或信件", mailsum, mailsumlimit); + if(showmail_mode != SHOWMAIL_SUM) + { + showmail_mode = SHOWMAIL_SUM; + vmsg("信箱顯示模式已自動改為顯示大小,請盡速整理"); + } + return mailsum; + + default: + return 0; + } +} + +static void +do_hold_mail(const char *fpath, const char *receiver, const char *holder) +{ + char buf[80], title[128]; + + fileheader_t mymail; + + sethomepath(buf, holder); + stampfile(buf, &mymail); + + mymail.filemode = FILE_READ ; + strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner)); + if (receiver) { + snprintf(title, sizeof(title), "(%s) %s", receiver, save_title); + strlcpy(mymail.title, title, sizeof(mymail.title)); + } else + strlcpy(mymail.title, save_title, sizeof(mymail.title)); + + sethomedir(title, holder); + + unlink(buf); + Copy(fpath, buf); + append_record_forward(title, &mymail, sizeof(mymail), holder); +} + +void +hold_mail(const char *fpath, const char *receiver) +{ + char buf[4]; + + getdata(b_lines - 1, 0, + (cuser.uflag & DEFBACKUP_FLAG) ? + "已順利寄出,是否自存底稿(Y/N)?[Y] " : + "已順利寄出,是否自存底稿(Y/N)?[N] ", + buf, sizeof(buf), LCECHO); + + if (TOBACKUP(buf[0])) + do_hold_mail(fpath, receiver, cuser.userid); +} + +int +do_send(const char *userid, const char *title) +{ + fileheader_t mhdr; + char fpath[STRLEN]; + char receiver[IDLEN + 1]; + char genbuf[200]; + int internet_mail, i; + userec_t xuser; + + STATINC(STAT_DOSEND); + if (strchr(userid, '@')) + internet_mail = 1; + else { + internet_mail = 0; + if (!getuser(userid, &xuser)) + return -1; + if (!(xuser.userlevel & PERM_READMAIL)) + return -3; + + curredit |= EDIT_MAIL; + curredit &= ~EDIT_ITEM; + } + /* process title */ + if (title) + strlcpy(save_title, title, sizeof(save_title)); + else { + char tmp_title[STRLEN-20]; + getdata(2, 0, "主題:", tmp_title, sizeof(tmp_title), DOECHO); + strlcpy(save_title, tmp_title, sizeof(save_title)); + } + + setutmpmode(SMAIL); + + fpath[0] = '\0'; + + if (internet_mail) { + int res, ch; + + if (vedit(fpath, NA, NULL) == -1) { + unlink(fpath); + clear(); + return -2; + } + clear(); + prints("信件即將寄給 %s\n標題為:%s\n確定要寄出嗎? (Y/N) [Y]", + userid, save_title); + ch = igetch(); + switch (ch) { + case 'N': + case 'n': + outs("N\n信件已取消"); + res = -2; + break; + default: + outs("Y\n請稍候, 信件傳遞中...\n"); + res = +#ifndef USE_BSMTP + bbs_sendmail(fpath, save_title, userid); +#else + bsmtp(fpath, save_title, userid); +#endif + hold_mail(fpath, userid); + } + unlink(fpath); + return res; + } else { + strlcpy(receiver, userid, sizeof(receiver)); + sethomepath(genbuf, userid); + stampfile(genbuf, &mhdr); + strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner)); + if (vedit(genbuf, YEA, NULL) == -1) { + unlink(genbuf); + clear(); + return -2; + } + /* why not make title here? */ + strlcpy(mhdr.title, save_title, sizeof(mhdr.title)); + clear(); + sethomefile(fpath, userid, FN_OVERRIDES); + i = belong(fpath, cuser.userid); + sethomefile(fpath, userid, FN_REJECT); + + if (i || !belong(fpath, cuser.userid)) {/* Ptt: 用belong有點討厭 */ + sethomedir(fpath, userid); + if (append_record_forward(fpath, &mhdr, sizeof(mhdr), userid) == -1) + return -1; + sendalert(userid,ALERT_NEW_MAIL); + } + hold_mail(genbuf, userid); + return 0; + } +} + +void +my_send(const char *uident) +{ + switch (do_send(uident, NULL)) { + case -1: + outs(err_uid); + break; + case -2: + outs(msg_cancel); + break; + case -3: + prints("使用者 [%s] 無法收信", uident); + break; + } + pressanykey(); +} + +int +m_send(void) +{ + char uident[40]; + + stand_title("且聽風的話"); + usercomplete(msg_uid, uident); + showplans(uident); + if (uident[0]) + my_send(uident); + return 0; +} + +/* 群組寄信、回信 : multi_send, multi_reply */ +static void +multi_list(int *reciper) +{ + char uid[16]; + char genbuf[200]; + + while (1) { + stand_title("群組寄信名單"); + ShowNameList(3, 0, msg_cc); + move(1, 0); + outs("(I)引入好友 (O)引入上線通知 (N)引入新文章通知 (0-9)引入其他特別名單"); + getdata(2, 0, + "(A)增加 (D)刪除 (M)確認寄信名單 (Q)取消 ?[M]", + genbuf, 4, LCECHO); + switch (genbuf[0]) { + case 'a': + while (1) { + move(1, 0); + usercomplete("請輸入要增加的代號(只按 ENTER 結束新增): ", uid); + if (uid[0] == '\0') + break; + + move(2, 0); + clrtoeol(); + + if (!searchuser(uid, uid)) + outs(err_uid); + else if (!InNameList(uid)) { + AddNameList(uid); + (*reciper)++; + } + ShowNameList(3, 0, msg_cc); + } + break; + case 'd': + while (*reciper) { + move(1, 0); + namecomplete("請輸入要刪除的代號(只按 ENTER 結束刪除): ", uid); + if (uid[0] == '\0') + break; + if (RemoveNameList(uid)) + (*reciper)--; + ShowNameList(3, 0, msg_cc); + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + listfile[5] = genbuf[0]; + genbuf[0] = '1'; + case 'i': + setuserfile(genbuf, genbuf[0] == '1' ? listfile : fn_overrides); + ToggleNameList(reciper, genbuf, msg_cc); + break; + case 'o': + setuserfile(genbuf, "alohaed"); + ToggleNameList(reciper, genbuf, msg_cc); + break; + case 'n': + setuserfile(genbuf, "postlist"); + ToggleNameList(reciper, genbuf, msg_cc); + break; + case 'q': + *reciper = 0; + return; + default: + return; + } + } +} + +static void +multi_send(char *title) +{ + FILE *fp; + struct word_t *p = NULL; + fileheader_t mymail; + char fpath[TTLEN], *ptr; + int reciper, listing; + char genbuf[256]; + + CreateNameList(); + listing = reciper = 0; + if (*quote_file) { + AddNameList(quote_user); + reciper = 1; + fp = fopen(quote_file, "r"); + assert(fp); + while (fgets(genbuf, sizeof(genbuf), fp)) { + if (strncmp(genbuf, "※ ", 3)) { + if (listing) + break; + } else { + if (listing) { + char *strtok_pos; + ptr = genbuf + 3; + for (ptr = strtok_r(ptr, " \n\r", &strtok_pos); + ptr; + ptr = strtok_r(NULL, " \n\r", &strtok_pos)) { + if (searchuser(ptr, ptr) && !InNameList(ptr) && + strcmp(cuser.userid, ptr)) { + AddNameList(ptr); + reciper++; + } + } + } else if (!strncmp(genbuf + 3, "[通告]", 6)) + listing = 1; + } + } + fclose(fp); + ShowNameList(3, 0, msg_cc); + } + multi_list(&reciper); + move(1, 0); + clrtobot(); + + if (reciper) { + setutmpmode(SMAIL); + if (title) + do_reply_title(2, title); + else { + getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO); + snprintf(save_title, sizeof(save_title), "[通告] %s", fpath); + } + + setuserfile(fpath, fn_notes); + + if ((fp = fopen(fpath, "w"))) { + fprintf(fp, "※ [通告] 共 %d 人收件", reciper); + listing = 80; + + for (p = toplev; p; p = p->next) { + reciper = strlen(p->word) + 1; + if (listing + reciper > 75) { + listing = reciper; + fprintf(fp, "\n※"); + } else + listing += reciper; + + fprintf(fp, " %s", p->word); + } + memset(genbuf, '-', 75); + genbuf[75] = '\0'; + fprintf(fp, "\n%s\n\n", genbuf); + fclose(fp); + } + curredit |= EDIT_LIST; + + if (vedit(fpath, YEA, NULL) == -1) { + unlink(fpath); + curredit = 0; + vmsg(msg_cancel); + return; + } + listing = 80; + + for (p = toplev; p; p = p->next) { + reciper = strlen(p->word) + 1; + if (listing + reciper > 75) { + listing = reciper; + outc('\n'); + } else { + listing += reciper; + outc(' '); + } + outs(p->word); + if (searchuser(p->word, p->word) && strcmp(STR_GUEST, p->word)) { + sethomefile(genbuf, p->word, FN_OVERRIDES); + if (!belong(genbuf, cuser.userid)) { // not friend, check if rejected + sethomefile(genbuf, p->word, FN_REJECT); + if (belong(genbuf, cuser.userid)) + continue; + } + sethomepath(genbuf, p->word); + } else + continue; + stampfile(genbuf, &mymail); + unlink(genbuf); + Copy(fpath, genbuf); + + strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); + strlcpy(mymail.title, save_title, sizeof(mymail.title)); + mymail.filemode |= FILE_MULTI; /* multi-send flag */ + sethomedir(genbuf, p->word); + if (append_record_forward(genbuf, &mymail, sizeof(mymail), p->word) == -1) + vmsg(err_uid); + sendalert(p->word, ALERT_NEW_MAIL); + } + hold_mail(fpath, NULL); + unlink(fpath); + curredit = 0; + } else + vmsg(msg_cancel); +} + +static int +multi_reply(int ent, fileheader_t * fhdr, const char *direct) +{ + if (!fhdr || !fhdr->filename[0]) + return DONOTHING; + + if (!(fhdr->filemode & FILE_MULTI)) + return mail_reply(ent, fhdr, direct); + + stand_title("群組回信"); + strlcpy(quote_user, fhdr->owner, sizeof(quote_user)); + setuserfile(quote_file, fhdr->filename); + if (!dashf(quote_file)) + { + vmsg("原檔案已消失。"); + return FULLUPDATE; + } + multi_send(fhdr->title); + quote_user[0]='\0'; + quote_file[0]='\0'; + return FULLUPDATE; +} + +int +mail_list(void) +{ + stand_title("群組作業"); + multi_send(NULL); + return 0; +} + +int +mail_all(void) +{ + FILE *fp; + fileheader_t mymail; + char fpath[TTLEN]; + char genbuf[200]; + int i, unum; + char *userid; + + stand_title("給所有使用者的系統通告"); + setutmpmode(SMAIL); + getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO); + snprintf(save_title, sizeof(save_title), + "[系統通告]" ANSI_COLOR(1;32) " %s" ANSI_RESET, fpath); + + setuserfile(fpath, fn_notes); + + if ((fp = fopen(fpath, "w"))) { + fprintf(fp, "※ [" ANSI_COLOR(1) "系統通告" ANSI_RESET "] 這是封給所有使用者的信\n"); + fprintf(fp, "-----------------------------------------------------" + "----------------------\n"); + fclose(fp); + } + *quote_file = 0; + + curredit |= EDIT_MAIL; + curredit &= ~EDIT_ITEM; + if (vedit(fpath, YEA, NULL) == -1) { + curredit = 0; + unlink(fpath); + outs(msg_cancel); + pressanykey(); + return 0; + } + curredit = 0; + + setutmpmode(MAILALL); + stand_title("寄信中..."); + + sethomepath(genbuf, cuser.userid); + stampfile(genbuf, &mymail); + unlink(genbuf); + Copy(fpath, genbuf); + unlink(fpath); + strcpy(fpath, genbuf); + + strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); /* 站長 ID */ + strlcpy(mymail.title, save_title, sizeof(mymail.title)); + + sethomedir(genbuf, cuser.userid); + if (append_record_forward(genbuf, &mymail, sizeof(mymail), cuser.userid) == -1) + outs(err_uid); + + for (unum = SHM->number, i = 0; i < unum; i++) { + if (bad_user_id(SHM->userid[i])) + continue; /* Ptt */ + + userid = SHM->userid[i]; + if (strcmp(userid, STR_GUEST) && strcmp(userid, "new") && + strcmp(userid, cuser.userid)) { + sethomepath(genbuf, userid); + stampfile(genbuf, &mymail); + unlink(genbuf); + Copy(fpath, genbuf); + + strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); + strlcpy(mymail.title, save_title, sizeof(mymail.title)); + /* mymail.filemode |= FILE_MARKED; Ptt 公告改成不會mark */ + sethomedir(genbuf, userid); + if (append_record_forward(genbuf, &mymail, sizeof(mymail), userid) == -1) + outs(err_uid); + vmsgf("%*s %5d / %5d", IDLEN + 1, userid, i + 1, unum); + } + } + return 0; +} + +int +mail_mbox(void) +{ + char cmd[100]; + fileheader_t fhdr; + + snprintf(cmd, sizeof(cmd), "/tmp/%s.uu", cuser.userid); + snprintf(fhdr.title, sizeof(fhdr.title), "%s 私人資料", cuser.userid); + doforward(cmd, &fhdr, 'Z'); + return 0; +} + +static int +m_forward(int ent, fileheader_t * fhdr, const char *direct) +{ + char uid[STRLEN]; + + stand_title("轉達信件"); + usercomplete(msg_uid, uid); + if (uid[0] == '\0') + return FULLUPDATE; + + strlcpy(quote_user, fhdr->owner, sizeof(quote_user)); + setuserfile(quote_file, fhdr->filename); + snprintf(save_title, sizeof(save_title), "%.64s (fwd)", fhdr->title); + move(1, 0); + clrtobot(); + prints("轉信給: %s\n標 題: %s\n", uid, save_title); + + switch (do_send(uid, save_title)) { + case -1: + outs(err_uid); + break; + case -2: + outs(msg_cancel); + break; + case -3: + prints("使用者 [%s] 無法收信", uid); + break; + } + pressanykey(); + quote_user[0]='\0'; + quote_file[0]='\0'; + if (strcasecmp(uid, cuser.userid) == 0) + return DIRCHANGED; + return FULLUPDATE; +} + +struct ReadNewMailArg { + int idc; + int *delmsgs; + int delcnt; + int mrd; +}; + +static int +read_new_mail(void * voidfptr, void *optarg) +{ + fileheader_t *fptr=(fileheader_t*)voidfptr; + struct ReadNewMailArg *arg=(struct ReadNewMailArg*)optarg; + char done = NA, delete_it; + char fname[PATHLEN]; + char genbuf[4]; + + arg->idc++; + // XXX fptr->filename may be invalid. + if (fptr->filemode || !fptr->filename[0]) + return 0; + clear(); + move(10, 0); + prints("您要讀來自[%s]的訊息(%s)嗎?", fptr->owner, fptr->title); + getdata(11, 0, "請您確定(Y/N/Q)?[Y] ", genbuf, 3, DOECHO); + if (genbuf[0] == 'q') + return QUIT; + if (genbuf[0] == 'n') + return 0; + + setuserfile(fname, fptr->filename); + fptr->filemode |= FILE_READ; + if (substitute_record(currmaildir, fptr, sizeof(*fptr), arg->idc)) + return -1; + + arg->mrd = 1; + delete_it = NA; + while (!done) { + int more_result = more(fname, YEA); + + switch (more_result) { + case RET_DOREPLY: + mail_reply(arg->idc, fptr, currmaildir); + return FULLUPDATE; + case RET_DOREPLYALL: + multi_reply(arg->idc, fptr, currmaildir); + return FULLUPDATE; + case RET_DORECOMMEND: // we don't accept this. + return FULLUPDATE; + case -1: + return READ_SKIP; + case 0: + break; + default: + return more_result; + } + + outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0); + + switch (igetch()) { + case 'r': + case 'R': + mail_reply(arg->idc, fptr, currmaildir); + break; + case 'y': + multi_reply(arg->idc, fptr, currmaildir); + break; + case 'x': + m_forward(arg->idc, fptr, currmaildir); + break; + case 'd': + case 'D': + delete_it = YEA; + default: + done = YEA; + } + } + if (delete_it) { + if(arg->delcnt==1000) { + vmsg("一次最多刪 1000 封信"); + return 0; + } + clear(); + prints("刪除信件《%s》", fptr->title); + getdata(1, 0, msg_sure_ny, genbuf, 2, LCECHO); + if (genbuf[0] == 'y') { + if(arg->delmsgs==NULL) { + arg->delmsgs=(int*)malloc(sizeof(int)*1000); + if(arg->delmsgs==NULL) { + vmsg("失敗, 請洽站長"); + return 0; + } + } + unlink(fname); + arg->delmsgs[arg->delcnt++] = arg->idc; + + loadmailusage(); + } + } + clear(); + return 0; +} + +void setmailalert() +{ + if(load_mailalert(cuser.userid)) + currutmp->alerts |= ALERT_NEW_MAIL; + else + currutmp->alerts &= ~ALERT_NEW_MAIL; +} +int +m_new(void) +{ + struct ReadNewMailArg arg; + clear(); + setutmpmode(RMAIL); + memset(&arg, 0, sizeof(arg)); + clear(); + curredit |= EDIT_MAIL; + curredit &= ~EDIT_ITEM; + if (apply_record(currmaildir, read_new_mail, sizeof(fileheader_t), &arg) == -1) { + if(arg.delmsgs) + free(arg.delmsgs); + vmsg("沒有新信件了"); + return -1; + } + curredit = 0; + setmailalert(); + while (arg.delcnt--) + delete_record(currmaildir, sizeof(fileheader_t), arg.delmsgs[arg.delcnt]); + if(arg.delmsgs) + free(arg.delmsgs); + vmsg(arg.mrd ? "信已閱\畢" : "沒有新信件了"); + return -1; +} + +static void +mailtitle(void) +{ + char buf[STRLEN]; + int msglen = 0; + + showtitle("郵件選單", BBSName); + prints("[←]離開[↑↓]選擇[→]閱\讀信件 [X]轉錄看板[F]轉寄站外 " + " [O]站外信:%s [h]求助\n" + ANSI_COLOR(7) " 編號 %s 作 者 信 件 標 題" + "", + REJECT_OUTTAMAIL ? ANSI_COLOR(31) "關" ANSI_RESET : "開", + (showmail_mode == SHOWMAIL_SUM) ? "大 小":"日 期"); + + /* 43 columns in length, used later. */ + buf[0] = 0; + + if (mailsumlimit) + { + /* warning: snprintf returns length "if not limited". + * however if this case, they should be the same. */ + + msglen = snprintf(buf, sizeof(buf), + ANSI_COLOR(32) + " (容量:%d/%dk %d/%d篇) ", + mailsum, mailsumlimit, + mailkeep, mailmaxkeep); + msglen -= strlen(ANSI_COLOR(32)); + } + outslr("", 44, buf, msglen); + outs(ANSI_RESET); +} + +static void +maildoent(int num, fileheader_t * ent) +{ + char *title, *mark, *color = NULL, type = ' '; + char datepart[6]; + char isonline = 0; + + if (ent->filemode & FILE_MARKED) + { + type = (ent->filemode & FILE_READ) ? + 'm' : 'M'; + } + else if (ent->filemode & FILE_REPLIED) + { + type = (ent->filemode & FILE_READ) ? + 'r' : 'R'; + } + else + { + type = (ent->filemode & FILE_READ) ? + ' ' : '+'; + } + + if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN)) + type = 'D'; + + title = subject(mark = ent->title); + if (title == mark) { + color = ANSI_COLOR(1;31); + mark = "◇"; + } else { + color = ANSI_COLOR(1;33); + mark = "R:"; + } + + strlcpy(datepart, ent->date, sizeof(datepart)); + + isonline = query_online(ent->owner); + + switch(showmail_mode) + { + case SHOWMAIL_SUM: + { + /* evaluate size */ + size_t filesz = 0; + char ut = 'k'; + char buf[MAXPATHLEN]; + struct stat st; + + if( !ent->filename[0] ){ + filesz = 0; + } else { + setuserfile(buf, ent->filename); + if (stat(buf, &st) >= 0) { + filesz = st.st_size; + /* find printing unit */ + filesz = (filesz + 1023) / 1024; + if(filesz > 9999){ + filesz = (filesz+512) / 1024; + ut = 'M'; + } + if(filesz > 9999) { + filesz = (filesz+512) / 1024; + ut = 'G'; + } + } + } + sprintf(datepart, "%4lu%c", (unsigned long)filesz, ut); + } + break; + default: + break; + } + + /* print out */ + if (strncmp(currtitle, title, TTLEN) != 0) + { + /* is title. */ + color = ""; + } + + prints("%6d %c %-6s%s%-15.14s%s%s %s%-*.*s%s\n", + num, type, datepart, + isonline ? ANSI_COLOR(1) : "", + ent->owner, + isonline ? ANSI_RESET : "", + mark, color, + t_columns - 34, t_columns - 34, + title, + *color ? ANSI_RESET : ""); +} + + +static int +mail_del(int ent, const fileheader_t * fhdr, const char *direct) +{ + char genbuf[200]; + + if (fhdr->filemode & FILE_MARKED) + return DONOTHING; + + if (currmode & MODE_SELECT) { + vmsg("請先回到正常模式後再進行刪除..."); + return READ_REDRAW; + } + + if (getans(msg_del_ny) == 'y') { + if (!delete_record(direct, sizeof(*fhdr), ent)) { + setupmailusage(); + setdirpath(genbuf, direct, fhdr->filename); +#ifdef USE_RECYCLE + RcyAddFile(fhdr, 0, genbuf); +#endif // USE_RECYCLE + unlink(genbuf); + loadmailusage(); + return DIRCHANGED; + } + } + return READ_REDRAW; +} + +int b_call_in(int ent, const fileheader_t * fhdr, const char *direct); + +static int +mail_read(int ent, fileheader_t * fhdr, const char *direct) +{ + char buf[PATHLEN]; + char done, delete_it, replied; + + clear(); + setdirpath(buf, direct, fhdr->filename); + strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle)); + done = delete_it = replied = NA; + while (!done) { + int more_result = more(buf, YEA); + + /* whether success or not, update flag. + * or users may bug about "black-hole" mails + * and blinking notification */ + if( !(fhdr->filemode & FILE_READ)) + { + fhdr->filemode |= FILE_READ; + substitute_ref_record(direct, fhdr, ent); + } + switch (more_result) { + case -1: + /* no such file */ + clear(); + vmsg("此封信無內容。"); + return FULLUPDATE; + case RET_DOREPLY: + mail_reply(ent, fhdr, direct); + return FULLUPDATE; + case RET_DOREPLYALL: + multi_reply(ent, fhdr, direct); + return FULLUPDATE; + case RET_DORECOMMEND: // we don't accept this. + return FULLUPDATE; + case 0: + break; + default: + return more_result; + } + outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0); + + switch (igetch()) { + case 'r': + case 'R': + replied = YEA; + mail_reply(ent, fhdr, direct); + break; + case 'y': + multi_reply(ent, fhdr, direct); + break; + case 'x': + m_forward(ent, fhdr, direct); + break; + case 'd': + delete_it = YEA; + default: + done = YEA; + } + } + if (delete_it) + mail_del(ent, fhdr, direct); + else { + fhdr->filemode |= FILE_READ; + substitute_ref_record(direct, fhdr, ent); + } + return FULLUPDATE; +} + +static int +mail_read_all(int ent, fileheader_t * fhdr, const char *direct) +{ + off_t i = 0, num = 0; + int fd = 0; + fileheader_t xfhdr; + + currutmp->alerts &= ~ALERT_NEW_MAIL; + if ((fd = open(currmaildir, O_RDWR)) < 0) + return DONOTHING; + + if ((num = lseek(fd, 0, SEEK_END)) < 0) + num = 0; + num /= sizeof(fileheader_t); + + i = num - NEWMAIL_CHECK_RANGE; + if (i < 0) i = 0; + + if (lseek(fd, i * (off_t)sizeof(fileheader_t), SEEK_SET) < 0) + i = num; + + for (; i < num; i++) + { + if (read(fd, &xfhdr, sizeof(xfhdr)) <= 0) + break; + if (xfhdr.filemode & FILE_READ) + continue; + xfhdr.filemode |= FILE_READ; + if (lseek(fd, i * (off_t)sizeof(fileheader_t), SEEK_SET) < 0) + break; + write(fd, &xfhdr, sizeof(xfhdr)); + } + + close(fd); + return DIRCHANGED; +} + +static int +mail_unread(int ent, fileheader_t * fhdr, const char *direct) +{ + // this function may cause arguments, so please specify + // if you want this to be enabled. +#ifdef USE_USER_MAIL_UNREAD + if (fhdr && fhdr->filemode & FILE_READ) + { + fhdr->filemode &= ~FILE_READ; + substitute_record(direct, fhdr, ent); + return FULLUPDATE; + } +#endif // USE_USER_MAIL_UNREAD + return DONOTHING; +} + +/* in boards/mail 回信給原作者,轉信站亦可 */ +int +mail_reply(int ent, fileheader_t * fhdr, const char *direct) +{ + char uid[STRLEN]; + FILE *fp; + char genbuf[512]; + int oent = ent; + + if (!fhdr || !fhdr->filename[0]) + return DONOTHING; + + stand_title("回 信"); + + /* 判斷是 boards 或 mail */ + if (curredit & EDIT_MAIL) + setuserfile(quote_file, fhdr->filename); + else + setbfile(quote_file, currboard, fhdr->filename); + + /* find the author */ + strlcpy(quote_user, fhdr->owner, sizeof(quote_user)); + if (strchr(quote_user, '.')) { + char *t; + char *strtok_pos; + genbuf[0] = '\0'; + if ((fp = fopen(quote_file, "r"))) { + fgets(genbuf, sizeof(genbuf), fp); + fclose(fp); + } + t = strtok_r(genbuf, str_space, &strtok_pos); + if (t && (strcmp(t, str_author1)==0 || strcmp(t, str_author2)==0) + && (t=strtok_r(NULL, str_space, &strtok_pos)) != NULL) + strlcpy(uid, t, sizeof(uid)); + else { + vmsg("錯誤: 找不到作者。"); + quote_user[0]='\0'; + quote_file[0]='\0'; + return FULLUPDATE; + } + } else + strlcpy(uid, quote_user, sizeof(uid)); + + /* make the title */ + do_reply_title(3, fhdr->title); + prints("\n收信人: %s\n標 題: %s\n", uid, save_title); + + /* edit, then send the mail */ + ent = curredit; + switch (do_send(uid, save_title)) { + case -1: + outs(err_uid); + break; + case -2: + outs(msg_cancel); + break; + case -3: + prints("使用者 [%s] 無法收信", uid); + break; + + case 0: + /* success */ + if ( direct && /* for board, no direct */ + (curredit & EDIT_MAIL) && + !(fhdr->filemode & FILE_REPLIED)) + { + fhdr->filemode |= FILE_REPLIED; + substitute_ref_record(direct, fhdr, oent); + } + break; + } + curredit = ent; + pressanykey(); + quote_user[0]='\0'; + quote_file[0]='\0'; + if (strcasecmp(uid, cuser.userid) == 0) + return DIRCHANGED; + return FULLUPDATE; +} + +static int +mail_edit(int ent, fileheader_t * fhdr, const char *direct) +{ + char genbuf[200]; + + if (!HasUserPerm(PERM_SYSOP)) + return DONOTHING; + + setdirpath(genbuf, direct, fhdr->filename); + vedit(genbuf, NA, NULL); + return FULLUPDATE; +} + +static int +mail_nooutmail(int ent, fileheader_t * fhdr, const char *direct) +{ + cuser.uflag2 ^= REJ_OUTTAMAIL; + passwd_update(usernum, &cuser); + return FULLUPDATE; + +} + +static int +mail_mark(int ent, fileheader_t * fhdr, const char *direct) +{ + fhdr->filemode ^= FILE_MARKED; + + substitute_ref_record(direct, fhdr, ent); + return PART_REDRAW; +} + +/* help for mail reading */ +static const char * const mail_help[] = { + "\0電子信箱操作說明", + "\01基本命令", + "(p/↑)(n/↓) 前一篇/下一篇文章", + "(P)(PgUp) 前一頁", + "(N)(PgDn) 下一頁", + "(數字鍵) 跳到第 ## 筆", + "($) 跳到最後一筆", + "(r)(→) 讀信", + "(R)/(y) 回信 / 群組回信", + "\01進階命令", + "(TAB) 切換顯示模式(目前有一般及顯示大小)", + "(O) 關閉/開啟 站外信件轉入", + "(c)/(z) 此信件收入私人信件夾/進入私人信件夾", + "(x)/(X) 轉信給其它使用者/轉錄文章到其他看板", + "(F)/(u) 將信傳送回您的電子信箱/水球整理寄回信箱", + "(d) 殺掉此信", + "(D) 殺掉指定範圍的信", + "(m) 將信標記,以防被清除", + "(^G) 立即重建信箱 (信箱毀損時用)", + "(t) 標記欲刪除信件", + "(^D) 刪除已標記信件", + NULL +}; + +static int +m_help(void) +{ + show_help(mail_help); + return FULLUPDATE; +} + +static int +mail_cross_post(int ent, fileheader_t * fhdr, const char *direct) +{ + char xboard[20], fname[80], xfpath[80], xtitle[80], inputbuf[10]; + fileheader_t xfile; + FILE *xptr; + int author = 0; + char genbuf[200]; + char genbuf2[4]; + +#if 0 + // 除非有人明白為何要先 ChekPostPerm 並修復, + // 否則先 disable 這段 code - 目前常造成 crash。 + // + // XXX (will crash sometimes because currborad is not defined yet) + // 麻煩 in2 來修復這裡: 確認轉錄為何要先 CheckPostPerm + if (!currboard || currboard[0] == 0) + { + enter_board(DEFAULT_BOARD); + } + assert(0<=ent-1 && ent-1 1) + { + outs(ANSI_COLOR(1;31) + "請注意: 若過量重複轉錄將視為洗板,導致被開罰單停權。\n" ANSI_RESET + "若有特別需求請洽各板主,請他們幫你轉文。\n\n"); + } + move(1, 0); + CompleteBoard("轉錄本文章於看板:", xboard); + + if (*xboard == '\0' || !haspostperm(xboard)) + { + vmsg("無法轉錄"); + return FULLUPDATE; + } + + /* 借用變數 */ + ent = StringHash(fhdr->title); + /* 同樣 title 不管對哪個板都算 cross post , 所以不用檢查 author */ + + if ((ent != 0 && ent == postrecord.checksum[0])) { + /* 檢查 cross post 次數 */ + if (postrecord.times++ > MAX_CROSSNUM) + anticrosspost(); + } else { + postrecord.times = 0; + postrecord.last_bid = 0; + postrecord.checksum[0] = ent; + } + + ent = getbnum(xboard); + assert(0<=ent-1 && ent-1owner, cuser.userid)) { + getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ", + genbuf, 3, DOECHO); + if (genbuf[0] != '2') { + ent = 0; + getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO); + if (inputbuf[0] != 'n' && inputbuf[0] != 'N') + author = 1; + } + } + if (ent) + snprintf(xtitle, sizeof(xtitle), "[轉錄]%.66s", fhdr->title); + else + strlcpy(xtitle, fhdr->title, sizeof(xtitle)); + + snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", xtitle); + getdata(2, 0, genbuf, genbuf2, sizeof(genbuf2), LCECHO); + if (*genbuf2 == 'n') + if (getdata(2, 0, "標題:", genbuf, TTLEN, DOECHO)) + strlcpy(xtitle, genbuf, sizeof(xtitle)); + + getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO); + if (genbuf[0] == 'l' || genbuf[0] == 's') { + int currmode0 = currmode; + + currmode = 0; + setbpath(xfpath, xboard); + stampfile(xfpath, &xfile); + if (author) + strlcpy(xfile.owner, fhdr->owner, sizeof(xfile.owner)); + else + strlcpy(xfile.owner, cuser.userid, sizeof(xfile.owner)); + strlcpy(xfile.title, xtitle, sizeof(xfile.title)); + if (genbuf[0] == 'l') { + xfile.filemode = FILE_LOCAL; + } + setuserfile(fname, fhdr->filename); + { + const char *save_currboard; + xptr = fopen(xfpath, "w"); + assert(xptr); + + strlcpy(save_title, xfile.title, sizeof(save_title)); + save_currboard = currboard; + currboard = xboard; + write_header(xptr, save_title); + currboard = save_currboard; + + fprintf(xptr, "※ [本文轉錄自 %s 信箱]\n\n", cuser.userid); + + b_suckinfile(xptr, fname); + addsignature(xptr, 0); + fclose(xptr); + } + + setbdir(fname, xboard); + append_record(fname, &xfile, sizeof(xfile)); + setbtotal(getbnum(xboard)); + + if (!xfile.filemode) + outgo_post(&xfile, xboard, cuser.userid, cuser.nickname); +#ifdef USE_COOLDOWN + if (bcache[getbnum(xboard) - 1].brdattr & BRD_COOLDOWN) + add_cooldowntime(usernum, 5); + add_posttimes(usernum, 1); +#endif + + // cross-post does not add numpost. + outs("轉錄信件不增加文章數,敬請包涵。"); + + vmsg("文章轉錄完成"); + currmode = currmode0; + } + return FULLUPDATE; +} + +int +mail_man(void) +{ + char buf[PATHLEN], buf1[64]; + int mode0 = currutmp->mode; + int stat0 = currstat; + + // TODO if someday we put things in user man...? + sethomeman(buf, cuser.userid); + + // if user already has man directory or permission, + // allow entering mail-man folder. + + if (!dashd(buf) && !HasUserPerm(PERM_MAILLIMIT)) + return DONOTHING; + + snprintf(buf1, sizeof(buf1), "%s 的信件夾", cuser.userid); + a_menu(buf1, buf, HasUserPerm(PERM_MAILLIMIT) ? 1 : 0, 0, NULL); + currutmp->mode = mode0; + currstat = stat0; + return FULLUPDATE; +} + +// XXX BUG mail_cite 有可能會跳進 a_menu, 而 a_menu 會 check +// currbid。 一整個糟糕的邏輯錯誤... +static int +mail_cite(int ent, fileheader_t * fhdr, const char *direct) +{ + char fpath[PATHLEN]; + char title[TTLEN + 1]; + static char xboard[20] = ""; + char buf[20]; + int bid; + + setuserfile(fpath, fhdr->filename); + strlcpy(title, "◇ ", sizeof(title)); + strlcpy(title + 3, fhdr->title, sizeof(title) - 3); + a_copyitem(fpath, title, 0, 1); + + if (cuser.userlevel >= PERM_BM) { + move(2, 0); + clrtoeol(); + move(3, 0); + clrtoeol(); + move(1, 0); + + CompleteBoard( + HasUserPerm(PERM_MAILLIMIT) ? + "輸入看板名稱 (直接Enter進入私人信件夾):" : + "輸入看板名稱:", + buf); + if (*buf) + strlcpy(xboard, buf, sizeof(xboard)); + if (*xboard && ((bid = getbnum(xboard)) > 0)){ /* XXXbid */ + setapath(fpath, xboard); + setutmpmode(ANNOUNCE); + a_menu(xboard, fpath, + HasUserPerm(PERM_ALLBOARD) ? 2 : is_BM_cache(bid) ? 1 : 0, + bid, + NULL); + } else { + mail_man(); + } + return FULLUPDATE; + } else { + mail_man(); + return FULLUPDATE; + } +} + +static int +mail_save(int ent, fileheader_t * fhdr, const char *direct) +{ + char fpath[PATHLEN]; + char title[TTLEN + 1]; + + if (HasUserPerm(PERM_MAILLIMIT)) { + setuserfile(fpath, fhdr->filename); + strlcpy(title, "◇ ", sizeof(title)); + strlcpy(title + 3, fhdr->title, sizeof(title) - 3); + a_copyitem(fpath, title, fhdr->owner, 1); + sethomeman(fpath, cuser.userid); + a_menu(cuser.userid, fpath, 1, 0, NULL); + return FULLUPDATE; + } + return DONOTHING; +} + +#ifdef OUTJOBSPOOL +static int +mail_waterball(int ent, fileheader_t * fhdr, const char *direct) +{ + static char address[60] = "", cmode = 1; + char fname[500], genbuf[200]; + FILE *fp; + + if (!(strstr(fhdr->title, "熱線") && strstr(fhdr->title, "記錄"))) { + vmsg("必須是 熱線記錄 才能使用水球整理的唷!"); + return 1; + } + + if (!address[0]) + strlcpy(address, cuser.email, sizeof(address)); + + move(b_lines - 8, 0); clrtobot(); + outs(ANSI_COLOR(1;33;45) "★水球整理程式 " ANSI_RESET "\n" + "系統將會按照和不同人丟的水球各自獨立\n" + "於整點的時候 (尖峰時段除外) 將資料整理好寄送給您\n\n\n"); + + if (address[0]) { + snprintf(genbuf, sizeof(genbuf), "寄往 [%s] 嗎[Y/n/q]? ", address); + getdata(b_lines - 5, 0, genbuf, fname, 3, LCECHO); + if (fname[0] == 'q') { + outmsg("取消處理"); + return 1; + } + if (fname[0] == 'n') + address[0] = '\0'; + } + if (!address[0]) { + move(b_lines-4, 0); + prints( "請注意目前只支援寄往標準 e-mail 地址。\n" + "若想寄回此信箱請用輸入 %s.bbs@" MYHOSTNAME "\n", cuser.userid); + + getdata(b_lines - 5, 0, "請輸入郵件地址:", fname, 60, DOECHO); + if (fname[0] && strchr(fname, '.')) { + strlcpy(address, fname, sizeof(address)); + } else { + vmsg("地址格式不正確,取消處理"); + return 1; + } + } + trim(address); + if (invalidaddr(address)) + return -2; + move(b_lines-4, 0); clrtobot(); + + if( strstr(address, ".bbs") && REJECT_OUTTAMAIL ){ + outs("\n您必須要打開接受站外信, 水球整理系統才能寄入結果\n" + "請麻煩到【郵件選單】按大寫 O改成接受站外信 (在右上角)\n" + "再重新執行本功\能 :)\n"); + vmsg("請打開站外信, 再重新執行本功\能"); + return FULLUPDATE; + } + + //snprintf(fname, sizeof(fname), "%d\n", cmode); + outs("系統提供兩種模式: \n" + "模式 0: 精簡模式, 將不含顏色控制碼, 方便以純文字編輯器整理收藏\n" + "模式 1: 華麗模式, 包含顏色控制碼等, 方便在 bbs上直接編輯收藏\n"); + getdata(b_lines - 1, 0, "使用模式(0/1/Q)? [1]", fname, 3, LCECHO); + if (fname[0] == 'Q' || fname[0] == 'q') { + outmsg("取消處理"); + return FULLUPDATE; + } + cmode = (fname[0] != '0' && fname[0] != '1') ? 1 : fname[0] - '0'; + + snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.src.%s-%d", + cuser.userid, (int)now); + snprintf(genbuf, sizeof(genbuf), "cp " BBSHOME "/home/%c/%s/%s %s", + cuser.userid[0], cuser.userid, fhdr->filename, fname); + system(genbuf); + /* dirty code ;x */ + snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.des.%s-%d", + cuser.userid, (int)now); + fp = fopen(fname, "wt"); + assert(fp); + fprintf(fp, "%s\n%s\n%d\n", cuser.userid, address, cmode); + fclose(fp); + vmsg("設定完成, 系統將在下一個整點(尖峰時段除外)將資料寄給您"); + return FULLUPDATE; +} +#endif +static const onekey_t mail_comms[] = { + { 0, NULL }, // Ctrl('A') + { 0, NULL }, // Ctrl('B') + { 0, NULL }, // Ctrl('C') + { 0, NULL }, // Ctrl('D') + { 0, NULL }, // Ctrl('E') + { 0, NULL }, // Ctrl('F') + { 0, built_mail_index }, // Ctrl('G') + { 0, NULL }, // Ctrl('H') + { 0, toggle_showmail_mode }, // Ctrl('I') + { 0, NULL }, // Ctrl('J') + { 0, NULL }, // Ctrl('K') + { 0, NULL }, // Ctrl('L') + { 0, NULL }, // Ctrl('M') + { 0, NULL }, // Ctrl('N') + { 0, NULL }, // Ctrl('O') // DO NOT USE THIS KEY - UNIX not sending + { 0, NULL }, // Ctrl('P') + { 0, NULL }, // Ctrl('Q') + { 0, NULL }, // Ctrl('R') + { 0, NULL }, // Ctrl('S') + { 0, NULL }, // Ctrl('T') + { 0, NULL }, // Ctrl('U') + { 0, NULL }, // Ctrl('V') + { 0, NULL }, // Ctrl('W') + { 0, NULL }, // Ctrl('X') + { 0, NULL }, // Ctrl('Y') + { 0, NULL }, // Ctrl('Z') 26 + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, // 'A' 65 + { 0, NULL }, // 'B' + { 0, NULL }, // 'C' + { 1, del_range }, // 'D' + { 1, mail_edit }, // 'E' + { 0, NULL }, // 'F' + { 0, NULL }, // 'G' + { 0, NULL }, // 'H' + { 0, NULL }, // 'I' + { 0, NULL }, // 'J' + { 0, NULL }, // 'K' + { 0, NULL }, // 'L' + { 0, NULL }, // 'M' + { 0, NULL }, // 'N' + { 1, mail_nooutmail }, // 'O' + { 0, NULL }, // 'P' + { 0, NULL }, // 'Q' + { 1, mail_reply }, // 'R' + { 0, NULL }, // 'S' + { 1, edit_title }, // 'T' + { 0, NULL }, // 'U' + { 1, mail_unread }, // 'V' + { 0, NULL }, // 'W' + { 1, mail_cross_post }, // 'X' + { 0, NULL }, // 'Y' + { 0, NULL }, // 'Z' 90 + { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, + { 0, NULL }, // 'a' 97 + { 0, NULL }, // 'b' + { 1, mail_cite }, // 'c' + { 1, mail_del }, // 'd' + { 0, NULL }, // 'e' + { 0, NULL }, // 'f' + { 0, NULL }, // 'g' + { 0, m_help }, // 'h' + { 0, NULL }, // 'i' + { 0, NULL }, // 'j' + { 0, NULL }, // 'k' + { 0, NULL }, // 'l' + { 1, mail_mark }, // 'm' + { 0, NULL }, // 'n' + { 0, NULL }, // 'o' + { 0, NULL }, // 'p' + { 0, NULL }, // 'q' + { 1, mail_read }, // 'r' + { 1, mail_save }, // 's' + { 0, NULL }, // 't' +#ifdef OUTJOBSPOOL + { 1, mail_waterball }, // 'u' +#else + { 0, NULL }, // 'u' +#endif + { 0, mail_read_all }, // 'v' + { 1, b_call_in }, // 'w' + { 1, m_forward }, // 'x' + { 1, multi_reply }, // 'y' + { 0, mail_man }, // 'z' 122 +}; + +int +m_read(void) +{ + int back_bid; + if (get_num_records(currmaildir, sizeof(fileheader_t))) { + curredit = EDIT_MAIL; + curredit &= ~EDIT_ITEM; + back_bid = currbid; + currbid = 0; + i_read(RMAIL, currmaildir, mailtitle, maildoent, mail_comms, -1); + currbid = back_bid; + curredit = 0; + setmailalert(); + return 0; + } else { + outs("您沒有來信"); + return XEASY; + } +} + +/* 寄站內信 */ +static int +send_inner_mail(const char *fpath, const char *title, const char *receiver) +{ + char fname[PATHLEN]; + fileheader_t mymail; + char rightid[IDLEN+1]; + + if (!searchuser(receiver, rightid)) + return -2; + + /* to avoid DDOS of disk */ + sethomedir(fname, rightid); + if (strcmp(rightid, cuser.userid) == 0) { + if (chk_mailbox_limit()) + return -4; + } + + sethomepath(fname, rightid); + stampfile(fname, &mymail); + if (!strcmp(rightid, cuser.userid)) { + /* Using BBSNAME may be too loooooong. */ + strlcpy(mymail.owner, "[站內]", sizeof(mymail.owner)); + mymail.filemode = FILE_READ; + } else + strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); + strlcpy(mymail.title, title, sizeof(mymail.title)); + unlink(fname); + Copy(fpath, fname); + sethomedir(fname, rightid); + append_record_forward(fname, &mymail, sizeof(mymail), rightid); + sendalert(receiver, ALERT_NEW_MAIL); + return 0; +} + +#include +#include +#include + +#ifndef USE_BSMTP +static int +bbs_sendmail(const char *fpath, const char *title, char *receiver) +{ + char *ptr; + char genbuf[256]; + FILE *fin, *fout; + + /* 中途攔截 */ + if ((ptr = strchr(receiver, ';'))) { + *ptr = '\0'; + } + if ((ptr = strstr(receiver, str_mail_address)) || !strchr(receiver, '@')) { + char hacker[20]; + int len; + + if (strchr(receiver, '@')) { + len = ptr - receiver; + memcpy(hacker, receiver, len); + hacker[len] = '\0'; + } else + strlcpy(hacker, receiver, sizeof(hacker)); + return send_inner_mail(fpath, title, hacker); + } + /* Running the sendmail */ + if (fpath == NULL) { + snprintf(genbuf, sizeof(genbuf), + "/usr/sbin/sendmail %s > /dev/null", receiver); + fin = fopen("etc/confirm", "r"); + } else { + snprintf(genbuf, sizeof(genbuf), + "/usr/sbin/sendmail -f %s%s %s > /dev/null", + cuser.userid, str_mail_address, receiver); + fin = fopen(fpath, "r"); + } + if (fin == NULL) + return -1; + fout = popen(genbuf, "w"); + if (fout == NULL) { + fclose(fin); + return -1; + } + + if (fpath) + fprintf(fout, "Reply-To: %s%s\nFrom: %s <%s%s>\n", + cuser.userid, str_mail_address, + cuser.nickname, + cuser.userid, str_mail_address); + fprintf(fout,"To: %s\nSubject: %s\n" + "Mime-Version: 1.0\r\n" + "Content-Type: text/plain; charset=\"big5\"\r\n" + "Content-Transfer-Encoding: 8bit\r\n" + "X-Disclaimer: " BBSNAME "對本信內容恕不負責。\n\n", + receiver, title); + + while (fgets(genbuf, sizeof(genbuf), fin)) { + if (genbuf[0] == '.' && genbuf[1] == '\n') + fputs(". \n", fout); + else + fputs(genbuf, fout); + } + fclose(fin); + fprintf(fout, ".\n"); + pclose(fout); + return 0; +} +#else /* USE_BSMTP */ + +int +bsmtp(const char *fpath, const char *title, const char *rcpt) +{ + char buf[80], *ptr; + time4_t chrono; + MailQueue mqueue; + + /* check if the mail is a inner mail */ + if ((ptr = strstr(rcpt, str_mail_address)) || !strchr(rcpt, '@')) { + char hacker[20]; + int len; + + if (strchr(rcpt, '@')) { + len = ptr - rcpt; + memcpy(hacker, rcpt, len); + hacker[len] = '\0'; + } else + strlcpy(hacker, rcpt, sizeof(hacker)); + return send_inner_mail(fpath, title, hacker); + } + chrono = now; + + /* stamp the queue file */ + strlcpy(buf, "out/", sizeof(buf)); + for (;;) { + snprintf(buf + 4, sizeof(buf) - 4, "M.%d.%d.A", (int)++chrono, getpid()); + if (!dashf(buf)) { + Copy(fpath, buf); + break; + } + } + + fpath = buf; + + /* setup mail queue */ + mqueue.mailtime = chrono; + // XXX (unused) mqueue.method = method; + strlcpy(mqueue.filepath, fpath, sizeof(mqueue.filepath)); + strlcpy(mqueue.subject, title, sizeof(mqueue.subject)); + strlcpy(mqueue.sender, cuser.userid, sizeof(mqueue.sender)); + strlcpy(mqueue.username, cuser.nickname, sizeof(mqueue.username)); + strlcpy(mqueue.rcpt, rcpt, sizeof(mqueue.rcpt)); + + if (append_record("out/" FN_DIR, (fileheader_t *) & mqueue, sizeof(mqueue)) < 0) + return 0; + return chrono; +} +#endif /* USE_BSMTP */ + +int +doforward(const char *direct, const fileheader_t * fh, int mode) +{ + static char address[STRLEN] = ""; + char fname[PATHLEN]; + char genbuf[PATHLEN]; + int return_no; + + if (!address[0] && strcmp(cuser.email, "x") != 0) + strlcpy(address, cuser.email, sizeof(address)); + + if( mode == 'U' ){ + vmsg("將進行 uuencode 。若您不清楚什麼是 uuencode 請改用 F轉寄。"); + } + trim(address); + + // if user has address and not the default 'x' (no-email)... + if (address[0]) { + snprintf(genbuf, sizeof(genbuf), + "確定轉寄給 [%s] 嗎(Y/N/Q)?[Y] ", address); + getdata(b_lines, 0, genbuf, fname, 3, LCECHO); + + if (fname[0] == 'q') { + outmsg("取消轉寄"); + return 1; + } + if (fname[0] == 'n') + address[0] = '\0'; + } + if (!address[0]) { + do { + getdata(b_lines - 1, 0, "請輸入轉寄地址:", fname, 60, DOECHO); + if (fname[0]) { + if (strchr(fname, '.')) + strlcpy(address, fname, sizeof(address)); + else + snprintf(address, sizeof(address), + "%s.bbs@%s", fname, MYHOSTNAME); + } else { + vmsg("取消轉寄"); + return 1; + } + } while (mode == 'Z' && strstr(address, MYHOSTNAME)); + } + /* according to our experiment, many users leave blanks */ + trim(address); + if (invalidaddr(address)) + return -2; + + outmsg("正轉寄請稍候..."); + refresh(); + + /* 追蹤使用者 */ + if (HasUserPerm(PERM_LOGUSER)) + log_user("mailforward to %s ",address); + if (mode == 'Z') { + snprintf(fname, sizeof(fname), + TAR_PATH " cfz /tmp/home.%s.tgz home/%c/%s; " + MUTT_PATH " -a /tmp/home.%s.tgz -s 'home.%s.tgz' '%s' %s", + cuser.userid[0], cuser.userid, cuser.userid, direct); + system(fname); + strlcpy(fname, direct, sizeof(fname)); + } else if (mode == 'U') { + char tmp_buf[128]; + + snprintf(fname, sizeof(fname), "/tmp/bbs.uu%05d", (int)currpid); + snprintf(tmp_buf, sizeof(tmp_buf), + "/usr/bin/uuencode %s/%s uu.%05d > %s", + direct, fh->filename, (int)currpid, fname); + system(tmp_buf); + } else if (mode == 'F') { + char tmp_buf[128]; + + snprintf(fname, sizeof(fname), "/tmp/bbs.f%05d", (int)currpid); + snprintf(tmp_buf, sizeof(tmp_buf), "%s/%s", direct, fh->filename); + Copy(tmp_buf, fname); + } else + return -1; + + return_no = +#ifndef USE_BSMTP + bbs_sendmail(fname, fh->title, address); +#else + bsmtp(fname, fh->title, address); +#endif + unlink(fname); + return (return_no); +} + +int +load_mailalert(const char *userid) +{ + struct stat st; + char maildir[MAXPATHLEN]; + int fd; + register int num; + fileheader_t my_mail; + + sethomedir(maildir, userid); + if (!HasUserPerm(PERM_BASIC)) + return 0; + if (stat(maildir, &st) < 0) + return 0; + num = st.st_size / sizeof(fileheader_t); + if (num <= 0) + return 0; + if (num > NEWMAIL_CHECK_RANGE) + num = NEWMAIL_CHECK_RANGE; + + /* 看看有沒有信件還沒讀過?從檔尾回頭檢查,效率較高 */ + if ((fd = open(maildir, O_RDONLY)) > 0) { + lseek(fd, st.st_size - sizeof(fileheader_t), SEEK_SET); + while (num--) { + read(fd, &my_mail, sizeof(fileheader_t)); + if (!(my_mail.filemode & FILE_READ)) { + close(fd); + return ALERT_NEW_MAIL; + } + lseek(fd, -(off_t) 2 * sizeof(fileheader_t), SEEK_CUR); + } + close(fd); + } + return 0; +} diff --git a/console/mbbsd.c b/console/mbbsd.c new file mode 100644 index 00000000..0c561ed1 --- /dev/null +++ b/console/mbbsd.c @@ -0,0 +1,1832 @@ +/* $Id$ */ +#include "bbs.h" +#include "banip.h" + +#ifdef __linux__ +# ifdef CRITICAL_MEMORY +# include +# endif +# ifdef DEBUG +# include +# 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 + +////////////////////////////////////////////////////////////////// +// Site Optimization +// override these macro if you need more optimization, +// based on OS/lib/package... +#ifndef OPTIMIZE_LISTEN_SOCKET +#define OPTIMIZE_LISTEN_SOCKET(sock,sz) +#endif + +#ifndef XAUTH_HOST +#define XAUTH_HOST(x) x +#endif + +#ifndef XAUTH_GETREMOTENAME +#define XAUTH_GETREMOTENAME(x) x +#endif + +#ifndef XAUTH_TRYREMOTENAME +#define XAUTH_TRYREMOTENAME() +#endif + +#if 0 +static jmp_buf byebye; +#endif + +static char remoteusername[40] = "?"; +static unsigned char enter_uflag; +static int use_shell_login_mode = 0; +static int listen_port = 23; + +#ifdef DETECT_CLIENT +Fnv32_t client_code=FNV1_32_INIT; + +void UpdateClientCode(unsigned char c) +{ + FNV1A_CHAR(c, client_code); +} +#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_filef(FN_USIES, LOG_CREAT, + "%s %s %-12s Stay:%d (%s)\n", + Cdate(&now), mode, cuser.userid , + (int)(now - login_start_time) / 60, cuser.nickname); + else + log_filef(FN_USIES, LOG_CREAT, + "%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程式異常, 立刻斷線. \r\n" \ + "請洽 " GLOBAL_BUGREPORT " 板詳述問題發生經過。\r\n" + +#define XCPU_MSG ANSI_COLOR(0) \ + "\r\n程式耗用過多計算資源, 立刻斷線。\r\n" \ + "可能是 (a)執行太多耗用資源的動作 或 (b)程式掉入無窮迴圈. "\ + "請洽 " GLOBAL_BUGREPORT " 板詳述問題發生經過。\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_filef("log/crash.log", LOG_CREAT, + "%d %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(DEBUGSLEEP_SECONDS); +#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]; + + syncnow(); + 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; + scr_dump(&old_screen); + talkreply(); + currutmp->mode = mode0; + currutmp->chatid[0] = c0; + scr_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 + syncnow(); + 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] ", + 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, SHOWFILE_ALLOW_ALL); +#endif + // XXX why output("1", 1); here? + // this output has been here since rev 1... + // 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 + + if (getdata(20, 0, "請輸入代號,或以[guest]參觀,以[new]註冊: ", + uid, sizeof(uid), DOECHO) < 1) + { + // got nothing + outs("請重新輸入。\n"); + continue; + } + +#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; + redrawwin(); + } + else if (uid[0] && uid[len - 1] == ',') { + set_converting_type(CONV_UTF8); + uid[len - 1] = 0; + redrawwin(); + } + else if (len >= IDLEN + 1) + uid[IDLEN] = 0; +#endif + + if (strcasecmp(uid, str_new) == 0) { +#ifdef LOGINASNEW + new_register(); + mkuserdir(cuser.userid); + reginit_fav(); + 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; + cuser.uflag2= 0; // we don't need FAVNEW_FLAG or anything else. + +#ifdef GUEST_DEFAULT_DBCS_NOINTRESC + cuser.uflag |= DBCS_NOINTRESC; +#endif + // can we prevent mkuserdir() here? + 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; +#endif + } + /* 早該有 home 了, 不知道為何有的帳號會沒有, 被砍掉了? */ + mkuserdir(cuser.userid); + 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] ") == '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); + if (enter_board("WhoAmI")==0) { + 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(); + + 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); + redrawwin(); + + /* 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(); + resolve_over18(); + + 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 { + // XXX no userlevel, no guest - what is this? + // clear(); + // outs("此帳號停權中"); + // pressanykey(); + // exit(1); + + 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 + XAUTH_HOST(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)); + + OPTIMIZE_LISTEN_SOCKET(sock, 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; +} + +static int +daemon_login(int argc, char *argv[], char *envp[]) +{ + int msock, csock; /* socket for Master and Child */ + FILE *fp; + 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; + } + + XAUTH_TRYREMOTENAME(); + + 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); + + XAUTH_GETREMOTENAME(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 = NULL; + + 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; +} + +/* vim: sw=4 + */ diff --git a/console/menu.c b/console/menu.c new file mode 100644 index 00000000..6aaf3399 --- /dev/null +++ b/console/menu.c @@ -0,0 +1,718 @@ +/* $Id$ */ +#include "bbs.h" + +#define CheckMenuPerm(x) \ + ( (x == MENU_UNREGONLY)? \ + ((cuser.userlevel == 0 ||HasUserPerm(PERM_LOGINOK))?0:1) :\ + ((x) ? HasUserPerm(x) : 1)) + +/* help & menu processring */ +static int refscreen = NA; +extern char *boardprefix; +extern struct utmpfile_t *utmpshm; + +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, rlen, mlen, mpos = 0; + int pos = 0; + int tail_type; + const char *mid_attr = ANSI_COLOR(33); + int is_currboard_special = 0; + char buf[64]; + + + /* prepare mid */ +#ifdef DEBUG + { + sprintf(buf, " current pid: %6d ", getpid()); + mid = buf; + mid_attr = ANSI_COLOR(41;5); + } +#else + if (ISNEWMAIL(currutmp)) { + mid = " 你有新信件 "; + mid_attr = ANSI_COLOR(41;5); + } 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; + } + } +#endif + + /* prepare tail */ + if (currmode & MODE_SELECT) + tail_type = TITLE_TAIL_SELECT; + else if (currmode & MODE_DIGEST) + tail_type = TITLE_TAIL_DIGEST; + else + tail_type = TITLE_TAIL_BOARD; + + if(currbid > 0) + { + assert(0<=currbid-1 && currbid-1brdattr & BRD_HIDE) && + (getbcache(currbid)->brdattr & BRD_POSTMASK)); + } + + /* now, calculate real positioning info */ + llen = strlen(title); + mlen = strlen(mid); + mpos = (t_columns -1 - mlen)/2; + + /* first, print left. */ + clear(); + outs(TITLE_COLOR "【"); + outs(title); + outs("】"); + pos = llen + 4; + + /* print mid */ + while(pos++ < mpos) + outc(' '); + 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 (is_currboard_special) + 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); // 3 = ANSI_COLOR, 25 = stuff inside + sprintf(mystatus, "[扣機]" ANSI_COLOR(31) "%s ", + msgs[currutmp->pager]); + outslr("", i, mystatus, strlen(msgs[currutmp->pager]) + 7); + outs(ANSI_RESET); +} + +/* + * current caller 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); + clrtoln(1 + FILMROW); /* 清掉上次的 */ + out_lines(SHM->notes[i], 11); /* 只印11行就好 */ + outs(reset_color); + } + show_status(); + refresh(); +} + +typedef struct { + int (*cmdfunc)(); + int level; + char *desc; /* next/key/description */ +} commands_t; + +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, M_XMAX +}; + +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; + + moviemode = cmdmode; + assert(cmdmode < M_XMAX); + cmdmode = mode_map[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]; + } + 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|PERM_BOARD, "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 進站水球"}, + {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 站內寄信"}, + {mail_list, PERM_LOGINOK, "RMail List 群組寄信"}, + {x_love, PERM_LOGINOK, "PPaper 情書產生器"}, + {setforward, PERM_LOGINOK, "FForward " ANSI_COLOR(1;32) + "設定信箱自動轉寄" ANSI_RESET}, + {m_sysop, 0, "YYes, sir! 寫信給站長"}, + {m_internet, PERM_INTERNET, "RInternet 寄信到站外"}, + {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 查詢寵物"}, + // PERM_PAGE - 水球都要 PERM_LOGIN 了 + // 沒道理可以 talk 不能水球。 + {t_talk, PERM_LOGINOK, "TTalk 找人聊聊"}, + // PERM_CHAT 非 login 也有,會有人用此吵別人。 + {t_chat, PERM_LOGINOK, "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 上站通知名單"}, + {t_fix_aloha,PERM_LOGINOK,"XXFixALOHA 修正上站通知"}, + {t_special,PERM_LOGINOK, "SSpecial 其他特別名單"}, + {NULL, 0, NULL} +}; + +void Customize(); // user.c +int u_customize() +{ + Customize(); + return 0; +} + +int u_fixgoodpost(void); // assess.c +/* User menu */ +static const commands_t userlist[] = { + {u_customize, PERM_BASIC, "UUCustomize 個人化設定"}, + {u_info, PERM_LOGINOK, "IInfo 設定個人資料與密碼"}, + {calendar, PERM_LOGINOK, "CCalendar 個人行事曆"}, + {u_loginview, PERM_BASIC, "LLogin View 選擇進站畫面"}, + {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, MENU_UNREGONLY, "RRegister 填寫《註冊申請單》"}, +#ifdef ASSESS + {u_cancelbadpost, PERM_LOGINOK, "BBye BadPost 申請刪除劣文"}, + {u_fixgoodpost, PERM_LOGINOK, "FFix GoodPost 修復優文"}, +#endif // ASSESS + {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} +}; + +static 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, PERM_BASIC, "UUser 【 個人設定區 】"}, + {Xyz, 0, "XXyz 【 系統資訊區 】"}, + {Play_Play, PERM_LOGINOK, "PPlay 【 娛樂與休閒 】"}, + {Name_Menu, PERM_LOGINOK, "NNamelist 【 編特別名單 】"}, +#ifdef DEBUG + {Goodbye, 0, "GGoodbye 再見再見再見再見"}, +#else + {Goodbye, 0, "GGoodbye 離開,再見… "}, +#endif + {NULL, 0, NULL} +}; + +int main_menu(void) { + domenu(M_MMENU, "主功\能表", (ISNEWMAIL(currutmp) ? 'M' : 'C'), cmdlist); + return 0; +} + +static int p_money() { + domenu(M_PSALE, BBSMNAME2 "量販店", '0', moneylist); + return 0; +}; + +// static int forsearch(); +static int playground(); +static int chessroom(); + +/* Ptt Play menu */ +static const commands_t playlist[] = { + {note, PERM_LOGINOK, "NNote 【 刻刻流言板 】"}, + /* // useless. + {forsearch,PERM_LOGINOK, "SSearchEngine【" ANSI_COLOR(1;35) " " + BBSMNAME2 "搜尋器 " ANSI_RESET "】"}, + */ + {topsong,PERM_LOGINOK, "TTop Songs 【" ANSI_COLOR(1;32) " 點歌排行榜 " ANSI_RESET "】"}, + {p_money,PERM_LOGINOK, "PPay 【" ANSI_COLOR(1;31) " " + BBSMNAME2 "量販店 " ANSI_RESET "】"}, + {chicken_main,PERM_LOGINOK, "CChicken " + "【" ANSI_COLOR(1;34) " " BBSMNAME2 "養雞場 " ANSI_RESET "】"}, + {playground,PERM_LOGINOK, "AAmusement 【" ANSI_COLOR(1;33) " " + BBSMNAME2 "遊樂場 " ANSI_RESET "】"}, + {chessroom, PERM_LOGINOK, "BBChess 【" ANSI_COLOR(1;34) " " + BBSMNAME2 "棋院 " 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, BBSMNAME2 "棋院", '1', chesslist); + return 0; +} + +static const commands_t plist[] = { + +/* {p_ticket_main, PERM_LOGINOK,"00Pre 【 總統機 】"}, + {alive, PERM_LOGINOK, "00Alive 【 訂票雞 】"}, +*/ + {ticket_main, PERM_LOGINOK, "11Gamble 【 " BBSMNAME2 "賭場 】"}, + {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, BBSMNAME2 "遊樂場",'1',plist); + return 0; +} + +static const commands_t slist[] = { + /* + // x_dict: useless + {x_dict,0, "11Dictionary " + "【" ANSI_COLOR(1;33) " 趣味大字典 " ANSI_RESET "】"}, + */ + {x_mrtmap, 0, "22MRTmap " + "【" ANSI_COLOR(1;34) " 捷運地圖 " ANSI_RESET "】"}, + {NULL, 0, NULL} +}; + +/* // nothing to search... +static int forsearch() { + domenu(M_SREG, BBSMNAME2 "搜尋器", '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, "個人設定", 'U', 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/console/merge.c b/console/merge.c new file mode 100644 index 00000000..913f94f5 --- /dev/null +++ b/console/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 換算成 " MONEYNAME " 幣為 %9d (匯率 22:1), \n" + " 沙灘貝殼有 %10d 換算為 " MONEYNAME " 幣為 %9d (匯率 222105:1), \n" + " 原有 %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(GLOBAL_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/console/more.c b/console/more.c new file mode 100644 index 00000000..d506754a --- /dev/null +++ b/console/more.c @@ -0,0 +1,77 @@ +/* $Id$ */ +#include "bbs.h" + +/* use new pager: piaip's more. */ +int more(char *fpath, int promptend) +{ + int r = pmore(fpath, promptend); + + switch(r) + { + + case RET_DOSYSOPEDIT: + r = FULLUPDATE; + + if (!HasUserPerm(PERM_SYSOP) || + strcmp(fpath, "etc/ve.hlp") == 0) + break; + +#ifdef GLOBAL_SECURITY + if (strcmp(currboard, GLOBAL_SECURITY) == 0) + break; +#endif // GLOBAL_SECURITY + + log_filef("log/security", LOG_CREAT, + "%u %24.24s %d %s admin edit file=%s\n", + (int)now, ctime4(&now), getpid(), cuser.userid, fpath); + + // no need to allow anything... + // at least, no need to change title. + vedit2(fpath, NA, NULL, 0); + break; + + case RET_SELECTBRD: + r = FULLUPDATE; + if (HasUserPerm(PERM_BASIC)) + { + if (currstat == READING) + return Select(); + } + break; + + case RET_COPY2TMP: + r = FULLUPDATE; + if (HasUserPerm(PERM_BASIC)) + { + char buf[PATHLEN]; + getdata(b_lines - 1, 0, "把這篇文章收入到暫存檔?[y/N] ", + buf, 4, LCECHO); + if (buf[0] != 'y') + break; + setuserfile(buf, ask_tmpbuf(b_lines - 1)); + Copy(fpath, buf); + } + break; + + case RET_DOCHESSREPLAY: + r = FULLUPDATE; + if (HasUserPerm(PERM_BASIC)) + { + ChessReplayGame(fpath); + } + break; + +#if defined(USE_BBSLUA) + case RET_DOBBSLUA: + r = FULLUPDATE; + if (HasUserPerm(PERM_BASIC)) + { + bbslua(fpath); + } + break; +#endif + } + + return r; +} + diff --git a/console/name.c b/console/name.c new file mode 100644 index 00000000..4e9d7134 --- /dev/null +++ b/console/name.c @@ -0,0 +1,1067 @@ +/* $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); +} + +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 && idxsize); + return self->base[idx]; +} + +static int NameList_MaxLen(const struct NameList *list, int offset, int count) +{ + int i; + int maxlen = 0; + + for(i=offset; isize; 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; isize; 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; isize; 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; isize; 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; isize; 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, scrx; + 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); + scrx = origx = x; + data[count] = 0; + + while (1) + { + // print input field again + move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); + outs(ANSI_COLOR(7)); + prints("%-*s", IDLEN + 1, data); + outs(ANSI_RESET); + move(y, scrx + count); + + // get input + if ((ch = igetch()) == EOF) + break; + + 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); + count = strlen(data); + temp = data + count; + 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); + } + continue; + } + if (ch == '\177' || ch == '\010') { + if (temp == data) + continue; + temp--; + count--; + *temp = '\0'; + ClearSubList(cwlist); + cwlist = GetSubList(data, toplev); + morelist = NULL; + 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; + } + } + 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(y, origx); + outs(data); + outc('\n'); + } +} + +void +namecomplete2(struct NameList *namelist, const char *prompt, char *data) +{ + char *temp; + int x, y, origx, scrx; + 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); + scrx = origx = x; + data[count] = 0; + viewoffset = 0; + + while (1) + { + // print input field + move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); + outs(ANSI_COLOR(7)); + prints("%-*s", IDLEN + 1, data); + outs(ANSI_RESET); + move(y, scrx + count); + + // get input + if ((ch = igetch()) == EOF) + break; + + 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)); + count = strlen(data); + temp = data + count; + 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); + } + continue; + } + if (ch == '\177' || ch == '\010') { + if (temp == data) + continue; + temp--; + count--; + *temp = '\0'; + NameList_sublist(namelist, &sublist, data); + viewoffset = 0; + 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; + } + } + 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(y, origx); + outs(data); + outc('\n'); + } +} + +void +usercomplete(const char *prompt, char *data) +{ + char *temp; + char *cwbuf, *cwlist; + int cwnum, x, y, origx, scrx; + 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); + scrx = origx = x; + data[count] = 0; + + while (1) + { + // print input field again + move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); + outs(ANSI_COLOR(7)); + prints("%-*s", IDLEN + 1, data); + outs(ANSI_RESET); + move(y, scrx + count); + + // get input + if ((ch = igetch()) == EOF) + break; + + 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; + 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; + 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); + count = strlen(data); + temp = data + count; + 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; + + 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(y, 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, scrx, ch, i, morelist = -1, col, ret = -1; + int start, end, ptr; + int clearbot = NA; + + outs(prompt); + clrtoeol(); + getyx(&y, &x); + scrx = origx = x; + + ptr = 0; + data[ptr] = 0; + + start = 0; end = nmemb - 1; + while (1) + { + // print input field again + move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); + outs(ANSI_COLOR(7)); + // data[ptr] = 0; + prints("%-*s", len, data); + outs(ANSI_RESET); + move(y, scrx + ptr); + + // get input + if ((ch = igetch()) == EOF) + break; + + 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) { + ptr = strlen(data); + 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 */ + ptr = i; + } + } + morelist = start; + } else if (morelist > end) + continue; + clearbot = YEA; + move(2, 0); + clrtobot(); + printdash("相關資訊一覽表", 0); + + col = 0; + while (len + col < t_columns-1) { + 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); + } + continue; + + } else if (ch == '\177' || ch == '\010') { /* backspace */ + if (ptr == 0) + continue; + morelist = -1; + --ptr; + data[ptr] = 0; + 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; + } + } + } + + outc('\n'); + if (clearbot) { + move(2, 0); + clrtobot(); + } + if (*data) { + move(y, 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)); +} + +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/console/osdep.c b/console/osdep.c new file mode 100644 index 00000000..f4a9bd85 --- /dev/null +++ b/console/osdep.c @@ -0,0 +1,643 @@ +/* $Id$ */ +#include "bbs.h" + +#ifdef NEED_STRLCAT + +#include +#include + +/* size_t + * strlcat(char *dst, const char *src, size_t size); + */ +/* + * Copyright (c) 1998 Todd C. Miller + * 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 +#include + +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 + * 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 +#include +#include +#include +#include + +/* + * 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 + +/* + * 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 +#include + + +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 + + +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 +#include + +#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/console/othello.c b/console/othello.c new file mode 100644 index 00000000..99dd4794 --- /dev/null +++ b/console/othello.c @@ -0,0 +1,577 @@ +/* $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("[0] 離開\n"); + outs("(1) CD-65\n");/* 想 1 步 */ + outs("(2) 嬰兒\n"); /* 想 3 步 */ + outs("(3) 小孩\n"); /* 想 4 步 */ + do { + if (getdata(4, 0, "請選擇一個對象和您對打:(1~3)", + thinkstep, sizeof(thinkstep), LCECHO) == 0 || + thinkstep[0] == '0') + return 0; + + } 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(); + if (!od->think) + { + unlockutmpmode(); + free(od); + return 0; + } + 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/console/passwd.c b/console/passwd.c new file mode 100644 index 00000000..d0dd4015 --- /dev/null +++ b/console/passwd.c @@ -0,0 +1,171 @@ +/* $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 +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; +} + +void +passwd_force_update(int flag) +{ + if(!currutmp || (currutmp->alerts & ALERT_PWD) == 0) + return; + currutmp->alerts &= ~flag; +} + +int +passwd_update(int num, userec_t * buf) +{ + int pwdfd; + if (num < 1 || num > MAX_USERS) + return -1; + buf->money = moneyof(num); + if(usernum == num && currutmp && ((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_GOODPOST) + cuser.goodpost = buf->goodpost = u.goodpost; + if(pwdfd & ALERT_PWD_PERM) + cuser.userlevel = buf->userlevel = u.userlevel; + if(pwdfd & ALERT_PWD_JUSTIFY) + { + memcpy(buf->justify, u.justify, sizeof(u.justify)); + memcpy(cuser.justify, u.justify, sizeof(u.justify)); + memcpy(buf->email, u.email, sizeof(u.email)); + memcpy(cuser.email, u.email, sizeof(u.email)); + } + 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(void *ctx, int (*fptr) (void *ctx, int, userec_t *)) +{ + int i; + userec_t user; + for (i = 0; i < MAX_USERS; i++) { + passwd_query(i + 1, &user); + if ((*fptr) (ctx, i, &user) < 0) + return -1; + } + 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/console/pfterm.c b/console/pfterm.c new file mode 100644 index 00000000..65a09a62 --- /dev/null +++ b/console/pfterm.c @@ -0,0 +1,2502 @@ +////////////////////////////////////////////////////////////////////////// +// pfterm environment settings +////////////////////////////////////////////////////////////////////////// +#ifdef _PFTERM_TEST_MAIN + +#define USE_PFTERM +#define EXP_PFTERM +#define DBCSAWARE +#define FT_DBCS_NOINTRESC 1 +#define DBG_TEXT_FD + +#include +#include +#include +#include +#include + +#else + +#define HAVE_GRAYOUT +#include "bbs.h" + +#ifdef DBCS_NOINTRESC +// # ifdef CONVERT +// extern int bbs_convert_type; +// # define FT_DBCS_NOINTRESC ( +// (cuser.uflag & DBCS_NOINTRESC) || +// (bbs_convert_type == CONV_UTF8)) +// # else +# define FT_DBCS_NOINTRESC (cuser.uflag & DBCS_NOINTRESC) +// # endif +#else +# define FT_DBCS_NOINTRESC 0 +#endif + +#endif + +////////////////////////////////////////////////////////////////////////// +// pfterm debug settings +////////////////////////////////////////////////////////////////////////// + +// #define DBG_SHOW_DIRTY +// #define DBG_SHOW_REPRINT // will not work if you enable SHOW_DIRTY. +// #define DBG_DISABLE_OPTMOVE +// #define DBG_DISABLE_REPRINT + +////////////////////////////////////////////////////////////////////////// +// pmore style ansi +// #include "ansi.h" +////////////////////////////////////////////////////////////////////////// + +#ifndef PMORE_STYLE_ANSI +#define ESC_CHR '\x1b' +#define ESC_STR "\x1b" +#define ANSI_COLOR(x) ESC_STR "[" #x "m" +#define ANSI_RESET ESC_STR "[m" +#endif // PMORE_STYLE_ANSI + +#ifndef ANSI_IS_PARAM +#define ANSI_IS_PARAM(c) (c == ';' || (c >= '0' && c <= '9')) +#endif // ANSI_IS_PARAM + +////////////////////////////////////////////////////////////////////////// +// grayout advanced control +// #include "grayout.h" +////////////////////////////////////////////////////////////////////////// +#ifndef GRAYOUT_DARK +#define GRAYOUT_COLORBOLD (-2) +#define GRAYOUT_BOLD (-1) +#define GRAYOUT_DARK (0) +#define GRAYOUT_NORM (1) +#define GRAYOUT_COLORNORM (+2) +#endif // GRAYOUT_DARK + +////////////////////////////////////////////////////////////////////////// +// Typeahead +////////////////////////////////////////////////////////////////////////// +#ifndef TYPEAHEAD_NONE +#define TYPEAHEAD_NONE (-1) +#define TYPEAHEAD_STDIN (0) +#endif // TYPEAHEAD + +////////////////////////////////////////////////////////////////////////// +// pfterm: piaip's flat terminal system, a new replacement for 'screen'. +// pfterm can also be read as "Perfect Term" +// +// piaip's new implementation of terminal system (term/screen) with dirty +// map, designed and optimized for ANSI based output. +// +// "pfterm" is "piaip's flat terminal" or "perfect term", not "PTT's term"!!! +// pfterm is designed for general maple-family BBS systems, not +// specific to any branch. +// +// Author: Hung-Te Lin (piaip), Dec. 2007. +// +// Copyright(c) 2007-2008 Hung-Te Lin +// All Rights Reserved. +// You are free to use, modify, redistribute this program in any +// non-commercial usage (including network service). +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// MAJOR IMPROVEMENTS: +// - Interpret ANSI code and maintain a virtual terminal +// - Dual buffer for dirty map and optimized output +// +// TODO AND DONE: +// - DBCS aware and prevent interrupting DBCS [done] +// - optimization with relative movement [done] +// - optimization with ENTER/clrtoeol [done] +// - ncurses-like API [done] +// - inansistr to output escaped string [done] +// - handle incomplete DBCS characters [done] +// - optimization with reprint ability [done] +// - add auto wrap control +// +// DEPRECATED: +// - standout() [rework getdata() and namecomplete()] +// - talk.c (big_picture) [rework talk.c] +// +////////////////////////////////////////////////////////////////////////// +// Reference: +// http://en.wikipedia.org/wiki/ANSI_escape_code +// http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf +////////////////////////////////////////////////////////////////////////// + +// Experimental now +#if defined(EXP_PFTERM) || defined(USE_PFTERM) + +////////////////////////////////////////////////////////////////////////// +// pfterm Configurations +////////////////////////////////////////////////////////////////////////// + +// Prevent invalid DBCS characters +#define FTCONF_PREVENT_INVALID_DBCS + +// Some terminals use current attribute to erase background +#define FTCONF_CLEAR_SETATTR + +// Some terminals (NetTerm, PacketSite) have bug in bolding attribute. +#define FTCONF_WORKAROUND_BOLD + +// Some terminals prefer VT100 style scrolling, including Win/DOS telnet +#undef FTCONF_USE_ANSI_SCROLL +#undef FTCONF_USE_VT100_SCROLL + +// Few poor terminals do not have relative move (ABCD). +#undef FTCONF_USE_ANSI_RELMOVE + +// Handling ANSI commands with 2 parameters (ex, ESC[m;nH) +// 2: Good terminals can accept any omit format (ESC[;nH) +// 1: Poor terminals (eg, Win/DOS telnet) can only omit 2nd (ESC[mH) +// 0: Very few poor terminals (eg, CrazyTerm/BBMan) cannot omit any parameters +#define FTCONF_ANSICMD2_OMIT (0) + +////////////////////////////////////////////////////////////////////////// +// Flat Terminal Definition +////////////////////////////////////////////////////////////////////////// + +#define FTSZ_DEFAULT_ROW (24) +#define FTSZ_DEFAULT_COL (80) +#define FTSZ_MIN_ROW (24) +#define FTSZ_MAX_ROW (100) +#define FTSZ_MIN_COL (80) +#define FTSZ_MAX_COL (320) + +#define FTCHAR_ERASE (' ') +#define FTATTR_ERASE (0x07) +#define FTCHAR_BLANK (' ') +#define FTATTR_DEFAULT (FTATTR_ERASE) +#define FTCHAR_INVALID_DBCS ('?') +// #define FTATTR_TRANSPARENT (0x80) + +#define FTDIRTY_CHAR (0x01) +#define FTDIRTY_ATTR (0x02) +#define FTDIRTY_DBCS (0x04) +#define FTDIRTY_INVALID_DBCS (0x08) +#define FTDIRTY_RAWMOVE (0x10) + +#define FTDBCS_SAFE (0) // standard DBCS +#define FTDBCS_UNSAFE (1) // not on all systems (better do rawmove) +#define FTDBCS_INVALID (2) // invalid + +#define FTCMD_MAXLEN (63) // max escape command sequence length +#define FTATTR_MINCMD (16) + +#ifndef FTCONF_USE_ANSI_RELMOVE +# define FTMV_COST (8) // always ESC[m;nH will costs avg 8 bytes +#else +# define FTMV_COST (5) // ESC[ABCD with ESC[m;nH costs avg 4+ bytes +#endif + +////////////////////////////////////////////////////////////////////////// +// Flat Terminal Data Type +////////////////////////////////////////////////////////////////////////// + +typedef unsigned char ftchar; // primitive character type +typedef unsigned char ftattr; // primitive attribute type + +////////////////////////////////////////////////////////////////////////// +// Flat Terminal Structure +////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + ftchar **cmap[2]; // character map + ftattr **amap[2]; // attribute map + ftchar *dmap; // dirty map + ftchar *dcmap; // processed display map + ftattr attr; + int rows, cols; + int y, x; + int sy,sx; // stored cursor + int mi; // map index, mi = current map and (1-mi) = old map + int dirty; + int scroll; + + // memory allocation + int mrows, mcols; + + // raw terminal status + int ry, rx; + ftattr rattr; + + // typeahead + char typeahead; + + // escape command + ftchar cmd[FTCMD_MAXLEN+1]; + int szcmd; + +} FlatTerm; + +static FlatTerm ft; + +#ifdef _WIN32 + // sorry, we support only 80x24 on Windows now. + HANDLE hStdout; + COORD coordBufSize = {80, 24}, coordBufCoord = {0, 0}; + SMALL_RECT winrect = {0, 0, 79, 23}; + CHAR_INFO winbuf[80*24]; +#endif + +////////////////////////////////////////////////////////////////////////// +// Flat Terminal Utility Macro +////////////////////////////////////////////////////////////////////////// + +// ftattr: 0| FG(3) | BOLD(1) | BG(3) | BLINK(1) |8 +#define FTATTR_FGSHIFT (0) +#define FTATTR_BGSHIFT (4) +#define FTATTR_GETFG(x) ((x >> FTATTR_FGSHIFT) & 0x7) +#define FTATTR_GETBG(x) ((x >> FTATTR_BGSHIFT) & 0x7) +#define FTATTR_FGMASK ((ftattr)(0x7 << FTATTR_FGSHIFT)) +#define FTATTR_BGMASK ((ftattr)(0x7 << FTATTR_BGSHIFT)) +#define FTATTR_BOLD (0x08) +#define FTATTR_BLINK (0x80) +#define FTATTR_DEFAULT_FG (FTATTR_GETFG(FTATTR_DEFAULT)) +#define FTATTR_DEFAULT_BG (FTATTR_GETBG(FTATTR_DEFAULT)) +#define FTATTR_MAKE(f,b) (((f)<=(0x80)) +#define FTDBCS_ISTAIL(x) (((unsigned char)(x))>=(0x40)) + +// - 0xFF is used as telnet IAC, don't use it! +// - 0x80 is invalid for Big5. +#define FTDBCS_ISBADLEAD(x) ((((unsigned char)(x)) == 0x80) || (((unsigned char)(x)) == 0xFF)) + +// even faster: +// #define FTDBCS_ISLEAD(x) (((unsigned char)(x)) & 0x80) +// #define FTDBCS_ISTAIL(x) (((unsigned char)(x)) & ((unsigned char)~0x3F)) + +#define FTDBCS_ISSBCSPRINT(x) \ + (((unsigned char)(x))>=' ' && ((unsigned char)(x))<0x80) + +#ifndef min +#define min(x,y) (((x)<(y))?(x):(y)) +#endif + +#ifndef max +#define max(x,y) (((x)>(y))?(x):(y)) +#endif + +#ifndef abs +#define abs(x) (((x)>0)?(x):-(x)) +#endif + +#define ranged(x,vmin,vmax) (max(min(x,vmax),vmin)) + + +////////////////////////////////////////////////////////////////////////// +// Flat Terminal API +////////////////////////////////////////////////////////////////////////// + +//// common ncurse-like library interface + +// initialization +void initscr (void); +int resizeterm (int rows, int cols); +int endwin (void); + +// attributes +ftattr attrget (void); +void attrset (ftattr attr); +void attrsetfg (ftattr attr); +void attrsetbg (ftattr attr); + +// cursor +void getyx (int *y, int *x); +void getmaxyx (int *y, int *x); +void move (int y, int x); + +// clear +void clear (void); // clrscr + move(0,0) +void clrtoeol (void); // end of line +void clrtobot (void); +// clear (non-ncurses) +void clrtoln (int ln); // clear down to ln ( excluding ln, as [y,ln) ) +void clrcurln (void); // whole line +void clrtobeg (void); // begin of line +void clrtohome (void); +void clrscr (void); // clear and keep cursor untouched +void clrregion (int r1, int r2); // clear [r1,r2], bi-directional. + +// window control +void newwin (int nlines, int ncols, int y, int x); + +// flushing +void refresh (void); // optimized refresh +void doupdate (void); // optimized refresh, ignore input queue +void redrawwin (void); // invalidate whole screen +int typeahead (int fd);// prevent refresh if input queue is not empty + +// scrolling +void scroll (void); // scroll up +void rscroll (void); // scroll down +void scrl (int rows); + +// output (ncurses flavor) +void addch (unsigned char c); // equivalent to outc() +void addstr (const char *s); // equivalent to outs() +void addnstr (const char *s, int n); + +// output (non-ncurses) +void outc (unsigned char c); +void outs (const char *s); +void outns (const char *s, int n); +void outstr (const char *str); // prepare and print a complete string. +void addstring (const char *str); // ncurses-like of outstr(). + +// readback +int instr (char *str); +int innstr (char *str, int n); // n includes \0 +int inansistr (char *str, int n); // n includes \0 + +// deprecated +void standout (void); +void standend (void); + +// grayout advanced control +void grayout (int y, int end, int level); + +//// flat-term internal processor + +int fterm_inbuf (void); // raw input adapter +void fterm_rawc (int c); // raw output adapter +void fterm_rawnewline(void); // raw output adapter +void fterm_rawflush (void); // raw output adapter +void fterm_raws (const char *s); +void fterm_rawnc (int c, int n); +void fterm_rawnum (int arg); +void fterm_rawcmd (int arg, int defval, char c); +void fterm_rawcmd2 (int arg1, int arg2, int defval, char c); +void fterm_rawattr (ftattr attr); // optimized changing attribute +void fterm_rawclear (void); +void fterm_rawclreol (void); +void fterm_rawhome (void); +void fterm_rawscroll (int dy); +void fterm_rawcursor (void); +void fterm_rawmove (int y, int x); +void fterm_rawmove_opt(int y, int x); +void fterm_rawmove_rel(int dy, int dx); + +int fterm_chattr (char *s, ftattr oa, ftattr na); // size(s) > FTATTR_MINCMD +void fterm_exec (void); // execute ft.cmd +void fterm_flippage (void); +void fterm_dupe2bk (void); +void fterm_markdirty (void); // mark as dirty +int fterm_strdlen (const char *s); // length of string for display +int fterm_prepare_str(int len); + +// DBCS supporting +int fterm_DBCS_Big5(unsigned char c1, unsigned char c2); + +////////////////////////////////////////////////////////////////////////// +// Flat Terminal Implementation +////////////////////////////////////////////////////////////////////////// + +#define fterm_markdirty() { ft.dirty = 1; } + +// initialization + +void +initscr(void) +{ +#ifdef _WIN32 + hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleScreenBufferSize(hStdout, coordBufSize); + SetConsoleCursorPosition(hStdout, coordBufCoord); +#endif + + memset(&ft, sizeof(ft), 0); + ft.attr = ft.rattr = FTATTR_DEFAULT; + resizeterm(FTSZ_DEFAULT_ROW, FTSZ_DEFAULT_COL); + + // clear both pages + ft.mi = 0; clrscr(); + ft.mi = 1; clrscr(); + ft.mi = 0; + + // typeahead + ft.typeahead = 1; + + fterm_rawclear(); + move(0, 0); +} + +int +endwin(void) +{ + int r, mi = 0; + + // fterm_rawclear(); + + for (mi = 0; mi < 2; mi++) + { + for (r = 0; r < ft.mrows; r++) + { + free(ft.cmap[mi][r]); + free(ft.amap[mi][r]); + } + } + free(ft.dmap); + free(ft.dcmap); + memset(&ft, sizeof(ft), 0); + return 0; +} + +int +resizeterm(int rows, int cols) +{ + int dirty = 0, mi = 0, i = 0; + + rows = ranged(rows, FTSZ_MIN_ROW, FTSZ_MAX_ROW); + cols = ranged(cols, FTSZ_MIN_COL, FTSZ_MAX_COL); + + // adjust memory only for increasing buffer + if (rows > ft.mrows || cols > ft.mcols) + { + for (mi = 0; mi < 2; mi++) + { + // allocate rows + if (rows > ft.mrows) + { + ft.cmap[mi] = (ftchar**)realloc(ft.cmap[mi], + sizeof(ftchar*) * rows); + ft.amap[mi] = (ftattr**)realloc(ft.amap[mi], + sizeof(ftattr*) * rows); + + // allocate new columns + for (i = ft.mrows; i < rows; i++) + { + ft.cmap[mi][i] = (ftchar*)malloc((cols+1) * sizeof(ftchar)); + ft.amap[mi][i] = (ftattr*)malloc((cols+1) * sizeof(ftattr)); + // zero at end to prevent over-run + ft.cmap[mi][i][cols] = 0; + ft.amap[mi][i][cols] = 0; + } + } + + // resize cols + if (cols > ft.mcols) + { + for (i = 0; i < ft.mrows; i++) + { + ft.cmap[mi][i] = (ftchar*)realloc(ft.cmap[mi][i], + (cols+1) * sizeof(ftchar)); + ft.amap[mi][i] = (ftattr*)realloc(ft.amap[mi][i], + (cols+1) * sizeof(ftattr)); + // zero at end to prevent over-run + ft.cmap[mi][i][cols] = 0; + ft.amap[mi][i][cols] = 0; + } + } else { + // we have to deal one case: + // expand x, shrink x, expand y -> + // now new allocated lines will have small x(col) instead of mcol. + // the solution is to modify mcols here, or change the malloc above + // to max(ft.mcols, cols). + ft.mcols = cols; + } + } + + // adjusts dirty and display map. + // no need to initialize anyway. + if (cols > ft.mcols) + { + ft.dmap = (ftchar*) realloc(ft.dmap, + (cols+1) * sizeof(ftchar)); + ft.dcmap = (ftchar*) realloc(ft.dcmap, + (cols+1) * sizeof(ftchar)); + } + + // do mrows/mcols assignment here, because we had 2 maps running loop above. + if (cols > ft.mcols) ft.mcols = cols; + if (rows > ft.mrows) ft.mrows = rows; + dirty = 1; + } + + // clear new exposed buffer after resized + // because we will redawwin(), so need to change front buffer only. + for (i = ft.rows; i < rows; i++) + { + memset(FTCMAP[i], FTCHAR_ERASE, + (cols) * sizeof(ftchar)); + memset(FTAMAP[i], FTATTR_ERASE, + (cols) * sizeof(ftattr)); + } + if (cols > ft.cols) + { + for (i = 0; i < ft.rows; i++) + { + memset(FTCMAP[i]+ft.cols, FTCHAR_ERASE, + (cols-ft.cols) * sizeof(ftchar)); + memset(FTAMAP[i]+ft.cols, FTATTR_ERASE, + (cols-ft.cols) * sizeof(ftattr)); + } + } + + if (ft.rows != rows || ft.cols != cols) + { + ft.rows = rows; + ft.cols = cols; + redrawwin(); + } + + ft.x = ranged(ft.x, 0, ft.cols-1); + ft.y = ranged(ft.y, 0, ft.rows-1); + + return dirty; +} + +// attributes + +ftattr +attrget(void) +{ + return ft.attr; +} + +void +attrset(ftattr attr) +{ + ft.attr = attr; +} + +void +attrsetfg(ftattr attr) +{ + ft.attr &= (~FTATTR_FGMASK); + ft.attr |= ((attr & FTATTR_FGMASK) << FTATTR_FGSHIFT); +} + +void +attrsetbg(ftattr attr) +{ + ft.attr &= (~FTATTR_BGMASK); + ft.attr |= ((attr & FTATTR_FGMASK) << FTATTR_BGSHIFT); +} + +// clear + +void +clrscr(void) +{ + int r; + for (r = 0; r < ft.rows; r++) + memset(FTCMAP[r], FTCHAR_ERASE, ft.cols * sizeof(ftchar)); + for (r = 0; r < ft.rows; r++) + memset(FTAMAP[r], FTATTR_ERASE, ft.cols * sizeof(ftattr)); + fterm_markdirty(); +} + +void +clear(void) +{ + clrscr(); + move(0,0); +} + +void +clrtoeol(void) +{ + ft.x = ranged(ft.x, 0, ft.cols-1); + ft.y = ranged(ft.y, 0, ft.rows-1); + memset(FTPC, FTCHAR_ERASE, ft.cols - ft.x); + memset(FTPA, FTATTR_ERASE, ft.cols - ft.x); + fterm_markdirty(); +} + +void +clrtobeg(void) +{ + ft.x = ranged(ft.x, 0, ft.cols-1); + ft.y = ranged(ft.y, 0, ft.rows-1); + memset(FTCROW, FTCHAR_ERASE, ft.x+1); + memset(FTAROW, FTATTR_ERASE, ft.x+1); + fterm_markdirty(); +} + +void +clrcurrline(void) +{ + ft.y = ranged(ft.y, 0, ft.rows-1); + memset(FTCROW, FTCHAR_ERASE, ft.cols); + memset(FTAROW, FTATTR_ERASE, ft.cols); + fterm_markdirty(); +} + +void +clrtoln(int line) +{ + if (line <= ft.y) + return; + clrregion(ft.y, line-1); +} + +void +clrregion(int r1, int r2) +{ + // bi-direction + if (r1 > r2) + { + int r = r1; + r1 = r2; r2 = r; + } + + // check r1, r2 range + r1 = ranged(r1, 0, ft.rows-1); + r2 = ranged(r2, 0, ft.rows-1); + + for (; r1 <= r2; r1++) + { + memset(FTCMAP[r1], FTCHAR_ERASE, ft.cols); + memset(FTAMAP[r1], FTATTR_ERASE, ft.cols); + } + fterm_markdirty(); +} + +void +clrtobot(void) +{ + clrtoeol(); + clrregion(ft.y+1, ft.rows-1); +} + +void +clrtohome(void) +{ + clrtobeg(); + clrregion(ft.y-1, 0); +} + +void newwin (int nlines, int ncols, int y, int x) +{ + int oy, ox; + // check range + + x = ranged(x, 0, ft.cols-1); + y = ranged(y, 0, ft.rows-1); + ncols = ranged(x+ncols-1, x, ft.cols-1); + nlines = ranged(y+nlines-1, y, ft.rows-1); + ncols = ncols - x + 1; + nlines= nlines- y + 1; + + if (nlines <= 0 || ncols <= 0) + return; + getyx(&oy, &ox); + + while (nlines-- > 0) + { + move(y++, x); + // use prepare_str to erase character + fterm_prepare_str(ncols); + // memset(FTAMAP[y]+x, ft.attr, ncols); + // memset(FTCMAP[y]+x, FTCHAR_ERASE, ncols); + } + move(oy, ox); +} + +// dirty and flushing + +void +redrawwin(void) +{ + // flip page + fterm_flippage(); + clrscr(); + + // clear raw terminal + fterm_rawclear(); + + // flip page again + fterm_flippage(); + + // mark for refresh. + fterm_markdirty(); +} + +int +typeahead(int fd) +{ + switch(fd) + { + case TYPEAHEAD_NONE: + ft.typeahead = 0; + break; + case TYPEAHEAD_STDIN: + ft.typeahead = 1; + break; + default: // shall never reach here + assert(NULL); + break; + } + return 0; +} + +void +refresh(void) +{ + // prevent passive update + if(fterm_inbuf() && ft.typeahead) + return; + doupdate(); +} + +void +doupdate(void) +{ + int y, x; + char touched = 0; + + if (!ft.dirty) + { + fterm_rawcursor(); + return; + } + +#ifdef _WIN32 + + assert(ft.rows == coordBufSize.Y); + assert(ft.cols == coordBufSize.X); + + for (y = 0; y < ft.rows; y++) + { + for (x = 0; x < ft.cols; x++) + { + WORD xAttr = FTAMAP[y][x], xxAttr; + // w32 attribute: bit swap (0,2) and (4, 6) + xxAttr = xAttr & 0xAA; + if (xAttr & 0x01) xxAttr |= 0x04; + if (xAttr & 0x04) xxAttr |= 0x01; + if (xAttr & 0x10) xxAttr |= 0x40; + if (xAttr & 0x40) xxAttr |= 0x10; + + winbuf[y*ft.cols + x].Attributes= xxAttr; + winbuf[y*ft.cols + x].Char.AsciiChar = FTCMAP[y][x]; + } + } + WriteConsoleOutputA(hStdout, winbuf, coordBufSize, coordBufCoord, &winrect); + +#else // !_WIN32 + + // if scroll, do it first + if (ft.scroll) + fterm_rawscroll(ft.scroll); + + // calculate and optimize dirty + for (y = 0; y < ft.rows; y++) + { + int len = ft.cols, ds = 0, derase = 0; + char dbcs = 0, odbcs = 0; // 0: none, 1: lead, 2: tail + + // reset dirty and display map + memset(FTD, 0, ft.cols * sizeof(ftchar)); + memcpy(FTDC,FTCMAP[y], ft.cols * sizeof(ftchar)); + + // first run: character diff + for (x = 0; x < len; x++) + { + // build base dirty information + if (FTCMAP[y][x] != FTOCMAP[y][x]) + FTD[x] |= FTDIRTY_CHAR, ds++; + if (FTAMAP[y][x] != FTOAMAP[y][x]) + FTD[x] |= FTDIRTY_ATTR, ds++; + + // determine DBCS status + if (dbcs == 1) + { +#ifdef FTCONF_PREVENT_INVALID_DBCS + switch(fterm_DBCS_Big5(FTCMAP[y][x-1], FTCMAP[y][x])) + { + case FTDBCS_SAFE: + // safe to print + FTD[x-1] &= ~FTDIRTY_INVALID_DBCS; + FTDC[x-1] = FTCMAP[y][x-1]; + break; + + case FTDBCS_UNSAFE: + // ok to print, but need to rawmove. + FTD[x-1] &= ~FTDIRTY_INVALID_DBCS; + FTDC[x-1] = FTCMAP[y][x-1]; + FTD[x-1] |= FTDIRTY_CHAR; + FTD[x] |= FTDIRTY_RAWMOVE; + break; + + case FTDBCS_INVALID: + // only SBCS safe characters can be print. + if (!FTDBCS_ISSBCSPRINT(FTCMAP[y][x])) + { + FTD[x] |= FTDIRTY_INVALID_DBCS; + FTDC[x] = FTCHAR_INVALID_DBCS; + } + break; + } +#endif // FTCONF_PREVENT_INVALID_DBCS + + dbcs = 2; + // TAIL: dirty prev and me if any is dirty. + if (FTD[x] || FTD[x-1]) + { + FTD[x] |= FTDIRTY_DBCS; + FTD[x-1]|= FTDIRTY_CHAR; + } + } + else if (FTDBCS_ISLEAD(FTCMAP[y][x])) + { + // LEAD: clear dirty when tail was found. + dbcs = 1; +#ifdef FTCONF_PREVENT_INVALID_DBCS + FTD[x] |= FTDIRTY_INVALID_DBCS; + FTDC[x] = FTCHAR_INVALID_DBCS; +#endif // FTCONF_PREVENT_INVALID_DBCS + } + else + { + // NON-DBCS + dbcs = 0; + } + + if (odbcs == 1) + { + // TAIL: dirty prev and me if any is dirty. + odbcs = 2; + if (FTD[x] || FTD[x-1]) + { + FTD[x] |= FTDIRTY_CHAR; + FTD[x-1]|= FTDIRTY_CHAR; + } + } + else if (FTDBCS_ISLEAD(FTOCMAP[y][x])) + { + // LEAD: dirty next? + odbcs = 1; + } + else + { + odbcs = 0; + } + } + +#ifndef DBG_SHOW_DIRTY + if (!ds) + continue; +#endif // DBG_SHOW_DIRTY + + // Optimization: calculate ERASE section + // TODO ERASE takes 3 bytes (ESC [ K), so enable only if derase >= 3? + // TODO ERASE then print can avoid lots of space, optimize in future. + for (x = ft.cols - 1; x >= 0; x--) + if (FTCMAP[y][x] != FTCHAR_ERASE || + FTAMAP[y][x] != FTATTR_ERASE) + break; + else if (FTD[x]) + derase++; + + len = x+1; + + for (x = 0; x < len; x++) + { +#ifndef DBG_SHOW_DIRTY + if (!FTD[x]) + continue; +#endif // !DBG_SHOW_DIRTY + + // Optimization: re-print or move? +#ifndef DBG_DISABLE_REPRINT + while (ft.ry == y && x > ft.rx && abs(x-ft.rx) < FTMV_COST) + { + int i; + // Special case: we may be in position of DBCS tail... + // Inside a refresh() loop, this will never happen. + // However it may occur for the first print entering refresh. + // So enable only space if this is the first run (!touched). + + // if we don't need to change attributes, + // just print remaining characters + for (i = ft.rx; i < x; i++) + { + // if same attribute, simply accept. + if (FTAMAP[y][i] == ft.rattr && touched) + continue; + // XXX spaces may accept (BG=rBG), + // but that will also change cached attribute. + if (!FTCHAR_ISBLANK(FTCMAP[y][i])) + break; + if (FTATTR_GETBG(FTAMAP[y][i]) != FTATTR_GETBG(ft.rattr)) + break; + } + if (i != x) + break; + + // safe to print all! + // printf("[re-print %d chars]", i-ft.rx); + +#ifdef DBG_SHOW_REPRINT + // reverse to hint this is a re-print + fterm_rawattr(FTATTR_MAKE(0, 7) | FTATTR_BOLD); +#endif // DBG_SHOW_REPRINT + + for (i = ft.rx; i < x; i++) + { + fterm_rawc(FTDC[i]); + FTAMAP[y][i] = FTOAMAP[y][i]; // spaces may change attr... + ft.rx++; + } + + break; + } +#endif // !DBG_DISABLE_REPRINT + + if (y != ft.ry || x != ft.rx) + fterm_rawmove_opt(y, x); + +#ifdef DBCSAWARE + if ((FTD[x] & FTDIRTY_DBCS) && (FT_DBCS_NOINTRESC)) + { + // prevent changing attributes inside DBCS + } + else +#endif // DBCSAWARE +#ifdef DBG_SHOW_DIRTY + fterm_rawattr(FTD[x] ? + (FTAMAP[y][x] | FTATTR_BOLD) : (FTAMAP[y][x] & ~FTATTR_BOLD)); +#else // !DBG_SHOW_DIRTY + fterm_rawattr(FTAMAP[y][x]); +#endif // !DBG_SHOW_DIRTY + + fterm_rawc(FTDC[x]); + ft.rx++; + touched = 1; + + if (FTD[x] & FTDIRTY_RAWMOVE) + { + fterm_rawcmd2(ft.ry+1, ft.rx+1, 1, 'H'); + } + } + + if (derase) + { + fterm_rawmove_opt(y, len); + fterm_rawclreol(); + } + } + +#endif // !_WIN32 + + // doing fterm_rawcursor() earlier to enable max display time + fterm_rawcursor(); + fterm_dupe2bk(); + ft.dirty = 0; +} + +// cursor management + +void +getyx(int *y, int *x) +{ + if (y) + *y = ft.y; + if (x) + *x = ft.x; +} + +void +getmaxyx(int *y, int *x) +{ + if (y) + *y = ft.rows; + if (x) + *x = ft.cols; +} + +void +move(int y, int x) +{ + ft.y = ranged(y, 0, ft.rows-1); + ft.x = ranged(x, 0, ft.cols-1); +} + +// scrolling + +void +scrl(int rows) +{ + if (!rows) + return; + if (rows > 0) + { + for (; rows > 0; rows--) + scroll(); + } else { + for (; rows < 0; rows++) + rscroll(); + } +} + +void +scroll() +{ + // scroll up + int y; + ftchar *c0 = FTCMAP[0], *oc0 = FTOCMAP[0]; + ftattr *a0 = FTAMAP[0], *oa0 = FTOAMAP[0]; + + // prevent mixing buffered scroll up+down + if (ft.scroll < 0) + fterm_rawscroll(ft.scroll); + + // smart scroll: move pointers + for (y = 0; y < ft.rows-1; y++) + { + FTCMAP[y] = FTCMAP[y+1]; + FTAMAP[y] = FTAMAP[y+1]; + FTOCMAP[y]= FTOCMAP[y+1]; + FTOAMAP[y]= FTOAMAP[y+1]; + } + FTCMAP[y] = c0; + FTAMAP[y] = a0; + FTOCMAP[y]= oc0; + FTOAMAP[y]= oa0; + + // XXX also clear backup buffer + // must carefully consider if up then down scrolling. + fterm_flippage(); + clrregion(ft.rows-1, ft.rows-1); + fterm_flippage(); + clrregion(ft.rows-1, ft.rows-1); + + ft.scroll ++; + // fterm_markdirty(); // should be already dirty +} + +void +rscroll() +{ + // scroll down + int y; + ftchar *c0 = FTCMAP[ft.rows -1], *oc0 = FTOCMAP[ft.rows -1]; + ftattr *a0 = FTAMAP[ft.rows -1], *oa0 = FTOAMAP[ft.rows -1]; + + // prevent mixing buffered scroll up+down + if (ft.scroll > 0) + fterm_rawscroll(ft.scroll); + + // smart scroll: move pointers + for (y = ft.rows -1; y > 0; y--) + { + FTCMAP[y] = FTCMAP[y-1]; + FTAMAP[y] = FTAMAP[y-1]; + FTOCMAP[y]= FTOCMAP[y-1]; + FTOAMAP[y]= FTOAMAP[y-1]; + } + FTCMAP[y] = c0; + FTAMAP[y] = a0; + FTOCMAP[y]= oc0; + FTOAMAP[y]= oa0; + + // XXX also clear backup buffer + // must carefully consider if up then down scrolling. + fterm_flippage(); + clrregion(0, 0); + fterm_flippage(); + clrregion(0, 0); + + ft.scroll --; + // fterm_markdirty(); // should be already dirty +} + +// output + +void +addch (unsigned char c) +{ + outc(c); +} + +void +addstr (const char *s) +{ + outs(s); +} + +void +addnstr(const char *s, int n) +{ + outns(s, n); +} + +void +addstring(const char *s) +{ + outstr(s); +} + +void +outs(const char *s) +{ + if (!s) + return; + while (*s) + outc(*s++); +} + +void +outns(const char *s, int n) +{ + if (!s) + return; + while (*s && n-- > 0) + outc(*s++); +} + +void +outstr(const char *str) +{ + if (!str) + { + fterm_prepare_str(0); + return; + } + + // calculate real length of str (stripping escapes) + // TODO only print by the returned size + + fterm_prepare_str(fterm_strdlen(str)); + + outs(str); + + // maybe the source string itself is broken... + // basically this check should be done by clients, not term library. +#if 0 + { + int isdbcs = 0; + while (*str) + { + if (isdbcs == 1) isdbcs = 2; + else if (FTDBCS_ISLEAD(*str)) isdbcs = 1; + else isdbcs = 0; + str++; + } + + if (isdbcs == 1) // incomplete string! + outs("\b?"); + } +#endif +} + +void +outc(unsigned char c) +{ + // 0xFF is invalid for most cases (even DBCS), + if (c == 0xFF || c == 0x00) + return; + + fterm_markdirty(); + if (ft.szcmd) + { + // collecting commands + ft.cmd[ft.szcmd++] = c; + + if ((ft.szcmd == 2 && c == '[') || + (ANSI_IS_PARAM(c) && ft.szcmd < FTCMD_MAXLEN)) + return; + + // process as command + fterm_exec(); + ft.szcmd = 0; + } + else if (c == ESC_CHR) + { + // start of escaped commands + ft.cmd[ft.szcmd++] = c; + } + else if (c == '\t') + { + // tab: move by 8, and erase the moved range + int x = ft.x; + if (x % 8 == 0) + x += 8; + else + x += (8-(x%8)); + x = ranged(x, 0, ft.cols-1); + // erase the characters between + if (x > ft.x) + { + memset(FTCROW+ft.x, FTCHAR_ERASE, x - ft.x); + memset(FTAROW+ft.x, ft.attr, x-ft.x); + } + ft.x = x; + } + else if (c == '\b') + { + ft.x = ranged(ft.x-1, 0, ft.cols-1); + } + else if (c == '\r' || c == '\n') + { + // new line: cursor movement, and do not print anything + // XXX old screen.c also calls clrtoeol() for newlins. + clrtoeol(); + ft.x = 0; + ft.y ++; + while (ft.y >= ft.rows) + { + // XXX scroll at next dirty? + // screen.c ignored such scroll. + // scroll(); + ft.y --; + } + } + else if (iscntrl((unsigned char)c)) + { + // unknown control characters: ignore + } + else // normal characters + { + assert (ft.x >= 0 && ft.x < ft.cols); + + // normal characters + FTC = c; +#ifdef FTATTR_TRANSPARENT + if (ft.attr != FTATTR_TRANSPARENT) +#endif // FTATTR_TRANSPARENT + FTA = ft.attr; + + ft.x++; + // XXX allow x == ft.cols? + if (ft.x >= ft.cols) + { + ft.x = 0; + ft.y ++; + while (ft.y >= ft.rows) + { + // XXX scroll at next dirty? + // screen.c ignored such scroll. + // scroll(); + ft.y --; + } + } + } +} + +// readback +int +instr (char *str) +{ + int x = ft.cols -1; + *str = 0; + if (ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) + return 0; + + // determine stopping location + while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE) + x--; + if (x < ft.x) return 0; + x = x - ft.x + 1; + memcpy(str, FTCROW+ft.x, x); + str[x] = 0; + return x; +} + +int +innstr (char *str, int n) +{ + int on = n; + int x = ranged(ft.x + n-1, 0, ft.cols-1); + *str = 0; + n = x - ft.x+1; + if (on < 1 || ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) + return 0; + + // determine stopping location + while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE) + x--; + if (x < ft.x) return 0; + n = x - ft.x + 1; + if (n >= on) n = on-1; + memcpy(str, FTCROW+ft.x, n); + str[n] = 0; + return n; +} + +int +inansistr (char *str, int n) +{ + int x = ft.cols -1, i = 0, szTrail = 0; + char *ostr = str; + char cmd[FTATTR_MINCMD*2] = ""; + + ftattr a = FTATTR_DEFAULT; + *str = 0; + if (ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) + return 0; + + if (n < 1) + return 0; + n--; // preserve last zero + + // determine stopping location + while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE && FTAROW[x] == FTATTR_ERASE) + x--; + + // retrieve [rt.x, x] + if (x < ft.x) return 0; + + // preserve some bytes if last attribute is not FTATTR_DEFAULT + for (i = ft.x; n > szTrail && i <= x; i++) + { + *str = 0; + + if (a != FTAROW[i]) + { + fterm_chattr(cmd, a, FTAROW[i]); + a = FTAROW[i]; + + if (a != FTATTR_DEFAULT) + szTrail = 3; // ESC [ m + else + szTrail = 0; + + if (strlen(cmd) >= n-szTrail) + break; + + strcpy(str, cmd); + n -= strlen(cmd); + str += strlen(cmd); + } + + // n should > szTrail + *str ++ = FTCROW[i]; + n--; + } + + if (szTrail && n >= szTrail) + { + *str++ = ESC_CHR; *str++ = '['; *str++ = 'm'; + } + + *str = 0; + return (str - ostr); +} + +// internal core of piaip's flat-term + +void +fterm_flippage (void) +{ + // we have only 2 pages now. + ft.mi = 1 - ft.mi; +} + +#ifndef fterm_markdirty +void +fterm_markdirty (void) +{ + ft.dirty = 1; +} +#endif + +void fterm_dupe2bk(void) +{ + int r = 0; + + for (r = 0; r < ft.rows; r++) + { + memcpy(FTOCMAP[r], FTCMAP[r], ft.cols * sizeof(ftchar)); + memcpy(FTOAMAP[r], FTAMAP[r], ft.cols * sizeof(ftattr)); + } +} + +int +fterm_DBCS_Big5(unsigned char c1, unsigned char c2) +{ + // ref: http://www.cns11643.gov.tw/web/word/big5/index.html + // High byte: 0xA1-0xFE, 0x8E-0xA0, 0x81-0x8D + // Low byte: 0x40-0x7E, 0xA1-0xFE + // C1: 0x80-0x9F + if (FTDBCS_ISBADLEAD(c1)) + return FTDBCS_INVALID; + if (!FTDBCS_ISTAIL(c2)) + return FTDBCS_INVALID; + if (c1 >= 0x80 && c1 <= 0x9F) + return FTDBCS_UNSAFE; + return FTDBCS_SAFE; +} + +int +fterm_prepare_str(int len) +{ + // clear and make (cursor, +len) as DBCS-ready. + int x = ranged(ft.x, 0, ft.cols-1); + int y = ranged(ft.y, 0, ft.rows-1); + int dbcs = 0, sdbcs = 0; + + // TODO what if x+len is outside range? + + // check if (x,y) is in valid range + if (x != ft.x || y != ft.y) + return -1; + + len = ranged(x+len, x, ft.cols); + + for (x = 0; x < len; x++) + { + // determine DBCS status + if (dbcs == 1) + dbcs = 2; // TAIL + else if (FTDBCS_ISLEAD(FTCROW[x])) + dbcs = 1; // LEAD + else + dbcs = 0; + if (x == ft.x) sdbcs = dbcs; + } + + x = ft.x; + // fix start and end + if(sdbcs == 2 && x > 0) // TAIL, remove word + x--; + if (dbcs == 1 && len < ft.cols) // LEAD, remove word + len ++; + len = ranged(len, 0, ft.cols); + len -= x; + if (len < 0) len = 0; + + memset(FTCROW + x, FTCHAR_ERASE, len); + memset(FTAROW + x, ft.attr, len); + return len; +} + + +void +fterm_exec(void) +{ + ftchar cmd = ft.cmd[ft.szcmd-1]; + char *p = (char*)ft.cmd + 2; // ESC [ + int n = -1, x = -1, y; + + ft.cmd[ft.szcmd] = 0; + + if (isdigit(*p)) + { + n = atoi(p); + + while (*p && isdigit(*p)) + p++; + + if (*p == ';') + p++; + // p points to next param now + } + + switch(cmd) + { + // Cursor Movement + + case 'A': // CUU: CSI n A + case 'B': // CUD: CSI n B + case 'C': // CUF: CSI n C + case 'D': // CUB: CSI n D + // Moves the cursor n (default 1) cells in the given direction. + // If the cursor is already at the edge of the screen, this has no effect. + if (n < 1) + n = 1; + getyx(&y, &x); + if (cmd == 'A') { y -= n; } + else if (cmd == 'B') { y += n; } + else if (cmd == 'C') { x += n; } + else if (cmd == 'D') { x -= n; } + move(y, x); + break; + + case 'E': // CNL: CSI n E + case 'F': // CPL: CSI n F + // Moves cursor to beginning of the line + // n (default 1) lines up/down (next/previous line). + if (n < 1) + n = 1; + getyx(&y, &x); + if (cmd == 'E') { y -= n; } + else if (cmd == 'F') { y += n; } + move(y, 0); + break; + + case 'G': // CHA: CSI n G + // Moves the cursor to column n. + if (n < 1) + n = 1; + getyx(&y, &x); + move(y, n-1); + break; + + case 'H': // CUP: CSI n ; m H + case 'f': // HVP: CSI n ; m f + // Moves the cursor to row n, column m. + // The values are 1-based, and default to 1 (top left corner) if omitted. + // A sequence such as CSI ;5H is a synonym for CSI 1;5H as well as + // CSI 17;H is the same as CSI 17H and CSI 17;1H + y = n; + if (y >= 0 && isdigit(*p)) + x = atoi((char*)p); + if (y < 0) y = 1; + if (x < 0) x = 1; + move(y-1, x-1); + break; + + // Clear + + case 'J': // ED: CSI n J + // Clears part of the screen. + // If n is zero (or missing), clear from cursor to end of screen. + // If n is one, clear from cursor to beginning of the screen. + // If n is two, clear entire screen + if (n == 0 || n < 0) + clrtobot(); + else if (n == 1) + clrtohome(); + else if (n == 2) + { + clrregion(0, ft.rows-1); + } + break; + + case 'K': // EL: CSI n K + // Erases part of the line. + // If n is zero (or missing), clear from cursor to the end of the line. + // If n is one, clear from cursor to beginning of the line. + // If n is two, clear entire line. Cursor position does not change. + if (n == 0 || n < 0) + clrtoeol(); + else if (n == 1) + clrtobeg(); + else if (n == 2) + clrcurrline(); + break; + + case 's': // SCP: CSI s + // Saves the cursor position. + getyx(&ft.sy, &ft.sx); + break; + + case 'u': // RCP: CSI u + // Restores the cursor position. + move(ft.sy, ft.sx); + break; + + case 'S': // SU: CSI n S + // Scroll whole page up by n (default 1) lines. + // New lines are added at the bottom. + if (n < 1) + n = 1; + scrl(n); + break; + + case 'T': // SD: CSI n T + // Scroll whole page down by n (default 1) lines. + // New lines are added at the top. + if (n < 1) + n = 1; + scrl(-n); + break; + + case 'm': // SGR: CSI n [;k] m + // Sets SGR (Select Graphic Rendition) parameters. + // After CSI can be zero or more parameters separated with ;. + // With no parameters, CSI m is treated as CSI 0 m (reset / normal), + // which is typical of most of the ANSI codes. + // --------------------------------------------------------- + // SGR implementation: + // SGR 0 (reset/normal) is supported. + // SGR 1 (intensity: bold) is supported. + // SGR 2 (intensity: faint) is not supported. + // SGR 3 (italic: on) is not supported. (converted to inverse?) + // SGR 4 (underline: single) is not supported. + // SGR 5 (blink: slow) is supported. + // SGR 6 (blink: rapid) is converted to (blink: slow) + // SGR 7 (image: negative) is partially supported (not a really attribute). + // SGR 8 (conceal) is not supported. + // SGR 21(underline: double) is not supported. + // SGR 22(intensity: normal) is supported. + // SGR 24(underline: none) is not supported. + // SGR 25(blink: off) is supported. + // SGR 27(image: positive) is not supported. + // SGR 28(reveal) is not supported. + // SGR 30-37 (FG) is supported. + // SGR 39 (FG-reset) is supported. + // SGR 40-47 (BG) is supported. + // SGR 49 (BG-reset) is supported. + if (n == -1) // first param + n = 0; + while (n > -1) + { + if (n >= 30 && n <= 37) + { + // set foreground + attrsetfg(n - 30); + } + else if (n >= 40 && n <= 47) + { + // set background + attrsetbg(n - 40); + } + else switch(n) + { + case 0: + attrset(FTATTR_DEFAULT); + break; + case 1: + attrset(attrget() | FTATTR_BOLD); + break; + case 22: + attrset(attrget() & ~FTATTR_BOLD); + break; + case 5: + case 6: + attrset(attrget() | FTATTR_BLINK); + break; + case 25: + attrset(attrget() & ~FTATTR_BLINK); + break; + case 3: + case 7: + { + ftattr a = attrget(); + attrsetfg(FTATTR_GETBG(a)); + attrsetbg(FTATTR_GETFG(a)); + } + break; + case 39: + attrsetfg(FTATTR_DEFAULT_FG); + break; + case 49: + attrsetbg(FTATTR_DEFAULT_BG); + break; + } + + // parse next command + n = -1; + if (*p == ';') + { + n = 0; + p++; + } + else if (isdigit(*p)) + { + n = atoi(p); + while (isdigit(*p)) p++; + if (*p == ';') + p++; + } + } + break; + + default: // unknown command. + break; + } +} + +int +fterm_chattr(char *s, ftattr oattr, ftattr nattr) +{ + ftattr + fg, bg, bold, blink, + ofg, obg, obold, oblink; + char lead = 1; + + if (oattr == nattr) + return 0; + + *s++ = ESC_CHR; + *s++ = '['; + + // optimization: reset as default + if (nattr == FTATTR_DEFAULT) + { + *s++ = 'm'; + *s++ = 0; + return 1; + } + + fg = FTATTR_GETFG(nattr); + bg = FTATTR_GETBG(nattr); + bold = (nattr & FTATTR_BOLD) ? 1 : 0; + blink = (nattr & FTATTR_BLINK)? 1 : 0; + + ofg = FTATTR_GETFG(oattr); + obg = FTATTR_GETBG(oattr); + obold = (oattr & FTATTR_BOLD) ? 1 : 0; + oblink = (oattr & FTATTR_BLINK)? 1 : 0; + + // we dont use "disable blink/bold" commands, + // so if these settings are changed then we must reset. + // another case is changing background to default background - + // better use "RESET" to override it. + // Same for foreground. + // Possible optimization: when blink/bold on, don't RESET + // for background change? + if ((oblink != blink && !blink) || + (obold != bold && !bold) || + (bg == FTATTR_DEFAULT_BG && obg != bg) || + (fg == FTATTR_DEFAULT_FG && ofg != fg) ) + { + if (lead) lead = 0; else *s++ = ';'; + *s++ = '0'; + + ofg = FTATTR_DEFAULT_FG; + obg = FTATTR_DEFAULT_BG; + obold = 0; oblink = 0; + } + + if (bold && !obold) + { + if (lead) lead = 0; else *s++ = ';'; + *s++ = '1'; + +#ifdef FTCONF_WORKAROUND_BOLD + // Issue here: + // PacketSite does not understand ESC[1m. It needs ESC[1;37m + // NetTerm defaults bold color to yellow. + // As a result, we use ESC[1;37m for the case. + if (fg == FTATTR_DEFAULT_FG) + ofg = ~ofg; +#endif // FTCONF_WORKAROUND_BOLD + + } + if (blink && !oblink) + { + if (lead) lead = 0; else *s++ = ';'; + *s++ = '5'; // XXX 5(slow) or 6(fast)? + } + if (ofg != fg) + { + if (lead) lead = 0; else *s++ = ';'; + *s++ = '3'; + *s++ = '0' + fg; + } + if (obg != bg) + { + if (lead) lead = 0; else *s++ = ';'; + *s++ = '4'; + *s++ = '0' + bg; + } + *s++ = 'm'; + *s++ = 0; + return 1; +} + +int +fterm_strdlen(const char *s) +{ + char ansi = 0; + int sz = 0; + + // the logic should match outc(). + + while (s && *s) + { + if (!ansi) // ansi == 0 + { + switch(*s) + { + case ESC_CHR: + ansi++; + break; + + case '\r': + case '\n': + break; + + case '\b': + if (sz) sz--; + break; + + case '\t': + // XXX how to deal with this? + sz ++; + break; + + default: + if (!iscntrl((unsigned char)*s)) + sz++; + break; + } + } + else if (ansi == 1) + { + if (*s == '[') + ansi++; + else + ansi = 0; + } + else if (!ANSI_IS_PARAM(*s)) // ansi == 2 + { + // TODO outc() take max to FTCMD_MAXLEN now... + ansi = 0; + } + s++; + } + return sz; +} + +void +fterm_rawattr(ftattr rattr) +{ + static char cmd[FTATTR_MINCMD*2]; + if (!fterm_chattr(cmd, ft.rattr, rattr)) + return; + + fterm_raws(cmd); + ft.rattr = rattr; +} + +void +fterm_rawnum(int arg) +{ + if (arg < 0 || arg > 99) + { + // complex. use printf. + char sarg[16]; // max int + sprintf(sarg, "%d", arg); + fterm_raws(sarg); + } else if (arg < 10) { + // 0 .. 10 + fterm_rawc('0' + arg); + } else { + fterm_rawc('0' + arg/10); + fterm_rawc('0' + arg%10); + } +} +void +fterm_rawcmd(int arg, int defval, char c) +{ + fterm_rawc(ESC_CHR); + fterm_rawc('['); + if (arg != defval) + fterm_rawnum(arg); + fterm_rawc(c); +} + +void +fterm_rawcmd2(int arg1, int arg2, int defval, char c) +{ + fterm_rawc(ESC_CHR); + fterm_rawc('['); + + // See FTCONF_ANSICMD2_OMIT + // XXX Win/DOS telnet does now accept omitting first value + // ESC[nX and ESC[n;X works, but ESC[;mX does not work. + if (arg1 != defval || arg2 != defval) + { +#if (FTCONF_ANSICMD2_OMIT >= 2) + if (arg1 != defval) +#endif + fterm_rawnum(arg1); + +#if (FTCONF_ANSICMD2_OMIT >= 1) + if (arg2 != defval) +#endif + { + fterm_rawc(';'); + fterm_rawnum(arg2); + } + } + fterm_rawc(c); +} + +void +fterm_rawclear(void) +{ + fterm_rawhome(); + // ED: CSI n J, 0 = cursor to bottom, 2 = whole + fterm_raws(ESC_STR "[2J"); +} + +void +fterm_rawclreol(void) +{ +#ifdef FTCONF_CLEAR_SETATTR + // ftattr oattr = ft.rattr; + // XXX If we skip with "backround only" here, future updating + // may get wrong attributes. Or not? (consider DBCS...) + // if (FTATTR_GETBG(oattr) != FTATTR_GETBG(FTATTR_ERASE)) + fterm_rawattr(FTATTR_ERASE); +#endif + + // EL: CSI n K, n = 0 + fterm_raws(ESC_STR "[K"); + +#ifdef FTCONF_CLEAR_SETATTR + // No need to do so? because we will always reset attribute... + // fterm_rawattr(oattr); +#endif +} + +void +fterm_rawhome(void) +{ + // CUP: CSI n ; m H + fterm_raws(ESC_STR "[H"); + ft.rx = ft.ry = 0; +} + +void +fterm_rawmove_rel(int dy, int dx) +{ +#ifndef FTCONF_USE_ANSI_RELMOVE + // Old BBS system does not output relative moves (ESC[ABCD) . + // Poor terminals (ex, pcman-1.0.5-FF20.xpi) + // do not support relmoves + fterm_rawmove(ft.ry + dy, ft.rx + dx); +#else + if (!dx) + { + int y = ranged(dy + ft.ry, 0, ft.rows-1); + dy = y - ft.ry; + if (!dy) + return; + + fterm_rawcmd(abs(dy), 1, dy < 0 ? 'A' : 'B'); + ft.ry = y; + } + else if (!dy) + { + int x = ranged(dx + ft.rx, 0, ft.cols-1); + dx = x - ft.rx; + if (!dx) + return; + + fterm_rawcmd(abs(dx), 1, dx < 0 ? 'D' : 'C'); + ft.rx = x; + } + else + { + // (dy, dx) are given - use fterm_move. + fterm_rawmove(ft.ry + dy, ft.rx + dx); + } +#endif +} + +void +fterm_rawmove(int y, int x) +{ + y = ranged(y, 0, ft.rows-1); + x = ranged(x, 0, ft.cols-1); + + if (y == ft.ry && x == ft.rx) + return; + + // CUP: CSI n ; m H + fterm_rawcmd2(y+1, x+1, 1, 'H'); + + ft.ry = y; + ft.rx = x; +} + +void +fterm_rawmove_opt(int y, int x) +{ + // optimized move + int ady = abs(y-ft.ry), adx=abs(x-ft.rx); + + if (!adx && !ady) + return; + +#ifdef DBG_DISABLE_OPTMOVE + return fterm_rawmove(y, x); +#endif + + // known hacks: \r = (x=0), \b=(x--), \n = (y++) + // + // Warning: any optimization here should not change displayed content, + // because we don't have information about content variation information + // (eg, invalid DBCS bytes will become special marks) here. + // Any hacks which will try to display data from FTCMAP should be done + // inside dirty-map calculation, for ex, using spaces to move right, + // or re-print content. + +#ifndef DBG_TEXT_FD + // x=0: the cheapest output. However not work for text mode fd output. + // a special case is "if we have to move y to up". + // and FTCONF_ANSICMD2_OMIT < 1 (cannot omit x). +#if FTCONF_ANSICMD2_OMIT < 1 + if (y >= ft.ry) +#endif + if (adx && x == 0) + { + fterm_rawc('\r'); + ft.rx = x = adx = 0; + } + +#endif // !DBG_TEXT_FD + + // x--: compare with FTMV_COST: ESC[m;nH costs 5-8 bytes + if (x < ft.rx && y >= ft.ry && (adx+ady) < FTMV_COST) + { + while (adx > 0) + fterm_rawc('\b'), adx--; + ft.rx = x; + } + + // finishing movement + if (y > ft.ry && ady < FTMV_COST && adx == 0) + { + while (ft.ry++ < y) + fterm_rawc('\n'); + ft.ry = y; + } + else if (ady && adx) + { + fterm_rawmove(y, x); + } + else if (ady) + { + fterm_rawmove_rel(y-ft.ry, 0); + } + else if (adx) + { + fterm_rawmove_rel(0, x-ft.rx); + } +} + +void +fterm_rawcursor(void) +{ +#ifdef _WIN32 + COORD cursor; + cursor.X = ft.x; + cursor.Y = ft.y; + SetConsoleCursorPosition(hStdout, cursor); +#else + // fterm_rawattr(FTATTR_DEFAULT); + fterm_rawattr(ft.attr); + fterm_rawmove_opt(ft.y, ft.x); + fterm_rawflush(); +#endif // !_WIN32 +} + +void +fterm_rawscroll (int dy) +{ +#ifdef FTCONF_USE_ANSI_SCROLL + // SU: CSI n S (up) + // SD: CSI n T (down) + + char cmd = (dy > 0) ? 'S' : 'T'; + int ady = abs(dy); + if (ady == 0) + return; + if (ady >= ft.rows) ady = ft.rows; + fterm_rawcmd(ady, 1, cmd); + ft.scroll -= dy; + +#else + // VT100 flavor: + // * ESC D: scroll down + // * ESC M: scroll up + // + // Elder BBS systems works in a mixed way: + // \n at (rows-1) as scroll() + // and ESC-M at(0) as rscoll(). + // + // SCP: CSI s / RCP: CSI u + // Warning: cannot use SCP/RCP here, because on Win/DOS telnet + // the position will change after scrolling (ex, (25,0)->(24,0). + // + // Since scroll does not happen very often, let's relax and not + // optimize these commands here... + + int ady = abs(dy); + if (ady == 0) + return; + if (ady >= ft.rows) ady = ft.rows; + + // we are not going to preserve (rx,ry) + // so don't use fterm_move*. + if (dy > 0) + fterm_rawcmd2(ft.rows, 1, 1, 'H'); + else + fterm_rawcmd2(1, 1, 1, 'H'); + + for (; ady > 0; ady--) + { + if (dy >0) + { + // Win/DOS telnet may have extra text in new line, + // because of the IME line. +#ifdef FTCONF_USE_VT100_SCROLL + fterm_raws(ESC_STR "D" ESC_STR "[K"); // ESC_STR "[K"); +#else + fterm_raws("\n" ESC_STR "[K"); +#endif + } else { + fterm_raws(ESC_STR "M"); // ESC_STR "[K"); + } + } + + // Do not use optimized move here, because in poor terminals + // the coordinates are already out of sync. + fterm_rawcmd2(ft.ry+1, ft.rx+1, 1, 'H'); + ft.scroll -= dy; +#endif +} + +void +fterm_raws(const char *s) +{ + while (*s) + fterm_rawc(*s++); +} + +void +fterm_rawnc(int c, int n) +{ + while (n-- > 0) + fterm_rawc(c); +} + +////////////////////////////////////////////////////////////////////////// +// grayout advanced control +////////////////////////////////////////////////////////////////////////// +void +grayout(int y, int end, int level) +{ + char grattr = FTATTR_DEFAULT; + + y = ranged(y, 0, ft.rows-1); + end = ranged(end, 0, ft.rows-1); + + if (level == GRAYOUT_COLORBOLD) + { + int x = 0; + for (; y < end; y++) + { + for (x = 0; x < ft.cols-1; x++) + FTAMAP[y][x] |= FTATTR_BOLD; + } + return; + } + + if (level == GRAYOUT_COLORNORM) + { + int x = 0; + for (; y < end; y++) + { + for (x = 0; x < ft.cols-1; x++) + FTAMAP[y][x] &= ~(FTATTR_BLINK | FTATTR_BOLD); + } + return; + } + + if (level == GRAYOUT_BOLD) + { + grattr |= FTATTR_BOLD; + } + else if (level == GRAYOUT_DARK) + { + grattr = FTATTR_MAKE(0,0); + grattr |= FTATTR_BOLD; + } + else if (level == GRAYOUT_NORM) + { + // normal text + } + else + { + // not supported yet + } + + for (; y <= end; y++) + { + memset(FTAMAP[y], grattr, ft.cols); + } +} + +////////////////////////////////////////////////////////////////////////// +// deprecated api +////////////////////////////////////////////////////////////////////////// + +void +standout(void) +{ + outs(ANSI_COLOR(7)); +} + +void +standend(void) +{ + outs(ANSI_RESET); +} + +#ifndef _PFTERM_TEST_MAIN + +void +scr_dump(screen_backup_t *psb) +{ + int y = 0; + char *p = NULL; + + psb->row= ft.rows; + psb->col= ft.cols; + psb->y = ft.y; + psb->x = ft.x; + p = psb->raw_memory = + malloc (ft.rows * ft.cols * (sizeof(ftchar) + sizeof(ftattr))); + + for (y = 0; y < ft.rows; y++) + { + memcpy(p, FTCMAP[y], ft.cols * sizeof(ftchar)); + p += ft.cols * sizeof(ftchar); + memcpy(p, FTAMAP[y], ft.cols * sizeof(ftattr)); + p += ft.cols * sizeof(ftattr); + } +} + +void +scr_restore(const screen_backup_t *psb) +{ + int y = 0; + char *p = NULL; + int c = ft.cols, r = ft.rows; + if (!psb || !psb->raw_memory) + return; + + p = psb->raw_memory; + c = ranged(c, 0, psb->col); + r = ranged(r, 0, psb->row); + + ft.y = ranged(psb->y, 0, ft.rows-1); + ft.x = ranged(psb->x, 0, ft.cols-1); + clrscr(); + + for (y = 0; y < r; y++) + { + memcpy(FTCMAP[y], p, c * sizeof(ftchar)); + p += psb->col * sizeof(ftchar); + memcpy(FTAMAP[y], p, c * sizeof(ftattr)); + p += psb->col * sizeof(ftattr); + } + + free(psb->raw_memory); + ft.dirty = 1; + refresh(); +} + +void +move_ansi(int y, int x) +{ + move(y, x); +} + +void +getyx_ansi(int *y, int *x) +{ + getyx(y, x); +} + +void +region_scroll_up(int top, int bottom) +{ + int i; + ftchar *c0; + ftattr *a0; + + // logic same with old screen.c + if (top > bottom) { + i = top; + top = bottom; + bottom = i; + } + if (top < 0 || bottom >= ft.rows) + return; + + c0 = FTCMAP[top]; + a0 = FTAMAP[top]; + + for (i = top; i < bottom; i++) + { + FTCMAP[i] = FTCMAP[i+1]; + FTAMAP[i] = FTAMAP[i+1]; + } + FTCMAP[bottom] = c0; + FTAMAP[bottom] = a0; + + clrregion(bottom, bottom); + fterm_markdirty(); +} + +#endif + +////////////////////////////////////////////////////////////////////////// +// adapter +////////////////////////////////////////////////////////////////////////// + +int +fterm_inbuf(void) +{ +#ifdef _PFTERM_TEST_MAIN + return 0; +#else + return num_in_buf(); +#endif +} + +void +fterm_rawc(int c) +{ +#ifdef _PFTERM_TEST_MAIN + // if (c == ESC_CHR) putchar('*'); else + putchar(c); +#else + ochar(c); +#endif +} + +void +fterm_rawnewline(void) +{ +#ifdef _PFTERM_TEST_MAIN + putchar('\n'); +#else + ochar('\r'); + ochar('\n'); +#endif +} + +void +fterm_rawflush(void) +{ +#ifdef _PFTERM_TEST_MAIN + fflush(stdout); +#else + oflush(); +#endif +} + +////////////////////////////////////////////////////////////////////////// +// test +////////////////////////////////////////////////////////////////////////// + +#ifdef _PFTERM_TEST_MAIN +int main(int argc, char* argv[]) +{ + char buf[512]; + initscr(); + + if (argc < 2) + { +#if 0 + // DBCS test + char *a1 = ANSI_COLOR(1;33) "測試" ANSI_COLOR(34) "中文" + ANSI_COLOR(7) "測試" ANSI_RESET "測試" + "測試a" ANSI_RESET "\n"; + outstr(a1); + move(0, 2); + outstr("中文1"); + outstr(ANSI_COLOR(1;33)"中文2"); + outstr(" 中\x85"); + outstr("okok herer\x8a"); + + move(0, 8); + inansistr(buf, sizeof(buf)-1); + + move(3,5); outs(ANSI_RESET "(From inansistr:) "); outs(buf); + move(7, 3); + sprintf(buf, "strlen()=%d\n", fterm_strdlen(a1)); + outstr(buf); + refresh(); + getchar(); + + outs(ANSI_COLOR(1;33) "test " ANSI_COLOR(34) "x" + ANSI_RESET "te" ANSI_COLOR(43;0;1;35) " st" + ANSI_COLOR(0) "testx\n"); + refresh(); + getchar(); + + clear(); + outs("中文中文中文中文中文中文中文中文中文中文中文中文"); + move(0, 0); + outs(" this\xFF (ff)is te.(80 tail)->\x80 (80)"); + refresh(); + getchar(); +#endif + +#if 1 + // test resize + clear(); move(1, 0); outs("test resize\n"); + doupdate(); getchar(); + // expand X + resizeterm(25,200); + clear(); move(20, 0); clrtoeol(); outs("200x25"); + doupdate(); getchar(); + // resize back + resizeterm(25,80); + clear(); move(20, 0); clrtoeol(); outs("80x25"); + doupdate(); getchar(); + // expand Y + resizeterm(60,80); + clear(); move(20, 0); clrtoeol(); outs("80x60"); + doupdate(); getchar(); + // see if you crash here. + resizeterm(60,200); + clear(); move(20, 0); clrtoeol(); outs("200x60"); + doupdate(); getchar(); +#endif + +#if 0 + // test optimization + clear(); + move(1, 0); + outs("x++ optimization test\n"); + outs("1 2 3 4 5 6 7 8 9 0\n"); + outs("1122233334444455555566666667777777788888888899999999990\n"); + refresh(); + getchar(); + + move(2, 0); + outs("1122233334444455555566666667777777788888888899999999990\n"); + outs("1 2 3 4 5 6 7 8 9 0\n"); + refresh(); + getchar(); + + rscroll(); + refresh(); + getchar(); +#endif + } else { + FILE *fp = fopen(argv[1], "r"); + int c = 0; + + while (fp && (c=getc(fp)) > 0) + { + outc(c); + } + fclose(fp); + refresh(); + } + + endwin(); + printf("\ncomplete. enter to exit.\n"); + getchar(); + return 0; +} +#endif // _PFTERM_TEST_MAIN + +#endif // defined(EXP_PFTERM) || defined(USE_PFTERM) + +// vim:ts=4:sw=4:expandtab diff --git a/console/pmore.c b/console/pmore.c new file mode 100644 index 00000000..fde26f3a --- /dev/null +++ b/console/pmore.c @@ -0,0 +1,3732 @@ +/* $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 maple-family BBS systems, not + * specific to any branch. + * + * Author: Hung-Te Lin (piaip), June 2005. + * + * Copyright(c) 2005-2008 Hung-Te Lin + * All Rights Reserved. + * You are free to use, modify, redistribute this program in any + * non-commercial usage (including network service). + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * MAJOR IMPROVEMENTS: + * - Clean source code, and more readable for 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 AND DONE: + * - [2005, Initial Release] + * - Optimized speed up with Scroll supporting [done] + * - Wrap long lines [done] + * - Left-right wide navigation [done] + * - DBCS friendly wrap [done] + * - Reenrtance for main procedure [done] + * - Support PTT_PRINTS [done] + * - ASCII Art movie support [done] + * - ASCII Art movie navigation keys [pending] + * - A new optimized terminal base system (piterm) [dropped] + * - + * - [2007, Interactive Movie Enhancement] + * - New Invisible Frame Header Code [done] + * - Playback Control (pause, stop, skip) [done] + * - Interactive Movie (Hyper-text) [done] + * - Preference System (like board-conf) [done] + * - Traditional Movie Compatible Mode [done] + * - movie: Optimization on relative frame numbers [done] + * - movie: Optimization on named frames (by hash) + * - + * - Support Anti-anti-idle (ex, PCMan sends up-down) + * - Better help system [pending] + * - Virtual Contatenate [pending] + * - Drop ANSI between DBCS words if outputing UTF8 [drop, done by term] + */ + +// --------------------------------------------------------------- +/* 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_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 +#define PMORE_LOG_SYSOP_EDIT // log whenever sysop uses E +#define PMORE_OVERRIDE_TIME // override time format if possible + +// if you are working with a terminal without ANSI control, +// you are using a poor term (define PMORE_USING_POOR_TERM). +#ifndef USE_PFTERM // pfterm is a good terminal system. +#define PMORE_USING_POOR_TERM +#define PMORE_WORKAROUND_CLRTOEOL // try to work with poor terminal sys +#endif // USE_PFTERM +// -------------------------------------------------------------- + +// ----------------------------------------------------------- +// Messages for localization are listed here. +#define PMORE_MSG_PREF_TITLE \ + " pmore 2007 設定選項 " +#define PMORE_MSG_PREF_TITLE_QRAW \ + " pmore 2007 快速設定選項 - 色彩(ANSI碼)顯示模式 " +#define PMORE_MSG_WARN_FAKEUSERINFO \ + " ▲此頁內容會依閱\讀者不同,原文未必有您的資料 " +#define PMORE_MSG_WARN_MOVECMD \ + " ▲此頁內容含移位碼,可能會顯示偽造的系統訊息 " +#define PMORE_MSG_SEARCH_KEYWORD \ + "[搜尋]關鍵字:" + +#define PMORE_MSG_MOVIE_DETECTED \ + " ★ 這份文件是可播放的文字動畫,要開始播放嗎? [Y/n]" +#define PMORE_MSG_MOVIE_PLAYOLD_GETTIME \ + "這可能是傳統動畫檔, 若要直接播放請輸入速度(秒): " +#define PMORE_MSG_MOVIE_PLAYOLD_AS24L \ + "傳統動畫是以 24 行為單位設計的, 要模擬 24 行嗎? (否則會用現在的行數)[Yn] " +#define PMORE_MSG_MOVIE_PAUSE \ + " >>> 暫停播放動畫,請按任意鍵繼續或 q 中斷。 <<<" +#define PMORE_MSG_MOVIE_PLAYING \ + " >>> 動畫播放中... 可按 q, Ctrl-C 或其它任意鍵停止"; +#define PMORE_MSG_MOVIE_INTERACTION_PLAYING \ + " >>> 互動式動畫播放中... 可按 q 或 Ctrl-C 停止"; +#define PMORE_MSG_MOVIE_INTERACTION_STOPPED \ + "已強制中斷互動式系統" + +// ----------------------------------------------------------- + +#include "bbs.h" + +#include +#include +#include +#include +#include +#include +#include + +// Platform Related. NoSync is faster but if we don't have it... +// Experimental: POPULATE should work faster? +#ifdef MAP_NOSYNC +#define MF_MMAP_OPTION (MAP_NOSYNC|MAP_SHARED) +#elif defined(MAP_POPULATE) +#define MF_MMAP_OPTION (MAP_POPULATE|MAP_SHARED) +#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 + * + * DETAIL + * - 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) + + +// --------------------------------------------- + +// --------------------------- + +/* 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_CLRTOEND ESC_STR "[K" +#define ANSI_MOVETO(y,x) ESC_STR "[" #y ";" #x "H" + +#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_CLRTOEOL + int i; + move(y, x); + for (i = x; i < t_columns; i++) + outc(' '); + clrtoeol(); + move(y, x); // this is required, due to outc(). +#else + move(y, x); + clrtoeol(); +#endif +} + +// --------------------------- + +// ---------------------------
+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_OPT_CLEAR = 0, + MFDISP_OPT_OPTIMIZED, + MFDISP_OPT_FORCEDIRTY, + + // prefs + + MFDISP_WRAP_TRUNCATE = 0, + MFDISP_WRAP_WRAP, + MFDISP_WRAP_MODES, + + MFDISP_SEP_NONE = 0x00, + MFDISP_SEP_LINE = 0x01, + MFDISP_SEP_WRAP = 0x02, + MFDISP_SEP_OLD = MFDISP_SEP_LINE | MFDISP_SEP_WRAP, + MFDISP_SEP_MODES= 0x04, + + MFDISP_RAW_NA = 0x00, + MFDISP_RAW_NOANSI, + MFDISP_RAW_PLAIN, + MFDISP_RAW_MODES, + +}; + +#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 +// ---------------------------
+ +// --------------------------- +/* browsing preference */ +typedef struct +{ + /* mode flags */ + unsigned char + wrapmode, // wrap? + separator, // separator style + wrapindicator, // show wrap indicators + + oldwrapmode, // traditional wrap + oldstatusbar, // traditional statusbar + rawmode; // show file as-is. +} MF_BrowsingPreference; + +MF_BrowsingPreference 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 +#define FH_FLOATS (2) // right floating, name and val +static const char *_fh_disp_heads[FH_HEADERS] = + {"作者", "標題", "時間", "轉信"}; + +typedef struct +{ + int lines; // header lines + unsigned char *headers[FH_HEADERS]; + unsigned char *floats [FH_FLOATS]; +} 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; } + +// Artwork +#define OPTATTR_NORMAL ANSI_COLOR(0;34;47) +#define OPTATTR_NORMAL_KEY ANSI_COLOR(0;31;47) +#define OPTATTR_SELECTED ANSI_COLOR(0;1;37;46) +#define OPTATTR_SELECTED_KEY ANSI_COLOR(0;31;46) +#define OPTATTR_BAR ANSI_COLOR(0;1;30;47) + +// --------------------------- + +// ---------------------------------------------
+ +// --------------------------------------------- +#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 *options, + *optkeys; + unsigned char mode, + compat24, + interactive, + pause; +} MF_Movie; + +MF_Movie mfmovie; + +#define STOP_MOVIE() { \ + mfmovie.options = NULL; \ + mfmovie.pause = 0; \ + if (mfmovie.mode == MFDISP_MOVIE_PLAYING) \ + mfmovie.mode = MFDISP_MOVIE_YES; \ + if (mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD) \ + mfmovie.mode = MFDISP_MOVIE_NO; \ + mf_determinemaxdisps(MFNAV_PAGE, 0); \ + mf_forward(0); \ +} + +#define RESET_MOVIE() { \ + mfmovie.mode = MFDISP_MOVIE_UNKNOWN; \ + mfmovie.options = NULL; \ + mfmovie.optkeys = NULL; \ + mfmovie.compat24 = 1; \ + mfmovie.pause = 0; \ + mfmovie.interactive = 0; \ + mfmovie.synctime.tv_sec = mfmovie.synctime.tv_usec = 0; \ + mfmovie.frameclk.tv_sec = 1; mfmovie.frameclk.tv_usec = 0; \ +} + +#define MOVIE_IS_PLAYING() \ + ((mfmovie.mode == MFDISP_MOVIE_PLAYING) || \ + (mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD)) + +unsigned char * + mf_movieFrameHeader(unsigned char *p, unsigned char *end); + +int pmore_wait_key(struct timeval *ptv, int dorefresh); +int mf_movieNextFrame(); +int mf_movieSyncFrame(); +int mf_moviePromptPlaying(int type); +int mf_movieMaskedInput(int c); + +void mf_float2tv(float f, struct timeval *ptv); + +#define MOVIE_MIN_FRAMECLK (0.1f) +#define MOVIE_MAX_FRAMECLK (3600.0f) +#define MOVIE_SECOND_U (1000000L) +#define MOVIE_ANTI_ANTI_IDLE + +// some magic value that your igetch() will never return +#define MOVIE_KEY_ANY (0x4d464b41) + +#ifndef MOVIE_KEY_BS2 +#define MOVIE_KEY_BS2 (0x7f) +#endif + +#endif +// --------------------------------------------- + +// 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; + } + + // BSD mmap advise. comment if your OS does not support this. + madvise(mf.start, mf.len, MADV_SEQUENTIAL); + + 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 separators */ + if(mf.maxlinenoS > 0 && + fh.lines >= mf.maxlinenoS && + bpref.separator & MFDISP_SEP_WRAP) + { + mf_determinemaxdisps(+1, 1); + } + + return 1; +} + +void +mf_detach() +{ + mf_freeHeaders(); + if(mf.start) { + munmap(mf.start, mf.len); + RESETMF(); + } +} + +/* + * 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++) + free(fh.headers[i]); + for (i = 0; i < FH_FLOATS; i++) + free(fh.floats[i]); + RESETFH(); + } +} + +void +mf_parseHeaders() +{ + /* file format: + * AUTHOR: author BOARD: blah <- headers[0], floats[0], floats[1] + * XXX: xxx <- headers[1] + * XXX: xxx <- headers[n] + * [blank, fill with separator] <- 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); + +#ifdef PMORE_OVERRIDE_TIME + // (deprecated: too many formats for newsgroup) + // try to see if this is a valid time line + // use strptime to convert +#endif // PMORE_OVERRIDE_TIME + + // 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; + +#define RESET_OVERRIDE_MSG() { override_attr = override_msg = 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 needMove2bot = 0; + + 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 + +#if defined(PMORE_USE_ASCII_MOVIE) && !defined(PMORE_USING_POOR_TERM) + // For movies, maybe clear() is better. + // Let's enable for good terminals (which does not need workarounds) + if (MOVIE_IS_PLAYING()) + { + clear(); move(0, 0); + } else +#endif // PMORE_USE_ASCII_MOVIE && (!PMORE_USING_POOR_TERM) + + /* 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; + +#if defined(USE_PFTERM) + // In fact, pfterm will flash black screen when scrolling pages... + // So it may work better if we refresh whole screen. + if (i >= b_lines / 2) + { + clear(); move(0, 0); + scrll = MFDISP_PAGE; + } else +#endif // defined(USE_PFTERM) + + 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_DETECTED) + { + // detected only applies for first page. + // since this is not very often, let's prevent + // showing control codes. + if(mf_movieFrameHeader(mf.dispe, mf.end)) + MFDISP_SKIPCURLINE(); + } + else if(mfmovie.mode == MFDISP_MOVIE_UNKNOWN || + mfmovie.mode == MFDISP_MOVIE_PLAYING) + { + if(mf_movieFrameHeader(mf.dispe, mf.end)) + 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, separator, or normal text) + * is current line. + */ + else if (currline == fh.lines && bpref.rawmode == MFDISP_RAW_NA) + { + /* case 1, header separator line */ + if (bpref.separator & MFDISP_SEP_LINE) + { + outs(ANSI_COLOR(36)); + for(col = 0; col < headerw; col+=2) + { + // prints("%02d", col); + outs("─"); + } + outs(ANSI_RESET); + } + + /* Traditional 'more' adds separator 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.separator & 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.wrapindicator) + { + 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 // PMORE_RESTRICT_ANSI_MOVEMENT + // some user cannot live without this. + // make them happy. + newline_default = newline = MFDISP_NEWLINE_MOVE; +#ifdef PMORE_USE_ASCII_MOVIE + // relax for movies + if (!MOVIE_IS_PLAYING()) +#endif // PMORE_USE_ASCII_MOVIE + { + override_attr = ANSI_COLOR(1;37;41); + override_msg = PMORE_MSG_WARN_MOVECMD; + } +#endif // PMORE_RESTRICT_ANSI_MOVEMENT + needMove2bot = 1; + } + 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)); + memcpy(buf, mf.dispe, 3); // ^[*s + mf.dispe += 2; + + if(bpref.rawmode) + buf[0] = '*'; + else + { +#ifdef LOW_SECURITY +# define PTTPRINT_WARN_PATTERN "slpnbm" +#else +# define PTTPRINT_WARN_PATTERN "slpn" +#endif // LOW_SECURITY + if(strchr(PTTPRINT_WARN_PATTERN, buf[2]) != NULL) + { + override_attr = ANSI_COLOR(1;37;41); + override_msg = PMORE_MSG_WARN_FAKEUSERINFO; + } + 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.wrapindicator) + 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.wrapindicator && 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 separator + * however this code is rarely used now. + * only if no preload file. + */ + if (bpref.separator & 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 separator? + * 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; + + if (needMove2bot) + move(b_lines, 0); +} + +/* --------------------- MAIN PROCEDURE ------------------------- */ +void pmore_Preference(); +void pmore_QuickRawModePref(); +void pmore_Help(); + +static const char * const pmore_help[] = { + "\0閱\讀文章功\能鍵使用說明", + "\01游標移動功\能鍵", + "(k/↑) (j/↓/Enter) 上捲/下捲一行", + "(^B/PgUp/BackSpace) 上捲一頁", + "(^F/PgDn/Space/→) 下捲一頁", + "(,//TAB) 左/右捲動", + "(0/g/Home) ($/G/End) 檔案開頭/結尾", + "數字鍵 1-9 (;/:) 跳至輸入的頁數或行數", + "\01進階功\能鍵", + "(/) (s) 搜尋關鍵字/切換至其它看板", + "(n/N) 重複正/反向搜尋", + "(f/b) 跳至下/上篇", + "(a/A) 跳至同一作者下/上篇", + "(t/[-/]+) 主題式閱\讀:循序/前/後篇", + "\01其他功\能鍵", + + // the line below is already aligned, because of the backslash. + "(o)/(\\) 選項設定/色彩顯示模式", + +#if defined (PMORE_USE_ASCII_MOVIE) || defined(RET_DOCHESSREPLAY) + "(p)/(z) 播放動畫/棋局打譜", +#endif // defined(PMORE_USE_ASCII_MOVIE) || defined(RET_DOCHESSREPLAY) +#ifdef RET_COPY2TMP + "(Ctrl-T) 存入暫存檔", +#endif + "(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 2007, 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; + RESETFH(); + RESETMF(); + + 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 USE_BBSLUA + // TODO prompt BBS Lua status here. +#endif // USE_BBSLUA + +#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 = PMORE_MSG_MOVIE_DETECTED; + + outs(ANSI_RESET ANSI_COLOR(1;33;44)); + w -= strlen(s); outs(s); + + while(w-- > 0) outc(' '); outs(ANSI_RESET ANSI_CLRTOEND); + 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(); + // remove override messages + RESET_OVERRIDE_MSG(); + continue; + } + /* else, we have to clean up. */ + move(b_lines, 0); + clrtoeol(); + } + break; + + case MFDISP_MOVIE_PLAYING_OLD: + case MFDISP_MOVIE_PLAYING: + + mf_moviePromptPlaying(0); + + // doing refresh() here is better, + // to prevent that we forgot to refresh + // in SyncFrame. + refresh(); + + if(mf_movieSyncFrame()) + { + /* user did not hit anything. + * play next frame. + */ + if(mfmovie.mode == MFDISP_MOVIE_PLAYING) + { + if(!mf_movieNextFrame()) + { + STOP_MOVIE(); + + 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 { + /* TODO simple navigation here? */ + + /* stop playing */ + if(mfmovie.mode == MFDISP_MOVIE_PLAYING) + { + STOP_MOVIE(); + 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.separator & 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); + RESET_OVERRIDE_MSG(); + } + 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 = 17; + + 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) { + /* -------------- NEW EXITING KEYS ------------------ */ +#ifdef RET_DOREPLY + case 'r': case 'R': + flExit = 1, retval = RET_DOREPLY; + break; + case 'Y': case 'y': + flExit = 1, retval = RET_DOREPLYALL; + break; +#endif +#ifdef RET_DORECOMMEND + // recommend + case '%': + case 'X': + flExit = 1, retval = RET_DORECOMMEND; + break; +#endif +#ifdef RET_DOQUERYINFO + case 'Q': // info query interface + flExit = 1, retval = RET_DOQUERYINFO; + break; +#endif +#ifdef RET_DOSYSOPEDIT + case 'E': + flExit = 1, retval = RET_DOSYSOPEDIT; + break; +#endif +#ifdef RET_DOCHESSREPLAY + case 'z': + flExit = 1, retval = RET_DOCHESSREPLAY; + break; +#endif +#ifdef RET_COPY2TMP + case Ctrl('T'): + flExit = 1, retval = RET_COPY2TMP; + break; +#endif +#ifdef RET_SELECTBRD + case 's': + flExit = 1, retval = RET_SELECTBRD; + break; +#endif + /* ------------------ EXITING KEYS ------------------ */ + 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: + flExit = 1, retval = FULLUPDATE; + break; + 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 '/': + { + char sbuf[81] = ""; + char ans[4] = "n"; + + if(sr.search_str) { + free(sr.search_str); + sr.search_str = NULL; + } + + getdata(b_lines - 1, 0, PMORE_MSG_SEARCH_KEYWORD, 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 'h': case 'H': case KEY_F1: + case '?': + // help + show_help(pmore_help); + MFDISP_DIRTY(); + break; + +#ifdef PMORE_NOTIFY_NEWPREF + //let's be backward compatible! + case 'l': + case 'w': + case 'W': + case '|': + vmsg("這個按鍵已整合進新的設定 (o) 了"); + break; + +#endif // PMORE_NOTIFY_NEWPREF + + case '\\': // everyone loves backslash, let's keep it. + pmore_QuickRawModePref(); + MFDISP_DIRTY(); + break; + + case 'o': + pmore_Preference(); + MFDISP_DIRTY(); + break; + +#if defined(USE_BBSLUA) && defined(RET_DOBBSLUA) + case 'P': + vmsg("非常抱歉,BBS-Lua 的熱鍵已改為 L ,請改按 L"); + break; + + case 'L': + case 'l': + flExit = 1, retval = RET_DOBBSLUA; + break; +#endif + +#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, + PMORE_MSG_MOVIE_PLAYOLD_GETTIME, + buf, 8, LCECHO); + + if(buf[0]) + { + float nf = 0; + nf = atof(buf); // sscanf(buf, "%f", &nf); + RESET_MOVIE(); + + mfmovie.mode = MFDISP_MOVIE_PLAYING_OLD; + mf_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, + PMORE_MSG_MOVIE_PLAYOLD_AS24L, + 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 + + } + /* 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; +} + +// ---------------------------------------------------- Preference and Help + +static void +pmore_prefEntry( + int isel, + const char *key, int szKey, + const char *text,int szText, + const char* options) +{ + int i = 23; + // print key/text + outs(" " ANSI_COLOR(1;31)); //OPTATTR_NORMAL_KEY); + if (szKey < 0) szKey = strlen(key); + if (szKey > 0) outs_n(key, szKey); + outs(ANSI_RESET " "); + if (szText < 0) szText = strlen(text); + if (szText > 0) outs_n(text, szText); + + i -= szKey + szText; + if (i < 0) i+= 20; // one more chance + while (i-- > 0) outc(' '); + + // print options + i = 0; + while (*options) + { + if (*options == '\t') + { + // blank option, skip it. + i++, options++; + continue; + } + + if (i > 0) + outs(ANSI_COLOR(1;30) " |" ANSI_RESET); //OPTATTR_BAR " | " ANSI_RESET); + + // test if option has hotkey + if (*options && *options != '\t' && + *(options+1) && *(options+1) == '.') + { + // found hotkey + outs(ANSI_COLOR(1;31)); //OPTATTR_NORMAL_KEY); + outc(*options); + outs(ANSI_RESET); + options +=2; + } + + if (i == isel) + { + outs(ANSI_COLOR(1;36) "*");// OPTATTR_SELECTED); + } + else + outc(' '); + + while (*options && *options != '\t') + outc(*options++); + + outs(ANSI_RESET); + + if (*options) + i++, options ++; + } + outc('\n'); +} + +void +pmore_PromptBar(const char *caption, int shadow) +{ + int i = 0; + + if (shadow) + { + outs(ANSI_COLOR(0;1;30)); + for(i = 0; i+2 < t_columns ; i+=2) + outs("▁"); + outs(ANSI_RESET "\n"); + } + else + i = t_columns -2; + + outs(ANSI_COLOR(7)); + outs(caption); + for(i -= strlen(caption); i > 0; i--) + outs(" "); + outs(ANSI_RESET "\n"); +} + +void +pmore_QuickRawModePref() +{ + int ystart = b_lines -2; + +#ifdef HAVE_GRAYOUT + grayout(0, ystart-1, GRAYOUT_DARK); +#endif // HAVE_GRAYOUT + + while(1) + { + move(ystart, 0); + clrtobot(); + pmore_PromptBar(PMORE_MSG_PREF_TITLE_QRAW, 0); + + // list options + pmore_prefEntry(bpref.rawmode, + "\\", 1, "色彩顯示方式:", -1, + "1.預設格式化內容\t2.原始ANSI控制碼\t3.純文字"); + + switch(vmsg("請調整設定 (1-3 可直接選定,\\可切換) 或其它任意鍵結束。")) + { + case '\\': + bpref.rawmode = (bpref.rawmode+1) % MFDISP_RAW_MODES; + break; + case '1': + bpref.rawmode = MFDISP_RAW_NA; + return; + case '2': + bpref.rawmode = MFDISP_RAW_NOANSI; + return; + case '3': + bpref.rawmode = MFDISP_RAW_PLAIN; + return; + case KEY_LEFT: + if (bpref.rawmode > 0) bpref.rawmode --; + break; + case KEY_RIGHT: + if (bpref.rawmode < MFDISP_RAW_MODES-1) bpref.rawmode ++; + break; + default: + return; + } + } +} + +void +pmore_Preference() +{ + int ystart = b_lines - 9; + // TODO even better pref navigation, like arrow keys + // static int lastkey = '\\'; // default key + +#ifdef HAVE_GRAYOUT + grayout(0, ystart-1, GRAYOUT_DARK); +#endif // HAVE_GRAYOUT + + while (1) + { + move(ystart, 0); + clrtobot(); + pmore_PromptBar(PMORE_MSG_PREF_TITLE, 1); + outs("\n"); + + // list options + pmore_prefEntry(bpref.rawmode, + "\\", 1, "色彩顯示方式:", -1, + "預設格式化內容\t原始ANSI控制碼\t純文字"); + + pmore_prefEntry(bpref.wrapmode, + "w", 1, "斷行方式:", -1, + "直接截行\t自動斷行"); + + pmore_prefEntry(bpref.wrapindicator, + "m", 1, "斷行符號:", -1, + "不顯示\t顯示"); + + pmore_prefEntry(bpref.separator, + "l", 1, "文章標頭分隔線:", -1, + "無\t單行\t\t傳統分隔線加空行"); + + pmore_prefEntry(bpref.oldstatusbar, + "t", 1, "傳統狀態列與斷行方式: ", -1, + "停用\t啟用"); + + switch(vmsg("請調整設定或其它任意鍵結束。")) + { + case '\\': + case '|': + bpref.rawmode = (bpref.rawmode+1) % MFDISP_RAW_MODES; + break; + case 'w': + bpref.wrapmode = (bpref.wrapmode+1) % MFDISP_WRAP_MODES; + break; + case 'm': + bpref.wrapindicator = !bpref.wrapindicator; + break; + case 'l': + // there's no MFDISP_SEP_WRAP only mode. + if (++bpref.separator == MFDISP_SEP_WRAP) + bpref.separator ++; + bpref.separator %= MFDISP_SEP_MODES; + break; + case 't': + bpref.oldwrapmode = !bpref.oldwrapmode; + bpref.oldstatusbar = !bpref.oldstatusbar; + break; + + default: + // finished settings + return; + } + } +} + +void +pmore_Help() +{ + clear(); + stand_title("pmore 使用說明"); + vmsg(""); +} + +// ---------------------------------------------------- Extra modules + +#ifdef PMORE_USE_ASCII_MOVIE +void +mf_float2tv(float f, struct timeval *ptv) +{ + if(f < MOVIE_MIN_FRAMECLK) + f = MOVIE_MIN_FRAMECLK; + if (f > MOVIE_MAX_FRAMECLK) + f = MOVIE_MAX_FRAMECLK; + + ptv->tv_sec = (long) f; + ptv->tv_usec = (f - (long)f) * MOVIE_SECOND_U; +} + +int +mf_str2float(unsigned char *p, unsigned char *end, float *pf) +{ + char buf[16] = {0}; + int cbuf = 0; + + /* process time */ + while ( p < end && + cbuf < sizeof(buf)-1 && + (isdigit(*p) || *p == '.' || *p == '+' || *p == '-')) + buf[cbuf++] = *p++; + + if (!cbuf) + return 0; + + buf[cbuf] = 0; + *pf = atof(buf); + + return 1; +} + +/* + * 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_key(struct timeval *ptv, int dorefresh) +{ + int sel = 0; + fd_set readfds; + int c = 0; + + if (dorefresh) + refresh(); + + do { + // if already something in queue, + // detemine if ok to break. + while ( num_in_buf() > 0) + { + if (!mf_movieMaskedInput((c = igetch()))) + return c; + } + + // wait for real user interaction + FD_ZERO(&readfds); + FD_SET(0, &readfds); + +#ifdef STATINC + STATINC(STAT_SYSSELECT); +#endif + sel = select(1, &readfds, NULL, NULL, ptv); + + // if select() stopped by other interrupt, + // do it again. + if (sel < 0 && errno == EINTR) + continue; + + // if (sel > 0), try to read. + // note: there may be more in queue. + // will be processed at next loop. + if (sel > 0 && !mf_movieMaskedInput((c = igetch()))) + return c; + + } while (sel > 0); + + // now, maybe something for read (sel > 0) + // or time out (sel == 0) + // or weird error (sel < 0) + + // sync clock(now) if timeout. + if (sel == 0) + syncnow(); + + return (sel == 0) ? 0 : 1; +} + +// type : 1 = option selection, 0 = normal +int +mf_moviePromptPlaying(int type) +{ + int w = t_columns - 1; + // s may change to anykey... + const char *s = PMORE_MSG_MOVIE_PLAYING; + + if (override_msg) + { + // we must warn user about something... + move(type ? b_lines-2 : b_lines-1, 0); // clrtoeol? + outs(ANSI_RESET); + if (override_attr) outs(override_attr); + w -= strlen(override_msg); + outs(override_msg); + while(w-- > 0) outc(' '); + + outs(ANSI_RESET ANSI_CLRTOEND); + RESET_OVERRIDE_MSG(); + w = t_columns -1; + } + + move(type ? b_lines-1 : b_lines, 0); // clrtoeol? + + if (type) + { + outs(ANSI_RESET ANSI_COLOR(1;34;47)); + s = " >> 請輸入選項: (互動式動畫播放中,可按 q 或 Ctrl-C 中斷)"; + } + else if (mfmovie.interactive) + { + outs(ANSI_RESET ANSI_COLOR(1;34;47)); + s = PMORE_MSG_MOVIE_INTERACTION_PLAYING; + } else { + outs(ANSI_RESET ANSI_COLOR(1;30;47)); + } + + w -= strlen(s); outs(s); + + while(w-- > 0) outc(' '); outs(ANSI_RESET ANSI_CLRTOEND); + if (type) + { + move(b_lines, 0); + clrtoeol(); + } + + return 1; +} + +// return = printed characters +int +mf_moviePromptOptions( + int isel, int maxsel, + int key, + unsigned char *text, unsigned int szText) +{ + unsigned char *s = text; + int printlen = 0; + // determine if we need separator + if (maxsel) + { + outs(OPTATTR_BAR "|" ); + printlen += 1; + } + + // highlight if is selected + if (isel == maxsel) + outs(OPTATTR_SELECTED_KEY); + else + outs(OPTATTR_NORMAL_KEY); + + outc(' '); printlen ++; + + if (key > ' ' && key < 0x80) // isprint(key)) + { + outc(key); + printlen += 1; + } else { + // named keys + printlen += 2; + + if (key == KEY_UP) outs("↑"); + else if (key == KEY_LEFT) outs("←"); + else if (key == KEY_DOWN) outs("↓"); + else if (key == KEY_RIGHT) outs("→"); + else if (key == KEY_PGUP) { outs("PgUp"); printlen += 2; } + else if (key == KEY_PGDN) { outs("PgDn"); printlen += 2; } + else if (key == KEY_HOME) { outs("Home"); printlen += 2; } + else if (key == KEY_END) { outs("End"); printlen ++; } + else if (key == KEY_INS) { outs("Ins"); printlen ++; } + else if (key == KEY_DEL) { outs("Del"); printlen ++; } + else if (key == '\b') { outs("←BS"); printlen += 2; } + // else if (key == MOVIE_KEY_ANY) // same as default + else printlen -= 2; + } + + // check text: we don't allow special char. + if (text && szText && text[0]) + { + while (s < text + szText && *s > ' ') + { + s++; + } + szText = s - text; + } + else + { + // default option text + text = (unsigned char*)"☆"; + szText = ustrlen(text); + } + + if (szText) + { + if (isel == maxsel) + outs(OPTATTR_SELECTED); + else + outs(OPTATTR_NORMAL); + outs_n((char*)text, szText); + printlen += szText; + } + + outc(' '); printlen ++; + + // un-highlight + if (isel == maxsel) + outs(OPTATTR_NORMAL); + + return printlen; +} + +int +mf_movieNamedKey(int c) +{ + switch (c) + { + case 'u': return KEY_UP; + case 'd': return KEY_DOWN; + case 'l': return KEY_LEFT; + case 'r': return KEY_RIGHT; + + case 'b': return '\b'; + + case 'H': return KEY_HOME; + case 'E': return KEY_END; + case 'I': return KEY_INS; + case 'D': return KEY_DEL; + case 'P': return KEY_PGUP; + case 'N': return KEY_PGDN; + + case 'a': return MOVIE_KEY_ANY; + default: + break; + } + return 0; +} + +int mf_movieIsSystemBreak(int c) +{ + return (c == 'q' || c == 'Q' || c == Ctrl('C')) + ? 1 : 0; +} + +int +mf_movieMaskedInput(int c) +{ + unsigned char *p = mfmovie.optkeys; + + if (!p) + return 0; + + // some keys cannot be masked + if (mf_movieIsSystemBreak(c)) + return 0; + + // treat BS and DEL as same one + if (c == MOVIE_KEY_BS2) + c = '\b'; + + // general look up + while (p < mf.end && *p && *p != '\n' && *p != '#') + { + if (*p == '@' && mf.end - p > 1 + && isalnum(*(p+1))) // named key + { + p++; + + // special: 'a' masks all + if (*p == 'a' || mf_movieNamedKey(*p) == c) + return 1; + } else { + if ((int)*p == c) + return 1; + } + p++; + } + return 0; +} + +unsigned char * +mf_movieFrameHeader(unsigned char *p, unsigned char *end) +{ + // ANSI has ESC_STR [8m as "Conceal" but + // not widely supported, even PieTTY. + // So let's go back to fixed format... + static char *patHeader = "==" ESC_STR "[30;40m^L"; + static char *patHeader2= ESC_STR "[30;40m^L"; // patHeader + 2; // "==" + // static char *patHeader3= ESC_STR "[m^L"; + static size_t szPatHeader = 12; // strlen(patHeader); + static size_t szPatHeader2 = 10; // strlen(patHeader2); + // static size_t szPatHeader3 = 5; // strlen(patHeader3); + + size_t sz = end - p; + + if (sz < 1) return NULL; + if(*p == 12) // ^L + return p+1; + + if (sz < 2) return NULL; + if( *p == '^' && + *(p+1) == 'L') + return p+2; + + // Add more frame headers + + /* // *[m seems not so common, skip. + if (sz < szPatHeader3) return NULL; + if (memcmp(p, patHeader3, szPatHeader3) == 0) + return p + szPatHeader3; + */ + + if (sz < szPatHeader2) return NULL; + if (memcmp(p, patHeader2, szPatHeader2) == 0) + return p + szPatHeader2; + + if (sz < szPatHeader) return NULL; + if (memcmp(p, patHeader, szPatHeader) == 0) + return p + szPatHeader; + + return NULL; +} + +int +mf_movieGotoNamedFrame(const unsigned char *name, const unsigned char *end) +{ + const unsigned char *p = name; + size_t sz = 0; + + // resolve name first + while (p < end && isalnum(*p)) + p++; + sz = p - name; + + if (sz < 1) return 0; + + // now search entire file for frame + + mf_goTop(); + + do + { + if ((p = mf_movieFrameHeader(mf.disps, mf.end)) == NULL || + *p != ':') + continue; + + // got some frame. let's check the name + p++; + if (mf.end - p < sz) + continue; + + // check: target of p must end. + if (mf.end -p > sz && + isalnum(*(p+sz))) + continue; + + if (memcmp(p, name, sz) == 0) + return 1; + + } while (mf_forward(1) > 0); + return 0; +} + +int +mf_movieGotoFrame(int fno, int relative) +{ + if (!relative) + mf_goTop(); + else if (fno > 0) + mf_forward(1); + // for absolute, fno = 1..N + + if (fno > 0) + { + // move forward + do { + while (mf_movieFrameHeader(mf.disps, mf.end) == NULL) + { + if (mf_forward(1) < 1) + return 0; + } + // found frame. + if (--fno > 0) + mf_forward(1); + } while (fno > 0); + } else { + // backward + // XXX check if we reached head? + while (fno < 0) + { + do { + if (mf_backward(1) < 1) + return 0; + } while (mf_movieFrameHeader(mf.disps, mf.end) == NULL); + fno ++; + } + } + return 1; +} + +int +mf_parseOffsetCmd( + unsigned char *s, unsigned char *end, + int base) +{ + // return is always > 0, or base. + int v = 0; + + if (s >= end) + return base; + + v = atoi((char*)s); + + if (*s == '+' || *s == '-') + { + // relative format + v = base + v; + } else if (isdigit(*s)) { + // absolute format + } else { + // error format? + v = 0; + } + + if (v <= 0) + v = base; + return v; +} + +int +mf_movieExecuteOffsetCmd(unsigned char *s, unsigned char *end) +{ + // syntax: type[+-]offset + // + // type: l(line), f(frame), p(page). + // +-: if empty, absolute. if assigned, relative. + // offset: is 1 .. N for all cases + + int curr = 0, newno = 0; + + switch(*s) + { + case 'p': + // by page + curr = (mf.lineno / MFDISP_PAGE) + 1; + newno = mf_parseOffsetCmd(s+1, end, curr); +#ifdef DEBUG + vmsgf("page: %d -> %d\n", curr, newno); +#endif // DEBUG + // prevent endless loop + if (newno == curr) + return 0; + + return mf_goto((newno -1) * MFDISP_PAGE); + + case 'l': + // by lines + curr = mf.lineno + 1; + newno = mf_parseOffsetCmd(s+1, end, curr); +#ifdef DEBUG + vmsgf("line: %d -> %d\n", curr, newno); +#endif // DEBUG + // prevent endless loop + if (newno == curr) + return 0; + + return mf_goto(newno-1); + + case 'f': + // by frame [optimized] + if (++s >= end) + return 0; + + curr = 0; + newno = atoi((char*)s); + if (*s == '+' || *s == '-') // relative + { + curr = 1; + if (newno == 0) + return 0; + } else { + // newno starts from 1 + if (newno <= 0) + return 0; + } + return mf_movieGotoFrame(newno, curr); + + case ':': + // by names + return mf_movieGotoNamedFrame(s+1, end); + + default: + // not supported yet + break; + } + return 0; +} + + +int +mf_movieOptionHandler(unsigned char *opt, unsigned char *end) +{ + // format: #time#key1,cmd,text1#key2,cmd,text2# + // if key is empty, use auto-increased key. + // if cmd is empty, invalid. + // if text is empty, display key only or hide if time is assigned. + + int ient = 0; + unsigned char *pkey = NULL, *cmd = NULL, *text = NULL; + unsigned int szCmd = 0, szText = 0; + unsigned char *p = opt; + int key = 0; + float optclk = -1.0f; // < 0 means infinite wait + struct timeval tv; + + int isel = 0, c = 0, maxsel = 0, selected = 0; + int newOpt = 1; + int hideOpts = 0; + int promptlen = 0; + + // TODO handle line length + // TODO restrict option size + + // set up timer (opt points to optional time now) + do { + p = opt; + while ( p < end && + (isdigit(*p) || *p == '+' || *p == '-' || *p == '.') ) + p++; + + // if no number, abort. + if (p == opt || (p < end && *p != '#')) break; + + // p looks like valid timer now + if (mf_str2float(opt, p, &optclk)) + { + // conversion failed. + if (optclk == 0) + optclk = -1.0f; + } + + // point opt to new var after #. + opt = p + 1; + break; + + } while (1); + + // UI Selection + do { + // do c test here because we need parser to help us + // finding the selection + if (c == '\r' || c == '\n' || c == ' ') + { + selected = 1; + } + + newOpt = 1; + promptlen = 0; + + // parse (key,frame,text) + for ( p = opt, ient = 0, maxsel = 0, + key = '0'; // default command + p < end && *p != '\n'; p++) + { + if (newOpt) + { + // prepare for next loop + pkey = p; + cmd = text = NULL; + szCmd = szText = 0; + ient = 0; + newOpt = 0; + } + + // calculation of fields + if (*p == ',' || *p == '#') + { + switch (++ient) + { + // case 0 is already processed. + case 1: + cmd = p+1; + break; + + case 2: + text = p+1; + szCmd = p - cmd; + break; + + case 3: + szText = p - text; + + default: + // unknown parameters + break; + } + } + + // ready to parse one option + if (*p == '#') + { + newOpt = 1; + + // first, fix pointers + if (szCmd == 0 || *cmd == ',' || *cmd == '#') + { cmd = NULL; szCmd = 0; } + + // quick abort if option is invalid. + if (!cmd) + continue; + + if (szText == 0 || *text == ',' || *text == '#') + { text = NULL; szText = 0; } + + // assign key + if (*pkey == ',' || *pkey == '#') + key++; + else + { + // named key? + int nk = 0; + + // handle special case @a (all) here + + if (*pkey == '@' && + ++ pkey < end && + (nk = mf_movieNamedKey(*pkey))) + { + key = nk; + } else { + key = *pkey; + } + // warning: pkey may be changed after this. + } + + // calculation complete. + + // print option + if (!hideOpts && maxsel == 0 && text == NULL) + { + hideOpts = 1; + mf_moviePromptPlaying(0); + // prevent more hideOpt test + } + + if (!hideOpts) + { + // print header + if (maxsel == 0) + { + pmore_clrtoeol(b_lines-1, 0); + mf_moviePromptPlaying(1); + } + + promptlen += mf_moviePromptOptions( + isel, maxsel, key, + text, szText); + } + + // handle selection + if (c == key || + (key == MOVIE_KEY_ANY && c != 0)) + { + // hotkey pressed + selected = 1; + isel = maxsel; + } + + maxsel ++; + + // parse complete. + // test if this item is selected. + if (selected && isel == maxsel - 1) + break; + } + } + + if (selected || maxsel == 0) + break; + + // finish the selection bar + if (!hideOpts && maxsel > 0) + { + int iw = 0; + for (iw = 0; iw + promptlen < t_columns-1; iw++) + outc(' '); + outs(ANSI_RESET ANSI_CLRTOEND); + } + + // wait for input + if (optclk > 0) + { + // timed interaction + + // disable optkeys to allow masked input + unsigned char *tmpopt = mfmovie.optkeys; + mfmovie.optkeys = NULL; + + mf_float2tv(optclk, &tv); + c = pmore_wait_key(&tv, 1); + mfmovie.optkeys = tmpopt; + + // if timeout, drop. + if (!c) + return 0; + } else { + // infinite wait + c = igetch(); + } + + // parse keyboard input + if (mf_movieIsSystemBreak(c)) + { + // cannot be masked, + // also force stop of playback + STOP_MOVIE(); + vmsg(PMORE_MSG_MOVIE_INTERACTION_STOPPED); + return 0; + } + + // treat BS and DEL as same one + if (c == MOVIE_KEY_BS2) + c = '\b'; + + // standard navigation keys. + if (mf_movieMaskedInput(c)) + continue; + + // these keys can be masked + if (c == KEY_LEFT || c == KEY_UP) + { + if (isel > 0) isel --; + } + else if (c == KEY_RIGHT || c == KEY_TAB || c == KEY_DOWN) + { + if (isel < maxsel-1) isel ++; + } + else if (c == KEY_HOME) + { + isel = 0; + } + else if (c == KEY_END) + { + isel = maxsel -1; + } + + } while ( !selected ); + + // selection is made now. + outs(ANSI_RESET); // required because options bar may be not closed + pmore_clrtoeol(b_lines, 0); + +#ifdef DEBUG + prints("selection: %d\n", isel); + igetch(); +#endif + + // Execute Selection + if (!cmd || !szCmd) + return 0; + + return mf_movieExecuteOffsetCmd(cmd, cmd+szCmd); +} + +/* + * mf_movieSyncFrame: + * wait until synchronization, and flush break key (if any). + * return meaning: + * I've got synchronized. + * If no (user breaks), return 0 + */ +int mf_movieSyncFrame() +{ + if (mfmovie.pause) + { + int c = 0; + mfmovie.pause = 0; + c = vmsg(PMORE_MSG_MOVIE_PAUSE); + if (mf_movieIsSystemBreak(c)) + return 0; + return 1; + } + else if (mfmovie.options) + { + unsigned char *opt = mfmovie.options; + mfmovie.options = NULL; + mf_movieOptionHandler(opt, mf.end); + return 1; + } + else 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_key(&dv, 0); + } 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_key(&dv, 0); + } +} + +#define MOVIECMD_SKIP_ALL(p,end) \ + while (p < end && *p && *p != '\n') \ + { p++; } \ + +unsigned char * +mf_movieProcessCommand(unsigned char *p, unsigned char *end) +{ + for (; p < end && *p != '\n'; p++) + { + if (*p == 'S') { + // SYNCHRONIZATION + gettimeofday(&mfmovie.synctime, NULL); + // S can take other commands + } + else if (*p == 'E') + { + // END + STOP_MOVIE(); + // MFDISP_SKIPCURLINE(); + MOVIECMD_SKIP_ALL(p,end); + return p; + } + else if (*p == 'P') + { + // PAUSE + mfmovie.pause = 1; + // MFDISP_SKIPCURLINE(); + MOVIECMD_SKIP_ALL(p,end); + return p; + + } + else if (*p == 'G') + { + // GOTO + // Gt+-n,t+-n,t+-n (random select one) + // jump +-n of type(l,p,f) + + // random select, if multiple + unsigned char *pe = p; + unsigned int igs = 0; + + for (pe = p ; pe < end && *pe && + *pe > ' ' && *pe < 0x80 + ; pe ++) + if (*pe == ',') igs++; + + if (igs) + { + // make random + igs = random() % (igs+1); + + for (pe = p ; igs > 0 && pe < end && *pe && + *pe > ' ' && *pe < 0x80 + ; pe ++) + if (*pe == ',') igs--; + + if (pe != p) + p = pe-1; + } + + mf_movieExecuteOffsetCmd(p+1, end); + MOVIECMD_SKIP_ALL(p,end); + return p; + } + else if (*p == ':') + { + // NAMED + // :name: + // name allows alnum only + p++; + // TODO check isalnum p? + + // :name can accept trailing commands + while (p < end && *p != '\n' && *p != ':') + p++; + + if (*p == ':') p++; + + // continue will increase p + p--; + continue; + } + else if (*p == 'K') + { + // Reserve Key for interactive usage. + // Currently only K#...# format is supported. + if (p+2 < end && *(p+1) == '#') + { + p += 2; + mfmovie.optkeys = p; + mfmovie.interactive = 1; + + // K#..# can accept trailing commands + while (p < end && *p != '\n' && *p != '#') + p++; + + // if empty, set optkeys to NULL? + if (mfmovie.optkeys == p) + mfmovie.optkeys = NULL; + + if (*p == '#') p++; + + // continue will increase p + p--; + continue; + } + MOVIECMD_SKIP_ALL(p,end); + return p; + } + else if (*p == '#') + { + // OPTIONS + // #key1,frame1,text1#key2,frame2,text2# + mfmovie.options = p+1; + mfmovie.interactive = 1; + // MFDISP_SKIPCURLINE(); + MOVIECMD_SKIP_ALL(p,end); + return p; + } + else if (*p == 'O') + { + // OLD compatible mode + // = -> compat24 + // - -> ? + // == -> ? + p++; + if (p >= end) + return end; + if (*p == '=') + { + mfmovie.mode = MFDISP_MOVIE_PLAYING_OLD; + mfmovie.compat24 = 1; + p++; + } + // MFDISP_SKIPCURLINE(); + return p+1; + } + else if (*p == 'L') + { + // LOOP + // Lm,n + // m times to backward n + break; + } + else + { + // end of known control codes + break; + } + } + return p; +} + +int +mf_movieNextFrame() +{ + while (1) + { + unsigned char *p = mf_movieFrameHeader(mf.disps, mf.end); + + if(p) + { + float nf = 0; + unsigned char *odisps = mf.disps; + + /* process leading */ + p = mf_movieProcessCommand(p, mf.end); + + // disps may change after commands + if (mf.disps != odisps) + { + // commands changing location must + // support at least one frame pause + // to allow user break + struct timeval tv; + mf_float2tv(MOVIE_MIN_FRAMECLK, &tv); + + if (pmore_wait_key(&tv, 0)) + { + STOP_MOVIE(); + return 0; + } + continue; + } + + /* process time */ + if (mf_str2float(p, mf.end, &nf)) + { + mf_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; + } + + if (mfmovie.mode != MFDISP_MOVIE_PLAYING_OLD) + mf_forward(1); + + return 1; + } + + if (mf_forward(1) <= 0) + break; + } + + return 0; +} +#endif + +/* vim:sw=4:ts=8:expandtab:nofoldenable + */ diff --git a/console/random.c b/console/random.c new file mode 100644 index 00000000..dd369c41 --- /dev/null +++ b/console/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 +#include +#include +#include +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 +#include +#include + + +/* 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/console/read.c b/console/read.c new file mode 100644 index 00000000..97621725 --- /dev/null +++ b/console/read.c @@ -0,0 +1,1371 @@ +/* $Id$ */ +#include "bbs.h" + +static int headers_size = 0; +static fileheader_t *headers = NULL; +static int last_line; // PTT: last_line 游標可指的最後一個 + +#include + +/* ----------------------------------------------------- */ +/* Tag List 標籤 */ +/* ----------------------------------------------------- */ +static TagItem *TagList = NULL; /* ascending list */ + +/** + * @param locus + * @return void + */ +void +UnTagger(int locus) +{ + if (locus > TagNum || TagNum <= 0) + 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+1)); + } + + 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 || TagNum <= 0) + 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 + +#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, GLOBAL_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 key=StringHash(s); + int i; + + if (def_cursline >= 0) { + struct keepsome *kl=keeplist; + while(kl) { + for(i=0; iused; 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 + +// return: 1 - found, 0 - fail. +inline static int +dbcs_strcasestr(const char* pool, const char *ptr) +{ +#if 0 + // old method + 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; + +#endif + + int i = 0, i2 = 0, found = 0, + szpool = strlen(pool), + szptr = strlen(ptr); + + for (i = 0; i <= szpool-szptr; i++) + { + found = 1; + + // compare szpool[i..szptr] with ptr + for (i2 = 0; i2 < szptr; i2++) + { + if (pool[i + i2] > 0) + { + // ascii + if (ptr[i2] < 0 || + tolower(ptr[i2]) != tolower(pool[i+i2])) + { + // printf("break on ascii (i=%d, i2=%d).\n", i, i2); + found = 0; + break; + } + } else { + // non-ascii + if (ptr[i2] != pool[i+i2] || + ptr[i2+1] != pool[i+i2+1]) + { + // printf("break on non-ascii (i=%d, i2=%d).\n", i, i2); + found = 0; + break; + } + i2 ++; + } + } + + if (found) break; + + // next iteration: if target is DBCS, skip one more byte. + if (pool[i] < 0) + i++; + } + return found; +} + +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) ? + "增加條件 推文數: ": + "搜尋推文數高於多少" +#ifndef OLDRECOMMEND + " (<0則搜噓文數) " +#endif // OLDRECOMMEND + "的文章: ", + 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), DBCS_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 (currstat != RMAIL && currboard[0] && currbid > 0) + { + time4_t filecreate = dashc(newdirect); + boardheader_t *bp = getbcache(currbid); + assert(bp); + + if (bp->SRexpire) + { + if (bp->SRexpire > now) // invalid expire time. + bp->SRexpire = now; + + if (bp->SRexpire > filecreate) + filetime = -1; + } + } + + 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; + inc = 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) && + (n_recommend > 0 ? + (fhs[i].recommend < n_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 newdirect_new_ln = -1; + +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 '#': + { + char aidc[100]; + aidu_t aidu = 0; + char dirfile[PATHLEN]; + char *sp; + int n = -1; + + if(!getdata(b_lines, 0, "搜尋" AID_DISPLAYNAME ": #", aidc, 20, LCECHO)) + { + move(b_lines, 0); + clrtoeol(); + mode = FULLUPDATE; + break; + } + + if((currmode & MODE_SELECT) || + (currstat == RMAIL)) + { + move(21, 0); + clrtobot(); + move(22, 0); + prints("此狀態下無法使用搜尋" AID_DISPLAYNAME "功\能"); + pressanykey(); + mode = FULLUPDATE; + break; + } + + /* strip leading spaces and '#' */ + sp = aidc; + while(*sp == ' ') + sp ++; + if(*sp == '#') + sp ++; + + if((aidu = aidc2aidu(sp)) > 0) + { + /* search bottom */ + /* FIXME: 置底文但沒列在 .DIR.bottom 的在這段會搜不到, + 在下一段 search board 時才會搜到本體。難解。 */ + { + char buf[FNLEN]; + + snprintf(buf, FNLEN, "%s.bottom", FN_DIR); + setbfile(dirfile, currboard, buf); + if((n = search_aidu(dirfile, aidu)) >= 0) + { + n += getbtotal(currbid); + /* 不可用 bottom_line,因為如果是在 digest mode, + bottom_line 會是文摘的數目,而不是真正的文章數 */ + if(currmode & MODE_DIGEST) + { + newdirect_new_ln = n; + + new_ln = locmem->crs_ln; + /* dirty hack for crs_ln = 1, then HOME pressed */ + + default_ch = KEY_TAB; + mode = DONOTHING; + break; + } + } + } + if(n < 0) + /* search board */ + { + setbfile(dirfile, currboard, FN_DIR); + n = search_aidu(dirfile, aidu); + if(n >= 0 && (currmode & MODE_DIGEST)) + /* switch to normal read mode */ + { + newdirect_new_ln = n; + + new_ln = locmem->crs_ln; + /* dirty hack for crs_ln = 1, then HOME pressed */ + + default_ch = KEY_TAB; + mode = DONOTHING; + break; + } + } + if(n < 0) + /* search digest */ + { + setbfile(dirfile, currboard, fn_mandex); + n = search_aidu(dirfile, aidu); + if(n >= 0 && !(currmode & MODE_DIGEST)) + /* switch to digest mode */ + { + newdirect_new_ln = n; + + new_ln = locmem->crs_ln; + /* dirty hack for crs_ln = 1, then HOME pressed */ + + default_ch = KEY_TAB; + mode = DONOTHING; + break; + } + } + } /* if(aidu > 0) */ + if(n < 0) + { + move(21, 0); + clrtobot(); + move(22, 0); + if(aidu <= 0) + prints("不合法的" AID_DISPLAYNAME ",請確定輸入是正確的"); + else + prints("找不到這個" AID_DISPLAYNAME ",可能是文章已消失,或是你找錯看板了"); + pressanykey(); + mode = FULLUPDATE; + } /* if(n < 0) */ + else + { + new_ln = n + 1; + move(b_lines, 0); + clrtoeol(); + mode = DONOTHING; + } + } + break; + case Ctrl('L'): + redrawwin(); + refresh(); + 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, locmem->crs_ln - locmem->top_ln); + } + else + mode = (*func)(locmem->crs_ln, + &headers[locmem->crs_ln - locmem->top_ln], + currdirect, locmem->crs_ln - locmem->top_ln); + 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; +} + +// recbase: 顯示位置的開頭 +// headers_size:要顯示幾行 +// last_line: 全板 .DIR + 置底 的有效數目 +// bottom_line: 全板 .DIR (無置底) 的有效數目 + +// XXX never return -1! + +static int +get_records_and_bottom(char *direct, fileheader_t* headers, + int recbase, int headers_size, int last_line, int bottom_line) +{ + // n: 置底除外的可顯示數目 + int n = bottom_line - recbase + 1, rv = 0; + + if( last_line < 1) // 完全沒東西 + return 0; + + // 不顯示置底的情形 + if( n >= headers_size || (currmode & (MODE_SELECT | MODE_DIGEST)) ) + { + rv = get_records(direct, headers, sizeof(fileheader_t), + recbase, headers_size); + return rv > 0 ? rv : 0; + } + + //////// 顯示本文+置底: //////// + + // 讀取 .DIR 本文 + if (n > 0) + { + n = get_records(direct, headers, sizeof(fileheader_t), recbase, n); + if (n < 0) n = 0; + rv += n; // rv 為有效本文數 + + recbase = 1; + } else { + // n <= 0 + recbase = 1 + (-n); + } + + // 讀取置底 (注意 recbase 可能超過 bottom_line, 也就是以置底第 n 個開始) + n = last_line - bottom_line +1 - (recbase-1); + if (rv + n > headers_size) + n = headers_size - rv; + + if (n > 0) { + char directbottom[PATHLEN]; + snprintf(directbottom, sizeof(directbottom), "%s.bottom", direct); + n = get_records(directbottom, headers+rv, sizeof(fileheader_t), recbase, n); + if (n < 0) n = 0; + rv += 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 DONOTHING: + break; + + 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, + bottom_line ? bottom_line : last_line); + if(newdirect_new_ln >= 0) + { + locmem->crs_ln = newdirect_new_ln + 1; + newdirect_new_ln = -1; + } + } + recbase = -1; + /* no break */ + + default: // for any unknown keys + 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 or black-hole */ + 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/console/record.c b/console/record.c new file mode 100644 index 00000000..d50df1d9 --- /dev/null +++ b/console/record.c @@ -0,0 +1,666 @@ +/* $Id$ */ + +#include "bbs.h" + +#undef HAVE_MMAP +#define BUFSIZE 512 + +#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]? 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, FN_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( d0) + { + 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--); + if (n + sizeof(".forward") > sizeof(buf)) + return -1; + memcpy(buf, fpath, n+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; +} + +#ifndef _BBS_UTIL_C_ +void +setaidfile(char *buf, const char *bn, aidu_t aidu) +{ + // try to load by AID + int bid = 0; + int n = 0, fd = 0; + char bfpath[PATHLEN] = ""; + boardheader_t *bh = NULL; + fileheader_t fh; + + buf[0] = 0; + bid = getbnum(bn); + + if (bid <= 0) return; + assert(0<=bid-1 && bid-1brdname, FN_DIR); + n = search_aidu(bfpath, aidu); + + if (n < 0) return; + fd = open(bfpath, O_RDONLY); + if (fd < 0) return; + + lseek(fd, n*sizeof(fileheader_t), SEEK_SET); + memset(&fh, 0, sizeof(fh)); + if (read(fd, &fh, sizeof(fh)) > 0) + { + setbfile(buf, bh->brdname, fh.filename); + if (!dashf(buf)) + buf[0] = 0; + } + close(fd); +} +#endif diff --git a/console/register.c b/console/register.c new file mode 100644 index 00000000..e0db7d37 --- /dev/null +++ b/console/register.c @@ -0,0 +1,3040 @@ +/* $Id$ */ +#include "bbs.h" + +#define FN_REGISTER_LOG "register.log" // global registration history +#define FN_JUSTIFY "justify" +#define FN_JUSTIFY_WAIT "justify.wait" +#define FN_REJECT_NOTIFY "justify.reject" + +// New style (Regform2) file names: +#define FN_REGFORM "regform" // registration form in user home +#define FN_REGFORM_LOG "regform.log" // regform history in user home +#define FN_REQLIST "reg.wait" // request list file, in global directory (replacing fn_register) + +//////////////////////////////////////////////////////////////////////////// +// Password Hash +//////////////////////////////////////////////////////////////////////////// + +// prototype of crypt() +char *crypt(const char *key, const char *salt); + +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; +} + +//////////////////////////////////////////////////////////////////////////// +// Value Validation +//////////////////////////////////////////////////////////////////////////// +static int +HaveRejectStr(const char *s, const char **rej) +{ + int i; + char *ptr, *rejectstr[] = + {"幹", "阿", "不", "你媽", "某", "笨", "呆", "..", "xx", + "你管", "管我", "猜", "天才", "超人", + "ㄅ", "ㄆ", "ㄇ", "ㄈ", "ㄉ", "ㄊ", "ㄋ", "ㄌ", "ㄍ", "ㄎ", "ㄏ", + "ㄐ", "ㄑ", "ㄒ", "ㄓ",/*"ㄔ",*/ "ㄕ", "ㄖ", "ㄗ", "ㄘ", "ㄙ", + "ㄧ", "ㄨ", "ㄩ", "ㄚ", "ㄛ", "ㄜ", "ㄝ", "ㄞ", "ㄟ", "ㄠ", "ㄡ", + "ㄢ", "ㄣ", "ㄤ", "ㄥ", "ㄦ", NULL}; + + if( rej != NULL ) + for( i = 0 ; rej[i] != NULL ; ++i ) + if( strstr(s, rej[i]) ) + return 1; + + for( i = 0 ; rejectstr[i] != NULL ; ++i ) + if( strstr(s, rejectstr[i]) ) + return 1; + + if( (ptr = strstr(s, "ㄔ")) != NULL ){ + if( ptr != s && strncmp(ptr - 1, "都市", 4) == 0 ) + return 0; + return 1; + } + return 0; +} + +static int +removespace(char *s) +{ + int i, index; + + for (i = 0, index = 0; s[i]; i++) { + if (s[i] != ' ') + s[index++] = s[i]; + } + s[index] = '\0'; + return index; +} + +int +bad_user_id(const char *userid) +{ + 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; +} + +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}; + + // addr[0] > 0: check if address is starting by Chinese. + if (!removespace(addr) || strlen(addr) < 15) + return "這個地址似乎並不完整"; + if (strstr(addr, "信箱") != NULL || strstr(addr, "郵政") != NULL) + return "抱歉我們不接受郵政信箱"; + if ((strstr(addr, "市") == NULL && strstr(addr, "巿") == NULL && + strstr(addr, "縣") == NULL && strstr(addr, "室") == NULL) || + HaveRejectStr(addr, rejectstr) || + strcmp(&addr[strlen(addr) - 2], "段") == 0 || + strcmp(&addr[strlen(addr) - 2], "路") == 0 || + strcmp(&addr[strlen(addr) - 2], "巷") == 0 || + strcmp(&addr[strlen(addr) - 2], "弄") == 0 || + strcmp(&addr[strlen(addr) - 2], "區") == 0 || + strcmp(&addr[strlen(addr) - 2], "市") == 0 || + strcmp(&addr[strlen(addr) - 2], "街") == 0 ) + return "這個地址似乎並不完整"; + return NULL; +} + +static char * +isvalidphone(char *phone) +{ + int i; + for( i = 0 ; phone[i] != 0 ; ++i ) + if( !isdigit((int)phone[i]) ) + return "請不要加分隔符號"; + if (!removespace(phone) || + strlen(phone) < 9 || + strstr(phone, "00000000") != NULL || + strstr(phone, "22222222") != NULL ) { + return "這個電話號碼並不正確(請含區碼)" ; + } + return NULL; +} + + +//////////////////////////////////////////////////////////////////////////// +// Account Expiring +//////////////////////////////////////////////////////////////////////////// + +/* -------------------------------- */ +/* New policy for allocate new user */ +/* (a) is the worst user currently */ +/* (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 */ + // XXX 目前 new acccount 並不會在 utmp 裡放 str_new... + 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, int expireRange) +{ + 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); + + // 若超過 expireRange 則砍人, + // 不然就 return 0 + if (-val > expireRange) + { + log_usies("DATED", genbuf); + // log_usies("CLEAN", genbuf); + kill_user(uid, urec->userid); + } else val = 0; + } + return val; +} + +//////////////////////////////////////////////////////////////////////////// +// Regcode Support +//////////////////////////////////////////////////////////////////////////// + +#define REGCODE_INITIAL "v6" // always 2 characters + +static char * +getregfile(char *buf) +{ + // not in user's home because s/he could zip his/her home + snprintf(buf, PATHLEN, "jobspool/.regcode.%s", cuser.userid); + return buf; +} + +static char * +makeregcode(char *buf) +{ + char fpath[PATHLEN]; + int fd, i; + // prevent ambigious characters: oOlI + const char *alphabet = "qwertyuipasdfghjkzxcvbnmoQWERTYUPASDFGHJKLZXCVBNM"; + + /* generate a new regcode */ + buf[13] = 0; + buf[0] = REGCODE_INITIAL[0]; + buf[1] = REGCODE_INITIAL[1]; + for( i = 2 ; i < 13 ; ++i ) + buf[i] = alphabet[random() % strlen(alphabet)]; + + getregfile(fpath); + if( (fd = open(fpath, O_WRONLY | O_CREAT, 0600)) == -1 ){ + perror("open"); + exit(1); + } + write(fd, buf, 13); + close(fd); + + return buf; +} + +static char * +getregcode(char *buf) +{ + int fd; + char fpath[PATHLEN]; + + getregfile(fpath); + if( (fd = open(fpath, O_RDONLY)) == -1 ){ + buf[0] = 0; + return buf; + } + read(fd, buf, 13); + close(fd); + buf[13] = 0; + return buf; +} + +void +delregcodefile(void) +{ + char fpath[PATHLEN]; + getregfile(fpath); + unlink(fpath); +} + +//////////////////////////////////////////////////////////////////////////// +// Justify Utilities +//////////////////////////////////////////////////////////////////////////// + +static void +justify_wait(char *userid, char *phone, char *career, + char *rname, char *addr, char *mobile) +{ + char buf[PATHLEN]; + sethomefile(buf, userid, FN_JUSTIFY_WAIT); + if (phone[0] != 0) { + FILE* fn = fopen(buf, "w"); + assert(fn); + fprintf(fn, "%s\n%s\ndummy\n%s\n%s\n%s\n", + phone, career, rname, addr, mobile); + fclose(fn); + } +} + +static void +email_justify(const userec_t *muser) +{ + char tmp[IDLEN + 1], buf[256], genbuf[256]; + /* + * It is intended to use BBSENAME instead of BBSNAME here. + * Because recently many poor users with poor mail clients + * (or evil mail servers) cannot handle/decode Chinese + * subjects (BBSNAME) correctly, so we'd like to use + * BBSENAME here to prevent subject being messed up. + * And please keep BBSENAME short or it may be truncated + * by evil mail servers. + */ + snprintf(buf, sizeof(buf), + " " BBSENAME " - [ %s ]", makeregcode(genbuf)); + + strlcpy(tmp, cuser.userid, sizeof(tmp)); + // XXX dirty, set userid=SYSOP + strlcpy(cuser.userid, str_sysop, sizeof(cuser.userid)); +#ifdef HAVEMOBILE + if (strcmp(muser->email, "m") == 0 || strcmp(muser->email, "M") == 0) + mobile_message(mobile, buf); + else +#endif + bsmtp("etc/registermail", buf, muser->email); + strlcpy(cuser.userid, tmp, sizeof(cuser.userid)); + move(20,0); + clrtobot(); + outs("我們即將寄出認證信 (您應該會在 10 分鐘內收到)\n" + "收到後您可以根據認證信標題的認證碼\n" + "輸入到 (U)ser -> (R)egister 後就可以完成註冊"); + pressanykey(); + return; +} + + +/* 使用者填寫註冊表格 */ +static void +getfield(int line, const char *info, const char *desc, char *buf, int len) +{ + char prompt[STRLEN]; + char genbuf[200]; + + // clear first + move(line+1, 0); clrtoeol(); + move(line, 0); clrtoeol(); + prints(" 原先設定:%-30.30s (%s)", buf, info); + snprintf(prompt, sizeof(prompt), " %s:", desc); + if (getdata_str(line + 1, 0, prompt, genbuf, len, DOECHO, buf)) + strcpy(buf, genbuf); + move(line+1, 0); clrtoeol(); + move(line, 0); clrtoeol(); + prints(" %s:%s", desc, buf); +} + + +int +setupnewuser(const userec_t *user) +{ + char genbuf[50]; + char *fn_fresh = ".fresh"; + userec_t utmp; + time_t clock; + struct stat st; + int fd, uid; + + clock = now; + + // XXX race condition... + if (dosearchuser(user->userid, NULL)) + { + vmsg("手腳不夠快,別人已經搶走了!"); + exit(1); + } + + /* 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); + // tolerate for one year. + check_and_expire_account(uid, &utmp, 365*12*60); + } + } + } + + /* 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; +} + +///////////////////////////////////////////////////////////////////////////// +// New Registration (Phase 1) +///////////////////////////////////////////////////////////////////////////// + +void +new_register(void) +{ + userec_t newuser; + char passbuf[STRLEN]; + int try, id, uid; + char *errmsg = NULL; + +#ifdef HAVE_USERAGREEMENT + more(HAVE_USERAGREEMENT, YEA); + while( 1 ){ + getdata(b_lines, 0, "請問您接受這份使用者條款嗎? (yes/no) ", + passbuf, 4, LCECHO); + if( passbuf[0] == 'y' ) + break; + if( passbuf[0] == 'n' ){ + vmsg("抱歉, 您須要接受使用者條款才能註冊帳號享受我們的服務唷!"); + exit(1); + } + vmsg("請輸入 y表示接受, n表示不接受"); + } +#endif + + // setup newuser + memset(&newuser, 0, sizeof(newuser)); + 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; + strlcpy(newuser.lasthost, fromhost, sizeof(newuser.lasthost)); + +#ifdef DBCSAWARE + if(u_detectDBCSAwareEvilClient()) + newuser.uflag &= ~DBCSAWARE_FLAG; + else + newuser.uflag |= DBCSAWARE_FLAG; +#endif + + 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)) && + // >=: see check_and_expire_account definition + (minute = check_and_expire_account(id, &xuser, 0)) >= 0) + { + if (minute == 999999) // XXX magic number. It should be greater than MAX_USERS at least. + outs("此代號已經有人使用 是不死之身"); + else { + prints("此代號已經有人使用 還有 %d 天才過期 \n", + minute / (60 * 24) + 1); + } + } else + break; + } + + // XXX 記得最後 create account 前還要再檢查一次 acc + + try = 0; + while (1) { + if (++try >= 6) { + vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); + exit(1); + } + move(19, 0); clrtoeol(); + outs(ANSI_COLOR(1;33) + "為避免被偷看,您的密碼並不會顯示在畫面上,直接輸入完後按 Enter 鍵即可。\n" + "另外請注意密碼只有前八個字元有效,超過的將自動忽略。" + ANSI_RESET); + if ((getdata(18, 0, "請設定密碼:", passbuf, + sizeof(passbuf), NOECHO) < 3) || + !strcmp(passbuf, newuser.userid)) { + outs("密碼太簡單,易遭入侵,至少要 4 個字,請重新輸入\n"); + continue; + } + strlcpy(newuser.passwd, passbuf, PASSLEN); + getdata(19, 0, "請檢查密碼:", passbuf, sizeof(passbuf), NOECHO); + if (strncmp(passbuf, newuser.passwd, PASSLEN)) { + outs("密碼輸入錯誤, 請重新輸入密碼.\n"); + continue; + } + passbuf[8] = '\0'; + strlcpy(newuser.passwd, genpasswd(passbuf), PASSLEN); + break; + } + // set-up more information. + + // warning: because currutmp=NULL, we can simply pass newuser.* to getdata. + // DON'T DO THIS IF YOUR currutmp != NULL. + try = 0; + while (strlen(newuser.nickname) < 2) + { + if (++try > 10) { + vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); + exit(1); + } + getdata(19, 0, "綽號暱稱:", newuser.nickname, + sizeof(newuser.nickname), DOECHO); + } + + try = 0; + while (strlen(newuser.realname) < 4) + { + if (++try > 10) { + vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); + exit(1); + } + getdata(20, 0, "真實姓名:", newuser.realname, + sizeof(newuser.realname), DOECHO); + + if ((errmsg = isvalidname(newuser.realname))) + { + memset(newuser.realname, 0, sizeof(newuser.realname)); + vmsg(errmsg); + } + } + + try = 0; + while (strlen(newuser.address) < 8) + { + // do not use isvalidaddr to check, + // because that requires foreign info. + if (++try > 10) { + vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); + exit(1); + } + getdata(21, 0, "聯絡地址:", newuser.address, + sizeof(newuser.address), DOECHO); + } + + try = 0; + while (newuser.year < 40) // magic number 40: see user.c + { + char birthday[sizeof("mmmm/yy/dd ")]; + int y, m, d; + + if (++try > 20) { + vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); + exit(1); + } + getdata(22, 0, "生日 (西元年/月/日, 如 1984/02/29):", birthday, + sizeof(birthday), DOECHO); + + if (ParseDate(birthday, &y, &m, &d)) { + vmsg("日期格式不正確"); + continue; + } else if (y < 1940) { + vmsg("你真的有那麼老嗎?"); + continue; + } + newuser.year = (unsigned char)(y-1900); + newuser.month = (unsigned char)m; + newuser.day = (unsigned char)d; + } + + setupnewuser(&newuser); + + if( (uid = initcuser(newuser.userid)) < 0) { + vmsg("無法建立帳號"); + exit(1); + } + log_usies("REGISTER", fromhost); +} + +void +check_birthday(void) +{ + // check birthday + int changed = 0; + time_t t = (time_t)now; + struct tm tm; + + localtime_r(&t, &tm); + while ( cuser.year < 40 || // magic number 40: see user.c + cuser.year+3 > tm.tm_year) + { + char birthday[sizeof("mmmm/yy/dd ")]; + int y, m, d; + + clear(); + stand_title("輸入生日"); + move(2,0); + outs("本站為配合實行內容分級制度,請您輸入正確的生日資訊。"); + + getdata(5, 0, "生日 (西元年/月/日, 如 1984/02/29):", birthday, + sizeof(birthday), DOECHO); + + if (ParseDate(birthday, &y, &m, &d)) { + vmsg("日期格式不正確"); + continue; + } else if (y < 1940) { + vmsg("你真的有那麼老嗎?"); + continue; + } else if (y+3 > tm.tm_year+1900) { + vmsg("嬰兒/未出生應該無法使用 BBS..."); + continue; + } + cuser.year = (unsigned char)(y-1900); + cuser.month = (unsigned char)m; + cuser.day = (unsigned char)d; + changed = 1; + } + + if (changed) { + clear(); + resolve_over18(); + } +} + +void +check_register(void) +{ + char fn[PATHLEN]; + + check_birthday(); + + if (HasUserPerm(PERM_LOGINOK)) + return; + + setuserfile(fn, FN_REJECT_NOTIFY); + + /* + * 避免使用者被退回註冊單後,在知道退回的原因之前, + * 又送出一次註冊單。 + */ + if (dashf(fn)) + { + more(fn, NA); + move(b_lines-3, 0); + outs("上次註冊單審查失敗。\n" + "請重新申請並照上面指示正確填寫註冊單。"); + while(getans("請輸入 y 繼續: ") != 'y'); + unlink(fn); + } else + if (ISNEWMAIL(currutmp)) + m_read(); + + 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 + } +} + +///////////////////////////////////////////////////////////////////////////// +// User Registration (Phase 2) +///////////////////////////////////////////////////////////////////////////// + +static void +toregister(char *email, char *phone, char *career, + char *rname, char *addr, char *mobile) +{ + FILE *fn = NULL; + + justify_wait(cuser.userid, phone, career, rname, addr, mobile); + + clear(); + stand_title("認證設定"); + if (cuser.userlevel & PERM_NOREGCODE){ + strcpy(email, "x"); + goto REGFORM2; + } + move(1, 0); + outs("您好, 本站認證認證的方式有:\n" + " 1.若您有 E-Mail (本站不接受 yahoo, kimo等免費的 E-Mail)\n" + " 請輸入您的 E-Mail , 我們會寄發含有認證碼的信件給您\n" + " 收到後請到 (U)ser => (R)egister 輸入認證碼, 即可通過認證\n" + "\n" + " 2.若您沒有 E-Mail 或是一直無法收到認證信, 請輸入 x \n" + " 會有站長親自人工審核註冊資料," ANSI_COLOR(1;33) + "但注意這可能會花上數週或更多時間。" ANSI_RESET "\n" + "**********************************************************\n" + "* 注意! *\n" + "* 通常應該會在輸入完成後十分鐘內收到認證信, 若過久未收到 *\n" + "* 請到郵件垃圾桶檢查是否被當作垃圾信(SPAM)了,另外若是 *\n" + "* 輸入後發生認證碼錯誤請重填一次 E-Mail *\n" + "**********************************************************\n"); + +#ifdef HAVEMOBILE + outs(" 3.若您有手機門號且想採取手機簡訊認證的方式 , 請輸入 m \n" + " 我們將會寄發含有認證碼的簡訊給您 \n" + " 收到後請到(U)ser => (R)egister 輸入認證碼, 即可通過認證\n"); +#endif + + while (1) { + email[0] = 0; + getfield(15, "身分認證用", "E-Mail Address", email, 50); + if (strcmp(email, "x") == 0 || strcmp(email, "X") == 0) + break; +#ifdef HAVEMOBILE + else if (strcmp(email, "m") == 0 || strcmp(email, "M") == 0) { + if (isvalidmobile(mobile)) { + char yn[3]; + getdata(16, 0, "請再次確認您輸入的手機號碼正確嘛? [y/N]", + yn, sizeof(yn), LCECHO); + if (yn[0] == 'Y' || yn[0] == 'y') + break; + } else { + move(15, 0); clrtobot(); + move(17, 0); + outs("指定的手機號碼不正確," + "若您無手機門號請選擇其他方式認證"); + } + + } +#endif + else if (isvalidemail(email)) { + char yn[3]; +#ifdef USE_EMAILDB + int email_count = emaildb_check_email(email, strlen(email)); + + if (email_count < 0) { + move(15, 0); clrtobot(); + move(17, 0); + outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); + pressanykey(); + return; + } else if (email_count >= EMAILDB_LIMIT) { + move(15, 0); clrtobot(); + move(17, 0); + outs("指定的 E-Mail 已註冊過多帳號, 請使用其他 E-Mail, 或輸入 x 採手動認證\n"); + outs("但注意手動認證通常會花上數週以上的時間。\n"); + } else { +#endif + getdata(16, 0, "請再次確認您輸入的 E-Mail 位置正確嘛? [y/N]", + yn, sizeof(yn), LCECHO); + if (yn[0] == 'Y' || yn[0] == 'y') + break; +#ifdef USE_EMAILDB + } +#endif + } else { + move(15, 0); clrtobot(); + move(17, 0); + outs("指定的 E-Mail 不正確, 若您無 E-Mail 請輸入 x 由站長手動認證\n"); + outs("但注意手動認證通常會花上數週以上的時間。\n"); + } + } +#ifdef USE_EMAILDB + if (emaildb_update_email(cuser.userid, strlen(cuser.userid), + email, strlen(email)) < 0) { + move(15, 0); clrtobot(); + move(17, 0); + outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); + pressanykey(); + return; + } +#endif + strlcpy(cuser.email, email, sizeof(cuser.email)); + REGFORM2: + if (strcasecmp(email, "x") == 0) { /* 手動認證 */ + if ((fn = fopen(fn_register, "a"))) { + fprintf(fn, "num: %d, %s", usernum, ctime4(&now)); + fprintf(fn, "uid: %s\n", cuser.userid); + fprintf(fn, "name: %s\n", rname); + fprintf(fn, "career: %s\n", career); + fprintf(fn, "addr: %s\n", addr); + fprintf(fn, "phone: %s\n", phone); + fprintf(fn, "mobile: %s\n", mobile); + fprintf(fn, "email: %s\n", email); + fprintf(fn, "----\n"); + fclose(fn); + // save justify information + snprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:", phone, career); + } + // XXX what if we cannot open register form? + } else { + // register by mail of phone + snprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:", phone, career); +#ifdef HAVEMOBILE + if (phone != NULL && email[1] == 0 && tolower(email[0]) == 'm') + sprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:", phone, career); +#endif + email_justify(&cuser); + } +} + + +int +u_register(void) +{ + char rname[20], addr[50], mobile[16]; +#ifdef FOREIGN_REG + char fore[2]; +#endif + char phone[20], career[40], email[50], birthday[11], sex_is[2]; + unsigned char year, mon, day; + char inregcode[14], regcode[50]; + char ans[3], *ptr, *errcode; + char genbuf[200]; + FILE *fn; + + if (cuser.userlevel & PERM_LOGINOK) { + outs("您的身份確認已經完成,不需填寫申請表"); + return XEASY; + } + if ((fn = fopen(fn_register, "r"))) { + int i =0; + while (fgets(genbuf, STRLEN, fn)) { + if ((ptr = strchr(genbuf, '\n'))) + *ptr = '\0'; + if (strncmp(genbuf, "uid: ", 5) != 0) + continue; + i++; + if(strcmp(genbuf + 5, cuser.userid) != 0) + continue; + fclose(fn); + /* idiots complain about this, so bug them */ + clear(); + move(3, 0); + prints(" 您的註冊申請單尚在處理中(處理順位: %d),請耐心等候\n\n", i); + outs(" 如果您已收到註冊碼卻看到這個畫面,那代表您在使用 Email 註冊後\n"); + outs(" " ANSI_COLOR(1;31) "又另外申請了站長直接人工審核的註冊申請單。" + ANSI_RESET "\n\n"); + // outs("該死,都不看說明的...\n"); + outs(" 進入人工審核程序後 Email 註冊自動失效,有註冊碼也沒用,\n"); + outs(" 要等到審核完成 (會多花很多時間,通常起碼數天) ,所以請耐心等候。\n\n"); + + /* 下面是國王的 code 所需要的 message */ +#if 0 + outs(" 另外請注意,若站長審註冊單時您正在站上則會無法審核、自動跳過。\n"); + outs(" 所以等候審核時請勿掛站。若超過兩三天仍未被審到,通常就是這個原因。\n"); +#endif + + vmsg("您的註冊申請單尚在處理中"); + return FULLUPDATE; + } + fclose(fn); + } + strlcpy(rname, cuser.realname, sizeof(rname)); + strlcpy(addr, cuser.address, sizeof(addr)); + strlcpy(email, cuser.email, sizeof(email)); + if (cuser.mobile) + snprintf(mobile, sizeof(mobile), "0%09d", cuser.mobile); + else + mobile[0] = 0; + if (cuser.month == 0 && cuser.day == 0 && cuser.year == 0) + birthday[0] = 0; + else + snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", + 1900 + cuser.year, cuser.month, cuser.day); + sex_is[0] = (cuser.sex % 8) + '1'; + sex_is[1] = 0; + career[0] = phone[0] = '\0'; + sethomefile(genbuf, cuser.userid, FN_JUSTIFY_WAIT); + if ((fn = fopen(genbuf, "r"))) { + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(phone, genbuf, sizeof(phone)); + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(career, genbuf, sizeof(career)); + + fgets(genbuf, sizeof(genbuf), fn); // old version compatible + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(rname, genbuf, sizeof(rname)); + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(addr, genbuf, sizeof(addr)); + + fgets(genbuf, sizeof(genbuf), fn); + chomp(genbuf); + strlcpy(mobile, genbuf, sizeof(mobile)); + + fclose(fn); + } + + if (cuser.userlevel & PERM_NOREGCODE) { + vmsg("您不被允許\使用認證碼認證。請填寫註冊申請單"); + goto REGFORM; + } + + // getregcode(regcode); + + // XXX why check by year? + // birthday is moved to earlier, so let's check email instead. + if (cuser.email[0] && // cuser.year != 0 && /* 已經第一次填過了~ ^^" */ + strcmp(cuser.email, "x") != 0 && /* 上次手動認證失敗 */ + strcmp(cuser.email, "X") != 0) + { + clear(); + stand_title("EMail認證"); + move(2, 0); + + prints("請輸入您的認證碼。(由 %s 開頭無空白的十三碼)\n" + "或輸入 x 來重新填寫 E-Mail 或改由站長手動認證\n", REGCODE_INITIAL); + inregcode[0] = 0; + + do{ + getdata(10, 0, "您的認證碼:", + inregcode, sizeof(inregcode), DOECHO); + if( strcmp(inregcode, "x") == 0 || strcmp(inregcode, "X") == 0 ) + break; + if( strlen(inregcode) != 13 || inregcode[0] == ' ') + vmsg("認證碼輸入不完整,總共應有十三碼,沒有空白字元。"); + else if( inregcode[0] != REGCODE_INITIAL[0] || inregcode[1] != REGCODE_INITIAL[1] ) { + /* old regcode */ + vmsg("輸入的認證碼錯誤," // "或因系統昇級已失效," + "請輸入 x 重填一次 E-Mail"); + } + else + break; + } while( 1 ); + + // make it case insensitive. + if (strcasecmp(inregcode, getregcode(regcode)) == 0) { + int unum; + delregcodefile(); + if ((unum = searchuser(cuser.userid, NULL)) == 0) { + vmsg("系統錯誤,查無此人!"); + u_exit("getuser error"); + exit(0); + } + mail_muser(cuser, "[註冊成功\囉]", "etc/registeredmail"); +#if FOREIGN_REG_DAY > 0 + if(cuser.uflag2 & FOREIGN) + mail_muser(cuser, "[出入境管理局]", "etc/foreign_welcome"); +#endif + cuser.userlevel |= (PERM_LOGINOK | PERM_POST); + outs("\n註冊成功\, 重新上站後將取得完整權限\n" + "請按下任一鍵跳離後重新上站~ :)"); + sethomefile(genbuf, cuser.userid, FN_JUSTIFY_WAIT); + unlink(genbuf); + snprintf(cuser.justify, sizeof(cuser.justify), + "%s:%s:email", phone, career); + sethomefile(genbuf, cuser.userid, FN_JUSTIFY); + log_file(genbuf, LOG_CREAT, cuser.justify); + pressanykey(); + u_exit("registed"); + exit(0); + return QUIT; + } else if (strcasecmp(inregcode, "x") != 0) { + if (regcode[0]) + { + vmsg("認證碼錯誤!"); + return FULLUPDATE; + } + else + { + vmsg("認證碼已過期,請重新註冊。"); + toregister(email, phone, career, rname, addr, mobile); + return FULLUPDATE; + } + } else { + toregister(email, phone, career, rname, addr, mobile); + return FULLUPDATE; + } + } + + REGFORM: + getdata(b_lines - 1, 0, "您確定要填寫註冊單嗎(Y/N)?[N] ", + ans, 3, LCECHO); + if (ans[0] != 'y') + return FULLUPDATE; + + move(2, 0); + clrtobot(); + while (1) { + clear(); + move(1, 0); + prints("%s(%s) 您好,請據實填寫以下的資料:", + cuser.userid, cuser.nickname); +#ifdef FOREIGN_REG + fore[0] = 'y'; + fore[1] = 0; + getfield(2, "Y/n", "是否現在住在台灣", fore, 2); + if (fore[0] == 'n') + fore[0] |= FOREIGN; + else + fore[0] = 0; +#endif + while (1) { + getfield(8, +#ifdef FOREIGN_REG + "請用本名", +#else + "請用中文", +#endif + "真實姓名", rname, 20); + if( (errcode = isvalidname(rname)) == NULL ) + break; + else + vmsg(errcode); + } + + move(11, 0); + outs(" 請盡量詳細的填寫您的服務單位,大專院校請麻煩" + "加" ANSI_COLOR(1;33) "系所" ANSI_RESET ",公司單位請加" ANSI_COLOR(1;33) "職稱" ANSI_RESET ",\n" + " 暫無工作請麻煩填寫" ANSI_COLOR(1;33) "畢業學校" ANSI_RESET "。\n"); + while (1) { + getfield(9, "(畢業)學校(含" ANSI_COLOR(1;33) "系所年級" ANSI_RESET ")或單位職稱", + "服務單位", career, 40); + if( (errcode = isvalidcareer(career)) == NULL ) + break; + else + vmsg(errcode); + } + move(10, 0); clrtobot(); + while (1) { + getfield(10, "含" ANSI_COLOR(1;33) "縣市" ANSI_RESET "及門寢號碼" + "(台北請加" ANSI_COLOR(1;33) "行政區" ANSI_RESET ")", + "目前住址", addr, sizeof(addr)); + if( (errcode = isvalidaddr(addr)) == NULL +#ifdef FOREIGN_REG + || fore[0] +#endif + ) + break; + else + vmsg(errcode); + } + while (1) { + getfield(11, "不加-(), 包括長途區號", "連絡電話", phone, 11); + if( (errcode = isvalidphone(phone)) == NULL ) + break; + else + vmsg(errcode); + } + getfield(12, "只輸入數字 如:0912345678 (可不填)", + "手機號碼", mobile, 20); + while (1) { + getfield(13, "西元/月月/日日 如:1984/02/29", "生日", birthday, sizeof(birthday)); + if (birthday[0] == 0) { + snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", + 1900 + cuser.year, cuser.month, cuser.day); + mon = cuser.month; + day = cuser.day; + year = cuser.year; + } else { + int y, m, d; + if (ParseDate(birthday, &y, &m, &d)) { + vmsg("您的輸入不正確"); + continue; + } + mon = (unsigned char)m; + day = (unsigned char)d; + year = (unsigned char)(y - 1900); + } + if (year < 40) { + vmsg("您的輸入不正確"); + continue; + } + break; + } + getfield(14, "1.葛格 2.姐接 ", "性別", sex_is, 2); + getdata(20, 0, "以上資料是否正確(Y/N)?(Q)取消註冊 [N] ", + ans, 3, LCECHO); + if (ans[0] == 'q') + return 0; + if (ans[0] == 'y') + break; + } + strlcpy(cuser.realname, rname, sizeof(cuser.realname)); + strlcpy(cuser.address, addr, sizeof(cuser.address)); + strlcpy(cuser.email, email, sizeof(cuser.email)); + cuser.mobile = atoi(mobile); + cuser.sex = (sex_is[0] - '1') % 8; + cuser.month = mon; + cuser.day = day; + cuser.year = year; +#ifdef FOREIGN_REG + if (fore[0]) + cuser.uflag2 |= FOREIGN; + else + cuser.uflag2 &= ~FOREIGN; +#endif + trim(career); + trim(addr); + trim(phone); + + toregister(email, phone, career, rname, addr, mobile); + + // update cuser + passwd_update(usernum, &cuser); + + return FULLUPDATE; +} + +///////////////////////////////////////////////////////////////////////////// +// Administration (SYSOP Validation) +///////////////////////////////////////////////////////////////////////////// + +#define REJECT_REASONS (6) +#define REASON_LEN (60) +static const char *reasonstr[REJECT_REASONS] = { + "輸入真實姓名", + "詳填(畢業)學校『系』『級』或服務單位(含所屬縣市及職稱)", + "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", + "詳填連絡電話 (含區碼, 中間不加 '-', '(', ')' 等符號)", + "精確並完整填寫註冊申請表", + "用中文填寫申請單", +}; + +#define REASON_FIRSTABBREV '0' +#define REASON_IN_ABBREV(x) \ + ((x) >= REASON_FIRSTABBREV && (x) - REASON_FIRSTABBREV < REJECT_REASONS) +#define REASON_EXPANDABBREV(x) reasonstr[(x) - REASON_FIRSTABBREV] + +void +regform_accept(const char *userid, const char *justify) +{ + char buf[PATHLEN]; + int unum = 0; + userec_t muser; + + unum = getuser(userid, &muser); + if (unum == 0) + return; // invalid user + + muser.userlevel |= (PERM_LOGINOK | PERM_POST); + strlcpy(muser.justify, justify, sizeof(muser.justify)); + // manual accept sets email to 'x' + strlcpy(muser.email, "x", sizeof(muser.email)); + + // handle files + sethomefile(buf, muser.userid, FN_JUSTIFY_WAIT); + unlink(buf); + sethomefile(buf, muser.userid, FN_REJECT_NOTIFY); + unlink(buf); + sethomefile(buf, muser.userid, FN_JUSTIFY); + log_filef(buf, LOG_CREAT, "%s\n", muser.justify); + + // update password file + passwd_update(unum, &muser); + + // alert online users? + sendalert(muser.userid, ALERT_PWD_PERM|ALERT_PWD_JUSTIFY); // force to reload perm + +#if FOREIGN_REG_DAY > 0 + if(muser.uflag2 & FOREIGN) + mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); + else +#endif + // last: send notification mail + mail_muser(muser, "[註冊成功\囉]", "etc/registered"); +} + +void +regform_reject(const char *userid, const char *reason) +{ + char buf[PATHLEN]; + FILE *fp = NULL; + int unum = 0; + userec_t muser; + + unum = getuser(userid, &muser); + if (unum == 0) + return; // invalid user + + muser.userlevel &= ~(PERM_LOGINOK | PERM_POST); + + // handle files + sethomefile(buf, muser.userid, FN_JUSTIFY_WAIT); + unlink(buf); + + // update password file + passwd_update(unum, &muser); + + // alert notify users? + sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm + + // last: send notification + mkuserdir(muser.userid); + sethomefile(buf, muser.userid, FN_REJECT_NOTIFY); + fp = fopen(buf, "wt"); + assert(fp); + syncnow(); + fprintf(fp, "%s 註冊失敗。\n", Cdate(&now)); + + // multiple abbrev loop + if (REASON_IN_ABBREV(reason[0])) + { + int i = 0; + for (i = 0; i < REASON_LEN && REASON_IN_ABBREV(reason[i]); i++) + fprintf(fp, "[退回原因] 請%s\n", REASON_EXPANDABBREV(reason[i])); + } else { + fprintf(fp, "[退回原因] %s\n", reason); + } + fclose(fp); + mail_muser(muser, "[註冊失敗]", buf); +} + +// Regform v1 API +// read count entries from regsrc to a temp buffer +FILE * +pull_regform(const char *regfile, char *workfn, int count) +{ + FILE *fp = NULL; + + snprintf(workfn, PATHLEN, "%s.tmp", regfile); + if (dashf(workfn)) { + vmsg("其他 SYSOP 也在審核註冊申請單"); + return NULL; + } + + // count < 0 means unlimited pulling + Rename(regfile, workfn); + if ((fp = fopen(workfn, "r")) == NULL) { + vmsgf("系統錯誤,無法讀取註冊資料檔: %s", workfn); + return NULL; + } + return fp; +} + +// write all left in "remains" to regfn. +void +pump_regform(const char *regfn, FILE *remains) +{ + // restore trailing tickets + char buf[PATHLEN]; + FILE *fout = fopen(regfn, "at"); + if (!fout) + return; + + while (fgets(buf, sizeof(buf), remains)) + fputs(buf, fout); + fclose(fout); +} + +// New Regform UI +static void +prompt_regform_ui() +{ + move(b_lines, 0); + outs(ANSI_COLOR(30;47) " " + ANSI_COLOR(31) "y" ANSI_COLOR(30) "接受 " + ANSI_COLOR(31) "n" ANSI_COLOR(30) "拒絕 " + ANSI_COLOR(31) "d" ANSI_COLOR(30) "刪除 " + ANSI_COLOR(31) "s" ANSI_COLOR(30) "跳過 " + ANSI_COLOR(31) "u" ANSI_COLOR(30) "復原 " + " " + ANSI_COLOR(31) "0-9jk↑↓" ANSI_COLOR(30) "移動 " + ANSI_COLOR(31) "空白/PgDn" ANSI_COLOR(30) "儲存+下頁 " + " " + ANSI_COLOR(31) "q/END" ANSI_COLOR(30) "結束 " + ANSI_RESET); +} + +static void +resolve_reason(char *s, int y) +{ + // should start with REASON_FIRSTABBREV + const char *reason_prompt = + " (0)真實姓名 (1)詳填系級 (2)完整住址" + " (3)詳填電話 (4)確實填寫 (5)中文填寫"; + + s[0] = 0; + move(y, 0); + outs(reason_prompt); outs("\n"); + + do { + getdata(y+1, 0, + "退回原因: ", s, REASON_LEN, DOECHO); + + // convert abbrev reasons (format: single digit, or multiple digites) + if (REASON_IN_ABBREV(s[0])) + { + if (s[1] == 0) // simple replace ment + { + strlcpy(s+2, REASON_EXPANDABBREV(s[0]), + REASON_LEN-2); + s[0] = 0xbd; // '請'[0]; + s[1] = 0xd0; // '請'[1]; + } else { + // strip until all digites + char *p = s; + while (*p) + { + if (!REASON_IN_ABBREV(*p)) + *p = ' '; + p++; + } + strip_blank(s, s); + strlcat(s, " [多重原因]", REASON_LEN); + } + } + + if (strlen(s) < 4) + { + if (vmsg("原因太短。 要取消退回嗎? (y/N): ") == 'y') + { + *s = 0; + return; + } + } + } while (strlen(s) < 4); +} + +//////////////////////////////////////////////////////////////////////////// +// Regform Utilities +//////////////////////////////////////////////////////////////////////////// + +// TODO define and use structure instead, even in reg request file. +typedef struct { + // current format: + // (optional) num: unum, date + // [0] uid: xxxxx (IDLEN=12) + // [1] name: RRRRRR (20) + // [2] career: YYYYYYYYYYYYYYYYYYYYYYYYYY (40) + // [3] addr: TTTTTTTTT (50) + // [4] phone: 02DDDDDDDD (20) + // [5] email: x (50) (deprecated) + // [6] mobile: (deprecated) + // [7] ---- + // lasthost: 16 + char userid[IDLEN+1]; + + char exist; + char online; + char pad [ 5]; // IDLEN(12)+1+1+1+5=20 + + char name [20]; + char career[40]; + char addr [50]; + char phone [20]; +} RegformEntry; + +// regform format utilities +int +load_regform_entry(RegformEntry *pre, FILE *fp) +{ + char buf[STRLEN]; + char *v; + + memset(pre, 0, sizeof(RegformEntry)); + while (fgets(buf, sizeof(buf), fp)) + { + if (buf[0] == '-') + break; + buf[sizeof(buf)-1] = 0; + v = strchr(buf, ':'); + if (v == NULL) + continue; + *v++ = 0; + if (*v == ' ') v++; + chomp(v); + + if (strcmp(buf, "uid") == 0) + strlcpy(pre->userid, v, sizeof(pre->userid)); + else if (strcmp(buf, "name") == 0) + strlcpy(pre->name, v, sizeof(pre->name)); + else if (strcmp(buf, "career") == 0) + strlcpy(pre->career, v, sizeof(pre->career)); + else if (strcmp(buf, "addr") == 0) + strlcpy(pre->addr, v, sizeof(pre->addr)); + else if (strcmp(buf, "phone") == 0) + strlcpy(pre->phone, v, sizeof(pre->phone)); + } + return pre->userid[0] ? 1 : 0; +} + +int +print_regform_entry(const RegformEntry *pre, FILE *fp, int close) +{ + fprintf(fp, "uid: %s\n", pre->userid); + fprintf(fp, "name: %s\n", pre->name); + fprintf(fp, "career: %s\n", pre->career); + fprintf(fp, "addr: %s\n", pre->addr); + fprintf(fp, "phone: %s\n", pre->phone); + if (close) + fprintf(fp, "----\n"); + return 1; +} + +int +append_regform(const RegformEntry *pre, const char *logfn, + const char *varname, const char *varval1, const char *varval2) +{ + FILE *fout = fopen(logfn, "at"); + if (!fout) + return 0; + + print_regform_entry(pre, fout, 0); + if (varname && *varname) + { + syncnow(); + fprintf(fout, "Date: %s\n", Cdate(&now)); + if (!varval1) varval1 = ""; + fprintf(fout, "%s: %s", varname, varval1); + if (varval2) fprintf(fout, " %s", varval2); + fprintf(fout, "\n"); + } + // close it + fprintf(fout, "----\n"); + fclose(fout); + return 1; +} + +//////////////////////////////////////////////////////////////////////////// +// Regform2 API +//////////////////////////////////////////////////////////////////////////// + +// registration queue +int +regq_append(const char *userid) +{ + if (file_append_record(FN_REQLIST, userid) < 0) + return 0; + return 1; +} + +int +regq_find(const char *userid) +{ + return file_find_record(FN_REQLIST, userid); +} + +int +regq_delete(const char *userid) +{ + return file_delete_record(FN_REQLIST, userid, 0); +} + +// user home regform operation +int +regfrm_exist(const char *userid) +{ + char fn[PATHLEN]; + sethomefile(fn, userid, FN_REGFORM); + return dashf(fn) ? 1 : 0; +} + +int +regfrm_load(const char *userid, RegformEntry *pre) +{ + FILE *fp = NULL; + char fn[PATHLEN]; + int ret = 0; + sethomefile(fn, userid, FN_REGFORM); + if (!dashf(fn)) + return 0; + + fp = fopen(fn, "rt"); + if (!fp) + return 0; + ret = load_regform_entry(pre, fp); + fclose(fp); + return ret; +} + +int +regfrm_save(const char *userid, const RegformEntry *pre) +{ + FILE *fp = NULL; + char fn[PATHLEN]; + int ret = 0; + sethomefile(fn, userid, FN_REGFORM); + + fp = fopen(fn, "wt"); + if (!fp) + return 0; + ret = print_regform_entry(pre, fp, 1); + fclose(fp); + return ret; +} + +int +regfrm_trylock(const char *userid) +{ + int fd = 0; + char fn[PATHLEN]; + sethomefile(fn, userid, FN_REGFORM); + if (!dashf(fn)) return 0; + fd = open(fn, O_RDONLY); + if (fd < 0) return 0; + if (flock(fd, LOCK_EX|LOCK_NB) == 0) + return fd; + close(fd); + return 0; +} + +int +regfrm_unlock(int lockfd) +{ + int fd = lockfd; + if (lockfd <= 0) + return 0; + lockfd = flock(fd, LOCK_UN) == 0 ? 1 : 0; + close(fd); + return lockfd; +} + +// regform processors +int +regfrm_accept(RegformEntry *pre) +{ + char justify[REGLEN+1], buf[STRLEN*2]; + char fn[PATHLEN], fnlog[PATHLEN]; + + // dry run! + vmsg("regfrm_accept"); + return 1; + + sethomefile(fn, pre->userid, FN_REGFORM); + + // build justify string + removespace(pre->phone); + removespace(pre->career); + snprintf(justify, sizeof(justify), + "%s:%s:%s", pre->phone, pre->career, cuser.userid); + + // call handler + regform_accept(pre->userid, justify); + + // append current form to history. + sethomefile(fnlog, pre->userid, FN_REGFORM_LOG); + AppendTail(fn, fnlog, 0); + // global history + snprintf(buf, sizeof(buf), "Approved: %s -> %s\nDate: %s\n", + cuser.userid, pre->userid, Cdate(&now)); + file_append_line(FN_REGISTER_LOG, buf); + AppendTail(fn, FN_REGISTER_LOG, 0); + + // remove from queue + unlink(fn); + regq_delete(pre->userid); + return 1; +} + +int +regfrm_reject(RegformEntry *pre, const char *reason) +{ + char buf[STRLEN*2]; + char fn[PATHLEN]; + + // dry run! + vmsg("regfrm_reject"); + return 1; + + sethomefile(fn, pre->userid, FN_REGFORM); + + // call handler + regform_reject(pre->userid, reason); + + // log it + snprintf(buf, sizeof(buf), "Rejected: %s -> %s [%s]\nDate: %s\n", + cuser.userid, pre->userid, reason, Cdate(&now)); + file_append_line(FN_REGISTER_LOG, buf); + AppendTail(fn, FN_REGISTER_LOG, 0); + + // remove from queue + unlink(fn); + regq_delete(pre->userid); + return 1; +} + +int +regfrm_delete(const char *userid) +{ + char fn[PATHLEN]; + sethomefile(fn, userid, FN_REGFORM); + + // dry run! + vmsgf("regfrm_delete (%s)", userid); + return 1; + + // directly delete. + unlink(fn); + + // remove from queue + regq_delete(userid); + return 1; +} + +// working queue +FILE * +regq_init_pull() +{ + FILE *fp = tmpfile(), *src =NULL; + char buf[STRLEN]; + if (!fp) return NULL; + src = fopen(FN_REQLIST, "rt"); + if (!src) { fclose(fp); return NULL; } + while (fgets(buf, sizeof(buf), src)) + fputs(buf, fp); + fclose(src); + rewind(fp); + return fp; +} + +int +regq_pull(FILE *fp, char *uid) +{ + char buf[STRLEN]; + size_t idlen = 0; + uid[0] = 0; + if (fgets(buf, sizeof(buf), fp) == NULL) + return 0; + idlen = strcspn(buf, str_space); + if (idlen < 1) return 0; + if (idlen > IDLEN) idlen = IDLEN; + strlcpy(uid, buf, idlen+1); + return 1; +} + +int +regq_end_pull(FILE *fp) +{ + // no need to unlink because fp is a tmpfile. + if (!fp) return 0; + fclose(fp); + return 1; +} + +// UI part +int +ui_display_regform_single( + const userec_t *xuser, + const RegformEntry *pre, + int tid, char *reason) +{ + int c; + + while (1) + { + move(1, 0); + user_display(xuser, 1); + move(14, 0); + prints(ANSI_COLOR(1;32) + "--------------- 這是第 %2d 份註冊單 ------------------" + ANSI_RESET "\n", tid); + prints(" %-12s: %s\n", "帳號", pre->userid); + prints("0.%-12s: %s%s\n", "真實姓名", pre->name, + xuser->uflag2 & FOREIGN ? " (外籍)" : + ""); + prints("1.%-12s: %s\n", "服務單位", pre->career); + prints("2.%-12s: %s\n", "目前住址", pre->addr); + prints("3.%-12s: %s\n", "連絡電話", pre->phone); + + move(b_lines, 0); + outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] "); + + c = tolower(igetch() & 0xFF); // round to ASCII + if (c == 'y' || c == 'q' || c == 'd' || c == 's') + return c; + if (c == 'n') + { + int n = 0; + move(3, 0); + outs("\n" ANSI_COLOR(1;31) + "請提出退回申請表原因,按 取消:\n" ANSI_RESET); + for (n = 0; n < REJECT_REASONS; n++) + prints("%d) 請%s\n", n, reasonstr[n]); + outs("\n\n\n"); // preserved for prompt + + getdata(3+2+REJECT_REASONS+1, 0,"退回原因: ", + reason, REASON_LEN, DOECHO); + if (reason[0] == 0) + continue; + // interprete reason + return 'n'; + } + else if (REASON_IN_ABBREV(c)) + { + // quick set + sprintf(reason, "%c", c); + return 'n'; + } + return 's'; + } + // shall never reach here + return 's'; +} + +// sample validator +void +regform2_validate_single() +{ + int lfd = 0; + int tid = 0; + char uid[IDLEN+1]; + char rsn[REASON_LEN]; + FILE *fpregq = regq_init_pull(); + RegformEntry re; + + if (!fpregq) + return; + + while (regq_pull(fpregq, uid)) + { + userec_t muser; + int unum = 0; + int abort = 0; + + // check if user exists. + memset(&muser, 0, sizeof(muser)); + unum = getuser(uid, &muser); + + if (unum < 1) + { + regq_delete(uid); + continue; + } + + // check if regform exists. + if (!regfrm_exist(uid)) + { + // TODO delete here? + regq_delete(uid); + continue; + } + + // TODO check if user is already registered +#if 0 + if (muser.userlevel & PERM_LOGINOK) + { + regfrm_delete(uid); + continue; + } +#endif + + // try to lock + lfd = regfrm_trylock(uid); + if (lfd <= 0) + continue; + + // load it + if (!regfrm_load(uid, &re)) + { + regfrm_delete(uid); + regfrm_unlock(lfd); + // regq_delete(uid); // done in regfrm_delete + continue; + } + + tid ++; + // display regform and process + switch(ui_display_regform_single(&muser, &re, tid, rsn)) + { + case 'a': // accept + regfrm_accept(&re); + break; + + case 'd': // delete + regfrm_delete(uid); + break; + + case 'q': // quit + abort = 1; + break; + + case 'n': // reject + regfrm_reject(&re, rsn); + break; + + case 's': // skip + // do nothing. + break; + + default: // shall never reach here + assert(0); + break; + } + + // final processing + regfrm_unlock(lfd); + + if (abort) + break; + } + regq_end_pull(fpregq); + + // finishing + clear(); move(5, 0); + prints("您審了 %d 份註冊單份。", tid); + pressanykey(); +} + +#define FORMS_IN_PAGE (10) + +int +regform2_validate_page(int dryrun) +{ + int unum = 0; + int yMsg = FORMS_IN_PAGE*2+1; + userec_t muser; + RegformEntry forms [FORMS_IN_PAGE]; + char ans [FORMS_IN_PAGE]; + int lfds [FORMS_IN_PAGE]; + char rejects[FORMS_IN_PAGE][REASON_LEN]; // reject reason length + char rsn [REASON_LEN]; + int cforms = 0, // current loaded forms + ci = 0, // cursor index + ch = 0, // input key + i; + int tid = 0; + char uid[IDLEN+1]; + FILE *fpregq = regq_init_pull(); + + if (!fpregq) + return 0; + + while (ch != 'q') + { + // initialize and prepare + memset(ans, 0, sizeof(ans)); + memset(rejects, 0, sizeof(rejects)); + memset(forms, 0, sizeof(forms)); + memset(lfds, 0, sizeof(lfds)); + cforms = 0; + clear(); + + // load forms + while (cforms < FORMS_IN_PAGE) + { + if (!regq_pull(fpregq, uid)) + break; + i = cforms; // align index + + // check if user exists. + memset(&muser, 0, sizeof(muser)); + unum = getuser(uid, &muser); + if (unum < 1) + { + regq_delete(uid); + continue; + } + + // check if regform exists. + if (!regfrm_exist(uid)) + { + // TODO delete here? + regq_delete(uid); + continue; + } + // try to lock + lfds[i] = regfrm_trylock(uid); + if (lfds[i] <= 0) + continue; + + // load it + if (!regfrm_load(uid, &forms[i])) + { + regfrm_delete(uid); + regfrm_unlock(lfds[i]); + // regq_delete(uid); // done in regfrm_delete + continue; + } + + forms[i].exist = 1; + forms[i].online = search_ulist(unum) ? 1 : 0; + + // assign default answers + if (muser.userlevel & PERM_LOGINOK) + ans[i] = 'd'; +#ifdef REGFORM_DISABLE_ONLINE_USER + else if (forms[i].online) + ans[i] = 's'; +#endif // REGFORM_DISABLE_ONLINE_USER + + + // display + move(i*2, 0); + prints(" %2d%s %s%-12s " ANSI_RESET, + i+1, + (unum == 0) ? ANSI_COLOR(1;31) "D" : + ( (muser.userlevel & PERM_LOGINOK) ? + ANSI_COLOR(1;33) "Y" : +#ifdef REGFORM_DISABLE_ONLINE_USER + forms[i].online ? "s" : +#endif + "."), + forms[i].online ? ANSI_COLOR(1;35) : ANSI_COLOR(1), + forms[i].userid); + + prints( ANSI_COLOR(1;31) "%19s " + ANSI_COLOR(1;32) "%-40s" ANSI_RESET"\n", + forms[i].name, forms[i].career); + + move(i*2+1, 0); + prints(" %s %-50s%20s\n", + (muser.userlevel & PERM_NOREGCODE) ? + ANSI_COLOR(1;31) "T" ANSI_RESET : " ", + forms[i].addr, forms[i].phone); + + cforms++, tid ++; + } + + // if no more forms then leave. + if (cforms < 1) + break; + + // adjust cursor if required + if (ci >= cforms) + ci = cforms-1; + + // display page info + { + char msg[STRLEN]; + snprintf(msg, sizeof(msg), + "%s 已顯示 %d 份註冊單 ", // "(%2d%%) ", + dryrun? "(測試模式)" : "", + tid); + prints(ANSI_COLOR(7) "\n%78s" ANSI_RESET "\n", msg); + } + + // handle user input + prompt_regform_ui(); + ch = 0; + while (ch != 'q' && ch != ' ') { + ch = cursor_key(ci*2, 0); + switch (ch) + { + // nav keys + case KEY_UP: + case 'k': + if (ci > 0) ci--; + break; + + case KEY_DOWN: + case 'j': + ch = 'j'; // go next + break; + + // quick nav (assuming to FORMS_IN_PAGE=10) + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + ci = ch - '1'; + if (ci >= cforms) ci = cforms-1; + break; + case '0': + ci = 10-1; + if (ci >= cforms) ci = cforms-1; + break; + + /* + case KEY_HOME: ci = 0; break; + case KEY_END: ci = cforms-1; break; + */ + + // abort + case KEY_END: + case 'q': + ch = 'q'; + if (getans("確定要離開了嗎? (本頁變更將不會儲存) [y/N]: ") != 'y') + { + prompt_regform_ui(); + ch = 0; + continue; + } + break; + + // prepare to go next page + case KEY_PGDN: + case ' ': + ch = ' '; + + { + int blanks = 0; + // solving blank (undecided entries) + for (i = 0, blanks = 0; i < cforms; i++) + if (ans[i] == 0) blanks ++; + + if (!blanks) + break; + + // have more blanks + ch = getans("尚未指定的 %d 個項目要: (S跳過/y通過/n拒絕/e繼續編輯): ", + blanks); + } + + if (ch == 'e') + { + prompt_regform_ui(); + ch = 0; + continue; + } + if (ch == 'y') { + // do nothing. + } else if (ch == 'n') { + // query reject reason + resolve_reason(rsn, yMsg); + if (*rsn == 0) + ch = 's'; + } else ch = 's'; + + // filling answers + for (i = 0; i < cforms; i++) + { + if (ans[i] != 0) + continue; + ans[i] = ch; + if (ch != 'n') + continue; + strlcpy(rejects[i], rsn, REASON_LEN); + } + + ch = ' '; // go to page mode! + break; + + // function keys + case 'y': // accept +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + case 's': // skip + case 'd': // delete + case KEY_DEL: //delete + if (ch == KEY_DEL) ch = 'd'; + + grayout(ci*2, ci*2+1, GRAYOUT_DARK); + move_ansi(ci*2, 4); outc(ch); + ans[ci] = ch; + ch = 'j'; // go next + break; + + case 'u': // undo +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + grayout(ci*2, ci*2+1, GRAYOUT_NORM); + move_ansi(ci*2, 4); outc('.'); + ans[ci] = 0; + ch = 'j'; // go next + break; + + case 'n': // reject +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + // query for reason + resolve_reason(rejects[ci], yMsg); + prompt_regform_ui(); + + if (!rejects[ci][0]) + break; + + move(yMsg, 0); + prints("退回 %s 註冊單原因:\n %s\n", forms[ci].userid, rejects[ci]); + + // do reject + grayout(ci*2, ci*2+1, GRAYOUT_DARK); + move_ansi(ci*2, 4); outc(ch); + ans[ci] = ch; + ch = 'j'; // go next + + break; + } // switch(ch) + + // change cursor + if (ch == 'j' && ++ci >= cforms) + ci = cforms -1; + } // while(ch != QUIT/SAVE) + + // if exit, we still need to skip all read forms + if (ch == 'q') + { + for (i = 0; i < cforms; i++) + ans[i] = 's'; + } + + // page complete (save). + assert(ch == ' ' || ch == 'q'); + + // save/commit if required. + if (dryrun) + { + // prmopt for debug + clear(); + stand_title("測試模式"); + outs("您正在執行測試模式,所以剛審的註冊單並不會生效。\n" + "下面列出的是剛才您審完的結果:\n\n"); + + for (i = 0; i < cforms; i++) + { + char justify[REGLEN]; + if (ans[i] == 'y') + snprintf(justify, sizeof(justify), // build justify string + "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); + + prints("%2d. %-12s - %c %s\n", i+1, forms[i].userid, ans[i], + ans[i] == 'n' ? rejects[i] : + ans[i] == 'y' ? justify : ""); + } + if (ch != 'q') + pressanykey(); + } + else + { + // real functionality + for (i = 0; i < cforms; i++) + { + switch(ans[i]) + { + case 'a': // accept + regfrm_accept(&forms[i]); + break; + + case 'd': // delete + regfrm_delete(uid); + break; + + case 'n': // reject + regfrm_reject(&forms[i], rsn); + break; + + case 's': // skip + // do nothing. + break; + + default: + assert(0); + break; + } + } + } // !dryrun + + // unlock all forms + for (i = 0; i < cforms; i++) + regfrm_unlock(lfds[i]); + + } // while (ch != 'q') + + regq_end_pull(fpregq); + + // finishing + clear(); move(5, 0); + prints("您審了 %d 份註冊單份。", tid); + pressanykey(); + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +// Regform UI +// 處理 Register Form +///////////////////////////////////////////////////////////////////////////// + +/* Auto-Regform-Scan + * 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; +} + +///////////////////////////////////////////////////////////////////////////// +// Traditional Regform UI +///////////////////////////////////////////////////////////////////////////// +// TODO XXX process someone directly, according to target_uid. +int +scan_register_form(const char *regfile, int automode, const char *target_uid) +{ + char genbuf[200]; + char *field[] = { + "uid", "name", "career", "addr", "phone", "email", NULL + }; + char *finfo[] = { + "帳號", "真實姓名", "服務單位", "目前住址", + "連絡電話", "電子郵件信箱", NULL + }; + char *reason[REJECT_REASONS+1] = { + "輸入真實姓名", + "詳填「(畢業)學校及『系』『級』」或「服務單位(含所屬縣市及職稱)」", + "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", + "詳填連絡電話 (含區域碼, 中間不用加 '-', '(', ')'等符號", + "精確並完整填寫註冊申請表", + "用中文填寫申請單", + NULL + }; + char *autoid = "AutoScan"; + userec_t muser; + FILE *fn, *fout, *freg; + char fdata[6][STRLEN]; + char fname[STRLEN] = "", buf[STRLEN]; + char ans[4], *ptr, *uid; + int n = 0, unum = 0, tid = 0; + int nSelf = 0, nAuto = 0; + + uid = cuser.userid; + move(2, 0); + + fn = pull_regform(regfile, fname, -1); + if (!fn) + return -1; + + while( fgets(genbuf, STRLEN, fn) ){ + memset(fdata, 0, sizeof(fdata)); + do { + if( genbuf[0] == '-' ) + break; + if ((ptr = (char *)strstr(genbuf, ": "))) { + *ptr = '\0'; + for (n = 0; field[n]; n++) { + if (strcmp(genbuf, field[n]) == 0) { + strlcpy(fdata[n], ptr + 2, sizeof(fdata[n])); + if ((ptr = (char *)strchr(fdata[n], '\n'))) + *ptr = '\0'; + } + } + } + } while( fgets(genbuf, STRLEN, fn) ); + tid ++; + + if ((unum = getuser(fdata[0], &muser)) == 0) { + move(2, 0); + clrtobot(); + outs("系統錯誤,查無此人\n\n"); + for (n = 0; field[n]; n++) + prints("%s : %s\n", finfo[n], fdata[n]); + pressanykey(); + } else { + if (automode) + uid = autoid; + + if ((!automode || !auto_scan(fdata, ans))) { + uid = cuser.userid; + + move(1, 0); + clrtobot(); + prints("帳號位置 : %d\n", unum); + user_display(&muser, 1); + move(14, 0); + prints(ANSI_COLOR(1;32) "------------- " + "請站長嚴格審核使用者資料,這是第 %d 份" + "------------" ANSI_RESET "\n", tid); + prints(" %-12s: %s\n", finfo[0], fdata[0]); +#ifdef FOREIGN_REG + prints("0.%-12s: %s%s\n", finfo[1], fdata[1], + muser.uflag2 & FOREIGN ? " (外籍)" : ""); +#else + prints("0.%-12s: %s\n", finfo[1], fdata[1]); +#endif + for (n = 2; field[n]; n++) { + prints("%d.%-12s: %s\n", n - 1, finfo[n], fdata[n]); + } + if (muser.userlevel & PERM_LOGINOK) { + ans[0] = getkey("此帳號已經完成註冊, " + "更新(Y/N/Skip)?[N] "); + if (ans[0] != 'y' && ans[0] != 's') + ans[0] = 'd'; + } else { + if (search_ulist(unum) == NULL) + { + move(b_lines, 0); clrtoeol(); + outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] "); + // FIXME if the user got online here + ans[0] = igetch(); + } + else + ans[0] = 's'; + ans[0] = tolower(ans[0]); + if (ans[0] != 'y' && ans[0] != 'n' && + ans[0] != 'q' && ans[0] != 'd' && + !('0' <= ans[0] && ans[0] < ('0' + REJECT_REASONS))) + ans[0] = 's'; + ans[1] = 0; + } + nSelf++; + } else + nAuto++; + + switch (ans[0]) { + case 'q': + if ((freg = fopen(regfile, "a"))) { + for (n = 0; field[n]; n++) + fprintf(freg, "%s: %s\n", field[n], fdata[n]); + fprintf(freg, "----\n"); + while (fgets(genbuf, STRLEN, fn)) + fputs(genbuf, freg); + fclose(freg); + } + case 'd': + break; + + case '0': case '1': case '2': + case '3': case '4': case '5': + /* please confirm match REJECT_REASONS here */ + case 'n': + if (ans[0] == 'n') { + int nf = 0; + move(8, 0); + clrtobot(); + outs("請提出退回申請表原因,按 取消\n"); + for (n = 0; n < REJECT_REASONS; n++) + prints("%d) 請%s\n", n, reason[n]); + outs("\n"); // preserved for prompt + for (nf = 0; field[nf]; nf++) + prints("%s: %s\n", finfo[nf], fdata[nf]); + } else + buf[0] = ans[0]; + + if (ans[0] != 'n' || + getdata(9 + n, 0, "退回原因: ", buf, 60, DOECHO)) + if ((buf[0] - '0') >= 0 && (buf[0] - '0') < n) { + int i; + fileheader_t mhdr; + char title[128], buf1[80]; + FILE *fp; + + sethomepath(buf1, muser.userid); + stampfile(buf1, &mhdr); + strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner)); + strlcpy(mhdr.title, "[註冊失敗]", TTLEN); + mhdr.filemode = 0; + sethomedir(title, muser.userid); + if (append_record(title, &mhdr, sizeof(mhdr)) != -1) { + char rejfn[PATHLEN]; + fp = fopen(buf1, "w"); + + for(i = 0; buf[i] && i < sizeof(buf); i++){ + if (buf[i] >= '0' && buf[i] < '0'+n) + { + fprintf(fp, "[退回原因] 請%s\n", + reason[buf[i] - '0']); + } + } + + fclose(fp); + + // build reject file + sethomefile(rejfn, muser.userid, FN_REJECT_NOTIFY); + Copy(buf1, rejfn); + } + if ((fout = fopen(FN_REGISTER_LOG, "a"))) { + for (n = 0; field[n]; n++) + fprintf(fout, "%s: %s\n", field[n], fdata[n]); + fprintf(fout, "Date: %s\n", Cdate(&now)); + fprintf(fout, "Rejected: %s [%s]\n----\n", + uid, buf); + fclose(fout); + } + break; + } + move(10, 0); + clrtobot(); + outs("取消退回此註冊申請表"); + /* no break? */ + + case 's': + if ((freg = fopen(regfile, "a"))) { + for (n = 0; field[n]; n++) + fprintf(freg, "%s: %s\n", field[n], fdata[n]); + fprintf(freg, "----\n"); + fclose(freg); + } + break; + + default: + outs("以下使用者資料已經更新:\n"); + mail_muser(muser, "[註冊成功\囉]", "etc/registered"); + +#if FOREIGN_REG_DAY > 0 + if(muser.uflag2 & FOREIGN) + mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); +#endif + + muser.userlevel |= (PERM_LOGINOK | PERM_POST); + strlcpy(muser.realname, fdata[1], sizeof(muser.realname)); + strlcpy(muser.address, fdata[3], sizeof(muser.address)); + strlcpy(muser.email, fdata[5], sizeof(muser.email)); + snprintf(genbuf, sizeof(genbuf), "%s:%s:%s", + fdata[4], fdata[2], uid); + strlcpy(muser.justify, genbuf, sizeof(muser.justify)); + + passwd_update(unum, &muser); + // XXX TODO notify users? + sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm + + sethomefile(buf, muser.userid, FN_JUSTIFY); + log_file(buf, LOG_CREAT, genbuf); + + if ((fout = fopen(FN_REGISTER_LOG, "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, FN_JUSTIFY_WAIT); + unlink(genbuf); + break; + } + } + } + + fclose(fn); + unlink(fname); + + clear(); move(5, 0); + prints("您審了 %d 份註冊單,AutoScan 審了 %d 份", nSelf, nAuto); + pressanykey(); + return (0); +} + +///////////////////////////////////////////////////////////////////////////// +// New Regform UI +///////////////////////////////////////////////////////////////////////////// + +// #define REGFORM_DISABLE_ONLINE_USER +// #define FORMS_IN_PAGE (10) + +int +handle_register_form(const char *regfile, int dryrun) +{ + int unum = 0; + int yMsg = FORMS_IN_PAGE*2+1; + FILE *fp = NULL; + userec_t muser; + RegformEntry forms [FORMS_IN_PAGE]; + char ans [FORMS_IN_PAGE]; + char rejects[FORMS_IN_PAGE][REASON_LEN]; // reject reason length + char fname [PATHLEN] = ""; + char justify[REGLEN+1]; + char rsn [REASON_LEN]; + int cforms = 0, // current loaded forms + parsed = 0, // total parsed forms + ci = 0, // cursor index + ch = 0, // input key + i, blanks; + long fsz = 0, fpos = 0; + + // prepare reg tickets + if (dryrun) + { + // directly open regfile to try + fp = fopen(regfile, "rt"); + } else { + fp = pull_regform(regfile, fname, -1); + } + + if (!fp) + return 0; + + // retreieve file info + fpos = ftell(fp); + fseek(fp, 0, SEEK_END); + fsz = ftell(fp); + fseek(fp, fpos, SEEK_SET); + if (!fsz) fsz = 1; + + while (ch != 'q') + { + // initialize and prepare + memset(ans, 0, sizeof(ans)); + memset(rejects, 0, sizeof(rejects)); + memset(forms, 0, sizeof(forms)); + cforms = 0; + + // load forms + while (cforms < FORMS_IN_PAGE && load_regform_entry(&forms[cforms], fp)) + cforms++, parsed ++; + + // if no more forms then leave. + // TODO what if regform error? + if (cforms < 1) + break; + + // adjust cursor if required + if (ci >= cforms) + ci = cforms-1; + + // display them all. + clear(); + for (i = 0; i < cforms; i++) + { + // fetch user information + memset(&muser, 0, sizeof(muser)); + unum = getuser(forms[i].userid, &muser); + forms[i].exist = unum ? 1 : 0; + if (unum) forms[i].online = search_ulist(unum) ? 1 : 0; + + // if already got login level, delete by default. + if (!unum) + ans[i] = 'd'; + else { + if (muser.userlevel & PERM_LOGINOK) + ans[i] = 'd'; +#ifdef REGFORM_DISABLE_ONLINE_USER + else if (forms[i].online) + ans[i] = 's'; +#endif // REGFORM_DISABLE_ONLINE_USER + } + + // print + move(i*2, 0); + prints(" %2d%s %s%-12s " ANSI_RESET, + i+1, + (unum == 0) ? ANSI_COLOR(1;31) "D" : + ( (muser.userlevel & PERM_LOGINOK) ? + ANSI_COLOR(1;33) "Y" : +#ifdef REGFORM_DISABLE_ONLINE_USER + forms[i].online ? "s" : +#endif + "."), + forms[i].online ? ANSI_COLOR(1;35) : ANSI_COLOR(1), + forms[i].userid); + + prints( ANSI_COLOR(1;31) "%19s " + ANSI_COLOR(1;32) "%-40s" ANSI_RESET"\n", + forms[i].name, forms[i].career); + + move(i*2+1, 0); + prints(" %s %-50s%20s\n", + (muser.userlevel & PERM_NOREGCODE) ? + ANSI_COLOR(1;31) "T" ANSI_RESET : " ", + forms[i].addr, forms[i].phone); + } + + // display page info + { + char msg[STRLEN]; + fpos = ftell(fp); + if (fpos > fsz) fsz = fpos*10; + snprintf(msg, sizeof(msg), + "%s 已顯示 %d 份註冊單 (%2d%%) ", + dryrun? "(測試模式)" : "", + parsed, (int)(fpos*100/fsz)); + prints(ANSI_COLOR(7) "\n%78s" ANSI_RESET "\n", msg); + } + + // handle user input + prompt_regform_ui(); + ch = 0; + while (ch != 'q' && ch != ' ') { + ch = cursor_key(ci*2, 0); + switch (ch) + { + // nav keys + case KEY_UP: + case 'k': + if (ci > 0) ci--; + break; + + case KEY_DOWN: + case 'j': + ch = 'j'; // go next + break; + + // quick nav (assuming to FORMS_IN_PAGE=10) + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + ci = ch - '1'; + if (ci >= cforms) ci = cforms-1; + break; + case '0': + ci = 10-1; + if (ci >= cforms) ci = cforms-1; + break; + + /* + case KEY_HOME: ci = 0; break; + case KEY_END: ci = cforms-1; break; + */ + + // abort + case KEY_END: + case 'q': + ch = 'q'; + if (getans("確定要離開了嗎? (本頁變更將不會儲存) [y/N]: ") != 'y') + { + prompt_regform_ui(); + ch = 0; + continue; + } + break; + + // prepare to go next page + case KEY_PGDN: + case ' ': + ch = ' '; + + // solving blank (undecided entries) + for (i = 0, blanks = 0; i < cforms; i++) + if (ans[i] == 0) blanks ++; + + if (!blanks) + break; + + // have more blanks + ch = getans("尚未指定的 %d 個項目要: (S跳過/y通過/n拒絕/e繼續編輯): ", + blanks); + + if (ch == 'e') + { + prompt_regform_ui(); + ch = 0; + continue; + } + if (ch == 'y') { + // do nothing. + } else if (ch == 'n') { + // query reject reason + resolve_reason(rsn, yMsg); + if (*rsn == 0) + ch = 's'; + } else ch = 's'; + + // filling answers + for (i = 0; i < cforms; i++) + { + if (ans[i] != 0) + continue; + ans[i] = ch; + if (ch != 'n') + continue; + strlcpy(rejects[i], rsn, REASON_LEN); + } + + ch = ' '; // go to page mode! + break; + + // function keys + case 'y': // accept +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + case 's': // skip + case 'd': // delete + case KEY_DEL: //delete + if (ch == KEY_DEL) ch = 'd'; + + grayout(ci*2, ci*2+1, GRAYOUT_DARK); + move_ansi(ci*2, 4); outc(ch); + ans[ci] = ch; + ch = 'j'; // go next + break; + + case 'u': // undo +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + grayout(ci*2, ci*2+1, GRAYOUT_NORM); + move_ansi(ci*2, 4); outc('.'); + ans[ci] = 0; + ch = 'j'; // go next + break; + + case 'n': // reject +#ifdef REGFORM_DISABLE_ONLINE_USER + if (forms[ci].online) + { + vmsg("暫不開放審核在線上使用者。"); + break; + } +#endif + // query for reason + resolve_reason(rejects[ci], yMsg); + prompt_regform_ui(); + + if (!rejects[ci][0]) + break; + + move(yMsg, 0); + prints("退回 %s 註冊單原因:\n %s\n", forms[ci].userid, rejects[ci]); + + // do reject + grayout(ci*2, ci*2+1, GRAYOUT_DARK); + move_ansi(ci*2, 4); outc(ch); + ans[ci] = ch; + ch = 'j'; // go next + + break; + } // switch(ch) + + // change cursor + if (ch == 'j' && ++ci >= cforms) + ci = cforms -1; + } // while(ch != QUIT/SAVE) + + // if exit, we still need to skip all read forms + if (ch == 'q') + { + for (i = 0; i < cforms; i++) + ans[i] = 's'; + } + + // page complete (save). + assert(ch == ' ' || ch == 'q'); + + // save/commit if required. + if (dryrun) + { + // prmopt for debug + clear(); + stand_title("測試模式"); + outs("您正在執行測試模式,所以剛審的註冊單並不會生效。\n" + "下面列出的是剛才您審完的結果:\n\n"); + + for (i = 0; i < cforms; i++) + { + if (ans[i] == 'y') + snprintf(justify, sizeof(justify), // build justify string + "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); + + prints("%2d. %-12s - %c %s\n", i+1, forms[i].userid, ans[i], + ans[i] == 'n' ? rejects[i] : + ans[i] == 'y' ? justify : ""); + } + if (ch != 'q') + pressanykey(); + } + else + { + // real functionality + for (i = 0; i < cforms; i++) + { + if (ans[i] == 'y') + { + // build justify string + snprintf(justify, sizeof(justify), + "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); + + regform_accept(forms[i].userid, justify); + // log form to FN_REGISTER_LOG + append_regform(&forms[i], FN_REGISTER_LOG, + "Approved", cuser.userid, NULL); + } + else if (ans[i] == 'n') + { + regform_reject(forms[i].userid, rejects[i]); + // log form to FN_REGISTER_LOG + append_regform(&forms[i], FN_REGISTER_LOG, + "Rejected", cuser.userid, rejects[i]); + } + else if (ans[i] == 's') + { + // append form back to fn_register + append_regform(&forms[i], fn_register, + NULL, NULL, NULL); + } + } + } // !dryrun + + } // while (ch != 'q') + + // cleaning left regforms + if (!dryrun) + { + pump_regform(regfile, fp); + fclose(fp); + unlink(fname); + } else { + // directly close file should be OK. + fclose(fp); + } + + return 0; +} + +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不審/Exp新界面)?[N] ", + ans, sizeof(ans), LCECHO); + if (ans[0] == 'a') + scan_register_form(fn_register, 1, NULL); + else if (ans[0] == 'y') + scan_register_form(fn_register, 0, NULL); + else if (ans[0] == 'e') + { +#ifdef EXP_ADMIN_REGFORM_DRYRUN + int dryrun = 0; + if (getans("你要進行純測試(T)還是真的執行審核(y)?") == 'y') + { + vmsg("進入實際執行模式,所有審核動作都是真的。"); + dryrun = 0; + } else { + vmsg("測試模式。"); + dryrun = 1; + } + handle_register_form(fn_register, dryrun); +#else + // run directly. + handle_register_form(fn_register, 0); +#endif + } + + return 0; +} + +int +cat_register(void) +{ + if (system("cat register.new.tmp >> register.new") == 0 && + unlink("register.new.tmp") == 0) + vmsg("OK 嚕~~ 繼續去奮鬥吧!!"); + else + vmsg("沒辦法CAT過去呢 去檢查一下系統吧!!"); + return 0; +} + +/* vim:sw=4 + */ diff --git a/console/reversi.c b/console/reversi.c new file mode 100644 index 00000000..ea64f35a --- /dev/null +++ b/console/reversi.c @@ -0,0 +1,513 @@ +/* $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) +{ + char buf[ANSILINELEN] = ""; + const int nStep = info->history.used; + int i, x, y; + + getyx(&y, &x); + for (i = 2; i <= 21; i++) + { + move(i, 0); + inansistr(buf, sizeof(buf)-1); + fprintf(fp, "%s\n", buf); + } + move(y, x); + + fprintf(fp, "\n"); + fprintf(fp, "按 z 可進入打譜模式\n"); + fprintf(fp, "\n"); + + fprintf(fp, "\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("\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("\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/console/screen.c b/console/screen.c new file mode 100644 index 00000000..8b518070 --- /dev/null +++ b/console/screen.c @@ -0,0 +1,819 @@ +/* $Id$ */ +#include "bbs.h" + +#if !defined(USE_PFTERM) + +#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; +static unsigned char _typeahead = 1; + +#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; + } +} + +int +resizeterm(int w, int h) +{ + screenline_t *new_picture; + + /* 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 0; + } + memcpy(new_picture, big_picture, t_lines * sizeof(screenline_t)); + free(big_picture); + big_picture = new_picture; + return 1; + } + return 0; +} + +void +move(int y, int x) +{ + if (y < 0) y = 0; + if (y >= t_lines) y = t_lines -1; + if (x < 0) x = 0; + if (x >= ANSILINELEN) x = ANSILINELEN -1; + // assert(y>=0); + // assert(x>=0); + cur_col = x; + cur_ln = y; +} + +void +move_ansi(int y, int x) +{ + // take ANSI length in consideration + register screenline_t *slp; + if (y < 0) y = 0; + if (y >= t_lines) y = t_lines -1; + if (x < 0) x = 0; + if (x >= ANSILINELEN) x = ANSILINELEN -1; + + cur_ln = y; + cur_col = x; + + if (y >= scr_lns || x < 1) + return; + + slp = &big_picture[y]; + if (slp->len < 1) + return; + + slp->data[slp->len] = 0; + x += (strlen((char*)slp->data) - strlen_noansi((char*)slp->data)); + cur_col = x; +} + +void +getyx(int *y, int *x) +{ + *y = cur_ln; + *x = cur_col; +} + +void +getyx_ansi(int *py, int *px) +{ + // take ANSI length in consideration + register screenline_t *slp; + int y = cur_ln, x = cur_col; + char c = 0; + + if (y < 0) y = 0; + if (y >= t_lines) y = t_lines -1; + if (x < 0) x = 0; + if (x >= ANSILINELEN) x = ANSILINELEN -1; + + *py = y; *px = x; + + if (y >= scr_lns || x < 1) + return; + + slp = &big_picture[y]; + if (slp->len < 1) + return; + c = slp->data[x]; + *px += (strlen((char*)slp->data) - strlen_noansi((char*)slp->data)); + slp->data[x] = c; +} + +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 +redrawwin(void) +{ + register screenline_t *bp; + register int i, j; + int 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); + +#ifdef DBCSAWARE + if (!(bp->mode & STANDOUT) && + (cuser.uflag & DBCS_NOINTRESC) && + DBCS_RemoveIntrEscape(bp->data, &len)) + { + // if anything changed, dirty whole line. + bp->len = len; + } +#endif // DBCSAWARE + + 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(); +} + +int +typeahead(int fd) +{ + switch(fd) + { + case TYPEAHEAD_NONE: + _typeahead = 0; + break; + case TYPEAHEAD_STDIN: + _typeahead = 1; + break; + default: // shall never reach here + assert(NULL); + break; + } + return 0; +} + +void +refresh(void) +{ + if (num_in_buf() && _typeahead) + return; + doupdate(); +} + +void +doupdate(void) +{ + /* TODO remove unnecessary refresh() call, to save CPU time */ + register screenline_t *bp = big_picture; + register int i, j; + int len; + if ((docls) || (abs(scrollcnt) >= (scr_lns - 3))) { + redrawwin(); + return; + } + if (scrollcnt < 0) { + if (!scrollrevlen) { + redrawwin(); + 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); + +#ifdef DBCSAWARE + if (!(bp->mode & STANDOUT) && + (cuser.uflag & DBCS_NOINTRESC) && + DBCS_RemoveIntrEscape(bp->data, &len)) + { + // if anything changed, dirty whole line. + bp->len = len; + bp->smod = 0; bp->emod = len; + } +#endif // DBCSAWARE + +#if 0 + // disabled now, bugs: + // (1) input number (goto) in bbs list (search_num) + // (2) some empty lines becomes weird (eg, b_config) + // + // more effort to determine ANSI smod + if (bp->smod > 0) + { + int iesc; + for (iesc = bp->smod-1; iesc >= 0; iesc--) + { + if (bp->data[iesc] == ESC_CHR) + { + bp->smod = 0;// iesc; + bp->emod =len -1; + break; + } + } + } +#endif + + 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; imode = 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 == 0) // TODO and contains ANSI + { + // workaround poor ANSI issue + size_t sz = (slp->len > slp->oldlen) ? slp->len : slp->oldlen; + sz = (sz < ANSILINELEN) ? sz : ANSILINELEN; + memset(slp->data, ' ', sz); + slp->len = 0; + return; + } + */ + + 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; +} + + +void newwin (int nlines, int ncols, int y, int x) +{ + int i=0, oy, ox; + getyx(&oy, &ox); + + while (nlines-- > 0) + { + move_ansi(y++, x); + for (i = 0; i < ncols; i++) + outc(' '); + } + move(oy, ox); +} + +/** + * 從目前的行數(scr_ln) clear 到第 line 行 + */ +void +clrtoln(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) +{ + clrtoln(scr_lns); +} + +void +outc(unsigned char c) +{ + register screenline_t *slp = GetCurrentLine(); + register int i; + + // 0xFF is invalid for most cases (even DBCS), + if (c == 0xFF || c == 0x00) + return; + + 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) +{ + if (!str) + return; + while (*str) { + outc(*str++); + } +} + +void +outns(const char *str, int n) +{ + if (!str) + return; + while (*str && n-- > 0) { + outc(*str++); + } +} + +void +outstr(const char *str) +{ + // XXX TODO cannot prepare DBCS-ready environment? + + outs(str); +} + +void +addch(unsigned char c) +{ + outc(c); +} + +void +addstr(const char *s) +{ + outs(s); +} + +void +addnstr(const char *s, int n) +{ + outns(s, n); +} + +void +addstring(const char *s) +{ + outs(s); +} + + +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); + } +} + +// readback +int +instr(char *str) +{ + register screenline_t *slp = GetCurrentLine(); + *str = 0; + if (!slp) + return 0; + slp->data[slp->len] = 0; + strip_ansi(str, (char*)slp->data, STRIP_ALL); + return strlen(str); +} + +int +innstr(char *str, int n) +{ + register screenline_t *slp = GetCurrentLine(); + char buf[ANSILINELEN]; + *str = 0; + if (!slp) + return 0; + slp->data[slp->len] = 0; + strip_ansi(buf, (char*)slp->data, STRIP_ALL); + buf[ANSILINELEN] = 0; + strlcpy(str, buf, n); + return strlen(str); +} + +int +inansistr(char *str, int n) +{ + register screenline_t *slp = GetCurrentLine(); + *str = 0; + if (!slp) + return 0; + slp->data[slp->len] = 0; + strlcpy(str, (char*)slp->data, n); + return strlen(str); +} + +// level: +// -1 - bold out +// 0 - dark text +// 1 - text +// 2 - no highlight (not implemented) +void +grayout(int y, int end, int level) +{ + register screenline_t *slp = NULL; + char buf[ANSILINELEN]; + int i = 0; + + if (y < 0) y = 0; + if (end > b_lines) end = b_lines; + + // TODO change to y <= end someday + // loop lines + for (; y <= end; y ++) + { + // modify by scroll + i = y + roll; + if (i < 0) + i += scr_lns; + else if (i >= scr_lns) + i %= scr_lns; + + slp = &big_picture[i]; + + if (slp->len < 1) + continue; + + if (slp->len >= ANSILINELEN) + slp->len = ANSILINELEN -1; + + // tweak slp + slp->data[slp->len] = 0; + slp->mode &= ~STANDOUT; + slp->mode |= MODIFIED; + slp->oldlen = 0; + slp->sso = slp->eso = 0; + slp->smod = 0; + + // make slp->data a pure string + for (i=0; ilen; i++) + { + if (!slp->data[i]) + slp->data[i] = ' '; + } + + slp->len = strip_ansi(buf, (char*)slp->data, STRIP_ALL); + buf[slp->len] = 0; + + switch(level) + { + case GRAYOUT_DARK: // dark text + case GRAYOUT_BOLD:// bold text + // basically, in current system slp->data will + // not exceed t_columns. buffer overflow is impossible. + // but to make it more robust, let's quick check here. + // of course, t_columns should always be far smaller. + if (strlen((char*)slp->data) > t_columns) + slp->data[t_columns] = 0; + strcpy((char*)slp->data, + level < 0 ? ANSI_COLOR(1) : ANSI_COLOR(1;30;40)); + strcat((char*)slp->data, buf); + strcat((char*)slp->data, ANSI_RESET ANSI_CLRTOEND); + slp->len = strlen((char*)slp->data); + break; + + case GRAYOUT_NORM: // Plain text + memcpy(slp->data, buf, slp->len + 1); + break; + } + slp->emod = slp->len -1; + } +} + +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 scr_dump(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 scr_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); + redrawwin(); +} + +#endif // !defined(USE_PFTERM) + +/* vim:sw=4 + */ diff --git a/console/stuff.c b/console/stuff.c new file mode 100644 index 00000000..2349a68d --- /dev/null +++ b/console/stuff.c @@ -0,0 +1,828 @@ +/* $Id$ */ +#include "bbs.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 const char * const str_dotdir = FN_DIR; + +/* 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; +} + +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 (!isalpha(id[0])) + return 0; + for (i = 1; i < len; i++) + if (!isalnum(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; +} + +int +belong(const char *filelist, const char *key) +{ + return file_exist_record(filelist, key); +} + + +#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; + time_t t; + + 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); + // signed: limited on (2037, ...) + // unsigned: limited on (..., 1970) + // let's restrict inside the boundary. + } while ((endtime.tm_year = atoi(yn) - 1900) < 70 || endtime.tm_year > 135); + 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); + t = mktime(&endtime); + /* saturation check */ + if(t < 0) + t = 1; + if(t > INT_MAX) + t = INT_MAX; + return t; +} + +// synchronize 'now' +void syncnow(void) +{ +#ifdef OUTTA_TIMER + now = SHM->GV2.e.now; +#else + now = time(0); +#endif +} + +#endif + + +#ifndef _BBS_UTIL_C_ +/* 這一區都是有關於畫面處理的, 故 _BBS_UTIL_C_ 不須要 */ + +#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_RESET); + do { + ch = igetch(); + 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[3]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg , sizeof(msg), 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 , sizeof(msg), 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) + + // what is 200/1431/506/201? +static const char* msg_pressanykey_trail = + ANSI_COLOR(33;46) " [按任意鍵繼續] " 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 - pad -2 ; + /* pad is now those left . */ + if (pad > 0) + { + for (i = 0; i <= pad-2; i += 2) + outs("▄"); + if (i == pad-1) + outs(" "); + } + outs(ANSI_RESET); + } 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 { + i = igetch(); + } 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), 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: SHOWFILE_*, see modes.h + * @return 失敗傳回 0,否則為 1。 + * 2 表示有 PttPrints 碼 + */ +int +show_file(const char *filename, int y, int lines, int mode) +{ + FILE *fp; + char buf[1024]; + int ret = 1; + int strpmode = STRIP_ALL; + + if (mode & SHOWFILE_ALLOW_COLOR) + strpmode = ONLY_COLOR; + if (mode & SHOWFILE_ALLOW_MOVE) + strpmode = NO_RELOAD; + + if (y >= 0) + move(y, 0); + clrtoln(lines + y); + if ((fp = fopen(filename, "r"))) { + while (fgets(buf, sizeof(buf), fp) && lines--) + { + move(y++, 0); + if (mode == SHOWFILE_RAW) + { + outs(buf); + } + else if ((mode & SHOWFILE_ALLOW_STAR) && (strstr(buf, ESC_STR "*") != NULL)) + { + // because Ptt_prints escapes are not so often, + // let's try harder to detect it. + outs(Ptt_prints(buf, sizeof(buf), strpmode)); + ret = 2; + } else { + // ESC is very common... + strip_ansi(buf, buf, strpmode); + outs(buf); + } + } + fclose(fp); + outs(ANSI_RESET); // prevent some broken Welcome file + } else + return 0; + return ret; +} + +void +bell(void) +{ + char c; + + c = Ctrl('G'); + write(1, &c, 1); +} + +int +search_num(int ch, int max) +{ + int clen = 1, y = b_lines - msg_occupied; + char genbuf[10]; + + genbuf[0] = ch; genbuf[1] = 0; + clen = getdata_buf(y, 0, + " 跳至第幾項: ", genbuf, sizeof(genbuf)-1, NUMECHO); + + move(y, 0); clrtoeol(); + genbuf[clen] = '\0'; + 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 , sizeof(msg), fmt, ap); + va_end(ap); + + sethomefile(filename, cuser.userid, "USERLOG"); + return log_filef(filename, LOG_CREAT, "%s: %s %s", cuser.userid, msg, Cdate(&now)); +} + +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, SHOWFILE_ALLOW_ALL); +#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 +DBCS_StringHash(const char *s) +{ + return fnv1a_32_dbcs_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); +} + +/* AIDS */ +aidu_t fn2aidu(char *fn) +{ + aidu_t aidu = 0; + aidu_t type = 0; + aidu_t v1 = 0; + aidu_t v2 = 0; + char *sp = fn; + + if(fn == NULL) + return 0; + + switch(*(sp ++)) + { + case 'M': + type = 0; + break; + case 'G': + type = 1; + break; + default: + return 0; + break; + } + + if(*(sp ++) != '.') + return 0; + v1 = strtoul(sp, &sp, 10); + if(sp == NULL) + return 0; + if(*sp != '.' || *(sp + 1) != 'A') + return 0; + sp += 2; + if(*(sp ++) == '.') + { + v2 = strtoul(sp, &sp, 16); + if(sp == NULL) + return 0; + } + aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffff) << 12) | (v2 & 0xfff); + + return aidu; +} + +/* IMPORTANT: + * size of buf must be at least 8+1 bytes + */ +char *aidu2aidc(char *buf, aidu_t aidu) +{ + const char aidu2aidc_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"; + const int aidu2aidc_tablesize = sizeof(aidu2aidc_table) - 1; + char *sp = buf + 8; + aidu_t v; + + *(sp --) = '\0'; + while(sp >= buf) + { + /* FIXME: 能保證 aidu2aidc_tablesize 是 2 的冪次的話, + 這裡可以改用 bitwise operation 做 */ + v = aidu % aidu2aidc_tablesize; + aidu = aidu / aidu2aidc_tablesize; + *(sp --) = aidu2aidc_table[v]; + } + return buf; +} + +/* IMPORTANT: + * size of fn must be at least FNLEN bytes + */ +char *aidu2fn(char *fn, aidu_t aidu) +{ + aidu_t type = ((aidu >> 44) & 0xf); + aidu_t v1 = ((aidu >> 12) & 0xffffffff); + aidu_t v2 = (aidu & 0xfff); + + if(fn == NULL) + return NULL; + snprintf(fn, FNLEN - 1, "%c.%d.A.%03X", ((type == 0) ? 'M' : 'G'), (unsigned int)v1, (unsigned int)v2); + fn[FNLEN - 1] = '\0'; + return fn; +} + +aidu_t aidc2aidu(char *aidc) +{ + char *sp = aidc; + aidu_t aidu = 0; + + if(aidc == NULL) + return 0; + + while(*sp != '\0' && /* ignore trailing spaces */ *sp != ' ') + { + aidu_t v = 0; + /* FIXME: 查表法會不會比較快? */ + if(*sp >= '0' && *sp <= '9') + v = *sp - '0'; + else if(*sp >= 'A' && *sp <= 'Z') + v = *sp - 'A' + 10; + else if(*sp >= 'a' && *sp <= 'z') + v = *sp - 'a' + 36; + else if(*sp == '-') + v = 62; + else if(*sp == '_') + v = 63; + else + return 0; + aidu <<= 6; + aidu |= (v & 0x3f); + sp ++; + } + + return aidu; +} + +int search_aidu(char *bfile, aidu_t aidu) +{ + char fn[FNLEN]; + int fd; + fileheader_t fhs[64]; + int len, i; + int pos = 0; + int found = 0; + int lastpos = 0; + + if(aidu2fn(fn, aidu) == NULL) + return -1; + if((fd = open(bfile, O_RDONLY, 0)) < 0) + return -1; + + while(!found && (len = read(fd, fhs, sizeof(fhs))) > 0) + { + len /= sizeof(fileheader_t); + for(i = 0; i < len; i ++) + { + int l; + if(strcmp(fhs[i].filename, fn) == 0 || + ((aidu & 0xfff) == 0 && (l = strlen(fhs[i].filename)) > 6 && + strncmp(fhs[i].filename, fn, l) == 0)) + { + if(fhs[i].filemode & FILE_BOTTOM) + { + lastpos = pos; + } + else + { + found = 1; + break; + } + } + pos ++; + } + } + close(fd); + + return (found ? pos : (lastpos ? lastpos : -1)); +} +/* end of AIDS */ diff --git a/console/syspost.c b/console/syspost.c new file mode 100644 index 00000000..9ced9918 --- /dev/null +++ b/console/syspost.c @@ -0,0 +1,146 @@ +/* $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) +{ + char genbuf[(NUMPERMS+1) * STRLEN*2] = "", reason[30] = ""; + char title[TTLEN+1]; + char *s = genbuf; + int i, flag = 0; + + // generate log (warning: each line should <= STRLEN*2) + for (i = 0; i < NUMPERMS; i++) { + if (((oldperm >> i) & 1) != ((newperm >> i) & 1)) { + sprintf(s, " 站長" 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]); + s += strlen(s); + flag++; + } + } + + if (!flag) // nothing changed + return; + + clrtobot(); + clear(); + while (!getdata(5, 0, "請輸入理由以示負責:", + reason, sizeof(reason), DOECHO)); + sprintf(s, "\n " ANSI_COLOR(1;37) "站長%s修改權限理由是:%s" ANSI_RESET, + cuser.userid, reason); + + snprintf(title, sizeof(title), "[公安報告] 站長%s修改%s權限報告", + cuser.userid, userid); + + post_msg(GLOBAL_SECURITY, title, genbuf, "[系統安全局]"); +} + +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, "[" BBSMNAME "法院]"); + + 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, "[" BBSMNAME "法院]"); +} + +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/console/talk.c b/console/talk.c new file mode 100644 index 00000000..70bcdb97 --- /dev/null +++ b/console/talk.c @@ -0,0 +1,3654 @@ +/* $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 + +#define TALK_MAXCOL (78) +#define TALK_BUFLEN (TALK_MAXCOL+2) +typedef struct twpic { + unsigned short len; + unsigned char data[TALK_BUFLEN]; // bound to specific size +} twpic_t; + +typedef struct talkwin_t { + int curcol, curln; + int sline, eline; + twpic_t *big_picture; +} 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); +} + +int query_online(const char *userid) +{ + userinfo_t *uentp; + + if (!userid || !*userid) + return 0; + + if (!isalnum(*userid)) + return 0; + + if (strchr(userid, '.') || SHM->GV2.e.noonlineuser) + return 0; + + uentp = search_ulist_userid(userid); + + if (!uentp ||!isvisible(currutmp, uentp)) + return 0; + + return 1; +} + +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; iuinfo[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(); +} + +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 { + +#ifndef USE_PFTERM + // workaround poor terminal, made by in2. + move(8 + which, 28); + outs("123456789012345678901234567890"); +#endif // !USE_PFTERM + + 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) { + grayout(0, b_lines-5, GRAYOUT_DARK); + move(b_lines - 4, 0); + clrtobot(); + outs(ANSI_COLOR(1;33;45) "★水球整理程式 " ANSI_RESET "\n" + "提醒您: 可將水球存入信箱(M)後, 到【郵件選單】該信件前按 u,\n" + "系統會將水球紀錄重新整理後寄送給您唷! " ANSI_RESET "\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 + { + int i, iend = twin->eline - twin->sline; + region_scroll_up(twin->sline, twin->eline); + // also scroll up our buffer + for (i = 0; i < iend; i++) + { + memcpy(&twin->big_picture[i], &twin->big_picture[i+1], + sizeof(twpic_t)); + } + // zero last line + memset(&twin->big_picture[iend], 0, sizeof(twpic_t)); + } + move(twin->curln, twin->curcol); +} + +static int +iscompletedbcs(const unsigned char* str) +{ + int isdbcs = 0; + while (*str) + { + if (isdbcs == 1) isdbcs = 2; + else if ((unsigned char)*str > 0x80) isdbcs = 1; + else isdbcs = 0; + str++; + } + + return (isdbcs == 1) ? 0 : 1; +} + +static void +talk_refreshline(talkwin_t *twin) +{ + // dirty screen + twpic_t *line = twin->big_picture + (twin->curln - twin->sline); + int iscomplete = iscompletedbcs(line->data); + int len = strlen((char*)line->data); + + move(twin->curln, 0); + clrtoeol(); + if (!iscomplete) len--; + outs_n((char*)line->data, len); + if (!iscomplete) outc('?'); + move(twin->curln, twin->curcol); +} + +static void +do_talk_char(talkwin_t * twin, int ch, FILE *flog) +{ + twpic_t *line; + char buf[TALK_BUFLEN] = ""; + + line = twin->big_picture+(twin->curln - twin->sline); + + if (isprint2(ch)) { + + + if (line->len < TALK_MAXCOL) + move(twin->curln, twin->curcol); + else + do_talk_nextline(twin); + + // do_talk_nextline may change current line + line = twin->big_picture + (twin->curln -twin->sline); + memmove(line->data + twin->curcol+1, line->data + twin->curcol, + line->len - twin->curcol +1); + line->data[twin->curcol++] = ch; + line->data[++line->len] = 0; + + // dirty screen + talk_refreshline(twin); + + if (line->len < TALK_MAXCOL) + return; + + // max buffer, log it. + strlcpy(buf, (char *)line->data, line->len + 1); + + } else switch (ch) { + + case Ctrl('G'): + bell(); + return; + + case Ctrl('A'): + twin->curcol = 0; + move(twin->curln, twin->curcol); + return; + case Ctrl('E'): + twin->curcol = line->len; + move(twin->curln, twin->curcol); + return; + + + case Ctrl('K'): + line->len = twin->curcol; + memset(line->data+line->len, 0, TALK_MAXCOL - line->len); + move(twin->curln, twin->curcol); + clrtoeol(); + return; + case Ctrl('Y'): + twin->curcol = 0; + line->len = 0; + memset(line->data, 0, TALK_MAXCOL); + move(twin->curln, twin->curcol); + clrtoeol(); + return; + + case Ctrl('M'): + case Ctrl('J'): + strlcpy(buf, (char *)line->data, line->len + 1); + buf[line->len] = 0; + do_talk_nextline(twin); + break; + + case Ctrl('B'): + if (twin->curcol > 0) { + --(twin->curcol); +#ifdef DBCSAWARE + if(twin->curcol > 0 && twin->curcol < line->len && ISDBCSAWARE()) + { + if(getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) + twin->curcol --; + } +#endif + move(twin->curln, twin->curcol); + } + return; + + case Ctrl('F'): + if (twin->curcol < line->len) { + ++(twin->curcol); +#ifdef DBCSAWARE + if(twin->curcol < TALK_MAXCOL && twin->curcol < line->len && ISDBCSAWARE()) + { + if(getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) + twin->curcol++; + } +#endif + move(twin->curln, twin->curcol); + } + return; + + + case Ctrl('P'): + strlcpy(buf, (char *)line->data, line->len + 1); + if (twin->curln > twin->sline) { + --twin->curln; + line = twin->big_picture + (twin->curln -twin->sline); + } + if (twin->curcol > line->len) + twin->curcol = line->len; + +#ifdef DBCSAWARE + // curln may be changed. + if(twin->curcol > 0 && twin->curcol < line->len && + getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) + twin->curcol--; +#endif + move(twin->curln, twin->curcol); + // XXX break here (for log)? + break; + + case Ctrl('N'): + strlcpy(buf, (char *)line->data, line->len + 1); + if (twin->curln < twin->eline) { + ++twin->curln; + line = twin->big_picture + (twin->curln -twin->sline); + } + if (twin->curcol > line->len) + twin->curcol = line->len; + +#ifdef DBCSAWARE + // curln may be changed. + line = twin->big_picture + (twin->curln -twin->sline); + if(twin->curcol > 0 && twin->curcol < line->len && + getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) + twin->curcol--; +#endif + move(twin->curln, twin->curcol); + // XXX break here (for log)? + break; + + // complex data change + case Ctrl('H'): + case '\177': + if (twin->curcol > 0) + { + int delta = 1; + +#ifdef DBCSAWARE + if (twin->curcol > 1 && ISDBCSAWARE() && + getDBCSstatus(line->data, twin->curcol-1) == DBCS_TRAILING) + delta++; +#endif + memmove(line->data + twin->curcol-delta, line->data + twin->curcol, + line->len - twin->curcol +1); + line->len -= delta; + twin->curcol -= delta; + // dirty screen + talk_refreshline(twin); + } + return; + + case Ctrl('D'): + if (twin->curcol < line->len) + { + int delta = 1; + +#ifdef DBCSAWARE + if (ISDBCSAWARE() && + getDBCSstatus(line->data, twin->curcol) == DBCS_LEADING) + delta++; +#endif + memmove(line->data + twin->curcol, line->data + twin->curcol+delta, + line->len - twin->curcol -delta+1); + line->len -= delta; + memset(line->data + line->len, 0, TALK_MAXCOL - line->len); + // dirty screen + talk_refreshline(twin); + } + return; + } + + 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; + + // memory allocation + mywin.big_picture = (twpic_t*) malloc ( + sizeof(twpic_t) * (mywin.eline - mywin.sline +1)); + itswin.big_picture = (twpic_t*) malloc ( + sizeof(twpic_t) * (itswin.eline- itswin.sline +1)); + + // reset buffer + for (i = mywin.sline; i <= mywin.eline; i++) + { + mywin.big_picture[i - mywin.sline].len = 0; + memset(mywin.big_picture[i-mywin.sline].data, 0, TALK_BUFLEN); + } + for (i = itswin.sline; i <= itswin.eline; i++) + { + itswin.big_picture[i - itswin.sline].len = 0; + memset(itswin.big_picture[i-itswin.sline].data, 0, TALK_BUFLEN); + } + + clear(); + move(mywin.eline+1, 0); + outs(mid_line); + move(0, 0); + + add_io(fd, 0); + + while (1) { + ch = igetch(); + if (ch == I_OTHERDATA) { + // getyx(&y, &x); + datac = recv(fd, data, sizeof(data), 0); + if (datac <= 0) + break; + for (i = 0; i < datac; i++) + do_talk_char(&itswin, data[i], flog); + // move(y, x); + } 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)); + + fprintf(flog, "[%s]:\n", cuser.userid); + for (i = 0; i < mywin.eline - mywin.sline +1; i++) + if (mywin.big_picture[i].len) + fprintf(flog, "%.*s\n", mywin.big_picture[i].len, mywin.big_picture[i].data); + + fprintf(flog, "[%s]:\n", getuserid(currutmp->destuid)); + for (i = 0; i < itswin.eline - itswin.sline +1; i++) + if (itswin.big_picture[i].len) + fprintf(flog, "%.*s\n", itswin.big_picture[i].len, itswin.big_picture[i].data); + + fclose(flog); + redrawwin(); + more(fpath, NA); + + // force user decide how to deal with the log + while (1) { + getdata(b_lines - 1, 0, "清除(C) 移至備忘錄(M). (c/m)? ", + ans, sizeof(ans), LCECHO); + if (ans[0] == 'c' || ans[0] == 'm') + break; + move(b_lines-2, 0); + prints(ANSI_COLOR(0;1;3%d) "請正確輸入 c 或 m 的指令。" ANSI_RESET, + (now % 7)+1); + if (ans[0] == 0) outs("為避免誤按所以只 ENTER 是不行的。"); + } + + 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; + } + + // free memory + free(mywin.big_picture); + free(itswin.big_picture); + setutmpmode(XINFO); + redrawwin(); +} + +#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, ch; + pid_t pid; + char c; + char genbuf[4]; + unsigned char mode0 = currutmp->mode; + + 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<withme & (1<<(j+i))) + if(withme_str[j/2]) { + outs(withme_str[j/2]); + outc(' '); + } + outc('\n'); + } + } + move(4, 0); + outs("要和他(她) (T)談天(F)下五子棋" +#ifdef USE_CHICKEN_PK + "(P)鬥寵物" +#endif // USE_CHICKEN_PK + "(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; +#ifdef USE_CHICKEN_PK + case 'p': + { + userec_t xuser; + chicken_t xchk; + int error = 0; + + getuser(uin->userid, &xuser); + if (uin->lockmode == CHICKEN || currutmp->lockmode == CHICKEN) + error = 1; + else if (!load_chicken(cuser.userid, &xchk) || + !load_chicken(xuser.userid, &xchk)) + error = 2; + + if (error) { + vmsg(error == 2 ? "並非兩人都養寵物" : + "有一方的寵物正在使用中"); + return; + } + uin->sig = SIG_PK; + } + break; +#endif // USE_CHICKEN_PK + 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; +#ifdef USE_CHICKEN_PK + case SIG_PK: + chickenpk(msgsock); + break; +#endif + 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 + */ +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 當 friendtotalbrc_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<withme&=~(1<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 '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) { + give_money_ui(uentp->userid); + 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); + outs("系統提供 一般 進階 未來 三種模式\n" + "在切換後請正常下線再重新登入, 以確保結構正確\n"); + vmsgf( "目前切換到 %s 水球模式", wm[tmp]); + redrawall = redraw = 1; + } + break; + + case 'r': + if (HasUserPerm(PERM_LOGINOK)) { + if (curredit & EDIT_MAIL) { + /* deny reentrance, which may cause many problems */ + vmsg("你進入使用者列表前就已經在閱\讀信件了"); + } else { + // XXX in fact we should check size here... + // chkmailbox(); + m_read(); + setutmpmode(LUSERS); + } + redrawall = redraw = 1; + } + break; + + case 'N': + if (HasUserPerm(PERM_LOGINOK)) { + char tmp_nick[sizeof(cuser.nickname)]; + // XXX why do so many copy here? + // why not just use cuser.nickname? + // XXX old code forget to initialize. + // will changing to init everytime cause user + // complain? + + strlcpy(tmp_nick, currutmp->nickname, sizeof(cuser.nickname)); + + if (oldgetdata(1, 0, "新的暱稱: ", + tmp_nick, sizeof(tmp_nick), DOECHO) > 0) + { + strlcpy(cuser.nickname, tmp_nick, sizeof(cuser.nickname)); + 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 ){ + 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]; + int idle_type; + char idle_reason[sizeof(currutmp->chatid)]; + + + 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') + idle_type = genbuf[0] - '0'; + else + idle_type = 0; + + if (idle_type == 6) { + if (cuser.userlevel && getdata(b_lines - 1, 0, "發呆的理由:", idle_reason, sizeof(idle_reason), DOECHO)) { + strlcpy(currutmp->chatid, idle_reason, sizeof(currutmp->chatid)); + } else { + idle_type = 0; + } + } + currutmp->destuid = idle_type; + do { + move(b_lines - 2, 0); + clrtobot(); + prints("(鎖定螢幕)發呆原因: %s", (idle_type != 6) ? + IdleTypeTable[idle_type] : idle_reason); + 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("socket 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 && siguserid, 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; +#ifdef USE_CHICKEN_PK + case SIG_PK: + chickenpk(a); + break; +#endif // USE_CHICKEN_PK + 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(){ + char old_board[IDLEN + 1] = ""; + int canRead = 1; + + if (currutmp && currutmp->mode == EDITING) + return; + + // usually crashed as 'assert(currbid == brc_currbid)' + if (currboard[0]) { + strlcpy(old_board, currboard, IDLEN + 1); + currboard = "";// force enter_board + } + + if (enter_board(GLOBAL_NEWBIE) == 0) + canRead = 1; + + if (canRead) + Read(); + + if (canRead && old_board[0]) + enter_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("小主人使用上問題找不到小天使請到新手版(" GLOBAL_NEWBIE ")\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; + + scr_dump(&old_screen); + + TalkToAngel(); + + scr_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/console/telnet.c b/console/telnet.c new file mode 100644 index 00000000..332b3f3a --- /dev/null +++ b/console/telnet.c @@ -0,0 +1,338 @@ +/* + * piaip's simplified implementation of TELNET protocol + */ +#ifdef DEBUG +#define TELOPTS +#define TELCMDS +#endif + +#include "bbs.h" + +#ifdef DETECT_CLIENT +void UpdateClientCode(unsigned char c); // see mbbsd.c +#endif + +unsigned int telnet_handler(unsigned char c) ; +void telnet_init(void); +ssize_t tty_read(unsigned char *buf, size_t max); + +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; +} + +#ifdef DBG_OUTRPT +extern unsigned char fakeEscape; +#endif // DBG_OUTRPT + +/* 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) + UpdateClientCode(IAC); + UpdateClientCode(c); + } + } +#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: +#if 0 // def 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 allow IACs as input. + return 1; + + /* we don't want to process these. or maybe in future. */ + case BREAK: /* break */ +#ifdef DBG_OUTRPT + fakeEscape = !fakeEscape; + return NOP; +#endif + + 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: +#if 0 // def 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; +#if 0 // def 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) { + UpdateClientCode(iac_buf[0]); + if(w==80 && h==24) + UpdateClientCode(1); + else if(w==80) + UpdateClientCode(2); + else if(h==24) + UpdateClientCode(3); + else + UpdateClientCode(4); + UpdateClientCode(IAC); + UpdateClientCode(SE); + } +#endif + } + break; + + default: +#ifdef DETECT_CLIENT + if(cuser.userid[0]==0) { + int i; + for(i=0;i +#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/console/topsong.c b/console/topsong.c new file mode 100644 index 00000000..906dadbf --- /dev/null +++ b/console/topsong.c @@ -0,0 +1,72 @@ +/* $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; +} + + +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/console/user.c b/console/user.c new file mode 100644 index 00000000..d235d01f --- /dev/null +++ b/console/user.c @@ -0,0 +1,1452 @@ +/* $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, const 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, in; + unsigned int pbits = cuser.loginview; + + clear(); + move(4, 0); + for (i = 0; i < NUMVIEWFILE && loginview_file[i][0]; i++) + prints(" %c. %-20s %-15s \n", 'A' + i, + loginview_file[i][1], ((pbits >> i) & 1 ? "ˇ" : "X")); + in = i; + + clrtobot(); + while ((i = getkey("請按 [A-N] 切換設定,按 [Return] 結束:"))!='\r') + { + i = i - 'a'; + if (i >= in || 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); + if (currutmp && (currutmp->alerts & ALERT_PWD)) + currutmp->alerts &= ~ALERT_PWD; + + 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) + { + int prev = cuser.badpost--; + cuser.timeremovebadpost = now; + passwd_update(usernum, &cuser); + log_filef("log/cancelbadpost.log", LOG_CREAT, + "%s %s 刪除一篇劣文 (%d -> %d 篇)\n", + Cdate(&now), cuser.userid, prev, cuser.badpost); + } + 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 (%s滿18歲)\n", + get_num_records(genbuf, sizeof(fileheader_t)), + u->exmailbox, u->mobile, + u->year + 1900, u->month, u->day, over18 ? "已" : "未" + ); + +#ifdef ASSESS + prints(" 優 劣 文: 優:%d / 劣:%d\n", + u->goodpost, u->badpost); +#endif // ASSESS + + 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, "作者: [" BBSMNAME "警察局]\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\n" + "請到 " GLOBAL_LAW " 查詢相關法規資訊,並從主選單進入:\n" + "(P)lay【娛樂與休閒】=>(P)ay【Ptt量販店 】=> (1)ViolateLaw 繳罰單\n" + "以繳交罰單。\n", + ctime4(&now), police, crime, reason, result); + fclose(fp); + strcpy(fhdr.title, "[報告] 違法判決報告"); + strcpy(fhdr.owner, "[" BBSMNAME "警察局]"); + 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)) != NULL) + { + 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, + NO_MODMARK_FLAG , + COLORED_MODMARK, +#ifdef DBCSAWARE + DBCSAWARE_FLAG, + DBCS_NOINTRESC, +#endif + DEFBACKUP_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, + }; + + while ( !done ) { + int i = 0, ia = 0, ic = 0; /* general uflags */ + int iax = 0; /* extended flags */ + + clear(); + showtitle("個人化設定", "個人化設定"); + move(2, 0); + outs("您目前的個人化設定: "); + move(4, 0); + + /* print uflag options */ + for (i = 0; masks1[i]; i++, ia++) + { + clrtoeol(); + prints( ANSI_COLOR(1;36) "%c" ANSI_RESET + ". %-40s%s\n", + 'a' + ia, desc1[i], + (cuser.uflag & masks1[i]) ? + ANSI_COLOR(1;36) "是" ANSI_RESET : "否"); + } + ic = i; + /* print uflag2 options */ + for (i = 0; masks2[i]; i++, ia++) + { + clrtoeol(); + prints( ANSI_COLOR(1;36) "%c" ANSI_RESET + ". %-40s%s" ANSI_RESET "\n", + 'a' + ia, desc2[i], + (cuser.uflag2 & masks2[i]) ? + ANSI_COLOR(1;36) "是" ANSI_RESET : "否"); + } + /* extended stuff */ + { + char mindbuf[5]; + static const char *wm[] = + {"一般", "進階", "未來", ""}; + + prints("%c. %-40s%s\n", + '1' + iax++, + "水球模式", + wm[(cuser.uflag2 & WATER_MASK)]); + memcpy(mindbuf, &currutmp->mind, 4); + mindbuf[4] = 0; + prints("%c. %-40s%s\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 + + } + + grayout(1, b_lines-2, GRAYOUT_DARK); + move(b_lines-1, 0); clrtoeol(); + + if(dirty) + { + passwd_update(usernum, &cuser); + outs("設定已儲存。\n"); + } else { + outs("結束設定。\n"); + } + + redrawwin(); // in case we changed output pref (like DBCS) + vmsg("設定完成"); +} + + +void +uinfo_query(userec_t *u, int adminmode, int unum) +{ + userec_t x; + int i = 0, fail; + int ans; + char buf[STRLEN]; + char genbuf[200]; + int y = 0; + int perm_changed; + int mail_changed; + int money_changed; + int tokill = 0; + int changefrom = 0; + + fail = 0; + mail_changed = money_changed = perm_changed = 0; + + { + // verify unum + int xuid = getuser(u->userid, &x); + if (xuid != unum) + { + vmsg("系統錯誤: 使用者資料號碼 (unum) 不合。請至 " GLOBAL_BUGREPORT "報告。"); + return; + } + } + + 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(); + y = 1; + move(y++, 0); + outs(msg_uid); + outs(x.userid); + } + switch (ans) { + case 'c': + Customize(); + return; + + case 'm': + do { + getdata_str(y, 0, "電子信箱 [變動要重新認證]:", buf, + sizeof(x.email), DOECHO, x.email); + // TODO 這裡也要 emaildb_check +#ifdef USE_EMAILDB + if (isvalidemail(buf)) + { + int email_count = emaildb_check_email(buf, strlen(buf)); + if (email_count < 0) + vmsg("暫時不允許\ email 認證, 請稍後再試"); + else if (email_count >= EMAILDB_LIMIT) + vmsg("指定的 E-Mail 已註冊過多帳號, 請使用其他 E-Mail"); + else // valid + break; + } + continue; +#endif + } while (!isvalidemail(buf) && vmsg("認證信箱不能用使用免費信箱")); + y++; + // admins may want to use special names + if (strcmp(buf, x.email) && + (strchr(buf, '@') || adminmode)) { + + // TODO 這裡也要 emaildb_check +#ifdef USE_EMAILDB + if (emaildb_update_email(cuser.userid, strlen(cuser.userid), + buf, strlen(buf)) < 0) { + vmsg("暫時不允許\ email 認證, 請稍後再試"); + break; + } +#endif + strlcpy(x.email, buf, sizeof(x.email)); + mail_changed = 1; + delregcodefile(); + } + break; + + case '7': + violate_law(&x, unum); + return; + case '1': + move(0, 0); + outs("請逐項修改。"); + + getdata_buf(y++, 0, " 暱 稱 :", x.nickname, + sizeof(x.nickname), DOECHO); + if (adminmode) { + getdata_buf(y++, 0, "真實姓名:", + x.realname, sizeof(x.realname), DOECHO); + getdata_buf(y++, 0, "居住地址:", + x.address, sizeof(x.address), DOECHO); + } + buf[0] = 0; + if (x.mobile) + snprintf(buf, sizeof(buf), "%010d", x.mobile); + getdata_buf(y++, 0, "手機號碼:", buf, 11, LCECHO); + x.mobile = atoi(buf); + snprintf(genbuf, sizeof(genbuf), "%d", (u->sex + 1) % 8); + getdata_str(y++, 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(y, 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; + y++; + 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(y, 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); + ++y; + 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(y, 0, mybuf, genbuf + 11, 80 - 11, DOECHO); + ++y; + + 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 tmp; + if (HasUserPerm(PERM_BBSADM)) { + snprintf(genbuf, sizeof(genbuf), "%d", x.money); + if (getdata_str(y++, 0, "銀行帳戶:", buf, 10, DOECHO, genbuf)) + if ((tmp = atol(buf)) != 0) { + if (tmp != x.money) { + money_changed = 1; + changefrom = x.money; + x.money = tmp; + } + } + } + snprintf(genbuf, sizeof(genbuf), "%d", x.exmailbox); + if (getdata_str(y++, 0, "購買信箱數:", buf, 6, + DOECHO, genbuf)) + if ((tmp = atoi(buf)) != 0) + x.exmailbox = (int)tmp; + + getdata_buf(y++, 0, "認證資料:", x.justify, + sizeof(x.justify), DOECHO); + getdata_buf(y++, 0, "最近光臨機器:", + x.lasthost, sizeof(x.lasthost), DOECHO); + + snprintf(genbuf, sizeof(genbuf), "%d", x.numlogins); + if (getdata_str(y++, 0, "上線次數:", buf, 10, DOECHO, genbuf)) + if ((tmp = atoi(buf)) >= 0) + x.numlogins = tmp; + snprintf(genbuf, sizeof(genbuf), "%d", u->numposts); + if (getdata_str(y++, 0, "文章數目:", buf, 10, DOECHO, genbuf)) + if ((tmp = atoi(buf)) >= 0) + x.numposts = tmp; + snprintf(genbuf, sizeof(genbuf), "%d", u->goodpost); + if (getdata_str(y++, 0, "優良文章數:", buf, 10, DOECHO, genbuf)) + if ((tmp = atoi(buf)) >= 0) + x.goodpost = tmp; + snprintf(genbuf, sizeof(genbuf), "%d", u->badpost); + if (getdata_str(y++, 0, "惡劣文章數:", buf, 10, DOECHO, genbuf)) + if ((tmp = atoi(buf)) >= 0) + x.badpost = tmp; + snprintf(genbuf, sizeof(genbuf), "%d", u->vl_count); + if (getdata_str(y++, 0, "違法記錄:", buf, 10, DOECHO, genbuf)) + if ((tmp = atoi(buf)) >= 0) + x.vl_count = tmp; + + snprintf(genbuf, sizeof(genbuf), + "%d/%d/%d", u->five_win, u->five_lose, u->five_tie); + if (getdata_str(y++, 0, "五子棋戰績 勝/敗/和:", buf, 16, DOECHO, + genbuf)) + while (1) { + char *p; + 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(y++, 0, "象棋戰績 勝/敗/和:", buf, 16, DOECHO, + genbuf)) + while (1) { + char *p; + 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(y++, 0, "住在 1)台灣 2)其他:", buf, 2, DOECHO, x.uflag2 & FOREIGN ? "2" : "1")) + if ((tmp = atoi(buf)) > 0){ + if (tmp == 2){ + x.uflag2 |= FOREIGN; + } + else + x.uflag2 &= ~FOREIGN; + } + if (x.uflag2 & FOREIGN) + if (getdata_str(y++, 0, "永久居留權 1)是 2)否:", buf, 2, DOECHO, x.uflag2 & LIVERIGHT ? "1" : "2")){ + if ((tmp = atoi(buf)) > 0){ + if (tmp == 1){ + x.uflag2 |= LIVERIGHT; + x.userlevel |= (PERM_LOGINOK | PERM_POST); + } + else{ + x.uflag2 &= ~LIVERIGHT; + x.userlevel &= ~(PERM_LOGINOK | PERM_POST); + } + } + } +#endif + } + break; + + case '2': + y = 19; + if (!adminmode) { + if (!getdata(y++, 0, "請輸入原密碼:", buf, PASSLEN, NOECHO) || + !checkpasswd(u->passwd, buf)) { + outs("\n\n您輸入的密碼不正確\n"); + fail++; + break; + } + } else { + FILE *fp; + char witness[3][32], title[100]; + int uid; + 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(GLOBAL_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); + } + y = 20; + } + + if (!getdata(y++, 0, "請設定新密碼:", buf, PASSLEN, NOECHO)) { + outs("\n\n密碼設定取消, 繼續使用舊密碼\n"); + fail++; + break; + } + strlcpy(genbuf, buf, PASSLEN); + + move(y+1, 0); + outs("請注意設定密碼只有前八個字元有效,超過的將自動忽略。"); + + getdata(y++, 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': + { + int tmp = setperms(x.userlevel, str_permid); + if (tmp == x.userlevel) + fail++; + else { + perm_changed = 1; + changefrom = x.userlevel; + x.userlevel = tmp; + } + } + break; + + case '4': + tokill = 1; + 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': + chicken_toggle_death(x.userid); + break; + default: + return; + } + + if (fail) { + pressanykey(); + return; + } + if (getans(msg_sure_ny) == 'y') { + if (perm_changed) { + post_change_perm(changefrom, x.userlevel, cuser.userid, x.userid); +#ifdef PLAY_ANGEL + if (x.userlevel & ~changefrom & 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 && !adminmode) { + // wait registration. + x.userlevel &= ~(PERM_LOGINOK | PERM_POST); + } + memcpy(u, &x, sizeof(x)); + if (tokill) { + kill_user(unum, x.userid); + return; + } else + log_usies("SetUser", x.userid); + if (money_changed) { + char title[TTLEN+1]; + char msg[200]; + char reason[50]; + 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, changefrom, x.money, + cuser.userid, reason); + snprintf(title, sizeof(title), + "[公安報告] 站長%s修改%s錢報告", cuser.userid, + x.userid); + post_msg(GLOBAL_SECURITY, title, msg, "[系統安全局]"); + setumoney(unum, x.money); + } + passwd_update(unum, &x); + if(perm_changed) + sendalert(x.userid, ALERT_PWD_PERM); // force to reload perm + + // resolve_over18 only works for cuser + if (!adminmode) + resolve_over18(); + } +} + +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(" " ANSI_COLOR(1;31) "此人違規 尚未繳交罰單" ANSI_RESET); + 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, SHOWFILE_ALLOW_COLOR); + return; + } + } +#endif /* defined(CHESSCOUNTRY) */ + + sethomefile(genbuf, user->userid, fn_plans); + if (!show_file(genbuf, 7, MAX_QUERYLINES, SHOWFILE_ALLOW_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 +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; +} + +/* 列出所有註冊使用者 */ +struct ListAllUsetCtx { + int usercounter; + int totalusers; + unsigned short u_list_special; + int y; +}; + +static int +u_list_CB(void *data, int num, userec_t * uentp) +{ + char permstr[8], *ptr; + register int level; + struct ListAllUsetCtx *ctx = (struct ListAllUsetCtx*) data; + (void)num; + + if (uentp == NULL) { + move(2, 0); + clrtoeol(); + prints(ANSI_COLOR(7) " 使用者代號 %-25s 上站 文章 %s " + "最近光臨日期 " ANSI_COLOR(0) "\n", + "綽號暱稱", + HasUserPerm(PERM_SEEULEVELS) ? "等級" : ""); + ctx->y = 3; + return 0; + } + if (bad_user_id(uentp->userid)) + return 0; + + if ((uentp->userlevel & ~(ctx->u_list_special)) == 0) + return 0; + + if (ctx->y == b_lines) { + int ch; + 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, + ctx->usercounter, ctx->totalusers, ctx->usercounter * 100 / ctx->totalusers); + ch = igetch(); + if (ch == 'q' || ch == 'Q') + return -1; + ctx->y = 3; + } + if (ctx->y == 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); + ctx->usercounter++; + ctx->y++; + return 0; +} + +int +u_list(void) +{ + char genbuf[3]; + struct ListAllUsetCtx data, *ctx = &data; + + setutmpmode(LAUSERS); + ctx->u_list_special = ctx->usercounter = 0; + ctx->totalusers = SHM->number; + if (HasUserPerm(PERM_SEEULEVELS)) { + getdata(b_lines - 1, 0, "觀看 [1]特殊等級 (2)全部?", + genbuf, 3, DOECHO); + if (genbuf[0] != '2') + ctx->u_list_special = PERM_BASIC | PERM_CHAT | PERM_PAGE | PERM_POST | PERM_LOGINOK | PERM_BM; + } + u_list_CB(ctx, 0, NULL); + passwd_apply(ctx, u_list_CB); + move(b_lines, 0); + clrtoeol(); + prints(ANSI_COLOR(34;46) " 已顯示 %d/%d 的使用者(系統容量無上限) " + ANSI_COLOR(31;47) " (請按任意鍵繼續) " ANSI_RESET, ctx->usercounter, ctx->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(); + stand_title("設定自動偵測雙位元字集 (全型中文)"); + move(2, 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 */ + peek_input(0.1, Ctrl('C')); + drop_input(); + + while (1) + { + int ch = 0; + + move(14, 0); + outs("這是偵測區,您的游標會出現在" + ANSI_COLOR(7) "這裡" ANSI_RESET); + move(14, 15*2); + ch = igetch(); + if(ch != KEY_LEFT && ch != KEY_RIGHT && + ch != Ctrl('H') && ch != '\177') + { + move(16, 0); + bell(); + 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. + */ + refresh(); + if(wait_input(0.1, 0)) + // if(igetch() == ch) + // if (num_in_buf() > 0) + { + /* evil dbcs aware client */ + outs("偵測到您的連線程式會自行處理游標移動。\n" + // "若日後因此造成瀏覽上的問題本站恕不處理。\n\n" + "已設定為「讓您的連線程式處理游標移動」\n"); + ret = 1; + } else { + /* good non-dbcs aware client */ + outs("您的連線程式似乎不會多送按鍵," + "這樣 BBS 可以更精準的控制畫面。\n" + "已設定為「讓 BBS 伺服器直接處理游標移動」\n"); + ret = 0; + } + outs( "\n若想改變設定請至 個人設定區 → 個人化設定 → \n" + " 調整「自動偵測雙位元字集(如全型中文)」之設定"); + while(num_in_buf()) + igetch(); + break; + } + } + drop_input(); + pressanykey(); + return ret; +} +#endif + +/* vim:sw=4 + */ diff --git a/console/var.c b/console/var.c new file mode 100644 index 00000000..ca50824c --- /dev/null +++ b/console/var.c @@ -0,0 +1,628 @@ +/* $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", "看板人氣排行榜"}, + {NULL, NULL} +}; + +/* message */ +char * const msg_seperator = MSG_SEPERATOR; + +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; + +/* MAX_MODES is defined in common.h */ + +char * const ModeTypeTable[] = { + "發呆", /* IDLE */ + "主選單", /* MMENU */ + "系統維護", /* ADMIN */ + "郵件選單", /* MAIL */ + "交談選單", /* TMENU */ + "使用者選單", /* UMENU */ + "XYZ 選單", /* XMENU */ + "分類看板", /* CLASS */ + "Play選單", /* PMENU */ + "編特別名單", /* NMENU */ + BBSMNAME2 "量販店", /* 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 */ + BBSMNAME2 "查榜系統", /* JCEE */ + "重編文章", /* REEDIT */ + "部落格", /* BLOGGING */ + "看棋", /* CHESSWATCHING */ + "下圍棋", /* UMODE_GO */ + "[系統錯誤]", /* DEBUGSLEEPING */ + "連六棋", /* UMODE_CONN6 */ + "黑白棋", /* REVERSI */ + "BBS-Lua", /* UMODE_BBSLUA */ + "播放動畫", /* UMODE_ASCIIMOVIE */ + "", + "", + "", // 90 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 100 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 110 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", // 120 + "", + "", + "", + "", + "", + "", + "", + "" +}; + +/* 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 = 0; /* 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; + +/* 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/console/vice.c b/console/vice.c new file mode 100644 index 00000000..14e4e8f4 --- /dev/null +++ b/console/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/console/vote.c b/console/vote.c new file mode 100644 index 00000000..7562a7b8 --- /dev/null +++ b/console/vote.c @@ -0,0 +1,1088 @@ +/* $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; + +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, FN_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"); + 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); + 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|BRD_HIDE))) { // from ptt2 local modification + 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"); + + 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); + 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-1control); + 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-1bvote != 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 count, tickets; + short curr_page, item_num, max_page; + char inbuf[80], choices[ITEM_PER_PAGE+1], vote[4], *chosen; + time4_t closetime; + + // pos = boaord id, must be at least one int. + // fd should be int. + int pos = 0, i = 0; + int fd = 0; + + 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("你不夠資深喔!"); + 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 下一頁, < 上一頁\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][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-1bvote == 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/console/voteboard.c b/console/voteboard.c new file mode 100644 index 00000000..81018da2 --- /dev/null +++ b/console/voteboard.c @@ -0,0 +1,400 @@ +/* $Id$ */ +#include "bbs.h" + +#define VOTEBOARD "NewBoard" + +// user +int CheckVoteRestriction(int bid) +{ + if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) + return 1; + + // check first-login + if (cuser.firstlogin > (now - (time4_t)bcache[bid - 1].vote_limit_regtime * 2592000)) + return 0; + if (cuser.numlogins / 10 < (unsigned int)bcache[bid - 1].vote_limit_logins) + return 0; + if (cuser.numposts / 10 < (unsigned int)bcache[bid - 1].vote_limit_posts) + return 0; + if (cuser.badpost > (255 - (unsigned int)bcache[bid - 1].vote_limit_badpost)) + return 0; + + return 1; +} + +// article +int CheckVoteRestrictionFile(const fileheader_t * fhdr) +{ + if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) + return 1; + + // check first-login + if (cuser.firstlogin > (now - (time4_t)fhdr->multi.vote_limits.regtime * 2592000)) + return 0; + if (cuser.numlogins / 10 < (unsigned int)fhdr->multi.vote_limits.logins) + return 0; + if (cuser.numposts / 10 < (unsigned int)fhdr->multi.vote_limits.posts) + return 0; + if (cuser.badpost > (255 - (unsigned int)fhdr->multi.vote_limits.badpost)) + return 0; + + return 1; +} + +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 (!CheckVoteRestrictionFile(fhdr)) + { + move(5, 10); // why 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 (!CheckVoteRestriction(currbid)) + { + move(5, 10); // why move (5, 10)? + vmsg("你不夠資深喔! (可按 i 查看限制)"); + return FULLUPDATE; + } + move(0, 0); + clrtobot(); + outs("您正在使用" BBSNAME "的連署系統\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_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) { + char *suser = sysoplist[i].userid; + clear(); + showplans(suser); + do_send(suser, 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/daemon/cached/Makefile b/daemon/cached/Makefile new file mode 100644 index 00000000..bdff3f08 --- /dev/null +++ b/daemon/cached/Makefile @@ -0,0 +1,30 @@ +# $Id$ +SRCROOT= .. +.include "$(SRCROOT)/pttbbs.mk" + +PROGRAMS= utmpserver utmpsync utmpserver2 utmpserver3 authserver +UTILDIR= $(SRCROOT)/util +UTILOBJ= $(UTILDIR)/util_stuff.o $(UTILDIR)/util_var.o $(UTILDIR)/util_file.o $(UTILDIR)/util_cache.o $(UTILDIR)/util_passwd.o $(UTILDIR)/util_record.o $(UTILDIR)/util_osdep.o $(UTILDIR)/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/daemon/cached/README b/daemon/cached/README new file mode 100644 index 00000000..3bfeddbc --- /dev/null +++ b/daemon/cached/README @@ -0,0 +1 @@ +這是一個測試的東西. 除非確定你知道這個程式在幹什麼, 否則請不理會這個目錄 :P diff --git a/daemon/cached/authserver.c b/daemon/cached/authserver.c new file mode 100644 index 00000000..5fbba03a --- /dev/null +++ b/daemon/cached/authserver.c @@ -0,0 +1,236 @@ +/* $Id$ */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#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/daemon/cached/friend.cpp b/daemon/cached/friend.cpp new file mode 100644 index 00000000..e62a7198 --- /dev/null +++ b/daemon/cached/friend.cpp @@ -0,0 +1,350 @@ +#define NDEBUG +#include +#include + +// 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 +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] T* freelist::list[8][KEEP]; +template int freelist::tail[8]={0}; + +template +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(room0); + 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::free(base, room); + base=0; + room=0; + } else { + S origroom=room; + if(room==0) + room=MIN_ROOM; + while(roomsize) room=S(room/2); + if(room!=origroom || base==0) { + //base=(T*)realloc(base, sizeof(T)*room); + T* tmp=freelist::alloc(room); + assert(tmp); + if(n>0) + memcpy(tmp, base, sizeof(T)*n); + if(base!=0) + freelist::free(base, origroom); + base=tmp; + } + assert(base); + } + } +}; + +template +struct RelationList: public myvector { + RelationList() :myvector() {} + void add(Uid me, Uid him) { + RelationList& 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& 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; +}; + +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& backlist(Uid him); +}; +struct Likeby: public Relation { + Likeby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {} + static RelationList& backlist(Uid him); +}; +struct Hate: public Relation { + Hate(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {} + static RelationList& backlist(Uid him); +}; +struct Hateby: public Relation { + Hateby(Uid _him, short _him_offset=-1) :Relation(_him,_him_offset) {} + static RelationList& backlist(Uid him); +}; + + +struct Utmp { + Utmp() { + for(int i=0; i utmplist; + + RelationList like; + RelationList hate; + RelationList likeby; + RelationList 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& Like::backlist(Uid him) { return userlist.users[him].likeby; } +RelationList& Likeby::backlist(Uid him) { return userlist.users[him].like; } +RelationList& Hate::backlist(Uid him) { return userlist.users[him].hateby; } +RelationList& 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 work; + for(int i=0; i0) { + int newn=1; + for(int i=1; i + +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/daemon/cached/utmpserver2.c b/daemon/cached/utmpserver2.c new file mode 100644 index 00000000..12c3a299 --- /dev/null +++ b/daemon/cached/utmpserver2.c @@ -0,0 +1,290 @@ +/* $Id$ */ +#include +#include + +#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; i30 || + 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 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/daemon/cached/utmpserver3.c b/daemon/cached/utmpserver3.c new file mode 100644 index 00000000..0e1eef02 --- /dev/null +++ b/daemon/cached/utmpserver3.c @@ -0,0 +1,341 @@ +/* $Id$ */ +#include +#include +#include +#include + +#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; i30 || + 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; ievb, 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/daemon/cached/utmpsync.c b/daemon/cached/utmpsync.c new file mode 100644 index 00000000..69d1c623 --- /dev/null +++ b/daemon/cached/utmpsync.c @@ -0,0 +1,27 @@ +/* $Id$ */ +#include "bbs.h" +#include + +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/daemon/innbbsd/COPYRIGHT.nocem b/daemon/innbbsd/COPYRIGHT.nocem new file mode 100644 index 00000000..fdd43b20 --- /dev/null +++ b/daemon/innbbsd/COPYRIGHT.nocem @@ -0,0 +1,11 @@ +# Author: Yen-Ming Lee +# 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/daemon/innbbsd/Makefile b/daemon/innbbsd/Makefile new file mode 100644 index 00000000..3365e9c8 --- /dev/null +++ b/daemon/innbbsd/Makefile @@ -0,0 +1,74 @@ +# $Id$ +SRCROOT= .. +.include "$(SRCROOT)/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= $(SRCROOT)/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 + +LINK_UTIL_OBJS+= $(SRCROOT)/src/libbbsutil/libbbsutil.a \ + $(SRCROOT)/src/libbbs/libbbs.a + +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/daemon/innbbsd/antisplam.h b/daemon/innbbsd/antisplam.h new file mode 100644 index 00000000..0832533f --- /dev/null +++ b/daemon/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/daemon/innbbsd/bbslib.c b/daemon/innbbsd/bbslib.c new file mode 100644 index 00000000..326c7d87 --- /dev/null +++ b/daemon/innbbsd/bbslib.c @@ -0,0 +1,773 @@ +#include +#if defined( LINUX ) +#include "innbbsconf.h" +#include "bbslib.h" +#include +#else +#include +#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 = ≮ + 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/daemon/innbbsd/bbslib.h b/daemon/innbbsd/bbslib.h new file mode 100644 index 00000000..2aebcb96 --- /dev/null +++ b/daemon/innbbsd/bbslib.h @@ -0,0 +1,62 @@ +#ifndef BBSLIB_H +#define BBSLIB_H +#include /* 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/daemon/innbbsd/bbslink.c b/daemon/innbbsd/bbslink.c new file mode 100644 index 00000000..fde7af9e --- /dev/null +++ b/daemon/innbbsd/bbslink.c @@ -0,0 +1,1809 @@ +#include "antisplam.h" +#if defined( LINUX ) +#include "innbbsconf.h" +#include "bbslib.h" +#include +#else +#include +#include "innbbsconf.h" +#include "bbslib.h" +#endif + +#include + +#ifndef AIX +#include +#endif + +#if defined(PalmBBS) +#include +#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; +} + +#if 0 // moved to libbbsutil +/* + * 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); + +} +#endif + +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(" 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(" 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(" 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(" %s %s\n", board, filename); + FN = fopen(fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename), "w"); + if (FN == NULL) { + bbslog(" err: %s %s\n", board, filename); + if (Verbose) + printf(" 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), ×); + 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(" %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(" %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, " %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(" :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(" :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(" :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(" :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(" DATA Sendout <%s> from %s/%s\n", msgid, board, filename); + if (Verbose) + printf(" <%s> from %s/%s\n", msgid, board, filename); + } else { + bbslog(" :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(" %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(" %s %s\n", hostname, hostport); + if ((NNTP = inetclient(hostname, hostport, "tcp")) < 0) { + bbslog(" :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(" :Err: server error: %s", NNTPbuffer); + if (Verbose) + printf(":Err: server error: %s", NNTPbuffer); + return 0; + /* exit( 0 ); */ + } + } else { + if (Verbose) + printf(" %s %s\n", hostname, hostport); + } + } else { + if (!NoAction) { + if (Verbose) + printf(" localhost %s\n", hostport); + if ((NNTP = inetclient("localhost", hostport, "tcp")) < 0) { + bbslog(" :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(" :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(" %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(" %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(" :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(" :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(" :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(" :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(" :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(" %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(" %s %s %s %s\n", board, filename, from, subject); + } + sprintf(TMPFILE, "/tmp/cancel_outgoing.%d.%d", getuid(), getpid()); + + bbslog(" 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(" can't run %s/bbspost\n", INNDHOME); + if (Verbose) + printf(" 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, "標 題:
\n"); + if (NoAction) + return; + status = tcpcommand("QUIT"); + if (status != 205 && status != 221) { + bbslog(" :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(" %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(" %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(" 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(" [%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/daemon/innbbsd/bbsnnrp.c b/daemon/innbbsd/bbsnnrp.c new file mode 100644 index 00000000..bee96899 --- /dev/null +++ b/daemon/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 +#include "innbbsconf.h" +#include "osdep.h" +#include +#ifndef AIX +#include +#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/daemon/innbbsd/clibrary.h b/daemon/innbbsd/clibrary.h new file mode 100644 index 00000000..1248e650 --- /dev/null +++ b/daemon/innbbsd/clibrary.h @@ -0,0 +1,142 @@ +/* + * $Revision: 1.1 $ * + * + * Here be declarations of routines and variables in the C library. * You + * must #include and before this file. + */ + +#if defined(DO_HAVE_UNISTD) +#include +#endif /* defined(DO_HAVE_UNISTD) */ + +#if defined(DO_HAVE_VFORK) +#include +#endif /* defined(DO_HAVE_VFORK) */ + +/* Generic pointer, used by memcpy, malloc, etc. */ +/* =()@ *POINTER;>()= */ +typedef char *POINTER; +/* What is a file offset? Will not work unless long! */ +/* =()@ OFFSET_T;>()= */ +typedef long OFFSET_T; +/* What is the type of an object size? */ +/* =()@ SIZE_T;>()= */ +typedef int SIZE_T; +/* What is the type of a passwd uid and gid, for use in chown(2)? */ +/* =()@ UID_T;>()= */ +typedef int UID_T; +/* =()@ GID_T;>()= */ +typedef int GID_T; +/* =()@ PID_T;>()= */ +typedef int PID_T; +/* What should a signal handler return? */ +/* =()<#define SIGHANDLER @@>()= */ +#define SIGHANDLER void + +#if defined(SIG_DFL) +/* What types of variables can be modified in a signal handler? */ +/* =()@ SIGVAR;>()= */ +typedef int SIGVAR; +#endif /* defined(SIG_DFL) */ + +/* =()<#include @@>()= */ +#include +/* =()<#include @@>()= */ +#include + + +/* + * * 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 +#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 @@>()= */ +#define FREEVAL int + +#include +#include +#include +#include +#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 , but not in some system string headers, so we put + * it here just in case. + */ +extern int strncasecmp(); + +/* =()@ abort();>()= */ +extern int abort(); +/* =()@ alarm();>()= */ +extern int alarm(); +/* =()@ exit();>()= */ +extern void exit(); +/* =()@ getpid();>()= */ +extern int getpid(); +/* =()@ lseek();>()= */ +extern off_t lseek(); +/* =()@ qsort();>()= */ +extern int qsort(); +/* =()@ sleep();>()= */ +extern int sleep(); +/* =()@ _exit();>()= */ +extern int _exit(); +#endif diff --git a/daemon/innbbsd/closeonexec.c b/daemon/innbbsd/closeonexec.c new file mode 100644 index 00000000..1fd1a24e --- /dev/null +++ b/daemon/innbbsd/closeonexec.c @@ -0,0 +1,69 @@ +/* + * $Revision: 1.1 $ * + * + */ +/* #include "configdata.h" */ +#include +#include +#include +#include +#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 +#else +#include +#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 + + +/* + * * 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/daemon/innbbsd/connectsock.c b/daemon/innbbsd/connectsock.c new file mode 100644 index 00000000..5e526715 --- /dev/null +++ b/daemon/innbbsd/connectsock.c @@ -0,0 +1,464 @@ +#include +#include "osdep.h" +#include "innbbsconf.h" +#include "daemon.h" +#include +#include +#include +#include +#include +#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/daemon/innbbsd/ctlinnbbsd.c b/daemon/innbbsd/ctlinnbbsd.c new file mode 100644 index 00000000..95e7d315 --- /dev/null +++ b/daemon/innbbsd/ctlinnbbsd.c @@ -0,0 +1,167 @@ +#include +#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 path: add history\n"); + fprintf(stderr, " ctlinnbbsd grephist : 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/daemon/innbbsd/daemon.c b/daemon/innbbsd/daemon.c new file mode 100644 index 00000000..b764fda1 --- /dev/null +++ b/daemon/innbbsd/daemon.c @@ -0,0 +1,177 @@ +#include +#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/daemon/innbbsd/daemon.h b/daemon/innbbsd/daemon.h new file mode 100644 index 00000000..36384a20 --- /dev/null +++ b/daemon/innbbsd/daemon.h @@ -0,0 +1,54 @@ +#ifndef DAEMON_H +#define DAEMON_H + +#include +#include + +#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/daemon/innbbsd/dbz.c b/daemon/innbbsd/dbz.c new file mode 100644 index 00000000..9b031678 --- /dev/null +++ b/daemon/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 + * and by Rich $alz (December, + * 1992). Merged in MMAP code by David Robinson, formerly + * now (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 +#include +#include +#include +#include +#ifndef __STDC__ +extern int errno; +#endif +#include +#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 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 +#else +#define SEEK_SET 0 +#endif +#ifdef OVERFLOW +#include +#endif + +static int dbzversion = 3; /* for validating .dir file format */ + +/* + * The dbz database exploits the fact that when news stores a + * 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)< +#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/daemon/innbbsd/dbz.h b/daemon/innbbsd/dbz.h new file mode 100644 index 00000000..4883c1e6 --- /dev/null +++ b/daemon/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/daemon/innbbsd/dbztool.c b/daemon/innbbsd/dbztool.c new file mode 100644 index 00000000..c2d77476 --- /dev/null +++ b/daemon/innbbsd/dbztool.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include "his.h" +#include "externs.h" +#include + +#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/daemon/innbbsd/echobbslib.c b/daemon/innbbsd/echobbslib.c new file mode 100644 index 00000000..0d34a443 --- /dev/null +++ b/daemon/innbbsd/echobbslib.c @@ -0,0 +1,777 @@ +#include +#if defined( LINUX ) +#include "innbbsconf.h" +#include "bbslib.h" +#include +#else +#include +#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 = ≮ + 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/daemon/innbbsd/externs.h b/daemon/innbbsd/externs.h new file mode 100644 index 00000000..7fe63b71 --- /dev/null +++ b/daemon/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 +#include +#include +#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/daemon/innbbsd/file.c b/daemon/innbbsd/file.c new file mode 100644 index 00000000..29b6384a --- /dev/null +++ b/daemon/innbbsd/file.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#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/daemon/innbbsd/his.c b/daemon/innbbsd/his.c new file mode 100644 index 00000000..773fb78c --- /dev/null +++ b/daemon/innbbsd/his.c @@ -0,0 +1,477 @@ +/* + * $Revision: 1.1 $ * + * + * History file routines. + */ +#include +#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/daemon/innbbsd/his.h b/daemon/innbbsd/his.h new file mode 100644 index 00000000..fab0f76e --- /dev/null +++ b/daemon/innbbsd/his.h @@ -0,0 +1,77 @@ +#ifndef HIS_H +#define HIS_H +#include +#include +#include +#include +#include +#ifndef SEEK_SET +#include +#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/daemon/innbbsd/innbbsconf.h b/daemon/innbbsd/innbbsconf.h new file mode 100644 index 00000000..dcdc5f21 --- /dev/null +++ b/daemon/innbbsd/innbbsconf.h @@ -0,0 +1,186 @@ +#ifndef INNBBSCONF_H +#define INNBBSCONF_H +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifndef BSD44 +#include +#endif +#include +#include +#include + +/* #include "bbs.h" */ +#if defined(AIX) +#include +#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/daemon/innbbsd/innbbsd.c b/daemon/innbbsd/innbbsd.c new file mode 100644 index 00000000..f71ab30c --- /dev/null +++ b/daemon/innbbsd/innbbsd.c @@ -0,0 +1,794 @@ +#include "innbbsconf.h" +#include "daemon.h" +#include "innbbsd.h" +#include +#include "bbslib.h" +#include "inntobbs.h" +#include "nntp.h" +#include "externs.h" + +#ifdef GETRUSAGE +#include +#include +#endif + +#ifdef STDC +#ifndef ARG +#define ARG(x) (x) +#else +#define ARG(x) () +#endif +#endif + +/* + * < add ... > 200 OK < quit 500 BYE + * + * > 300 DBZ Server ... < query > 250 ... > 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 ", 3, 3, NNTP_ADDHIST_BAD, NNTP_ADDHIST_OK, CMDaddhist}, +{"grephist", "grephist ", 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/daemon/innbbsd/innbbsd.h b/daemon/innbbsd/innbbsd.h new file mode 100644 index 00000000..90a019d1 --- /dev/null +++ b/daemon/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/daemon/innbbsd/inncheck.pl b/daemon/innbbsd/inncheck.pl new file mode 100644 index 00000000..2b98a305 --- /dev/null +++ b/daemon/innbbsd/inncheck.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl +use IO::Socket::INET; +$BBSHOME = '/home/bbs'; + +open NODES, "<$BBSHOME/innd/nodelist.bbs"; +while( ){ + 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( ){ + ++$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/daemon/innbbsd/inndchannel.c b/daemon/innbbsd/inndchannel.c new file mode 100644 index 00000000..fe5b74ef --- /dev/null +++ b/daemon/innbbsd/inndchannel.c @@ -0,0 +1,681 @@ +#include +#include "innbbsconf.h" +#include "daemon.h" +#include "bbslib.h" +#include "config.h" +#include "externs.h" +#include +#include +#include +#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/daemon/innbbsd/inntobbs.c b/daemon/innbbsd/inntobbs.c new file mode 100644 index 00000000..fca7c3d5 --- /dev/null +++ b/daemon/innbbsd/inntobbs.c @@ -0,0 +1,342 @@ +#include +#include +#include "daemon.h" +#include "bbslib.h" +#include +#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 + +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/daemon/innbbsd/inntobbs.h b/daemon/innbbsd/inntobbs.h new file mode 100644 index 00000000..6d910252 --- /dev/null +++ b/daemon/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/daemon/innbbsd/mkhistory.c b/daemon/innbbsd/mkhistory.c new file mode 100644 index 00000000..c1278adb --- /dev/null +++ b/daemon/innbbsd/mkhistory.c @@ -0,0 +1,18 @@ +#include +#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/daemon/innbbsd/nntp.h b/daemon/innbbsd/nntp.h new file mode 100644 index 00000000..78129d7c --- /dev/null +++ b/daemon/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/daemon/innbbsd/nocem.c b/daemon/innbbsd/nocem.c new file mode 100644 index 00000000..595ecb1d --- /dev/null +++ b/daemon/innbbsd/nocem.c @@ -0,0 +1,636 @@ +/* + * NoCeM-INNBBSD Yen-Ming Lee NCMparse(), + * NCMverify(), NCMcancel(): return 0 success, otherwise fail; + */ + +#include +#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/daemon/innbbsd/nocem.h b/daemon/innbbsd/nocem.h new file mode 100644 index 00000000..18a4c5e9 --- /dev/null +++ b/daemon/innbbsd/nocem.h @@ -0,0 +1,57 @@ +/* + NoCeM-INNBBSD + Yen-Ming Lee +*/ + +#ifndef NOCEM_H +#define NOCEM_H + +#include "innbbsconf.h" +#include "bbslib.h" +#include "inntobbs.h" + +#include /* 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/daemon/innbbsd/pmain.c b/daemon/innbbsd/pmain.c new file mode 100644 index 00000000..1b039c48 --- /dev/null +++ b/daemon/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/daemon/innbbsd/port.c b/daemon/innbbsd/port.c new file mode 100644 index 00000000..fab82771 --- /dev/null +++ b/daemon/innbbsd/port.c @@ -0,0 +1,35 @@ +#include "innbbsconf.h" + +#ifdef NO_getdtablesize +#include +#include +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 +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/daemon/innbbsd/receive_article.c b/daemon/innbbsd/receive_article.c new file mode 100644 index 00000000..4ef00008 --- /dev/null +++ b/daemon/innbbsd/receive_article.c @@ -0,0 +1,1125 @@ + +/* + * 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 +#define _XOPEN_SOURCE /* glibc2 needs this */ +#include +#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() + */ + +#if 0 // moved to libbbsutil +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); +} +#endif + + +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/daemon/innbbsd/rfc931.c b/daemon/innbbsd/rfc931.c new file mode 100644 index 00000000..33ee49fd --- /dev/null +++ b/daemon/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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 ! + */ + + 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 + * . + */ + + 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/daemon/innbbsd/str_decode.c b/daemon/innbbsd/str_decode.c new file mode 100644 index 00000000..72a1f225 --- /dev/null +++ b/daemon/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 +#include +#include +#include /* isspace() */ + +#define USE_ICONV 1 +/* + * bsd 底下使用要編譯時要加 -I/usr/local/include -L/usr/local/lib -liconv + * 若真的沒有iconv就把上面這行 #define 刪了 + */ + +#ifdef USE_ICONV +#include +#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/innbbsd/COPYRIGHT.nocem b/innbbsd/COPYRIGHT.nocem deleted file mode 100644 index fdd43b20..00000000 --- a/innbbsd/COPYRIGHT.nocem +++ /dev/null @@ -1,11 +0,0 @@ -# Author: Yen-Ming Lee -# 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/innbbsd/Makefile b/innbbsd/Makefile deleted file mode 100644 index 3365e9c8..00000000 --- a/innbbsd/Makefile +++ /dev/null @@ -1,74 +0,0 @@ -# $Id$ -SRCROOT= .. -.include "$(SRCROOT)/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= $(SRCROOT)/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 - -LINK_UTIL_OBJS+= $(SRCROOT)/src/libbbsutil/libbbsutil.a \ - $(SRCROOT)/src/libbbs/libbbs.a - -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/innbbsd/antisplam.h b/innbbsd/antisplam.h deleted file mode 100644 index 0832533f..00000000 --- a/innbbsd/antisplam.h +++ /dev/null @@ -1,25 +0,0 @@ -#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/innbbsd/bbslib.c b/innbbsd/bbslib.c deleted file mode 100644 index 326c7d87..00000000 --- a/innbbsd/bbslib.c +++ /dev/null @@ -1,773 +0,0 @@ -#include -#if defined( LINUX ) -#include "innbbsconf.h" -#include "bbslib.h" -#include -#else -#include -#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 = ≮ - 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/innbbsd/bbslib.h b/innbbsd/bbslib.h deleted file mode 100644 index 2aebcb96..00000000 --- a/innbbsd/bbslib.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef BBSLIB_H -#define BBSLIB_H -#include /* 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/innbbsd/bbslink.c b/innbbsd/bbslink.c deleted file mode 100644 index fde7af9e..00000000 --- a/innbbsd/bbslink.c +++ /dev/null @@ -1,1809 +0,0 @@ -#include "antisplam.h" -#if defined( LINUX ) -#include "innbbsconf.h" -#include "bbslib.h" -#include -#else -#include -#include "innbbsconf.h" -#include "bbslib.h" -#endif - -#include - -#ifndef AIX -#include -#endif - -#if defined(PalmBBS) -#include -#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; -} - -#if 0 // moved to libbbsutil -/* - * 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); - -} -#endif - -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(" 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(" 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(" 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(" %s %s\n", board, filename); - FN = fopen(fileglue("%s/boards/%c/%s/%s", BBSHOME, board[0], board, filename), "w"); - if (FN == NULL) { - bbslog(" err: %s %s\n", board, filename); - if (Verbose) - printf(" 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), ×); - 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(" %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(" %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, " %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(" :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(" :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(" :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(" :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(" DATA Sendout <%s> from %s/%s\n", msgid, board, filename); - if (Verbose) - printf(" <%s> from %s/%s\n", msgid, board, filename); - } else { - bbslog(" :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(" %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(" %s %s\n", hostname, hostport); - if ((NNTP = inetclient(hostname, hostport, "tcp")) < 0) { - bbslog(" :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(" :Err: server error: %s", NNTPbuffer); - if (Verbose) - printf(":Err: server error: %s", NNTPbuffer); - return 0; - /* exit( 0 ); */ - } - } else { - if (Verbose) - printf(" %s %s\n", hostname, hostport); - } - } else { - if (!NoAction) { - if (Verbose) - printf(" localhost %s\n", hostport); - if ((NNTP = inetclient("localhost", hostport, "tcp")) < 0) { - bbslog(" :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(" :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(" %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(" %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(" :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(" :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(" :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(" :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(" :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(" %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(" %s %s %s %s\n", board, filename, from, subject); - } - sprintf(TMPFILE, "/tmp/cancel_outgoing.%d.%d", getuid(), getpid()); - - bbslog(" 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(" can't run %s/bbspost\n", INNDHOME); - if (Verbose) - printf(" 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, "標 題:
\n"); - if (NoAction) - return; - status = tcpcommand("QUIT"); - if (status != 205 && status != 221) { - bbslog(" :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(" %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(" %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(" 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(" [%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/innbbsd/bbsnnrp.c b/innbbsd/bbsnnrp.c deleted file mode 100644 index bee96899..00000000 --- a/innbbsd/bbsnnrp.c +++ /dev/null @@ -1,1247 +0,0 @@ -/* - * 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 -#include "innbbsconf.h" -#include "osdep.h" -#include -#ifndef AIX -#include -#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/innbbsd/clibrary.h b/innbbsd/clibrary.h deleted file mode 100644 index 1248e650..00000000 --- a/innbbsd/clibrary.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * $Revision: 1.1 $ * - * - * Here be declarations of routines and variables in the C library. * You - * must #include and before this file. - */ - -#if defined(DO_HAVE_UNISTD) -#include -#endif /* defined(DO_HAVE_UNISTD) */ - -#if defined(DO_HAVE_VFORK) -#include -#endif /* defined(DO_HAVE_VFORK) */ - -/* Generic pointer, used by memcpy, malloc, etc. */ -/* =()@ *POINTER;>()= */ -typedef char *POINTER; -/* What is a file offset? Will not work unless long! */ -/* =()@ OFFSET_T;>()= */ -typedef long OFFSET_T; -/* What is the type of an object size? */ -/* =()@ SIZE_T;>()= */ -typedef int SIZE_T; -/* What is the type of a passwd uid and gid, for use in chown(2)? */ -/* =()@ UID_T;>()= */ -typedef int UID_T; -/* =()@ GID_T;>()= */ -typedef int GID_T; -/* =()@ PID_T;>()= */ -typedef int PID_T; -/* What should a signal handler return? */ -/* =()<#define SIGHANDLER @@>()= */ -#define SIGHANDLER void - -#if defined(SIG_DFL) -/* What types of variables can be modified in a signal handler? */ -/* =()@ SIGVAR;>()= */ -typedef int SIGVAR; -#endif /* defined(SIG_DFL) */ - -/* =()<#include @@>()= */ -#include -/* =()<#include @@>()= */ -#include - - -/* - * * 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 -#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 @@>()= */ -#define FREEVAL int - -#include -#include -#include -#include -#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 , but not in some system string headers, so we put - * it here just in case. - */ -extern int strncasecmp(); - -/* =()@ abort();>()= */ -extern int abort(); -/* =()@ alarm();>()= */ -extern int alarm(); -/* =()@ exit();>()= */ -extern void exit(); -/* =()@ getpid();>()= */ -extern int getpid(); -/* =()@ lseek();>()= */ -extern off_t lseek(); -/* =()@ qsort();>()= */ -extern int qsort(); -/* =()@ sleep();>()= */ -extern int sleep(); -/* =()@ _exit();>()= */ -extern int _exit(); -#endif diff --git a/innbbsd/closeonexec.c b/innbbsd/closeonexec.c deleted file mode 100644 index 1fd1a24e..00000000 --- a/innbbsd/closeonexec.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * $Revision: 1.1 $ * - * - */ -/* #include "configdata.h" */ -#include -#include -#include -#include -#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 -#else -#include -#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 - - -/* - * * 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/innbbsd/connectsock.c b/innbbsd/connectsock.c deleted file mode 100644 index 5e526715..00000000 --- a/innbbsd/connectsock.c +++ /dev/null @@ -1,464 +0,0 @@ -#include -#include "osdep.h" -#include "innbbsconf.h" -#include "daemon.h" -#include -#include -#include -#include -#include -#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/innbbsd/ctlinnbbsd.c b/innbbsd/ctlinnbbsd.c deleted file mode 100644 index 95e7d315..00000000 --- a/innbbsd/ctlinnbbsd.c +++ /dev/null @@ -1,167 +0,0 @@ -#include -#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 path: add history\n"); - fprintf(stderr, " ctlinnbbsd grephist : 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/innbbsd/daemon.c b/innbbsd/daemon.c deleted file mode 100644 index b764fda1..00000000 --- a/innbbsd/daemon.c +++ /dev/null @@ -1,177 +0,0 @@ -#include -#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/innbbsd/daemon.h b/innbbsd/daemon.h deleted file mode 100644 index 36384a20..00000000 --- a/innbbsd/daemon.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef DAEMON_H -#define DAEMON_H - -#include -#include - -#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/innbbsd/dbz.c b/innbbsd/dbz.c deleted file mode 100644 index 9b031678..00000000 --- a/innbbsd/dbz.c +++ /dev/null @@ -1,1885 +0,0 @@ -/* - * 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 - * and by Rich $alz (December, - * 1992). Merged in MMAP code by David Robinson, formerly - * now (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 -#include -#include -#include -#include -#ifndef __STDC__ -extern int errno; -#endif -#include -#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 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 -#else -#define SEEK_SET 0 -#endif -#ifdef OVERFLOW -#include -#endif - -static int dbzversion = 3; /* for validating .dir file format */ - -/* - * The dbz database exploits the fact that when news stores a - * 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)< -#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/innbbsd/dbz.h b/innbbsd/dbz.h deleted file mode 100644 index 4883c1e6..00000000 --- a/innbbsd/dbz.h +++ /dev/null @@ -1,36 +0,0 @@ -/* 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/innbbsd/dbztool.c b/innbbsd/dbztool.c deleted file mode 100644 index c2d77476..00000000 --- a/innbbsd/dbztool.c +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include -#include "his.h" -#include "externs.h" -#include - -#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/innbbsd/echobbslib.c b/innbbsd/echobbslib.c deleted file mode 100644 index 0d34a443..00000000 --- a/innbbsd/echobbslib.c +++ /dev/null @@ -1,777 +0,0 @@ -#include -#if defined( LINUX ) -#include "innbbsconf.h" -#include "bbslib.h" -#include -#else -#include -#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 = ≮ - 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/innbbsd/externs.h b/innbbsd/externs.h deleted file mode 100644 index 7fe63b71..00000000 --- a/innbbsd/externs.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef EXTERNS_H -#define EXTERNS_H - -#ifndef ARG -#ifdef __STDC__ -#define ARG(x) x -#else -#define ARG(x) () -#endif -#endif - -#include -#include -#include -#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/innbbsd/file.c b/innbbsd/file.c deleted file mode 100644 index 29b6384a..00000000 --- a/innbbsd/file.c +++ /dev/null @@ -1,205 +0,0 @@ -#include -#include -#include -#include -#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/innbbsd/his.c b/innbbsd/his.c deleted file mode 100644 index 773fb78c..00000000 --- a/innbbsd/his.c +++ /dev/null @@ -1,477 +0,0 @@ -/* - * $Revision: 1.1 $ * - * - * History file routines. - */ -#include -#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/innbbsd/his.h b/innbbsd/his.h deleted file mode 100644 index fab0f76e..00000000 --- a/innbbsd/his.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef HIS_H -#define HIS_H -#include -#include -#include -#include -#include -#ifndef SEEK_SET -#include -#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/innbbsd/innbbsconf.h b/innbbsd/innbbsconf.h deleted file mode 100644 index dcdc5f21..00000000 --- a/innbbsd/innbbsconf.h +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef INNBBSCONF_H -#define INNBBSCONF_H -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#ifndef BSD44 -#include -#endif -#include -#include -#include - -/* #include "bbs.h" */ -#if defined(AIX) -#include -#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/innbbsd/innbbsd.c b/innbbsd/innbbsd.c deleted file mode 100644 index f71ab30c..00000000 --- a/innbbsd/innbbsd.c +++ /dev/null @@ -1,794 +0,0 @@ -#include "innbbsconf.h" -#include "daemon.h" -#include "innbbsd.h" -#include -#include "bbslib.h" -#include "inntobbs.h" -#include "nntp.h" -#include "externs.h" - -#ifdef GETRUSAGE -#include -#include -#endif - -#ifdef STDC -#ifndef ARG -#define ARG(x) (x) -#else -#define ARG(x) () -#endif -#endif - -/* - * < add ... > 200 OK < quit 500 BYE - * - * > 300 DBZ Server ... < query > 250 ... > 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 ", 3, 3, NNTP_ADDHIST_BAD, NNTP_ADDHIST_OK, CMDaddhist}, -{"grephist", "grephist ", 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/innbbsd/innbbsd.h b/innbbsd/innbbsd.h deleted file mode 100644 index 90a019d1..00000000 --- a/innbbsd/innbbsd.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef INNBBSD_H -#define INNBBSD_H -#include "daemon.h" - -#ifndef ADMINUSER -#define ADMINUSER "usenet@csie.nctu.edu.tw" -#endif - -#endif diff --git a/innbbsd/inncheck.pl b/innbbsd/inncheck.pl deleted file mode 100644 index 2b98a305..00000000 --- a/innbbsd/inncheck.pl +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/perl -use IO::Socket::INET; -$BBSHOME = '/home/bbs'; - -open NODES, "<$BBSHOME/innd/nodelist.bbs"; -while( ){ - 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( ){ - ++$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/innbbsd/inndchannel.c b/innbbsd/inndchannel.c deleted file mode 100644 index fe5b74ef..00000000 --- a/innbbsd/inndchannel.c +++ /dev/null @@ -1,681 +0,0 @@ -#include -#include "innbbsconf.h" -#include "daemon.h" -#include "bbslib.h" -#include "config.h" -#include "externs.h" -#include -#include -#include -#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/innbbsd/inntobbs.c b/innbbsd/inntobbs.c deleted file mode 100644 index fca7c3d5..00000000 --- a/innbbsd/inntobbs.c +++ /dev/null @@ -1,342 +0,0 @@ -#include -#include -#include "daemon.h" -#include "bbslib.h" -#include -#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 - -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/innbbsd/inntobbs.h b/innbbsd/inntobbs.h deleted file mode 100644 index 6d910252..00000000 --- a/innbbsd/inntobbs.h +++ /dev/null @@ -1,39 +0,0 @@ -#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/innbbsd/mkhistory.c b/innbbsd/mkhistory.c deleted file mode 100644 index c1278adb..00000000 --- a/innbbsd/mkhistory.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#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/innbbsd/nntp.h b/innbbsd/nntp.h deleted file mode 100644 index 78129d7c..00000000 --- a/innbbsd/nntp.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * $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/innbbsd/nocem.c b/innbbsd/nocem.c deleted file mode 100644 index 595ecb1d..00000000 --- a/innbbsd/nocem.c +++ /dev/null @@ -1,636 +0,0 @@ -/* - * NoCeM-INNBBSD Yen-Ming Lee NCMparse(), - * NCMverify(), NCMcancel(): return 0 success, otherwise fail; - */ - -#include -#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/innbbsd/nocem.h b/innbbsd/nocem.h deleted file mode 100644 index 18a4c5e9..00000000 --- a/innbbsd/nocem.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - NoCeM-INNBBSD - Yen-Ming Lee -*/ - -#ifndef NOCEM_H -#define NOCEM_H - -#include "innbbsconf.h" -#include "bbslib.h" -#include "inntobbs.h" - -#include /* 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/innbbsd/pmain.c b/innbbsd/pmain.c deleted file mode 100644 index 1b039c48..00000000 --- a/innbbsd/pmain.c +++ /dev/null @@ -1,64 +0,0 @@ -#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/innbbsd/port.c b/innbbsd/port.c deleted file mode 100644 index fab82771..00000000 --- a/innbbsd/port.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "innbbsconf.h" - -#ifdef NO_getdtablesize -#include -#include -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 -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/innbbsd/receive_article.c b/innbbsd/receive_article.c deleted file mode 100644 index 4ef00008..00000000 --- a/innbbsd/receive_article.c +++ /dev/null @@ -1,1125 +0,0 @@ - -/* - * 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 -#define _XOPEN_SOURCE /* glibc2 needs this */ -#include -#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() - */ - -#if 0 // moved to libbbsutil -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); -} -#endif - - -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/innbbsd/rfc931.c b/innbbsd/rfc931.c deleted file mode 100644 index 33ee49fd..00000000 --- a/innbbsd/rfc931.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#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 ! - */ - - 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 - * . - */ - - 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/innbbsd/str_decode.c b/innbbsd/str_decode.c deleted file mode 100644 index 72a1f225..00000000 --- a/innbbsd/str_decode.c +++ /dev/null @@ -1,291 +0,0 @@ -/* - * 使用方法大致如下: 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 -#include -#include -#include /* isspace() */ - -#define USE_ICONV 1 -/* - * bsd 底下使用要編譯時要加 -I/usr/local/include -L/usr/local/lib -liconv - * 若真的沒有iconv就把上面這行 #define 刪了 - */ - -#ifdef USE_ICONV -#include -#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/mbbsd/Makefile b/mbbsd/Makefile deleted file mode 100644 index a33301c6..00000000 --- a/mbbsd/Makefile +++ /dev/null @@ -1,136 +0,0 @@ -# $Id$ - -SRCROOT= .. -.include "$(SRCROOT)/pttbbs.mk" - -####################################################################### -# common modules -####################################################################### - -PROG= mbbsd -CHESSOBJS= chc.o chc_tab.o chess.o go.o gomo.o dark.o reversi.o -GAMEOBJS = card.o guess.o chicken.o othello.o -SYSOBJS = args.o crypt.o osdep.o -COREOBJS = bbs.o announce.o read.o board.o cache.o brc.o mail.o record.o fav.o -ACCOBJS = user.o register.o passwd.o -TALKOBJS = talk.o chat.o friend.o -NETOBJS = mbbsd.o io.o term.o -UTILOBJS = stuff.o file.o kaede.o convert.o name.o -PLUGOBJS = lovepaper.o calendar.o topsong.o vice.o -OBJS= admin.o assess.o cal.o edit.o menu.o more.o gamble.o \ - xyz.o syspost.o vote.o var.o voteboard.o \ - pmore.o telnet.o \ - $(COREOBJS) $(ACCOBJS) $(NETOBJS) $(TALKOBJS) $(UTILOBJS) \ - $(SYSOBJS) $(PLUGOBJS) $(CHESSOBJS) $(GAMEOBJS) - -####################################################################### -# conditional configurations and optional modules -####################################################################### - -.if !defined(WITHOUT_BLOG) && defined(WITH_BLOG) -CFLAGS+= -DBLOG -LDFLAGS+= -L/usr/local/lib/mysql -LIBS+= -lmysqlclient -.endif - -.if !defined(WITHOUT_LOG_CRAWLER) && defined(WITH_LOG_CRAWLER) -CFLAGS+= -DLOG_CRAWLER -.endif - -.if !defined(WITHOUT_EMAILDB) && defined(WITH_EMAILDB) -OBJS+= emaildb.o -CFLAGS+= -DUSE_EMAILDB -LIBS+= -lsqlite3 -.endif - -.if !defined(WITHOUT_BBSLUA_USAGE) && defined(WITH_BBSLUA_USAGE) -CFLAGS+= -DBBSLUA_USAGE -.endif - -.if !defined(WITHOUT_BBSLUA) && defined(WITH_BBSLUA) -OBJS+= bbslua.o bbsluaext.o -CFLAGS+= -DUSE_BBSLUA -# MODIFY THESE ENVIRONMENT SETTINGS TO FIT YOUR CONFIGURATION -CFLAGS+= -I/usr/include/lua5.1 -CFLAGS_FreeBSD += -I/usr/local/include/lua51 -LDFLAGS_FreeBSD+= -L/usr/local/lib/lua51 -# modify the lib name below to fit your configuration -# usually you'd try "-llua" instead of "-llua5.1". -LIBS+= -llua5.1 -lm -#LIBS+= -llua -lm -.endif - -.if !defined(WITHOUT_PFTERM) && defined(WITH_PFTERM) -OBJS+= pfterm.o -CFLAGS+= -DUSE_PFTERM -#CFLAGS+= -DDBG_OUTRPT -.else -OBJS+= screen.o -.endif - -####################################################################### -# special library (DIET) configuration -####################################################################### - -.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 -LIBS+= $(SRCROOT)/src/libbbsutil/libbbsutil.a \ - $(SRCROOT)/src/libbbs/libbbs.a - -####################################################################### -# Make Rules -####################################################################### - -.SUFFIXES: .c .o -.c.o: $(SRCROOT)/include/var.h - $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c - -all: $(PROG) - -$(PROG): $(OBJS) - sh $(SRCROOT)/util/newvers.sh - $(DIETCC) $(CC) $(LDFLAGS) -o $(PROG) $(OBJS) $(LIBS) $(EXT_LIBS) vers.c - -$(SRCROOT)/include/var.h: var.c - perl $(SRCROOT)/util/parsevar.pl < var.c > $(SRCROOT)/include/var.h - -$(SRCROOT)/include/banip.h: $(SRCROOT)/util/banip.pl - perl $(SRCROOT)/util/banip.pl > $@ - -mbbsd.o: mbbsd.c $(SRCROOT)/include/var.h $(SRCROOT)/include/banip.h - $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $< - -initemaildb: emaildb.c - $(CC) -DINIT_MAIN $(CFLAGS) $(LDFLAGS) -o initemaildb emaildb.c $(LIBS) - -ctags: - ctags *.c ../include/*.h ../src/libbbs/*.c ../src/libbbsutil/*.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/mbbsd/admin.c b/mbbsd/admin.c deleted file mode 100644 index d0efa3e1..00000000 --- a/mbbsd/admin.c +++ /dev/null @@ -1,1172 +0,0 @@ -/* $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 unum = 0; - FILE *fp1 = fopen(passwdfile, "r"); - char friendfile[128]="", key[22], *keymatch; - int keytype = 0; - char isCurrentPwd = 0; - - isCurrentPwd = (strcmp(passwdfile, FN_PASSWD) == 0) ? 1 : 0; - assert(fp1); - clear(); - if (!mode) - { - getdata(0, 0, "請輸入id :", key, sizeof(key), DOECHO); - } else { - // improved search - getdata(0, 0, "搜尋哪種欄位?" - "([0]全部 1.ID 2.姓名 3.暱稱 4.地址 5.email 6.IP 7.認證/電話) ", - key, 2, DOECHO); - if (isascii(key[0]) && isdigit(key[0])) - keytype = key[0] - '0'; - if (keytype < 0 || keytype > 7) - keytype = 0; - getdata(0, 0, "請輸入關鍵字: ", key, sizeof(key), DOECHO); - } - - if(!key[0]) { - fclose(fp1); - return 0; - } - while ((fread(&user, sizeof(user), 1, fp1)) > 0 && unum++ < MAX_USERS) { - - // skip empty records - if (!user.userid[0]) - continue; - - if (!(unum & 0xFF)) { - move(1, 0); - prints("第 [%d] 筆資料\n", unum); - refresh(); - } - - keymatch = NULL; - - if (!mode) - { - // only verify id - if (!strcasecmp(user.userid, key)) - keymatch = user.userid; - } else { - // search by keytype - if ((!keytype || keytype == 1) && - strcasestr(user.userid, key)) - keymatch = user.userid; - else if ((!keytype || keytype == 2) && - strcasestr(user.realname, key)) - keymatch = user.realname; - else if ((!keytype || keytype == 3) && - strcasestr(user.nickname, key)) - keymatch = user.nickname; - else if ((!keytype || keytype == 4) && - strcasestr(user.address, key)) - keymatch = user.address; - else if ((!keytype || keytype == 5) && - strcasestr(user.email, key)) - keymatch = user.email; - else if ((!keytype || keytype == 6) && - strcasestr(user.lasthost, key)) - keymatch = user.lasthost; - else if ((!keytype || keytype == 7) && - strcasestr(user.justify, key)) - keymatch = user.justify; - } - - if(keymatch) { - move(1, 0); - prints("第 [%d] 筆資料\n", unum); - refresh(); - - user_display(&user, 1); - // user_display does not have linefeed in tail. - - if (isCurrentPwd && HasUserPerm(PERM_ACCOUNTS)) - uinfo_query(&user, 1, unum); - else - outs("\n"); - - 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-1brdname); -} - -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/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(GLOBAL_SECURITY, bh.title, "請注意刪除的合法性", "[系統安全局]"); - assert(0<=bid-1 && bid-1 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 %s\n" - "板主: %s => %s\n", - bh.brdname, newbh.brdname, bh.BM, newbh.BM); - post_msg(GLOBAL_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 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-1number, 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人:%d"MONEYNAME"幣)", 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人:%d"MONEYNAME"幣)", count, total_money); - strcat(reason, buf); - - } - - fclose(fp2); - - sprintf(buf, "%s 紅包機: %s", cuser.userid, reason); - post_file(GLOBAL_SECURITY, buf, "etc/givemoney.log", "[紅包機報告]"); - pressanykey(); - return FULLUPDATE; -} diff --git a/mbbsd/aids.c b/mbbsd/aids.c deleted file mode 100644 index 56d19378..00000000 --- a/mbbsd/aids.c +++ /dev/null @@ -1,290 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#error "Not complete yet" - -aidu_t fn2aidu(char *fn) -{ - aidu_t aidu = 0; - aidu_t type = 0; - aidu_t v1 = 0; - aidu_t v2 = 0; - char *sp = fn; - - if(fn == NULL) - return 0; - - switch(*(sp ++)) - { - case 'M': - type = 0; - break; - case 'G': - type = 1; - break; - default: - return 0; - break; - } - - if(*(sp ++) != '.') - return 0; - v1 = strtoul(sp, &sp, 10); - if(sp == NULL) - return 0; - if(*sp != '.' || *(sp + 1) != 'A') - return 0; - sp += 2; - if(*(sp ++) == '.') - { - v2 = strtoul(sp, &sp, 16); - if(sp == NULL) - return 0; - } - aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffff) << 12) | (v2 & 0xfff); - - return aidu; -} - -/* IMPORTANT: - * size of buf must be at least 8+1 bytes - */ -char *aidu2aidc(char *buf, aidu_t aidu) -{ - const char aidu2aidc_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"; - const int aidu2aidc_tablesize = sizeof(aidu2aidc_table) - 1; - char *sp = buf + 8; - aidu_t v; - - *(sp --) = '\0'; - while(sp >= buf) - { - /* FIXME: 能保證 aidu2aidc_tablesize 是 2 的冪次的話, - 這裡可以改用 bitwise operation 做 */ - v = aidu % aidu2aidc_tablesize; - aidu = aidu / aidu2aidc_tablesize; - *(sp --) = aidu2aidc_table[v]; - } - return buf; -} - -/* IMPORTANT: - * size of fn must be at least FNLEN bytes - */ -char *aidu2fn(char *fn, aidu_t aidu) -{ - aidu_t type = ((aidu >> 44) & 0xf); - aidu_t v1 = ((aidu >> 12) & 0xffffffff); - aidu_t v2 = (aidu & 0xfff); - - if(fn == NULL) - return NULL; - snprintf(fn, FNLEN, "%c.%d.A.%03X", ((type == 0) ? 'M' : 'G'), (unsigned int)v1, (unsigned int)v2); - return fn; -} - -aidu_t aidc2aidu(char *aidc) -{ - char *sp = aidc; - aidu_t aidu = 0; - - if(aidc == NULL) - return 0; - - while(*sp != '\0' && /* ignore trailing spaces */ *sp != ' ') - { - aidu_t v = 0; - /* FIXME: 查表法會不會比較快? */ - if(*sp >= '0' && *sp <= '9') - v = *sp - '0'; - else if(*sp >= 'A' && *sp <= 'Z') - v = *sp - 'A' + 10; - else if(*sp >= 'a' && *sp <= 'z') - v = *sp - 'a' + 36; - else if(*sp == '-') - v = 62; - else if(*sp == '_') - v = 63; - else if(*sp == '@') - break; - else - return 0; - aidu <<= 6; - aidu |= (v & 0x3f); - sp ++; - } - - return aidu; -} - -int search_aidu_in_bfile(char *bfile, aidu_t aidu) -{ - char fn[FNLEN]; - int fd; - fileheader_t fhs[64]; - int len, i; - int pos = 0; - int found = 0; - int lastpos = 0; - - if(aidu2fn(fn, aidu) == NULL) - return -1; - if((fd = open(bfile, O_RDONLY, 0)) < 0) - return -1; - - while(!found && (len = read(fd, fhs, sizeof(fhs))) > 0) - { - len /= sizeof(fileheader_t); - for(i = 0; i < len; i ++) - { - int l; - if(strcmp(fhs[i].filename, fn) == 0 || - ((aidu & 0xfff) == 0 && (l = strlen(fhs[i].filename)) > 6 && - strncmp(fhs[i].filename, fn, l) == 0)) - { - if(fhs[i].filemode & FILE_BOTTOM) - { - lastpos = pos; - } - else - { - found = 1; - break; - } - } - pos ++; - } - } - close(fd); - - return (found ? pos : (lastpos ? lastpos : -1)); -} - -SearchAIDResult_t search_aidu_in_board(char *bname, aidu_t aidu) -{ - SearchAIDResult_t r = {AIDR_BOARD, -1}: - int n = -1; - char dirfile[PATHLEN]; - - { - char bf[FNLEN]; - - snprintf(bf, FNLEN, "%s.bottom", FN_DIR); - setbfile(dirfile, bname, bf); - if((n = search_aidu_in_bfile(dirfile, aidu)) >= 0) - { - r.where = AIDR_BOTTOM; - r.n = n; - } - } - if(r.n < 0) - { - setbfile(dirfile, bname, FN_DIR); - if((n = search_aidu_in_bfile(dirfile, aidu)) >= 0) - { - r.where = AIDR_BOARD; - r.n = n; - } - } - if(r.n < 0) - { - setbfile(dirfile, bname, fn_mandex); - if((n = search_aidu_in_bfile(dirfile, aidu)) >= 0) - { - r.where = AIDR_DIGEST; - r.n = n; - } - } - return r; -} - -SearchAIDResult_t do_search_aid(void) -{ - SearchAIDResult_t r = {AIDR_BOARD, -1}; - char aidc[100]; - char bname[IDLEN + 1] = ""; - aidu_t aidu = 0; - char *sp; - char *sp2; - char *emsg = NULL; - - if(!getdata(b_lines, 0, "搜尋" AID_DISPLAYNAME ": #", aidc, 15 + IDLEN, LCECHO)) - { - move(b_lines, 0); - clrtorol(); - r.n = -1; - return r; - } - - if(currstat == RMAIL) - { - move(21, 0); - clrtobot(); - move(22, 0); - prints("此狀態下無法搜尋" AID_DISPLAYNAME); - pressanykey(); - r.n = -1; - return r; - } - - sp = aidc; - while(*sp == ' ') - sp ++; - while(*sp == '#') - sp ++; - aidu = aidc2aidu(sp); - if((sp2 = strchr(sp, '@')) != NULL) - { - // assert(sizeof(bname) > IDLEN); - strlcpy(bname, sp2 + 1, IDLEN+1); - *sp2 = '\0'; - } - else - bname[0] = '\0'; - - if(aidu > 0) - { - if(bname[0] != '\0') - { - if(!HasBoardPerm_bn(bname)) - return FULLUPDATE; - r = search_aidu_in_board(bname, aidu); - if(r.n >= 0) - { - if(enter_board(bname) < 0) - { - r.n = -1; - emsg = "錯誤:無法進入指定的看板 %s"; - } - } - } - else - { - r = search_aidu_in_board(currboard, aidu); - } - } - - if(r.n < 0) - { - if(aidu == 0) - emsg = "不合法的" AID_DISPLAYNAME ",請確定輸入是正確的"; - else if(emsg == NULL) - { - if(bname[0] != '\0') - emsg = "看板 %s 內找不到這個" AID_DISPLAYNAME ",可能是文章已經消失,或是找錯看板了"; - else - emsg = "找不到這個" AID_DISPLAYNAME ",可能是文章已經消失,或是找錯看板了"; - } - move(21, 0); - clrtoeol(); - move(22, 0); - prints(emsg, bname); - pressanykey(); - r.n = -1; - return r; - } - else - { - return r; - } -} diff --git a/mbbsd/alloc.c b/mbbsd/alloc.c deleted file mode 100644 index de676ce4..00000000 --- a/mbbsd/alloc.c +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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 -#include -#include - -#include -#include -#include -#include -#include - -#include /* 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<>__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;inext=(((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 (sizesize=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 (sizesize!=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/mbbsd/announce.c b/mbbsd/announce.c deleted file mode 100644 index fb7db427..00000000 --- a/mbbsd/announce.c +++ /dev/null @@ -1,1558 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -// XXX piaip 2007/12/29 -// 最近發現很多 code 都死在 announce -// 因為進來要看 lastlevel 而非 currbid -// user 可能一進 BBS 直殺郵件->mail_cite->進精華區 -// 於是就爆炸 -// 同理 currboard 也不該用 -// 請改用 me.bid (注意 me.bid 可能為 0, 表示進來的非看板。) -// -// XXX 9999 麻煩想個方式改掉 - -// for max file size limitation here, see edit.c -#define MAX_FILE_SIZE (32768*1024) - -/* 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 int -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 - - // if len < 0, the directory is not valid anymore. - if (len < 0) - return 0; - - if (len < pm->header_size) - bzero(&pm->header[len], FHSZ * (pm->header_size - len)); - return 1; -} - -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 int -a_showmenu(menu_t * pm) -{ - char *title, *editor; - int n; - fileheader_t *item; - time4_t dtime; - - showtitle("精華文章", pm->mtitle); - prints(" " ANSI_COLOR(1;36) "編號 標 題%56s" ANSI_COLOR(0), - "編 選 日 期"); - - if (!pm->num) - { - outs("\n 《精華區》尚在吸取天地間的日月精華中... :)"); - } - else - { - char buf[PATHLEN]; - - // determine if path is valid. - if (!a_loadname(pm)) - return 0; - - 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); - } - } - - 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); - } - return 1; -} - -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; - - if (!a_loadname(pm)) - return pm->now; - } - 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 - -static void -a_newitem(menu_t * pm, int mode) -{ - char *mesg[3] = { - "[新增文章] 請輸入標題:", /* ADDITEM */ - "[新增目錄] 請輸入標題:", /* ADDGROUP */ - }; - - char fpath[PATHLEN]; - fileheader_t item; - - strlcpy(fpath, pm->path, sizeof(fpath)); - if (strlen(pm->path) + FNLEN*2 >= PATHLEN) - return; - - 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; - } - - 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: - { - int edflags = 0; -# ifdef GLOBAL_BBSMOVIE - if (pm && pm->bid && - strcmp(getbcache(pm->bid)->brdname, - GLOBAL_BBSMOVIE) == 0) - { - edflags |= EDITFLAG_UPLOAD; - edflags |= EDITFLAG_ALLOWLARGE; - } -# endif // GLOBAL_BBSMOVIE - if (vedit2(fpath, 0, NULL, edflags) == -1) { - unlink(fpath); - pressanykey(); - return; - } - } - break; - case ADDGROUP: - // do nothing - 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(); - off_t sz; - - if (!dashf(cq->copyfile)) { - vmsg("目錄不得附加於檔案後!"); - return; - } - - snprintf(fname, sizeof(fname), "%s/%s", pm->path, - pm->header[pm->now - pm->page].filename); - - if (!dashf(fname)) { - vmsg("檔案不得附加於此!"); - return; - } - - sz = dashs(fname); - if (sz >= MAX_FILE_SIZE) - { - vmsg("檔案已超過最大限制,無法再附加"); - return; - } - - 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' || !(fp = fopen(fname, "a+"))) - return; - - if (!(fin = fopen(cq->copyfile, "r"))) { - fclose(fp); - return; - } - - 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); - fclose(fp); - cq->copyfile[0] = '\0'; - } -} - -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[PATHLEN], buf[PATHLEN]; - - if (TagBoard == 0){ - sethomedir(dirname, cuser.userid); - } - else{ - bh = getbcache(TagBoard); - setbdir(dirname, bh->brdname); - } - tagnum = TagNum; - - // prevent if anything wrong - if (tagnum > MAXTAGS || tagnum < 0) - { - vmsg("內部錯誤。請把你剛剛進行的完整步驟貼到 " - GLOBAL_BUGREPORT " 板。"); - return ans; - } - - if (tagnum < 1) - return ans; - - /* since we use different tag features, - * copyqueue is not required/used. */ - copyqueue_reset(); - - while (tagnum-- > 0) { - memset(&fhdr, 0, sizeof(fhdr)); - EnumTagFhdr(&fhdr, dirname, ent++); - - // XXX many process crashed here as fhdr.filename[0] == 0 - // let's workaround it? or trace? - // if (!fhdr->filename[0]) - // continue; - - if (!fhdr.filename[0]) - { - grayout(0, b_lines-2, GRAYOUT_DARK); - move(b_lines-1, 0); clrtobot(); - prints("第 %d 項處理發生錯誤。 請把你剛剛進行的完整步驟貼到 " - GLOBAL_BUGREPORT " 板。\n", ent); - vmsg("忽略錯誤並繼續進行。"); - continue; - } - - 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/" FN_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/" FN_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]; - /* board friend only effact when in board reading mode */ - if (me->level >= MANAGER) - return 1; - if (fhdr->filemode & FILE_BM) - return 0; - if (fhdr->filemode & FILE_HIDE) - { - if (currstat == ANNOUNCE || - !is_hidden_board_friend(me->bid, currutmp->uid)) - return 0; - } - return 1; -} -int -a_menu(const char *maintitle, const char *path, - int lastlevel, int lastbid, - char *trans_buffer) -{ - static char Fexit; // 用來跳出 recursion - menu_t me; - char fname[PATHLEN]; - int ch, returnvalue = FULLUPDATE; - - // prevent deep resursive directories - if (strlen(path) + FNLEN >= PATHLEN) - { - // it is not save to enter such directory. - return returnvalue; - } - - if(trans_buffer) - trans_buffer[0] = '\0'; - - memset(&me, 0, sizeof(me)); - 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); - me.bid = lastbid; - - /* 精華區-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)); - if (!a_showmenu(&me)) - { - // some directories are invalid, restart! - Fexit = 1; - break; - } - } - 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) { - int edflags = 0; - *quote_file = 0; - -# ifdef GLOBAL_BBSMOVIE - if (me.bid && strcmp(getbcache(me.bid)->brdname, - GLOBAL_BBSMOVIE) == 0) - { - edflags |= EDITFLAG_UPLOAD; - edflags |= EDITFLAG_ALLOWLARGE; - } -# endif // GLOBAL_BBSMOVIE - - if (vedit2(fname, NA, NULL, edflags) != -1) { - char fpath[PATHLEN]; - fileheader_t fhdr; - strlcpy(fpath, path, sizeof(fpath)); - stampfile(fpath, &fhdr); - unlink(fpath); - strlcpy(fhdr.filename, - me.header[me.now - me.page].filename, - sizeof(fhdr.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) && - (me.bid==0 || !is_BM_cache(me.bid)) && 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_filef(FN_USSONG, LOG_CREAT, "%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, me.bid, 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; - - } - - if (me.level >= MANAGER) { - 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; -#ifdef BLOG - case 'b': - if (me.bid) - { - char genbuf[128]; - char *bname = getbcache(me.bid)->brdname; - snprintf(genbuf, sizeof(genbuf), - "bin/builddb.pl -f -n %d %s", me.now, bname); - system(genbuf); - vmsg("資料更新完成"); - } - me.page = 9999; - break; - - case 'B': - if (me.bid && me.bid == currbid) - { - BlogMain(me.now); - }; - me.page = 9999; - break; -#endif - } - - 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 '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), - 0, - NULL); - return 0; -} - -#ifdef BLOG -#include -void BlogMain(int num) -{ - int oldmode = currutmp->mode; - char genbuf[128], exit = 0; - - // WARNING: 要確認 currboard/currbid 已正確設定才能用此API。 - - //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, "/" FN_DIR); - append_record(adir, &item, FHSZ); - - snprintf(buf, sizeof(buf), - "cp -R etc/Blog.Default/" FN_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/mbbsd/args.c b/mbbsd/args.c deleted file mode 100644 index bbd6be21..00000000 --- a/mbbsd/args.c +++ /dev/null @@ -1,73 +0,0 @@ -/* $Id$ */ -#include "bbs.h" -#ifdef HAVE_SETPROCTITLE - -void -initsetproctitle(int argc, char **argv, char **envp) -{ -} - -#else - - -static char **Argv = NULL; /* pointer to argument vector */ -static int argv_size; /* 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) - argv_size = envp[i - 1] + strlen(envp[i - 1]) - Argv[0]; - else - argv_size = argv[argc - 1] + strlen(argv[argc - 1]) - Argv[0]; -} - -static void -do_setproctitle(const char *cmdline) -{ - int len; - - len = strlen(cmdline) + 1; // +1 for '\0' - if(len > argv_size - 2) // 2 ?? - len = argv_size - 2; - memset(Argv[0], 0, argv_size); - 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/mbbsd/assess.c b/mbbsd/assess.c deleted file mode 100644 index 4d63a8d8..00000000 --- a/mbbsd/assess.c +++ /dev/null @@ -1,268 +0,0 @@ -/* $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 - -// how long is AID? see read.c... -#ifndef AIDC_LEN -#define AIDC_LEN (20) -#endif // AIDC_LEN - -// #define MAXGP (100) -#define MAXGP (SALE_MAXVALUE) - -int -u_fixgoodpost(void) -{ - char endinput = 0; - unsigned int newgp = 0; - int bid; - char bname[IDLEN+1]; - char xaidc[AIDC_LEN+1]; - - aidu_t gpaids[MAXGP+1]; - int gpbids[MAXGP+1]; - int cgps = 0; - - clear(); - stand_title("自動優文修正程式"); - - outs("開始修正優文之前,有些功\課要麻煩您先查好:\n\n" - "請先找到你所有的優文文章的看板與" AID_DISPLAYNAME "\n" - AID_DISPLAYNAME "的查詢方法是在該篇文章前面按下大寫 Q 。\n" - "查好後請把這些資料放在手邊,等下會請您輸入。\n" - "另外,若有多重登入請先關閉其它連線。\n" - "\n"); - outs("如果一切都準備好了,請按下 y 開始,或其它任意鍵跳出。\n\n"); - if (getans("優文的資料都查好了嗎?") != 'y') - { - vmsg("跳出修正程式。"); - return 0; - } - while (!endinput && newgp < MAXGP) - { - int y, x = 0; - boardheader_t *bh = NULL; - - move(1, 0); clrtobot(); - outs("請依序輸入優文資訊,全部完成後按 ENTER 即可停止。\n"); - - move(b_lines-2, 0); clrtobot(); - prints("目前已確認優文數目: %d" ANSI_RESET "\n\n", newgp); - - if (!getdata(5, 0, "請輸入優文文章所在看板名稱: ", - bname, sizeof(bname), DOECHO)) - { - move(5, 0); - if (getans(ANSI_COLOR(1;33)"確定全部輸入完成了嗎? " - ANSI_RESET "[y/N]: ") != 'y') - continue; - endinput = 1; break; - } - move (6, 0); - outs("確認看板... "); - if (bname[0] == '\0' || !(bid = getbnum(bname))) - { - outs(ANSI_COLOR(1;31) "看板不存在!"); - vmsg("請重新輸入。"); - continue; - } - assert(0<=bid-1 && bid-1brdname, sizeof(bname)); - prints("已找到看板 --> %s\n", bname); - getyx(&y, &x); - - // loop AID query - while (newgp < MAXGP) - { - int n; - int fd; - char dirfile[PATHLEN]; - char *sp; - aidu_t aidu = 0; - fileheader_t fh; - - move(y, 0); clrtobot(); - move(b_lines-2, 0); clrtobot(); - prints("目前已確認優文數目: %d" ANSI_RESET "\n\n", newgp); - - if (getdata(y, 0, "請輸入" AID_DISPLAYNAME ": #", - xaidc, AIDC_LEN, DOECHO) == 0) - break; - - sp = xaidc; - while(*sp == ' ') sp ++; - if(*sp == '#') sp ++; - - if((aidu = aidc2aidu(sp)) <= 0) - { - outs(ANSI_COLOR(1;31) AID_DISPLAYNAME "格式不正確!"); - vmsg("請重新輸入。"); - continue; - } - - // check repeated input of same board+AID. - for (n = 0; n < cgps; n++) - { - if (gpaids[n] == aidu && gpbids[n] == bid) - { - vmsg("您已輸入過此優文了,請重新輸入。"); - aidu = 0; - break; - } - } - - if (aidu <= 0) - continue; - - // find aidu in board - n = -1; - // see read.c, search .bottom first. - if (n < 0) - { - outs("搜尋置底文章..."); - setbfile(dirfile, bname, FN_DIR ".bottom"); - n = search_aidu(dirfile, aidu); - } - if (n < 0) { - // search board - outs("未找到。\n搜尋看板文章.."); - setbfile(dirfile, bname, FN_DIR); - n = search_aidu(dirfile, aidu); - } - if (n < 0) - { - // search digest - outs("未找到。\n搜尋文摘.."); - setbfile(dirfile, currboard, fn_mandex); - n = search_aidu(dirfile, aidu); - } - if (n < 0) - { - // search failed... - outs("未找到\n" ANSI_COLOR(1;31) "找不到文章!"); - vmsg("請確認後重新輸入。"); - continue; - } - - // found something - fd = open(dirfile, O_RDONLY); - if (fd < 0) - { - outs(ANSI_COLOR(1;31) "系統錯誤。 請稍候再重試。\n"); - vmsg("若持續發生請至" GLOBAL_BUGREPORT "報告。"); - continue; - } - - lseek(fd, n*sizeof(fileheader_t), SEEK_SET); - memset(&fh, 0, sizeof(fh)); - read(fd, &fh, sizeof(fh)); - outs("\n開始核對資料...\n"); - n = 1; - if (strcmp(fh.owner, cuser.userid) != 0) - n = 0; - prints("作者: %s (%s)\n", fh.owner, n ? "正確" : - ANSI_COLOR(1;31) "錯誤" ANSI_RESET); - if (!(fh.filemode & FILE_MARKED)) - n = 0; - prints("M文: %s\n", (fh.filemode & FILE_MARKED) ? "正確" : - ANSI_COLOR(1;31) "錯誤" ANSI_RESET); - prints("推薦: %d\n", fh.recommend); - close(fd); - if (!n) - { - vmsg("輸入的文章並非優文,請重新輸入。"); - continue; - } - n = fh.recommend / 10; - prints("計算優文數值: %+d\n", n); - - if (n > 0) - { - // log new data - newgp += n; - gpaids[cgps] = aidu; - gpbids[cgps] = bid; - cgps ++; - } - - clrtobot(); - - - vmsg("優文已確認。若要輸入其它看板文章請在AID欄空白按 ENTER"); - } - vmsgf("%s 看板輸入完成。", bname); - } - if (newgp > MAXGP) - newgp = MAXGP; - if (newgp <= cuser.goodpost) - { - vmsg("確認優文數目未高於已有優文數,不調整。"); - } else { - log_filef("log/fixgoodpost.log", LOG_CREAT, - "%s %s 自動修正優文數: 由 %d 變為 %d\n", Cdate(&now), cuser.userid, - cuser.goodpost, newgp); - cuser.goodpost = newgp; - // update passwd file here? - passwd_force_update(ALERT_PWD_GOODPOST); - passwd_update(usernum, &cuser); - vmsgf("更新優文數目為%d。", newgp); - } - - return 0; -} - -#endif diff --git a/mbbsd/bbs.c b/mbbsd/bbs.c deleted file mode 100644 index 83cdb5c0..00000000 --- a/mbbsd/bbs.c +++ /dev/null @@ -1,3986 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#ifdef EDITPOST_SMARTMERGE - -#include "fnv_hash.h" -#define SMHASHLEN (64/8) - -#endif // EDITPOST_SMARTMERGE - -#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); -static int view_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln); - -#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, FN_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; -} - -// lite weight version to update dir files -static int -modify_dir_lite( - const char *direct, int ent, const char *fhdr_name, - time4_t modified, const char *title, char recommend) -{ - // since we want to do 'modification'... - int fd; - off_t sz = dashs(direct); - fileheader_t fhdr; - - // TODO lock? - // PttLock(fd, offset, size, F_WRLCK); - // write(fd, rptr, size); - // PttLock(fd, offset, size, F_UNLCK); - - // prevent black holes - if (sz < sizeof(fileheader_t) * (ent) || - (fd = open(direct, O_RDWR)) < 0 ) - return -1; - - // also check if fhdr->filename is same. - // let sz = base offset - sz = (sizeof(fileheader_t) * (ent-1)); - if (lseek(fd, sz, SEEK_SET) < 0 || - read(fd, &fhdr, sizeof(fhdr)) != sizeof(fhdr) || - strcmp(fhdr.filename, fhdr_name) != 0) - { - close(fd); - return -1; - } - - // update records - if (modified > 0) - fhdr.modified = modified; - - if (title && *title) - strcpy(fhdr.title, title); - - if (recommend) - { - recommend += fhdr.recommend; - if (recommend > MAX_RECOMMENDS) recommend = MAX_RECOMMENDS; - else if (recommend < -MAX_RECOMMENDS) recommend = -MAX_RECOMMENDS; - fhdr.recommend = recommend; - } - - if (lseek(fd, sz, SEEK_SET) >= 0) - write(fd, &fhdr, sizeof(fhdr)); - - close(fd); - - return 0; -} - -static void -check_locked(fileheader_t *fhdr) -{ - boardheader_t *bp = NULL; - - if (currstat == RMAIL) - return; - if (!currboard[0] || currbid <= 0) - return; - bp = getbcache(currbid); - if (!bp) - return; - if (!(fhdr->filemode & FILE_SOLVED)) - return; - if (!(fhdr->filemode & FILE_MARKED)) - return; - syncnow(); - bp->SRexpire = now; -} - -/* 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_filef("etc/illegal_money", LOG_CREAT, - 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, BBSMNAME "系統警察", - "Cross-post", "罰單處份"); - cuser.userlevel |= PERM_VIOLATELAW; - cuser.timeviolatelaw = now; - cuser.vl_count++; - mail_id(cuser.userid, "Cross-Post罰單", - "etc/crosspost.txt", BBSMNAME "警察部隊"); - 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); - prints("這是你第 %d 次違法 必須繳出 %d $Ptt\n\n", - cuser.vl_count, cuser.vl_count * 1000); - 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] != 'y') - { - move(15, 0); - outs( ANSI_COLOR(1;31) "不想付錢嗎? 還是不知道要按 y ?\n" - "請養成看清楚系統訊息的好習慣。\n" - "等你想通了再來吧!! 我相信你不會知錯不改的~~~" ANSI_RESET); - pressanykey(); - return 0; - } - - //Ptt:check one more time - reload_money(); - if (cuser.money < (int)cuser.vl_count * 1000) - { - log_filef("log/violation", LOG_CREAT, - "%24.24s %s pay-violation error: race-conditionn hack?\n", - ctime4(&now), cuser.userid); - vmsg("錢怎麼忽然不夠了? 試圖欺騙系統被查到將砍帳號!"); - return 0; - } - - demoney(-1000 * cuser.vl_count); - cuser.userlevel &= (~PERM_VIOLATELAW); - // force overriding alerts - if(currutmp) - currutmp->alerts &= ~ALERT_PWD_PERM; - passwd_update(usernum, &cuser); - sendalert(cuser.userid, ALERT_PWD_PERM); - log_filef("log/violation", LOG_CREAT, - "%24.24s %s pay-violation: $%d complete.\n", - ctime4(&now), cuser.userid, (int)cuser.vl_count*1000); - - vmsg("罰單已付,請盡速重新登入。"); - 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) && - !is_hidden_board_friend((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; - } -} - -int IsFreeBoardName(const char *brdname) -{ - if (strcmp(currboard, GLOBAL_TEST) == 0) - return 1; - if (strcmp(currboard, ALLPOST) == 0) - return 1; - return 0; -} - -/* 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_DIGEST) - return 0; - - 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-1perm_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); -} - -int CheckPostRestriction(int bid) -{ - boardheader_t *bp; - if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) - return 1; - assert(0<=bid-1 && bid-1 (now - (time4_t)bp->post_limit_regtime * 2592000)) - return 0; - if (cuser.numlogins / 10 < (unsigned int)bp->post_limit_logins) - return 0; - if (cuser.numposts / 10 < (unsigned int)bp->post_limit_posts) - return 0; - if (cuser.badpost > (255 - (unsigned int)bp->post_limit_badpost)) - return 0; - - return 1; -} - -static void -readtitle(void) -{ - boardheader_t *bp; - char *brd_title; - - assert(0<=currbid-1 && currbid-1bvote != 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-1bcache[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]; - char *typeattr = ""; - char isunread = 0, oisunread = 0; - - oisunread = isunread = - brc_unread(currbid, ent->filename, ent->modified); - - // modified tag - if (isunread == 2) - { - // ignore unread, if user doesn't want to show it. - if (cuser.uflag & NO_MODMARK_FLAG) - { - oisunread = isunread = 0; - } - // if user wants colored marks, use 'read' marks - else if (cuser.uflag & COLORED_MODMARK) - { - isunread = 0; - typeattr = ANSI_COLOR(36); - } - } - - type = isunread ? '+' : ' '; - if (isunread == 2) type = '~'; - - // handle 'type" - 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 if (isunread == 0) - type = 'm'; - else if (isunread == 1) - type = 'M'; - else if (isunread == 2) - type = '='; - } else if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN)) - type = 'D'; - else if (ent->filemode & FILE_SOLVED) - type = (type == ' ') ? 's': 'S'; - } - - // the only special case: ' ' with isunread == 2, - // change to '+' with gray attribute. - if (type == ' ' && oisunread == 2) - { - typeattr = ANSI_COLOR(1;30); - type = '+'; - } - - 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; - - isonline = query_online(ent->owner); - - if(ent->recommend >= MAX_RECOMMENDS) - 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 <= -MAX_RECOMMENDS) - 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. - * XXX if we are in big term, enlarge here. - */ - prints("%7d", num); - - prints(" %s%c" ESC_STR "[0;1;3%4.4s" ANSI_RESET, - typeattr, 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) "%-6.5s" ANSI_RESET, - (ent->date[3] + ent->date[4]) % 7 + 31, ent->date); -#else - prints("%-6.5s", 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(ANSI_COLOR(1;3%c) "%s %s" ANSI_RESET "\n", - color, mark, title); -} - -int -whereami(void) -{ - boardheader_t *bh, *p[WHEREAMI_LEVEL]; - int i, j; - int bid = currbid; - - if (!bid) - return 0; - - move(1, 0); - clrtobot(); - assert(0<=bid-1 && bid-1parent>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]; - - setutmpmode(SELECT); - move(0, 0); - clrtoeol(); - CompleteBoard(MSG_SELECT_BOARD, bname); - - if(enter_board(bname) < 0) - return FULLUPDATE; - - 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_filef(fn1, LOG_CREAT, "\n※ Deleted by: %s (%s) %d/%d", - cuser.userid, fromhost, ptime->tm_mon + 1, ptime->tm_mday); - Rename(fn1, newpath); - setbdir(genbuf, brd); - append_record(genbuf, &postfile, sizeof(postfile)); - setbtotal(getbnum(brd)); - } -} - -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); - setbtotal(bid); - unlink(file); - } -} - -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(const 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); - } -} - -/* ----------------------------------------------------- */ -/* 發表、回應、編輯、轉錄文章 */ -/* ----------------------------------------------------- */ -static int -solveEdFlagByBoard(const char *bn, int flags) -{ - if ( -#ifdef GLOBAL_BBSMOVIE - strcmp(bn, GLOBAL_BBSMOVIE) == 0 || -#endif -#ifdef GLOBAL_TEST - strcmp(bn, GLOBAL_TEST) == 0 || -#endif - 0 - ) - { - flags |= EDITFLAG_UPLOAD | EDITFLAG_ALLOWLARGE; - } - return flags; -} - -void -do_reply_title(int row, const char *title) -{ - char genbuf[200]; - char genbuf2[4]; - char tmp_title[STRLEN]; - - if (strncasecmp(title, str_reply, 4)) - snprintf(tmp_title, sizeof(tmp_title), "Re: %s", title); - else - strlcpy(tmp_title, title, sizeof(tmp_title)); - tmp_title[TTLEN - 1] = '\0'; - snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", tmp_title); - getdata(row, 0, genbuf, genbuf2, 4, LCECHO); - if (genbuf2[0] == 'n' || genbuf2[0] == 'N') - getdata(++row, 0, "標題:", tmp_title, TTLEN, DOECHO); - // don't getdata() on non-local variable save_title directly, to avoid reentrant crash. - strlcpy(save_title, tmp_title, sizeof(save_title)); -} - -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[PATHLEN]; - 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." MONEYNAME "幣 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]={MONEYNAME "幣", "郵局或銀行轉帳", - "支票或電匯", "郵局貨到付款"}; - 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[PATHLEN], buf[STRLEN]; - int aborted, defanony, ifuseanony, i; - char genbuf[PATHLEN], *owner; - char ctype[8][5] = {"問題", "建議", "討論", "心得", - "閒聊", "請益", "公告", "情報"}; - boardheader_t *bp; - int islocal, posttype=-1, edflags = 0; - - ifuseanony = 0; - assert(0<=currbid-1 && currbid-1brdname, GLOBAL_FOREIGN) == 0) -#endif - ) { - vmsg("對不起,您目前無法在此發表文章!"); - return READ_REDRAW; - } - -#ifndef DEBUG - if ( !CheckPostRestriction(currbid) ) - { - 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 { - char tmp_title[STRLEN]=""; - 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= 0 && posttype < i) - snprintf(tmp_title, sizeof(tmp_title), - "[%s] ", ctype[posttype]); - else - { - tmp_title[0] = '\0'; - posttype=-1; - } - } - getdata_buf(22, 0, "標題:", tmp_title, TTLEN, DOECHO); - strip_ansi(tmp_title, tmp_title, STRIP_ALL); - if( strcmp(tmp_title, "[711iB] 增加上站次數程式") == 0 ){ - cuser.userlevel |= PERM_VIOLATELAW; - sleep(60); - u_exit("bad program"); - } - strlcpy(save_title, tmp_title, sizeof(save_title)); - } - 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_f)) { - setbnfile(genbuf, bp->brdname, "postsample", posttype); - Copy(genbuf, fpath); - } - - edflags = EDITFLAG_ALLOWTITLE; - edflags = solveEdFlagByBoard(currboard, edflags); - - aborted = vedit2(fpath, YEA, &islocal, edflags); - 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 */ - postfile.modified = dasht(fpath); - 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); - - // 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 - strcpy(genbuf, fpath); - setbpath(fpath, currboard); - stampfile_u(fpath, &postfile); - - // warning: filename should be retrieved from new fpath. - if(isbid) { - char bidfn[PATHLEN] = ""; - sprintf(bidfn, "%s.bid", fpath); - append_record(bidfn,(void*) &bidinfo, sizeof(bidinfo)); - postfile.filemode |= FILE_BID ; - } - - if (append_record(buf, &postfile, sizeof(postfile)) == -1) - { - unlink(genbuf); - } - else - { - char addPost = 0; - 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, postfile.modified); - - 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 (!IsFreeBoardName(currboard) && !ifuseanony && - !(currbrdattr&BRD_BAD)) { - - if(postfile.filemode&FILE_BID) - outs("招標文章沒有稿酬。"); - else if (aborted > 0) - { - demoney(aborted); - addPost = 1; - prints("這是您的第 %d 篇文章,稿酬 %d 銀。", - ++cuser.numposts, aborted); - } else { - // no money, no record. - outs("本篇不列入記錄,敬請包涵。"); - } - } else - outs("不列入記錄,敬請包涵。"); - - /* 回應到原作者信箱 */ - - if (curredit & EDIT_BOTH) { - char *str, *msg = "回應至作者信箱"; - - genbuf[0] = 0; - // XXX quote_user may contain invalid user, like '-' (deleted). - if (is_validuserid(quote_user)) - { - sethomepath(genbuf, quote_user); - if (!dashd(genbuf)) - { - genbuf[0] = 0; - msg = err_uid; - } - } - - // now, genbuf[0] = "if user exists". - if (genbuf[0]) - { - 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); - } else 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 { - // unknown user id - msg = "作者無法收信"; - } - outs(msg); - curredit ^= EDIT_BOTH; - } // if (curredit & EDIT_BOTH) - if (currbrdattr & BRD_ANONYMOUS) - do_crosspost("UnAnonymous", &postfile, fpath, 0); -#ifdef USE_COOLDOWN - if(bp->nuser>30) - { - if (cooldowntimeof(usernum)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-1brdattr & 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-1title, 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; -} - -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 -do_reply(/*const*/ fileheader_t * fhdr) -{ - boardheader_t *bp; - if (!fhdr || !fhdr->filename[0]) - return DONOTHING; - - 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-1brdattr & BRD_NOREPLY) { - // try to reply by mail. - // vmsg("很抱歉, 本板不開放回覆文章."); - // return FULLUPDATE; - return mail_reply(0, fhdr, 0); - } - - 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); -} - -#ifdef EDITPOST_SMARTMERGE - -#define HASHPF_RET_OK (0) - -// return: 0 - ok; otherwise - fail. -static int -hash_partial_file( char *path, size_t sz, unsigned char output[SMHASHLEN] ) -{ - int fd; - size_t n; - unsigned char buf[1024]; - - Fnv64_t fnvseed = FNV1_64_INIT; - assert(SMHASHLEN == sizeof(fnvseed)); - - fd = open(path, O_RDONLY); - if (fd < 0) - return 1; - - while( sz > 0 && - (n = read(fd, buf, sizeof(buf))) > 0 ) - { - if (n > sz) n = sz; - fnvseed = fnv_64_buf(buf, (int) n, fnvseed); - sz -= n; - } - close(fd); - - if (sz > 0) // file is different - return 2; - - memcpy(output, (void*) &fnvseed, sizeof(fnvseed)); - return HASHPF_RET_OK; -} -#endif // EDITPOST_SMARTMERGE - -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); - // int recordTouched = 0; - time4_t oldmt, newmt; - off_t oldsz; - int edflags = 0; - -#ifdef EDITPOST_SMARTMERGE - char canDoSmartMerge = 1; -#endif // EDITPOST_SMARTMERGE - -#ifdef EXP_EDITPOST_TEXTONLY - // experimental: "text only" editing - edflags |= EXP_EDITPOST_TEXTONLY; -#endif - - assert(0<=currbid-1 && currbid-1brdname, GLOBAL_SECURITY) == EQUSTR || - (bp->brdattr & BRD_VOTEBOARD)) - return DONOTHING; - - // file check - if (fhdr->filemode & FILE_VOTE) - return DONOTHING; - -#ifdef SAFE_ARTICLE_DELETE - if( fhdr->filename[0] == '.' ) - return DONOTHING; -#endif - - // user check - if (!HasUserPerm(PERM_BASIC) || // includeing guests - !CheckPostPerm() ) - return DONOTHING; - - if (strcmp(fhdr->owner, cuser.userid) != EQUSTR) - { - if (!HasUserPerm(PERM_SYSOP)) - return DONOTHING; - - // admin edit! - log_filef("log/security", LOG_CREAT, - "%d %24.24s %d %s admin edit (board) file=%s\n", - (int)now, ctime4(&now), getpid(), cuser.userid, fpath); - } - - edflags = EDITFLAG_ALLOWTITLE; - edflags = solveEdFlagByBoard(bp->brdname, edflags); - - setutmpmode(REEDIT); - - - // XXX 不知何時起, edit_post 已經不會有 + 號了... - // 全部都是 Sysop Edit 的原地形式。 - // 哪天有空找個人寫個 mode 是改名 edit 吧 - // - // TODO 由於現在檔案都是直接蓋回原檔, - // 在原看板目錄開已沒有很大意義。 (效率稍高一點) - // 可以考慮改開在 user home dir - // 好處是看板的檔案數不會狂成長。 (when someone crashed) - // sethomedir(fpath, cuser.userid); - // XXX 如果你的系統有定期看板清孤兒檔,那就不用放 user home。 - setbpath(fpath, currboard); - - // XXX 以現在的模式,這是個 temp file - stampfile(fpath, &postfile); - setdirpath(genbuf, direct, fhdr->filename); - local_article = fhdr->filemode & FILE_LOCAL; - - // copying takes long time, add some visual effect - grayout(0, b_lines-2, GRAYOUT_DARK); - move(b_lines-1, 0); clrtoeol(); - outs("正在載入檔案..."); - refresh(); - - Copy(genbuf, fpath); - strlcpy(save_title, fhdr->title, sizeof(save_title)); - - // so far this is what we copied now... - oldmt = dasht(genbuf); - oldsz = dashs(fpath); // should be equal to genbuf(src). - // use fpath (dest) in case some - // modification was made. - do { -#ifdef EDITPOST_SMARTMERGE - - unsigned char oldsum[SMHASHLEN] = {0}, newsum[SMHASHLEN] = {0}; - - // make checksum of file genbuf - if (canDoSmartMerge && - hash_partial_file(fpath, oldsz, oldsum) != HASHPF_RET_OK) - canDoSmartMerge = 0; - -#endif // EDITPOST_SMARTMERGE - - - if (vedit2(fpath, 0, NULL, edflags) == -1) - break; - - newmt = dasht(genbuf); - -#ifdef EDITPOST_SMARTMERGE - - // only merge if file is enlarged and modified - if (newmt == oldmt || dashs(genbuf) < oldsz) - canDoSmartMerge = 0; - - // make checksum of new file [by oldsz] - if (canDoSmartMerge && - hash_partial_file(genbuf, oldsz, newsum) != HASHPF_RET_OK) - canDoSmartMerge = 0; - - // verify checksum - if (canDoSmartMerge && - memcmp(oldsum, newsum, sizeof(newsum)) != 0) - canDoSmartMerge = 0; - - if (canDoSmartMerge) - { - canDoSmartMerge = 0; // only try merge once - - move(b_lines-7, 0); - clrtobot(); - outs(ANSI_COLOR(1;33) "▲ 檔案已被修改過! ▲" ANSI_RESET "\n\n"); - outs("進行自動合併 [Smart Merge]...\n"); - - // smart merge - if (AppendTail(genbuf, fpath, oldsz) == 0) - { - // merge ok - oldmt = newmt; - outs(ANSI_COLOR(1) - "合併成功\,新修改(或推文)已加入您的文章中。\n" - "您沒有蓋\掉任何推文或修改,請勿擔心。" - ANSI_RESET "\n"); - -#ifdef WARN_EXP_SMARTMERGE - outs(ANSI_COLOR(1;33) - "自動合併 (Smart Merge) 是實驗中的新功\能," - "請檢查一下您的文章合併後是否正常。" ANSI_RESET "\n" - "若有問題請至 " GLOBAL_BUGREPORT " 板報告,謝謝。"); -#endif - vmsg("合併完成"); - } else { - outs(ANSI_COLOR(31) - "自動合併失敗。 請改用人工手動編輯合併。" ANSI_RESET); - vmsg("合併失敗"); - } - } - -#endif // EDITPOST_SMARTMERGE - - if (oldmt != newmt) - { - int c = 0; - - move(b_lines-7, 0); - clrtobot(); - outs(ANSI_COLOR(1;31) "▲ 檔案已被修改過! ▲" ANSI_RESET "\n\n"); - - outs("可能是您在編輯的過程中有人進行推文或修文。\n" - "您可以選擇直接覆蓋\檔案(y)、放棄(n),\n" - " 或是" ANSI_COLOR(1)"重新編輯" ANSI_RESET - "(新文會被貼到剛編的檔案後面)(e)。\n"); - c = tolower(getans("要直接覆蓋\檔案/取消/重編嗎 [Y/n/e]?")); - - if (c == 'n') - break; - - if (c == 'e') - { - FILE *fp, *src; - - /* merge new and old stuff */ - fp = fopen(fpath, "at"); - src = fopen(genbuf, "rt"); - - if(!fp) - { - vmsg("抱歉,檔案已損毀。"); - if(src) fclose(src); - unlink(fpath); // fpath is a temp file - return FULLUPDATE; - } - - if(src) - { - int c = 0; - struct tm *ptime; - - fprintf(fp, MSG_SEPERATOR "\n"); - fprintf(fp, "以下為被修改過的最新內容: "); - ptime = localtime4(&newmt); - 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); - - // update oldsz, old mt records - oldmt = dasht(genbuf); - oldsz = dashs(genbuf); - } - fclose(fp); - continue; - } - } - - // OK to save file. - - // piaip Wed Jan 9 11:11:33 CST 2008 - // in order to prevent calling system 'mv' all the - // time, it is better to unlink() first, which - // increased the chance of succesfully using rename(). - // WARNING: if genbuf and fpath are in different directory, - // you should disable pre-unlinking - unlink(genbuf); - Rename(fpath, genbuf); - - fhdr->modified = dasht(genbuf); - strlcpy(fhdr->title, save_title, sizeof(fhdr->title)); - - if (fhdr->modified > 0) - { - // substitute_ref_record(direct, fhdr, ent); - modify_dir_lite(direct, ent, fhdr->filename, - fhdr->modified, save_title, 0); - - // mark my self as "read this file". - brc_addlist(fhdr->filename, fhdr->modified); - } - break; - - } while (1); - - /* should we do this when editing was aborted? */ - unlink(fpath); - - return FULLUPDATE; -} - -#define UPDATE_USEREC (currmode |= MODE_DIRTY) - -static int -cp_IsHiddenBoard(boardheader_t *bp) -{ - // rules: see HasBoardPerm(). - if ((bp->brdattr & BRD_HIDE) && (bp->brdattr & BRD_POSTMASK)) - return 1; - if (bp->level && !(bp->brdattr & BRD_POSTMASK)) - return 1; - return 0; -} - -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, xbid, hashPost; - boardheader_t *bp; - - assert(0<=currbid-1 && currbid-1brdattr & BRD_VOTEBOARD) ) - return FULLUPDATE; - -#ifdef USE_AUTOCPLOG - // anti-crosspost spammers - // - // some spammers try to cross-post to other boards without - // restriction (see pakkei0712* events on 2007/12) - // for (1) increase numpost (2) flood target board - // (3) flood original post - // You must have post permission on current board - // - if( (bp->brdattr & BRD_CPLOG) && - (!CheckPostPerm() || !CheckPostRestriction(currbid))) - { - vmsg("由本板轉錄文章需有發文權限(可按 i 查看限制)"); - return FULLUPDATE; - } -#endif // USE_AUTOCPLOG - - move(2, 0); - clrtoeol(); - if (postrecord.times > 1) - { - outs(ANSI_COLOR(1;31) - "請注意: 若過量重複轉錄將視為洗板,導致被開罰單停權。\n" ANSI_RESET - "若有特別需求請洽各板主,請他們幫你轉文。\n\n"); - } - move(1, 0); - - CompleteBoard("轉錄本文章於看板:", xboard); - if (*xboard == '\0') - return FULLUPDATE; - - if (!haspostperm(xboard)) - { - vmsg("看板不存在或該看板禁止您發表文章!"); - return FULLUPDATE; - } - - /* 不要借用變數,記憶體沒那麼缺,人腦混亂的代價比較高 */ - - // XXX cross-posting a series of articles should not be cross-post? - // so let's use filename instead of title. - // hashPost = StringHash(fhdr->title); // why use title? - hashPost = StringHash(fhdr->filename); // let's try filename - xbid = getbnum(xboard); - assert(0<=xbid-1 && xbid-1= MAX_CROSSNUM) - { - anticrosspost(); - return FULLUPDATE; - } - } - -#ifdef USE_COOLDOWN - if(check_cooldown(getbcache(xbid))) - { - vmsg("該看板現在無法轉錄。"); - 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 (cp_IsHiddenBoard(bp)) - { - /* 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-1tm_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 - { - /* now point bp to new bord */ - xbid = getbnum(xboard); - assert(0<=xbid-1 && xbid-1brdattr & BRD_NOTRAN)) - outgo_post(&xfile, xboard, cuser.userid, cuser.nickname); -#ifdef USE_COOLDOWN - if(bp->nuser>30) - { - if (cooldowntimeof(usernum)owner[0] == '-' || fhdr->filename[0] == 'L' || !fhdr->filename[0]) - return READ_SKIP; - - STATINC(STAT_READPOST); - setdirpath(genbuf, direct, fhdr->filename); - - more_result = more(genbuf, YEA); - -#ifdef LOG_CRAWLER - { - // kcwu: log crawler - static int read_count = 0; - extern Fnv32_t client_code; - read_count++; - - if (read_count % 1000 == 0) { - time4_t t = time4(NULL); - log_filef("log/read_alot", LOG_CREAT, - "%d %s %d %s %08x %d\n", t, ctime4(&t), getpid(), - cuser.userid, client_code, read_count); - } - } -#endif // LOG_CRAWLER - - { - 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, fhdr->modified); - strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle)); - - switch(more_result) - { - case -1: - clear(); - vmsg("此文章無內容"); - return FULLUPDATE; - case RET_DOREPLY: - case RET_DOREPLYALL: - do_reply(fhdr); - return FULLUPDATE; - case RET_DORECOMMEND: - recommend(ent, fhdr, direct); - return FULLUPDATE; - case RET_DOQUERYINFO: - view_postinfo(ent, fhdr, direct, b_lines-3); - return FULLUPDATE; - } - if(more_result) - return more_result; - return FULLUPDATE; -} - -void -editLimits(unsigned char *pregtime, unsigned char *plogins, - unsigned char *pposts, unsigned char *pbadpost) -{ - char genbuf[STRLEN]; - int temp; - - // load var - unsigned char - regtime = *pregtime, - logins = *plogins, - posts = *pposts, - badpost = *pbadpost; - - // query UI - sprintf(genbuf, "%u", regtime); - do { - getdata_buf(b_lines - 1, 0, - "註冊時間限制 (以'月'為單位,0~255):", genbuf, 4, LCECHO); - temp = atoi(genbuf); - } while (temp < 0 || temp > 255); - regtime = (unsigned char)temp; - - sprintf(genbuf, "%u", logins*10); - do { - getdata_buf(b_lines - 1, 0, - "上站次數下限 (0~2550,以10為單位,個位數字將自動捨去):", genbuf, 5, LCECHO); - temp = atoi(genbuf); - } while (temp < 0 || temp > 2550); - logins = (unsigned char)(temp / 10); - - sprintf(genbuf, "%u", posts*10); - do { - getdata_buf(b_lines - 1, 0, - "文章篇數下限 (0~2550,以10為單位,個位數字將自動捨去):", genbuf, 5, LCECHO); - temp = atoi(genbuf); - } while (temp < 0 || temp > 2550); - posts = (unsigned char)(temp / 10); - - sprintf(genbuf, "%u", 255 - badpost); - do { - getdata_buf(b_lines - 1, 0, - "劣文篇數上限 (0~255):", genbuf, 5, LCECHO); - temp = atoi(genbuf); - } while (temp < 0 || temp > 255); - badpost = (unsigned char)(255 - temp); - - // save var - *pregtime = regtime; - *plogins = logins; - *pposts = posts; - *pbadpost = badpost; -} - -int -do_limitedit(int ent, fileheader_t * fhdr, const char *direct) -{ - char buf[STRLEN]; - boardheader_t *bp = getbcache(currbid); - - assert(0<=currbid-1 && currbid-1filemode & FILE_VOTE) - strcat(buf, " (C)本篇"); - strcat(buf, "連署限制 (Q)取消?[Q]"); - buf[0] = getans(buf); - - if ((HasUserPerm(PERM_SYSOP) || (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) && buf[0] == 'a') { - - editLimits( - &bp->post_limit_regtime, - &bp->post_limit_logins, - &bp->post_limit_posts, - &bp->post_limit_badpost); - - assert(0<=currbid-1 && currbid-1brdname); - vmsg("修改完成!"); - return FULLUPDATE; - } - else if (buf[0] == 'b') { - - editLimits( - &bp->vote_limit_regtime, - &bp->vote_limit_logins, - &bp->vote_limit_posts, - &bp->vote_limit_badpost); - - assert(0<=currbid-1 && currbid-1brdname); - vmsg("修改完成!"); - return FULLUPDATE; - } - else if ((fhdr->filemode & FILE_VOTE) && buf[0] == 'c') { - - editLimits( - &fhdr->multi.vote_limits.regtime, - &fhdr->multi.vote_limits.logins, - &fhdr->multi.vote_limits.posts, - &fhdr->multi.vote_limits.badpost); - - 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), - currbid, // getbnum(currboard)? - 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-1endgamble || 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-1brdattr & 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_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-1endgamble ? "賭盤結束時間: " : "", - 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; - int allow = 0; - - // should we allow edit-title here? - if (currstat == RMAIL) - allow = 0; - else if (HasUserPerm(PERM_SYSOP)) - allow = 2; - else if (currmode & MODE_BOARD || - strcmp(cuser.userid, fhdr->owner) == 0) - allow = 1; - - if (!allow) - return DONOTHING; - - if (fhdr && fhdr->title[0]) - strlcpy(genbuf, fhdr->title, TTLEN+1); - - if (getdata_buf(b_lines - 1, 0, "標題:", genbuf, TTLEN, DOECHO)) { - strlcpy(tmpfhdr.title, genbuf, sizeof(tmpfhdr.title)); - dirty++; - } - - if (allow >= 2) - { - 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 (dirty) - { - getdata(b_lines - 1, 0, "確定(Y/N)?[n] ", genbuf, 3, DOECHO); - if ((genbuf[0] == 'y' || genbuf[0] == 'Y') && dirty) { - // TODO verify if record is still valid - fileheader_t curr; - memset(&curr, 0, sizeof(curr)); - if (get_record(direct, &curr, sizeof(curr), ent) < 0 || - strcmp(curr.filename, fhdr->filename) != 0) - { - // modified... - vmsg("抱歉,系統忙碌中,請稍後再試。"); - return FULLUPDATE; - } - *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); - check_locked(fhdr); - 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)); - sendalert(fhdr->owner, ALERT_PWD_GOODPOST); - } -#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; - } - - // XXX do lock some day! - - /* This is a solution to avoid most racing (still some), but cost four - * system calls. */ - - if(type == 0 && fhdr->recommend < MAX_RECOMMENDS ) - update = 1; - else if(type == 1 && fhdr->recommend > -MAX_RECOMMENDS) - update = -1; - fhdr->recommend += update; - - // since we want to do 'modification'... - fhdr->modified = dasht(path); - - if (fhdr->modified > 0) - { - if (modify_dir_lite(direct, ent, fhdr->filename, - fhdr->modified, NULL, update) < 0) - return -1; - // mark my self as "read this file". - brc_addlist(fhdr->filename, fhdr->modified); - } - - 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"); - memset(&bidinfo, 0, sizeof(bidinfo)); - if (get_record(fpath, &bidinfo, sizeof(bidinfo), 1) < 0) - { - vmsg("系統錯誤: 競標資訊已遺失,請重開新標。"); - return FULLUPDATE; - } - - move(18,0); - clrtobot(); - prints("競標主題: %s\n", fhdr->title); - print_bidinfo(0, bidinfo); - money = bidinfo.payby ? " NT$ " : MONEYNAME "$ "; - 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; - int ymsg = b_lines -1; -#ifdef ASSESS - char oldrecom = fhdr->recommend; -#endif // ASSESS - - if (!fhdr || !fhdr->filename[0]) - return DONOTHING; - - assert(0<=currbid-1 && currbid-1brdattr & 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("您權限不足, 無法推薦!"); // "(可按大寫 I 查看限制)" - 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 (!CheckPostRestriction(currbid)) - { - 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; - } - } - { - // kcwu - char path[PATHLEN]; - off_t size; - setdirpath(path, direct, fhdr->filename); - size = dashs(path); - if (size > 5*1024*1024) { - vmsg("檔案太大, 無法繼續推文, 請另撰文發表"); - return FULLUPDATE; - } - - if (size > 100*1024) { - int d = 10 - (now - lastrecommend); - if (d > 0) { - vmsgf("本文已過長, 禁止快速連續推文, 請再等 %d 秒", d); - return FULLUPDATE; - } - } - } - - -#ifdef USE_COOLDOWN - if(check_cooldown(bp)) - return FULLUPDATE; -#endif - - type = 0; - - // why "recommend == 0" here? - // some users are complaining that they like to fxck up system - // with lots of recommend one-line text. - // since we don't support recognizing update of recommends now, - // they tend to use the counter to identify whether an arcitle - // has new recommends or not. - // so, make them happy here. -#ifndef OLDRECOMMEND - // no matter it is first time or not. - if (strcmp(cuser.userid, fhdr->owner) == 0) -#else - // old format is one way counter, so let's relax. - if (fhdr->recommend == 0 && strcmp(cuser.userid, fhdr->owner) == 0) -#endif - { - // owner recommend - type = 2; - move(ymsg--, 0); clrtoeol(); -#ifndef OLDRECOMMEND - outs("作者本人, 使用 → 加註方式\n"); -#else - outs("作者本人首推, 使用 → 加註方式\n"); -#endif - - } -#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(ymsg--, 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 */ - - move(b_lines, 0); clrtoeol(); - 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 - - // warn if article is outside post - if (strchr(fhdr->owner, '.') != NULL) - { - move(ymsg--, 0); clrtoeol(); - outs(ANSI_COLOR(1;31) - "◆這篇文章來自暱名板或外站轉信板,原作者可能無法看到推文。" - ANSI_RESET "\n"); - } - - // warn if in non-standard mode - { - char *p = strrchr(direct, '/'); - // allow .DIR or .DIR.bottom - if (!p || strncmp(p+1, FN_DIR, strlen(FN_DIR)) != 0) - { - ymsg --; - move(ymsg--, 0); clrtoeol(); - outs(ANSI_COLOR(1;33) - "◆您正在搜尋(標題、作者...)或其它特殊列表模式," - "推文計數與修改記錄將會分開計算。" - ANSI_RESET "\n" - " 若想正常計數請先左鍵退回正常列表模式。\n"); - } - } - - 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 // !OLDRECOMMEND - maxlength -= strlen(cuser.userid); - sprintf(buf, "%s%s%s %s:", - ctype_attr[type], ctype[type], ANSI_RESET, - cuser.userid); -#endif // !OLDRECOMMEND - - move(b_lines, 0); - clrtoeol(); - - if (!getdata(b_lines, 0, buf, msg, maxlength, DOECHO)) - return FULLUPDATE; - - // make sure to do modification - { - 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; - } - - // log if you want -#ifdef LOG_PUSH - { - static int tolog = 0; - if( tolog == 0 ) - tolog = - (cuser.numlogins < 50 || (now - cuser.firstlogin) < 86400 * 7) - ? 1 : 2; - if( tolog == 1 ){ - FILE *fp; - if( (fp = fopen("log/push", "a")) != NULL ){ - fprintf(fp, "%s %d %s %s %s\n", cuser.userid, now, currboard, fhdr->filename, msg); - fclose(fp); - } - sleep(1); - } - } -#endif // LOG_PUSH - - 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 // OLDRECOMMEND - } - - do_add_recommend(direct, fhdr, ent, buf, type); - -#ifdef ASSESS - /* 每 10 次推文 加一次 goodpost */ - // TODO 轉來的怎麼辦? - // when recommend reaches MAX_RECOMMENDS... - if (type ==0 && (fhdr->filemode & FILE_MARKED) && - (fhdr->recommend != oldrecom) && - fhdr->recommend % 10 == 0) - { - inc_goodpost(fhdr->owner, 1); - sendalert(fhdr->owner, ALERT_PWD_GOODPOST); - } -#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); - sendalert(fhdr->owner, ALERT_PWD_GOODPOST); - } - } - else if (fhdr->recommend > 9) - { - inc_goodpost(fhdr->owner, -1 * (fhdr->recommend / 10)); - sendalert(fhdr->owner, ALERT_PWD_GOODPOST); - } - } -#endif - - substitute_ref_record(direct, fhdr, ent); - check_locked(fhdr); - 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' && currbid) /* 信件不用 check */ - { - // 很不幸的是有一種是信件->mail_cite->精華區 - bp = getbcache(currbid); - if (strcmp(bp->brdname, GLOBAL_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 ((curredit & EDIT_MAIL)==0 && (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-1brdname, GLOBAL_SECURITY) == 0) - return DONOTHING; - - /* TODO recursive lookup */ - if (currmode & MODE_SELECT) { - vmsg("請回到一般模式再刪除文章"); - return DONOTHING; - } - - if ((fhdr->filemode & FILE_BOTTOM) || - (fhdr->filemode & FILE_MARKED) || (fhdr->filemode & FILE_DIGEST) || - (fhdr->owner[0] == '-')) - 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); - - 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 *) - - // TODO not_owned 時也要改變 numpost? - 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); - clrtobot(); - 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, BBSMNAME " 系統警察", - "劣文累計 5 篇", "罰單一張"); - mail_violatelaw(userid, BBSMNAME " 系統警察", - "劣文累計 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, "[" BBSMNAME "警察局]"); - 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 && - !IsFreeBoardName(currboard)) - { - deumoney(tusernum, -fhdr->multi.money); -#ifdef USE_COOLDOWN - if (bp->brdattr & BRD_COOLDOWN) - add_cooldowntime(tusernum, 15); -#endif - } - - // owner case. - - if (!not_owned && !IsFreeBoardName(currboard)) { - - // digest 不用管 - // new rule: only articles with money need updating - // numpost (to solve deleting cross-posts). - if (!(currmode & MODE_DIGEST) && (fhdr->multi.money > 0)) - { - if (cuser.numposts) - cuser.numposts--; - demoney(-fhdr->multi.money); - - vmsgf("您的文章減為 %d 篇,支付清潔費 %d 銀", - cuser.numposts, fhdr->multi.money); - } - } - return DIRCHANGED; - } // delete_record - } // genbuf[0] == 'y' - 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; - boardheader_t *bp = NULL; - - if (currstat == RMAIL) - return DONOTHING; - - if (!(currmode & MODE_BOARD) && !HasUserPerm(PERM_SYSOP | PERM_POLICE)) - return DONOTHING; - - bp = getbcache(currbid); - assert(bp); - - 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'; - syncnow(); - bp->SRexpire = now; - } - else if (fhdr->filename[0]=='L') { - if (getans("要將文章鎖定解除嗎(y/N)?") != 'y') - return FULLUPDATE; - fhdr->filename[0] = 'M'; - setbfile(fn1, currboard, fhdr->filename); - syncnow(); - bp->SRexpire = now; - } - 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_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln) -{ - aidu_t aidu = 0; - int l = crs_ln + 3; /* line of cursor */ - int area_l = l + 1; - const int area_lines = 4; - - if(!fhdr || fhdr->filename[0] == '.' || !fhdr->filename[0]) - return DONOTHING; - - if((area_l + area_lines > b_lines) || /* 下面放不下 */ - (l >= (b_lines * 2 / 3))) /* 略超過畫面 2/3 */ - area_l -= (area_lines + 1); - - grayout(0, MIN(l - 1, area_l)-1, GRAYOUT_DARK); - grayout(MAX(l + 1 + 1, area_l + area_lines), b_lines-1, GRAYOUT_DARK); - grayout(l, l, GRAYOUT_BOLD); - - /* 清除文章的前一行或後一行 */ - if(area_l > l) - move(l - 1, 0); - else - move(l + 1, 0); - clrtoeol(); - - move(area_l-(area_l < l), 0); - clrtoln(area_l -(area_l < l) + area_lines+1); - outc(' '); outs(ANSI_CLRTOEND); - move(area_l -(area_l < l) + area_lines, 0); - outc(' '); outs(ANSI_CLRTOEND); - move(area_l, 0); - - prints("┌─────────────────────────────────────┐\n"); - - aidu = fn2aidu((char *)fhdr->filename); - if(aidu > 0) - { - char aidc[10]; - int y, x; - - aidu2aidc(aidc, aidu); - prints("│ " AID_DISPLAYNAME ": " - ANSI_COLOR(1) "#%s" ANSI_RESET " (%s) [%s] ", - aidc, currboard && currboard[0] ? currboard : "未知", - MYHOSTNAME); - getyx_ansi(&y, &x); - x = 75 - x; - if (x > 1) - prints("%.*s ", x, fhdr->title); - outs("\n"); - } - else - { - prints("│\n"); - } - - if(fhdr->filemode & FILE_ANONYMOUS) - /* When the file is anonymous posted, fhdr->multi.anon_uid is author. - * see do_general() */ - prints("│ 匿名管理編號: %d (同一人號碼會一樣)", - fhdr->multi.anon_uid + (int)currutmp->pid); - else { - int m = query_file_money(fhdr); - - if(m < 0) - prints("│ 特殊文章,無價格記錄"); - else - prints("│ 這一篇文章值 %d 銀", m); - - } - prints("\n"); - prints("└─────────────────────────────────────┘\n"); - - /* 印對話框的右邊界 */ - { - int i; - - for(i = 1; i < area_lines - 1; i ++) - { - move_ansi(area_l + i , 76); - prints("│"); - } - } - { - int r = pressanykey(); - /* TODO: 多加一個 LISTMODE_AID? */ - /* QQ: enable money listing mode */ - if (r == '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; - } -#ifdef TARQUEUE_SENDURL - move (3,0); outs("請輸入通知信箱 (預設為此 BBS 帳號信箱): "); - if (!getdata_str(4, 2, "", - email, sizeof(email), DOECHO, cuser.userid)) - return FULLUPDATE; - if (strstr(email, "@") == NULL) - { - strcat(email, ".bbs@"); - strcat(email, MYHOSTNAME); - } - move(4,0); clrtoeol(); - outs(email); -#else - 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; - } -#endif - 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-1brdname, 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-1title + 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-1filename[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-1filemode & FILE_DIGEST ? - "取消看板文摘?(Y/n)" : "收入看板文摘?(Y/n)") == 'n') - return READ_REDRAW; - - if (fhdr->filemode & FILE_DIGEST) { - fhdr->filemode = (fhdr->filemode & ~FILE_DIGEST); - if (!strcmp(currboard, GLOBAL_NOTE) || -#ifdef GLOBAL_ARTDSN - !strcmp(currboard, GLOBAL_ARTDSN) || -#endif - !strcmp(currboard, GLOBAL_BUGREPORT) || - !strcmp(currboard, GLOBAL_LAW) - ) - { - 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-1brdattr & 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, GLOBAL_NOTE) || -#ifdef GLOBAL_ARTDSN - !strcmp(currboard, GLOBAL_ARTDSN) || -#endif - !strcmp(currboard, GLOBAL_BUGREPORT) || - !strcmp(currboard, GLOBAL_LAW) - ) - { - 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; -} - -#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-1brdname, NULL, "冷靜", genbuf, bp->brdattr & BRD_COOLDOWN); - pressanykey(); - return FULLUPDATE; -} -#endif - -static int -b_moved_to_config() -{ - if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) - { - vmsg("這個功\能已移入看板設定 (i) 去了!"); - return FULLUPDATE; - } - return DONOTHING; -} - -/* ----------------------------------------------------- */ -/* 看板功能表 */ -/* ----------------------------------------------------- */ -/* 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') - { 0, NULL }, // Ctrl('N') - { 0, do_post_openbid }, // Ctrl('O') // BETTER NOT USE ^O - UNIX not work - { 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, NULL }, // 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 }, - { 1, recommend }, // '%' (m3itoc style) - { 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' - { 0, b_moved_to_config }, // 'H' - { 0, b_config }, // 'I' -#ifdef USE_COOLDOWN - { 0, change_cooldown }, // 'J' -#else - { 0, NULL }, // 'J' -#endif - { 0, b_moved_to_config }, // 'K' - { 1, solve_post }, // 'L' - { 0, b_moved_to_config }, // 'M' - { 0, NULL }, // 'N' - { 0, NULL }, // 'O' - { 0, NULL }, // 'P' - { 1, view_postinfo }, // '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_config }, // 'i' - { 0, NULL }, // 'j' - { 0, NULL }, // 'k' - { 0, NULL }, // 'l' - { 1, mark_post }, // 'm' - { 0, NULL }, // 'n' - { 0, NULL }, // '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, b_moved_to_config }, // '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; - static int lastbid = -1; - char buf[PATHLEN]; -#ifdef LOG_BOARD - time4_t usetime = now; -#endif - - const char *bname = currboard[0] ? currboard : DEFAULT_BOARD; - if (enter_board(bname) < 0) - return 0; - - setutmpmode(READING); - - if (currbid != lastbid && - 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(); - } - lastbid = currbid; - i_read(READING, currdirect, 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) -{ - return do_select(); -} - -#ifdef HAVEMOBILE -void -mobile_message(const char *mobile, char *message) -{ - bsmtp(fpath, title, rcpt); -} -#endif diff --git a/mbbsd/bbslua.c b/mbbsd/bbslua.c deleted file mode 100644 index 0305bd78..00000000 --- a/mbbsd/bbslua.c +++ /dev/null @@ -1,1801 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// BBS-Lua Project -// -// Author: Hung-Te Lin(piaip), Jan. 2008. -// -// Create: 2008-01-04 22:02:58 -// $Id$ -// -// This source is released in MIT License, same as Lua 5.0 -// http://www.lua.org/license.html -// -// Copyright 2008 Hung-Te Lin -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// TODO: -// BBSLUA 1.0 -// 1. add quick key/val conversion [deprecated] -// 2. add key values (UP/DOWN/...) [done] -// 3. remove i/o libraries [done] -// 4. add system break key (Ctrl-C) [done] -// 5. add version string and script tags [done] -// 6. standalone w32 sdk [done] -// 7. syntax highlight in editor [done] -// 8. prevent loadfile, dofile [done] -// 9. provide local storage -// ?. modify bbs user data (eg, money) -// ?. os.date(), os.exit(), abort(), os.time() -// ?. memory free issue in C library level? -// ?. add digital signature -// -// BBSLUA 2.0 -// 1. 2 people communication -// -// BBSLUA 3.0 -// 1. n people communication -////////////////////////////////////////////////////////////////////////// - -#include "bbs.h" -#include "fnv_hash.h" - -#include -#include -#include - -////////////////////////////////////////////////////////////////////////// -// CONST DEFINITION -////////////////////////////////////////////////////////////////////////// - -#define BBSLUA_INTERFACE_VER 0.119 // (0.201) -#define BBSLUA_SIGNATURE "--#BBSLUA" - -// BBS-Lua script format: -// $BBSLUA_SIGNATURE -// -- Interface: $interface -// -- Title: $title -// -- Notes: $notes -// -- Author: $author -// -- Version: $version -// -- Date: $date -// -- LatestRef: #AID board -// [... script ...] -// $BBSLUA_SIGNATURE - -////////////////////////////////////////////////////////////////////////// -// CONFIGURATION VARIABLES -////////////////////////////////////////////////////////////////////////// -#define BLAPI_PROTO int - -#define BLCONF_BREAK_KEY Ctrl('C') -#define BLCONF_EXEC_COUNT (5000) -#define BLCONF_PEEK_TIME (0.01) -#define BLCONF_KBHIT_TMIN (BLCONF_PEEK_TIME) -#define BLCONF_KBHIT_TMAX (60*10) -#define BLCONF_SLEEP_TMIN (BLCONF_PEEK_TIME) -#define BLCONF_SLEEP_TMAX (BLCONF_KBHIT_TMAX) -#define BLCONF_U_SECOND (1000000L) -#define BLCONF_PRINT_TOC_INDEX (2) - -#define BLCONF_MMAP_ATTACH -#define BLCONF_CURRENT_USERID cuser.userid -#define BLCONF_CURRENT_USERNICK cuser.nickname - -// BBS-Lua Storage -enum { - BLS_INVALID= 0, - BLS_GLOBAL = 1, - BLS_USER, -}; - -// #define BLSCONF_ENABLED -#define BLSCONF_GLOBAL_VAL "global" -#define BLSCONF_USER_VAL "user" -#define BLSCONF_GMAXSIZE (16*1024) // should be aligned to block size -#define BLSCONF_UMAXSIZE (16*1024) // should be aligned to block size -#define BLSCONF_GPATH BBSHOME "/luastore" -#define BLSCONF_UPATH ".luastore" -#define BLSCONF_PREFIX "v1_" -#define BLSCONF_MAXIO 32 // prevent bursting system - -// #define BBSLUA_USAGE - -#ifdef _WIN32 -# undef BLCONF_MMAP_ATTACH -# undef BLCONF_CURRENT_USERID -# define BLCONF_CURRENT_USERID "guest" -# undef BLCONF_CURRENT_USERNICK -# define BLCONF_CURRENT_USERNICK "測試帳號" -#endif - -////////////////////////////////////////////////////////////////////////// -// GLOBAL VARIABLES -////////////////////////////////////////////////////////////////////////// -typedef struct { - char running; // prevent re-entrant - char abort; // system break key hit - char iocounter; // prevent bursting i/o - Fnv32_t storename; // storage filename -} BBSLuaRT; - -// runtime information -// static -BBSLuaRT blrt = {0}; - -#define BL_INIT_RUNTIME() { \ - memset(&blrt, 0, sizeof(blrt)); \ - blrt.storename = FNV1_32_INIT; \ -} - -#define BL_END_RUNTIME() { \ - memset(&blrt, 0, sizeof(blrt)); \ -} - -#ifdef BBSLUA_USAGE -static int bbslua_count; -#endif - -////////////////////////////////////////////////////////////////////////// -// UTILITIES -////////////////////////////////////////////////////////////////////////// - -static void -bl_double2tv(double d, struct timeval *tv) -{ - tv->tv_sec = d; - tv->tv_usec = (d - tv->tv_sec) * BLCONF_U_SECOND; -} - -static double -bl_tv2double(const struct timeval *tv) -{ - double d = tv->tv_sec; - d += tv->tv_usec / (double)BLCONF_U_SECOND; - return d; -} - -static int -bl_peekbreak(float f) -{ - if (input_isfull()) - drop_input(); - if (peek_input(f, BLCONF_BREAK_KEY)) - { - drop_input(); - blrt.abort = 1; - return 1; - } - return 0; -} - -static void -bl_k2s(lua_State* L, int v) -{ - if (v <= 0) - lua_pushnil(L); - else if (v == KEY_TAB) - lua_pushstring(L, "TAB"); - else if (v == '\b' || v == 0x7F) - lua_pushstring(L, "BS"); - else if (v == '\n' || v == '\r' || v == Ctrl('M')) - lua_pushstring(L, "ENTER"); - else if (v < ' ') - lua_pushfstring(L, "^%c", v-1+'A'); - else if (v < 0x100) - lua_pushfstring(L, "%c", v); - else if (v >= KEY_F1 && v <= KEY_F12) - lua_pushfstring(L, "F%d", v - KEY_F1 +1); - else switch(v) - { - case KEY_UP: lua_pushstring(L, "UP"); break; - case KEY_DOWN: lua_pushstring(L, "DOWN"); break; - case KEY_RIGHT: lua_pushstring(L, "RIGHT"); break; - case KEY_LEFT: lua_pushstring(L, "LEFT"); break; - case KEY_HOME: lua_pushstring(L, "HOME"); break; - case KEY_END: lua_pushstring(L, "END"); break; - case KEY_INS: lua_pushstring(L, "INS"); break; - case KEY_DEL: lua_pushstring(L, "DEL"); break; - case KEY_PGUP: lua_pushstring(L, "PGUP"); break; - case KEY_PGDN: lua_pushstring(L, "PGDN"); break; - default: lua_pushnil(L); break; - } -} - -BLAPI_PROTO -bl_newwin(int rows, int cols, const char *title) -{ - // input: (rows, cols, title) - int y = 0, x = 0, n = 0; - int oy = 0, ox = 0; - - if (rows <= 0 || cols <= 0) - return 0; - - getyx(&oy, &ox); - // now, draw the window - newwin(rows, cols, oy, ox); - - if (!title || !*title) - return 0; - - // draw center-ed title - n = strlen_noansi(title); - x = ox + (cols - n)/2; - y = oy + (rows)/2; - move(y, x); - outs(title); - - move(oy, ox); - return 0; -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA API IMPLEMENTATION -////////////////////////////////////////////////////////////////////////// - -BLAPI_PROTO -bl_getyx(lua_State* L) -{ - int y, x; - getyx(&y, &x); - lua_pushinteger(L, y); - lua_pushinteger(L, x); - return 2; -} - -BLAPI_PROTO -bl_getmaxyx(lua_State* L) -{ - lua_pushinteger(L, t_lines); - lua_pushinteger(L, t_columns); - return 2; -} - -BLAPI_PROTO -bl_move(lua_State* L) -{ - int n = lua_gettop(L); - int y = 0, x = 0; - if (n > 0) - y = lua_tointeger(L, 1); - if (n > 1) - x = lua_tointeger(L, 2); - move_ansi(y, x); - return 0; -} - -BLAPI_PROTO -bl_moverel(lua_State* L) -{ - int n = lua_gettop(L); - int y = 0, x = 0; - getyx(&y, &x); - if (n > 0) - y += lua_tointeger(L, 1); - if (n > 1) - x += lua_tointeger(L, 2); - move(y, x); - getyx(&y, &x); - lua_pushinteger(L, y); - lua_pushinteger(L, x); - return 2; -} - -BLAPI_PROTO -bl_clear(lua_State* L) -{ - (void)L; /* to avoid warnings */ - clear(); - return 0; -} - -BLAPI_PROTO -bl_clrtoeol(lua_State* L) -{ - (void)L; /* to avoid warnings */ - clrtoeol(); - return 0; -} - -BLAPI_PROTO -bl_clrtobot(lua_State* L) -{ - (void)L; /* to avoid warnings */ - clrtobot(); - return 0; -} - -BLAPI_PROTO -bl_refresh(lua_State* L) -{ - (void)L; /* to avoid warnings */ - // refresh(); - // Seems like that most people don't understand the relationship - // between refresh() and input queue, so let's force update here. - doupdate(); - return 0; -} - -BLAPI_PROTO -bl_addstr(lua_State* L) -{ - int n = lua_gettop(L); - int i = 1; - for (i = 1; i <= n; i++) - { - const char *s = lua_tostring(L, i); - if(s) - outs(s); - } - return 0; -} - -BLAPI_PROTO -bl_rect(lua_State *L) -{ - // input: (rows, cols, title) - int rows = 1, cols = 1; - int n = lua_gettop(L); - const char *title = NULL; - - if (n > 0) - rows = lua_tointeger(L, 1); - if (n > 1) - cols = lua_tointeger(L, 2); - if (n > 2) - title = lua_tostring(L, 3); - if (rows <= 0 || cols <= 0) - return 0; - - // now, draw the rectangle - bl_newwin(rows, cols, title); - return 0; -} - -BLAPI_PROTO -bl_print(lua_State* L) -{ - bl_addstr(L); - outc('\n'); - return 0; -} - -BLAPI_PROTO -bl_getch(lua_State* L) -{ - int c = igetch(); - if (c == BLCONF_BREAK_KEY) - { - drop_input(); - blrt.abort = 1; - return lua_yield(L, 0); - } - bl_k2s(L, c); - return 1; -} - - -BLAPI_PROTO -bl_getstr(lua_State* L) -{ - int y, x; - // TODO not using fixed length here? - char buf[PATHLEN] = ""; - int len = 2, echo = 1; - int n = lua_gettop(L); - const char *pmsg = NULL; - - if (n > 0) - len = lua_tointeger(L, 1); - if (n > 1) - echo = lua_tointeger(L, 2); - if (n > 2) - pmsg = lua_tostring(L, 3); - - if (len < 2) - len = 2; - if (len >= sizeof(buf)) - len = sizeof(buf)-1; - /* - * this part now done in getdata_str - if (pmsg && *pmsg) - { - strlcpy(buf, pmsg, sizeof(buf)); - } - */ - - // TODO process Ctrl-C here - getyx(&y, &x); - if (!pmsg) pmsg = ""; - len = getdata_str(y, x, NULL, buf, len, echo, pmsg); - if (len <= 0) - { - len = 0; - // check if we got Ctrl-C? (workaround in getdata) - // TODO someday write 'ungetch()' in io.c to prevent - // such workaround. - if (buf[1] == Ctrl('C')) - { - drop_input(); - blrt.abort = 1; - return lua_yield(L, 0); - } - lua_pushstring(L, ""); - } - else - { - lua_pushstring(L, buf); - } - // return len ? 1 : 0; - return 1; -} - -BLAPI_PROTO -bl_kbhit(lua_State *L) -{ - int n = lua_gettop(L); - double f = 0.1f; - - if (n > 0) - f = (double)lua_tonumber(L, 1); - - if (f < BLCONF_KBHIT_TMIN) f = BLCONF_KBHIT_TMIN; - if (f > BLCONF_KBHIT_TMAX) f = BLCONF_KBHIT_TMAX; - - refresh(); - if (num_in_buf() || wait_input(f, 0)) - lua_pushboolean(L, 1); - else - lua_pushboolean(L, 0); - return 1; -} - -BLAPI_PROTO -bl_kbreset(lua_State *L) -{ - // peek input queue first! - if (bl_peekbreak(BLCONF_PEEK_TIME)) - return lua_yield(L, 0); - - drop_input(); - return 0; -} - -BLAPI_PROTO -bl_sleep(lua_State *L) -{ - int n = lua_gettop(L); - double us = 0, nus = 0; - - // update screen first. - bl_refresh(L); - - if (n > 0) - us = lua_tonumber(L, 1); - if (us < BLCONF_SLEEP_TMIN) us = BLCONF_SLEEP_TMIN; - if (us > BLCONF_SLEEP_TMAX) us = BLCONF_SLEEP_TMAX; - nus = us; - -#ifdef _WIN32 - - Sleep(us * 1000); - -#else // !_WIN32 - { - struct timeval tp, tdest; - - gettimeofday(&tp, NULL); - - // nus is the destination time - nus = bl_tv2double(&tp) + us; - bl_double2tv(nus, &tdest); - - while ( (tp.tv_sec < tdest.tv_sec) || - ((tp.tv_sec == tdest.tv_sec) && (tp.tv_usec < tdest.tv_usec))) - { - // calculate new peek time - us = nus - bl_tv2double(&tp); - - // check if input key is system break key. - if (bl_peekbreak(us)) - return lua_yield(L, 0); - - // check time - gettimeofday(&tp, NULL); - } - } -#endif // !_WIN32 - - return 0; -} - -BLAPI_PROTO -bl_kball(lua_State *L) -{ - // first, sleep by given seconds - int r = 0, oldr = 0, i = 0; - - r = bl_sleep(L); - if (blrt.abort) - return r; - - // pop all arguments - lua_pop(L, lua_gettop(L)); - - -#ifdef _WIN32 - while (peekch(0)) - { - bl_k2s(L, igetch()); - i++; - } -#else - // next, collect all input and return. - if (num_in_buf() < 1) - return 0; - - oldr = num_in_buf() +1; - i = 0; - - while ( i < LUA_MINSTACK && - (r = num_in_buf()) > 0 && oldr > r) - { - oldr = r; - bl_k2s(L, igetch()); - i++; - } -#endif - - return i; -} - - -BLAPI_PROTO -bl_pause(lua_State* L) -{ - int n = lua_gettop(L); - const char *s = NULL; - if (n > 0) - s = lua_tostring(L, 1); - - n = vmsg(s); - if (n == BLCONF_BREAK_KEY) - { - drop_input(); - blrt.abort = 1; - return lua_yield(L, 0); - } - bl_k2s(L, n); - return 1; -} - -BLAPI_PROTO -bl_title(lua_State* L) -{ - int n = lua_gettop(L); - const char *s = NULL; - if (n > 0) - s = lua_tostring(L, 1); - if (s == NULL) - return 0; - - stand_title(s); - return 0; -} - -BLAPI_PROTO -bl_ansi_color(lua_State *L) -{ - char buf[PATHLEN] = ESC_STR "["; - char *p = buf + strlen(buf); - int i = 1; - int n = lua_gettop(L); - if (n >= 10) n = 10; - for (i = 1; i <= n; i++) - { - if (i > 1) *p++ = ';'; - sprintf(p, "%d", (int)lua_tointeger(L, i)); - p += strlen(p); - } - *p++ = 'm'; - *p = 0; - lua_pushstring(L, buf); - return 1; -} - -BLAPI_PROTO -bl_strip_ansi(lua_State *L) -{ - int n = lua_gettop(L); - const char *s = NULL; - char *s2 = NULL; - size_t os2 = 0; - - if (n < 1 || (s = lua_tostring(L, 1)) == NULL || - *s == 0) - { - lua_pushstring(L, ""); - return 1; - } - - os2 = strlen(s)+1; - s2 = (char*) lua_newuserdata(L, os2); - strip_ansi(s2, s, STRIP_ALL); - lua_pushstring(L, s2); - lua_remove(L, -2); - return 1; -} - -BLAPI_PROTO -bl_attrset(lua_State *L) -{ - char buf[PATHLEN] = ESC_STR "["; - char *p = buf + strlen(buf); - int i = 1; - int n = lua_gettop(L); - if (n == 0) - outs(ANSI_RESET); - for (i = 1; i <= n; i++) - { - sprintf(p, "%dm",(int)lua_tointeger(L, i)); - outs(buf); - } - return 0; -} - -BLAPI_PROTO -bl_time(lua_State *L) -{ - syncnow(); - lua_pushinteger(L, now); - return 1; -} - -BLAPI_PROTO -bl_ctime(lua_State *L) -{ - syncnow(); - lua_pushstring(L, ctime4(&now)); - return 1; -} - -BLAPI_PROTO -bl_clock(lua_State *L) -{ - double d = 0; - -#ifdef _WIN32 - - // XXX this is a fast hack because we don't want to do 64bit calculation. - SYSTEMTIME st; - GetSystemTime(&st); - syncnow(); - // XXX the may be some latency between our GetSystemTime and syncnow. - // So build again the "second" part. - d = (int)((now / 60) * 60); - d += st.wSecond; - d += (st.wMilliseconds / 1000.0f); - -#else // !_WIN32 - - struct timeval tp; - gettimeofday(&tp, NULL); - d = bl_tv2double(&tp); - -#endif // !_WIN32 - - lua_pushnumber(L, d); - return 1; -} - -////////////////////////////////////////////////////////////////////////// -// BBS-Lua Storage System -////////////////////////////////////////////////////////////////////////// -static int -bls_getcat(const char *s) -{ - if (!s || !*s) - return BLS_INVALID; - if (strcmp(s, BLSCONF_GLOBAL_VAL) == 0) - return BLS_GLOBAL; - else if (strcmp(s, BLSCONF_USER_VAL) == 0) - return BLS_USER; - return BLS_INVALID; -} - -static int -bls_getlimit(const char *p) -{ - switch(bls_getcat(p)) - { - case BLS_GLOBAL: - return BLSCONF_GMAXSIZE; - case BLS_USER: - return BLSCONF_UMAXSIZE; - } - return 0; -} - -static int -bls_setfn(char *fn, size_t sz, const char *p) -{ - *fn = 0; - switch(bls_getcat(p)) - { - case BLS_GLOBAL: - snprintf(fn, sz, "%s/" BLSCONF_PREFIX "U%08x", - BLSCONF_GPATH, blrt.storename); - return 1; - - case BLS_USER: - setuserfile(fn, BLSCONF_UPATH); - mkdir(fn, 0755); - assert(strlen(fn) +8 <= sz); - snprintf(fn + strlen(fn), - sz - strlen(fn), - "/" BLSCONF_PREFIX "G%08x", blrt.storename); - return 1; - } - return 0; -} - -BLAPI_PROTO -bls_iolimit(lua_State *L) -{ - lua_pushinteger(L, BLSCONF_MAXIO); - return 1; -} - -BLAPI_PROTO -bls_limit(lua_State *L) -{ - int n = lua_gettop(L); - const char *s = NULL; - if (n > 0) - s = lua_tostring(L, 1); - lua_pushinteger(L, bls_getlimit(s)); - return 1; -} - -BLAPI_PROTO -bls_load(lua_State *L) -{ - int n = lua_gettop(L); - const char *cat = NULL; - char fn[PATHLEN]; - - if (blrt.iocounter >= BLSCONF_MAXIO) - { - lua_pushnil(L); - return 1; - } - - if (n != 1) - { - lua_pushnil(L); - return 1; - } - cat = lua_tostring(L, 1); // category - if (!cat) - { - lua_pushnil(L); - return 1; - } - - blrt.iocounter++; - // read file! - if (bls_setfn(fn, sizeof(fn), cat)) - { - int fd = open(fn, O_RDONLY); - if (fd >= 0) - { - char buf[2048]; - luaL_Buffer b; - - luaL_buffinit(L, &b); - while ((n = read(fd, buf, sizeof(buf))) > 0) - luaL_addlstring(&b, buf, n); - close(fd); - luaL_pushresult(&b); - } else { - lua_pushnil(L); - } - } else { - lua_pushnil(L); - } - return 1; -} - -BLAPI_PROTO -bls_save(lua_State *L) -{ - int n = lua_gettop(L), fd = -1; - int limit = 0, slen = 0; - const char *s = NULL, *cat = NULL; - char fn[PATHLEN] = "", ret = 0; - - if (blrt.iocounter >= BLSCONF_MAXIO) - { - lua_pushboolean(L, 0); - return 1; - } - - if (n != 2) { lua_pushboolean(L, 0); return 1; } - - cat = lua_tostring(L, 1); // category - s = lua_tostring(L, 2); // data - limit = bls_getlimit(cat); - - if (!cat || !s || limit < 1) - { - lua_pushboolean(L, 0); - return 1; - } - - slen = lua_objlen(L, 2); - if (slen >= limit) slen = limit; - - blrt.iocounter++; - // write file! - if (bls_setfn(fn, sizeof(fn), cat)) - { - fd = open(fn, O_WRONLY|O_CREAT, 0644); - if (fd >= 0) - { - write(fd, s, slen); - close(fd); - ret = 1; - } - } - - lua_pushboolean(L, ret); - return 1; -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA LIBRARY -////////////////////////////////////////////////////////////////////////// - -static const struct luaL_reg lib_bbslua [] = { - /* curses output */ - { "getyx", bl_getyx }, - { "getmaxyx", bl_getmaxyx }, - { "move", bl_move }, - { "moverel", bl_moverel }, - { "clear", bl_clear }, - { "clrtoeol", bl_clrtoeol }, - { "clrtobot", bl_clrtobot }, - { "refresh", bl_refresh }, - { "addstr", bl_addstr }, - { "outs", bl_addstr }, - { "print", bl_print }, - /* input */ - { "getch", bl_getch }, - { "getdata", bl_getstr }, - { "getstr", bl_getstr }, - { "kbhit", bl_kbhit }, - { "kbreset", bl_kbreset }, - { "kball", bl_kball }, - /* advanced output */ - { "rect", bl_rect }, - /* BBS utilities */ - { "pause", bl_pause }, - { "title", bl_title }, - /* time */ - { "time", bl_time }, - { "now", bl_time }, - { "clock", bl_clock }, - { "ctime", bl_ctime }, - { "sleep", bl_sleep }, - /* ANSI helpers */ - { "ANSI_COLOR", bl_ansi_color }, - { "color", bl_attrset }, - { "attrset", bl_attrset }, - { "strip_ansi", bl_strip_ansi }, - { NULL, NULL}, -}; - -static const struct luaL_reg lib_store [] = { - { "load", bls_load }, - { "save", bls_save }, - { "limit", bls_limit }, - { "iolimit", bls_iolimit }, - { NULL, NULL}, -}; - -// non-standard modules in bbsluaext.c -LUALIB_API int luaopen_bit (lua_State *L); - -static const luaL_Reg bbslualibs[] = { - // standard modules - {"", luaopen_base}, - - // {LUA_LOADLIBNAME, luaopen_package}, - {LUA_TABLIBNAME, luaopen_table}, - // {LUA_IOLIBNAME, luaopen_io}, - // {LUA_OSLIBNAME, luaopen_os}, - {LUA_STRLIBNAME, luaopen_string}, - {LUA_MATHLIBNAME, luaopen_math}, - // {LUA_DBLIBNAME, luaopen_debug}, - - // bbslua-ext modules - {"bit", luaopen_bit}, - - {NULL, NULL} -}; - - -LUALIB_API void bbsluaL_openlibs (lua_State *L) { - const luaL_Reg *lib = bbslualibs; - for (; lib->func; lib++) { - lua_pushcfunction(L, lib->func); - lua_pushstring(L, lib->name); - lua_call(L, 1, 0); - } -} - - -// Constant registration - -typedef struct bbsluaL_RegStr { - const char *name; - const char *val; -} bbsluaL_RegStr; - -typedef struct bbsluaL_RegNum { - const char *name; - lua_Number val; -} bbsluaL_RegNum; - -static const bbsluaL_RegStr bbsluaStrs[] = { - {"ESC", ESC_STR}, - {"ANSI_RESET", ANSI_RESET}, - {"sitename", BBSNAME}, - {NULL, NULL}, -}; - -static const bbsluaL_RegNum bbsluaNums[] = { - {"interface", BBSLUA_INTERFACE_VER}, - {NULL, 0}, -}; - -static void -bbsluaRegConst(lua_State *L) -{ - int i = 0; - - // unbind unsafe API - lua_pushnil(L); lua_setglobal(L, "dofile"); - lua_pushnil(L); lua_setglobal(L, "loadfile"); - - // global - lua_pushcfunction(L, bl_print); - lua_setglobal(L, "print"); - - // bbs.* - lua_getglobal(L, "bbs"); - for (i = 0; bbsluaStrs[i].name; i++) - { - lua_pushstring(L, bbsluaStrs[i].val); - lua_setfield(L, -2, bbsluaStrs[i].name); - } - - for (i = 0; bbsluaNums[i].name; i++) - { - lua_pushnumber(L, bbsluaNums[i].val); - lua_setfield(L, -2, bbsluaNums[i].name); - } - // dynamic info - lua_pushstring(L, BLCONF_CURRENT_USERID); - lua_setfield(L, -2, "userid"); - lua_pushstring(L, BLCONF_CURRENT_USERNICK); - lua_setfield(L, -2, "usernick"); - - lua_pop(L, 1); - -#ifdef BLSCONF_ENABLED - // store.* - lua_getglobal(L, "store"); - lua_pushstring(L, BLSCONF_USER_VAL); - lua_setfield(L, -2, "USER"); - lua_pushstring(L, BLSCONF_GLOBAL_VAL); - lua_setfield(L, -2, "GLOBAL"); - lua_pop(L, 1); -#endif // BLSCONF_ENABLED -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA ENGINE UTILITIES -////////////////////////////////////////////////////////////////////////// - -static void -bbsluaHook(lua_State *L, lua_Debug* ar) -{ - // vmsg("bbslua HOOK!"); - if (blrt.abort) - { - drop_input(); - lua_yield(L, 0); - return; - } - - if (ar->event != LUA_HOOKCOUNT) - return; -#ifdef BBSLUA_USAGE - bbslua_count += BLCONF_EXEC_COUNT; -#endif - - // now, peek and check - if (input_isfull()) - drop_input(); - - // refresh(); - - // check if input key is system break key. - if (bl_peekbreak(BLCONF_PEEK_TIME)) - { - lua_yield(L, 0); - return; - } -} - -static char * -bbslua_attach(const char *fpath, size_t *plen) -{ - char *buf = NULL; - -#ifdef BLCONF_MMAP_ATTACH - struct stat st; - int fd = open(fpath, O_RDONLY); - - *plen = 0; - - if (fd < 0) return buf; - if (fstat(fd, &st) || ((*plen = st.st_size) < 1) || S_ISDIR(st.st_mode)) - { - close(fd); - return buf; - } - *plen = *plen +1; - - buf = mmap(NULL, *plen, PROT_READ, MAP_SHARED, fd, 0); - close(fd); - - if (buf == NULL || buf == MAP_FAILED) - { - *plen = 0; - return NULL; - } - madvise(buf, *plen, MADV_SEQUENTIAL); - -#else // !BLCONF_MMAP_ATTACH - - FILE *fp = fopen(fpath, "rt"); - *plen = 0; - if (!fp) - return NULL; - fseek(fp, 0, SEEK_END); - *plen = ftell(fp); - buf = (char*) malloc (*plen); - rewind(fp); - fread(buf, *plen, 1, fp); - -#endif // !BLCONF_MMAP_ATTACH - - return buf; -} - -static void -bbslua_detach(char *p, int len) -{ -#ifdef BLCONF_MMAP_ATTACH - munmap(p, len); -#else // !BLCONF_MMAP_ATTACH - free(p); -#endif // !BLCONF_MMAP_ATTACH -} - -int -bbslua_isHeader(const char *ps, const char *pe) -{ - int szsig = strlen(BBSLUA_SIGNATURE); - if (ps + szsig > pe) - return 0; - if (strncmp(ps, BBSLUA_SIGNATURE, szsig) == 0) - return 1; - return 0; -} - -static int -bbslua_detect_range(char **pbs, char **pbe, int *lineshift) -{ - int szsig = strlen(BBSLUA_SIGNATURE); - char *bs, *be, *ps, *pe; - int line = 0; - - bs = ps = *pbs; - be = pe = *pbe; - - // find start - while (ps + szsig < pe) - { - if (strncmp(ps, BBSLUA_SIGNATURE, szsig) == 0) - break; - // else, skip to next line - while (ps + szsig < pe && *ps++ != '\n'); - line++; - } - *lineshift = line; - - if (!(ps + szsig < pe)) - return 0; - - *pbs = ps; - *pbe = be; - - // find tail by SIGNATURE - pe = ps + 1; - while (pe + szsig < be) - { - if (strncmp(pe, BBSLUA_SIGNATURE, szsig) == 0) - break; - // else, skip to next line - while (pe + szsig < be && *pe++ != '\n'); - } - - if (pe + szsig < be) - { - // found sig, end at such line. - pe--; - *pbe = pe; - } else { - // abort. - *pbe = NULL; - *pbs = NULL; - return 0; - } - - // prevent trailing zeros - pe = *pbe; - while (pe > ps && !*pe) - pe--; - *pbe = pe; - - return 1; -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA TOC Processing -////////////////////////////////////////////////////////////////////////// - -static const char *bbsluaTocTags[] = -{ - "interface", - "latestref", - - // BLCONF_PRINT_TOC_INDEX - "title", - "notes", - "author", - "version", - "date", - NULL -}; - -static const char *bbsluaTocPrompts[] = -{ - "界面版本", - "最新版本", - - // BLCONF_PRINT_TOC_INDEX - "名稱", - "說明", - "作者", - "版本", - "日期", - NULL -}; - -int -bbslua_load_TOC(lua_State *L, const char *bs, const char *be, char **ppc) -{ - unsigned char *ps = NULL, *pe = NULL; - int i = 0; - - lua_newtable(L); - *ppc = NULL; - - while (bs < be) - { - // find stripped line start, end - ps = pe = (unsigned char *) bs; - while (pe < (unsigned char*)be && *pe != '\n' && *pe != '\r') - pe ++; - bs = (char*)pe+1; - while (ps < pe && *ps <= ' ') ps++; - while (pe > ps && *(pe-1) <= ' ') pe--; - // at least "--" - if (pe < ps+2) - break; - if (*ps++ != '-' || *ps++ != '-') - break; - while (ps < pe && *ps <= ' ') ps++; - // empty entry? - if (ps >= pe) - continue; - // find pattern - for (i = 0; bbsluaTocTags[i]; i++) - { - int l = strlen(bbsluaTocTags[i]); - if (ps + l > pe) - continue; - if (strncasecmp((char*)ps, bbsluaTocTags[i], l) != 0) - continue; - ps += l; - // found matching pattern, now find value - while (ps < pe && *ps <= ' ') ps++; - if (ps >= pe || *ps++ != ':') - break; - while (ps < pe && *ps <= ' ') ps++; - // finally, (ps, pe) is the value! - if (ps >= pe) - break; - - lua_pushlstring(L, (char*)ps, pe-ps); - - // accept only real floats for interface ([0]) - if (i == 0 && lua_tonumber(L, -1) <= 0) - { - lua_pop(L, 1); - } - else - { - lua_setfield(L, -2, bbsluaTocTags[i]); - } - break; - } - } - - if (ps >= (unsigned char*)bs && ps < (unsigned char*)be) - *ppc = (char*)ps; - lua_setglobal(L, "toc"); - return 0; -} - -static void -fullmsg(const char *s) -{ - clrtoeol(); - prints("%-*.*s\n", t_columns-1, t_columns-1, s); -} - -void -bbslua_logo(lua_State *L) -{ - int y, by = b_lines -1; // print - back from bottom - int i = 0; - double tocinterface = 0; - int tocs = 0; - char msg[STRLEN]; - - // get toc information - lua_getglobal(L, "toc"); - lua_getfield(L, -1, bbsluaTocTags[0]); - tocinterface = lua_tonumber(L, -1); lua_pop(L, 1); - - // query print-able toc tags - for (i = BLCONF_PRINT_TOC_INDEX; bbsluaTocTags[i]; i++) - { - lua_getfield(L, -1, bbsluaTocTags[i]); - if (lua_tostring(L, -1)) - tocs++; - lua_pop(L, 1); - } - - // prepare logo window - grayout(0, b_lines, GRAYOUT_DARK); - - // print compatibility test - // now (by) is the base of new information - if (tocinterface == 0) - { - by -= 4; y = by+2; - move(y-1, 0); - outs(ANSI_COLOR(0;31;47)); - fullmsg(""); - fullmsg(" ▲ 此程式缺少相容性資訊,您可能無法正常執行"); - fullmsg(" 若執行出現錯誤,請向原作者取得新版"); - fullmsg(""); - } - else if (tocinterface > BBSLUA_INTERFACE_VER) - { - by -= 4; y = by+2; - move(y-1, 0); - outs(ANSI_COLOR(0;1;37;41)); - fullmsg(""); - snprintf(msg, sizeof(msg), - " ▲ 此程式使用新版的 BBS-Lua 規格 (%0.3f),您可能無法正常執行", - tocinterface); - fullmsg(msg); - fullmsg(" 若執行出現錯誤,建議您重新登入 BBS 後再重試"); - fullmsg(""); - } - else if (tocinterface == BBSLUA_INTERFACE_VER) - { - // do nothing! - } - else - { - // should be comtaible - // prints("相容 (%.03f)", tocinterface); - } - - // print toc, if any. - if (tocs) - { - y = by - 1 - tocs; - by = y-1; - - move(y, 0); outs(ANSI_COLOR(0;1;30;47)); - fullmsg(""); - - // now try to print all TOC infos - for (i = BLCONF_PRINT_TOC_INDEX; bbsluaTocTags[i]; i++) - { - lua_getfield(L, -1, bbsluaTocTags[i]); - if (!lua_isstring(L, -1)) - { - lua_pop(L, 1); - continue; - } - move(++y, 0); - snprintf(msg, sizeof(msg), " %s: %-.*s", - bbsluaTocPrompts[i], STRLEN-12, lua_tostring(L, -1)); - msg[sizeof(msg)-1] = 0; - fullmsg(msg); - lua_pop(L, 1); - } - fullmsg(""); - } - - // print caption - move(by-2, 0); outc('\n'); - outs(ANSI_COLOR(0;1;37;44)); - snprintf(msg, sizeof(msg), - " ■ BBS-Lua %.03f (Build " __DATE__ " " __TIME__") ", - (double)BBSLUA_INTERFACE_VER); - fullmsg(msg); - - // system break key prompt - { - int sz = t_columns -1; - const char - *prompt1 = " 提醒您執行中隨時可按 ", - *prompt2 = "[Ctrl-C]", - *prompt3 = " 強制中斷 BBS-Lua 程式"; - sz -= strlen(prompt1); - sz -= strlen(prompt2); - sz -= strlen(prompt3); - outs(ANSI_COLOR(22;37)); outs(prompt1); - outs(ANSI_COLOR(1;31)); outs(prompt2); - outs(ANSI_COLOR(0;37;44));outs(prompt3); - prints("%*s", sz, ""); - outs(ANSI_RESET); - } - lua_pop(L, 1); -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA Script Loader -////////////////////////////////////////////////////////////////////////// - -typedef struct LoadS { - const char *s; - size_t size; - int lineshift; -} LoadS; - -static const char* bbslua_reader(lua_State *L, void *ud, size_t *size) -{ - LoadS *ls = (LoadS *)ud; - (void)L; - if (ls->size == 0) return NULL; - if (ls->lineshift > 0) { - const char *linefeed = "\n"; - *size = 1; - ls->lineshift--; - return linefeed; - } - *size = ls->size; - ls->size = 0; - return ls->s; -} - -static int bbslua_loadbuffer (lua_State *L, const char *buff, size_t size, - const char *name, int lineshift) { - LoadS ls; - ls.s = buff; - ls.size = size; - ls.lineshift = lineshift; - return lua_load(L, bbslua_reader, &ls, name); -} - -typedef struct AllocData { - size_t alloc_size; - size_t max_alloc_size; - size_t alloc_limit; -} AllocData; - -static void alloc_init(AllocData *ad) -{ - memset(ad, 0, sizeof(*ad)); - // real limit is not determined yet, just assign a big value - ad->alloc_limit = 20*1048576; -} - -static void *allocf (void *ud, void *ptr, size_t osize, size_t nsize) { - /* TODO use our own allocator, for better memory control, avoid fragment and leak */ - AllocData *ad = (AllocData*)ud; - - if (ad->alloc_size + nsize - osize > ad->alloc_limit) { - return NULL; - } - ad->alloc_size += nsize - osize; - if (ad->alloc_size > ad->max_alloc_size) { - ad->max_alloc_size = ad->alloc_size; - } - if (nsize == 0) { - free(ptr); - return NULL; - } - else - return realloc(ptr, nsize); -} -static int panic (lua_State *L) { - (void)L; /* to avoid warnings */ - fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", - lua_tostring(L, -1)); - return 0; -} - -void bbslua_loadLatest(lua_State *L, - char **pbs, char **pps, char **ppe, char **ppc, int *psz, - int *plineshift, char *bfpath, const char **pfpath) -{ - int r = 1; // max redirect for one article only. - - do { - char *xbs = NULL, *xps = NULL, *xpe = NULL, *xpc = NULL; - int xlineshift = 0; - size_t xsz; - const char *lastref = NULL; - char loadnext = 0, isnewver = 0; - - // detect file - xbs = bbslua_attach(bfpath, &xsz); - if (!xbs) - break; - - xps = xbs; - xpe = xps + xsz; - - if(!bbslua_detect_range(&xps, &xpe, &xlineshift)) - { - // not detected - bbslua_detach(xbs, xsz); - break; - } - - // detect and verify TOC meta data - lua_getglobal(L, "toc"); // stack 1 - if (lua_istable(L, -1)) - { - const char *oref, *otitle, *nref, *ntitle; - // load and verify tocinfo - lua_getfield(L, -1, "latestref"); // stack 2 - oref = lua_tostring(L, -1); - lua_getfield(L, -2, "title"); // stack 3 - otitle = lua_tostring(L, -1); - bbslua_load_TOC(L, xps, xpe, &xpc); - lua_getglobal(L, "toc"); // stack 4 - lua_getfield(L, -1, "latestref"); // stack 5 - nref = lua_tostring(L, -1); - lua_getfield(L, -2, "title"); // stack 6 - ntitle = lua_tostring(L, -1); - - if (oref && nref && otitle && ntitle && - strcmp(oref, nref) == 0 && - strcmp(otitle, ntitle) == 0) - isnewver = 1; - - // pop all - lua_pop(L, 5); // stack = 1 (old toc) - if (!isnewver) - lua_setglobal(L, "toc"); - else - lua_pop(L, 1); - } else { - lua_pop(L, 1); - bbslua_load_TOC(L, xps, xpe, &xpc); - } - - if (*pbs && !isnewver) - { - // different. - bbslua_detach(xbs, xsz); - break; - } - - // now, apply current buffer - if (*pbs) - { - bbslua_detach(*pbs, *psz); - // XXX fpath=bfpath, supporting only one level redirection now. - *pfpath = bfpath; - } - *pbs = xbs; *pps = xps; *ppe = xpe; *psz = xsz; - *ppc = xpc; - *plineshift = xlineshift; - -#ifdef AID_DISPLAYNAME - // quick exit - if (r <= 0) - break; - - // LatestRef only works if system supports AID. - // try to load next reference. - lua_getglobal(L, "toc"); // stack 1 - lua_getfield(L, -1, "latestref"); // stack 2 - lastref = lua_tostring(L, -1); - - while (lastref && *lastref) - { - // try to load by AID - char bn[IDLEN+1] = ""; - aidu_t aidu = 0; - unsigned char *p = (unsigned char*)bn; - - if (*lastref == '#') lastref++; - aidu = aidc2aidu((char*)lastref); - if (aidu <= 0) break; - - while (*lastref > ' ') lastref ++; // lastref points to zero of space - while (*lastref && *lastref <= ' ') lastref++; // lastref points to zero or board name - if (*lastref == '(') lastref ++; - - if (!*lastref) break; - strlcpy(bn, lastref, sizeof(bn)); - // truncate board name - // (not_alnum(ch) && ch != '_' && ch != '-' && ch != '.') - while (*p && - (isalnum(*p) || *p == '_' || *p == '-' || *p == '.')) p++; - *p = 0; - if (bn[0]) - { - bfpath[0] = 0; - setaidfile(bfpath, bn, aidu); - } - - if (bfpath[0]) - loadnext = 1; - break; - } - lua_pop(L, 2); - - if (loadnext) continue; -#endif // AID_DISPLAYNAME - break; - - } while (r-- > 0); -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA Hash -////////////////////////////////////////////////////////////////////////// - -#if 0 -static int -bbslua_hashWriter(lua_State *L, const void *p, size_t sz, void *ud) -{ - Fnv32_t *phash = (Fnv32_t*) ud; - *phash = fnv_32_buf(p, sz, *phash); - return 0; -} - -static Fnv32_t -bbslua_f2hash(lua_State *L) -{ - Fnv32_t fnvseed = FNV1_32_INIT; - lua_dump(L, bbslua_hashWriter, &fnvseed); - return fnvseed; -} - -static Fnv32_t -bbslua_str2hash(const char *str) -{ - if (!str) - return 0; - return fnv_32_str(str, FNV1_32_INIT); -} -#endif - -static Fnv32_t -bbslua_path2hash(const char *path) -{ - Fnv32_t seed = FNV1_32_INIT; - if (!path) - return 0; - if (path[0] != '/') // relative, append BBSHOME - seed = fnv_32_str(BBSHOME "/", seed); - return fnv_32_str(path, seed); -} - -////////////////////////////////////////////////////////////////////////// -// BBSLUA Main -////////////////////////////////////////////////////////////////////////// - -int -bbslua(const char *fpath) -{ - int r = 0; - lua_State *L; - char *bs = NULL, *ps = NULL, *pe = NULL, *pc = NULL; - char bfpath[PATHLEN] = ""; - int sz = 0; - int lineshift = 0; - AllocData ad; -#ifdef BBSLUA_USAGE - struct rusage rusage_begin, rusage_end; - struct timeval lua_begintime, lua_endtime; - gettimeofday(&lua_begintime, NULL); - getrusage(0, &rusage_begin); -#endif - -#ifdef UMODE_BBSLUA - unsigned int prevmode = getutmpmode(); -#endif - - // re-entrant not supported! - if (blrt.running) - return 0; - - // initialize runtime - BL_INIT_RUNTIME(); - -#ifdef BBSLUA_USAGE - bbslua_count = 0; -#endif - - // init lua - alloc_init(&ad); - L = lua_newstate(allocf, &ad); - if (!L) - return 0; - lua_atpanic(L, &panic); - - strlcpy(bfpath, fpath, sizeof(bfpath)); - - // load file - bbslua_loadLatest(L, &ps, &ps, &pe, &pc, &sz, &lineshift, bfpath, &fpath); - - if (!ps || !pe || ps >= pe) - { - if (bs) - bbslua_detach(bs, sz); - lua_close(L); - vmsg("BBS-Lua 載入錯誤: 未含 BBS-Lua 程式"); - return 0; - } - - // init library - bbsluaL_openlibs(L); - luaL_openlib(L, "bbs", lib_bbslua, 0); - -#ifdef BLSCONF_ENABLED - luaL_openlib(L, "store", lib_store, 0); -#endif // BLSCONF_ENABLED - - bbsluaRegConst(L); - - // load script - r = bbslua_loadbuffer(L, ps, pe-ps, "BBS-Lua", lineshift); - - // build hash or store name - blrt.storename = bbslua_path2hash(fpath); - // vmsgf("BBS-Lua Hash: %08X", blrt.storename); - - // unmap - bbslua_detach(bs, sz); - - if (r != 0) - { - const char *errmsg = lua_tostring(L, -1); - lua_close(L); - outs(ANSI_RESET); - move(b_lines-3, 0); clrtobot(); - outs("\n"); - outs(errmsg); - vmsg("BBS-Lua 載入錯誤: 請通知作者修正程式碼。"); - return 0; - } - -#ifdef UMODE_BBSLUA - setutmpmode(UMODE_BBSLUA); -#endif - - bbslua_logo(L); - vmsgf("提醒您執行中隨時可按 [Ctrl-C] 強制中斷 BBS-Lua 程式"); - - // ready for running - clear(); - blrt.running =1; - lua_sethook(L, bbsluaHook, LUA_MASKCOUNT, BLCONF_EXEC_COUNT ); - - refresh(); - // check is now done inside hook - r = lua_resume(L, 0); - - // even if r == yield, let's abort - you cannot yield main thread. - - if (r != 0) - { - const char *errmsg = lua_tostring(L, -1); - move(b_lines-3, 0); clrtobot(); - outs("\n"); - if (errmsg) outs(errmsg); - } - - lua_close(L); - blrt.running =0; - drop_input(); -#ifdef BBSLUA_USAGE - { - double cputime; - double load; - double walltime; - getrusage(0, &rusage_end); - gettimeofday(&lua_endtime, NULL); - cputime = bl_tv2double(&rusage_end.ru_utime) - bl_tv2double(&rusage_begin.ru_utime); - walltime = bl_tv2double(&lua_endtime) - bl_tv2double(&lua_begintime); - load = cputime / walltime; - log_filef("log/bbslua.log", LOG_CREAT, - "maxalloc=%d leak=%d op=%d cpu=%.3f Mop/s=%.1f load=%f file=%s\n", - (int)ad.max_alloc_size, (int)ad.alloc_size, - bbslua_count, cputime, bbslua_count / cputime / 1000000.0, load * 100, - fpath); - } -#endif - - // grayout(0, b_lines, GRAYOUT_DARK); - move(b_lines, 0); clrtoeol(); - vmsgf("BBS-Lua 執行結束%s。", - blrt.abort ? " (使用者中斷)" : r ? " (程式錯誤)" : ""); - BL_END_RUNTIME(); - clear(); - -#ifdef UMODE_BBSLUA - setutmpmode(prevmode); -#endif - - return 0; -} - -// vim:ts=4:sw=4:expandtab diff --git a/mbbsd/bbsluaext.c b/mbbsd/bbsluaext.c deleted file mode 100644 index ee3521ea..00000000 --- a/mbbsd/bbsluaext.c +++ /dev/null @@ -1,83 +0,0 @@ -/* This file contains non-standard modules which - * are fundamental in BBSLua framework. - */ - -/* Bitwise operations library */ -/* (c) Reuben Thomas 2000-2007 */ -/* - bitlib release 24 - ----------------- - by Reuben Thomas - http://luaforge.net/projects/bitlib - - -bitlib is a C library for Lua 5.x that provides bitwise operations. It -is copyright Reuben Thomas 2000-2007, and is released under the MIT -license, like Lua (see http://www.lua.org/copyright.html; it's -basically the same as the BSD license). There is no warranty. - -Please report bugs and make suggestions to the email address above, or -use the LuaForge trackers. - -Thanks to John Passaniti for his bitwise operations library, some of -whose ideas I used, to Shmuel Zeigerman for the test suite, to -Thatcher Ulrich for portability fixes, and to John Stiles for a bug -fix. -*/ - -#include -#include -#include - -typedef int32_t Integer; -typedef uint32_t UInteger; - -#define checkUInteger(L, n) ((UInteger)luaL_checknumber((L), (n))) - -#define TDYADIC(name, op, type1, type2) \ - static int bit_ ## name(lua_State* L) { \ - lua_pushnumber(L, (Integer)((type1)checkUInteger(L, 1) op (type2)checkUInteger(L, 2))); \ - return 1; \ - } - -#define MONADIC(name, op, type) \ - static int bit_ ## name(lua_State* L) { \ - lua_pushnumber(L, (Integer)(op (type)checkUInteger(L, 1))); \ - return 1; \ - } - -#define VARIADIC(name, op, type) \ - static int bit_ ## name(lua_State *L) { \ - int n = lua_gettop(L), i; \ - Integer w = (type)checkUInteger(L, 1); \ - for (i = 2; i <= n; i++) \ - w op (type)checkUInteger(L, i); \ - lua_pushnumber(L, (Integer)w); \ - return 1; \ - } - -MONADIC(cast, +, Integer) -MONADIC(bnot, ~, Integer) -VARIADIC(band, &=, Integer) -VARIADIC(bor, |=, Integer) -VARIADIC(bxor, ^=, Integer) -TDYADIC(lshift, <<, Integer, UInteger) -TDYADIC(rshift, >>, UInteger, UInteger) -TDYADIC(arshift, >>, Integer, UInteger) - -static const struct luaL_reg bitlib[] = { - {"cast", bit_cast}, - {"bnot", bit_bnot}, - {"band", bit_band}, - {"bor", bit_bor}, - {"bxor", bit_bxor}, - {"lshift", bit_lshift}, - {"rshift", bit_rshift}, - {"arshift", bit_arshift}, - {NULL, NULL} -}; - -LUALIB_API int luaopen_bit (lua_State *L) { - luaL_openlib(L, "bit", bitlib, 0); - return 1; -} diff --git a/mbbsd/board.c b/mbbsd/board.c deleted file mode 100644 index f5cfe0de..00000000 --- a/mbbsd/board.c +++ /dev/null @@ -1,1960 +0,0 @@ -/* $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]) - -#define HasFavEditPerm() HasUserPerm(PERM_BASIC) - -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. */ -// XXX IS_LISTING_FAV() does not mean we are in favorite. -// That is controlled by IN_FAVORITE(). -#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; -} - -/** - * @param[in] boardname board name, case insensitive - * @return 0 if success - * -1 if not found - * -2 permission denied - * -3 error - * @note enter board: - * 1. setup brc (currbid, currboard, currbrdattr) - * 2. set currbid, currBM, currmode, currdirect - * 3. utmp brc_id - */ -int enter_board(const char *boardname) -{ - boardheader_t *bh; - int bid; - char bname[IDLEN+1]; - char bpath[60]; - struct stat st; - - /* checking ... */ - if (boardname[0] == '\0' || !(bid = getbnum(boardname))) - return -1; - assert(0<=bid-1 && bid-1brdname, sizeof(bname)); - if (bname[0] == '\0') - return -3; - - setbpath(bpath, bname); - if (stat(bpath, &st) == -1) { - return -3; - } - - /* really enter board */ - brc_update(); - brc_initial_board(bname); - setutmpbid(currbid); - - set_board(); - setbdir(currdirect, currboard); - curredit &= ~EDIT_MAIL; - - return 0; -} - - -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; -} - -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( is_BM_cache(bptr - bcache + 1) ) /* XXXbid */ - return 1; - - /* 祕密看板:核對首席板主的好友名單 */ - if (brdattr & BRD_HIDE) { /* 隱藏 */ - if (!is_hidden_board_friend((int)(bptr - bcache) + 1, currutmp->uid)) { - if (brdattr & BRD_POSTMASK) - return 0; - else - return 2; - } else - return 1; - } - - /* 十八禁看板 */ - if( (brdattr & BRD_OVER18) && !over18 ) - return 0; - - /* 限制閱讀權限 */ - if (level && !(brdattr & BRD_POSTMASK) && !HasUserPerm(level)) - return 0; - - return 1; -} - -// board configuration utilities - -static int -b_post_note(void) -{ - char buf[200], yn[3]; - - // if(!(currmode & MODE_BOARD)) return DONOTHING; - stand_title("自訂注意事項"); - - setbfile(buf, currboard, FN_POST_NOTE); - move(b_lines-2, 0); clrtobot(); - - if (more(buf, NA) == -1) - more("etc/" FN_POST_NOTE, NA); - getdata(b_lines - 2, 0, "是否要用自訂發文注意事項? [y/N]", - 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, "是否要用自訂競標文章注意事項? [y/N]", - yn, sizeof(yn), LCECHO); - if (yn[0] == 'y') - vedit(buf, NA, NULL); - else - unlink(buf); - - return FULLUPDATE; -} - -static int -b_posttype() -{ - 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-1posttype_f; - for( i = 0 ; i < 8 ; ++i ){ - move(2+i,0); - outs("文章種類: "); - strlcpy(genbuf, bp->posttype + i * 4, 5); - sprintf(title, "%d.", i + 1); - if( !getdata_buf(2+i, 11, title, genbuf, 5, DOECHO) ) - break; - sprintf(posttype + i * 4, "%-4.4s", genbuf); - if( posttype_f & (1<brdname, "postsample", i); - aborted = vedit(filepath, NA, NULL); - if (aborted == -1) { - clear(); - posttype_f &= ~(1<posttype_f = posttype_f; - strlcpy(bp->posttype, posttype, sizeof(bp->posttype)); /* 這邊應該要防race condition */ - - assert(0<=currbid-1 && currbid-1brdname); outs(" 看板設定"); - i = t_columns - strlen(bp->brdname) - strlen(" 看板設定") - 2; - for (; i>0; i--) - outc(' '); - outs(ANSI_RESET); - - move(ytitle +2, 0); - clrtobot(); - - prints(" 中文敘述: %s\n", bp->title); - prints(" 板主名單: %s\n", (bp->BM[0] > ' ')? bp->BM : "(無)"); - - outs(" \n"); // at least one character, for move_ansi. - - prints( " " ANSI_COLOR(1;36) "h" ANSI_RESET - " - 公開狀態(是否隱形): %s " ANSI_RESET "\n", - (bp->brdattr & BRD_HIDE) ? - ANSI_COLOR(1)"隱形":"公開"); - - prints( " " ANSI_COLOR(1;36) "g" ANSI_RESET - " - 隱板時 %s 進入十大排行榜" ANSI_RESET "\n", - (bp->brdattr & BRD_BMCOUNT) ? - ANSI_COLOR(1)"可以" ANSI_RESET: - "不可"); - - prints( " " ANSI_COLOR(1;36) "r" ANSI_RESET - " - %s " ANSI_RESET "推薦文章\n", - (bp->brdattr & BRD_NORECOMMEND) ? - ANSI_COLOR(31)"不可":"可以"); - -#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 "自動記錄,且 %s " - ANSI_RESET "發文權限\n", - (bp->brdattr & BRD_CPLOG) ? - ANSI_COLOR(1)"會" : "不會" , - (bp->brdattr & BRD_CPLOG) ? - ANSI_COLOR(1)"需要" : "不需" - ); -#endif - - prints( " " ANSI_COLOR(1;36) "L" ANSI_RESET - " - 若有轉信則發文時預設 %s " ANSI_RESET "\n", - (bp->brdattr & BRD_LOCALSAVE) ? - "站內存檔(不轉出)" : ANSI_COLOR(1)"站際存檔(轉出)" ); - - // use '8' instead of '1', to prevent 'l'/'1' confusion - prints( " " ANSI_COLOR(1;36) "8" ANSI_RESET - " - 未滿十八歲 %s " ANSI_RESET - "進入\n", (bp->brdattr & BRD_OVER18) ? - ANSI_COLOR(1) "不可以" : "可以" ); - - prints( " " ANSI_COLOR(1;36) "y" ANSI_RESET - " - %s" ANSI_RESET - " 回文\n", - (bp->brdattr & BRD_NOREPLY) ? - ANSI_COLOR(1)"不可以" : "可以" ); - - prints( " " ANSI_COLOR(1;36) "e" ANSI_RESET - " - 發文權限: %s" ANSI_RESET "\n", - (bp->brdattr & BRD_RESTRICTEDPOST) ? - ANSI_COLOR(1)"只有板友才可發文" : "無特別設定" ); - - ipostres = b_lines - LNPOSTRES; - move_ansi(ipostres++, COLPOSTRES-2); - - if (CheckPostPerm() && CheckPostRestriction(currbid)) - outs(ANSI_COLOR(1;32)); - else - outs(ANSI_COLOR(1;31)); - outs("發文與推文限制" ANSI_RESET); - -#define POSTRESTRICTION(msg,utag) \ - prints(msg, attr ? ANSI_COLOR(1) : "", i, attr ? ANSI_RESET : "") - - if (bp->post_limit_logins) - { - move_ansi(ipostres++, COLPOSTRES); - i = (int)bp->post_limit_logins * 10; - attr = (cuser.numlogins < i) ? 1 : 0; - if (attr) outs(ANSI_COLOR(31)); - prints("上站次數 %d 次以上", i); - if (attr) outs(ANSI_RESET); - } - - if (bp->post_limit_posts) - { - move_ansi(ipostres++, COLPOSTRES); - i = (int)bp->post_limit_posts * 10; - attr = (cuser.numposts < i) ? 1 : 0; - if (attr) outs(ANSI_COLOR(31)); - prints("文章篇數 %d 篇以上", i); - if (attr) outs(ANSI_RESET); - } - - if (bp->post_limit_regtime) - { - move_ansi(ipostres++, COLPOSTRES); - i = bp->post_limit_regtime; - attr = (cuser.firstlogin > - (now - (time4_t)bp->post_limit_regtime * 2592000)) ? 1 : 0; - if (attr) outs(ANSI_COLOR(31)); - prints("註冊時間 %d 個月以上",i); - if (attr) outs(ANSI_RESET); - } - - // if (bp->post_limit_badpost) - { - move_ansi(ipostres++, COLPOSTRES); - i = 255 - bp->post_limit_badpost; - attr = (cuser.badpost > i) ? 1 : 0; - if (attr) outs(ANSI_COLOR(31)); - prints("劣文篇數 %d 篇以下", i); - if (attr) outs(ANSI_RESET); - } - - if (!CheckPostPerm()) - { - const char *msg = postperm_msg(bp->brdname); - if (msg) // some reasons - { - move_ansi(ipostres++, COLPOSTRES); - outs(ANSI_COLOR(1;31)); - outs(msg); - outs(ANSI_RESET); - } - } - - // show BM commands - { - const char *aCat = ANSI_COLOR(1;32); - const char *aHot = ANSI_COLOR(1;36); - const char *aRst = ANSI_RESET; - - if (!isBM) - { - aCat = ANSI_COLOR(1;30;40); - aHot = ""; - aRst = ""; - } - - ipostres ++; - move_ansi(ipostres++, COLPOSTRES-2); - outs(aCat); - outs("名單編輯與其它:"); - if (!isBM) outs(" (需板主權限)"); - outs(aRst); - move_ansi(ipostres++, COLPOSTRES); - prints("%sv%s)可見名單 %sw%s)水桶名單 ", - aHot, aRst, aHot, aRst); - move_ansi(ipostres++, COLPOSTRES); - prints("%sm%s)舉辦投票 %so%s)投票名單 ", - aHot, aRst, aHot, aRst); - move_ansi(ipostres++, COLPOSTRES); - prints("%sc%s)文章類別 %sn%s)發文注意事項 ", - aHot, aRst, aHot, aRst); - outs(ANSI_RESET); - } - - move(b_lines, 0); - if (!isBM) - { - vmsg("您對此板無管理權限"); - return FULLUPDATE; - } - - switch(tolower(getans("請輸入要改變的設定, 其它鍵結束: "))) - { -#ifdef USE_AUTOCPLOG - case 'x': - bp->brdattr ^= BRD_CPLOG; - touched = 1; - break; -#endif - case 'l': - 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; - hbflreload(currbid); - } else { - bp->brdattr |= BRD_HIDE; - bp->brdattr |= BRD_POSTMASK; - } - touched = 1; - break; - - case 'g': -#ifndef BMCHS - if (!HasUserPerm(PERM_SYSOP)) - { - vmsg("此項設定需要站長權限"); - break; - } -#endif - bp->brdattr ^= BRD_BMCOUNT; - 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 '8': - if (!over18) - { - vmsg("板主本身未滿 18 歲。"); - } else { - bp->brdattr ^= BRD_OVER18; - touched = 1; - } - break; - - case 'v': - clear(); - friend_edit(BOARD_VISABLE); - assert(0<=currbid-1 && currbid-1brdattr ^= BRD_NOREPLY; - touched = 1; - break; - - default: - finished = 1; - break; - } - } - if(touched) - { - assert(0<=currbid-1 && currbid-1brdname); - vmsg("已儲存新設定"); - } - else - vmsg("未改變任何設定"); - - return FULLUPDATE; -} - -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, 0) ) - 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-1bsorted[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; - - // ptt 2 local modification - // XXX maybe some developer discovered signed short issue? - assert(n != -32769); - - assert(0<=n && ntotal = &(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])-1title, 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; - // 有些人 某些 bid < 0 Orzz // ptt2 local modification - if (fav_getid(&fav->favh[i]) < 1) - continue; - 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(0HBcache[i] == -1) - continue; - addnewbrdstat(SHM->HBcache[i], HasBoardPerm(&bcache[SHM->HBcache[i]])); - } - } -#endif - else { // general case - nbrdsize = numboards; - assert(0bsorted[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-1firstchild[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-1brdattr & 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-1childcount = 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); - NameList_resizefor(&namelist, brdnum); - 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, 0)) - 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, 0)) - 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("篩選列表"); // never reach here? - - 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); - // TODO remove ascii art here - 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)); - - // boards in Ptt series are very, very large. - // let's create more space for board numbers, - // and less space for BM. - // - // newflag is not so different now because we use all 5 digits. - - outs( newflag ? " 總數" : " 編號"); - outs(" 看 板 類別 轉信 中 文 敘 述 人氣 板 主"); - outslr("", 74, 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() && brdnum == 1 && get_fav_type(&nbrd[0]) == 0) { - - // (a) or (i) needs HasUserPerm(PERM_LOGINOK)). - // 3 = first line of empty area - if (!HasFavEditPerm()) - { - // TODO actually we cannot use 's' (for PTT)... - mouts(3, 10, - "--- 註冊的使用者才能新增看板喔 (可按 s 手動選取) ---"); - } else { - // normal user. tell him what to do. - mouts(3, 10, - "--- 空目錄,請按 a 新增或用 y 列出全部看板後按 z 增刪 ---"); - } - return; - } - - while (++myrow < b_lines) { - - move(myrow, 0); - clrtoeol(); - - if (head < brdnum) { - assert(0<=head && headmyattr & NBRD_LINE){ - if( !newflag ) - prints("%7d %c ", head, ptr->myattr & NBRD_TAG ? 'D' : ' '); - else - prints("%7s ", ""); - - if (!(ptr->myattr & NBRD_FAV)) - outs(ANSI_COLOR(1;30)); - - outs("------------" - " " - // "------" - "------------------------------------------" - ANSI_RESET "\n"); - continue; - } - else if (ptr->myattr & NBRD_FOLDER){ - char *title = get_folder_title(ptr->bid); - prints("%7d %c ", - newflag ? - get_data_number(get_fav_folder(getfolder(ptr->bid))) : - head, ptr->myattr & NBRD_TAG ? 'D' : ' '); - - // well, what to print with myfav folders? - // this style is too long and we don't want to - // fight with users... - // think about new way some otherday. - prints("%sMyFavFolder" ANSI_RESET " 目錄 □%-34s", - !(cuser.uflag2 & FAVNOHILIGHT)?HILIGHT_COLOR : "", - title); - /* - if (!(cuser.uflag2 & FAVNOHILIGHT)) - outs(HILIGHT_COLOR); - prints("%-12s", "[Folder]"); - outs(ANSI_RESET); - prints(" 目錄 Σ%-34s", title); - */ - /* - outs(ANSI_COLOR(0;36)); - prints("Σ%-70.70s", title); - outs(ANSI_RESET); - */ - continue; - } - - if (IN_CLASSROOT()) - outs(" "); - else { - if (!GROUPOP() && !HasBoardPerm(B_BH(ptr))) { - if (newflag) prints("%7s", ""); - else prints("%7d", head); - prints(" %c Unknown?? 隱板 ?這個板是隱板", - ptr->myattr & NBRD_TAG ? 'D' : ' '); - continue; - } - } - - if (newflag && B_BH(ptr)->brdattr & BRD_GROUPBOARD) - outs(" "); - else - prints("%7d%c%s", - newflag ? (int)(B_TOTAL(ptr)) : 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]); - - 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); -#ifdef EXTRA_HOTBOARD_COLORS - // piaip 2008/02/04: new colors - else if (B_BH(ptr)->nuser >= 100000) - outs(ANSI_COLOR(1;35) "爆!" ANSI_RESET); - else if (B_BH(ptr)->nuser >= 60000) - outs(ANSI_COLOR(1;33) "爆!" ANSI_RESET); - else if (B_BH(ptr)->nuser >= 30000) - outs(ANSI_COLOR(1;32) "爆!" ANSI_RESET); - else if (B_BH(ptr)->nuser >= 10000) - outs(ANSI_COLOR(1;36) "爆!" ANSI_RESET); -#endif - 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 - 68, B_BH(ptr)->BM); - } else { - prints("%-40.40s %.*s", B_BH(ptr)->title + 7, - t_columns - 68, 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-1bid = 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-1favh[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-1brdname); - } - } - 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[PATHLEN]; - - 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) { - - /////////////////////////////////////////////////////// - // General Hotkeys - /////////////////////////////////////////////////////// - - case 'h': - show_helpfile(fn_boardlisthelp); - show_brdlist(head, 1, newflag); - break; - case Ctrl('W'): - whereami(); - head = -1; - break; - - case 'c': - show_brdlist(head, 1, newflag ^= 1); - break; - case Ctrl('I'): - t_idle(); - show_brdlist(head, 1, newflag); - break; - - case 'e': - case KEY_LEFT: - case EOF: - ch = 'q'; - case 'q': - if (keyword[0]) { - keyword[0] = 0; - brdnum = -1; - ch = ' '; - } - 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 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 && num0); - if(get_fav_type(&nbrd[0]) != 0) - fav_tag(ptr->bid, get_fav_type(ptr), EXCH); - } - else if (HasUserPerm(PERM_SYSOP) || - HasUserPerm(PERM_SYSSUPERSUBOP) || - HasUserPerm(PERM_SYSSUBOP) || - HasUserPerm(PERM_BOARD)) { - /* 站長管理用的 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 '/': - getdata_buf(b_lines - 1, 0, "請輸入看板中文關鍵字:", - keyword, sizeof(keyword), DOECHO); - brdnum = -1; - break; - case 'S': - if(IS_LISTING_FAV()){ - move(b_lines - 2, 0); clrtobot(); - 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 'v': - case 'V': - assert(0<=num && nummyattr &= ~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) { - head = -1; - num = tmp; - break; - } - // TODO try global search? - // TODO entering boards is now too complex... - // please refine the code. - // - // if (Select() != NEWDIRECT) { - // } - - // update screen - head = -1; - num = tmp; - 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 && nummyattr & 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 && nummyattr & NBRD_SYMBOLIC) { - replace_link_by_target(ptr); - } - } - - assert(0<=ptr->bid-1 && ptr->bid-1brdattr & 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; - } - } - break; - /////////////////////////////////////////////////////// - // MyFav Functionality (Require PERM_BASIC) - /////////////////////////////////////////////////////// - case 'y': - if (HasFavEditPerm() && !(IN_CLASS())) { - if (get_current_fav() != NULL || !IS_LISTING_FAV()){ - yank_flag ^= 1; /* FAV <=> BRD */ - } - brdnum = -1; - } - break; - case Ctrl('D'): - if (HasFavEditPerm()) { - if (getans("刪除所有標記[N]?") == 'y'){ - fav_remove_all_tagged_item(); - brdnum = -1; - } - } - break; - case Ctrl('A'): - if (HasFavEditPerm()) { - fav_add_all_tagged_item(); - brdnum = -1; - } - break; - case Ctrl('T'): - if (HasFavEditPerm()) { - 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()) { - // TODO XXX why need symlink here? Can we remove it? - if (make_symbolic_link_interactively(class_bid) < 0) - break; - brdnum = -1; - head = 9999; - } - else if (HasFavEditPerm() && 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 'd': // why don't we enable 'd'? - case 'z': - case 'm': - if (HasFavEditPerm()) { - assert(0<=num && nummyattr & 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 (ch != 'd') // 'd' only deletes something. - { - if (fav_add_board(ptr->bid) == NULL) - vmsg("你的最愛太多了啦 真花心"); - else - ptr->myattr |= NBRD_FAV; - } - } - brdnum = -1; - head = 9999; - } - break; - case 'M': - if (HasFavEditPerm()){ - if (IN_FAVORITE() && IS_LISTING_FAV()){ - imovefav(num); - brdnum = -1; - head = 9999; - } - } - break; - case 'g': - if (HasFavEditPerm() && 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 && numattr |= 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 BRC/fav once per 10 minutes */ - if (now - last_save_fav_and_brc > 10 * 60) { - fav_save(); - brc_finalize(); - - last_save_fav_and_brc = now; - } - break; - - /////////////////////////////////////////////////////// - // Administrator Only - /////////////////////////////////////////////////////// - - case 'F': - case 'f': - if (HasUserPerm(PERM_SYSOP)) { - getbcache(class_bid)->firstchild[cuser.uflag & BRDSORT_FLAG ? 1 : 0] = 0; - brdnum = -1; - } - break; - case 'D': - if (HasUserPerm(PERM_SYSOP) || - (HasUserPerm(PERM_SYSSUPERSUBOP) && GROUPOP())) { - assert(0<=num && nummyattr & NBRD_SYMBOLIC) { - if (getans("確定刪除連結?[N/y]") == 'y') - delete_symbolic_link(getbcache(ptr->bid), ptr->bid); - } - brdnum = -1; - } - break; - case 'E': - if (HasUserPerm(PERM_SYSOP | PERM_BOARD) || GROUPOP()) { - assert(0<=num && numbrdname); - 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; - - } - } 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/mbbsd/brc.c b/mbbsd/brc.c deleted file mode 100644 index 4317dfd1..00000000 --- a/mbbsd/brc.c +++ /dev/null @@ -1,596 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -/** - * 關於本檔案的細節,請見 docs/brc.txt。 - * v3: add last modified time for comment system. double max size. - * original time_t as 'create'. - */ - -// WARNING: Check ../pttbbs.conf, you may have overide these value there -// TODO MAXSIZE may be better smaller to fit into memory page. -#ifndef BRC_MAXNUM -#define BRC_MAXSIZE 49152 /* Effective size of brc rc file, 8192 * 3 * 2 */ -#define BRC_MAXNUM 80 /* Upper bound of brc_num, size of brc_list */ -#endif - -#define BRC_BLOCKSIZE 1024 - -// Note: BRC v3 should already support MAX_BOARD > 65535 and BRC_MAXSIZE > 65535, -// but not widely tested yet. -#if MAX_BOARD > 65535 || BRC_MAXSIZE > 65535 -#error Max number of boards or BRC_MAXSIZE cannot fit in unsigned short, \ -please rewrite brc.c (v2) -#endif - -typedef uint32_t brcbid_t; -typedef uint16_t brcnbrd_t; - -typedef struct { - time4_t create; - time4_t modified; -} brc_rec; - -/* old brc rc file form: - * board_name 15 bytes - * brc_num 1 byte, binary integer - * brc_list brc_num * sizeof(brc_rec) bytes */ - -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 brc_rec brc_list[BRC_MAXNUM]; - -static char * const fn_brc2= ".brc2"; -static char * const fn_brc = ".brc3"; - -/** - * 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(brc_rec); /* 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 brc_rec * -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 (brc_rec*)(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 brc_rec *list) -{ - char * tmp; - if (num > 0 && list[0].create > 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(brc_rec); - if (tmp <= endp) - memcpy(ptr, list, num * sizeof(brc_rec)); /* 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 brc_rec* 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].create < 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(brc_rec); - 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(brc_rec); - 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(brc_rec)); - 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); - } -} - -void -read_brc2(void) -{ - char brcfile[STRLEN]; - int fd; - size_t sz2 = 0, sz3 = 0; - char *cvt = NULL, *cvthead = NULL; - - // brc v2 is using 16 bit for brcbid_t and brcnbrd_t. - uint16_t bid2, num2; - - brcbid_t bid; - brcnbrd_t num; - time4_t create; - brc_rec rec; - - setuserfile(brcfile, fn_brc2); - - if ((fd = open(brcfile, O_RDONLY)) == -1) - return; - - sz2 = dashs(brcfile); - sz3 = sz2 * 2; // max double size - - cvthead = cvt = malloc (sz3); - memset(cvthead, 0, sz3); - // now calculate real sz3 - - while (read(fd, &bid2, sizeof(bid2)) > 0) - { - if (read(fd, &num2, sizeof(num2)) < 1) - break; - - bid = bid2; - num = num2; - - // some brc v2 contains bad structure. - // check pointer here. - if (cvt + sizeof(brcbid_t) + sizeof(brcnbrd_t) - cvthead >= sz3) - break; - - *(brcbid_t*) cvt = bid; cvt += sizeof(brcbid_t); - *(brcnbrd_t*)cvt = num; cvt += sizeof(brcnbrd_t); - - // some brc v2 contains bad structure. - // check pointer here. - for (; num > 0 && (cvt + sizeof(brc_rec) - cvthead) <= sz3 ; num--) - { - if (read(fd, &create, sizeof(create)) < 1) - break; - - rec.create = create; - rec.modified = create; - - *(brc_rec*)cvt = rec; cvt += sizeof(brc_rec); - } - } - close(fd); - - // now cvthead is ready for v3. - sz3 = cvt - cvthead; - brc_get_buf(sz3); - // new size maybe smaller, check brc_alloc instead - if (sz3 > brc_alloc) - sz3 = brc_alloc; - brc_size = sz3; - memcpy(brc_buf, cvthead, sz3); - - free(cvthead); -} - -inline static void -read_brc_buf(void) -{ - char brcfile[STRLEN]; - int fd; - struct stat brcstat; - - if (brc_buf != NULL) - return; - - brc_size = 0; - setuserfile(brcfile, fn_brc); - - if ((fd = open(brcfile, O_RDONLY)) == -1) - { - read_brc2(); - return; - } - - fstat(fd, &brcstat); - brc_get_buf(brcstat.st_size); - brc_size = read(fd, brc_buf, brc_alloc); - close(fd); -} - -/* 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, brc_rec *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(brc_rec)); - return *num; - } - list[0].create = *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 brc_list[n].create) { - if (brc_num < BRC_MAXNUM) - brc_num++; - /* insert frec into brc_list */ - for (i = brc_num - 1; --i >= n; brc_list[i + 1] = brc_list[i]); - brc_list[n] = frec; - brc_changed = 1; - return; - } - } -} - -// return: -// 0 - read -// 1 - unread (by create) -// 2 - unread (by modified) -int -brc_unread_time(int bid, time4_t ftime, time4_t modified) -{ - int i; - int bnum; - const brc_rec *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].create) - return 1; - else if (ftime == blist[i].create) - { - time4_t brcm = blist[i].modified; - if (modified == 0 || brcm == 0) - return 0; - - // bad case... seems like that someone is making -1. - if (modified == (time4_t)-1 || brcm == (time4_t)-1) - return 0; - - // one case is, some special file headers (ex, - // always bottom) may cause issue. They share create - // time (filename) and apply different modify time. - // so let's back to 'greater'. - return modified > brcm ? 2 : 0; - } - } - return 0; -} - -int -brc_unread(int bid, const char *fname, time4_t modified) -{ - int ftime; - - ftime = atoi(&fname[2]); /* this will get the time of the file created */ - - return brc_unread_time(bid, ftime, modified); -} diff --git a/mbbsd/cache.c b/mbbsd/cache.c deleted file mode 100644 index 5a30c24f..00000000 --- a/mbbsd/cache.c +++ /dev/null @@ -1,1215 +0,0 @@ -/* $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<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_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_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) - ;/* 指到第一筆 */ - // piaip Tue Jan 8 09:28:03 CST 2008 - // many people bugged about that their utmp have invalid - // entry on record. - // we found them caused by crash process (DEBUGSLEEPING) which - // may occupy utmp entries even after process was killed. - // because the memory is invalid, it is not safe for those process - // to wipe their utmp entry. it should be done by some external - // daemon. - // however, let's make a little workaround here... - for (; unum > 0 && i >= 0 && ulist[i] >= 0 && - SHM->uinfo[ulist[i]].uid == uid; unum--, i++) - { - if (SHM->uinfo[ulist[i]].mode == DEBUGSLEEPING) - unum ++; - } - if (unum == 0 && i > 0 && ulist[i-1] >= 0 && - SHM->uinfo[ulist[i-1]].uid == uid) - return &SHM->uinfo[ulist[i-1]]; - /* - 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); - } -} - -unsigned int -getutmpmode(void) -{ - if (currutmp) - return currutmp->mode; - return currstat; -} -#endif - -/* - * section - board cache - */ -void touchbtotal(int bid) { - assert(0<=bid-1 && bid-1total[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/" FN_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 && bidBbusystate || 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-1brdname[0]) return; - setbfile(fname, bh->brdname, FN_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-1brdname, FN_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-1total[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-1Bnumber - 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; -} - -const char * -postperm_msg(const char *bname) -{ - register int i; - char buf[PATHLEN]; - boardheader_t *bp = NULL; - - setbfile(buf, bname, fn_water); - if (belong(buf, cuser.userid)) - return "使用者水桶中"; - - if (!strcasecmp(bname, DEFAULT_BOARD)) - return NULL; - - if (!(i = getbnum(bname))) - return "看板不存在"; - - assert(0<=i-1 && i-1brdattr & BRD_GUESTPOST) - return NULL; - - if (!HasUserPerm(PERM_POST)) - return "無發文權限"; - - /* 秘密看板特別處理 */ - if (bp->brdattr & BRD_HIDE) - return NULL; - else if (bp->brdattr & BRD_RESTRICTEDPOST && - !is_hidden_board_friend(i, usernum)) - return "看板限制發文"; - - if (HasUserPerm(PERM_VIOLATELAW) && (bp->level & PERM_VIOLATELAW)) - return NULL; - else if (HasUserPerm(PERM_VIOLATELAW)) - return "罰單未繳"; - - if (!(bp->level & ~PERM_POST)) - return NULL; - if (!HasUserPerm(bp->level & ~PERM_POST)) - return "未達看板要求權限"; - return NULL; -} - -int -haspostperm(const char *bname) -{ - return postperm_msg(bname) == NULL ? 1 : 0; -} - -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-1BM, 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-1uid == 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 */ -/*-------------------------------------------------------*/ -int -filter_aggressive(const char*s) -{ - if ( - /* - strstr(s, "此處放較不適當的爭議性字句") != NULL || - */ - 0 - ) - return 1; - return 0; -} - -int -filter_dirtywords(const char*s) -{ - if ( - strstr(s, "幹你娘") != NULL || - 0) - return 1; - return 0; -} - -#define AGGRESSIVE_FN ".aggressive" -static char drop_aggressive = 0; - -void -load_aggressive_state() -{ - if (dashf(AGGRESSIVE_FN)) - drop_aggressive = 1; - else - drop_aggressive = 0; -} - -void -set_aggressive_state(int s) -{ - FILE *fp = NULL; - if (s) - { - fp = fopen(AGGRESSIVE_FN, "wb"); - fclose(fp); - } else { - remove(AGGRESSIVE_FN); - } -} - -/* 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, aggid, rawid; - - SHM->Pbusystate = 1; - SHM->last_film = 0; - bzero(SHM->notes, sizeof(SHM->notes)); - setapath(pbuf, GLOBAL_NOTE); - setadir(buf, pbuf); - - load_aggressive_state(); - id = aggid = rawid = 0; // effective count, aggressive count, total (raw) count - - if ((fp = fopen(buf, "r"))) { - // .DIR loop - while (fread(&item, sizeof(item), 1, fp)) { - - int chkagg = 0; // should we check aggressive? - - if (item.title[3] != '<' || item.title[8] != '>') - continue; - -#ifdef GLOBAL_NOTE_AGGCHKDIR - // TODO aggressive: only count '<點歌>' section - if (strcmp(item.title+3, GLOBAL_NOTE_AGGCHKDIR) == 0) - chkagg = 1; -#endif - - snprintf(buf, sizeof(buf), "%s/%s/" FN_DIR, - pbuf, item.filename); - - if (!(fp1 = fopen(buf, "r"))) - continue; - - // file loop - 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; - rawid ++; - - // filtering - if (filter_dirtywords(SHM->notes[id])) - { - memset(SHM->notes[id], 0, sizeof(SHM->notes[0])); - rawid --; - } - else if (chkagg && filter_aggressive(SHM->notes[id])) - { - aggid++; - // handle aggressive notes by last detemined state - if (drop_aggressive) - memset(SHM->notes[id], 0, sizeof(SHM->notes[0])); - else - id++; -#ifdef _BBS_UTIL_C_ - // Debug purpose - // printf("found aggressive: %s\n", buf); -#endif - } - else - { - id++; - } - - fclose(fp2); - if (id >= MAX_MOVIE) - break; - - } // end of file loop - fclose(fp1); - - if (id >= MAX_MOVIE) - break; - } // end of .DIR loop - fclose(fp); - - // decide next aggressive state - if (rawid && aggid*3 >= rawid) // if aggressive exceed 1/3 - set_aggressive_state(1); - else - set_aggressive_state(0); - -#ifdef _BBS_UTIL_C_ - printf("id(%d)/agg(%d)/raw(%d)\n", - id, aggid, rawid); -#endif - } - 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) { - strcpy(SHM->home_desc[SHM->home_num], "雲深不知處"); - } - else { - strlcpy(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-1hbfl[bid-1], hbfl, sizeof(hbfl)); -} - -/* 是否通過板友測試. 如果在板友名單中的話傳回 1, 否則為 0 */ -int -is_hidden_board_friend(int bid, int uid) -{ - int i; - - assert(0<=bid-1 && bid-1hbfl[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 1; - } - return 0; -} - -#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/mbbsd/cal.c b/mbbsd/cal.c deleted file mode 100644 index 1f6f9d9d..00000000 --- a/mbbsd/cal.c +++ /dev/null @@ -1,630 +0,0 @@ -/* $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" - -/* Heat:發票 */ -int -vice(int money, const char *item) -{ - char buf[128]; - unsigned int viceserial = (currutmp->lastact % 10000) * 10000 + - random() % 10000; - - // new logic: do not send useless vice tickets - demoney(-money); - if (money < VICE_MIN) - return 0; - - setuserfile(buf, VICE_NEW); - log_filef(buf, LOG_CREAT, "%8.8d\n", viceserial); - snprintf(buf, sizeof(buf), - "%s 花了$%d 編號[%08d]", item, money, viceserial); - mail_id(cuser.userid, buf, "etc/vice.txt", BBSMNAME "經濟部"); - 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\n", cuser.userid); - outs(ANSI_COLOR(1) "注意點歌內容請勿涉及謾罵 人身攻擊 猥褻" - "公然侮辱 誹謗\n" - "若有上述違規情形,站方將保留決定是否公開播放的權利\n" - "如不同意請按 (3) 離開。" ANSI_RESET "\n"); - getdata(18, 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, 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(19, 0, "點歌者(可匿名): ", sender, sizeof(sender), DOECHO, cuser.userid); - getdata(20, 0, "點給(可匿名): ", receiver, sizeof(receiver), DOECHO); - - getdata_str(21, 0, "想要要對他(她)說..:", say, - sizeof(say), DOECHO, "我愛妳.."); - snprintf(save_title, sizeof(save_title), - "%s:%s", sender, say); - getdata_str(22, 0, "寄到誰的信箱(真實 ID 或 E-mail)?", - address, sizeof(address), LCECHO, receiver); - vmsg("接著要選歌囉..進入歌本好好的選一首歌吧..^o^"); - a_menu("點歌歌本", SONGBOOK, 0, 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_filef("etc/osong.log", LOG_CREAT, "id: %-12s ◇ %s 點給 %s : \"%s\", 轉寄至 %s\n", cuser.userid, sender, receiver, say, address); - - if (append_record(OSONGPATH "/" FN_DIR, &mail, sizeof(mail)) != -1) { - cuser.lastsong = now; - /* Jaky 超過 MAX_MOVIE 首歌就開始砍 */ - nsongs = get_num_records(OSONGPATH "/" FN_DIR, sizeof(mail)); - if (nsongs > MAX_MOVIE) { - // XXX race condition - delete_range(OSONGPATH "/" FN_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) -{ - char tmp_from[sizeof(currutmp->from)]; - if (getans("確定要改故鄉?[y/N]") != 'y') - return 0; - reload_money(); - if (cuser.money < 49) - return 0; - if (getdata_buf(b_lines - 1, 0, "請輸入新故鄉:", - tmp_from, sizeof(tmp_from), DOECHO)) { - vice(49, "更改故鄉"); - strlcpy(currutmp->from, tmp_from, sizeof(currutmp->from)); - 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); - - // no need to create default prompt. - // and people usually come this this by accident... - getdata(b_lines - 2, 0, buf, ans, sizeof(ans), LCECHO); - - 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) - { - vmsg("你的錢不夠。"); - return 0; - } - - if (vmsgf("你想購買 %d 封信箱 (要花 %d 元), 確定嗎?[y/N] ", - n, n*1000) != 'y') - return 0; - - vice(n * 1000, "購買信箱"); - inmailbox(n); - vmsgf("已購買信箱。新容量上限: %d", cuser.exmailbox); - 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 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_filef(FN_MONEY, LOG_CREAT, "%-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]")); - if (money < 50) { - usleep(2000000); - } else if (money < 200) { - usleep(500000); - } else { - usleep(100000); - } - return 0; - } - return -1; -} - -int -p_give(void) -{ - give_money_ui(NULL); - return -1; -} - -int -give_money_ui(const char *userid) -{ - int uid; - char id[IDLEN + 1], money_buf[20]; - char passbuf[PASSLEN]; - int m = 0, tries = 3, skipauth = 0; - static time4_t lastauth = 0; - - // TODO prevent macros, we should check something here, - // like user pw/id/... - clear(); - stand_title("給予金錢"); - if (!userid || !*userid) - usercomplete("這位幸運兒的id: ", id); - else { - strlcpy(id, userid, sizeof(id)); - prints("這位幸運兒的id: %s\n", id); - } - move(2, 0); clrtobot(); - - if (!id[0] || !strcasecmp(cuser.userid, id)) - { - vmsg("交易取消!"); - return -1; - } - if (!getdata(2, 0, "要給他多少錢呢: ", money_buf, 7, LCECHO) || - ((m = atoi(money_buf)) <= 0)) - { - vmsg("交易取消!"); - return -1; - } - if ((uid = searchuser(id, id)) == 0) { - vmsg("查無此人!"); - return -1; - } - move(4, 0); - prints("交易內容: %s 將給予 %s : %d 元 (要再扣稅金 %d 元)\n", - cuser.userid, id, m, give_tax(m)); - - if (now - lastauth >= 15*60) // valid through 15 minutes - { - outs(ANSI_COLOR(1;31) "為了避免誤按或是惡意詐騙," - "在完成交易前要重新確認您的身份。" ANSI_RESET); - } else { - outs("你的認證尚未過期,可暫時跳過密碼認證程序。\n"); - // auth is valid. - if (getans("確定進行交易嗎? (y/N): ") == 'y') - skipauth = 1; - else - tries = -1; - } - - while (!skipauth && tries-- > 0) - { - getdata(6, 0, MSG_PASSWD, - passbuf, sizeof(passbuf), NOECHO); - passbuf[8] = '\0'; - if (checkpasswd(cuser.passwd, passbuf)) - { - lastauth = now; - break; - } - if (tries > 0) - vmsgf("密碼錯誤,還有 %d 次機會。", tries); - } - if (tries < 0) - { - vmsg("交易取消!"); - return -1; - } - // vmsg("準備交易。"); - // return -1; - return do_give_money(id, uid, m); -} - -void -resolve_over18(void) -{ - /* get local time */ - struct tm ptime = *localtime4(&now); - - over18 = 0; - /* check if over18 */ - // 照實歲計算,沒生日的當作未滿 18 - if (cuser.year < 1 || cuser.month < 1) - over18 = 0; - else if( (ptime.tm_year - cuser.year) > 18) - over18 = 1; - else if (ptime.tm_year - cuser.year < 18) - over18 = 0; - else if ((ptime.tm_mon+1) > cuser.month) - over18 = 1; - else if ((ptime.tm_mon+1) < cuser.month) - over18 = 0; - else if (ptime.tm_mday >= cuser.day ) - over18 = 1; -} - -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)); - -#ifdef REPORT_PIAIP_MODULES - outs("\n" ANSI_COLOR(1;30) - "Modules powered by piaip:\n" - "\ttelnet protocol, ALOHA fixer, BRC v3\n" -#if defined(USE_PIAIP_MORE) || defined(USE_PMORE) - "\tpmore (piaip's more) 2007 w/Movie\n" -#endif -#ifdef HAVE_GRAYOUT - "\tGrayout Advanced Control 淡入淡出特效系統\n" -#endif -#ifdef EDITPOST_SMARTMERGE - "\tSmart Merge 修文自動合併\n" -#endif -#ifdef EXP_EDIT_UPLOAD - "\t(EXP) Editor Uploader 長文上傳\n" -#endif -#if defined(USE_PFTERM) - "\t(EXP) pfterm (piaip's flat terminal, Perfect Term)\n" -#endif -#if defined(USE_BBSLUA) - "\t(EXP) BBS-Lua\n" -#endif - ANSI_RESET - ); -#endif // REPORT_PIAIP_MODULES - - 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: %u KB, " -#endif -#ifdef __linux__ - "VmData: %d KB, VmStk: %d KB, " -#endif - "idrss: %d KB, isrss: %d KB\n", -#ifdef IA32 - ((unsigned 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", - (long int)ru.ru_utime.tv_sec, - (long int)ru.ru_utime.tv_usec, - (long int)ru.ru_stime.tv_sec, - (long int)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/mbbsd/calendar.c b/mbbsd/calendar.c deleted file mode 100644 index b87c774a..00000000 --- a/mbbsd/calendar.c +++ /dev/null @@ -1,362 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -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}; - - assert(1<=m && m<=12); - return leap && m == 2 ? 29 : day[m - 1]; -} - -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, is_leap_year(y)); - return w; -} - -/** - * return 1 if date is invalid - */ -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); if (!y) return 1; - m = strtok_r(NULL, "/", &strtok_pos);if (!m) return 1; - d = strtok_r(NULL, "", &strtok_pos); if (!d) return 1; - - *year = atoi(y); - *month = atoi(m); - *day = atoi(d); - if (*year < 1 || *month < 1 || *month > 12 || - *day < 1 || *day > MonthDay(*month, is_leap_year(*year))) - return 1; - return 0; -} - -/** - * return 1 if date is invalid - */ -static int -ParseEventDate(const char *date, event_t * t) -{ - int retval = ParseDate(date, &t->year, &t->month, &t->day); - if (retval) - return retval; - 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, is_leap_year(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 -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); - clrtoln(b_lines); - move(3, 0); - prints("行事曆格式說明:\n編輯時以一行為單位,如:\n\n# 井號開頭的是註解\n2006/05/04 red 上批踢踢!\n\n其中的 red 是指表示的顏色。"); - pressanykey(); - } - return 0; -} - -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); - i = vmsg("請按 e 編輯行事曆,或其它任意鍵離開。"); - i = tolower(((unsigned char)i) & 0xFF); - if (i == 'e') - { - u_editcalendar(); - } - return 0; -} - diff --git a/mbbsd/card.c b/mbbsd/card.c deleted file mode 100644 index 5f5a1530..00000000 --- a/mbbsd/card.c +++ /dev/null @@ -1,672 +0,0 @@ -/* $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 元, 現在要分組(加收 %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 元, 還要加牌嗎? [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("好厲害唷! 六張牌還沒爆! 加 %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("呵呵...電腦爆掉了! 你贏了! 可得 %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("平局,退回 %d 元!", JACK); - return 0; - } - if(card_alls_upper(cpu)=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("好厲害唷! 過五關嘍! 加 %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("呵呵...電腦爆掉了! 你贏了! 可得 %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]; - int times = 0; - - setutmpmode(TENHALF); - while (1) { - reload_money(); - if (cuser.money < TEN_HALF) { - outs("您的錢不夠唷!去多發表些有意義的文章再來~~~"); - return 0; - } - - if (times++ % 5 == 0) - { - move(b_lines-2, 0); clrtoeol(); - outs(ANSI_COLOR(1;31) - "警告: 本遊戲由 PttGames 看板評鑑為極度黑店,請小心!" ANSI_RESET); - } - - 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/mbbsd/chat.c b/mbbsd/chat.c deleted file mode 100644 index 6623fbc2..00000000 --- a/mbbsd/chat.c +++ /dev/null @@ -1,597 +0,0 @@ -/* $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 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 ", "邀請 加入談天室"); - chathelp("[/k]ick ", "將 踢出談天室"); - chathelp("[/o]p ", "將 Op 的權力轉移給 "); - chathelp("[/t]opic ", "換個話題"); - chathelp("[/w]all", "廣播 (站長專用)"); - } else { - // chathelp("[/.]help", "chicken 鬥雞用指令"); - chathelp(" /help op", "談天室管理員專用指令"); - chathelp("[//]help", "MUD-like 社交動詞"); - chathelp("[/a]ct ", "做一個動作"); - chathelp("[/b]ye [msg]", "道別"); - chathelp("[/c]lear", "清除螢幕"); - chathelp("[/j]oin ", "建立或加入談天室"); - chathelp("[/l]ist [room]", "列出談天室使用者"); - chathelp("[/m]sg ", "跟 說悄悄話"); - chathelp("[/n]ick ", "將談天代號換成 "); - chathelp("[/p]ager", "切換呼叫器"); - chathelp("[/q]uery ", "查詢網友"); - chathelp("[/r]oom ", "列出一般談天室"); - chathelp("[/w]ho", "列出本談天室使用者"); - chathelp(" /whoin ", "列出談天室 的使用者"); - chathelp(" /ignore ", "忽略指定使用者的訊息"); - chathelp(" /unignore ", "停止忽略指定使用者的訊息"); - } -} - -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); -} - -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; -static time4_t lastEnter = 0; - -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; - } - - syncnow(); - -#ifdef CHAT_GAPMINS - if ((now - lastEnter)/60 < CHAT_GAPMINS) - { - vmsg("您才剛離開聊天室,裡面正在整理中。請稍後再試。"); - return 0; - } -#endif - -#ifdef CHAT_REGDAYS - if ((now - cuser.firstlogin)/86400 < CHAT_REGDAYS) - { - int i = CHAT_REGDAYS - (now-cuser.firstlogin)/86400 +1; - vmsgf("您還不夠資深喔 (再等 %d 天吧)", i); - return 0; - } -#endif - - 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); - vmsg("系統錯誤。"); - 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(); - } - syncnow(); - lastEnter = now; - - 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, 56); - outs(" /h 查詢指令 /b 離開 "); - 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) { - -#ifdef EXP_ANTIFLOOD - // prevent flooding */ - static time4_t lasttime = 0; - static int flood = 0; - - /* // debug anti flodding - move(b_lines-3, 0); clrtoeol(); - prints("lasttime=%d, now=%d, flood=%d\n", - lasttime, now, flood); - refresh(); - */ - syncnow(); - if (now - lasttime < 3 ) - { - // 3 秒內洗半面是不行的 ((25-5)/2) - if( ++flood > 10 ){ - // flush all input! - drop_input(); - while (wait_input(1, 0)) - { - if (num_in_buf()) - drop_input(); - else - tty_read((unsigned char*)inbuf, sizeof(inbuf)); - } - drop_input(); - vmsg("請勿大量剪貼或造成洗板面的效果。"); - // log? - sleep(2); - continue; - } - } else { - lasttime = now; - flood = 0; - } -#endif // anti-flood - - 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; - - scr_dump(&old_screen); - add_io(0, 0); - t_idle(); - scr_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/mbbsd/chc.c b/mbbsd/chc.c deleted file mode 100644 index 602fc37e..00000000 --- a/mbbsd/chc.c +++ /dev/null @@ -1,1012 +0,0 @@ -/* $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\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("\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 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("\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 = NULL; - 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/mbbsd/chc_tab.c b/mbbsd/chc_tab.c deleted file mode 100644 index 4bed88ab..00000000 --- a/mbbsd/chc_tab.c +++ /dev/null @@ -1,106 +0,0 @@ -/* $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/mbbsd/chess.c b/mbbsd/chess.c deleted file mode 100644 index a86ca766..00000000 --- a/mbbsd/chess.c +++ /dev/null @@ -1,1762 +0,0 @@ -/* $Id$ */ -#include "bbs.h" -#include "chess.h" -#include - -#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 || !CurrentPlayingGameInfo) - return; - - 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) -{ - char a = 0; - if (info->mode == CHESS_MODE_VERSUS && info->myturn == 0 && - info->constants->log_board) { - ChessGenLogGlobal(info, result); - } - - a = getans((cuser.uflag & DEFBACKUP_FLAG) ? - "是否將棋譜寄回信箱? [Y/n]" : - "是否將棋譜寄回信箱? [y/N]"); - - if (TOBACKUP(a)) - 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) { - scr_dump(&oldscreen); - ChessPlay(info); - scr_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/mbbsd/chicken.c b/mbbsd/chicken.c deleted file mode 100644 index 4de2bb85..00000000 --- a/mbbsd/chicken.c +++ /dev/null @@ -1,1138 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -// TODO pull chicken out of userec. -// remove chickenpk. - -#define NUM_KINDS 15 /* 有多少種動物 */ -#define CHICKENLOG "etc/chicken" - -// enable if you want to run live upgrade -// #define CHICKEN_LIVE_UPGRADE - -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}, -}; - -static void time_diff(chicken_t * thechicken); -static int isdeadth(const chicken_t * thechicken, chicken_t *mychicken); - -chicken_t * load_live_chicken(const char *uid) -{ - char fn[PATHLEN]; - int fd = 0; - chicken_t *p = NULL; - - if (!uid || !uid[0]) return NULL; - sethomefile(fn, uid, FN_CHICKEN); - if (!dashf(fn)) return NULL; - fd = open(fn, O_RDWR); - if (fd < 0) return NULL; - - // now fd is valie. open and mmap. - p = mmap(NULL, sizeof(chicken_t), PROT_READ|PROT_WRITE, MAP_SHARED, - fd, 0); - close(fd); - return p; -} - -int load_chicken(const char *uid, chicken_t *mychicken) -{ - char fn[PATHLEN]; - int fd = 0; - - memset(mychicken, 0, sizeof(chicken_t)); - if (!uid || !uid[0]) return 0; - sethomefile(fn, uid, FN_CHICKEN); - if (!dashf(fn)) return 0; - fd = open(fn, O_RDONLY); - if (fd < 0) return 0; - if (read(fd, mychicken, sizeof(chicken_t)) > 0 && mychicken->name[0]) - return 1; - return 0; -} - -void free_live_chicken(chicken_t *p) -{ - if (!p) return; - munmap(p, sizeof(chicken_t)); -} - -void -chicken_query(const char *userid) -{ - chicken_t xchicken; - -#ifdef CHICKEN_LIVE_UPGRADE - // live update - vmsg("PTT 系統進行更新,本週暫停開放寵物查詢。"); - return; -#endif - - if (!load_chicken(userid, &xchicken)) - { - move(1, 0); - clrtobot(); - prints("\n\n%s 並沒有養寵物..", userid); - } else { - time_diff(&xchicken); - if (!isdeadth(&xchicken, NULL)) - { - show_chicken_data(&xchicken, NULL); - prints("\n\n以上是 %s 的寵物資料..", userid); - } else { - move(1, 0); - clrtobot(); - prints("\n\n%s 的寵物死掉了...", userid); - } - } - - pressanykey(); -} - -static int -new_chicken(void) -{ - chicken_t mychicken; - int price, i; - int fd; - char fn[PATHLEN]; - - memset(&mychicken, 0, sizeof(chicken_t)); - - clear(); - move(2, 0); - outs("歡迎光臨 " ANSI_COLOR(33) "◎" ANSI_COLOR(37;44) " " - BBSMNAME "寵物市場 " 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 (n)就可人$100 (m)蘿莉 $77\n" - "[0]不想買了 $0\n"); - i = getans("請選擇你要養的動物:"); - - // since (o) is confusing to some people, we alias 'm' to 'o'. - if (i == 'm') i = 'o'; - - // (m, l) were political person. - // do not make them in a BBS system... - if (i == 'm' || i == 'l') - return 0; - - i -= 'a'; - if (i < 0 || i > NUM_KINDS - 1) - return 0; - - mychicken.type = i; - - price = egg_price[(int)mychicken.type]; - reload_money(); - if (cuser.money < price) { - vmsgf("錢不夠買蛋蛋,蛋蛋要 %d 元", price); - return 0; - } - - while (strlen(mychicken.name) < 3) - { - getdata(8, 0, "幫牠取個好名字:", mychicken.name, - sizeof(mychicken.name), DOECHO); - } - - 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; - - reload_money(); - if (cuser.money < price) - { - vmsg("錢不夠了。"); - return 0; - } - vice(price, "寵物蛋"); - - // flush it - setuserfile(fn, FN_CHICKEN); - fd = open(fn, O_WRONLY|O_CREAT, 0666); - if (fd < 0) - { - vmsg("系統錯誤: 無法建立資料,請至 " GLOBAL_BUGREPORT " 報告。"); - return 0; - } - - write(fd, &mychicken, sizeof(chicken_t)); - close(fd); - - // log data - log_filef(CHICKENLOG, LOG_CREAT, - 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)); - 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" - -static void -show_chicken_picture(const char *fpath) -{ - show_file(fpath, 5, 14, SHOWFILE_ALLOW_ALL); -} - -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 ? BBSMNAME2 "鬥雞場" : BBSMNAME2 "養雞場", 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_chicken_picture(buf); - - 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(chicken_t *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_chicken_picture(CHICKEN_PIC "/eat"); - pressanykey(); - } -} - -static void -ch_clean(chicken_t *mychicken) -{ - mychicken->clean = 0; - mychicken->tiredstrong += - time_change[(int)mychicken->type][TIREDSTRONG] / 3; - show_chicken_picture(CHICKEN_PIC "/clean"); - pressanykey(); -} - -static void -ch_guess(chicken_t *mychicken) -{ - char *guess[3] = {"剪刀", "石頭", "布"}, me, ch, win; - - 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(chicken_t *mychicken) -{ - mychicken->book += time_change[(int)mychicken->type][BOOK]; - mychicken->tiredstrong += time_change[(int)mychicken->type][TIREDSTRONG]; - show_chicken_picture(CHICKEN_PIC "/read"); - pressanykey(); -} - -static void -ch_kiss(chicken_t *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_chicken_picture(CHICKEN_PIC "/kiss"); - pressanykey(); -} - -static void -ch_hit(chicken_t *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_chicken_picture(CHICKEN_PIC "/hit"); - 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_chicken_picture(picture); - pressanykey(); - } else { - vmsg("現金不夠 !!!"); - } - usleep(100000); // sleep 0.1s -} - -static void -ch_eatoo(chicken_t *mychicken) -{ - if (mychicken->oo > 0) { - mychicken->oo--; - mychicken->tiredstrong = 0; - if (mychicken->happy > 5) - mychicken->happy -= 5; - show_chicken_picture(CHICKEN_PIC "/oo"); - pressanykey(); - } -} - -static void -ch_eatmedicine(chicken_t *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_chicken_picture(CHICKEN_PIC "/medicine"); - pressanykey(); - } -} - -static void -ch_kill(chicken_t *mychicken) -{ - int ans; - - ans = getans("棄養要被罰 100 元, 是否要棄養?(y/N)"); - if (ans == 'y') { - - vice(100, "棄養寵物費"); - more(CHICKEN_PIC "/deadth", YEA); - log_filef(CHICKENLOG, LOG_CREAT, - 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 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; - } -} - -/* 依時間變動的資料 */ -static 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(chicken_t *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) -{ - 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_filef(CHICKENLOG, LOG_CREAT, - 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; - } - 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; -} - -static int -isdeadth(const chicken_t * thechicken, chicken_t *mychicken) -{ - int i; - - if (!(i = deadtype(thechicken, mychicken))) - return 0; - return showdeadth(i); -} - -static void -ch_changename(chicken_t *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_filef(CHICKENLOG, LOG_CREAT, - 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) -{ - 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) "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(mychicken); - check_sick(mychicken); - break; - case '2': - ch_eat(mychicken); - check_sick(mychicken); - break; - case '3': - ch_guess(mychicken); - check_sick(mychicken); - break; - case '4': - ch_book(mychicken); - check_sick(mychicken); - break; - case '5': - ch_kiss(mychicken); - break; - case '6': - ch_hit(mychicken); - check_sick(mychicken); - break; - case '7': - ch_buyitem(food_price[(int)mychicken->type], CHICKEN_PIC "/food", - &mychicken->food, 1); - break; - case '8': - ch_eatoo(mychicken); - break; - case '9': - ch_eatmedicine(mychicken); - 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(mychicken); - break; - case 'K': - case 'k': - ch_kill(mychicken); - return 0; - 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]; - int money = price + (random() % price); - price *= 2; - // money is a little less than 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); - 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) { - 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; - thechicken->tiredstrong = 0; - thechicken->weight = thechicken->hp; - // thechicken->lastvisit = now; // really need so? - vice(money, "靈界守衛"); - snprintf(buf, sizeof(buf), - ANSI_COLOR(33;44) "★靈界守衛" ANSI_COLOR(37;45) - " OK了 記得餵他點東西 不然可能失效。" - "今天心情好,拿你$%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; - return 0; -} - -void -chicken_toggle_death(const char *uid) -{ - chicken_t *mychicken = load_live_chicken(uid); - -#ifdef CHICKEN_LIVE_UPGRADE - // live update - vmsg("PTT 系統進行更新,本週暫停開放寵物設定。"); - return; -#endif - - if (!uid) - return; - if (!mychicken) - { - vmsgf("%s 沒養寵物。", uid); - } - else if (mychicken->name[0]) - { - mychicken->name[0] = 0; - vmsgf("%s 的寵物被殺死了", uid); - } - else - { - strlcpy(mychicken->name, "[死]", sizeof(mychicken->name)); - vmsgf("%s 的寵物復活了", uid); - } - free_live_chicken(mychicken); -} - -#define lockreturn0(unmode, state) if(lockutmpmode(unmode, state)) return 0 - -#ifdef CHICKEN_LIVE_UPGRADE -static void -chicken_live_upgrade() -{ - char fn[PATHLEN]; - FILE *fp = NULL; - setuserfile(fn, FN_CHICKEN); - - if (dashf(fn)) - return; - - if (!cuser.old_chicken.name[0] && - !cuser.old_chicken.cbirth && - !cuser.old_chicken.hp_max) - return; - - // write to data. - fp = fopen(fn, "wb"); - fwrite(&cuser.old_chicken, sizeof(chicken_t), 1, fp); - fclose(fp); -#if 0 // enable if you want logs - log_filef("log/chicken_live_upgrade", LOG_CREAT, - "%s upgrade chicken at %s", - cuser.userid, ctime4(&now)); -#endif -} -#endif // CHICKEN_LIVE_UPGRADE - -int -chicken_main(void) -{ - int age; - chicken_t *mychicken = load_live_chicken(cuser.userid); - -#ifdef CHICKEN_LIVE_UPGRADE - if (mychicken == NULL) - { - chicken_live_upgrade(); - mychicken = load_live_chicken(cuser.userid); - } -#endif - - lockreturn0(CHICKEN, LOCK_MULTI); - if (mychicken && !mychicken->name[0]) - { - // possible for recovery - recover_chicken(mychicken); - } - if (!mychicken || !mychicken->name[0]) - { - free_live_chicken(mychicken); - mychicken = NULL; - - // create new? - if (new_chicken()) - mychicken = load_live_chicken(cuser.userid); - - // exit if still no valid data. - if (!mychicken || !mychicken->name[0]) - { - unlockutmpmode(); - free_live_chicken(mychicken); - return 0; - } - } - assert(mychicken); - age = ((now - mychicken->cbirth) / (60 * 60 * 24)); - do { - time_diff(mychicken); - if (isdeadth(mychicken, mychicken)) - break; - show_chicken_data(mychicken, NULL); - } while (select_menu(age, mychicken)); - unlockutmpmode(); - free_live_chicken(mychicken); - return 0; -} - -#ifdef USE_CHICKEN_PK -int -chickenpk(int fd) -{ - chicken_t *mychicken = load_live_chicken(cuser.userid); - chicken_t *ochicken = load_live_chicken(currutmp->mateid); - - char mateid[IDLEN + 1], data[200], buf[200]; - int ch = 0; - - userinfo_t *uin = &SHM->uinfo[currutmp->destuip]; - int r, attmax, i, datac, catched = 0, - count = 0; - - lockreturn0(CHICKEN, LOCK_MULTI); - /* 把對手的id用local buffer記住 */ - strlcpy(mateid, currutmp->mateid, sizeof(mateid)); - - if (!mychicken || !ochicken || - !ochicken->name[0] || !mychicken->name[0]) { - free_live_chicken(mychicken); - free_live_chicken(ochicken); - 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(); - 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, mychicken)) { - 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; - 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, mychicken)); - unlockutmpmode(); - free_live_chicken(mychicken); - free_live_chicken(ochicken); - return 0; -} -#endif // USE_CHICKEN_PK diff --git a/mbbsd/convert.c b/mbbsd/convert.c deleted file mode 100644 index e5d0f07b..00000000 --- a/mbbsd/convert.c +++ /dev/null @@ -1,123 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#ifdef CONVERT - -extern unsigned char *gb2big(unsigned char *, int *, int); -extern unsigned char *big2gb(unsigned char *, int *, int); -extern unsigned char *utf8_uni(unsigned char *, int *, int); -extern unsigned char *uni_utf8(unsigned char *, int *, int); -extern unsigned char *uni2big(unsigned char *, int *, int); -extern 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; - -// enable this in case some day we want to detect -// current type. but right now disable for less memory cost -// int bbs_convert_type = CONV_NORMAL; - -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; - } - // bbs_convert_type = which; -} - -#endif diff --git a/mbbsd/crypt.c b/mbbsd/crypt.c deleted file mode 100644 index 7283c8a6..00000000 --- a/mbbsd/crypt.c +++ /dev/null @@ -1,647 +0,0 @@ -/* $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. (non-const 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 -#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>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 - * 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/mbbsd/dark.c b/mbbsd/dark.c deleted file mode 100644 index f19184dc..00000000 --- a/mbbsd/dark.c +++ /dev/null @@ -1,565 +0,0 @@ -/* $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/mbbsd/edit.c b/mbbsd/edit.c deleted file mode 100644 index c6b3a544..00000000 --- a/mbbsd/edit.c +++ /dev/null @@ -1,3886 +0,0 @@ -/* $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) - * - * FIXME 20071201 piaip - * block selection 不知何時已變為 line level 而非 character level 了, - * 這樣也比較好寫,所以把 blockpnt 拿掉吧! - * - * 20071230 piaip - * BBSmovie 有人作出了 1.9G 的檔案, 看來要分 hard limit 跟 soft limit - * [第 7426572/7426572 頁 (100%) 目前顯示: 第 163384551~163384573 行] - * 當日調查 BBSmovie 看板與精華區,平均檔案皆在 5M 以下 - * 最大的為 16M 的 Haruhi OP (avi 轉檔 with massive ANSI) - * [第 2953/2953 頁 (100%) 目前顯示: 第 64942~64964 行] - * 另外互動迷宮的大小為 - * [第 1408/1408 頁 (100%) 目前顯示: 第 30940~30962 行] - * 是以定義: - * 32M 為 size limit - * 1M 為 line limit - * 又,忽然發現之前 totaln 之類都是 short... 所以 65536 就夠了? - * 後註: 似乎是用 announce 的 append 作出來的,有看到 > --- <- mark。 - */ -#include "bbs.h" - -#define EDIT_SIZE_LIMIT (32768*1024) -#define EDIT_LINE_LIMIT (65530) // (1048576) - -#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 - * 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; - - int currln; /* current line of the article. */ - short currpnt; /* current column of the article. */ - int totaln; /* total lines of the article. */ - int 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; - int blockln; /* the row 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 *sitesig_string; - char *(*substr_fp) (); - - char synparser; // syntax parser - - 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); - if (curr_buf->sitesig_string != NULL) - free(curr_buf->sitesig_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); - } -} - -/** - * 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+B1next)) - 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, off_t offSig) -{ - char buf[WRAPMARGIN + 2]; - int indent_mode0 = curr_buf->indent_mode; - size_t szread = 0; - - assert(fp); - curr_buf->indent_mode = 0; - while (fgets(buf, sizeof(buf), fp)) - { - szread += strlen(buf); - if (offSig < 0 || szread <= offSig) - { - insert_string(buf); - } - else - { - // this is the site sig - break; - } - } - 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 (curr_buf->totaln >= EDIT_LINE_LIMIT) - { - vmsg("檔案已超過最大限制,無法再讀入暫存檔。"); - return; - } - - 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, -1); - 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; - off_t sz = 0; - - 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 (ans[0] != 'w') // 'a' - { - sz = dashs(fp_tmpbuf); - if (sz > EDIT_SIZE_LIMIT) - { - vmsg("暫存檔已超過大小限制,無法再附加。"); - 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[512]; - - 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, sizeof(buf), 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, sizeof(buf), inf) && buf[0] != '\n'); - /* FIXME by MH: - 如果 header 到內文中間沒有空行分隔,會造成 All 以外的模式 - 都引不到內文。 - */ - - if (op == 'a') - while (fgets(buf, sizeof(buf), inf)) { - insert_char(':'); - insert_char(' '); - quote_strip_ansi_inline((unsigned char *)buf); - insert_string(buf); - } - else if (op == 'r') - while (fgets(buf, sizeof(buf), 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, sizeof(buf), inf) && (!strncmp(buf, "※ ", 3))); - while (fgets(buf, sizeof(buf), 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; -} - -/* 檔案處理:讀檔、存檔、標題、簽名檔 */ -off_t loadsitesig(const char *fname); - -static void -read_file(const char *fpath, int splitSig) -{ - FILE *fp; - off_t offSig = -1; - - if (splitSig) - offSig = loadsitesig(fpath); - - 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, offSig); - 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)); -} - -off_t -loadsitesig(const char *fname) -{ - int fd = 0; - off_t sz = 0, ret = -1; - char *start, *sp; - - sz = dashs(fname); - if (sz < 1) - return -1; - fd = open(fname, O_RDONLY); - if (fd < 0) - return -1; - start = (char*)mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); - if (start) - { - sp = start + sz - 4 - 1; // 4 = \n--\n - while (sp > start) - { - if ((*sp == '\n' && strncmp(sp, "\n--\n", 4) == 0) || - (*sp == '\r' && strncmp(sp, "\r--\r", 4) == 0) ) - { - size_t szSig = sz - (sp-start+1); - ret = sp - start + 1; - // allocate string - curr_buf->sitesig_string = (char*) malloc (szSig + 1); - if (curr_buf->sitesig_string) - { - memcpy(curr_buf->sitesig_string, sp+1, szSig); - curr_buf->sitesig_string[szSig] = 0; - } - break; - } - sp --; - } - munmap(start, sz); - } - - close(fd); - return ret; -} - -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 -} - -#ifdef EXP_EDIT_UPLOAD -static void upload_file(void); -#endif // EXP_EDIT_UPLOAD - -static int -write_file(char *fpath, int saveheader, int *islocal, char *mytitle, int upload, int chtitle) -{ - 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("檔案處理"); - move(1,0); - -#ifdef EDIT_UPLOAD_ALLOWALL - upload = 1; -#endif // EDIT_UPLOAD_ALLOWALL - - // common trail - - if (currstat == SMAIL) - outs("[S]儲存"); - else if (local_article) - outs("[L]站內信件 (S)儲存"); - else - outs("[S]儲存 (L)站內信件"); - -#ifdef EXP_EDIT_UPLOAD - if (upload) - outs(" (U)上傳資料"); -#endif // EXP_EDIT_UPLOAD - - if (chtitle) - outs(" (T)改標題"); - - outs(" (A)放棄 (E)繼續 (R/W/D)讀寫刪暫存檔"); - - getdata(2, 0, "確定要儲存檔案嗎? ", ans, 2, LCECHO); - - // avoid lots pots - sleep(1); - - switch (ans[0]) { - case 'a': - outs("文章" ANSI_COLOR(1) " 沒有 " ANSI_RESET "存入"); - aborted = -1; - break; - case 'e': - return KEEP_EDITING; -#ifdef EXP_EDIT_UPLOAD - case 'u': - if (upload) - upload_file(); - return KEEP_EDITING; -#endif // EXP_EDIT_UPLOAD - case 'r': - read_tmpbuf(-1); - return KEEP_EDITING; - case 'w': - write_tmpbuf(); - return KEEP_EDITING; - case 'd': - erase_tmpbuf(); - return KEEP_EDITING; - case 't': - if (!chtitle) - return KEEP_EDITING; - 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; - - // what if currbid == 0? add currstat checking. - if (currstat == POSTING && - postrecord.times > MAX_CROSSNUM-1 && - !is_hidden_board_friend(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) - return aborted; - - if (islocal) - *islocal = local_article; - - if (curr_buf->sitesig_string) - fprintf(fp, curr_buf->sitesig_string); - - if (currstat == POSTING || currstat == SMAIL) - { - addsignature(fp, curr_buf->ifuseanony); - } - else if (currstat == REEDIT) - { -#ifndef ALL_REEDIT_LOG - // why force signature in SYSOP board? - if(strcmp(currboard, GLOBAL_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 0; -} - -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; - } -} - -#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+"))) { - - 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; - textline_t *p; - - if (!has_block_selection()) - return; - - setup_block_begin_end(&begin, &end); - - // the block region is (currln, block) or (blockln, currln). - - if (curr_buf->currln > curr_buf->blockln) { - // case (blockln, currln) - // piaip 2007/1201 在這裡原有 offset-by-one issue - // 如果又遇到,請檢查這附近。 - curr_buf->curr_window_line -= (curr_buf->currln - curr_buf->blockln); - - 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); - } else { - // case (currln, blockln) - } - - // adjust buffer after delete - 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; - } - - // adjust current line - 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--; - } - - // remove buffer - 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->blockline = curr_buf->currline; -} - -enum { - EOATTR_NORMAL = 0x00, - EOATTR_SELECTED = 0x01, // selected (reverse) - EOATTR_MOVIECODE= 0x02, // pmore movie - EOATTR_BBSLUA = 0x04, // BBS Lua (header) - EOATTR_COMMENT = 0x08, // comment syntax - -}; - -static const char *luaKeywords[] = { - "and", "break", "do", "else", "elseif", - "end", "for", "if", "in", "not", "or", - "repeat","return","then","until","while", - NULL -}; - -static const char *luaDataKeywords[] = { - "false", "function", "local", "nil", "true", - NULL -}; - -static const char *luaFunctions[] = { - "assert", "print", "tonumber", "tostring", "type", - NULL -}; - -static const char *luaMath[] = { - "abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "deg", - "exp", "floor", "fmod", "frexp", "ldexp", "log", "log10", "max", "min", - "modf", "pi", "pow", "rad", "random", "randomseed", "sin", "sinh", - "sqrt", "tan", "tanh", - NULL -}; - -static const char *luaTable[] = { - "concat", "insert", "maxn", "remove", "sort", - NULL -}; - -static const char *luaString[] = { - "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", - "lower", "match", "rep", "reverse", "sub", "upper", NULL -}; - -static const char *luaBbs[] = { - "ANSI_COLOR", "ANSI_RESET", "ESC", "addstr", "clear", "clock", - "clrtobot", "clrtoeol", "color", "ctime", "getch","getdata", - "getmaxyx", "getstr", "getyx", "interface", "kball", "kbhit", "kbreset", - "move", "moverel", "now", "outs", "pause", "print", "rect", "refresh", - "setattr", "sitename", "sleep", "strip_ansi", "time", "title", - "userid", "usernick", - NULL -}; - -static const char *luaToc[] = { - "author", "date", "interface", "latestref", - "notes", "title", "version", - NULL -}; - -static const char *luaBit[] = { - "arshift", "band", "bnot", "bor", "bxor", "cast", "lshift", "rshift", - NULL -}; - -static const char *luaStore[] = { - "USER", "GLOBAL", "iolimit", "limit", "load", "save", - NULL -}; - -static const char *luaLibs[] = { - "bbs", "bit", "math", "store", "string", "table", "toc", - NULL -}; -static const char**luaLibAPI[] = { - luaBbs, luaBit, luaMath, luaStore, luaString, luaTable, luaToc, - NULL -}; - -int synLuaKeyword(const char *text, int n, char *wlen) -{ - int i = 0; - const char **tbl = NULL; - if (*text >= 'A' && *text <= 'Z') - { - // normal identifier - while (n-- > 0 && (isalnum(*text) || *text == '_')) - { - text++; - (*wlen) ++; - } - return 0; - } - if (*text >= '0' && *text <= '9') - { - // digits - while (n-- > 0 && (isdigit(*text) || *text == '.' || *text == 'x')) - { - text++; - (*wlen) ++; - } - return 5; - } - if (*text == '#') - { - text++; - (*wlen) ++; - // length of identifier - while (n-- > 0 && (isalnum(*text) || *text == '_')) - { - text++; - (*wlen) ++; - } - return -2; - } - - // ignore non-identifiers - if (!(*text >= 'a' && *text <= 'z')) - return 0; - - // 1st, try keywords - for (i = 0; luaKeywords[i] && *text >= *luaKeywords[i]; i++) - { - int l = strlen(luaKeywords[i]); - if (n < l) - continue; - if (isalnum(text[l])) - continue; - if (strncmp(text, luaKeywords[i], l) == 0) - { - *wlen = l; - return 3; - } - } - for (i = 0; luaDataKeywords[i] && *text >= *luaDataKeywords[i]; i++) - { - int l = strlen(luaDataKeywords[i]); - if (n < l) - continue; - if (isalnum(text[l])) - continue; - if (strncmp(text, luaDataKeywords[i], l) == 0) - { - *wlen = l; - return 2; - } - } - for (i = 0; luaFunctions[i] && *text >= *luaFunctions[i]; i++) - { - int l = strlen(luaFunctions[i]); - if (n < l) - continue; - if (isalnum(text[l])) - continue; - if (strncmp(text, luaFunctions[i], l) == 0) - { - *wlen = l; - return 6; - } - } - for (i = 0; luaLibs[i]; i++) - { - int l = strlen(luaLibs[i]); - if (n < l) - continue; - if (text[l] != '.' && text[l] != ':') - continue; - if (strncmp(text, luaLibs[i], l) == 0) - { - *wlen = l+1; - text += l; text ++; - n -= l; n--; - break; - } - } - - tbl = luaLibAPI[i]; - if (!tbl) - { - // calcualte wlen - while (n-- > 0 && (isalnum(*text) || *text == '_')) - { - text++; - (*wlen) ++; - } - return 0; - } - - for (i = 0; tbl[i]; i++) - { - int l = strlen(tbl[i]); - if (n < l) - continue; - if (isalnum(text[l])) - continue; - if (strncmp(text, tbl[i], l) == 0) - { - *wlen += l; - return 6; - } - } - // luaLib. only - return -6; -} - -/** - * Just like outs, but print out '*' instead of 27(decimal) in the given string. - * - * FIXME column could not start from 0 - */ - -static void -edit_outs_attr_n(const char *text, int n, int attr) -{ - int column = 0; - register unsigned char inAnsi = 0; - register unsigned char ch; - int doReset = 0; - const char *reset = ANSI_RESET; - - // syntax attributes - char fComment = 0, - fSingleQuote = 0, - fDoubleQuote = 0, - fSquareQuote = 0, - fWord = 0; - -#ifdef COLORED_SELECTION - if ((attr & EOATTR_SELECTED) && - (attr & ~EOATTR_SELECTED)) - { - reset = ANSI_COLOR(0;7;36); - doReset = 1; - outs(reset); - } - else -#endif // if not defined, color by priority - selection first - if (attr & EOATTR_SELECTED) - { - reset = ANSI_COLOR(0;7); - doReset = 1; - outs(reset); - } - else if (attr & EOATTR_MOVIECODE) - { - reset = ANSI_COLOR(0;36); - doReset = 1; - outs(reset); - } - else if (attr & EOATTR_BBSLUA) - { - reset = ANSI_COLOR(0;1;31); - doReset = 1; - outs(reset); - } - else if (attr & EOATTR_COMMENT) - { - reset = ANSI_COLOR(0;1;34); - doReset = 1; - outs(reset); - } - -#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 == 1) - { - if(ch == ESC_CHR) - outc('*'); - else - { - outc(ch); - - if(!ANSI_IN_ESCAPE(ch)) - { - inAnsi = 0; - outs(reset); - } - } - - } - else if(ch == ESC_CHR) - { - inAnsi = 1; -#ifdef DBCSAWARE - if(isDBCS == 1) - { - isDBCS = 2; - outs(ANSI_COLOR(1;33) "?"); - outs(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) "?"); - outs(reset); - isDBCS = 0; - continue; - } - else - if(IS_BIG5_HI(ch)) - { - isDBCS = 1; - // peak next char - if(n > 0 && *text == ESC_CHR) - continue; - } -#endif - // Lua Parser! - if (!attr && curr_buf->synparser && !fComment) - { - // syntax highlight! - if (fSquareQuote) { - if (ch == ']' && n > 0 && *(text) == ']') - { - fSquareQuote = 0; - doReset = 0; - // directly print quotes - outc(ch); outc(ch); - text++, n--; - outs(ANSI_RESET); - continue; - } - } else if (fSingleQuote) { - if (ch == '\'') - { - fSingleQuote = 0; - doReset = 0; - // directly print quotes - outc(ch); - outs(ANSI_RESET); - continue; - } - } else if (fDoubleQuote) { - if (ch == '"') - { - fDoubleQuote = 0; - doReset = 0; - // directly print quotes - outc(ch); - outs(ANSI_RESET); - continue; - } - } else if (ch == '-' && n > 0 && *(text) == '-') { - fComment = 1; - doReset = 1; - outs(ANSI_COLOR(0;1;34)); - } else if (ch == '[' && n > 0 && *(text) == '[') { - fSquareQuote = 1; - doReset = 1; - fWord = 0; - outs(ANSI_COLOR(1;35)); - } else if (ch == '\'' || ch == '"') { - if (ch == '"') - fDoubleQuote = 1; - else - fSingleQuote = 1; - doReset = 1; - fWord = 0; - outs(ANSI_COLOR(1;35)); - } else { - // normal words - if (fWord) - { - // inside a word. - if (--fWord <= 0){ - fWord = 0; - doReset = 0; - outc(ch); - outs(ANSI_RESET); - continue; - } - } else if (isalnum(tolower(ch)) || ch == '#') { - char attr[] = ANSI_COLOR(0;1;37); - int x = synLuaKeyword(text-1, n+1, &fWord); - if (fWord > 0) - fWord --; - if (x != 0) - { - // sorry, fixed string here. - // 7 = *[0;1;3? - if (x<0) { attr[4] = '0'; x= -x; } - attr[7] = '0' + x; - prints(attr); - doReset = 1; - } - if (!fWord) - { - outc(ch); - outs(ANSI_RESET); - doReset = 0; - continue; - } - } - } - } - outc(ch); - } - } - - // this must be ANSI_RESET, not "reset". - if(inAnsi || doReset) - outs(ANSI_RESET); -} - -static void -edit_outs_attr(const char *text, int attr) -{ - edit_outs_attr_n(text, scr_cols, attr); -} - -static void -edit_ansi_outs_n(const char *str, int n, int attr) -{ - 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('*'); - } else { - outc(c); - } - } -} - -static void -edit_ansi_outs(const char *str, int attr) -{ - return edit_ansi_outs_n(str, strlen(str), attr); -} - -// old compatible API -void -edit_outs(const char *text) -{ - edit_outs_attr(text, 0); -} - -void -edit_outs_n(const char *text, int n) -{ - edit_outs_attr_n(text, n, 0); -} - - -#define PMORE_USE_ASCII_MOVIE // disable this if you don't enable ascii movie - -#ifdef PMORE_USE_ASCII_MOVIE -// pmore movie header support -unsigned char * - mf_movieFrameHeader(unsigned char *p, unsigned char *end); - -#endif // PMORE_USE_ASCII_MOVIE - -static int -detect_attr(const char *ps, size_t len) -{ - int attr = 0; - -#ifdef PMORE_USE_ASCII_MOVIE - if (mf_movieFrameHeader((unsigned char*)ps, (unsigned char*)ps+len)) - attr |= EOATTR_MOVIECODE; -#endif -#ifdef USE_BBSLUA - if (bbslua_isHeader(ps, ps + len)) - { - attr |= EOATTR_BBSLUA; - if (!curr_buf->synparser) - { - curr_buf->synparser = 1; - // if you need indent, toggle by hotkey. - // enabling indent by default may cause trouble to copy pasters - // curr_buf->indent_mode = 1; - } - } -#endif - return attr; -} - -static inline void -display_textline_internal(textline_t *p, int i) -{ - short tmp; - void (*output)(const char *, int) = edit_outs_attr; - void (*output_n)(const char *, int, int)= edit_outs_attr_n; - - int attr = EOATTR_NORMAL; - - move(i, 0); - clrtoeol(); - - if (!p) { - outc('~'); - outs(ANSI_CLRTOEND); - return; - } - - if (curr_buf->ansimode) { - output = edit_ansi_outs; - output_n = edit_ansi_outs_n; - } - - tmp = curr_buf->currln - curr_buf->curr_window_line + i; - - // parse attribute of line - - // selected attribute? - 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)); // remove me when EOATTR is ready... - attr |= EOATTR_SELECTED; - } - - attr |= detect_attr(p->data, p->len); - -#ifdef DBCSAWARE - if(mbcs_mode && curr_buf->edit_margin > 0) - { - if(curr_buf->edit_margin >= p->len) - { - (*output)("", attr); - } 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, attr); - } - - } else -#endif - (*output)((curr_buf->edit_margin < p->len) ? - &p->data[curr_buf->edit_margin] : "", attr); - - if (attr) - outs(ANSI_RESET); - - // workaround poor terminal - outs(ANSI_CLRTOEND); -} - -static void -refresh_window(void) -{ - register textline_t *p; - register int i; - - for (p = curr_buf->top_of_win, i = 0; i < b_lines; i++) { - display_textline_internal(p, i); - - 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; -} - -#ifdef EXP_EDIT_UPLOAD - -static void -upload_file(void) -{ - size_t szdata = 0; - int c = 1; - char promptmsg = 0; - - clear(); - block_cancel(); - stand_title("上傳文字檔案"); - move(3,0); - outs("利用本服務您可以上傳較大的文字檔 (但不計入稿費)。\n" - "\n" - "上傳期間您打的字暫時不會出現在螢幕上,除了 Ctrl-U 會被轉換為 ANSI \n" - "控制碼的 ESC 外,其它特殊鍵一律沒有作用。\n" - "\n" - "請在您的電腦本機端複製好內容後貼上即可開始傳送。\n"); - - do { - if (!num_in_buf()) - { - move(10, 0); clrtobot(); - prints("\n\n資料接收中... %u 位元組。\n", (unsigned int)szdata); - outs(ANSI_COLOR(1) - "◆全部完成後按下 End 或 ^X/^Q/^C 即可回到編輯畫面。" - ANSI_RESET "\n"); - promptmsg = 0; - } - - c = igetch(); - if (c < 0x100 && isprint2(c)) - { - insert_char(c); - szdata ++; - } - else if (c == Ctrl('U') || c == ESC_CHR) - { - insert_char(ESC_CHR); - szdata ++; - } - else if (c == Ctrl('I')) - { - insert_tab(); - szdata ++; - } - else if (c == '\r' || c == '\n') - { - split(curr_buf->currline, curr_buf->currpnt); - curr_buf->oldcurrline = curr_buf->currline; - szdata ++; - promptmsg = 1; - } - - if (!promptmsg) - promptmsg = (szdata && szdata % 1024 == 0); - - // all other keys are ignored. - } while (c != KEY_END && c != Ctrl('X') && - c != Ctrl('C') && c != Ctrl('Q') && - curr_buf->totaln <= EDIT_LINE_LIMIT && - szdata <= EDIT_SIZE_LIMIT); - - move(12, 0); - prints("傳送結束: 收到 %u 位元組。", (unsigned int)szdata); - vmsgf("回到編輯畫面"); -} - -#endif // EXP_EDIT_UPLOAD - - -/* 編輯處理:主程式、鍵盤處理 */ -int -vedit2(char *fpath, int saveheader, int *islocal, int flags) -{ - 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, (flags & EDITFLAG_TEXTONLY) ? 1 : 0); - } - - 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, BBSMNAME "系統警察", - "用機器人發表文章", "強制離站"); - 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, - (flags & EDITFLAG_UPLOAD) ? 1 : 0, - (flags & EDITFLAG_ALLOWTITLE) ? 1 : 0); - 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 */ - grayout(0, b_lines-1, GRAYOUT_DARK); - ch = vmsg("結束但不儲存 [y/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; - 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': - block_cancel(); - if (curr_buf->totaln >= EDIT_LINE_LIMIT) - { - vmsg("檔案已超過最大限制,無法再增加行數。"); - break; - } - -#ifdef MAX_EDIT_LINE - if(curr_buf->totaln == - ((flags & EDITFLAG_ALLOWLARGE) ? - MAX_EDIT_LINE_LARGE : MAX_EDIT_LINE)) - { - vmsg("已到達最大行數限制。"); - 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), - 0, - 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 Ctrl('O'): // better not use ^O - UNIX not sending. - case KEY_INS: /* Toggle insert/overwrite */ - 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 */ - block_cancel(); - 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 */ - block_cancel(); - 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 */ - block_cancel(); - 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 - { - int attr = EOATTR_NORMAL; - attr |= detect_attr(curr_buf->currline->data, curr_buf->currline->len); - edit_outs_attr(&curr_buf->currline->data[curr_buf->edit_margin], attr); - } - outs(ANSI_RESET ANSI_CLRTOEND); - edit_msg(); - } - } /* redraw */ - } /* main event loop */ - - exit_edit_buffer(); -} - -int -vedit(char *fpath, int saveheader, int *islocal) -{ - return vedit2(fpath, saveheader, islocal, EDITFLAG_ALLOWTITLE); -} - -/* vim:sw=4:nofoldenable - */ diff --git a/mbbsd/emaildb.c b/mbbsd/emaildb.c deleted file mode 100644 index fa057102..00000000 --- a/mbbsd/emaildb.c +++ /dev/null @@ -1,244 +0,0 @@ -/* $Id$ */ -#include -#include "bbs.h" - -#define EMAILDB_PATH BBSHOME "/emaildb.db" - -// define FORK model to minimize memory usage. -#define FORK_MODEL - -static int emaildb_open(sqlite3 **Db) { - int rc; - - if ((rc = sqlite3_open(EMAILDB_PATH, Db)) != SQLITE_OK) - return rc; - - // create table if it doesn't exist - rc = sqlite3_exec(*Db, "CREATE TABLE IF NOT EXISTS emaildb (userid TEXT, email TEXT, PRIMARY KEY (userid));" - "CREATE INDEX IF NOT EXISTS email ON emaildb (email);", - NULL, NULL, NULL); - - return rc; -} - -#ifndef INIT_MAIN -int emaildb_check_email(char * email, int email_len) -{ - int count = -1; - pid_t pid = -1; - sqlite3 *Db = NULL; - sqlite3_stmt *Stmt = NULL; - -#ifdef FORK_MODEL - switch((pid = fork())) - { - case -1: // error - break; - - case 0: // child - break; - - default: - waitpid(pid, &count, 0); - count = WEXITSTATUS(count); - // vmsgf(ANSI_RESET "found %d emails", count); - return count; - } -#endif - - if (emaildb_open(&Db) != SQLITE_OK) - goto end; - - if (sqlite3_prepare(Db, "SELECT userid FROM emaildb WHERE email LIKE lower(?);", - -1, &Stmt, NULL) != SQLITE_OK) - goto end; - - if (sqlite3_bind_text(Stmt, 1, email, email_len, SQLITE_STATIC) != SQLITE_OK) - goto end; - - count = 0; - while (sqlite3_step(Stmt) == SQLITE_ROW) { - char *result; - userec_t u; - - if ((result = (char*)sqlite3_column_text(Stmt, 0)) == NULL) - break; - - // ignore my self, because I may be the one going to - // use mail. - if (strcasecmp(result, cuser.userid) == 0) - continue; - - // force update - u.email[0] = 0; - - if (getuser(result, &u)) - if (strcasecmp(email, u.email) == 0) - count++; - } - -end: - if (Stmt != NULL) - if (sqlite3_finalize(Stmt) != SQLITE_OK) - count = -1; - - if (Db != NULL) - sqlite3_close(Db); - - if (pid == 0) - exit(count); - - return count; -} -#endif - -int emaildb_update_email(char * userid, int userid_len, char * email, int email_len) -{ - int ret = -1; - pid_t pid = -1; - -#ifdef FORK_MODEL - switch((pid = fork())) - { - case -1: // error - break; - - case 0: // child - break; - - default: - waitpid(pid, &ret, 0); - ret = WEXITSTATUS(ret); - return ret; - } -#endif - - sqlite3 *Db = NULL; - sqlite3_stmt *Stmt = NULL; - - if (emaildb_open(&Db) != SQLITE_OK) - goto end; - - if (sqlite3_prepare(Db, "REPLACE INTO emaildb (userid, email) VALUES (lower(?),lower(?));", - -1, &Stmt, NULL) != SQLITE_OK) - goto end; - - if (sqlite3_bind_text(Stmt, 1, userid, userid_len, SQLITE_STATIC) != SQLITE_OK) - goto end; - - if (sqlite3_bind_text(Stmt, 2, email, email_len, SQLITE_STATIC) != SQLITE_OK) - goto end; - - if (sqlite3_step(Stmt) == SQLITE_DONE) - ret = 0; - -end: - if (Stmt != NULL) - sqlite3_finalize(Stmt); - if (Db != NULL) - sqlite3_close(Db); - - if (pid == 0) - exit(ret); - - return ret; -} - -#ifdef INIT_MAIN - -// standalone initialize builder - -#define TRANSCATION_PERIOD (4096) -int main() -{ - int fd = 0; - userec_t xuser; - off_t sz = 0, i = 0, valids = 0; - sqlite3 *Db = NULL; - sqlite3_stmt *Stmt = NULL, *tranStart = NULL, *tranEnd = NULL; - - // init passwd - sz = dashs(FN_PASSWD); - fd = open(FN_PASSWD, O_RDONLY); - if (fd < 0 || sz <= 0) - { - fprintf(stderr, "cannot open ~/.PASSWDS.\n"); - return 0; - } - sz /= sizeof(userec_t); - - // init emaildb - if (emaildb_open(&Db) != SQLITE_OK) - { - fprintf(stderr, "cannot initialize emaildb.\n"); - return 0; - } - - if (sqlite3_prepare(Db, "REPLACE INTO emaildb (userid, email) VALUES (lower(?),lower(?));", - -1, &Stmt, NULL) != SQLITE_OK || - sqlite3_prepare(Db, "BEGIN TRANSACTION;", -1, &tranStart, NULL) != SQLITE_OK || - sqlite3_prepare(Db, "COMMIT;", -1, &tranEnd, NULL) != SQLITE_OK) - { - fprintf(stderr, "SQLite 3 internal error.\n"); - return 0; - } - - sqlite3_step(tranStart); - sqlite3_reset(tranStart); - while (read(fd, &xuser, sizeof(xuser)) == sizeof(xuser)) - { - i++; - // got a record - if (strlen(xuser.userid) < 2 || strlen(xuser.userid) > IDLEN) - continue; - if (strlen(xuser.email) < 5) - continue; - - if (sqlite3_bind_text(Stmt, 1, xuser.userid, strlen(xuser.userid), - SQLITE_STATIC) != SQLITE_OK) - { - fprintf(stderr, "\ncannot prepare userid param.\n"); - break; - } - if (sqlite3_bind_text(Stmt, 2, xuser.email, strlen(xuser.email), - SQLITE_STATIC) != SQLITE_OK) - { - fprintf(stderr, "\ncannot prepare email param.\n"); - break; - } - - if (sqlite3_step(Stmt) != SQLITE_DONE) - { - fprintf(stderr, "\ncannot execute statement.\n"); - break; - } - sqlite3_reset(Stmt); - - valids ++; - if (valids % 10 == 0) - fprintf(stderr, "%d/%d (valid: %d)\r", - (int)i, (int)sz, (int)valids); - if (valids % TRANSCATION_PERIOD == 0) - { - sqlite3_step(tranEnd); - sqlite3_step(tranStart); - sqlite3_reset(tranEnd); - sqlite3_reset(tranStart); - } - } - - if (valids % TRANSCATION_PERIOD) - sqlite3_step(tranEnd); - - if (Stmt != NULL) - sqlite3_finalize(Stmt); - - if (Db != NULL) - sqlite3_close(Db); - - close(fd); - return 0; -} -#endif - -// vim: sw=4 diff --git a/mbbsd/fav.c b/mbbsd/fav.c deleted file mode 100644 index 8a6c1e2f..00000000 --- a/mbbsd/fav.c +++ /dev/null @@ -1,1320 +0,0 @@ -/* $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 cmd: FALSE: unset, TRUE: set, EXCH: opposite - */ -void set_attr(fav_type_t *ft, int bit, char cmd){ - if (ft == NULL) - return; - if (cmd == EXCH) - ft->attr ^= bit; - else if (cmd == TRUE) - ft->attr |= bit; - else - ft->attr &= ~bit; - dirty = 1; -} - -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-1bid - 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-1bid - 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 出我的最愛。 - * TODO create default fav, and add SYSOP/PttNewHand (see reginit_fav) - */ -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_number = 0; - 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-1DataTail; 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-1attr; -} - -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); -} - -// create defaults for new user -void reginit_fav(void) -{ - int bid = 0; - - fav_load(); // for creating root - -#ifdef GLOBAL_NEWBIE - bid = getbnum(GLOBAL_NEWBIE); - if (bid > 0) fav_add_board(bid); -#endif - -#ifdef GLOBAL_TEST - bid = getbnum(GLOBAL_TEST); - if (bid > 0) fav_add_board(bid); -#endif - -#ifdef GLOBAL_ASKBOARD - bid = getbnum(GLOBAL_ASKBOARD); - if (bid > 0) fav_add_board(bid); -#endif - -#ifdef GLOBAL_SYSOP - bid = getbnum(GLOBAL_SYSOP); - if (bid > 0) fav_add_board(bid); -#endif - - fav_save(); -} - -#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 - -// vim:ts=8:sw=4 diff --git a/mbbsd/file.c b/mbbsd/file.c deleted file mode 100644 index 3b657acc..00000000 --- a/mbbsd/file.c +++ /dev/null @@ -1,186 +0,0 @@ -/* $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; -} - -/** - * 將 "$key\n" append 到檔案 file 後端 - * @param file 要被 append 的檔 - * @param key 沒有換行的字串 - * @return 成功傳回 0,失敗傳回 -1。 - */ -int file_append_record(const char *file, const char *key) -{ - FILE *fp; - if (!key || !*key) return -1; - if ((fp = fopen(file, "a")) == NULL) - return -1; - flock(fileno(fp), LOCK_EX); - fputs(key, fp); - fputs("\n", fp); - flock(fileno(fp), LOCK_UN); - fclose(fp); - return 0; -} - -/** - * 傳回檔案 file 中 key 所在行數 - */ -int file_find_record(const char *file, const char *key) -{ - FILE *fp; - char buf[STRLEN], *ptr; - int i = 0; - - if ((fp = fopen(file, "r")) == NULL) - return 0; - - while (fgets(buf, STRLEN, fp)) { - char *strtok_pos; - i++; - if ((ptr = strtok_r(buf, str_space, &strtok_pos)) && !strcasecmp(ptr, key)) { - fclose(fp); - return i; - } - } - fclose(fp); - return 0; -} - -/** - * 傳回檔案 file 中是否有 key - */ -int file_exist_record(const char *file, const char *key) -{ - return file_find_record(file, key) > 0 ? 1 : 0; -} - -/** - * 刪除檔案 file 中以 string 開頭的行 - * @param file 要處理的檔案 - * @param string 尋找的 key name - * @param case_sensitive 是否要處理大小寫 - * @return 成功傳回 0,失敗傳回 -1。 - */ -int -file_delete_record(const char *file, const char *string, int case_sensitive) -{ - // TODO nfp 用 tmpfile() 比較好? 不過 Rename 會變慢... - FILE *fp = NULL, *nfp = NULL; - char fnew[PATHLEN]; - char buf[STRLEN + 1]; - int ret = -1, i = 0; - const size_t toklen = strlen(string); - - if (!toklen) - return 0; - - do { - snprintf(fnew, sizeof(fnew), "%s.%3.3X", file, (unsigned int)(random() & 0xFFF)); - if (access(fnew, 0) != 0) - break; - } while (i++ < 10); // max tries = 10 - - if (access(fnew, 0) == 0) return -1; // cannot create temp file. - - i = 0; - if ((fp = fopen(file, "r")) && (nfp = fopen(fnew, "w"))) { - while (fgets(buf, sizeof(buf), fp)) - { - size_t klen = strcspn(buf, str_space); - if (toklen == klen) - { - if (((case_sensitive && strncmp(buf, string, toklen) == 0) || - (!case_sensitive && strncasecmp(buf, string, toklen) == 0))) - { - // found line. skip it. - i++; - continue; - } - } - // other wise, keep the line. - fputs(buf, nfp); - } - fclose(nfp); nfp = NULL; - if (i > 0) - { - if(Rename(fnew, file) < 0) - ret = -1; - else - ret = 0; - } else { - unlink(fnew); - ret = 0; - } - } - if(fp) - fclose(fp); - if(nfp) - fclose(nfp); - return ret; -} - -/** - * 對每一筆 record 做 func 這件事。 - * @param file - * @param func 處理每筆 record 的 handler,為一 function pointer。 - * 第一個參數是檔案中的一行,第二個參數為 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/mbbsd/friend.c b/mbbsd/friend.c deleted file mode 100644 index 4fbc0be3..00000000 --- a/mbbsd/friend.c +++ /dev/null @@ -1,572 +0,0 @@ -/* $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; - char myboard[IDLEN+1] = ""; - int boardChanged = 0; - - setfriendfile(fpath, type); - - if (currboard && *currboard) - strcpy(myboard, currboard); - - 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]); - } - if (HasUserPerm(PERM_SYSOP)) - outs(" (S) 選擇其他看板的特別名單"); - - getdata(11, 0, "請選擇 或 直接[Enter] 放棄:", buf, 3, LCECHO); - if (!buf[0]) - return; - - if (HasUserPerm(PERM_SYSOP) && buf[0] == 's') - { - Select(); - boardChanged = 1; - } - - j = buf[0] - '1'; - if (j >= type) - j++; - if (!(HasUserPerm(PERM_SYSOP) || currmode & MODE_BOARD) && j >= 5) - { - if (boardChanged) - enter_board(myboard); - 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); - } - if (boardChanged) - enter_board(myboard); -} - -static int -delete_friend_from_file(const char *file, const char *string, int case_sensitive) -{ - FILE *fp = NULL, *nfp = NULL; - char fnew[PATHLEN]; - char genbuf[STRLEN + 1]; - int ret = 0; - - 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); - else - ret = 1; - } - Rename(fnew, file); - } - if(fp) - fclose(fp); - if(nfp) - fclose(nfp); - return ret; -} - -void -friend_delete(const char *uident, int type) -{ - char fn[STRLEN]; - 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[PATHLEN]; - sethomefile(fn, uident, "aloha"); - delete_friend_from_file(fn, thefriend, 0); -} - -void -friend_delete_all(const char *uident, int type) -{ - char buf[PATHLEN], line[PATHLEN]; - 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), cmp_int); - 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-1perm_reload = now; - assert(0<=currbid-1 && currbid-1brdname); - } - friend_load(0); - } -} - -int -t_override(void) -{ - friend_edit(FRIEND_OVERRIDE); - return 0; -} - -int -t_reject(void) -{ - friend_edit(FRIEND_REJECT); - return 0; -} - -int -t_fix_aloha() -{ - char xid[IDLEN+1] = ""; - char fn[PATHLEN] = ""; - - clear(); - stand_title("修正上站通知"); - - outs("這是用來修正某些使用者遇到錯誤的上站通知的問題。\n" - ANSI_COLOR(1) "如果你沒遇到此類問題可直接離開。" ANSI_RESET "\n\n" - "▼如果你遇到有人沒在你的上站通知名單內但又會丟上站通知水球給你,\n" - " 請輸入他的 ID。\n"); - - move(7, 0); - usercomplete("有誰不在你的通知名單內但又會送上站通知水球給您呢? ", xid); - - if (!xid[0]) - { - vmsg("修正結束。"); - return 0; - } - - // check by xid - move(9, 0); - outs("檢查中...\n"); - - // xid in my override list? - setuserfile(fn, "alohaed"); - if (belong(fn, xid)) - { - prints(ANSI_COLOR(1;32) "[%s] 確實在你的上站通知名單內。" - "請編輯 [上站通知名單]。" ANSI_RESET "\n", xid); - vmsg("不需修正。"); - return 0; - } - - sethomefile(fn, xid, "aloha"); - if (delete_friend_from_file(fn, cuser.userid, 0)) - { - outs(ANSI_COLOR(1;33) "已找到錯誤並修復完成。" ANSI_RESET "\n"); - } else { - outs(ANSI_COLOR(1;31) "找不到錯誤... 打錯 ID 了?" ANSI_RESET "\n"); - } - - vmsg("若上站通知錯誤仍持續發生請通知站方處理。"); - return 0; -} - diff --git a/mbbsd/gamble.c b/mbbsd/gamble.c deleted file mode 100644 index 2f3e2dd7..00000000 --- a/mbbsd/gamble.c +++ /dev/null @@ -1,388 +0,0 @@ -/* $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(BBSMNAME "賭盤", 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, SHOWFILE_ALLOW_ALL); - 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("無法準備開獎... 請至 " GLOBAL_BUGREPORT " 報告並附上板名。"); - 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 " MONEYNAME "幣 \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 枚" MONEYNAME "幣\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 枚" MONEYNAME "幣\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", BBSMNAME "賭場"); - } - 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(GLOBAL_SECURITY, buf + 7, outcome, "[馬路探子]"); - - setbfile(buf, bh->brdname, FN_TICKET_RECORD); - unlink(buf); - - setbfile(buf, bh->brdname, FN_TICKET_USER); - post_file(GLOBAL_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/mbbsd/go.c b/mbbsd/go.c deleted file mode 100644 index 44cf9cc8..00000000 --- a/mbbsd/go.c +++ /dev/null @@ -1,1118 +0,0 @@ -/* $Id$ */ - -#include "bbs.h" -#include - -#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, 2, 1, - 1, 1, 1, 1, 2, - 1, 1, 1, 1, 1, - 2, 1, 1, 3 }; - - 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 = - &BoardPic[BoardPicIndex[board_line] * 4]; - 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; - char buf[ANSILINELEN] = ""; - int i, x, y; - 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; - } - - getyx(&y, &x); - for (i = 1; i <= 22; i++) - { - move(i, 0); - inansistr(buf, sizeof(buf)-1); - fprintf(fp, "%s\n", buf); - } - move(y, x); - - 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\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\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, "\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/mbbsd/gomo.c b/mbbsd/gomo.c deleted file mode 100644 index 90f72c63..00000000 --- a/mbbsd/gomo.c +++ /dev/null @@ -1,590 +0,0 @@ -/* $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, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 3 }; - - 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 = - &BoardPic[BoardPicIndex[board_line] * 4]; - 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) -{ - char buf[ANSILINELEN] = ""; - const int nStep = info->history.used; - int i, x, y; - - getyx(&y, &x); - for (i = 1; i <= 18; i++) - { - move(i, 0); - inansistr(buf, sizeof(buf)-1); - fprintf(fp, "%s\n", buf); - } - move(y, x); - - fprintf(fp, "\n"); - fprintf(fp, "按 z 可進入打譜模式\n"); - fprintf(fp, "\n"); - - fprintf(fp, "\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("\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("\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/mbbsd/guess.c b/mbbsd/guess.c deleted file mode 100644 index c5408b88..00000000 --- a/mbbsd/guess.c +++ /dev/null @@ -1,369 +0,0 @@ -/* $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 " MONEYNAME "$", 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 " MONEYNAME "$", 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 " MONEYNAME "$"); - return 1; - } - if ((money = get_money()) == 0) - return 1; - vice(money, "猜數字"); - - Diff_Random(answer); - move(2, 0); - clrtoeol(); - prints("您下注 :%d " MONEYNAME "$", money); - - getdata_str(4, 0, "您要和電腦比賽嗎? [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 " MONEYNAME "$\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 " MONEYNAME "$\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 " MONEYNAME "$\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 " MONEYNAME "$\n", - cuser.userid, count, money); - fclose(file); - } - } else { - outs("五次猜出來, 還你本錢吧"); - move(18, 35); - clrtoeol(); - prints("你拿回了%d " MONEYNAME "$\n", money); - if ((file = fopen(LOGPASS, "a"))) { - fprintf(file, "id: %s, 第%d次猜中, 拿回了本錢 %d " MONEYNAME "$\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 " MONEYNAME "$\n", cuser.userid, money); - fclose(file); - } - unlockutmpmode(); - pressanykey(); - return 1; -} diff --git a/mbbsd/io.c b/mbbsd/io.c deleted file mode 100644 index ae78bbcd..00000000 --- a/mbbsd/io.c +++ /dev/null @@ -1,1050 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -//kcwu: 80x24 一般使用者名單 1.9k, 含 header 2.4k -// 一般文章推文頁約 2590 bytes -#define OBUFSIZE 3072 -#define IBUFSIZE 128 - -/* realXbuf is Xbuf+3 because hz convert library requires buf[-2]. */ -#define CVTGAP (3) - -#ifdef DEBUG -#define register -#endif - -static unsigned char real_outbuf[OBUFSIZE + CVTGAP*2] = " ", - real_inbuf [IBUFSIZE + CVTGAP*2] = " "; - -// use defines instead - it is discovered that sometimes the input/output buffer was overflow, -// without knowing why. -// static unsigned char *outbuf = real_outbuf + 3, *inbuf = real_inbuf + 3; -#define inbuf (real_inbuf +CVTGAP) -#define outbuf (real_outbuf+CVTGAP) - -static int obufsize = 0, ibufsize = 0; -static int icurrchar = 0; - -#ifdef DBG_OUTRPT -// output counter -static unsigned long szTotalOutput = 0, szLastOutput = 0; -extern unsigned char fakeEscape; -unsigned char fakeEscape = 0; - -unsigned char fakeEscFilter(unsigned char c) -{ - if (!fakeEscape) return c; - if (c == ESC_CHR) return '*'; - else if (c == '\n') return 'N'; - else if (c == '\r') return 'R'; - else if (c == '\b') return 'B'; - else if (c == '\t') return 'I'; - return c; -} -#endif // DBG_OUTRPT - -/* ----------------------------------------------------- */ -/* 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; - } - -#ifdef DBG_OUTRPT - // if (0) - { - static char xbuf[128]; - sprintf(xbuf, ESC_STR "[s" ESC_STR "[H" " [%lu/%lu] " ESC_STR "[u", - szLastOutput, szTotalOutput); - write(1, xbuf, strlen(xbuf)); - szLastOutput = 0; - } -#endif // DBG_OUTRPT - - fsync(1); -} - -void -output(const char *s, int len) -{ -#ifdef DBG_OUTRPT - int i = 0; - if (fakeEscape) - for (i = 0; i < obufsize; i++) - outbuf[i] = fakeEscFilter(outbuf[i]); - - szTotalOutput += len; - szLastOutput += len; -#endif // DBG_OUTRPT - - /* Invalid if len >= OBUFSIZE */ - assert(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) -{ - -#ifdef DBG_OUTRPT - c = fakeEscFilter(c); - szTotalOutput ++; - szLastOutput ++; -#endif // DBG_OUTRPT - - 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) -{ - if (ibufsize <= icurrchar) - return 0; - return ibufsize - icurrchar; -} - -int -input_isfull(void) -{ - return ibufsize >= IBUFSIZE; -} - -/* - * 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){ - syncnow(); - return I_TIMEOUT; - } - - if (i_newfd && FD_ISSET(i_newfd, &readfds)){ - syncnow(); - 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 -#ifdef DBG_OUTRPT - // if (0) - { - static char xbuf[128]; - sprintf(xbuf, ESC_STR "[s" ESC_STR "[2;1H [%ld] " - ESC_STR "[u", len); - write(1, xbuf, strlen(xbuf)); - fsync(1); - } -#endif // DBG_OUTRPT - - } while (len <= 0); - - ibufsize = len; - icurrchar = 0; - } - - if (currutmp) { - syncnow(); - /* 3 秒內超過兩 byte 才算 active, anti-antiidle. - * 不過方向鍵等組合鍵不止 1 byte */ - if (now - lastact < 3) - currutmp->lastact = now; - lastact = now; - } - - // CR LF are treated as one. - if (inbuf[icurrchar] == Ctrl('M')) - { - if (++icurrchar < ibufsize && - inbuf[icurrchar] == Ctrl('J')) - icurrchar ++; - return Ctrl('M'); - } - 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 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 == '~') - { - // vt220 style - if (last >= '1' && last <= '6') - return KEY_HOME + (last - '1'); - // else, unknown. - return KEY_UNKNOWN; - } - else if (last == '1') - { - if (ch >= '1' && ch <= '6') - { - // use num_in_buf() to prevent waiting keys - if (num_in_buf() && dogetch() == '~') /* must be '~' */ - return KEY_F1 + ch - '1'; - } - else if (ch >= '7' && ch <= '9') - { - // use num_in_buf() to prevent waiting keys - if (num_in_buf() && dogetch() == '~') /* must be '~' */ - return KEY_F6 + ch - '7'; - } - return KEY_UNKNOWN; - } - else if (last == '2') - { - if (ch >= '0' && ch <= '4') - { - // use inbuf() to prevent waiting keys - if (num_in_buf() && dogetch() == '~') /* hope you are '~' */ - return KEY_F9 + ch - '0'; - } - return KEY_UNKNOWN; - } - // if fall here, then escape sequence is broken. - 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'): - redrawwin(); - refresh(); - 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; - - scr_dump(&old_screen); - my_newfd = i_newfd; - i_newfd = 0; - - t_users(); - - i_newfd = my_newfd; - roll = oldroll; - scr_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; - - scr_dump(&old_screen); - - my_newfd = i_newfd; - i_newfd = 0; - my_write2(); - scr_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; - scr_dump(&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; - - /* 還原螢幕 */ - scr_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; - - // try to do this in getch() level. -#if 0 - case Ctrl('J'): /* Ptt 把 \n 拿掉 */ -#ifdef PLAY_ANGEL - /* Seams some telnet client still send CR LF when changing lines. - CallAngel(); - */ -#endif - continue; -#endif - - 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 bIgnoreBuf) -{ - int sel = 0; - fd_set readfds; - struct timeval tv, *ptv = &tv; - - if(!bIgnoreBuf && num_in_buf() > 0) - return 1; - - FD_ZERO(&readfds); - FD_SET(0, &readfds); - - 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(!bIgnoreBuf && 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; -} - -void -drop_input(void) -{ - icurrchar = ibufsize = 0; -} - -int -peek_input(float f, int c) -{ - int i = 0; - assert (c > 0 && c < ' '); // only ^x keys are safe to be detected. - // other keys may fall into escape sequence. - - if (wait_input(f, 1) && (IBUFSIZE > ibufsize)) - { - int len = tty_read(inbuf + ibufsize, IBUFSIZE - ibufsize); -#ifdef CONVERT - if(len > 0) - len = input_wrapper(inbuf+ibufsize, len); -#endif - if (len > 0) - ibufsize += len; - } - for (i = icurrchar; i < ibufsize; i++) - { - if (inbuf[i] == c) - return 1; - } - return 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, lprompt = 0; - int cx = col, cy = 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 (prompt) - { - lprompt = strlen_noansi(prompt); - cx += lprompt; - } - - if(line == b_lines-msg_occupied) - occupy_msg=1, msg_occupied ++; - - // workaround poor terminal - move_ansi(line, col); - getyx(&line, &col); - clrtoeol(); - - // (line, col) are real starting address - - if (!echo) { - if (prompt) outs(prompt); - len--; - clen = 0; - while ((ch = igetch()) != '\r') { - if (ch == Ctrl('C')) - { - // abort - clen = 0; - if (len > 1) - buf[1] = ch; // workaround for BBS-Lua - break; - } - if (ch == '\177' || ch == Ctrl('H')) { - if (!clen) { - bell(); - continue; - } - clen--; - continue; - } - if (ch>=0x100 || !isprint(ch)) { - bell(); - continue; - } - if (clen >= len) { - bell(); - 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); - - while (1) { - // refresh from prompt - move(line, col); outc(' '); move(line, col); clrtoeol(); - if (prompt) outs(prompt); - - outs(ANSI_COLOR(7)); - outs(buf); - for(i=clen; i<=len; i++) - outc(' '); - outs(ANSI_RESET); - move(cy, cx + currchar); - - if ((ch = igetch()) == '\r') - break; - - if (ch == Ctrl('C')) - { - // abort - clen = currchar = 0; - if (len > 1) - buf[1] = ch; // workaround for BBS-Lua - break; - } - - switch (ch) { - case Ctrl('A'): - case KEY_HOME: - currchar = 0; - break; - - case Ctrl('E'): - case KEY_END: - currchar = clen; - break; - - case KEY_UNKNOWN: - break; - - case KEY_LEFT: - if (currchar <= 0) - break; - --currchar; -#ifdef DBCSAWARE - if(currchar > 0 && ISDBCSAWARE() && - getDBCSstatus((unsigned char*)buf, currchar) == DBCS_TRAILING) - currchar --; -#endif - break; - - case KEY_RIGHT: - if (!buf[currchar]) - break; - ++currchar; -#ifdef DBCSAWARE - if(buf[currchar] && ISDBCSAWARE() && - getDBCSstatus((unsigned char*)buf, currchar) == DBCS_TRAILING) - currchar++; -#endif - break; - - case Ctrl('Y'): - currchar = 0; - case Ctrl('K'): - /* we shoud be able to avoid DBCS issues in ^K mode */ - buf[currchar] = '\0'; - clen = currchar; - break; - - 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); - clen = currchar = strlen(buf); - break; - - case '\177': - case Ctrl('H'): - if (!currchar) - break; -#ifdef DBCSAWARE - if (ISDBCSAWARE() && getDBCSstatus((unsigned char*)buf, - currchar-1) == DBCS_TRAILING) - { - memmove(buf+currchar-1, buf+currchar, clen-currchar+1); - currchar--, clen--; - } -#endif - memmove(buf+currchar-1, buf+currchar, clen-currchar+1); - currchar--, clen--; - break; - - case Ctrl('D'): - case KEY_DEL: - if (!buf[currchar]) - break; -#ifdef DBCSAWARE - if (ISDBCSAWARE() && buf[currchar+1] && getDBCSstatus( - (unsigned char*)buf, currchar+1) == DBCS_TRAILING) - { - memmove(buf+currchar, buf+currchar+1, clen-currchar); - clen --; - } -#endif - memmove(buf+currchar, buf+currchar+1, clen-currchar); - clen --; - break; - - default: - if (echo == NUMECHO && !isdigit(ch)) - { - bell(); - break; - } - if (isprint2(ch) && clen < len && cx + 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; - currchar++; - clen++; - } - break; - } /* end case */ - assert(0<=clen); - } /* end while */ - buf[clen] = '\0'; - - 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(line+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/mbbsd/kaede.c b/mbbsd/kaede.c deleted file mode 100644 index b3d9c0ae..00000000 --- a/mbbsd/kaede.c +++ /dev/null @@ -1,165 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -// TODO move stuff to libbbs(or util)/string.c, ... -// this file can be removed (or not?) - -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] ){ - - // secure content - - case 't': // current time - strlcpy(strbuf+w, Cdate(&now), size-w); - w += strlen(strbuf+w); - break; - case 'u': // current online users - w += snprintf(&strbuf[w], size - w, - "%d", SHM->UTMPnumber); - break; - - // insecure content - - case 's': // current user id - strlcpy(strbuf+w, cuser.userid, size-w); - w += strlen(strbuf+w); - break; - case 'n': // current user nickname - strlcpy(strbuf+w, cuser.nickname, size-w); - w += strlen(strbuf+w); - break; - case 'l': // current user logins - w += snprintf(&strbuf[w], size - w, - "%d", cuser.numlogins); - break; - case 'p': // current user posts - w += snprintf(&strbuf[w], size - w, - "%d", cuser.numposts); - 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; -} - -// utility from screen.c -void -outs_n(const char *str, int n) -{ - while (*str && n--) { - outc(*str++); - } -} - -// XXX left-right (for large term) -// TODO someday please add ANSI detection version -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) -{ - int y, x; - getyx(&y, &x); - while (*str && line) { - if (*str == '\n') - { - move(++y, 0); - line--; - } else - { - outc(*str); - } - 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); -} - -// vim:ts=4 diff --git a/mbbsd/lovepaper.c b/mbbsd/lovepaper.c deleted file mode 100644 index 50c643bb..00000000 --- a/mbbsd/lovepaper.c +++ /dev/null @@ -1,114 +0,0 @@ -/* $Id$ */ -#include "bbs.h" -#define DATA "etc/lovepaper.dat" - -int -x_love(void) -{ - char buf1[200], 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, "主 題:", title, - sizeof(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); - strlcpy(save_title, title, sizeof(save_title)); - curredit |= EDIT_MAIL; - if (vedit(path, YEA, NULL) == -1) { - curredit &= ~EDIT_MAIL; - unlink(path); - clear(); - outs("\n\n 放棄寄情書\n"); - pressanykey(); - return -2; - } - curredit &= ~EDIT_MAIL; - 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; - sendalert(receiver, ALERT_NEW_MAIL); - hold_mail(buf1, receiver); - return 1; - } else { - vmsg("本站目前無情書資料庫,請向站長反應。"); - } - fclose(fpo); - return 0; -} diff --git a/mbbsd/mail.c b/mbbsd/mail.c deleted file mode 100644 index 38e19619..00000000 --- a/mbbsd/mail.c +++ /dev/null @@ -1,2092 +0,0 @@ -/* $Id$ */ -#include "bbs.h" -static int mailkeep = 0, mailsum = 0; -static int mailsumlimit = 0, mailmaxkeep = 0; -static char currmaildir[32]; -static char msg_cc[] = ANSI_COLOR(32) "[群組名單]" ANSI_RESET "\n"; -static char listfile[] = "list.0"; - -// check only 20 mails (one page) is enough. -// #define NEWMAIL_CHECK_RANGE (1) -// checking only 1 mail works more like brc style. -#define NEWMAIL_CHECK_RANGE (5) - -enum SHOWMAIL_MODES { - SHOWMAIL_NORM = 0, - SHOWMAIL_SUM, - SHOWMAIL_RANGE, -}; -static int showmail_mode = SHOWMAIL_NORM; - -int -setforward(void) -{ - char buf[80], ip[50] = "", yn[4]; - FILE *fp; - int flIdiotSent2Self = 0; - int oidlen = strlen(cuser.userid); - - sethomepath(buf, cuser.userid); - strcat(buf, "/.forward"); - if ((fp = fopen(buf, "r"))) { - fscanf(fp, "%" toSTR(sizeof(ip)) "s", ip); - fclose(fp); - } - getdata_buf(b_lines - 1, 0, "請輸入自動轉寄的Email: ", - ip, sizeof(ip), DOECHO); - - /* anti idiots */ - if (strncasecmp(ip, cuser.userid, oidlen) == 0) - { - int addrlen = strlen(ip); - if( addrlen == oidlen || - (addrlen > oidlen && - strcasecmp(ip + oidlen, str_mail_address) == 0)) - flIdiotSent2Self = 1; - } - - if (ip[0] && ip[0] != ' ' && !flIdiotSent2Self) { - getdata(b_lines, 0, "確定開啟自動轉信功\能?(Y/n)", yn, sizeof(yn), - LCECHO); - if (yn[0] != 'n' && (fp = fopen(buf, "w"))) { - fputs(ip, fp); - fclose(fp); - vmsg("設定完成!"); - return 0; - } - } - unlink(buf); - if(flIdiotSent2Self) - vmsg("自動轉寄是不會設定給自己的,想取消用空白就可以了。"); - else - vmsg("取消自動轉信!"); - return 0; -} - -int -toggle_showmail_mode(void) -{ - showmail_mode ++; - showmail_mode %= SHOWMAIL_RANGE; - return FULLUPDATE; -} - -int -built_mail_index(void) -{ - char genbuf[128]; - - move(b_lines - 4, 0); - outs("本功\能只在信箱檔毀損時使用," ANSI_COLOR(1;33) "無法" ANSI_RESET "救回被刪除的信件。\n" - "除非您清楚這個功\能的作用,否則" ANSI_COLOR(1;33) "請不要使用" ANSI_RESET "。\n" - "警告:任意的使用將導致" ANSI_COLOR(1;33) "不可預期的結果" ANSI_RESET "!\n"); - getdata(b_lines - 1, 0, - "確定重建信箱?(y/N)", genbuf, 3, - LCECHO); - if (genbuf[0] != 'y') - return 0; - - snprintf(genbuf, sizeof(genbuf), - BBSHOME "/bin/buildir " BBSHOME "/home/%c/%s > /dev/null", - cuser.userid[0], cuser.userid); - mouts(b_lines - 1, 0, ANSI_COLOR(1;31) "已經處理完畢!! 諸多不便 敬請原諒~" ANSI_RESET); - system(genbuf); - pressanykey(); - return 0; -} - -int -sendalert(const char *userid, int alert) -{ - userinfo_t *uentp = NULL; - int n, tuid, i; - - if ((tuid = searchuser(userid, NULL)) == 0) - return -1; - - n = count_logins(tuid, 0); - for (i = 1; i <= n; i++) - if ((uentp = (userinfo_t *) search_ulistn(tuid, i))) - uentp->alerts |= alert; - return 0; -} - -int -mail_muser(userec_t muser, const char *title, const char *filename) -{ - return mail_id(muser.userid, title, filename, cuser.userid); -} - -int -mail_id(const char *id, const char *title, const char *src, const char *owner) -{ - fileheader_t mhdr; - char dst[128], dirf[128]; - sethomepath(dst, id); - if (stampfile(dst, &mhdr)) - return 0; - strlcpy(mhdr.owner, owner, sizeof(mhdr.owner)); - strlcpy(mhdr.title, title, sizeof(mhdr.title)); - mhdr.filemode = 0; - Copy(src, dst); - - sethomedir(dirf, id); - append_record_forward(dirf, &mhdr, sizeof(mhdr), id); - sendalert(id, ALERT_NEW_MAIL); - return 0; -} - -int -invalidaddr(const char *addr) -{ -#ifdef DEBUG_FWDADDRERR - const char *origaddr = addr; - char errmsg[PATHLEN]; -#endif - - if (*addr == '\0') - return 1; /* blank */ - - while (*addr) { -#ifdef DEBUG_FWDADDRERR - if (not_alnum(*addr) && !strchr("[].@-_+", *addr)) - { - int c = (*addr) & 0xff; - clear(); - move(2,0); - outs( - "您輸入的位址錯誤 (address error)。 \n\n" - "由於最近許\多人反應打入正確的位址(id或email)後系統會判斷錯誤\n" - "但檢查不出原因,所以我們需要正確的錯誤回報。\n\n" - "如果你確實打錯了,請直接略過下面的說明。\n" - "如果你認為你輸入的位址確實是對的,請把下面的訊息複製起來\n" - "並貼到 " GLOBAL_BUGREPORT " 板。本站為造成不便深感抱歉。\n\n" - ANSI_COLOR(1;33)); - sprintf(errmsg, "原始輸入位址: [%s]\n" - "錯誤位置: 第 %d 字元: 0x%02X [ %c ]\n", - origaddr, (int)(addr - origaddr+1), c, c); - outs(errmsg); - outs(ANSI_RESET); - vmsg("請按任意鍵繼續"); - clear(); - return 1; - } -#else - if (not_alnum(*addr) && !strchr("[].@-_", *addr)) - return 1; -#endif - addr++; - } - return 0; -} - -int -m_internet(void) -{ - char receiver[60]; - char title[STRLEN]; - - getdata(20, 0, "收信人:", receiver, sizeof(receiver), DOECHO); - trim(receiver); - if (strchr(receiver, '@') && !invalidaddr(receiver) && - getdata(21, 0, "主 題:", title, sizeof(title), DOECHO)) - do_send(receiver, title); - else { - vmsg("收信人或主題不正確,請重新選取指令"); - } - return 0; -} - -void -m_init(void) -{ - sethomedir(currmaildir, cuser.userid); -} - -static void -loadmailusage(void) -{ - mailkeep=get_num_records(currmaildir,sizeof(fileheader_t)); - mailsum =get_sum_records(currmaildir, sizeof(fileheader_t)); -} - -void -setupmailusage(void) -{ // Ptt: get_sum_records is a bad function - int max_keepmail = MAX_KEEPMAIL; -#ifdef PLAY_ANGEL - if (HasUserPerm(PERM_SYSSUPERSUBOP | PERM_ANGEL)) -#else - if (HasUserPerm(PERM_SYSSUPERSUBOP)) -#endif - { - mailsumlimit = 900; - max_keepmail = 700; - } - else if (HasUserPerm(PERM_SYSSUBOP | PERM_ACCTREG | PERM_PRG | - PERM_ACTION | PERM_PAINT)) { - mailsumlimit = 700; - max_keepmail = 500; - } else if (HasUserPerm(PERM_BM)) { - mailsumlimit = 500; - max_keepmail = 300; - } else if (HasUserPerm(PERM_LOGINOK)) - mailsumlimit = 200; - else - mailsumlimit = 50; - mailsumlimit += (cuser.exmailbox + ADD_EXMAILBOX) * 10; - mailmaxkeep = max_keepmail + cuser.exmailbox; - loadmailusage(); -} - -#define MAILBOX_LIM_OK 0 -#define MAILBOX_LIM_KEEP 1 -#define MAILBOX_LIM_SUM 2 -static int -chk_mailbox_limit(void) -{ - if (HasUserPerm(PERM_SYSOP) || HasUserPerm(PERM_MAILLIMIT)) - return MAILBOX_LIM_OK; - - if (!mailkeep) - setupmailusage(); - - if (mailkeep > mailmaxkeep) - return MAILBOX_LIM_KEEP; - if (mailsum > mailsumlimit) - return MAILBOX_LIM_SUM; - return MAILBOX_LIM_OK; -} - -int -chkmailbox(void) -{ - m_init(); - - switch (chk_mailbox_limit()) { - case MAILBOX_LIM_KEEP: - bell(); - bell(); - vmsgf("您保存信件數目 %d 超出上限 %d, 請整理", mailkeep, mailmaxkeep); - return mailkeep; - - case MAILBOX_LIM_SUM: - bell(); - bell(); - vmsgf("信箱容量(大小,非件數) %d 超出上限 %d, " - "請砍過長的水球記錄或信件", mailsum, mailsumlimit); - if(showmail_mode != SHOWMAIL_SUM) - { - showmail_mode = SHOWMAIL_SUM; - vmsg("信箱顯示模式已自動改為顯示大小,請盡速整理"); - } - return mailsum; - - default: - return 0; - } -} - -static void -do_hold_mail(const char *fpath, const char *receiver, const char *holder) -{ - char buf[80], title[128]; - - fileheader_t mymail; - - sethomepath(buf, holder); - stampfile(buf, &mymail); - - mymail.filemode = FILE_READ ; - strlcpy(mymail.owner, "[備.忘.錄]", sizeof(mymail.owner)); - if (receiver) { - snprintf(title, sizeof(title), "(%s) %s", receiver, save_title); - strlcpy(mymail.title, title, sizeof(mymail.title)); - } else - strlcpy(mymail.title, save_title, sizeof(mymail.title)); - - sethomedir(title, holder); - - unlink(buf); - Copy(fpath, buf); - append_record_forward(title, &mymail, sizeof(mymail), holder); -} - -void -hold_mail(const char *fpath, const char *receiver) -{ - char buf[4]; - - getdata(b_lines - 1, 0, - (cuser.uflag & DEFBACKUP_FLAG) ? - "已順利寄出,是否自存底稿(Y/N)?[Y] " : - "已順利寄出,是否自存底稿(Y/N)?[N] ", - buf, sizeof(buf), LCECHO); - - if (TOBACKUP(buf[0])) - do_hold_mail(fpath, receiver, cuser.userid); -} - -int -do_send(const char *userid, const char *title) -{ - fileheader_t mhdr; - char fpath[STRLEN]; - char receiver[IDLEN + 1]; - char genbuf[200]; - int internet_mail, i; - userec_t xuser; - - STATINC(STAT_DOSEND); - if (strchr(userid, '@')) - internet_mail = 1; - else { - internet_mail = 0; - if (!getuser(userid, &xuser)) - return -1; - if (!(xuser.userlevel & PERM_READMAIL)) - return -3; - - curredit |= EDIT_MAIL; - curredit &= ~EDIT_ITEM; - } - /* process title */ - if (title) - strlcpy(save_title, title, sizeof(save_title)); - else { - char tmp_title[STRLEN-20]; - getdata(2, 0, "主題:", tmp_title, sizeof(tmp_title), DOECHO); - strlcpy(save_title, tmp_title, sizeof(save_title)); - } - - setutmpmode(SMAIL); - - fpath[0] = '\0'; - - if (internet_mail) { - int res, ch; - - if (vedit(fpath, NA, NULL) == -1) { - unlink(fpath); - clear(); - return -2; - } - clear(); - prints("信件即將寄給 %s\n標題為:%s\n確定要寄出嗎? (Y/N) [Y]", - userid, save_title); - ch = igetch(); - switch (ch) { - case 'N': - case 'n': - outs("N\n信件已取消"); - res = -2; - break; - default: - outs("Y\n請稍候, 信件傳遞中...\n"); - res = -#ifndef USE_BSMTP - bbs_sendmail(fpath, save_title, userid); -#else - bsmtp(fpath, save_title, userid); -#endif - hold_mail(fpath, userid); - } - unlink(fpath); - return res; - } else { - strlcpy(receiver, userid, sizeof(receiver)); - sethomepath(genbuf, userid); - stampfile(genbuf, &mhdr); - strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner)); - if (vedit(genbuf, YEA, NULL) == -1) { - unlink(genbuf); - clear(); - return -2; - } - /* why not make title here? */ - strlcpy(mhdr.title, save_title, sizeof(mhdr.title)); - clear(); - sethomefile(fpath, userid, FN_OVERRIDES); - i = belong(fpath, cuser.userid); - sethomefile(fpath, userid, FN_REJECT); - - if (i || !belong(fpath, cuser.userid)) {/* Ptt: 用belong有點討厭 */ - sethomedir(fpath, userid); - if (append_record_forward(fpath, &mhdr, sizeof(mhdr), userid) == -1) - return -1; - sendalert(userid,ALERT_NEW_MAIL); - } - hold_mail(genbuf, userid); - return 0; - } -} - -void -my_send(const char *uident) -{ - switch (do_send(uident, NULL)) { - case -1: - outs(err_uid); - break; - case -2: - outs(msg_cancel); - break; - case -3: - prints("使用者 [%s] 無法收信", uident); - break; - } - pressanykey(); -} - -int -m_send(void) -{ - char uident[40]; - - stand_title("且聽風的話"); - usercomplete(msg_uid, uident); - showplans(uident); - if (uident[0]) - my_send(uident); - return 0; -} - -/* 群組寄信、回信 : multi_send, multi_reply */ -static void -multi_list(int *reciper) -{ - char uid[16]; - char genbuf[200]; - - while (1) { - stand_title("群組寄信名單"); - ShowNameList(3, 0, msg_cc); - move(1, 0); - outs("(I)引入好友 (O)引入上線通知 (N)引入新文章通知 (0-9)引入其他特別名單"); - getdata(2, 0, - "(A)增加 (D)刪除 (M)確認寄信名單 (Q)取消 ?[M]", - genbuf, 4, LCECHO); - switch (genbuf[0]) { - case 'a': - while (1) { - move(1, 0); - usercomplete("請輸入要增加的代號(只按 ENTER 結束新增): ", uid); - if (uid[0] == '\0') - break; - - move(2, 0); - clrtoeol(); - - if (!searchuser(uid, uid)) - outs(err_uid); - else if (!InNameList(uid)) { - AddNameList(uid); - (*reciper)++; - } - ShowNameList(3, 0, msg_cc); - } - break; - case 'd': - while (*reciper) { - move(1, 0); - namecomplete("請輸入要刪除的代號(只按 ENTER 結束刪除): ", uid); - if (uid[0] == '\0') - break; - if (RemoveNameList(uid)) - (*reciper)--; - ShowNameList(3, 0, msg_cc); - } - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - listfile[5] = genbuf[0]; - genbuf[0] = '1'; - case 'i': - setuserfile(genbuf, genbuf[0] == '1' ? listfile : fn_overrides); - ToggleNameList(reciper, genbuf, msg_cc); - break; - case 'o': - setuserfile(genbuf, "alohaed"); - ToggleNameList(reciper, genbuf, msg_cc); - break; - case 'n': - setuserfile(genbuf, "postlist"); - ToggleNameList(reciper, genbuf, msg_cc); - break; - case 'q': - *reciper = 0; - return; - default: - return; - } - } -} - -static void -multi_send(char *title) -{ - FILE *fp; - struct word_t *p = NULL; - fileheader_t mymail; - char fpath[TTLEN], *ptr; - int reciper, listing; - char genbuf[256]; - - CreateNameList(); - listing = reciper = 0; - if (*quote_file) { - AddNameList(quote_user); - reciper = 1; - fp = fopen(quote_file, "r"); - assert(fp); - while (fgets(genbuf, sizeof(genbuf), fp)) { - if (strncmp(genbuf, "※ ", 3)) { - if (listing) - break; - } else { - if (listing) { - char *strtok_pos; - ptr = genbuf + 3; - for (ptr = strtok_r(ptr, " \n\r", &strtok_pos); - ptr; - ptr = strtok_r(NULL, " \n\r", &strtok_pos)) { - if (searchuser(ptr, ptr) && !InNameList(ptr) && - strcmp(cuser.userid, ptr)) { - AddNameList(ptr); - reciper++; - } - } - } else if (!strncmp(genbuf + 3, "[通告]", 6)) - listing = 1; - } - } - fclose(fp); - ShowNameList(3, 0, msg_cc); - } - multi_list(&reciper); - move(1, 0); - clrtobot(); - - if (reciper) { - setutmpmode(SMAIL); - if (title) - do_reply_title(2, title); - else { - getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO); - snprintf(save_title, sizeof(save_title), "[通告] %s", fpath); - } - - setuserfile(fpath, fn_notes); - - if ((fp = fopen(fpath, "w"))) { - fprintf(fp, "※ [通告] 共 %d 人收件", reciper); - listing = 80; - - for (p = toplev; p; p = p->next) { - reciper = strlen(p->word) + 1; - if (listing + reciper > 75) { - listing = reciper; - fprintf(fp, "\n※"); - } else - listing += reciper; - - fprintf(fp, " %s", p->word); - } - memset(genbuf, '-', 75); - genbuf[75] = '\0'; - fprintf(fp, "\n%s\n\n", genbuf); - fclose(fp); - } - curredit |= EDIT_LIST; - - if (vedit(fpath, YEA, NULL) == -1) { - unlink(fpath); - curredit = 0; - vmsg(msg_cancel); - return; - } - listing = 80; - - for (p = toplev; p; p = p->next) { - reciper = strlen(p->word) + 1; - if (listing + reciper > 75) { - listing = reciper; - outc('\n'); - } else { - listing += reciper; - outc(' '); - } - outs(p->word); - if (searchuser(p->word, p->word) && strcmp(STR_GUEST, p->word)) { - sethomefile(genbuf, p->word, FN_OVERRIDES); - if (!belong(genbuf, cuser.userid)) { // not friend, check if rejected - sethomefile(genbuf, p->word, FN_REJECT); - if (belong(genbuf, cuser.userid)) - continue; - } - sethomepath(genbuf, p->word); - } else - continue; - stampfile(genbuf, &mymail); - unlink(genbuf); - Copy(fpath, genbuf); - - strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); - strlcpy(mymail.title, save_title, sizeof(mymail.title)); - mymail.filemode |= FILE_MULTI; /* multi-send flag */ - sethomedir(genbuf, p->word); - if (append_record_forward(genbuf, &mymail, sizeof(mymail), p->word) == -1) - vmsg(err_uid); - sendalert(p->word, ALERT_NEW_MAIL); - } - hold_mail(fpath, NULL); - unlink(fpath); - curredit = 0; - } else - vmsg(msg_cancel); -} - -static int -multi_reply(int ent, fileheader_t * fhdr, const char *direct) -{ - if (!fhdr || !fhdr->filename[0]) - return DONOTHING; - - if (!(fhdr->filemode & FILE_MULTI)) - return mail_reply(ent, fhdr, direct); - - stand_title("群組回信"); - strlcpy(quote_user, fhdr->owner, sizeof(quote_user)); - setuserfile(quote_file, fhdr->filename); - if (!dashf(quote_file)) - { - vmsg("原檔案已消失。"); - return FULLUPDATE; - } - multi_send(fhdr->title); - quote_user[0]='\0'; - quote_file[0]='\0'; - return FULLUPDATE; -} - -int -mail_list(void) -{ - stand_title("群組作業"); - multi_send(NULL); - return 0; -} - -int -mail_all(void) -{ - FILE *fp; - fileheader_t mymail; - char fpath[TTLEN]; - char genbuf[200]; - int i, unum; - char *userid; - - stand_title("給所有使用者的系統通告"); - setutmpmode(SMAIL); - getdata(2, 0, "主題:", fpath, sizeof(fpath), DOECHO); - snprintf(save_title, sizeof(save_title), - "[系統通告]" ANSI_COLOR(1;32) " %s" ANSI_RESET, fpath); - - setuserfile(fpath, fn_notes); - - if ((fp = fopen(fpath, "w"))) { - fprintf(fp, "※ [" ANSI_COLOR(1) "系統通告" ANSI_RESET "] 這是封給所有使用者的信\n"); - fprintf(fp, "-----------------------------------------------------" - "----------------------\n"); - fclose(fp); - } - *quote_file = 0; - - curredit |= EDIT_MAIL; - curredit &= ~EDIT_ITEM; - if (vedit(fpath, YEA, NULL) == -1) { - curredit = 0; - unlink(fpath); - outs(msg_cancel); - pressanykey(); - return 0; - } - curredit = 0; - - setutmpmode(MAILALL); - stand_title("寄信中..."); - - sethomepath(genbuf, cuser.userid); - stampfile(genbuf, &mymail); - unlink(genbuf); - Copy(fpath, genbuf); - unlink(fpath); - strcpy(fpath, genbuf); - - strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); /* 站長 ID */ - strlcpy(mymail.title, save_title, sizeof(mymail.title)); - - sethomedir(genbuf, cuser.userid); - if (append_record_forward(genbuf, &mymail, sizeof(mymail), cuser.userid) == -1) - outs(err_uid); - - for (unum = SHM->number, i = 0; i < unum; i++) { - if (bad_user_id(SHM->userid[i])) - continue; /* Ptt */ - - userid = SHM->userid[i]; - if (strcmp(userid, STR_GUEST) && strcmp(userid, "new") && - strcmp(userid, cuser.userid)) { - sethomepath(genbuf, userid); - stampfile(genbuf, &mymail); - unlink(genbuf); - Copy(fpath, genbuf); - - strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); - strlcpy(mymail.title, save_title, sizeof(mymail.title)); - /* mymail.filemode |= FILE_MARKED; Ptt 公告改成不會mark */ - sethomedir(genbuf, userid); - if (append_record_forward(genbuf, &mymail, sizeof(mymail), userid) == -1) - outs(err_uid); - vmsgf("%*s %5d / %5d", IDLEN + 1, userid, i + 1, unum); - } - } - return 0; -} - -int -mail_mbox(void) -{ - char cmd[100]; - fileheader_t fhdr; - - snprintf(cmd, sizeof(cmd), "/tmp/%s.uu", cuser.userid); - snprintf(fhdr.title, sizeof(fhdr.title), "%s 私人資料", cuser.userid); - doforward(cmd, &fhdr, 'Z'); - return 0; -} - -static int -m_forward(int ent, fileheader_t * fhdr, const char *direct) -{ - char uid[STRLEN]; - - stand_title("轉達信件"); - usercomplete(msg_uid, uid); - if (uid[0] == '\0') - return FULLUPDATE; - - strlcpy(quote_user, fhdr->owner, sizeof(quote_user)); - setuserfile(quote_file, fhdr->filename); - snprintf(save_title, sizeof(save_title), "%.64s (fwd)", fhdr->title); - move(1, 0); - clrtobot(); - prints("轉信給: %s\n標 題: %s\n", uid, save_title); - - switch (do_send(uid, save_title)) { - case -1: - outs(err_uid); - break; - case -2: - outs(msg_cancel); - break; - case -3: - prints("使用者 [%s] 無法收信", uid); - break; - } - pressanykey(); - quote_user[0]='\0'; - quote_file[0]='\0'; - if (strcasecmp(uid, cuser.userid) == 0) - return DIRCHANGED; - return FULLUPDATE; -} - -struct ReadNewMailArg { - int idc; - int *delmsgs; - int delcnt; - int mrd; -}; - -static int -read_new_mail(void * voidfptr, void *optarg) -{ - fileheader_t *fptr=(fileheader_t*)voidfptr; - struct ReadNewMailArg *arg=(struct ReadNewMailArg*)optarg; - char done = NA, delete_it; - char fname[PATHLEN]; - char genbuf[4]; - - arg->idc++; - // XXX fptr->filename may be invalid. - if (fptr->filemode || !fptr->filename[0]) - return 0; - clear(); - move(10, 0); - prints("您要讀來自[%s]的訊息(%s)嗎?", fptr->owner, fptr->title); - getdata(11, 0, "請您確定(Y/N/Q)?[Y] ", genbuf, 3, DOECHO); - if (genbuf[0] == 'q') - return QUIT; - if (genbuf[0] == 'n') - return 0; - - setuserfile(fname, fptr->filename); - fptr->filemode |= FILE_READ; - if (substitute_record(currmaildir, fptr, sizeof(*fptr), arg->idc)) - return -1; - - arg->mrd = 1; - delete_it = NA; - while (!done) { - int more_result = more(fname, YEA); - - switch (more_result) { - case RET_DOREPLY: - mail_reply(arg->idc, fptr, currmaildir); - return FULLUPDATE; - case RET_DOREPLYALL: - multi_reply(arg->idc, fptr, currmaildir); - return FULLUPDATE; - case RET_DORECOMMEND: // we don't accept this. - return FULLUPDATE; - case -1: - return READ_SKIP; - case 0: - break; - default: - return more_result; - } - - outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0); - - switch (igetch()) { - case 'r': - case 'R': - mail_reply(arg->idc, fptr, currmaildir); - break; - case 'y': - multi_reply(arg->idc, fptr, currmaildir); - break; - case 'x': - m_forward(arg->idc, fptr, currmaildir); - break; - case 'd': - case 'D': - delete_it = YEA; - default: - done = YEA; - } - } - if (delete_it) { - if(arg->delcnt==1000) { - vmsg("一次最多刪 1000 封信"); - return 0; - } - clear(); - prints("刪除信件《%s》", fptr->title); - getdata(1, 0, msg_sure_ny, genbuf, 2, LCECHO); - if (genbuf[0] == 'y') { - if(arg->delmsgs==NULL) { - arg->delmsgs=(int*)malloc(sizeof(int)*1000); - if(arg->delmsgs==NULL) { - vmsg("失敗, 請洽站長"); - return 0; - } - } - unlink(fname); - arg->delmsgs[arg->delcnt++] = arg->idc; - - loadmailusage(); - } - } - clear(); - return 0; -} - -void setmailalert() -{ - if(load_mailalert(cuser.userid)) - currutmp->alerts |= ALERT_NEW_MAIL; - else - currutmp->alerts &= ~ALERT_NEW_MAIL; -} -int -m_new(void) -{ - struct ReadNewMailArg arg; - clear(); - setutmpmode(RMAIL); - memset(&arg, 0, sizeof(arg)); - clear(); - curredit |= EDIT_MAIL; - curredit &= ~EDIT_ITEM; - if (apply_record(currmaildir, read_new_mail, sizeof(fileheader_t), &arg) == -1) { - if(arg.delmsgs) - free(arg.delmsgs); - vmsg("沒有新信件了"); - return -1; - } - curredit = 0; - setmailalert(); - while (arg.delcnt--) - delete_record(currmaildir, sizeof(fileheader_t), arg.delmsgs[arg.delcnt]); - if(arg.delmsgs) - free(arg.delmsgs); - vmsg(arg.mrd ? "信已閱\畢" : "沒有新信件了"); - return -1; -} - -static void -mailtitle(void) -{ - char buf[STRLEN]; - int msglen = 0; - - showtitle("郵件選單", BBSName); - prints("[←]離開[↑↓]選擇[→]閱\讀信件 [X]轉錄看板[F]轉寄站外 " - " [O]站外信:%s [h]求助\n" - ANSI_COLOR(7) " 編號 %s 作 者 信 件 標 題" - "", - REJECT_OUTTAMAIL ? ANSI_COLOR(31) "關" ANSI_RESET : "開", - (showmail_mode == SHOWMAIL_SUM) ? "大 小":"日 期"); - - /* 43 columns in length, used later. */ - buf[0] = 0; - - if (mailsumlimit) - { - /* warning: snprintf returns length "if not limited". - * however if this case, they should be the same. */ - - msglen = snprintf(buf, sizeof(buf), - ANSI_COLOR(32) - " (容量:%d/%dk %d/%d篇) ", - mailsum, mailsumlimit, - mailkeep, mailmaxkeep); - msglen -= strlen(ANSI_COLOR(32)); - } - outslr("", 44, buf, msglen); - outs(ANSI_RESET); -} - -static void -maildoent(int num, fileheader_t * ent) -{ - char *title, *mark, *color = NULL, type = ' '; - char datepart[6]; - char isonline = 0; - - if (ent->filemode & FILE_MARKED) - { - type = (ent->filemode & FILE_READ) ? - 'm' : 'M'; - } - else if (ent->filemode & FILE_REPLIED) - { - type = (ent->filemode & FILE_READ) ? - 'r' : 'R'; - } - else - { - type = (ent->filemode & FILE_READ) ? - ' ' : '+'; - } - - if (TagNum && !Tagger(atoi(ent->filename + 2), 0, TAG_NIN)) - type = 'D'; - - title = subject(mark = ent->title); - if (title == mark) { - color = ANSI_COLOR(1;31); - mark = "◇"; - } else { - color = ANSI_COLOR(1;33); - mark = "R:"; - } - - strlcpy(datepart, ent->date, sizeof(datepart)); - - isonline = query_online(ent->owner); - - switch(showmail_mode) - { - case SHOWMAIL_SUM: - { - /* evaluate size */ - size_t filesz = 0; - char ut = 'k'; - char buf[MAXPATHLEN]; - struct stat st; - - if( !ent->filename[0] ){ - filesz = 0; - } else { - setuserfile(buf, ent->filename); - if (stat(buf, &st) >= 0) { - filesz = st.st_size; - /* find printing unit */ - filesz = (filesz + 1023) / 1024; - if(filesz > 9999){ - filesz = (filesz+512) / 1024; - ut = 'M'; - } - if(filesz > 9999) { - filesz = (filesz+512) / 1024; - ut = 'G'; - } - } - } - sprintf(datepart, "%4lu%c", (unsigned long)filesz, ut); - } - break; - default: - break; - } - - /* print out */ - if (strncmp(currtitle, title, TTLEN) != 0) - { - /* is title. */ - color = ""; - } - - prints("%6d %c %-6s%s%-15.14s%s%s %s%-*.*s%s\n", - num, type, datepart, - isonline ? ANSI_COLOR(1) : "", - ent->owner, - isonline ? ANSI_RESET : "", - mark, color, - t_columns - 34, t_columns - 34, - title, - *color ? ANSI_RESET : ""); -} - - -static int -mail_del(int ent, const fileheader_t * fhdr, const char *direct) -{ - char genbuf[200]; - - if (fhdr->filemode & FILE_MARKED) - return DONOTHING; - - if (currmode & MODE_SELECT) { - vmsg("請先回到正常模式後再進行刪除..."); - return READ_REDRAW; - } - - if (getans(msg_del_ny) == 'y') { - if (!delete_record(direct, sizeof(*fhdr), ent)) { - setupmailusage(); - setdirpath(genbuf, direct, fhdr->filename); -#ifdef USE_RECYCLE - RcyAddFile(fhdr, 0, genbuf); -#endif // USE_RECYCLE - unlink(genbuf); - loadmailusage(); - return DIRCHANGED; - } - } - return READ_REDRAW; -} - -int b_call_in(int ent, const fileheader_t * fhdr, const char *direct); - -static int -mail_read(int ent, fileheader_t * fhdr, const char *direct) -{ - char buf[PATHLEN]; - char done, delete_it, replied; - - clear(); - setdirpath(buf, direct, fhdr->filename); - strlcpy(currtitle, subject(fhdr->title), sizeof(currtitle)); - done = delete_it = replied = NA; - while (!done) { - int more_result = more(buf, YEA); - - /* whether success or not, update flag. - * or users may bug about "black-hole" mails - * and blinking notification */ - if( !(fhdr->filemode & FILE_READ)) - { - fhdr->filemode |= FILE_READ; - substitute_ref_record(direct, fhdr, ent); - } - switch (more_result) { - case -1: - /* no such file */ - clear(); - vmsg("此封信無內容。"); - return FULLUPDATE; - case RET_DOREPLY: - mail_reply(ent, fhdr, direct); - return FULLUPDATE; - case RET_DOREPLYALL: - multi_reply(ent, fhdr, direct); - return FULLUPDATE; - case RET_DORECOMMEND: // we don't accept this. - return FULLUPDATE; - case 0: - break; - default: - return more_result; - } - outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0); - - switch (igetch()) { - case 'r': - case 'R': - replied = YEA; - mail_reply(ent, fhdr, direct); - break; - case 'y': - multi_reply(ent, fhdr, direct); - break; - case 'x': - m_forward(ent, fhdr, direct); - break; - case 'd': - delete_it = YEA; - default: - done = YEA; - } - } - if (delete_it) - mail_del(ent, fhdr, direct); - else { - fhdr->filemode |= FILE_READ; - substitute_ref_record(direct, fhdr, ent); - } - return FULLUPDATE; -} - -static int -mail_read_all(int ent, fileheader_t * fhdr, const char *direct) -{ - off_t i = 0, num = 0; - int fd = 0; - fileheader_t xfhdr; - - currutmp->alerts &= ~ALERT_NEW_MAIL; - if ((fd = open(currmaildir, O_RDWR)) < 0) - return DONOTHING; - - if ((num = lseek(fd, 0, SEEK_END)) < 0) - num = 0; - num /= sizeof(fileheader_t); - - i = num - NEWMAIL_CHECK_RANGE; - if (i < 0) i = 0; - - if (lseek(fd, i * (off_t)sizeof(fileheader_t), SEEK_SET) < 0) - i = num; - - for (; i < num; i++) - { - if (read(fd, &xfhdr, sizeof(xfhdr)) <= 0) - break; - if (xfhdr.filemode & FILE_READ) - continue; - xfhdr.filemode |= FILE_READ; - if (lseek(fd, i * (off_t)sizeof(fileheader_t), SEEK_SET) < 0) - break; - write(fd, &xfhdr, sizeof(xfhdr)); - } - - close(fd); - return DIRCHANGED; -} - -static int -mail_unread(int ent, fileheader_t * fhdr, const char *direct) -{ - // this function may cause arguments, so please specify - // if you want this to be enabled. -#ifdef USE_USER_MAIL_UNREAD - if (fhdr && fhdr->filemode & FILE_READ) - { - fhdr->filemode &= ~FILE_READ; - substitute_record(direct, fhdr, ent); - return FULLUPDATE; - } -#endif // USE_USER_MAIL_UNREAD - return DONOTHING; -} - -/* in boards/mail 回信給原作者,轉信站亦可 */ -int -mail_reply(int ent, fileheader_t * fhdr, const char *direct) -{ - char uid[STRLEN]; - FILE *fp; - char genbuf[512]; - int oent = ent; - - if (!fhdr || !fhdr->filename[0]) - return DONOTHING; - - stand_title("回 信"); - - /* 判斷是 boards 或 mail */ - if (curredit & EDIT_MAIL) - setuserfile(quote_file, fhdr->filename); - else - setbfile(quote_file, currboard, fhdr->filename); - - /* find the author */ - strlcpy(quote_user, fhdr->owner, sizeof(quote_user)); - if (strchr(quote_user, '.')) { - char *t; - char *strtok_pos; - genbuf[0] = '\0'; - if ((fp = fopen(quote_file, "r"))) { - fgets(genbuf, sizeof(genbuf), fp); - fclose(fp); - } - t = strtok_r(genbuf, str_space, &strtok_pos); - if (t && (strcmp(t, str_author1)==0 || strcmp(t, str_author2)==0) - && (t=strtok_r(NULL, str_space, &strtok_pos)) != NULL) - strlcpy(uid, t, sizeof(uid)); - else { - vmsg("錯誤: 找不到作者。"); - quote_user[0]='\0'; - quote_file[0]='\0'; - return FULLUPDATE; - } - } else - strlcpy(uid, quote_user, sizeof(uid)); - - /* make the title */ - do_reply_title(3, fhdr->title); - prints("\n收信人: %s\n標 題: %s\n", uid, save_title); - - /* edit, then send the mail */ - ent = curredit; - switch (do_send(uid, save_title)) { - case -1: - outs(err_uid); - break; - case -2: - outs(msg_cancel); - break; - case -3: - prints("使用者 [%s] 無法收信", uid); - break; - - case 0: - /* success */ - if ( direct && /* for board, no direct */ - (curredit & EDIT_MAIL) && - !(fhdr->filemode & FILE_REPLIED)) - { - fhdr->filemode |= FILE_REPLIED; - substitute_ref_record(direct, fhdr, oent); - } - break; - } - curredit = ent; - pressanykey(); - quote_user[0]='\0'; - quote_file[0]='\0'; - if (strcasecmp(uid, cuser.userid) == 0) - return DIRCHANGED; - return FULLUPDATE; -} - -static int -mail_edit(int ent, fileheader_t * fhdr, const char *direct) -{ - char genbuf[200]; - - if (!HasUserPerm(PERM_SYSOP)) - return DONOTHING; - - setdirpath(genbuf, direct, fhdr->filename); - vedit(genbuf, NA, NULL); - return FULLUPDATE; -} - -static int -mail_nooutmail(int ent, fileheader_t * fhdr, const char *direct) -{ - cuser.uflag2 ^= REJ_OUTTAMAIL; - passwd_update(usernum, &cuser); - return FULLUPDATE; - -} - -static int -mail_mark(int ent, fileheader_t * fhdr, const char *direct) -{ - fhdr->filemode ^= FILE_MARKED; - - substitute_ref_record(direct, fhdr, ent); - return PART_REDRAW; -} - -/* help for mail reading */ -static const char * const mail_help[] = { - "\0電子信箱操作說明", - "\01基本命令", - "(p/↑)(n/↓) 前一篇/下一篇文章", - "(P)(PgUp) 前一頁", - "(N)(PgDn) 下一頁", - "(數字鍵) 跳到第 ## 筆", - "($) 跳到最後一筆", - "(r)(→) 讀信", - "(R)/(y) 回信 / 群組回信", - "\01進階命令", - "(TAB) 切換顯示模式(目前有一般及顯示大小)", - "(O) 關閉/開啟 站外信件轉入", - "(c)/(z) 此信件收入私人信件夾/進入私人信件夾", - "(x)/(X) 轉信給其它使用者/轉錄文章到其他看板", - "(F)/(u) 將信傳送回您的電子信箱/水球整理寄回信箱", - "(d) 殺掉此信", - "(D) 殺掉指定範圍的信", - "(m) 將信標記,以防被清除", - "(^G) 立即重建信箱 (信箱毀損時用)", - "(t) 標記欲刪除信件", - "(^D) 刪除已標記信件", - NULL -}; - -static int -m_help(void) -{ - show_help(mail_help); - return FULLUPDATE; -} - -static int -mail_cross_post(int ent, fileheader_t * fhdr, const char *direct) -{ - char xboard[20], fname[80], xfpath[80], xtitle[80], inputbuf[10]; - fileheader_t xfile; - FILE *xptr; - int author = 0; - char genbuf[200]; - char genbuf2[4]; - -#if 0 - // 除非有人明白為何要先 ChekPostPerm 並修復, - // 否則先 disable 這段 code - 目前常造成 crash。 - // - // XXX (will crash sometimes because currborad is not defined yet) - // 麻煩 in2 來修復這裡: 確認轉錄為何要先 CheckPostPerm - if (!currboard || currboard[0] == 0) - { - enter_board(DEFAULT_BOARD); - } - assert(0<=ent-1 && ent-1 1) - { - outs(ANSI_COLOR(1;31) - "請注意: 若過量重複轉錄將視為洗板,導致被開罰單停權。\n" ANSI_RESET - "若有特別需求請洽各板主,請他們幫你轉文。\n\n"); - } - move(1, 0); - CompleteBoard("轉錄本文章於看板:", xboard); - - if (*xboard == '\0' || !haspostperm(xboard)) - { - vmsg("無法轉錄"); - return FULLUPDATE; - } - - /* 借用變數 */ - ent = StringHash(fhdr->title); - /* 同樣 title 不管對哪個板都算 cross post , 所以不用檢查 author */ - - if ((ent != 0 && ent == postrecord.checksum[0])) { - /* 檢查 cross post 次數 */ - if (postrecord.times++ > MAX_CROSSNUM) - anticrosspost(); - } else { - postrecord.times = 0; - postrecord.last_bid = 0; - postrecord.checksum[0] = ent; - } - - ent = getbnum(xboard); - assert(0<=ent-1 && ent-1owner, cuser.userid)) { - getdata(2, 0, "(1)原文轉載 (2)舊轉錄格式?[1] ", - genbuf, 3, DOECHO); - if (genbuf[0] != '2') { - ent = 0; - getdata(2, 0, "保留原作者名稱嗎?[Y] ", inputbuf, 3, DOECHO); - if (inputbuf[0] != 'n' && inputbuf[0] != 'N') - author = 1; - } - } - if (ent) - snprintf(xtitle, sizeof(xtitle), "[轉錄]%.66s", fhdr->title); - else - strlcpy(xtitle, fhdr->title, sizeof(xtitle)); - - snprintf(genbuf, sizeof(genbuf), "採用原標題《%.60s》嗎?[Y] ", xtitle); - getdata(2, 0, genbuf, genbuf2, sizeof(genbuf2), LCECHO); - if (*genbuf2 == 'n') - if (getdata(2, 0, "標題:", genbuf, TTLEN, DOECHO)) - strlcpy(xtitle, genbuf, sizeof(xtitle)); - - getdata(2, 0, "(S)存檔 (L)站內 (Q)取消?[Q] ", genbuf, 3, LCECHO); - if (genbuf[0] == 'l' || genbuf[0] == 's') { - int currmode0 = currmode; - - currmode = 0; - setbpath(xfpath, xboard); - stampfile(xfpath, &xfile); - if (author) - strlcpy(xfile.owner, fhdr->owner, sizeof(xfile.owner)); - else - strlcpy(xfile.owner, cuser.userid, sizeof(xfile.owner)); - strlcpy(xfile.title, xtitle, sizeof(xfile.title)); - if (genbuf[0] == 'l') { - xfile.filemode = FILE_LOCAL; - } - setuserfile(fname, fhdr->filename); - { - const char *save_currboard; - xptr = fopen(xfpath, "w"); - assert(xptr); - - strlcpy(save_title, xfile.title, sizeof(save_title)); - save_currboard = currboard; - currboard = xboard; - write_header(xptr, save_title); - currboard = save_currboard; - - fprintf(xptr, "※ [本文轉錄自 %s 信箱]\n\n", cuser.userid); - - b_suckinfile(xptr, fname); - addsignature(xptr, 0); - fclose(xptr); - } - - setbdir(fname, xboard); - append_record(fname, &xfile, sizeof(xfile)); - setbtotal(getbnum(xboard)); - - if (!xfile.filemode) - outgo_post(&xfile, xboard, cuser.userid, cuser.nickname); -#ifdef USE_COOLDOWN - if (bcache[getbnum(xboard) - 1].brdattr & BRD_COOLDOWN) - add_cooldowntime(usernum, 5); - add_posttimes(usernum, 1); -#endif - - // cross-post does not add numpost. - outs("轉錄信件不增加文章數,敬請包涵。"); - - vmsg("文章轉錄完成"); - currmode = currmode0; - } - return FULLUPDATE; -} - -int -mail_man(void) -{ - char buf[PATHLEN], buf1[64]; - int mode0 = currutmp->mode; - int stat0 = currstat; - - // TODO if someday we put things in user man...? - sethomeman(buf, cuser.userid); - - // if user already has man directory or permission, - // allow entering mail-man folder. - - if (!dashd(buf) && !HasUserPerm(PERM_MAILLIMIT)) - return DONOTHING; - - snprintf(buf1, sizeof(buf1), "%s 的信件夾", cuser.userid); - a_menu(buf1, buf, HasUserPerm(PERM_MAILLIMIT) ? 1 : 0, 0, NULL); - currutmp->mode = mode0; - currstat = stat0; - return FULLUPDATE; -} - -// XXX BUG mail_cite 有可能會跳進 a_menu, 而 a_menu 會 check -// currbid。 一整個糟糕的邏輯錯誤... -static int -mail_cite(int ent, fileheader_t * fhdr, const char *direct) -{ - char fpath[PATHLEN]; - char title[TTLEN + 1]; - static char xboard[20] = ""; - char buf[20]; - int bid; - - setuserfile(fpath, fhdr->filename); - strlcpy(title, "◇ ", sizeof(title)); - strlcpy(title + 3, fhdr->title, sizeof(title) - 3); - a_copyitem(fpath, title, 0, 1); - - if (cuser.userlevel >= PERM_BM) { - move(2, 0); - clrtoeol(); - move(3, 0); - clrtoeol(); - move(1, 0); - - CompleteBoard( - HasUserPerm(PERM_MAILLIMIT) ? - "輸入看板名稱 (直接Enter進入私人信件夾):" : - "輸入看板名稱:", - buf); - if (*buf) - strlcpy(xboard, buf, sizeof(xboard)); - if (*xboard && ((bid = getbnum(xboard)) > 0)){ /* XXXbid */ - setapath(fpath, xboard); - setutmpmode(ANNOUNCE); - a_menu(xboard, fpath, - HasUserPerm(PERM_ALLBOARD) ? 2 : is_BM_cache(bid) ? 1 : 0, - bid, - NULL); - } else { - mail_man(); - } - return FULLUPDATE; - } else { - mail_man(); - return FULLUPDATE; - } -} - -static int -mail_save(int ent, fileheader_t * fhdr, const char *direct) -{ - char fpath[PATHLEN]; - char title[TTLEN + 1]; - - if (HasUserPerm(PERM_MAILLIMIT)) { - setuserfile(fpath, fhdr->filename); - strlcpy(title, "◇ ", sizeof(title)); - strlcpy(title + 3, fhdr->title, sizeof(title) - 3); - a_copyitem(fpath, title, fhdr->owner, 1); - sethomeman(fpath, cuser.userid); - a_menu(cuser.userid, fpath, 1, 0, NULL); - return FULLUPDATE; - } - return DONOTHING; -} - -#ifdef OUTJOBSPOOL -static int -mail_waterball(int ent, fileheader_t * fhdr, const char *direct) -{ - static char address[60] = "", cmode = 1; - char fname[500], genbuf[200]; - FILE *fp; - - if (!(strstr(fhdr->title, "熱線") && strstr(fhdr->title, "記錄"))) { - vmsg("必須是 熱線記錄 才能使用水球整理的唷!"); - return 1; - } - - if (!address[0]) - strlcpy(address, cuser.email, sizeof(address)); - - move(b_lines - 8, 0); clrtobot(); - outs(ANSI_COLOR(1;33;45) "★水球整理程式 " ANSI_RESET "\n" - "系統將會按照和不同人丟的水球各自獨立\n" - "於整點的時候 (尖峰時段除外) 將資料整理好寄送給您\n\n\n"); - - if (address[0]) { - snprintf(genbuf, sizeof(genbuf), "寄往 [%s] 嗎[Y/n/q]? ", address); - getdata(b_lines - 5, 0, genbuf, fname, 3, LCECHO); - if (fname[0] == 'q') { - outmsg("取消處理"); - return 1; - } - if (fname[0] == 'n') - address[0] = '\0'; - } - if (!address[0]) { - move(b_lines-4, 0); - prints( "請注意目前只支援寄往標準 e-mail 地址。\n" - "若想寄回此信箱請用輸入 %s.bbs@" MYHOSTNAME "\n", cuser.userid); - - getdata(b_lines - 5, 0, "請輸入郵件地址:", fname, 60, DOECHO); - if (fname[0] && strchr(fname, '.')) { - strlcpy(address, fname, sizeof(address)); - } else { - vmsg("地址格式不正確,取消處理"); - return 1; - } - } - trim(address); - if (invalidaddr(address)) - return -2; - move(b_lines-4, 0); clrtobot(); - - if( strstr(address, ".bbs") && REJECT_OUTTAMAIL ){ - outs("\n您必須要打開接受站外信, 水球整理系統才能寄入結果\n" - "請麻煩到【郵件選單】按大寫 O改成接受站外信 (在右上角)\n" - "再重新執行本功\能 :)\n"); - vmsg("請打開站外信, 再重新執行本功\能"); - return FULLUPDATE; - } - - //snprintf(fname, sizeof(fname), "%d\n", cmode); - outs("系統提供兩種模式: \n" - "模式 0: 精簡模式, 將不含顏色控制碼, 方便以純文字編輯器整理收藏\n" - "模式 1: 華麗模式, 包含顏色控制碼等, 方便在 bbs上直接編輯收藏\n"); - getdata(b_lines - 1, 0, "使用模式(0/1/Q)? [1]", fname, 3, LCECHO); - if (fname[0] == 'Q' || fname[0] == 'q') { - outmsg("取消處理"); - return FULLUPDATE; - } - cmode = (fname[0] != '0' && fname[0] != '1') ? 1 : fname[0] - '0'; - - snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.src.%s-%d", - cuser.userid, (int)now); - snprintf(genbuf, sizeof(genbuf), "cp " BBSHOME "/home/%c/%s/%s %s", - cuser.userid[0], cuser.userid, fhdr->filename, fname); - system(genbuf); - /* dirty code ;x */ - snprintf(fname, sizeof(fname), BBSHOME "/jobspool/water.des.%s-%d", - cuser.userid, (int)now); - fp = fopen(fname, "wt"); - assert(fp); - fprintf(fp, "%s\n%s\n%d\n", cuser.userid, address, cmode); - fclose(fp); - vmsg("設定完成, 系統將在下一個整點(尖峰時段除外)將資料寄給您"); - return FULLUPDATE; -} -#endif -static const onekey_t mail_comms[] = { - { 0, NULL }, // Ctrl('A') - { 0, NULL }, // Ctrl('B') - { 0, NULL }, // Ctrl('C') - { 0, NULL }, // Ctrl('D') - { 0, NULL }, // Ctrl('E') - { 0, NULL }, // Ctrl('F') - { 0, built_mail_index }, // Ctrl('G') - { 0, NULL }, // Ctrl('H') - { 0, toggle_showmail_mode }, // Ctrl('I') - { 0, NULL }, // Ctrl('J') - { 0, NULL }, // Ctrl('K') - { 0, NULL }, // Ctrl('L') - { 0, NULL }, // Ctrl('M') - { 0, NULL }, // Ctrl('N') - { 0, NULL }, // Ctrl('O') // DO NOT USE THIS KEY - UNIX not sending - { 0, NULL }, // Ctrl('P') - { 0, NULL }, // Ctrl('Q') - { 0, NULL }, // Ctrl('R') - { 0, NULL }, // Ctrl('S') - { 0, NULL }, // Ctrl('T') - { 0, NULL }, // Ctrl('U') - { 0, NULL }, // Ctrl('V') - { 0, NULL }, // Ctrl('W') - { 0, NULL }, // Ctrl('X') - { 0, NULL }, // Ctrl('Y') - { 0, NULL }, // Ctrl('Z') 26 - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, // 'A' 65 - { 0, NULL }, // 'B' - { 0, NULL }, // 'C' - { 1, del_range }, // 'D' - { 1, mail_edit }, // 'E' - { 0, NULL }, // 'F' - { 0, NULL }, // 'G' - { 0, NULL }, // 'H' - { 0, NULL }, // 'I' - { 0, NULL }, // 'J' - { 0, NULL }, // 'K' - { 0, NULL }, // 'L' - { 0, NULL }, // 'M' - { 0, NULL }, // 'N' - { 1, mail_nooutmail }, // 'O' - { 0, NULL }, // 'P' - { 0, NULL }, // 'Q' - { 1, mail_reply }, // 'R' - { 0, NULL }, // 'S' - { 1, edit_title }, // 'T' - { 0, NULL }, // 'U' - { 1, mail_unread }, // 'V' - { 0, NULL }, // 'W' - { 1, mail_cross_post }, // 'X' - { 0, NULL }, // 'Y' - { 0, NULL }, // 'Z' 90 - { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, { 0, NULL }, - { 0, NULL }, // 'a' 97 - { 0, NULL }, // 'b' - { 1, mail_cite }, // 'c' - { 1, mail_del }, // 'd' - { 0, NULL }, // 'e' - { 0, NULL }, // 'f' - { 0, NULL }, // 'g' - { 0, m_help }, // 'h' - { 0, NULL }, // 'i' - { 0, NULL }, // 'j' - { 0, NULL }, // 'k' - { 0, NULL }, // 'l' - { 1, mail_mark }, // 'm' - { 0, NULL }, // 'n' - { 0, NULL }, // 'o' - { 0, NULL }, // 'p' - { 0, NULL }, // 'q' - { 1, mail_read }, // 'r' - { 1, mail_save }, // 's' - { 0, NULL }, // 't' -#ifdef OUTJOBSPOOL - { 1, mail_waterball }, // 'u' -#else - { 0, NULL }, // 'u' -#endif - { 0, mail_read_all }, // 'v' - { 1, b_call_in }, // 'w' - { 1, m_forward }, // 'x' - { 1, multi_reply }, // 'y' - { 0, mail_man }, // 'z' 122 -}; - -int -m_read(void) -{ - int back_bid; - if (get_num_records(currmaildir, sizeof(fileheader_t))) { - curredit = EDIT_MAIL; - curredit &= ~EDIT_ITEM; - back_bid = currbid; - currbid = 0; - i_read(RMAIL, currmaildir, mailtitle, maildoent, mail_comms, -1); - currbid = back_bid; - curredit = 0; - setmailalert(); - return 0; - } else { - outs("您沒有來信"); - return XEASY; - } -} - -/* 寄站內信 */ -static int -send_inner_mail(const char *fpath, const char *title, const char *receiver) -{ - char fname[PATHLEN]; - fileheader_t mymail; - char rightid[IDLEN+1]; - - if (!searchuser(receiver, rightid)) - return -2; - - /* to avoid DDOS of disk */ - sethomedir(fname, rightid); - if (strcmp(rightid, cuser.userid) == 0) { - if (chk_mailbox_limit()) - return -4; - } - - sethomepath(fname, rightid); - stampfile(fname, &mymail); - if (!strcmp(rightid, cuser.userid)) { - /* Using BBSNAME may be too loooooong. */ - strlcpy(mymail.owner, "[站內]", sizeof(mymail.owner)); - mymail.filemode = FILE_READ; - } else - strlcpy(mymail.owner, cuser.userid, sizeof(mymail.owner)); - strlcpy(mymail.title, title, sizeof(mymail.title)); - unlink(fname); - Copy(fpath, fname); - sethomedir(fname, rightid); - append_record_forward(fname, &mymail, sizeof(mymail), rightid); - sendalert(receiver, ALERT_NEW_MAIL); - return 0; -} - -#include -#include -#include - -#ifndef USE_BSMTP -static int -bbs_sendmail(const char *fpath, const char *title, char *receiver) -{ - char *ptr; - char genbuf[256]; - FILE *fin, *fout; - - /* 中途攔截 */ - if ((ptr = strchr(receiver, ';'))) { - *ptr = '\0'; - } - if ((ptr = strstr(receiver, str_mail_address)) || !strchr(receiver, '@')) { - char hacker[20]; - int len; - - if (strchr(receiver, '@')) { - len = ptr - receiver; - memcpy(hacker, receiver, len); - hacker[len] = '\0'; - } else - strlcpy(hacker, receiver, sizeof(hacker)); - return send_inner_mail(fpath, title, hacker); - } - /* Running the sendmail */ - if (fpath == NULL) { - snprintf(genbuf, sizeof(genbuf), - "/usr/sbin/sendmail %s > /dev/null", receiver); - fin = fopen("etc/confirm", "r"); - } else { - snprintf(genbuf, sizeof(genbuf), - "/usr/sbin/sendmail -f %s%s %s > /dev/null", - cuser.userid, str_mail_address, receiver); - fin = fopen(fpath, "r"); - } - if (fin == NULL) - return -1; - fout = popen(genbuf, "w"); - if (fout == NULL) { - fclose(fin); - return -1; - } - - if (fpath) - fprintf(fout, "Reply-To: %s%s\nFrom: %s <%s%s>\n", - cuser.userid, str_mail_address, - cuser.nickname, - cuser.userid, str_mail_address); - fprintf(fout,"To: %s\nSubject: %s\n" - "Mime-Version: 1.0\r\n" - "Content-Type: text/plain; charset=\"big5\"\r\n" - "Content-Transfer-Encoding: 8bit\r\n" - "X-Disclaimer: " BBSNAME "對本信內容恕不負責。\n\n", - receiver, title); - - while (fgets(genbuf, sizeof(genbuf), fin)) { - if (genbuf[0] == '.' && genbuf[1] == '\n') - fputs(". \n", fout); - else - fputs(genbuf, fout); - } - fclose(fin); - fprintf(fout, ".\n"); - pclose(fout); - return 0; -} -#else /* USE_BSMTP */ - -int -bsmtp(const char *fpath, const char *title, const char *rcpt) -{ - char buf[80], *ptr; - time4_t chrono; - MailQueue mqueue; - - /* check if the mail is a inner mail */ - if ((ptr = strstr(rcpt, str_mail_address)) || !strchr(rcpt, '@')) { - char hacker[20]; - int len; - - if (strchr(rcpt, '@')) { - len = ptr - rcpt; - memcpy(hacker, rcpt, len); - hacker[len] = '\0'; - } else - strlcpy(hacker, rcpt, sizeof(hacker)); - return send_inner_mail(fpath, title, hacker); - } - chrono = now; - - /* stamp the queue file */ - strlcpy(buf, "out/", sizeof(buf)); - for (;;) { - snprintf(buf + 4, sizeof(buf) - 4, "M.%d.%d.A", (int)++chrono, getpid()); - if (!dashf(buf)) { - Copy(fpath, buf); - break; - } - } - - fpath = buf; - - /* setup mail queue */ - mqueue.mailtime = chrono; - // XXX (unused) mqueue.method = method; - strlcpy(mqueue.filepath, fpath, sizeof(mqueue.filepath)); - strlcpy(mqueue.subject, title, sizeof(mqueue.subject)); - strlcpy(mqueue.sender, cuser.userid, sizeof(mqueue.sender)); - strlcpy(mqueue.username, cuser.nickname, sizeof(mqueue.username)); - strlcpy(mqueue.rcpt, rcpt, sizeof(mqueue.rcpt)); - - if (append_record("out/" FN_DIR, (fileheader_t *) & mqueue, sizeof(mqueue)) < 0) - return 0; - return chrono; -} -#endif /* USE_BSMTP */ - -int -doforward(const char *direct, const fileheader_t * fh, int mode) -{ - static char address[STRLEN] = ""; - char fname[PATHLEN]; - char genbuf[PATHLEN]; - int return_no; - - if (!address[0] && strcmp(cuser.email, "x") != 0) - strlcpy(address, cuser.email, sizeof(address)); - - if( mode == 'U' ){ - vmsg("將進行 uuencode 。若您不清楚什麼是 uuencode 請改用 F轉寄。"); - } - trim(address); - - // if user has address and not the default 'x' (no-email)... - if (address[0]) { - snprintf(genbuf, sizeof(genbuf), - "確定轉寄給 [%s] 嗎(Y/N/Q)?[Y] ", address); - getdata(b_lines, 0, genbuf, fname, 3, LCECHO); - - if (fname[0] == 'q') { - outmsg("取消轉寄"); - return 1; - } - if (fname[0] == 'n') - address[0] = '\0'; - } - if (!address[0]) { - do { - getdata(b_lines - 1, 0, "請輸入轉寄地址:", fname, 60, DOECHO); - if (fname[0]) { - if (strchr(fname, '.')) - strlcpy(address, fname, sizeof(address)); - else - snprintf(address, sizeof(address), - "%s.bbs@%s", fname, MYHOSTNAME); - } else { - vmsg("取消轉寄"); - return 1; - } - } while (mode == 'Z' && strstr(address, MYHOSTNAME)); - } - /* according to our experiment, many users leave blanks */ - trim(address); - if (invalidaddr(address)) - return -2; - - outmsg("正轉寄請稍候..."); - refresh(); - - /* 追蹤使用者 */ - if (HasUserPerm(PERM_LOGUSER)) - log_user("mailforward to %s ",address); - if (mode == 'Z') { - snprintf(fname, sizeof(fname), - TAR_PATH " cfz /tmp/home.%s.tgz home/%c/%s; " - MUTT_PATH " -a /tmp/home.%s.tgz -s 'home.%s.tgz' '%s' %s", - cuser.userid[0], cuser.userid, cuser.userid, direct); - system(fname); - strlcpy(fname, direct, sizeof(fname)); - } else if (mode == 'U') { - char tmp_buf[128]; - - snprintf(fname, sizeof(fname), "/tmp/bbs.uu%05d", (int)currpid); - snprintf(tmp_buf, sizeof(tmp_buf), - "/usr/bin/uuencode %s/%s uu.%05d > %s", - direct, fh->filename, (int)currpid, fname); - system(tmp_buf); - } else if (mode == 'F') { - char tmp_buf[128]; - - snprintf(fname, sizeof(fname), "/tmp/bbs.f%05d", (int)currpid); - snprintf(tmp_buf, sizeof(tmp_buf), "%s/%s", direct, fh->filename); - Copy(tmp_buf, fname); - } else - return -1; - - return_no = -#ifndef USE_BSMTP - bbs_sendmail(fname, fh->title, address); -#else - bsmtp(fname, fh->title, address); -#endif - unlink(fname); - return (return_no); -} - -int -load_mailalert(const char *userid) -{ - struct stat st; - char maildir[MAXPATHLEN]; - int fd; - register int num; - fileheader_t my_mail; - - sethomedir(maildir, userid); - if (!HasUserPerm(PERM_BASIC)) - return 0; - if (stat(maildir, &st) < 0) - return 0; - num = st.st_size / sizeof(fileheader_t); - if (num <= 0) - return 0; - if (num > NEWMAIL_CHECK_RANGE) - num = NEWMAIL_CHECK_RANGE; - - /* 看看有沒有信件還沒讀過?從檔尾回頭檢查,效率較高 */ - if ((fd = open(maildir, O_RDONLY)) > 0) { - lseek(fd, st.st_size - sizeof(fileheader_t), SEEK_SET); - while (num--) { - read(fd, &my_mail, sizeof(fileheader_t)); - if (!(my_mail.filemode & FILE_READ)) { - close(fd); - return ALERT_NEW_MAIL; - } - lseek(fd, -(off_t) 2 * sizeof(fileheader_t), SEEK_CUR); - } - close(fd); - } - return 0; -} diff --git a/mbbsd/mbbsd.c b/mbbsd/mbbsd.c deleted file mode 100644 index 0c561ed1..00000000 --- a/mbbsd/mbbsd.c +++ /dev/null @@ -1,1832 +0,0 @@ -/* $Id$ */ -#include "bbs.h" -#include "banip.h" - -#ifdef __linux__ -# ifdef CRITICAL_MEMORY -# include -# endif -# ifdef DEBUG -# include -# 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 - -////////////////////////////////////////////////////////////////// -// Site Optimization -// override these macro if you need more optimization, -// based on OS/lib/package... -#ifndef OPTIMIZE_LISTEN_SOCKET -#define OPTIMIZE_LISTEN_SOCKET(sock,sz) -#endif - -#ifndef XAUTH_HOST -#define XAUTH_HOST(x) x -#endif - -#ifndef XAUTH_GETREMOTENAME -#define XAUTH_GETREMOTENAME(x) x -#endif - -#ifndef XAUTH_TRYREMOTENAME -#define XAUTH_TRYREMOTENAME() -#endif - -#if 0 -static jmp_buf byebye; -#endif - -static char remoteusername[40] = "?"; -static unsigned char enter_uflag; -static int use_shell_login_mode = 0; -static int listen_port = 23; - -#ifdef DETECT_CLIENT -Fnv32_t client_code=FNV1_32_INIT; - -void UpdateClientCode(unsigned char c) -{ - FNV1A_CHAR(c, client_code); -} -#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_filef(FN_USIES, LOG_CREAT, - "%s %s %-12s Stay:%d (%s)\n", - Cdate(&now), mode, cuser.userid , - (int)(now - login_start_time) / 60, cuser.nickname); - else - log_filef(FN_USIES, LOG_CREAT, - "%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程式異常, 立刻斷線. \r\n" \ - "請洽 " GLOBAL_BUGREPORT " 板詳述問題發生經過。\r\n" - -#define XCPU_MSG ANSI_COLOR(0) \ - "\r\n程式耗用過多計算資源, 立刻斷線。\r\n" \ - "可能是 (a)執行太多耗用資源的動作 或 (b)程式掉入無窮迴圈. "\ - "請洽 " GLOBAL_BUGREPORT " 板詳述問題發生經過。\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_filef("log/crash.log", LOG_CREAT, - "%d %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(DEBUGSLEEP_SECONDS); -#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]; - - syncnow(); - 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; - scr_dump(&old_screen); - talkreply(); - currutmp->mode = mode0; - currutmp->chatid[0] = c0; - scr_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 - syncnow(); - 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] ", - 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, SHOWFILE_ALLOW_ALL); -#endif - // XXX why output("1", 1); here? - // this output has been here since rev 1... - // 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 - - if (getdata(20, 0, "請輸入代號,或以[guest]參觀,以[new]註冊: ", - uid, sizeof(uid), DOECHO) < 1) - { - // got nothing - outs("請重新輸入。\n"); - continue; - } - -#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; - redrawwin(); - } - else if (uid[0] && uid[len - 1] == ',') { - set_converting_type(CONV_UTF8); - uid[len - 1] = 0; - redrawwin(); - } - else if (len >= IDLEN + 1) - uid[IDLEN] = 0; -#endif - - if (strcasecmp(uid, str_new) == 0) { -#ifdef LOGINASNEW - new_register(); - mkuserdir(cuser.userid); - reginit_fav(); - 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; - cuser.uflag2= 0; // we don't need FAVNEW_FLAG or anything else. - -#ifdef GUEST_DEFAULT_DBCS_NOINTRESC - cuser.uflag |= DBCS_NOINTRESC; -#endif - // can we prevent mkuserdir() here? - 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; -#endif - } - /* 早該有 home 了, 不知道為何有的帳號會沒有, 被砍掉了? */ - mkuserdir(cuser.userid); - 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] ") == '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); - if (enter_board("WhoAmI")==0) { - 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(); - - 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); - redrawwin(); - - /* 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(); - resolve_over18(); - - 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 { - // XXX no userlevel, no guest - what is this? - // clear(); - // outs("此帳號停權中"); - // pressanykey(); - // exit(1); - - 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 - XAUTH_HOST(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)); - - OPTIMIZE_LISTEN_SOCKET(sock, 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; -} - -static int -daemon_login(int argc, char *argv[], char *envp[]) -{ - int msock, csock; /* socket for Master and Child */ - FILE *fp; - 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; - } - - XAUTH_TRYREMOTENAME(); - - 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); - - XAUTH_GETREMOTENAME(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 = NULL; - - 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; -} - -/* vim: sw=4 - */ diff --git a/mbbsd/menu.c b/mbbsd/menu.c deleted file mode 100644 index 6aaf3399..00000000 --- a/mbbsd/menu.c +++ /dev/null @@ -1,718 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#define CheckMenuPerm(x) \ - ( (x == MENU_UNREGONLY)? \ - ((cuser.userlevel == 0 ||HasUserPerm(PERM_LOGINOK))?0:1) :\ - ((x) ? HasUserPerm(x) : 1)) - -/* help & menu processring */ -static int refscreen = NA; -extern char *boardprefix; -extern struct utmpfile_t *utmpshm; - -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, rlen, mlen, mpos = 0; - int pos = 0; - int tail_type; - const char *mid_attr = ANSI_COLOR(33); - int is_currboard_special = 0; - char buf[64]; - - - /* prepare mid */ -#ifdef DEBUG - { - sprintf(buf, " current pid: %6d ", getpid()); - mid = buf; - mid_attr = ANSI_COLOR(41;5); - } -#else - if (ISNEWMAIL(currutmp)) { - mid = " 你有新信件 "; - mid_attr = ANSI_COLOR(41;5); - } 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; - } - } -#endif - - /* prepare tail */ - if (currmode & MODE_SELECT) - tail_type = TITLE_TAIL_SELECT; - else if (currmode & MODE_DIGEST) - tail_type = TITLE_TAIL_DIGEST; - else - tail_type = TITLE_TAIL_BOARD; - - if(currbid > 0) - { - assert(0<=currbid-1 && currbid-1brdattr & BRD_HIDE) && - (getbcache(currbid)->brdattr & BRD_POSTMASK)); - } - - /* now, calculate real positioning info */ - llen = strlen(title); - mlen = strlen(mid); - mpos = (t_columns -1 - mlen)/2; - - /* first, print left. */ - clear(); - outs(TITLE_COLOR "【"); - outs(title); - outs("】"); - pos = llen + 4; - - /* print mid */ - while(pos++ < mpos) - outc(' '); - 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 (is_currboard_special) - 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); // 3 = ANSI_COLOR, 25 = stuff inside - sprintf(mystatus, "[扣機]" ANSI_COLOR(31) "%s ", - msgs[currutmp->pager]); - outslr("", i, mystatus, strlen(msgs[currutmp->pager]) + 7); - outs(ANSI_RESET); -} - -/* - * current caller 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); - clrtoln(1 + FILMROW); /* 清掉上次的 */ - out_lines(SHM->notes[i], 11); /* 只印11行就好 */ - outs(reset_color); - } - show_status(); - refresh(); -} - -typedef struct { - int (*cmdfunc)(); - int level; - char *desc; /* next/key/description */ -} commands_t; - -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, M_XMAX -}; - -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; - - moviemode = cmdmode; - assert(cmdmode < M_XMAX); - cmdmode = mode_map[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]; - } - 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|PERM_BOARD, "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 進站水球"}, - {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 站內寄信"}, - {mail_list, PERM_LOGINOK, "RMail List 群組寄信"}, - {x_love, PERM_LOGINOK, "PPaper 情書產生器"}, - {setforward, PERM_LOGINOK, "FForward " ANSI_COLOR(1;32) - "設定信箱自動轉寄" ANSI_RESET}, - {m_sysop, 0, "YYes, sir! 寫信給站長"}, - {m_internet, PERM_INTERNET, "RInternet 寄信到站外"}, - {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 查詢寵物"}, - // PERM_PAGE - 水球都要 PERM_LOGIN 了 - // 沒道理可以 talk 不能水球。 - {t_talk, PERM_LOGINOK, "TTalk 找人聊聊"}, - // PERM_CHAT 非 login 也有,會有人用此吵別人。 - {t_chat, PERM_LOGINOK, "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 上站通知名單"}, - {t_fix_aloha,PERM_LOGINOK,"XXFixALOHA 修正上站通知"}, - {t_special,PERM_LOGINOK, "SSpecial 其他特別名單"}, - {NULL, 0, NULL} -}; - -void Customize(); // user.c -int u_customize() -{ - Customize(); - return 0; -} - -int u_fixgoodpost(void); // assess.c -/* User menu */ -static const commands_t userlist[] = { - {u_customize, PERM_BASIC, "UUCustomize 個人化設定"}, - {u_info, PERM_LOGINOK, "IInfo 設定個人資料與密碼"}, - {calendar, PERM_LOGINOK, "CCalendar 個人行事曆"}, - {u_loginview, PERM_BASIC, "LLogin View 選擇進站畫面"}, - {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, MENU_UNREGONLY, "RRegister 填寫《註冊申請單》"}, -#ifdef ASSESS - {u_cancelbadpost, PERM_LOGINOK, "BBye BadPost 申請刪除劣文"}, - {u_fixgoodpost, PERM_LOGINOK, "FFix GoodPost 修復優文"}, -#endif // ASSESS - {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} -}; - -static 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, PERM_BASIC, "UUser 【 個人設定區 】"}, - {Xyz, 0, "XXyz 【 系統資訊區 】"}, - {Play_Play, PERM_LOGINOK, "PPlay 【 娛樂與休閒 】"}, - {Name_Menu, PERM_LOGINOK, "NNamelist 【 編特別名單 】"}, -#ifdef DEBUG - {Goodbye, 0, "GGoodbye 再見再見再見再見"}, -#else - {Goodbye, 0, "GGoodbye 離開,再見… "}, -#endif - {NULL, 0, NULL} -}; - -int main_menu(void) { - domenu(M_MMENU, "主功\能表", (ISNEWMAIL(currutmp) ? 'M' : 'C'), cmdlist); - return 0; -} - -static int p_money() { - domenu(M_PSALE, BBSMNAME2 "量販店", '0', moneylist); - return 0; -}; - -// static int forsearch(); -static int playground(); -static int chessroom(); - -/* Ptt Play menu */ -static const commands_t playlist[] = { - {note, PERM_LOGINOK, "NNote 【 刻刻流言板 】"}, - /* // useless. - {forsearch,PERM_LOGINOK, "SSearchEngine【" ANSI_COLOR(1;35) " " - BBSMNAME2 "搜尋器 " ANSI_RESET "】"}, - */ - {topsong,PERM_LOGINOK, "TTop Songs 【" ANSI_COLOR(1;32) " 點歌排行榜 " ANSI_RESET "】"}, - {p_money,PERM_LOGINOK, "PPay 【" ANSI_COLOR(1;31) " " - BBSMNAME2 "量販店 " ANSI_RESET "】"}, - {chicken_main,PERM_LOGINOK, "CChicken " - "【" ANSI_COLOR(1;34) " " BBSMNAME2 "養雞場 " ANSI_RESET "】"}, - {playground,PERM_LOGINOK, "AAmusement 【" ANSI_COLOR(1;33) " " - BBSMNAME2 "遊樂場 " ANSI_RESET "】"}, - {chessroom, PERM_LOGINOK, "BBChess 【" ANSI_COLOR(1;34) " " - BBSMNAME2 "棋院 " 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, BBSMNAME2 "棋院", '1', chesslist); - return 0; -} - -static const commands_t plist[] = { - -/* {p_ticket_main, PERM_LOGINOK,"00Pre 【 總統機 】"}, - {alive, PERM_LOGINOK, "00Alive 【 訂票雞 】"}, -*/ - {ticket_main, PERM_LOGINOK, "11Gamble 【 " BBSMNAME2 "賭場 】"}, - {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, BBSMNAME2 "遊樂場",'1',plist); - return 0; -} - -static const commands_t slist[] = { - /* - // x_dict: useless - {x_dict,0, "11Dictionary " - "【" ANSI_COLOR(1;33) " 趣味大字典 " ANSI_RESET "】"}, - */ - {x_mrtmap, 0, "22MRTmap " - "【" ANSI_COLOR(1;34) " 捷運地圖 " ANSI_RESET "】"}, - {NULL, 0, NULL} -}; - -/* // nothing to search... -static int forsearch() { - domenu(M_SREG, BBSMNAME2 "搜尋器", '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, "個人設定", 'U', 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/mbbsd/merge.c b/mbbsd/merge.c deleted file mode 100644 index 913f94f5..00000000 --- a/mbbsd/merge.c +++ /dev/null @@ -1,237 +0,0 @@ -/* $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 換算成 " MONEYNAME " 幣為 %9d (匯率 22:1), \n" - " 沙灘貝殼有 %10d 換算為 " MONEYNAME " 幣為 %9d (匯率 222105:1), \n" - " 原有 %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(GLOBAL_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/mbbsd/more.c b/mbbsd/more.c deleted file mode 100644 index d506754a..00000000 --- a/mbbsd/more.c +++ /dev/null @@ -1,77 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -/* use new pager: piaip's more. */ -int more(char *fpath, int promptend) -{ - int r = pmore(fpath, promptend); - - switch(r) - { - - case RET_DOSYSOPEDIT: - r = FULLUPDATE; - - if (!HasUserPerm(PERM_SYSOP) || - strcmp(fpath, "etc/ve.hlp") == 0) - break; - -#ifdef GLOBAL_SECURITY - if (strcmp(currboard, GLOBAL_SECURITY) == 0) - break; -#endif // GLOBAL_SECURITY - - log_filef("log/security", LOG_CREAT, - "%u %24.24s %d %s admin edit file=%s\n", - (int)now, ctime4(&now), getpid(), cuser.userid, fpath); - - // no need to allow anything... - // at least, no need to change title. - vedit2(fpath, NA, NULL, 0); - break; - - case RET_SELECTBRD: - r = FULLUPDATE; - if (HasUserPerm(PERM_BASIC)) - { - if (currstat == READING) - return Select(); - } - break; - - case RET_COPY2TMP: - r = FULLUPDATE; - if (HasUserPerm(PERM_BASIC)) - { - char buf[PATHLEN]; - getdata(b_lines - 1, 0, "把這篇文章收入到暫存檔?[y/N] ", - buf, 4, LCECHO); - if (buf[0] != 'y') - break; - setuserfile(buf, ask_tmpbuf(b_lines - 1)); - Copy(fpath, buf); - } - break; - - case RET_DOCHESSREPLAY: - r = FULLUPDATE; - if (HasUserPerm(PERM_BASIC)) - { - ChessReplayGame(fpath); - } - break; - -#if defined(USE_BBSLUA) - case RET_DOBBSLUA: - r = FULLUPDATE; - if (HasUserPerm(PERM_BASIC)) - { - bbslua(fpath); - } - break; -#endif - } - - return r; -} - diff --git a/mbbsd/name.c b/mbbsd/name.c deleted file mode 100644 index 4e9d7134..00000000 --- a/mbbsd/name.c +++ /dev/null @@ -1,1067 +0,0 @@ -/* $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); -} - -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 && idxsize); - return self->base[idx]; -} - -static int NameList_MaxLen(const struct NameList *list, int offset, int count) -{ - int i; - int maxlen = 0; - - for(i=offset; isize; 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; isize; 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; isize; 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; isize; 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; isize; 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, scrx; - 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); - scrx = origx = x; - data[count] = 0; - - while (1) - { - // print input field again - move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); - outs(ANSI_COLOR(7)); - prints("%-*s", IDLEN + 1, data); - outs(ANSI_RESET); - move(y, scrx + count); - - // get input - if ((ch = igetch()) == EOF) - break; - - 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); - count = strlen(data); - temp = data + count; - 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); - } - continue; - } - if (ch == '\177' || ch == '\010') { - if (temp == data) - continue; - temp--; - count--; - *temp = '\0'; - ClearSubList(cwlist); - cwlist = GetSubList(data, toplev); - morelist = NULL; - 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; - } - } - 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(y, origx); - outs(data); - outc('\n'); - } -} - -void -namecomplete2(struct NameList *namelist, const char *prompt, char *data) -{ - char *temp; - int x, y, origx, scrx; - 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); - scrx = origx = x; - data[count] = 0; - viewoffset = 0; - - while (1) - { - // print input field - move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); - outs(ANSI_COLOR(7)); - prints("%-*s", IDLEN + 1, data); - outs(ANSI_RESET); - move(y, scrx + count); - - // get input - if ((ch = igetch()) == EOF) - break; - - 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)); - count = strlen(data); - temp = data + count; - 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); - } - continue; - } - if (ch == '\177' || ch == '\010') { - if (temp == data) - continue; - temp--; - count--; - *temp = '\0'; - NameList_sublist(namelist, &sublist, data); - viewoffset = 0; - 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; - } - } - 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(y, origx); - outs(data); - outc('\n'); - } -} - -void -usercomplete(const char *prompt, char *data) -{ - char *temp; - char *cwbuf, *cwlist; - int cwnum, x, y, origx, scrx; - 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); - scrx = origx = x; - data[count] = 0; - - while (1) - { - // print input field again - move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); - outs(ANSI_COLOR(7)); - prints("%-*s", IDLEN + 1, data); - outs(ANSI_RESET); - move(y, scrx + count); - - // get input - if ((ch = igetch()) == EOF) - break; - - 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; - 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; - 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); - count = strlen(data); - temp = data + count; - 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; - - 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(y, 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, scrx, ch, i, morelist = -1, col, ret = -1; - int start, end, ptr; - int clearbot = NA; - - outs(prompt); - clrtoeol(); - getyx(&y, &x); - scrx = origx = x; - - ptr = 0; - data[ptr] = 0; - - start = 0; end = nmemb - 1; - while (1) - { - // print input field again - move(y, scrx); outc(' '); clrtoeol(); move(y, scrx); - outs(ANSI_COLOR(7)); - // data[ptr] = 0; - prints("%-*s", len, data); - outs(ANSI_RESET); - move(y, scrx + ptr); - - // get input - if ((ch = igetch()) == EOF) - break; - - 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) { - ptr = strlen(data); - 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 */ - ptr = i; - } - } - morelist = start; - } else if (morelist > end) - continue; - clearbot = YEA; - move(2, 0); - clrtobot(); - printdash("相關資訊一覽表", 0); - - col = 0; - while (len + col < t_columns-1) { - 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); - } - continue; - - } else if (ch == '\177' || ch == '\010') { /* backspace */ - if (ptr == 0) - continue; - morelist = -1; - --ptr; - data[ptr] = 0; - 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; - } - } - } - - outc('\n'); - if (clearbot) { - move(2, 0); - clrtobot(); - } - if (*data) { - move(y, 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)); -} - -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/mbbsd/osdep.c b/mbbsd/osdep.c deleted file mode 100644 index f4a9bd85..00000000 --- a/mbbsd/osdep.c +++ /dev/null @@ -1,643 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#ifdef NEED_STRLCAT - -#include -#include - -/* size_t - * strlcat(char *dst, const char *src, size_t size); - */ -/* - * Copyright (c) 1998 Todd C. Miller - * 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 -#include - -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 - * 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 -#include -#include -#include -#include - -/* - * 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 - -/* - * 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 -#include - - -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 - - -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 -#include - -#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/mbbsd/othello.c b/mbbsd/othello.c deleted file mode 100644 index 99dd4794..00000000 --- a/mbbsd/othello.c +++ /dev/null @@ -1,577 +0,0 @@ -/* $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("[0] 離開\n"); - outs("(1) CD-65\n");/* 想 1 步 */ - outs("(2) 嬰兒\n"); /* 想 3 步 */ - outs("(3) 小孩\n"); /* 想 4 步 */ - do { - if (getdata(4, 0, "請選擇一個對象和您對打:(1~3)", - thinkstep, sizeof(thinkstep), LCECHO) == 0 || - thinkstep[0] == '0') - return 0; - - } 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(); - if (!od->think) - { - unlockutmpmode(); - free(od); - return 0; - } - 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/mbbsd/passwd.c b/mbbsd/passwd.c deleted file mode 100644 index d0dd4015..00000000 --- a/mbbsd/passwd.c +++ /dev/null @@ -1,171 +0,0 @@ -/* $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 -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; -} - -void -passwd_force_update(int flag) -{ - if(!currutmp || (currutmp->alerts & ALERT_PWD) == 0) - return; - currutmp->alerts &= ~flag; -} - -int -passwd_update(int num, userec_t * buf) -{ - int pwdfd; - if (num < 1 || num > MAX_USERS) - return -1; - buf->money = moneyof(num); - if(usernum == num && currutmp && ((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_GOODPOST) - cuser.goodpost = buf->goodpost = u.goodpost; - if(pwdfd & ALERT_PWD_PERM) - cuser.userlevel = buf->userlevel = u.userlevel; - if(pwdfd & ALERT_PWD_JUSTIFY) - { - memcpy(buf->justify, u.justify, sizeof(u.justify)); - memcpy(cuser.justify, u.justify, sizeof(u.justify)); - memcpy(buf->email, u.email, sizeof(u.email)); - memcpy(cuser.email, u.email, sizeof(u.email)); - } - 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(void *ctx, int (*fptr) (void *ctx, int, userec_t *)) -{ - int i; - userec_t user; - for (i = 0; i < MAX_USERS; i++) { - passwd_query(i + 1, &user); - if ((*fptr) (ctx, i, &user) < 0) - return -1; - } - 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/mbbsd/pfterm.c b/mbbsd/pfterm.c deleted file mode 100644 index 65a09a62..00000000 --- a/mbbsd/pfterm.c +++ /dev/null @@ -1,2502 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// pfterm environment settings -////////////////////////////////////////////////////////////////////////// -#ifdef _PFTERM_TEST_MAIN - -#define USE_PFTERM -#define EXP_PFTERM -#define DBCSAWARE -#define FT_DBCS_NOINTRESC 1 -#define DBG_TEXT_FD - -#include -#include -#include -#include -#include - -#else - -#define HAVE_GRAYOUT -#include "bbs.h" - -#ifdef DBCS_NOINTRESC -// # ifdef CONVERT -// extern int bbs_convert_type; -// # define FT_DBCS_NOINTRESC ( -// (cuser.uflag & DBCS_NOINTRESC) || -// (bbs_convert_type == CONV_UTF8)) -// # else -# define FT_DBCS_NOINTRESC (cuser.uflag & DBCS_NOINTRESC) -// # endif -#else -# define FT_DBCS_NOINTRESC 0 -#endif - -#endif - -////////////////////////////////////////////////////////////////////////// -// pfterm debug settings -////////////////////////////////////////////////////////////////////////// - -// #define DBG_SHOW_DIRTY -// #define DBG_SHOW_REPRINT // will not work if you enable SHOW_DIRTY. -// #define DBG_DISABLE_OPTMOVE -// #define DBG_DISABLE_REPRINT - -////////////////////////////////////////////////////////////////////////// -// pmore style ansi -// #include "ansi.h" -////////////////////////////////////////////////////////////////////////// - -#ifndef PMORE_STYLE_ANSI -#define ESC_CHR '\x1b' -#define ESC_STR "\x1b" -#define ANSI_COLOR(x) ESC_STR "[" #x "m" -#define ANSI_RESET ESC_STR "[m" -#endif // PMORE_STYLE_ANSI - -#ifndef ANSI_IS_PARAM -#define ANSI_IS_PARAM(c) (c == ';' || (c >= '0' && c <= '9')) -#endif // ANSI_IS_PARAM - -////////////////////////////////////////////////////////////////////////// -// grayout advanced control -// #include "grayout.h" -////////////////////////////////////////////////////////////////////////// -#ifndef GRAYOUT_DARK -#define GRAYOUT_COLORBOLD (-2) -#define GRAYOUT_BOLD (-1) -#define GRAYOUT_DARK (0) -#define GRAYOUT_NORM (1) -#define GRAYOUT_COLORNORM (+2) -#endif // GRAYOUT_DARK - -////////////////////////////////////////////////////////////////////////// -// Typeahead -////////////////////////////////////////////////////////////////////////// -#ifndef TYPEAHEAD_NONE -#define TYPEAHEAD_NONE (-1) -#define TYPEAHEAD_STDIN (0) -#endif // TYPEAHEAD - -////////////////////////////////////////////////////////////////////////// -// pfterm: piaip's flat terminal system, a new replacement for 'screen'. -// pfterm can also be read as "Perfect Term" -// -// piaip's new implementation of terminal system (term/screen) with dirty -// map, designed and optimized for ANSI based output. -// -// "pfterm" is "piaip's flat terminal" or "perfect term", not "PTT's term"!!! -// pfterm is designed for general maple-family BBS systems, not -// specific to any branch. -// -// Author: Hung-Te Lin (piaip), Dec. 2007. -// -// Copyright(c) 2007-2008 Hung-Te Lin -// All Rights Reserved. -// You are free to use, modify, redistribute this program in any -// non-commercial usage (including network service). -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// MAJOR IMPROVEMENTS: -// - Interpret ANSI code and maintain a virtual terminal -// - Dual buffer for dirty map and optimized output -// -// TODO AND DONE: -// - DBCS aware and prevent interrupting DBCS [done] -// - optimization with relative movement [done] -// - optimization with ENTER/clrtoeol [done] -// - ncurses-like API [done] -// - inansistr to output escaped string [done] -// - handle incomplete DBCS characters [done] -// - optimization with reprint ability [done] -// - add auto wrap control -// -// DEPRECATED: -// - standout() [rework getdata() and namecomplete()] -// - talk.c (big_picture) [rework talk.c] -// -////////////////////////////////////////////////////////////////////////// -// Reference: -// http://en.wikipedia.org/wiki/ANSI_escape_code -// http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf -////////////////////////////////////////////////////////////////////////// - -// Experimental now -#if defined(EXP_PFTERM) || defined(USE_PFTERM) - -////////////////////////////////////////////////////////////////////////// -// pfterm Configurations -////////////////////////////////////////////////////////////////////////// - -// Prevent invalid DBCS characters -#define FTCONF_PREVENT_INVALID_DBCS - -// Some terminals use current attribute to erase background -#define FTCONF_CLEAR_SETATTR - -// Some terminals (NetTerm, PacketSite) have bug in bolding attribute. -#define FTCONF_WORKAROUND_BOLD - -// Some terminals prefer VT100 style scrolling, including Win/DOS telnet -#undef FTCONF_USE_ANSI_SCROLL -#undef FTCONF_USE_VT100_SCROLL - -// Few poor terminals do not have relative move (ABCD). -#undef FTCONF_USE_ANSI_RELMOVE - -// Handling ANSI commands with 2 parameters (ex, ESC[m;nH) -// 2: Good terminals can accept any omit format (ESC[;nH) -// 1: Poor terminals (eg, Win/DOS telnet) can only omit 2nd (ESC[mH) -// 0: Very few poor terminals (eg, CrazyTerm/BBMan) cannot omit any parameters -#define FTCONF_ANSICMD2_OMIT (0) - -////////////////////////////////////////////////////////////////////////// -// Flat Terminal Definition -////////////////////////////////////////////////////////////////////////// - -#define FTSZ_DEFAULT_ROW (24) -#define FTSZ_DEFAULT_COL (80) -#define FTSZ_MIN_ROW (24) -#define FTSZ_MAX_ROW (100) -#define FTSZ_MIN_COL (80) -#define FTSZ_MAX_COL (320) - -#define FTCHAR_ERASE (' ') -#define FTATTR_ERASE (0x07) -#define FTCHAR_BLANK (' ') -#define FTATTR_DEFAULT (FTATTR_ERASE) -#define FTCHAR_INVALID_DBCS ('?') -// #define FTATTR_TRANSPARENT (0x80) - -#define FTDIRTY_CHAR (0x01) -#define FTDIRTY_ATTR (0x02) -#define FTDIRTY_DBCS (0x04) -#define FTDIRTY_INVALID_DBCS (0x08) -#define FTDIRTY_RAWMOVE (0x10) - -#define FTDBCS_SAFE (0) // standard DBCS -#define FTDBCS_UNSAFE (1) // not on all systems (better do rawmove) -#define FTDBCS_INVALID (2) // invalid - -#define FTCMD_MAXLEN (63) // max escape command sequence length -#define FTATTR_MINCMD (16) - -#ifndef FTCONF_USE_ANSI_RELMOVE -# define FTMV_COST (8) // always ESC[m;nH will costs avg 8 bytes -#else -# define FTMV_COST (5) // ESC[ABCD with ESC[m;nH costs avg 4+ bytes -#endif - -////////////////////////////////////////////////////////////////////////// -// Flat Terminal Data Type -////////////////////////////////////////////////////////////////////////// - -typedef unsigned char ftchar; // primitive character type -typedef unsigned char ftattr; // primitive attribute type - -////////////////////////////////////////////////////////////////////////// -// Flat Terminal Structure -////////////////////////////////////////////////////////////////////////// - -typedef struct -{ - ftchar **cmap[2]; // character map - ftattr **amap[2]; // attribute map - ftchar *dmap; // dirty map - ftchar *dcmap; // processed display map - ftattr attr; - int rows, cols; - int y, x; - int sy,sx; // stored cursor - int mi; // map index, mi = current map and (1-mi) = old map - int dirty; - int scroll; - - // memory allocation - int mrows, mcols; - - // raw terminal status - int ry, rx; - ftattr rattr; - - // typeahead - char typeahead; - - // escape command - ftchar cmd[FTCMD_MAXLEN+1]; - int szcmd; - -} FlatTerm; - -static FlatTerm ft; - -#ifdef _WIN32 - // sorry, we support only 80x24 on Windows now. - HANDLE hStdout; - COORD coordBufSize = {80, 24}, coordBufCoord = {0, 0}; - SMALL_RECT winrect = {0, 0, 79, 23}; - CHAR_INFO winbuf[80*24]; -#endif - -////////////////////////////////////////////////////////////////////////// -// Flat Terminal Utility Macro -////////////////////////////////////////////////////////////////////////// - -// ftattr: 0| FG(3) | BOLD(1) | BG(3) | BLINK(1) |8 -#define FTATTR_FGSHIFT (0) -#define FTATTR_BGSHIFT (4) -#define FTATTR_GETFG(x) ((x >> FTATTR_FGSHIFT) & 0x7) -#define FTATTR_GETBG(x) ((x >> FTATTR_BGSHIFT) & 0x7) -#define FTATTR_FGMASK ((ftattr)(0x7 << FTATTR_FGSHIFT)) -#define FTATTR_BGMASK ((ftattr)(0x7 << FTATTR_BGSHIFT)) -#define FTATTR_BOLD (0x08) -#define FTATTR_BLINK (0x80) -#define FTATTR_DEFAULT_FG (FTATTR_GETFG(FTATTR_DEFAULT)) -#define FTATTR_DEFAULT_BG (FTATTR_GETBG(FTATTR_DEFAULT)) -#define FTATTR_MAKE(f,b) (((f)<=(0x80)) -#define FTDBCS_ISTAIL(x) (((unsigned char)(x))>=(0x40)) - -// - 0xFF is used as telnet IAC, don't use it! -// - 0x80 is invalid for Big5. -#define FTDBCS_ISBADLEAD(x) ((((unsigned char)(x)) == 0x80) || (((unsigned char)(x)) == 0xFF)) - -// even faster: -// #define FTDBCS_ISLEAD(x) (((unsigned char)(x)) & 0x80) -// #define FTDBCS_ISTAIL(x) (((unsigned char)(x)) & ((unsigned char)~0x3F)) - -#define FTDBCS_ISSBCSPRINT(x) \ - (((unsigned char)(x))>=' ' && ((unsigned char)(x))<0x80) - -#ifndef min -#define min(x,y) (((x)<(y))?(x):(y)) -#endif - -#ifndef max -#define max(x,y) (((x)>(y))?(x):(y)) -#endif - -#ifndef abs -#define abs(x) (((x)>0)?(x):-(x)) -#endif - -#define ranged(x,vmin,vmax) (max(min(x,vmax),vmin)) - - -////////////////////////////////////////////////////////////////////////// -// Flat Terminal API -////////////////////////////////////////////////////////////////////////// - -//// common ncurse-like library interface - -// initialization -void initscr (void); -int resizeterm (int rows, int cols); -int endwin (void); - -// attributes -ftattr attrget (void); -void attrset (ftattr attr); -void attrsetfg (ftattr attr); -void attrsetbg (ftattr attr); - -// cursor -void getyx (int *y, int *x); -void getmaxyx (int *y, int *x); -void move (int y, int x); - -// clear -void clear (void); // clrscr + move(0,0) -void clrtoeol (void); // end of line -void clrtobot (void); -// clear (non-ncurses) -void clrtoln (int ln); // clear down to ln ( excluding ln, as [y,ln) ) -void clrcurln (void); // whole line -void clrtobeg (void); // begin of line -void clrtohome (void); -void clrscr (void); // clear and keep cursor untouched -void clrregion (int r1, int r2); // clear [r1,r2], bi-directional. - -// window control -void newwin (int nlines, int ncols, int y, int x); - -// flushing -void refresh (void); // optimized refresh -void doupdate (void); // optimized refresh, ignore input queue -void redrawwin (void); // invalidate whole screen -int typeahead (int fd);// prevent refresh if input queue is not empty - -// scrolling -void scroll (void); // scroll up -void rscroll (void); // scroll down -void scrl (int rows); - -// output (ncurses flavor) -void addch (unsigned char c); // equivalent to outc() -void addstr (const char *s); // equivalent to outs() -void addnstr (const char *s, int n); - -// output (non-ncurses) -void outc (unsigned char c); -void outs (const char *s); -void outns (const char *s, int n); -void outstr (const char *str); // prepare and print a complete string. -void addstring (const char *str); // ncurses-like of outstr(). - -// readback -int instr (char *str); -int innstr (char *str, int n); // n includes \0 -int inansistr (char *str, int n); // n includes \0 - -// deprecated -void standout (void); -void standend (void); - -// grayout advanced control -void grayout (int y, int end, int level); - -//// flat-term internal processor - -int fterm_inbuf (void); // raw input adapter -void fterm_rawc (int c); // raw output adapter -void fterm_rawnewline(void); // raw output adapter -void fterm_rawflush (void); // raw output adapter -void fterm_raws (const char *s); -void fterm_rawnc (int c, int n); -void fterm_rawnum (int arg); -void fterm_rawcmd (int arg, int defval, char c); -void fterm_rawcmd2 (int arg1, int arg2, int defval, char c); -void fterm_rawattr (ftattr attr); // optimized changing attribute -void fterm_rawclear (void); -void fterm_rawclreol (void); -void fterm_rawhome (void); -void fterm_rawscroll (int dy); -void fterm_rawcursor (void); -void fterm_rawmove (int y, int x); -void fterm_rawmove_opt(int y, int x); -void fterm_rawmove_rel(int dy, int dx); - -int fterm_chattr (char *s, ftattr oa, ftattr na); // size(s) > FTATTR_MINCMD -void fterm_exec (void); // execute ft.cmd -void fterm_flippage (void); -void fterm_dupe2bk (void); -void fterm_markdirty (void); // mark as dirty -int fterm_strdlen (const char *s); // length of string for display -int fterm_prepare_str(int len); - -// DBCS supporting -int fterm_DBCS_Big5(unsigned char c1, unsigned char c2); - -////////////////////////////////////////////////////////////////////////// -// Flat Terminal Implementation -////////////////////////////////////////////////////////////////////////// - -#define fterm_markdirty() { ft.dirty = 1; } - -// initialization - -void -initscr(void) -{ -#ifdef _WIN32 - hStdout = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleScreenBufferSize(hStdout, coordBufSize); - SetConsoleCursorPosition(hStdout, coordBufCoord); -#endif - - memset(&ft, sizeof(ft), 0); - ft.attr = ft.rattr = FTATTR_DEFAULT; - resizeterm(FTSZ_DEFAULT_ROW, FTSZ_DEFAULT_COL); - - // clear both pages - ft.mi = 0; clrscr(); - ft.mi = 1; clrscr(); - ft.mi = 0; - - // typeahead - ft.typeahead = 1; - - fterm_rawclear(); - move(0, 0); -} - -int -endwin(void) -{ - int r, mi = 0; - - // fterm_rawclear(); - - for (mi = 0; mi < 2; mi++) - { - for (r = 0; r < ft.mrows; r++) - { - free(ft.cmap[mi][r]); - free(ft.amap[mi][r]); - } - } - free(ft.dmap); - free(ft.dcmap); - memset(&ft, sizeof(ft), 0); - return 0; -} - -int -resizeterm(int rows, int cols) -{ - int dirty = 0, mi = 0, i = 0; - - rows = ranged(rows, FTSZ_MIN_ROW, FTSZ_MAX_ROW); - cols = ranged(cols, FTSZ_MIN_COL, FTSZ_MAX_COL); - - // adjust memory only for increasing buffer - if (rows > ft.mrows || cols > ft.mcols) - { - for (mi = 0; mi < 2; mi++) - { - // allocate rows - if (rows > ft.mrows) - { - ft.cmap[mi] = (ftchar**)realloc(ft.cmap[mi], - sizeof(ftchar*) * rows); - ft.amap[mi] = (ftattr**)realloc(ft.amap[mi], - sizeof(ftattr*) * rows); - - // allocate new columns - for (i = ft.mrows; i < rows; i++) - { - ft.cmap[mi][i] = (ftchar*)malloc((cols+1) * sizeof(ftchar)); - ft.amap[mi][i] = (ftattr*)malloc((cols+1) * sizeof(ftattr)); - // zero at end to prevent over-run - ft.cmap[mi][i][cols] = 0; - ft.amap[mi][i][cols] = 0; - } - } - - // resize cols - if (cols > ft.mcols) - { - for (i = 0; i < ft.mrows; i++) - { - ft.cmap[mi][i] = (ftchar*)realloc(ft.cmap[mi][i], - (cols+1) * sizeof(ftchar)); - ft.amap[mi][i] = (ftattr*)realloc(ft.amap[mi][i], - (cols+1) * sizeof(ftattr)); - // zero at end to prevent over-run - ft.cmap[mi][i][cols] = 0; - ft.amap[mi][i][cols] = 0; - } - } else { - // we have to deal one case: - // expand x, shrink x, expand y -> - // now new allocated lines will have small x(col) instead of mcol. - // the solution is to modify mcols here, or change the malloc above - // to max(ft.mcols, cols). - ft.mcols = cols; - } - } - - // adjusts dirty and display map. - // no need to initialize anyway. - if (cols > ft.mcols) - { - ft.dmap = (ftchar*) realloc(ft.dmap, - (cols+1) * sizeof(ftchar)); - ft.dcmap = (ftchar*) realloc(ft.dcmap, - (cols+1) * sizeof(ftchar)); - } - - // do mrows/mcols assignment here, because we had 2 maps running loop above. - if (cols > ft.mcols) ft.mcols = cols; - if (rows > ft.mrows) ft.mrows = rows; - dirty = 1; - } - - // clear new exposed buffer after resized - // because we will redawwin(), so need to change front buffer only. - for (i = ft.rows; i < rows; i++) - { - memset(FTCMAP[i], FTCHAR_ERASE, - (cols) * sizeof(ftchar)); - memset(FTAMAP[i], FTATTR_ERASE, - (cols) * sizeof(ftattr)); - } - if (cols > ft.cols) - { - for (i = 0; i < ft.rows; i++) - { - memset(FTCMAP[i]+ft.cols, FTCHAR_ERASE, - (cols-ft.cols) * sizeof(ftchar)); - memset(FTAMAP[i]+ft.cols, FTATTR_ERASE, - (cols-ft.cols) * sizeof(ftattr)); - } - } - - if (ft.rows != rows || ft.cols != cols) - { - ft.rows = rows; - ft.cols = cols; - redrawwin(); - } - - ft.x = ranged(ft.x, 0, ft.cols-1); - ft.y = ranged(ft.y, 0, ft.rows-1); - - return dirty; -} - -// attributes - -ftattr -attrget(void) -{ - return ft.attr; -} - -void -attrset(ftattr attr) -{ - ft.attr = attr; -} - -void -attrsetfg(ftattr attr) -{ - ft.attr &= (~FTATTR_FGMASK); - ft.attr |= ((attr & FTATTR_FGMASK) << FTATTR_FGSHIFT); -} - -void -attrsetbg(ftattr attr) -{ - ft.attr &= (~FTATTR_BGMASK); - ft.attr |= ((attr & FTATTR_FGMASK) << FTATTR_BGSHIFT); -} - -// clear - -void -clrscr(void) -{ - int r; - for (r = 0; r < ft.rows; r++) - memset(FTCMAP[r], FTCHAR_ERASE, ft.cols * sizeof(ftchar)); - for (r = 0; r < ft.rows; r++) - memset(FTAMAP[r], FTATTR_ERASE, ft.cols * sizeof(ftattr)); - fterm_markdirty(); -} - -void -clear(void) -{ - clrscr(); - move(0,0); -} - -void -clrtoeol(void) -{ - ft.x = ranged(ft.x, 0, ft.cols-1); - ft.y = ranged(ft.y, 0, ft.rows-1); - memset(FTPC, FTCHAR_ERASE, ft.cols - ft.x); - memset(FTPA, FTATTR_ERASE, ft.cols - ft.x); - fterm_markdirty(); -} - -void -clrtobeg(void) -{ - ft.x = ranged(ft.x, 0, ft.cols-1); - ft.y = ranged(ft.y, 0, ft.rows-1); - memset(FTCROW, FTCHAR_ERASE, ft.x+1); - memset(FTAROW, FTATTR_ERASE, ft.x+1); - fterm_markdirty(); -} - -void -clrcurrline(void) -{ - ft.y = ranged(ft.y, 0, ft.rows-1); - memset(FTCROW, FTCHAR_ERASE, ft.cols); - memset(FTAROW, FTATTR_ERASE, ft.cols); - fterm_markdirty(); -} - -void -clrtoln(int line) -{ - if (line <= ft.y) - return; - clrregion(ft.y, line-1); -} - -void -clrregion(int r1, int r2) -{ - // bi-direction - if (r1 > r2) - { - int r = r1; - r1 = r2; r2 = r; - } - - // check r1, r2 range - r1 = ranged(r1, 0, ft.rows-1); - r2 = ranged(r2, 0, ft.rows-1); - - for (; r1 <= r2; r1++) - { - memset(FTCMAP[r1], FTCHAR_ERASE, ft.cols); - memset(FTAMAP[r1], FTATTR_ERASE, ft.cols); - } - fterm_markdirty(); -} - -void -clrtobot(void) -{ - clrtoeol(); - clrregion(ft.y+1, ft.rows-1); -} - -void -clrtohome(void) -{ - clrtobeg(); - clrregion(ft.y-1, 0); -} - -void newwin (int nlines, int ncols, int y, int x) -{ - int oy, ox; - // check range - - x = ranged(x, 0, ft.cols-1); - y = ranged(y, 0, ft.rows-1); - ncols = ranged(x+ncols-1, x, ft.cols-1); - nlines = ranged(y+nlines-1, y, ft.rows-1); - ncols = ncols - x + 1; - nlines= nlines- y + 1; - - if (nlines <= 0 || ncols <= 0) - return; - getyx(&oy, &ox); - - while (nlines-- > 0) - { - move(y++, x); - // use prepare_str to erase character - fterm_prepare_str(ncols); - // memset(FTAMAP[y]+x, ft.attr, ncols); - // memset(FTCMAP[y]+x, FTCHAR_ERASE, ncols); - } - move(oy, ox); -} - -// dirty and flushing - -void -redrawwin(void) -{ - // flip page - fterm_flippage(); - clrscr(); - - // clear raw terminal - fterm_rawclear(); - - // flip page again - fterm_flippage(); - - // mark for refresh. - fterm_markdirty(); -} - -int -typeahead(int fd) -{ - switch(fd) - { - case TYPEAHEAD_NONE: - ft.typeahead = 0; - break; - case TYPEAHEAD_STDIN: - ft.typeahead = 1; - break; - default: // shall never reach here - assert(NULL); - break; - } - return 0; -} - -void -refresh(void) -{ - // prevent passive update - if(fterm_inbuf() && ft.typeahead) - return; - doupdate(); -} - -void -doupdate(void) -{ - int y, x; - char touched = 0; - - if (!ft.dirty) - { - fterm_rawcursor(); - return; - } - -#ifdef _WIN32 - - assert(ft.rows == coordBufSize.Y); - assert(ft.cols == coordBufSize.X); - - for (y = 0; y < ft.rows; y++) - { - for (x = 0; x < ft.cols; x++) - { - WORD xAttr = FTAMAP[y][x], xxAttr; - // w32 attribute: bit swap (0,2) and (4, 6) - xxAttr = xAttr & 0xAA; - if (xAttr & 0x01) xxAttr |= 0x04; - if (xAttr & 0x04) xxAttr |= 0x01; - if (xAttr & 0x10) xxAttr |= 0x40; - if (xAttr & 0x40) xxAttr |= 0x10; - - winbuf[y*ft.cols + x].Attributes= xxAttr; - winbuf[y*ft.cols + x].Char.AsciiChar = FTCMAP[y][x]; - } - } - WriteConsoleOutputA(hStdout, winbuf, coordBufSize, coordBufCoord, &winrect); - -#else // !_WIN32 - - // if scroll, do it first - if (ft.scroll) - fterm_rawscroll(ft.scroll); - - // calculate and optimize dirty - for (y = 0; y < ft.rows; y++) - { - int len = ft.cols, ds = 0, derase = 0; - char dbcs = 0, odbcs = 0; // 0: none, 1: lead, 2: tail - - // reset dirty and display map - memset(FTD, 0, ft.cols * sizeof(ftchar)); - memcpy(FTDC,FTCMAP[y], ft.cols * sizeof(ftchar)); - - // first run: character diff - for (x = 0; x < len; x++) - { - // build base dirty information - if (FTCMAP[y][x] != FTOCMAP[y][x]) - FTD[x] |= FTDIRTY_CHAR, ds++; - if (FTAMAP[y][x] != FTOAMAP[y][x]) - FTD[x] |= FTDIRTY_ATTR, ds++; - - // determine DBCS status - if (dbcs == 1) - { -#ifdef FTCONF_PREVENT_INVALID_DBCS - switch(fterm_DBCS_Big5(FTCMAP[y][x-1], FTCMAP[y][x])) - { - case FTDBCS_SAFE: - // safe to print - FTD[x-1] &= ~FTDIRTY_INVALID_DBCS; - FTDC[x-1] = FTCMAP[y][x-1]; - break; - - case FTDBCS_UNSAFE: - // ok to print, but need to rawmove. - FTD[x-1] &= ~FTDIRTY_INVALID_DBCS; - FTDC[x-1] = FTCMAP[y][x-1]; - FTD[x-1] |= FTDIRTY_CHAR; - FTD[x] |= FTDIRTY_RAWMOVE; - break; - - case FTDBCS_INVALID: - // only SBCS safe characters can be print. - if (!FTDBCS_ISSBCSPRINT(FTCMAP[y][x])) - { - FTD[x] |= FTDIRTY_INVALID_DBCS; - FTDC[x] = FTCHAR_INVALID_DBCS; - } - break; - } -#endif // FTCONF_PREVENT_INVALID_DBCS - - dbcs = 2; - // TAIL: dirty prev and me if any is dirty. - if (FTD[x] || FTD[x-1]) - { - FTD[x] |= FTDIRTY_DBCS; - FTD[x-1]|= FTDIRTY_CHAR; - } - } - else if (FTDBCS_ISLEAD(FTCMAP[y][x])) - { - // LEAD: clear dirty when tail was found. - dbcs = 1; -#ifdef FTCONF_PREVENT_INVALID_DBCS - FTD[x] |= FTDIRTY_INVALID_DBCS; - FTDC[x] = FTCHAR_INVALID_DBCS; -#endif // FTCONF_PREVENT_INVALID_DBCS - } - else - { - // NON-DBCS - dbcs = 0; - } - - if (odbcs == 1) - { - // TAIL: dirty prev and me if any is dirty. - odbcs = 2; - if (FTD[x] || FTD[x-1]) - { - FTD[x] |= FTDIRTY_CHAR; - FTD[x-1]|= FTDIRTY_CHAR; - } - } - else if (FTDBCS_ISLEAD(FTOCMAP[y][x])) - { - // LEAD: dirty next? - odbcs = 1; - } - else - { - odbcs = 0; - } - } - -#ifndef DBG_SHOW_DIRTY - if (!ds) - continue; -#endif // DBG_SHOW_DIRTY - - // Optimization: calculate ERASE section - // TODO ERASE takes 3 bytes (ESC [ K), so enable only if derase >= 3? - // TODO ERASE then print can avoid lots of space, optimize in future. - for (x = ft.cols - 1; x >= 0; x--) - if (FTCMAP[y][x] != FTCHAR_ERASE || - FTAMAP[y][x] != FTATTR_ERASE) - break; - else if (FTD[x]) - derase++; - - len = x+1; - - for (x = 0; x < len; x++) - { -#ifndef DBG_SHOW_DIRTY - if (!FTD[x]) - continue; -#endif // !DBG_SHOW_DIRTY - - // Optimization: re-print or move? -#ifndef DBG_DISABLE_REPRINT - while (ft.ry == y && x > ft.rx && abs(x-ft.rx) < FTMV_COST) - { - int i; - // Special case: we may be in position of DBCS tail... - // Inside a refresh() loop, this will never happen. - // However it may occur for the first print entering refresh. - // So enable only space if this is the first run (!touched). - - // if we don't need to change attributes, - // just print remaining characters - for (i = ft.rx; i < x; i++) - { - // if same attribute, simply accept. - if (FTAMAP[y][i] == ft.rattr && touched) - continue; - // XXX spaces may accept (BG=rBG), - // but that will also change cached attribute. - if (!FTCHAR_ISBLANK(FTCMAP[y][i])) - break; - if (FTATTR_GETBG(FTAMAP[y][i]) != FTATTR_GETBG(ft.rattr)) - break; - } - if (i != x) - break; - - // safe to print all! - // printf("[re-print %d chars]", i-ft.rx); - -#ifdef DBG_SHOW_REPRINT - // reverse to hint this is a re-print - fterm_rawattr(FTATTR_MAKE(0, 7) | FTATTR_BOLD); -#endif // DBG_SHOW_REPRINT - - for (i = ft.rx; i < x; i++) - { - fterm_rawc(FTDC[i]); - FTAMAP[y][i] = FTOAMAP[y][i]; // spaces may change attr... - ft.rx++; - } - - break; - } -#endif // !DBG_DISABLE_REPRINT - - if (y != ft.ry || x != ft.rx) - fterm_rawmove_opt(y, x); - -#ifdef DBCSAWARE - if ((FTD[x] & FTDIRTY_DBCS) && (FT_DBCS_NOINTRESC)) - { - // prevent changing attributes inside DBCS - } - else -#endif // DBCSAWARE -#ifdef DBG_SHOW_DIRTY - fterm_rawattr(FTD[x] ? - (FTAMAP[y][x] | FTATTR_BOLD) : (FTAMAP[y][x] & ~FTATTR_BOLD)); -#else // !DBG_SHOW_DIRTY - fterm_rawattr(FTAMAP[y][x]); -#endif // !DBG_SHOW_DIRTY - - fterm_rawc(FTDC[x]); - ft.rx++; - touched = 1; - - if (FTD[x] & FTDIRTY_RAWMOVE) - { - fterm_rawcmd2(ft.ry+1, ft.rx+1, 1, 'H'); - } - } - - if (derase) - { - fterm_rawmove_opt(y, len); - fterm_rawclreol(); - } - } - -#endif // !_WIN32 - - // doing fterm_rawcursor() earlier to enable max display time - fterm_rawcursor(); - fterm_dupe2bk(); - ft.dirty = 0; -} - -// cursor management - -void -getyx(int *y, int *x) -{ - if (y) - *y = ft.y; - if (x) - *x = ft.x; -} - -void -getmaxyx(int *y, int *x) -{ - if (y) - *y = ft.rows; - if (x) - *x = ft.cols; -} - -void -move(int y, int x) -{ - ft.y = ranged(y, 0, ft.rows-1); - ft.x = ranged(x, 0, ft.cols-1); -} - -// scrolling - -void -scrl(int rows) -{ - if (!rows) - return; - if (rows > 0) - { - for (; rows > 0; rows--) - scroll(); - } else { - for (; rows < 0; rows++) - rscroll(); - } -} - -void -scroll() -{ - // scroll up - int y; - ftchar *c0 = FTCMAP[0], *oc0 = FTOCMAP[0]; - ftattr *a0 = FTAMAP[0], *oa0 = FTOAMAP[0]; - - // prevent mixing buffered scroll up+down - if (ft.scroll < 0) - fterm_rawscroll(ft.scroll); - - // smart scroll: move pointers - for (y = 0; y < ft.rows-1; y++) - { - FTCMAP[y] = FTCMAP[y+1]; - FTAMAP[y] = FTAMAP[y+1]; - FTOCMAP[y]= FTOCMAP[y+1]; - FTOAMAP[y]= FTOAMAP[y+1]; - } - FTCMAP[y] = c0; - FTAMAP[y] = a0; - FTOCMAP[y]= oc0; - FTOAMAP[y]= oa0; - - // XXX also clear backup buffer - // must carefully consider if up then down scrolling. - fterm_flippage(); - clrregion(ft.rows-1, ft.rows-1); - fterm_flippage(); - clrregion(ft.rows-1, ft.rows-1); - - ft.scroll ++; - // fterm_markdirty(); // should be already dirty -} - -void -rscroll() -{ - // scroll down - int y; - ftchar *c0 = FTCMAP[ft.rows -1], *oc0 = FTOCMAP[ft.rows -1]; - ftattr *a0 = FTAMAP[ft.rows -1], *oa0 = FTOAMAP[ft.rows -1]; - - // prevent mixing buffered scroll up+down - if (ft.scroll > 0) - fterm_rawscroll(ft.scroll); - - // smart scroll: move pointers - for (y = ft.rows -1; y > 0; y--) - { - FTCMAP[y] = FTCMAP[y-1]; - FTAMAP[y] = FTAMAP[y-1]; - FTOCMAP[y]= FTOCMAP[y-1]; - FTOAMAP[y]= FTOAMAP[y-1]; - } - FTCMAP[y] = c0; - FTAMAP[y] = a0; - FTOCMAP[y]= oc0; - FTOAMAP[y]= oa0; - - // XXX also clear backup buffer - // must carefully consider if up then down scrolling. - fterm_flippage(); - clrregion(0, 0); - fterm_flippage(); - clrregion(0, 0); - - ft.scroll --; - // fterm_markdirty(); // should be already dirty -} - -// output - -void -addch (unsigned char c) -{ - outc(c); -} - -void -addstr (const char *s) -{ - outs(s); -} - -void -addnstr(const char *s, int n) -{ - outns(s, n); -} - -void -addstring(const char *s) -{ - outstr(s); -} - -void -outs(const char *s) -{ - if (!s) - return; - while (*s) - outc(*s++); -} - -void -outns(const char *s, int n) -{ - if (!s) - return; - while (*s && n-- > 0) - outc(*s++); -} - -void -outstr(const char *str) -{ - if (!str) - { - fterm_prepare_str(0); - return; - } - - // calculate real length of str (stripping escapes) - // TODO only print by the returned size - - fterm_prepare_str(fterm_strdlen(str)); - - outs(str); - - // maybe the source string itself is broken... - // basically this check should be done by clients, not term library. -#if 0 - { - int isdbcs = 0; - while (*str) - { - if (isdbcs == 1) isdbcs = 2; - else if (FTDBCS_ISLEAD(*str)) isdbcs = 1; - else isdbcs = 0; - str++; - } - - if (isdbcs == 1) // incomplete string! - outs("\b?"); - } -#endif -} - -void -outc(unsigned char c) -{ - // 0xFF is invalid for most cases (even DBCS), - if (c == 0xFF || c == 0x00) - return; - - fterm_markdirty(); - if (ft.szcmd) - { - // collecting commands - ft.cmd[ft.szcmd++] = c; - - if ((ft.szcmd == 2 && c == '[') || - (ANSI_IS_PARAM(c) && ft.szcmd < FTCMD_MAXLEN)) - return; - - // process as command - fterm_exec(); - ft.szcmd = 0; - } - else if (c == ESC_CHR) - { - // start of escaped commands - ft.cmd[ft.szcmd++] = c; - } - else if (c == '\t') - { - // tab: move by 8, and erase the moved range - int x = ft.x; - if (x % 8 == 0) - x += 8; - else - x += (8-(x%8)); - x = ranged(x, 0, ft.cols-1); - // erase the characters between - if (x > ft.x) - { - memset(FTCROW+ft.x, FTCHAR_ERASE, x - ft.x); - memset(FTAROW+ft.x, ft.attr, x-ft.x); - } - ft.x = x; - } - else if (c == '\b') - { - ft.x = ranged(ft.x-1, 0, ft.cols-1); - } - else if (c == '\r' || c == '\n') - { - // new line: cursor movement, and do not print anything - // XXX old screen.c also calls clrtoeol() for newlins. - clrtoeol(); - ft.x = 0; - ft.y ++; - while (ft.y >= ft.rows) - { - // XXX scroll at next dirty? - // screen.c ignored such scroll. - // scroll(); - ft.y --; - } - } - else if (iscntrl((unsigned char)c)) - { - // unknown control characters: ignore - } - else // normal characters - { - assert (ft.x >= 0 && ft.x < ft.cols); - - // normal characters - FTC = c; -#ifdef FTATTR_TRANSPARENT - if (ft.attr != FTATTR_TRANSPARENT) -#endif // FTATTR_TRANSPARENT - FTA = ft.attr; - - ft.x++; - // XXX allow x == ft.cols? - if (ft.x >= ft.cols) - { - ft.x = 0; - ft.y ++; - while (ft.y >= ft.rows) - { - // XXX scroll at next dirty? - // screen.c ignored such scroll. - // scroll(); - ft.y --; - } - } - } -} - -// readback -int -instr (char *str) -{ - int x = ft.cols -1; - *str = 0; - if (ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) - return 0; - - // determine stopping location - while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE) - x--; - if (x < ft.x) return 0; - x = x - ft.x + 1; - memcpy(str, FTCROW+ft.x, x); - str[x] = 0; - return x; -} - -int -innstr (char *str, int n) -{ - int on = n; - int x = ranged(ft.x + n-1, 0, ft.cols-1); - *str = 0; - n = x - ft.x+1; - if (on < 1 || ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) - return 0; - - // determine stopping location - while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE) - x--; - if (x < ft.x) return 0; - n = x - ft.x + 1; - if (n >= on) n = on-1; - memcpy(str, FTCROW+ft.x, n); - str[n] = 0; - return n; -} - -int -inansistr (char *str, int n) -{ - int x = ft.cols -1, i = 0, szTrail = 0; - char *ostr = str; - char cmd[FTATTR_MINCMD*2] = ""; - - ftattr a = FTATTR_DEFAULT; - *str = 0; - if (ft.y < 0 || ft.y >= ft.rows || ft.x < 0 || ft.x >= ft.cols) - return 0; - - if (n < 1) - return 0; - n--; // preserve last zero - - // determine stopping location - while (x >= ft.x && FTCROW[x] == FTCHAR_ERASE && FTAROW[x] == FTATTR_ERASE) - x--; - - // retrieve [rt.x, x] - if (x < ft.x) return 0; - - // preserve some bytes if last attribute is not FTATTR_DEFAULT - for (i = ft.x; n > szTrail && i <= x; i++) - { - *str = 0; - - if (a != FTAROW[i]) - { - fterm_chattr(cmd, a, FTAROW[i]); - a = FTAROW[i]; - - if (a != FTATTR_DEFAULT) - szTrail = 3; // ESC [ m - else - szTrail = 0; - - if (strlen(cmd) >= n-szTrail) - break; - - strcpy(str, cmd); - n -= strlen(cmd); - str += strlen(cmd); - } - - // n should > szTrail - *str ++ = FTCROW[i]; - n--; - } - - if (szTrail && n >= szTrail) - { - *str++ = ESC_CHR; *str++ = '['; *str++ = 'm'; - } - - *str = 0; - return (str - ostr); -} - -// internal core of piaip's flat-term - -void -fterm_flippage (void) -{ - // we have only 2 pages now. - ft.mi = 1 - ft.mi; -} - -#ifndef fterm_markdirty -void -fterm_markdirty (void) -{ - ft.dirty = 1; -} -#endif - -void fterm_dupe2bk(void) -{ - int r = 0; - - for (r = 0; r < ft.rows; r++) - { - memcpy(FTOCMAP[r], FTCMAP[r], ft.cols * sizeof(ftchar)); - memcpy(FTOAMAP[r], FTAMAP[r], ft.cols * sizeof(ftattr)); - } -} - -int -fterm_DBCS_Big5(unsigned char c1, unsigned char c2) -{ - // ref: http://www.cns11643.gov.tw/web/word/big5/index.html - // High byte: 0xA1-0xFE, 0x8E-0xA0, 0x81-0x8D - // Low byte: 0x40-0x7E, 0xA1-0xFE - // C1: 0x80-0x9F - if (FTDBCS_ISBADLEAD(c1)) - return FTDBCS_INVALID; - if (!FTDBCS_ISTAIL(c2)) - return FTDBCS_INVALID; - if (c1 >= 0x80 && c1 <= 0x9F) - return FTDBCS_UNSAFE; - return FTDBCS_SAFE; -} - -int -fterm_prepare_str(int len) -{ - // clear and make (cursor, +len) as DBCS-ready. - int x = ranged(ft.x, 0, ft.cols-1); - int y = ranged(ft.y, 0, ft.rows-1); - int dbcs = 0, sdbcs = 0; - - // TODO what if x+len is outside range? - - // check if (x,y) is in valid range - if (x != ft.x || y != ft.y) - return -1; - - len = ranged(x+len, x, ft.cols); - - for (x = 0; x < len; x++) - { - // determine DBCS status - if (dbcs == 1) - dbcs = 2; // TAIL - else if (FTDBCS_ISLEAD(FTCROW[x])) - dbcs = 1; // LEAD - else - dbcs = 0; - if (x == ft.x) sdbcs = dbcs; - } - - x = ft.x; - // fix start and end - if(sdbcs == 2 && x > 0) // TAIL, remove word - x--; - if (dbcs == 1 && len < ft.cols) // LEAD, remove word - len ++; - len = ranged(len, 0, ft.cols); - len -= x; - if (len < 0) len = 0; - - memset(FTCROW + x, FTCHAR_ERASE, len); - memset(FTAROW + x, ft.attr, len); - return len; -} - - -void -fterm_exec(void) -{ - ftchar cmd = ft.cmd[ft.szcmd-1]; - char *p = (char*)ft.cmd + 2; // ESC [ - int n = -1, x = -1, y; - - ft.cmd[ft.szcmd] = 0; - - if (isdigit(*p)) - { - n = atoi(p); - - while (*p && isdigit(*p)) - p++; - - if (*p == ';') - p++; - // p points to next param now - } - - switch(cmd) - { - // Cursor Movement - - case 'A': // CUU: CSI n A - case 'B': // CUD: CSI n B - case 'C': // CUF: CSI n C - case 'D': // CUB: CSI n D - // Moves the cursor n (default 1) cells in the given direction. - // If the cursor is already at the edge of the screen, this has no effect. - if (n < 1) - n = 1; - getyx(&y, &x); - if (cmd == 'A') { y -= n; } - else if (cmd == 'B') { y += n; } - else if (cmd == 'C') { x += n; } - else if (cmd == 'D') { x -= n; } - move(y, x); - break; - - case 'E': // CNL: CSI n E - case 'F': // CPL: CSI n F - // Moves cursor to beginning of the line - // n (default 1) lines up/down (next/previous line). - if (n < 1) - n = 1; - getyx(&y, &x); - if (cmd == 'E') { y -= n; } - else if (cmd == 'F') { y += n; } - move(y, 0); - break; - - case 'G': // CHA: CSI n G - // Moves the cursor to column n. - if (n < 1) - n = 1; - getyx(&y, &x); - move(y, n-1); - break; - - case 'H': // CUP: CSI n ; m H - case 'f': // HVP: CSI n ; m f - // Moves the cursor to row n, column m. - // The values are 1-based, and default to 1 (top left corner) if omitted. - // A sequence such as CSI ;5H is a synonym for CSI 1;5H as well as - // CSI 17;H is the same as CSI 17H and CSI 17;1H - y = n; - if (y >= 0 && isdigit(*p)) - x = atoi((char*)p); - if (y < 0) y = 1; - if (x < 0) x = 1; - move(y-1, x-1); - break; - - // Clear - - case 'J': // ED: CSI n J - // Clears part of the screen. - // If n is zero (or missing), clear from cursor to end of screen. - // If n is one, clear from cursor to beginning of the screen. - // If n is two, clear entire screen - if (n == 0 || n < 0) - clrtobot(); - else if (n == 1) - clrtohome(); - else if (n == 2) - { - clrregion(0, ft.rows-1); - } - break; - - case 'K': // EL: CSI n K - // Erases part of the line. - // If n is zero (or missing), clear from cursor to the end of the line. - // If n is one, clear from cursor to beginning of the line. - // If n is two, clear entire line. Cursor position does not change. - if (n == 0 || n < 0) - clrtoeol(); - else if (n == 1) - clrtobeg(); - else if (n == 2) - clrcurrline(); - break; - - case 's': // SCP: CSI s - // Saves the cursor position. - getyx(&ft.sy, &ft.sx); - break; - - case 'u': // RCP: CSI u - // Restores the cursor position. - move(ft.sy, ft.sx); - break; - - case 'S': // SU: CSI n S - // Scroll whole page up by n (default 1) lines. - // New lines are added at the bottom. - if (n < 1) - n = 1; - scrl(n); - break; - - case 'T': // SD: CSI n T - // Scroll whole page down by n (default 1) lines. - // New lines are added at the top. - if (n < 1) - n = 1; - scrl(-n); - break; - - case 'm': // SGR: CSI n [;k] m - // Sets SGR (Select Graphic Rendition) parameters. - // After CSI can be zero or more parameters separated with ;. - // With no parameters, CSI m is treated as CSI 0 m (reset / normal), - // which is typical of most of the ANSI codes. - // --------------------------------------------------------- - // SGR implementation: - // SGR 0 (reset/normal) is supported. - // SGR 1 (intensity: bold) is supported. - // SGR 2 (intensity: faint) is not supported. - // SGR 3 (italic: on) is not supported. (converted to inverse?) - // SGR 4 (underline: single) is not supported. - // SGR 5 (blink: slow) is supported. - // SGR 6 (blink: rapid) is converted to (blink: slow) - // SGR 7 (image: negative) is partially supported (not a really attribute). - // SGR 8 (conceal) is not supported. - // SGR 21(underline: double) is not supported. - // SGR 22(intensity: normal) is supported. - // SGR 24(underline: none) is not supported. - // SGR 25(blink: off) is supported. - // SGR 27(image: positive) is not supported. - // SGR 28(reveal) is not supported. - // SGR 30-37 (FG) is supported. - // SGR 39 (FG-reset) is supported. - // SGR 40-47 (BG) is supported. - // SGR 49 (BG-reset) is supported. - if (n == -1) // first param - n = 0; - while (n > -1) - { - if (n >= 30 && n <= 37) - { - // set foreground - attrsetfg(n - 30); - } - else if (n >= 40 && n <= 47) - { - // set background - attrsetbg(n - 40); - } - else switch(n) - { - case 0: - attrset(FTATTR_DEFAULT); - break; - case 1: - attrset(attrget() | FTATTR_BOLD); - break; - case 22: - attrset(attrget() & ~FTATTR_BOLD); - break; - case 5: - case 6: - attrset(attrget() | FTATTR_BLINK); - break; - case 25: - attrset(attrget() & ~FTATTR_BLINK); - break; - case 3: - case 7: - { - ftattr a = attrget(); - attrsetfg(FTATTR_GETBG(a)); - attrsetbg(FTATTR_GETFG(a)); - } - break; - case 39: - attrsetfg(FTATTR_DEFAULT_FG); - break; - case 49: - attrsetbg(FTATTR_DEFAULT_BG); - break; - } - - // parse next command - n = -1; - if (*p == ';') - { - n = 0; - p++; - } - else if (isdigit(*p)) - { - n = atoi(p); - while (isdigit(*p)) p++; - if (*p == ';') - p++; - } - } - break; - - default: // unknown command. - break; - } -} - -int -fterm_chattr(char *s, ftattr oattr, ftattr nattr) -{ - ftattr - fg, bg, bold, blink, - ofg, obg, obold, oblink; - char lead = 1; - - if (oattr == nattr) - return 0; - - *s++ = ESC_CHR; - *s++ = '['; - - // optimization: reset as default - if (nattr == FTATTR_DEFAULT) - { - *s++ = 'm'; - *s++ = 0; - return 1; - } - - fg = FTATTR_GETFG(nattr); - bg = FTATTR_GETBG(nattr); - bold = (nattr & FTATTR_BOLD) ? 1 : 0; - blink = (nattr & FTATTR_BLINK)? 1 : 0; - - ofg = FTATTR_GETFG(oattr); - obg = FTATTR_GETBG(oattr); - obold = (oattr & FTATTR_BOLD) ? 1 : 0; - oblink = (oattr & FTATTR_BLINK)? 1 : 0; - - // we dont use "disable blink/bold" commands, - // so if these settings are changed then we must reset. - // another case is changing background to default background - - // better use "RESET" to override it. - // Same for foreground. - // Possible optimization: when blink/bold on, don't RESET - // for background change? - if ((oblink != blink && !blink) || - (obold != bold && !bold) || - (bg == FTATTR_DEFAULT_BG && obg != bg) || - (fg == FTATTR_DEFAULT_FG && ofg != fg) ) - { - if (lead) lead = 0; else *s++ = ';'; - *s++ = '0'; - - ofg = FTATTR_DEFAULT_FG; - obg = FTATTR_DEFAULT_BG; - obold = 0; oblink = 0; - } - - if (bold && !obold) - { - if (lead) lead = 0; else *s++ = ';'; - *s++ = '1'; - -#ifdef FTCONF_WORKAROUND_BOLD - // Issue here: - // PacketSite does not understand ESC[1m. It needs ESC[1;37m - // NetTerm defaults bold color to yellow. - // As a result, we use ESC[1;37m for the case. - if (fg == FTATTR_DEFAULT_FG) - ofg = ~ofg; -#endif // FTCONF_WORKAROUND_BOLD - - } - if (blink && !oblink) - { - if (lead) lead = 0; else *s++ = ';'; - *s++ = '5'; // XXX 5(slow) or 6(fast)? - } - if (ofg != fg) - { - if (lead) lead = 0; else *s++ = ';'; - *s++ = '3'; - *s++ = '0' + fg; - } - if (obg != bg) - { - if (lead) lead = 0; else *s++ = ';'; - *s++ = '4'; - *s++ = '0' + bg; - } - *s++ = 'm'; - *s++ = 0; - return 1; -} - -int -fterm_strdlen(const char *s) -{ - char ansi = 0; - int sz = 0; - - // the logic should match outc(). - - while (s && *s) - { - if (!ansi) // ansi == 0 - { - switch(*s) - { - case ESC_CHR: - ansi++; - break; - - case '\r': - case '\n': - break; - - case '\b': - if (sz) sz--; - break; - - case '\t': - // XXX how to deal with this? - sz ++; - break; - - default: - if (!iscntrl((unsigned char)*s)) - sz++; - break; - } - } - else if (ansi == 1) - { - if (*s == '[') - ansi++; - else - ansi = 0; - } - else if (!ANSI_IS_PARAM(*s)) // ansi == 2 - { - // TODO outc() take max to FTCMD_MAXLEN now... - ansi = 0; - } - s++; - } - return sz; -} - -void -fterm_rawattr(ftattr rattr) -{ - static char cmd[FTATTR_MINCMD*2]; - if (!fterm_chattr(cmd, ft.rattr, rattr)) - return; - - fterm_raws(cmd); - ft.rattr = rattr; -} - -void -fterm_rawnum(int arg) -{ - if (arg < 0 || arg > 99) - { - // complex. use printf. - char sarg[16]; // max int - sprintf(sarg, "%d", arg); - fterm_raws(sarg); - } else if (arg < 10) { - // 0 .. 10 - fterm_rawc('0' + arg); - } else { - fterm_rawc('0' + arg/10); - fterm_rawc('0' + arg%10); - } -} -void -fterm_rawcmd(int arg, int defval, char c) -{ - fterm_rawc(ESC_CHR); - fterm_rawc('['); - if (arg != defval) - fterm_rawnum(arg); - fterm_rawc(c); -} - -void -fterm_rawcmd2(int arg1, int arg2, int defval, char c) -{ - fterm_rawc(ESC_CHR); - fterm_rawc('['); - - // See FTCONF_ANSICMD2_OMIT - // XXX Win/DOS telnet does now accept omitting first value - // ESC[nX and ESC[n;X works, but ESC[;mX does not work. - if (arg1 != defval || arg2 != defval) - { -#if (FTCONF_ANSICMD2_OMIT >= 2) - if (arg1 != defval) -#endif - fterm_rawnum(arg1); - -#if (FTCONF_ANSICMD2_OMIT >= 1) - if (arg2 != defval) -#endif - { - fterm_rawc(';'); - fterm_rawnum(arg2); - } - } - fterm_rawc(c); -} - -void -fterm_rawclear(void) -{ - fterm_rawhome(); - // ED: CSI n J, 0 = cursor to bottom, 2 = whole - fterm_raws(ESC_STR "[2J"); -} - -void -fterm_rawclreol(void) -{ -#ifdef FTCONF_CLEAR_SETATTR - // ftattr oattr = ft.rattr; - // XXX If we skip with "backround only" here, future updating - // may get wrong attributes. Or not? (consider DBCS...) - // if (FTATTR_GETBG(oattr) != FTATTR_GETBG(FTATTR_ERASE)) - fterm_rawattr(FTATTR_ERASE); -#endif - - // EL: CSI n K, n = 0 - fterm_raws(ESC_STR "[K"); - -#ifdef FTCONF_CLEAR_SETATTR - // No need to do so? because we will always reset attribute... - // fterm_rawattr(oattr); -#endif -} - -void -fterm_rawhome(void) -{ - // CUP: CSI n ; m H - fterm_raws(ESC_STR "[H"); - ft.rx = ft.ry = 0; -} - -void -fterm_rawmove_rel(int dy, int dx) -{ -#ifndef FTCONF_USE_ANSI_RELMOVE - // Old BBS system does not output relative moves (ESC[ABCD) . - // Poor terminals (ex, pcman-1.0.5-FF20.xpi) - // do not support relmoves - fterm_rawmove(ft.ry + dy, ft.rx + dx); -#else - if (!dx) - { - int y = ranged(dy + ft.ry, 0, ft.rows-1); - dy = y - ft.ry; - if (!dy) - return; - - fterm_rawcmd(abs(dy), 1, dy < 0 ? 'A' : 'B'); - ft.ry = y; - } - else if (!dy) - { - int x = ranged(dx + ft.rx, 0, ft.cols-1); - dx = x - ft.rx; - if (!dx) - return; - - fterm_rawcmd(abs(dx), 1, dx < 0 ? 'D' : 'C'); - ft.rx = x; - } - else - { - // (dy, dx) are given - use fterm_move. - fterm_rawmove(ft.ry + dy, ft.rx + dx); - } -#endif -} - -void -fterm_rawmove(int y, int x) -{ - y = ranged(y, 0, ft.rows-1); - x = ranged(x, 0, ft.cols-1); - - if (y == ft.ry && x == ft.rx) - return; - - // CUP: CSI n ; m H - fterm_rawcmd2(y+1, x+1, 1, 'H'); - - ft.ry = y; - ft.rx = x; -} - -void -fterm_rawmove_opt(int y, int x) -{ - // optimized move - int ady = abs(y-ft.ry), adx=abs(x-ft.rx); - - if (!adx && !ady) - return; - -#ifdef DBG_DISABLE_OPTMOVE - return fterm_rawmove(y, x); -#endif - - // known hacks: \r = (x=0), \b=(x--), \n = (y++) - // - // Warning: any optimization here should not change displayed content, - // because we don't have information about content variation information - // (eg, invalid DBCS bytes will become special marks) here. - // Any hacks which will try to display data from FTCMAP should be done - // inside dirty-map calculation, for ex, using spaces to move right, - // or re-print content. - -#ifndef DBG_TEXT_FD - // x=0: the cheapest output. However not work for text mode fd output. - // a special case is "if we have to move y to up". - // and FTCONF_ANSICMD2_OMIT < 1 (cannot omit x). -#if FTCONF_ANSICMD2_OMIT < 1 - if (y >= ft.ry) -#endif - if (adx && x == 0) - { - fterm_rawc('\r'); - ft.rx = x = adx = 0; - } - -#endif // !DBG_TEXT_FD - - // x--: compare with FTMV_COST: ESC[m;nH costs 5-8 bytes - if (x < ft.rx && y >= ft.ry && (adx+ady) < FTMV_COST) - { - while (adx > 0) - fterm_rawc('\b'), adx--; - ft.rx = x; - } - - // finishing movement - if (y > ft.ry && ady < FTMV_COST && adx == 0) - { - while (ft.ry++ < y) - fterm_rawc('\n'); - ft.ry = y; - } - else if (ady && adx) - { - fterm_rawmove(y, x); - } - else if (ady) - { - fterm_rawmove_rel(y-ft.ry, 0); - } - else if (adx) - { - fterm_rawmove_rel(0, x-ft.rx); - } -} - -void -fterm_rawcursor(void) -{ -#ifdef _WIN32 - COORD cursor; - cursor.X = ft.x; - cursor.Y = ft.y; - SetConsoleCursorPosition(hStdout, cursor); -#else - // fterm_rawattr(FTATTR_DEFAULT); - fterm_rawattr(ft.attr); - fterm_rawmove_opt(ft.y, ft.x); - fterm_rawflush(); -#endif // !_WIN32 -} - -void -fterm_rawscroll (int dy) -{ -#ifdef FTCONF_USE_ANSI_SCROLL - // SU: CSI n S (up) - // SD: CSI n T (down) - - char cmd = (dy > 0) ? 'S' : 'T'; - int ady = abs(dy); - if (ady == 0) - return; - if (ady >= ft.rows) ady = ft.rows; - fterm_rawcmd(ady, 1, cmd); - ft.scroll -= dy; - -#else - // VT100 flavor: - // * ESC D: scroll down - // * ESC M: scroll up - // - // Elder BBS systems works in a mixed way: - // \n at (rows-1) as scroll() - // and ESC-M at(0) as rscoll(). - // - // SCP: CSI s / RCP: CSI u - // Warning: cannot use SCP/RCP here, because on Win/DOS telnet - // the position will change after scrolling (ex, (25,0)->(24,0). - // - // Since scroll does not happen very often, let's relax and not - // optimize these commands here... - - int ady = abs(dy); - if (ady == 0) - return; - if (ady >= ft.rows) ady = ft.rows; - - // we are not going to preserve (rx,ry) - // so don't use fterm_move*. - if (dy > 0) - fterm_rawcmd2(ft.rows, 1, 1, 'H'); - else - fterm_rawcmd2(1, 1, 1, 'H'); - - for (; ady > 0; ady--) - { - if (dy >0) - { - // Win/DOS telnet may have extra text in new line, - // because of the IME line. -#ifdef FTCONF_USE_VT100_SCROLL - fterm_raws(ESC_STR "D" ESC_STR "[K"); // ESC_STR "[K"); -#else - fterm_raws("\n" ESC_STR "[K"); -#endif - } else { - fterm_raws(ESC_STR "M"); // ESC_STR "[K"); - } - } - - // Do not use optimized move here, because in poor terminals - // the coordinates are already out of sync. - fterm_rawcmd2(ft.ry+1, ft.rx+1, 1, 'H'); - ft.scroll -= dy; -#endif -} - -void -fterm_raws(const char *s) -{ - while (*s) - fterm_rawc(*s++); -} - -void -fterm_rawnc(int c, int n) -{ - while (n-- > 0) - fterm_rawc(c); -} - -////////////////////////////////////////////////////////////////////////// -// grayout advanced control -////////////////////////////////////////////////////////////////////////// -void -grayout(int y, int end, int level) -{ - char grattr = FTATTR_DEFAULT; - - y = ranged(y, 0, ft.rows-1); - end = ranged(end, 0, ft.rows-1); - - if (level == GRAYOUT_COLORBOLD) - { - int x = 0; - for (; y < end; y++) - { - for (x = 0; x < ft.cols-1; x++) - FTAMAP[y][x] |= FTATTR_BOLD; - } - return; - } - - if (level == GRAYOUT_COLORNORM) - { - int x = 0; - for (; y < end; y++) - { - for (x = 0; x < ft.cols-1; x++) - FTAMAP[y][x] &= ~(FTATTR_BLINK | FTATTR_BOLD); - } - return; - } - - if (level == GRAYOUT_BOLD) - { - grattr |= FTATTR_BOLD; - } - else if (level == GRAYOUT_DARK) - { - grattr = FTATTR_MAKE(0,0); - grattr |= FTATTR_BOLD; - } - else if (level == GRAYOUT_NORM) - { - // normal text - } - else - { - // not supported yet - } - - for (; y <= end; y++) - { - memset(FTAMAP[y], grattr, ft.cols); - } -} - -////////////////////////////////////////////////////////////////////////// -// deprecated api -////////////////////////////////////////////////////////////////////////// - -void -standout(void) -{ - outs(ANSI_COLOR(7)); -} - -void -standend(void) -{ - outs(ANSI_RESET); -} - -#ifndef _PFTERM_TEST_MAIN - -void -scr_dump(screen_backup_t *psb) -{ - int y = 0; - char *p = NULL; - - psb->row= ft.rows; - psb->col= ft.cols; - psb->y = ft.y; - psb->x = ft.x; - p = psb->raw_memory = - malloc (ft.rows * ft.cols * (sizeof(ftchar) + sizeof(ftattr))); - - for (y = 0; y < ft.rows; y++) - { - memcpy(p, FTCMAP[y], ft.cols * sizeof(ftchar)); - p += ft.cols * sizeof(ftchar); - memcpy(p, FTAMAP[y], ft.cols * sizeof(ftattr)); - p += ft.cols * sizeof(ftattr); - } -} - -void -scr_restore(const screen_backup_t *psb) -{ - int y = 0; - char *p = NULL; - int c = ft.cols, r = ft.rows; - if (!psb || !psb->raw_memory) - return; - - p = psb->raw_memory; - c = ranged(c, 0, psb->col); - r = ranged(r, 0, psb->row); - - ft.y = ranged(psb->y, 0, ft.rows-1); - ft.x = ranged(psb->x, 0, ft.cols-1); - clrscr(); - - for (y = 0; y < r; y++) - { - memcpy(FTCMAP[y], p, c * sizeof(ftchar)); - p += psb->col * sizeof(ftchar); - memcpy(FTAMAP[y], p, c * sizeof(ftattr)); - p += psb->col * sizeof(ftattr); - } - - free(psb->raw_memory); - ft.dirty = 1; - refresh(); -} - -void -move_ansi(int y, int x) -{ - move(y, x); -} - -void -getyx_ansi(int *y, int *x) -{ - getyx(y, x); -} - -void -region_scroll_up(int top, int bottom) -{ - int i; - ftchar *c0; - ftattr *a0; - - // logic same with old screen.c - if (top > bottom) { - i = top; - top = bottom; - bottom = i; - } - if (top < 0 || bottom >= ft.rows) - return; - - c0 = FTCMAP[top]; - a0 = FTAMAP[top]; - - for (i = top; i < bottom; i++) - { - FTCMAP[i] = FTCMAP[i+1]; - FTAMAP[i] = FTAMAP[i+1]; - } - FTCMAP[bottom] = c0; - FTAMAP[bottom] = a0; - - clrregion(bottom, bottom); - fterm_markdirty(); -} - -#endif - -////////////////////////////////////////////////////////////////////////// -// adapter -////////////////////////////////////////////////////////////////////////// - -int -fterm_inbuf(void) -{ -#ifdef _PFTERM_TEST_MAIN - return 0; -#else - return num_in_buf(); -#endif -} - -void -fterm_rawc(int c) -{ -#ifdef _PFTERM_TEST_MAIN - // if (c == ESC_CHR) putchar('*'); else - putchar(c); -#else - ochar(c); -#endif -} - -void -fterm_rawnewline(void) -{ -#ifdef _PFTERM_TEST_MAIN - putchar('\n'); -#else - ochar('\r'); - ochar('\n'); -#endif -} - -void -fterm_rawflush(void) -{ -#ifdef _PFTERM_TEST_MAIN - fflush(stdout); -#else - oflush(); -#endif -} - -////////////////////////////////////////////////////////////////////////// -// test -////////////////////////////////////////////////////////////////////////// - -#ifdef _PFTERM_TEST_MAIN -int main(int argc, char* argv[]) -{ - char buf[512]; - initscr(); - - if (argc < 2) - { -#if 0 - // DBCS test - char *a1 = ANSI_COLOR(1;33) "測試" ANSI_COLOR(34) "中文" - ANSI_COLOR(7) "測試" ANSI_RESET "測試" - "測試a" ANSI_RESET "\n"; - outstr(a1); - move(0, 2); - outstr("中文1"); - outstr(ANSI_COLOR(1;33)"中文2"); - outstr(" 中\x85"); - outstr("okok herer\x8a"); - - move(0, 8); - inansistr(buf, sizeof(buf)-1); - - move(3,5); outs(ANSI_RESET "(From inansistr:) "); outs(buf); - move(7, 3); - sprintf(buf, "strlen()=%d\n", fterm_strdlen(a1)); - outstr(buf); - refresh(); - getchar(); - - outs(ANSI_COLOR(1;33) "test " ANSI_COLOR(34) "x" - ANSI_RESET "te" ANSI_COLOR(43;0;1;35) " st" - ANSI_COLOR(0) "testx\n"); - refresh(); - getchar(); - - clear(); - outs("中文中文中文中文中文中文中文中文中文中文中文中文"); - move(0, 0); - outs(" this\xFF (ff)is te.(80 tail)->\x80 (80)"); - refresh(); - getchar(); -#endif - -#if 1 - // test resize - clear(); move(1, 0); outs("test resize\n"); - doupdate(); getchar(); - // expand X - resizeterm(25,200); - clear(); move(20, 0); clrtoeol(); outs("200x25"); - doupdate(); getchar(); - // resize back - resizeterm(25,80); - clear(); move(20, 0); clrtoeol(); outs("80x25"); - doupdate(); getchar(); - // expand Y - resizeterm(60,80); - clear(); move(20, 0); clrtoeol(); outs("80x60"); - doupdate(); getchar(); - // see if you crash here. - resizeterm(60,200); - clear(); move(20, 0); clrtoeol(); outs("200x60"); - doupdate(); getchar(); -#endif - -#if 0 - // test optimization - clear(); - move(1, 0); - outs("x++ optimization test\n"); - outs("1 2 3 4 5 6 7 8 9 0\n"); - outs("1122233334444455555566666667777777788888888899999999990\n"); - refresh(); - getchar(); - - move(2, 0); - outs("1122233334444455555566666667777777788888888899999999990\n"); - outs("1 2 3 4 5 6 7 8 9 0\n"); - refresh(); - getchar(); - - rscroll(); - refresh(); - getchar(); -#endif - } else { - FILE *fp = fopen(argv[1], "r"); - int c = 0; - - while (fp && (c=getc(fp)) > 0) - { - outc(c); - } - fclose(fp); - refresh(); - } - - endwin(); - printf("\ncomplete. enter to exit.\n"); - getchar(); - return 0; -} -#endif // _PFTERM_TEST_MAIN - -#endif // defined(EXP_PFTERM) || defined(USE_PFTERM) - -// vim:ts=4:sw=4:expandtab diff --git a/mbbsd/pmore.c b/mbbsd/pmore.c deleted file mode 100644 index fde26f3a..00000000 --- a/mbbsd/pmore.c +++ /dev/null @@ -1,3732 +0,0 @@ -/* $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 maple-family BBS systems, not - * specific to any branch. - * - * Author: Hung-Te Lin (piaip), June 2005. - * - * Copyright(c) 2005-2008 Hung-Te Lin - * All Rights Reserved. - * You are free to use, modify, redistribute this program in any - * non-commercial usage (including network service). - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * MAJOR IMPROVEMENTS: - * - Clean source code, and more readable for 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 AND DONE: - * - [2005, Initial Release] - * - Optimized speed up with Scroll supporting [done] - * - Wrap long lines [done] - * - Left-right wide navigation [done] - * - DBCS friendly wrap [done] - * - Reenrtance for main procedure [done] - * - Support PTT_PRINTS [done] - * - ASCII Art movie support [done] - * - ASCII Art movie navigation keys [pending] - * - A new optimized terminal base system (piterm) [dropped] - * - - * - [2007, Interactive Movie Enhancement] - * - New Invisible Frame Header Code [done] - * - Playback Control (pause, stop, skip) [done] - * - Interactive Movie (Hyper-text) [done] - * - Preference System (like board-conf) [done] - * - Traditional Movie Compatible Mode [done] - * - movie: Optimization on relative frame numbers [done] - * - movie: Optimization on named frames (by hash) - * - - * - Support Anti-anti-idle (ex, PCMan sends up-down) - * - Better help system [pending] - * - Virtual Contatenate [pending] - * - Drop ANSI between DBCS words if outputing UTF8 [drop, done by term] - */ - -// --------------------------------------------------------------- -/* 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_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 -#define PMORE_LOG_SYSOP_EDIT // log whenever sysop uses E -#define PMORE_OVERRIDE_TIME // override time format if possible - -// if you are working with a terminal without ANSI control, -// you are using a poor term (define PMORE_USING_POOR_TERM). -#ifndef USE_PFTERM // pfterm is a good terminal system. -#define PMORE_USING_POOR_TERM -#define PMORE_WORKAROUND_CLRTOEOL // try to work with poor terminal sys -#endif // USE_PFTERM -// -------------------------------------------------------------- - -// ----------------------------------------------------------- -// Messages for localization are listed here. -#define PMORE_MSG_PREF_TITLE \ - " pmore 2007 設定選項 " -#define PMORE_MSG_PREF_TITLE_QRAW \ - " pmore 2007 快速設定選項 - 色彩(ANSI碼)顯示模式 " -#define PMORE_MSG_WARN_FAKEUSERINFO \ - " ▲此頁內容會依閱\讀者不同,原文未必有您的資料 " -#define PMORE_MSG_WARN_MOVECMD \ - " ▲此頁內容含移位碼,可能會顯示偽造的系統訊息 " -#define PMORE_MSG_SEARCH_KEYWORD \ - "[搜尋]關鍵字:" - -#define PMORE_MSG_MOVIE_DETECTED \ - " ★ 這份文件是可播放的文字動畫,要開始播放嗎? [Y/n]" -#define PMORE_MSG_MOVIE_PLAYOLD_GETTIME \ - "這可能是傳統動畫檔, 若要直接播放請輸入速度(秒): " -#define PMORE_MSG_MOVIE_PLAYOLD_AS24L \ - "傳統動畫是以 24 行為單位設計的, 要模擬 24 行嗎? (否則會用現在的行數)[Yn] " -#define PMORE_MSG_MOVIE_PAUSE \ - " >>> 暫停播放動畫,請按任意鍵繼續或 q 中斷。 <<<" -#define PMORE_MSG_MOVIE_PLAYING \ - " >>> 動畫播放中... 可按 q, Ctrl-C 或其它任意鍵停止"; -#define PMORE_MSG_MOVIE_INTERACTION_PLAYING \ - " >>> 互動式動畫播放中... 可按 q 或 Ctrl-C 停止"; -#define PMORE_MSG_MOVIE_INTERACTION_STOPPED \ - "已強制中斷互動式系統" - -// ----------------------------------------------------------- - -#include "bbs.h" - -#include -#include -#include -#include -#include -#include -#include - -// Platform Related. NoSync is faster but if we don't have it... -// Experimental: POPULATE should work faster? -#ifdef MAP_NOSYNC -#define MF_MMAP_OPTION (MAP_NOSYNC|MAP_SHARED) -#elif defined(MAP_POPULATE) -#define MF_MMAP_OPTION (MAP_POPULATE|MAP_SHARED) -#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 - * - * DETAIL - * - 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) - - -// --------------------------------------------- - -// --------------------------- - -/* 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_CLRTOEND ESC_STR "[K" -#define ANSI_MOVETO(y,x) ESC_STR "[" #y ";" #x "H" - -#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_CLRTOEOL - int i; - move(y, x); - for (i = x; i < t_columns; i++) - outc(' '); - clrtoeol(); - move(y, x); // this is required, due to outc(). -#else - move(y, x); - clrtoeol(); -#endif -} - -// --------------------------- - -// ---------------------------
-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_OPT_CLEAR = 0, - MFDISP_OPT_OPTIMIZED, - MFDISP_OPT_FORCEDIRTY, - - // prefs - - MFDISP_WRAP_TRUNCATE = 0, - MFDISP_WRAP_WRAP, - MFDISP_WRAP_MODES, - - MFDISP_SEP_NONE = 0x00, - MFDISP_SEP_LINE = 0x01, - MFDISP_SEP_WRAP = 0x02, - MFDISP_SEP_OLD = MFDISP_SEP_LINE | MFDISP_SEP_WRAP, - MFDISP_SEP_MODES= 0x04, - - MFDISP_RAW_NA = 0x00, - MFDISP_RAW_NOANSI, - MFDISP_RAW_PLAIN, - MFDISP_RAW_MODES, - -}; - -#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 -// ---------------------------
- -// --------------------------- -/* browsing preference */ -typedef struct -{ - /* mode flags */ - unsigned char - wrapmode, // wrap? - separator, // separator style - wrapindicator, // show wrap indicators - - oldwrapmode, // traditional wrap - oldstatusbar, // traditional statusbar - rawmode; // show file as-is. -} MF_BrowsingPreference; - -MF_BrowsingPreference 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 -#define FH_FLOATS (2) // right floating, name and val -static const char *_fh_disp_heads[FH_HEADERS] = - {"作者", "標題", "時間", "轉信"}; - -typedef struct -{ - int lines; // header lines - unsigned char *headers[FH_HEADERS]; - unsigned char *floats [FH_FLOATS]; -} 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; } - -// Artwork -#define OPTATTR_NORMAL ANSI_COLOR(0;34;47) -#define OPTATTR_NORMAL_KEY ANSI_COLOR(0;31;47) -#define OPTATTR_SELECTED ANSI_COLOR(0;1;37;46) -#define OPTATTR_SELECTED_KEY ANSI_COLOR(0;31;46) -#define OPTATTR_BAR ANSI_COLOR(0;1;30;47) - -// --------------------------- - -// ---------------------------------------------
- -// --------------------------------------------- -#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 *options, - *optkeys; - unsigned char mode, - compat24, - interactive, - pause; -} MF_Movie; - -MF_Movie mfmovie; - -#define STOP_MOVIE() { \ - mfmovie.options = NULL; \ - mfmovie.pause = 0; \ - if (mfmovie.mode == MFDISP_MOVIE_PLAYING) \ - mfmovie.mode = MFDISP_MOVIE_YES; \ - if (mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD) \ - mfmovie.mode = MFDISP_MOVIE_NO; \ - mf_determinemaxdisps(MFNAV_PAGE, 0); \ - mf_forward(0); \ -} - -#define RESET_MOVIE() { \ - mfmovie.mode = MFDISP_MOVIE_UNKNOWN; \ - mfmovie.options = NULL; \ - mfmovie.optkeys = NULL; \ - mfmovie.compat24 = 1; \ - mfmovie.pause = 0; \ - mfmovie.interactive = 0; \ - mfmovie.synctime.tv_sec = mfmovie.synctime.tv_usec = 0; \ - mfmovie.frameclk.tv_sec = 1; mfmovie.frameclk.tv_usec = 0; \ -} - -#define MOVIE_IS_PLAYING() \ - ((mfmovie.mode == MFDISP_MOVIE_PLAYING) || \ - (mfmovie.mode == MFDISP_MOVIE_PLAYING_OLD)) - -unsigned char * - mf_movieFrameHeader(unsigned char *p, unsigned char *end); - -int pmore_wait_key(struct timeval *ptv, int dorefresh); -int mf_movieNextFrame(); -int mf_movieSyncFrame(); -int mf_moviePromptPlaying(int type); -int mf_movieMaskedInput(int c); - -void mf_float2tv(float f, struct timeval *ptv); - -#define MOVIE_MIN_FRAMECLK (0.1f) -#define MOVIE_MAX_FRAMECLK (3600.0f) -#define MOVIE_SECOND_U (1000000L) -#define MOVIE_ANTI_ANTI_IDLE - -// some magic value that your igetch() will never return -#define MOVIE_KEY_ANY (0x4d464b41) - -#ifndef MOVIE_KEY_BS2 -#define MOVIE_KEY_BS2 (0x7f) -#endif - -#endif -// --------------------------------------------- - -// 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; - } - - // BSD mmap advise. comment if your OS does not support this. - madvise(mf.start, mf.len, MADV_SEQUENTIAL); - - 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 separators */ - if(mf.maxlinenoS > 0 && - fh.lines >= mf.maxlinenoS && - bpref.separator & MFDISP_SEP_WRAP) - { - mf_determinemaxdisps(+1, 1); - } - - return 1; -} - -void -mf_detach() -{ - mf_freeHeaders(); - if(mf.start) { - munmap(mf.start, mf.len); - RESETMF(); - } -} - -/* - * 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++) - free(fh.headers[i]); - for (i = 0; i < FH_FLOATS; i++) - free(fh.floats[i]); - RESETFH(); - } -} - -void -mf_parseHeaders() -{ - /* file format: - * AUTHOR: author BOARD: blah <- headers[0], floats[0], floats[1] - * XXX: xxx <- headers[1] - * XXX: xxx <- headers[n] - * [blank, fill with separator] <- 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); - -#ifdef PMORE_OVERRIDE_TIME - // (deprecated: too many formats for newsgroup) - // try to see if this is a valid time line - // use strptime to convert -#endif // PMORE_OVERRIDE_TIME - - // 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; - -#define RESET_OVERRIDE_MSG() { override_attr = override_msg = 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 needMove2bot = 0; - - 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 - -#if defined(PMORE_USE_ASCII_MOVIE) && !defined(PMORE_USING_POOR_TERM) - // For movies, maybe clear() is better. - // Let's enable for good terminals (which does not need workarounds) - if (MOVIE_IS_PLAYING()) - { - clear(); move(0, 0); - } else -#endif // PMORE_USE_ASCII_MOVIE && (!PMORE_USING_POOR_TERM) - - /* 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; - -#if defined(USE_PFTERM) - // In fact, pfterm will flash black screen when scrolling pages... - // So it may work better if we refresh whole screen. - if (i >= b_lines / 2) - { - clear(); move(0, 0); - scrll = MFDISP_PAGE; - } else -#endif // defined(USE_PFTERM) - - 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_DETECTED) - { - // detected only applies for first page. - // since this is not very often, let's prevent - // showing control codes. - if(mf_movieFrameHeader(mf.dispe, mf.end)) - MFDISP_SKIPCURLINE(); - } - else if(mfmovie.mode == MFDISP_MOVIE_UNKNOWN || - mfmovie.mode == MFDISP_MOVIE_PLAYING) - { - if(mf_movieFrameHeader(mf.dispe, mf.end)) - 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, separator, or normal text) - * is current line. - */ - else if (currline == fh.lines && bpref.rawmode == MFDISP_RAW_NA) - { - /* case 1, header separator line */ - if (bpref.separator & MFDISP_SEP_LINE) - { - outs(ANSI_COLOR(36)); - for(col = 0; col < headerw; col+=2) - { - // prints("%02d", col); - outs("─"); - } - outs(ANSI_RESET); - } - - /* Traditional 'more' adds separator 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.separator & 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.wrapindicator) - { - 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 // PMORE_RESTRICT_ANSI_MOVEMENT - // some user cannot live without this. - // make them happy. - newline_default = newline = MFDISP_NEWLINE_MOVE; -#ifdef PMORE_USE_ASCII_MOVIE - // relax for movies - if (!MOVIE_IS_PLAYING()) -#endif // PMORE_USE_ASCII_MOVIE - { - override_attr = ANSI_COLOR(1;37;41); - override_msg = PMORE_MSG_WARN_MOVECMD; - } -#endif // PMORE_RESTRICT_ANSI_MOVEMENT - needMove2bot = 1; - } - 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)); - memcpy(buf, mf.dispe, 3); // ^[*s - mf.dispe += 2; - - if(bpref.rawmode) - buf[0] = '*'; - else - { -#ifdef LOW_SECURITY -# define PTTPRINT_WARN_PATTERN "slpnbm" -#else -# define PTTPRINT_WARN_PATTERN "slpn" -#endif // LOW_SECURITY - if(strchr(PTTPRINT_WARN_PATTERN, buf[2]) != NULL) - { - override_attr = ANSI_COLOR(1;37;41); - override_msg = PMORE_MSG_WARN_FAKEUSERINFO; - } - 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.wrapindicator) - 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.wrapindicator && 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 separator - * however this code is rarely used now. - * only if no preload file. - */ - if (bpref.separator & 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 separator? - * 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; - - if (needMove2bot) - move(b_lines, 0); -} - -/* --------------------- MAIN PROCEDURE ------------------------- */ -void pmore_Preference(); -void pmore_QuickRawModePref(); -void pmore_Help(); - -static const char * const pmore_help[] = { - "\0閱\讀文章功\能鍵使用說明", - "\01游標移動功\能鍵", - "(k/↑) (j/↓/Enter) 上捲/下捲一行", - "(^B/PgUp/BackSpace) 上捲一頁", - "(^F/PgDn/Space/→) 下捲一頁", - "(,//TAB) 左/右捲動", - "(0/g/Home) ($/G/End) 檔案開頭/結尾", - "數字鍵 1-9 (;/:) 跳至輸入的頁數或行數", - "\01進階功\能鍵", - "(/) (s) 搜尋關鍵字/切換至其它看板", - "(n/N) 重複正/反向搜尋", - "(f/b) 跳至下/上篇", - "(a/A) 跳至同一作者下/上篇", - "(t/[-/]+) 主題式閱\讀:循序/前/後篇", - "\01其他功\能鍵", - - // the line below is already aligned, because of the backslash. - "(o)/(\\) 選項設定/色彩顯示模式", - -#if defined (PMORE_USE_ASCII_MOVIE) || defined(RET_DOCHESSREPLAY) - "(p)/(z) 播放動畫/棋局打譜", -#endif // defined(PMORE_USE_ASCII_MOVIE) || defined(RET_DOCHESSREPLAY) -#ifdef RET_COPY2TMP - "(Ctrl-T) 存入暫存檔", -#endif - "(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 2007, 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; - RESETFH(); - RESETMF(); - - 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 USE_BBSLUA - // TODO prompt BBS Lua status here. -#endif // USE_BBSLUA - -#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 = PMORE_MSG_MOVIE_DETECTED; - - outs(ANSI_RESET ANSI_COLOR(1;33;44)); - w -= strlen(s); outs(s); - - while(w-- > 0) outc(' '); outs(ANSI_RESET ANSI_CLRTOEND); - 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(); - // remove override messages - RESET_OVERRIDE_MSG(); - continue; - } - /* else, we have to clean up. */ - move(b_lines, 0); - clrtoeol(); - } - break; - - case MFDISP_MOVIE_PLAYING_OLD: - case MFDISP_MOVIE_PLAYING: - - mf_moviePromptPlaying(0); - - // doing refresh() here is better, - // to prevent that we forgot to refresh - // in SyncFrame. - refresh(); - - if(mf_movieSyncFrame()) - { - /* user did not hit anything. - * play next frame. - */ - if(mfmovie.mode == MFDISP_MOVIE_PLAYING) - { - if(!mf_movieNextFrame()) - { - STOP_MOVIE(); - - 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 { - /* TODO simple navigation here? */ - - /* stop playing */ - if(mfmovie.mode == MFDISP_MOVIE_PLAYING) - { - STOP_MOVIE(); - 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.separator & 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); - RESET_OVERRIDE_MSG(); - } - 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 = 17; - - 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) { - /* -------------- NEW EXITING KEYS ------------------ */ -#ifdef RET_DOREPLY - case 'r': case 'R': - flExit = 1, retval = RET_DOREPLY; - break; - case 'Y': case 'y': - flExit = 1, retval = RET_DOREPLYALL; - break; -#endif -#ifdef RET_DORECOMMEND - // recommend - case '%': - case 'X': - flExit = 1, retval = RET_DORECOMMEND; - break; -#endif -#ifdef RET_DOQUERYINFO - case 'Q': // info query interface - flExit = 1, retval = RET_DOQUERYINFO; - break; -#endif -#ifdef RET_DOSYSOPEDIT - case 'E': - flExit = 1, retval = RET_DOSYSOPEDIT; - break; -#endif -#ifdef RET_DOCHESSREPLAY - case 'z': - flExit = 1, retval = RET_DOCHESSREPLAY; - break; -#endif -#ifdef RET_COPY2TMP - case Ctrl('T'): - flExit = 1, retval = RET_COPY2TMP; - break; -#endif -#ifdef RET_SELECTBRD - case 's': - flExit = 1, retval = RET_SELECTBRD; - break; -#endif - /* ------------------ EXITING KEYS ------------------ */ - 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: - flExit = 1, retval = FULLUPDATE; - break; - 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 '/': - { - char sbuf[81] = ""; - char ans[4] = "n"; - - if(sr.search_str) { - free(sr.search_str); - sr.search_str = NULL; - } - - getdata(b_lines - 1, 0, PMORE_MSG_SEARCH_KEYWORD, 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 'h': case 'H': case KEY_F1: - case '?': - // help - show_help(pmore_help); - MFDISP_DIRTY(); - break; - -#ifdef PMORE_NOTIFY_NEWPREF - //let's be backward compatible! - case 'l': - case 'w': - case 'W': - case '|': - vmsg("這個按鍵已整合進新的設定 (o) 了"); - break; - -#endif // PMORE_NOTIFY_NEWPREF - - case '\\': // everyone loves backslash, let's keep it. - pmore_QuickRawModePref(); - MFDISP_DIRTY(); - break; - - case 'o': - pmore_Preference(); - MFDISP_DIRTY(); - break; - -#if defined(USE_BBSLUA) && defined(RET_DOBBSLUA) - case 'P': - vmsg("非常抱歉,BBS-Lua 的熱鍵已改為 L ,請改按 L"); - break; - - case 'L': - case 'l': - flExit = 1, retval = RET_DOBBSLUA; - break; -#endif - -#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, - PMORE_MSG_MOVIE_PLAYOLD_GETTIME, - buf, 8, LCECHO); - - if(buf[0]) - { - float nf = 0; - nf = atof(buf); // sscanf(buf, "%f", &nf); - RESET_MOVIE(); - - mfmovie.mode = MFDISP_MOVIE_PLAYING_OLD; - mf_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, - PMORE_MSG_MOVIE_PLAYOLD_AS24L, - 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 - - } - /* 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; -} - -// ---------------------------------------------------- Preference and Help - -static void -pmore_prefEntry( - int isel, - const char *key, int szKey, - const char *text,int szText, - const char* options) -{ - int i = 23; - // print key/text - outs(" " ANSI_COLOR(1;31)); //OPTATTR_NORMAL_KEY); - if (szKey < 0) szKey = strlen(key); - if (szKey > 0) outs_n(key, szKey); - outs(ANSI_RESET " "); - if (szText < 0) szText = strlen(text); - if (szText > 0) outs_n(text, szText); - - i -= szKey + szText; - if (i < 0) i+= 20; // one more chance - while (i-- > 0) outc(' '); - - // print options - i = 0; - while (*options) - { - if (*options == '\t') - { - // blank option, skip it. - i++, options++; - continue; - } - - if (i > 0) - outs(ANSI_COLOR(1;30) " |" ANSI_RESET); //OPTATTR_BAR " | " ANSI_RESET); - - // test if option has hotkey - if (*options && *options != '\t' && - *(options+1) && *(options+1) == '.') - { - // found hotkey - outs(ANSI_COLOR(1;31)); //OPTATTR_NORMAL_KEY); - outc(*options); - outs(ANSI_RESET); - options +=2; - } - - if (i == isel) - { - outs(ANSI_COLOR(1;36) "*");// OPTATTR_SELECTED); - } - else - outc(' '); - - while (*options && *options != '\t') - outc(*options++); - - outs(ANSI_RESET); - - if (*options) - i++, options ++; - } - outc('\n'); -} - -void -pmore_PromptBar(const char *caption, int shadow) -{ - int i = 0; - - if (shadow) - { - outs(ANSI_COLOR(0;1;30)); - for(i = 0; i+2 < t_columns ; i+=2) - outs("▁"); - outs(ANSI_RESET "\n"); - } - else - i = t_columns -2; - - outs(ANSI_COLOR(7)); - outs(caption); - for(i -= strlen(caption); i > 0; i--) - outs(" "); - outs(ANSI_RESET "\n"); -} - -void -pmore_QuickRawModePref() -{ - int ystart = b_lines -2; - -#ifdef HAVE_GRAYOUT - grayout(0, ystart-1, GRAYOUT_DARK); -#endif // HAVE_GRAYOUT - - while(1) - { - move(ystart, 0); - clrtobot(); - pmore_PromptBar(PMORE_MSG_PREF_TITLE_QRAW, 0); - - // list options - pmore_prefEntry(bpref.rawmode, - "\\", 1, "色彩顯示方式:", -1, - "1.預設格式化內容\t2.原始ANSI控制碼\t3.純文字"); - - switch(vmsg("請調整設定 (1-3 可直接選定,\\可切換) 或其它任意鍵結束。")) - { - case '\\': - bpref.rawmode = (bpref.rawmode+1) % MFDISP_RAW_MODES; - break; - case '1': - bpref.rawmode = MFDISP_RAW_NA; - return; - case '2': - bpref.rawmode = MFDISP_RAW_NOANSI; - return; - case '3': - bpref.rawmode = MFDISP_RAW_PLAIN; - return; - case KEY_LEFT: - if (bpref.rawmode > 0) bpref.rawmode --; - break; - case KEY_RIGHT: - if (bpref.rawmode < MFDISP_RAW_MODES-1) bpref.rawmode ++; - break; - default: - return; - } - } -} - -void -pmore_Preference() -{ - int ystart = b_lines - 9; - // TODO even better pref navigation, like arrow keys - // static int lastkey = '\\'; // default key - -#ifdef HAVE_GRAYOUT - grayout(0, ystart-1, GRAYOUT_DARK); -#endif // HAVE_GRAYOUT - - while (1) - { - move(ystart, 0); - clrtobot(); - pmore_PromptBar(PMORE_MSG_PREF_TITLE, 1); - outs("\n"); - - // list options - pmore_prefEntry(bpref.rawmode, - "\\", 1, "色彩顯示方式:", -1, - "預設格式化內容\t原始ANSI控制碼\t純文字"); - - pmore_prefEntry(bpref.wrapmode, - "w", 1, "斷行方式:", -1, - "直接截行\t自動斷行"); - - pmore_prefEntry(bpref.wrapindicator, - "m", 1, "斷行符號:", -1, - "不顯示\t顯示"); - - pmore_prefEntry(bpref.separator, - "l", 1, "文章標頭分隔線:", -1, - "無\t單行\t\t傳統分隔線加空行"); - - pmore_prefEntry(bpref.oldstatusbar, - "t", 1, "傳統狀態列與斷行方式: ", -1, - "停用\t啟用"); - - switch(vmsg("請調整設定或其它任意鍵結束。")) - { - case '\\': - case '|': - bpref.rawmode = (bpref.rawmode+1) % MFDISP_RAW_MODES; - break; - case 'w': - bpref.wrapmode = (bpref.wrapmode+1) % MFDISP_WRAP_MODES; - break; - case 'm': - bpref.wrapindicator = !bpref.wrapindicator; - break; - case 'l': - // there's no MFDISP_SEP_WRAP only mode. - if (++bpref.separator == MFDISP_SEP_WRAP) - bpref.separator ++; - bpref.separator %= MFDISP_SEP_MODES; - break; - case 't': - bpref.oldwrapmode = !bpref.oldwrapmode; - bpref.oldstatusbar = !bpref.oldstatusbar; - break; - - default: - // finished settings - return; - } - } -} - -void -pmore_Help() -{ - clear(); - stand_title("pmore 使用說明"); - vmsg(""); -} - -// ---------------------------------------------------- Extra modules - -#ifdef PMORE_USE_ASCII_MOVIE -void -mf_float2tv(float f, struct timeval *ptv) -{ - if(f < MOVIE_MIN_FRAMECLK) - f = MOVIE_MIN_FRAMECLK; - if (f > MOVIE_MAX_FRAMECLK) - f = MOVIE_MAX_FRAMECLK; - - ptv->tv_sec = (long) f; - ptv->tv_usec = (f - (long)f) * MOVIE_SECOND_U; -} - -int -mf_str2float(unsigned char *p, unsigned char *end, float *pf) -{ - char buf[16] = {0}; - int cbuf = 0; - - /* process time */ - while ( p < end && - cbuf < sizeof(buf)-1 && - (isdigit(*p) || *p == '.' || *p == '+' || *p == '-')) - buf[cbuf++] = *p++; - - if (!cbuf) - return 0; - - buf[cbuf] = 0; - *pf = atof(buf); - - return 1; -} - -/* - * 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_key(struct timeval *ptv, int dorefresh) -{ - int sel = 0; - fd_set readfds; - int c = 0; - - if (dorefresh) - refresh(); - - do { - // if already something in queue, - // detemine if ok to break. - while ( num_in_buf() > 0) - { - if (!mf_movieMaskedInput((c = igetch()))) - return c; - } - - // wait for real user interaction - FD_ZERO(&readfds); - FD_SET(0, &readfds); - -#ifdef STATINC - STATINC(STAT_SYSSELECT); -#endif - sel = select(1, &readfds, NULL, NULL, ptv); - - // if select() stopped by other interrupt, - // do it again. - if (sel < 0 && errno == EINTR) - continue; - - // if (sel > 0), try to read. - // note: there may be more in queue. - // will be processed at next loop. - if (sel > 0 && !mf_movieMaskedInput((c = igetch()))) - return c; - - } while (sel > 0); - - // now, maybe something for read (sel > 0) - // or time out (sel == 0) - // or weird error (sel < 0) - - // sync clock(now) if timeout. - if (sel == 0) - syncnow(); - - return (sel == 0) ? 0 : 1; -} - -// type : 1 = option selection, 0 = normal -int -mf_moviePromptPlaying(int type) -{ - int w = t_columns - 1; - // s may change to anykey... - const char *s = PMORE_MSG_MOVIE_PLAYING; - - if (override_msg) - { - // we must warn user about something... - move(type ? b_lines-2 : b_lines-1, 0); // clrtoeol? - outs(ANSI_RESET); - if (override_attr) outs(override_attr); - w -= strlen(override_msg); - outs(override_msg); - while(w-- > 0) outc(' '); - - outs(ANSI_RESET ANSI_CLRTOEND); - RESET_OVERRIDE_MSG(); - w = t_columns -1; - } - - move(type ? b_lines-1 : b_lines, 0); // clrtoeol? - - if (type) - { - outs(ANSI_RESET ANSI_COLOR(1;34;47)); - s = " >> 請輸入選項: (互動式動畫播放中,可按 q 或 Ctrl-C 中斷)"; - } - else if (mfmovie.interactive) - { - outs(ANSI_RESET ANSI_COLOR(1;34;47)); - s = PMORE_MSG_MOVIE_INTERACTION_PLAYING; - } else { - outs(ANSI_RESET ANSI_COLOR(1;30;47)); - } - - w -= strlen(s); outs(s); - - while(w-- > 0) outc(' '); outs(ANSI_RESET ANSI_CLRTOEND); - if (type) - { - move(b_lines, 0); - clrtoeol(); - } - - return 1; -} - -// return = printed characters -int -mf_moviePromptOptions( - int isel, int maxsel, - int key, - unsigned char *text, unsigned int szText) -{ - unsigned char *s = text; - int printlen = 0; - // determine if we need separator - if (maxsel) - { - outs(OPTATTR_BAR "|" ); - printlen += 1; - } - - // highlight if is selected - if (isel == maxsel) - outs(OPTATTR_SELECTED_KEY); - else - outs(OPTATTR_NORMAL_KEY); - - outc(' '); printlen ++; - - if (key > ' ' && key < 0x80) // isprint(key)) - { - outc(key); - printlen += 1; - } else { - // named keys - printlen += 2; - - if (key == KEY_UP) outs("↑"); - else if (key == KEY_LEFT) outs("←"); - else if (key == KEY_DOWN) outs("↓"); - else if (key == KEY_RIGHT) outs("→"); - else if (key == KEY_PGUP) { outs("PgUp"); printlen += 2; } - else if (key == KEY_PGDN) { outs("PgDn"); printlen += 2; } - else if (key == KEY_HOME) { outs("Home"); printlen += 2; } - else if (key == KEY_END) { outs("End"); printlen ++; } - else if (key == KEY_INS) { outs("Ins"); printlen ++; } - else if (key == KEY_DEL) { outs("Del"); printlen ++; } - else if (key == '\b') { outs("←BS"); printlen += 2; } - // else if (key == MOVIE_KEY_ANY) // same as default - else printlen -= 2; - } - - // check text: we don't allow special char. - if (text && szText && text[0]) - { - while (s < text + szText && *s > ' ') - { - s++; - } - szText = s - text; - } - else - { - // default option text - text = (unsigned char*)"☆"; - szText = ustrlen(text); - } - - if (szText) - { - if (isel == maxsel) - outs(OPTATTR_SELECTED); - else - outs(OPTATTR_NORMAL); - outs_n((char*)text, szText); - printlen += szText; - } - - outc(' '); printlen ++; - - // un-highlight - if (isel == maxsel) - outs(OPTATTR_NORMAL); - - return printlen; -} - -int -mf_movieNamedKey(int c) -{ - switch (c) - { - case 'u': return KEY_UP; - case 'd': return KEY_DOWN; - case 'l': return KEY_LEFT; - case 'r': return KEY_RIGHT; - - case 'b': return '\b'; - - case 'H': return KEY_HOME; - case 'E': return KEY_END; - case 'I': return KEY_INS; - case 'D': return KEY_DEL; - case 'P': return KEY_PGUP; - case 'N': return KEY_PGDN; - - case 'a': return MOVIE_KEY_ANY; - default: - break; - } - return 0; -} - -int mf_movieIsSystemBreak(int c) -{ - return (c == 'q' || c == 'Q' || c == Ctrl('C')) - ? 1 : 0; -} - -int -mf_movieMaskedInput(int c) -{ - unsigned char *p = mfmovie.optkeys; - - if (!p) - return 0; - - // some keys cannot be masked - if (mf_movieIsSystemBreak(c)) - return 0; - - // treat BS and DEL as same one - if (c == MOVIE_KEY_BS2) - c = '\b'; - - // general look up - while (p < mf.end && *p && *p != '\n' && *p != '#') - { - if (*p == '@' && mf.end - p > 1 - && isalnum(*(p+1))) // named key - { - p++; - - // special: 'a' masks all - if (*p == 'a' || mf_movieNamedKey(*p) == c) - return 1; - } else { - if ((int)*p == c) - return 1; - } - p++; - } - return 0; -} - -unsigned char * -mf_movieFrameHeader(unsigned char *p, unsigned char *end) -{ - // ANSI has ESC_STR [8m as "Conceal" but - // not widely supported, even PieTTY. - // So let's go back to fixed format... - static char *patHeader = "==" ESC_STR "[30;40m^L"; - static char *patHeader2= ESC_STR "[30;40m^L"; // patHeader + 2; // "==" - // static char *patHeader3= ESC_STR "[m^L"; - static size_t szPatHeader = 12; // strlen(patHeader); - static size_t szPatHeader2 = 10; // strlen(patHeader2); - // static size_t szPatHeader3 = 5; // strlen(patHeader3); - - size_t sz = end - p; - - if (sz < 1) return NULL; - if(*p == 12) // ^L - return p+1; - - if (sz < 2) return NULL; - if( *p == '^' && - *(p+1) == 'L') - return p+2; - - // Add more frame headers - - /* // *[m seems not so common, skip. - if (sz < szPatHeader3) return NULL; - if (memcmp(p, patHeader3, szPatHeader3) == 0) - return p + szPatHeader3; - */ - - if (sz < szPatHeader2) return NULL; - if (memcmp(p, patHeader2, szPatHeader2) == 0) - return p + szPatHeader2; - - if (sz < szPatHeader) return NULL; - if (memcmp(p, patHeader, szPatHeader) == 0) - return p + szPatHeader; - - return NULL; -} - -int -mf_movieGotoNamedFrame(const unsigned char *name, const unsigned char *end) -{ - const unsigned char *p = name; - size_t sz = 0; - - // resolve name first - while (p < end && isalnum(*p)) - p++; - sz = p - name; - - if (sz < 1) return 0; - - // now search entire file for frame - - mf_goTop(); - - do - { - if ((p = mf_movieFrameHeader(mf.disps, mf.end)) == NULL || - *p != ':') - continue; - - // got some frame. let's check the name - p++; - if (mf.end - p < sz) - continue; - - // check: target of p must end. - if (mf.end -p > sz && - isalnum(*(p+sz))) - continue; - - if (memcmp(p, name, sz) == 0) - return 1; - - } while (mf_forward(1) > 0); - return 0; -} - -int -mf_movieGotoFrame(int fno, int relative) -{ - if (!relative) - mf_goTop(); - else if (fno > 0) - mf_forward(1); - // for absolute, fno = 1..N - - if (fno > 0) - { - // move forward - do { - while (mf_movieFrameHeader(mf.disps, mf.end) == NULL) - { - if (mf_forward(1) < 1) - return 0; - } - // found frame. - if (--fno > 0) - mf_forward(1); - } while (fno > 0); - } else { - // backward - // XXX check if we reached head? - while (fno < 0) - { - do { - if (mf_backward(1) < 1) - return 0; - } while (mf_movieFrameHeader(mf.disps, mf.end) == NULL); - fno ++; - } - } - return 1; -} - -int -mf_parseOffsetCmd( - unsigned char *s, unsigned char *end, - int base) -{ - // return is always > 0, or base. - int v = 0; - - if (s >= end) - return base; - - v = atoi((char*)s); - - if (*s == '+' || *s == '-') - { - // relative format - v = base + v; - } else if (isdigit(*s)) { - // absolute format - } else { - // error format? - v = 0; - } - - if (v <= 0) - v = base; - return v; -} - -int -mf_movieExecuteOffsetCmd(unsigned char *s, unsigned char *end) -{ - // syntax: type[+-]offset - // - // type: l(line), f(frame), p(page). - // +-: if empty, absolute. if assigned, relative. - // offset: is 1 .. N for all cases - - int curr = 0, newno = 0; - - switch(*s) - { - case 'p': - // by page - curr = (mf.lineno / MFDISP_PAGE) + 1; - newno = mf_parseOffsetCmd(s+1, end, curr); -#ifdef DEBUG - vmsgf("page: %d -> %d\n", curr, newno); -#endif // DEBUG - // prevent endless loop - if (newno == curr) - return 0; - - return mf_goto((newno -1) * MFDISP_PAGE); - - case 'l': - // by lines - curr = mf.lineno + 1; - newno = mf_parseOffsetCmd(s+1, end, curr); -#ifdef DEBUG - vmsgf("line: %d -> %d\n", curr, newno); -#endif // DEBUG - // prevent endless loop - if (newno == curr) - return 0; - - return mf_goto(newno-1); - - case 'f': - // by frame [optimized] - if (++s >= end) - return 0; - - curr = 0; - newno = atoi((char*)s); - if (*s == '+' || *s == '-') // relative - { - curr = 1; - if (newno == 0) - return 0; - } else { - // newno starts from 1 - if (newno <= 0) - return 0; - } - return mf_movieGotoFrame(newno, curr); - - case ':': - // by names - return mf_movieGotoNamedFrame(s+1, end); - - default: - // not supported yet - break; - } - return 0; -} - - -int -mf_movieOptionHandler(unsigned char *opt, unsigned char *end) -{ - // format: #time#key1,cmd,text1#key2,cmd,text2# - // if key is empty, use auto-increased key. - // if cmd is empty, invalid. - // if text is empty, display key only or hide if time is assigned. - - int ient = 0; - unsigned char *pkey = NULL, *cmd = NULL, *text = NULL; - unsigned int szCmd = 0, szText = 0; - unsigned char *p = opt; - int key = 0; - float optclk = -1.0f; // < 0 means infinite wait - struct timeval tv; - - int isel = 0, c = 0, maxsel = 0, selected = 0; - int newOpt = 1; - int hideOpts = 0; - int promptlen = 0; - - // TODO handle line length - // TODO restrict option size - - // set up timer (opt points to optional time now) - do { - p = opt; - while ( p < end && - (isdigit(*p) || *p == '+' || *p == '-' || *p == '.') ) - p++; - - // if no number, abort. - if (p == opt || (p < end && *p != '#')) break; - - // p looks like valid timer now - if (mf_str2float(opt, p, &optclk)) - { - // conversion failed. - if (optclk == 0) - optclk = -1.0f; - } - - // point opt to new var after #. - opt = p + 1; - break; - - } while (1); - - // UI Selection - do { - // do c test here because we need parser to help us - // finding the selection - if (c == '\r' || c == '\n' || c == ' ') - { - selected = 1; - } - - newOpt = 1; - promptlen = 0; - - // parse (key,frame,text) - for ( p = opt, ient = 0, maxsel = 0, - key = '0'; // default command - p < end && *p != '\n'; p++) - { - if (newOpt) - { - // prepare for next loop - pkey = p; - cmd = text = NULL; - szCmd = szText = 0; - ient = 0; - newOpt = 0; - } - - // calculation of fields - if (*p == ',' || *p == '#') - { - switch (++ient) - { - // case 0 is already processed. - case 1: - cmd = p+1; - break; - - case 2: - text = p+1; - szCmd = p - cmd; - break; - - case 3: - szText = p - text; - - default: - // unknown parameters - break; - } - } - - // ready to parse one option - if (*p == '#') - { - newOpt = 1; - - // first, fix pointers - if (szCmd == 0 || *cmd == ',' || *cmd == '#') - { cmd = NULL; szCmd = 0; } - - // quick abort if option is invalid. - if (!cmd) - continue; - - if (szText == 0 || *text == ',' || *text == '#') - { text = NULL; szText = 0; } - - // assign key - if (*pkey == ',' || *pkey == '#') - key++; - else - { - // named key? - int nk = 0; - - // handle special case @a (all) here - - if (*pkey == '@' && - ++ pkey < end && - (nk = mf_movieNamedKey(*pkey))) - { - key = nk; - } else { - key = *pkey; - } - // warning: pkey may be changed after this. - } - - // calculation complete. - - // print option - if (!hideOpts && maxsel == 0 && text == NULL) - { - hideOpts = 1; - mf_moviePromptPlaying(0); - // prevent more hideOpt test - } - - if (!hideOpts) - { - // print header - if (maxsel == 0) - { - pmore_clrtoeol(b_lines-1, 0); - mf_moviePromptPlaying(1); - } - - promptlen += mf_moviePromptOptions( - isel, maxsel, key, - text, szText); - } - - // handle selection - if (c == key || - (key == MOVIE_KEY_ANY && c != 0)) - { - // hotkey pressed - selected = 1; - isel = maxsel; - } - - maxsel ++; - - // parse complete. - // test if this item is selected. - if (selected && isel == maxsel - 1) - break; - } - } - - if (selected || maxsel == 0) - break; - - // finish the selection bar - if (!hideOpts && maxsel > 0) - { - int iw = 0; - for (iw = 0; iw + promptlen < t_columns-1; iw++) - outc(' '); - outs(ANSI_RESET ANSI_CLRTOEND); - } - - // wait for input - if (optclk > 0) - { - // timed interaction - - // disable optkeys to allow masked input - unsigned char *tmpopt = mfmovie.optkeys; - mfmovie.optkeys = NULL; - - mf_float2tv(optclk, &tv); - c = pmore_wait_key(&tv, 1); - mfmovie.optkeys = tmpopt; - - // if timeout, drop. - if (!c) - return 0; - } else { - // infinite wait - c = igetch(); - } - - // parse keyboard input - if (mf_movieIsSystemBreak(c)) - { - // cannot be masked, - // also force stop of playback - STOP_MOVIE(); - vmsg(PMORE_MSG_MOVIE_INTERACTION_STOPPED); - return 0; - } - - // treat BS and DEL as same one - if (c == MOVIE_KEY_BS2) - c = '\b'; - - // standard navigation keys. - if (mf_movieMaskedInput(c)) - continue; - - // these keys can be masked - if (c == KEY_LEFT || c == KEY_UP) - { - if (isel > 0) isel --; - } - else if (c == KEY_RIGHT || c == KEY_TAB || c == KEY_DOWN) - { - if (isel < maxsel-1) isel ++; - } - else if (c == KEY_HOME) - { - isel = 0; - } - else if (c == KEY_END) - { - isel = maxsel -1; - } - - } while ( !selected ); - - // selection is made now. - outs(ANSI_RESET); // required because options bar may be not closed - pmore_clrtoeol(b_lines, 0); - -#ifdef DEBUG - prints("selection: %d\n", isel); - igetch(); -#endif - - // Execute Selection - if (!cmd || !szCmd) - return 0; - - return mf_movieExecuteOffsetCmd(cmd, cmd+szCmd); -} - -/* - * mf_movieSyncFrame: - * wait until synchronization, and flush break key (if any). - * return meaning: - * I've got synchronized. - * If no (user breaks), return 0 - */ -int mf_movieSyncFrame() -{ - if (mfmovie.pause) - { - int c = 0; - mfmovie.pause = 0; - c = vmsg(PMORE_MSG_MOVIE_PAUSE); - if (mf_movieIsSystemBreak(c)) - return 0; - return 1; - } - else if (mfmovie.options) - { - unsigned char *opt = mfmovie.options; - mfmovie.options = NULL; - mf_movieOptionHandler(opt, mf.end); - return 1; - } - else 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_key(&dv, 0); - } 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_key(&dv, 0); - } -} - -#define MOVIECMD_SKIP_ALL(p,end) \ - while (p < end && *p && *p != '\n') \ - { p++; } \ - -unsigned char * -mf_movieProcessCommand(unsigned char *p, unsigned char *end) -{ - for (; p < end && *p != '\n'; p++) - { - if (*p == 'S') { - // SYNCHRONIZATION - gettimeofday(&mfmovie.synctime, NULL); - // S can take other commands - } - else if (*p == 'E') - { - // END - STOP_MOVIE(); - // MFDISP_SKIPCURLINE(); - MOVIECMD_SKIP_ALL(p,end); - return p; - } - else if (*p == 'P') - { - // PAUSE - mfmovie.pause = 1; - // MFDISP_SKIPCURLINE(); - MOVIECMD_SKIP_ALL(p,end); - return p; - - } - else if (*p == 'G') - { - // GOTO - // Gt+-n,t+-n,t+-n (random select one) - // jump +-n of type(l,p,f) - - // random select, if multiple - unsigned char *pe = p; - unsigned int igs = 0; - - for (pe = p ; pe < end && *pe && - *pe > ' ' && *pe < 0x80 - ; pe ++) - if (*pe == ',') igs++; - - if (igs) - { - // make random - igs = random() % (igs+1); - - for (pe = p ; igs > 0 && pe < end && *pe && - *pe > ' ' && *pe < 0x80 - ; pe ++) - if (*pe == ',') igs--; - - if (pe != p) - p = pe-1; - } - - mf_movieExecuteOffsetCmd(p+1, end); - MOVIECMD_SKIP_ALL(p,end); - return p; - } - else if (*p == ':') - { - // NAMED - // :name: - // name allows alnum only - p++; - // TODO check isalnum p? - - // :name can accept trailing commands - while (p < end && *p != '\n' && *p != ':') - p++; - - if (*p == ':') p++; - - // continue will increase p - p--; - continue; - } - else if (*p == 'K') - { - // Reserve Key for interactive usage. - // Currently only K#...# format is supported. - if (p+2 < end && *(p+1) == '#') - { - p += 2; - mfmovie.optkeys = p; - mfmovie.interactive = 1; - - // K#..# can accept trailing commands - while (p < end && *p != '\n' && *p != '#') - p++; - - // if empty, set optkeys to NULL? - if (mfmovie.optkeys == p) - mfmovie.optkeys = NULL; - - if (*p == '#') p++; - - // continue will increase p - p--; - continue; - } - MOVIECMD_SKIP_ALL(p,end); - return p; - } - else if (*p == '#') - { - // OPTIONS - // #key1,frame1,text1#key2,frame2,text2# - mfmovie.options = p+1; - mfmovie.interactive = 1; - // MFDISP_SKIPCURLINE(); - MOVIECMD_SKIP_ALL(p,end); - return p; - } - else if (*p == 'O') - { - // OLD compatible mode - // = -> compat24 - // - -> ? - // == -> ? - p++; - if (p >= end) - return end; - if (*p == '=') - { - mfmovie.mode = MFDISP_MOVIE_PLAYING_OLD; - mfmovie.compat24 = 1; - p++; - } - // MFDISP_SKIPCURLINE(); - return p+1; - } - else if (*p == 'L') - { - // LOOP - // Lm,n - // m times to backward n - break; - } - else - { - // end of known control codes - break; - } - } - return p; -} - -int -mf_movieNextFrame() -{ - while (1) - { - unsigned char *p = mf_movieFrameHeader(mf.disps, mf.end); - - if(p) - { - float nf = 0; - unsigned char *odisps = mf.disps; - - /* process leading */ - p = mf_movieProcessCommand(p, mf.end); - - // disps may change after commands - if (mf.disps != odisps) - { - // commands changing location must - // support at least one frame pause - // to allow user break - struct timeval tv; - mf_float2tv(MOVIE_MIN_FRAMECLK, &tv); - - if (pmore_wait_key(&tv, 0)) - { - STOP_MOVIE(); - return 0; - } - continue; - } - - /* process time */ - if (mf_str2float(p, mf.end, &nf)) - { - mf_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; - } - - if (mfmovie.mode != MFDISP_MOVIE_PLAYING_OLD) - mf_forward(1); - - return 1; - } - - if (mf_forward(1) <= 0) - break; - } - - return 0; -} -#endif - -/* vim:sw=4:ts=8:expandtab:nofoldenable - */ diff --git a/mbbsd/random.c b/mbbsd/random.c deleted file mode 100644 index dd369c41..00000000 --- a/mbbsd/random.c +++ /dev/null @@ -1,711 +0,0 @@ -#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 -#include -#include -#include -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 -#include -#include - - -/* 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/mbbsd/read.c b/mbbsd/read.c deleted file mode 100644 index 97621725..00000000 --- a/mbbsd/read.c +++ /dev/null @@ -1,1371 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -static int headers_size = 0; -static fileheader_t *headers = NULL; -static int last_line; // PTT: last_line 游標可指的最後一個 - -#include - -/* ----------------------------------------------------- */ -/* Tag List 標籤 */ -/* ----------------------------------------------------- */ -static TagItem *TagList = NULL; /* ascending list */ - -/** - * @param locus - * @return void - */ -void -UnTagger(int locus) -{ - if (locus > TagNum || TagNum <= 0) - 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+1)); - } - - 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 || TagNum <= 0) - 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 - -#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, GLOBAL_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 key=StringHash(s); - int i; - - if (def_cursline >= 0) { - struct keepsome *kl=keeplist; - while(kl) { - for(i=0; iused; 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 - -// return: 1 - found, 0 - fail. -inline static int -dbcs_strcasestr(const char* pool, const char *ptr) -{ -#if 0 - // old method - 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; - -#endif - - int i = 0, i2 = 0, found = 0, - szpool = strlen(pool), - szptr = strlen(ptr); - - for (i = 0; i <= szpool-szptr; i++) - { - found = 1; - - // compare szpool[i..szptr] with ptr - for (i2 = 0; i2 < szptr; i2++) - { - if (pool[i + i2] > 0) - { - // ascii - if (ptr[i2] < 0 || - tolower(ptr[i2]) != tolower(pool[i+i2])) - { - // printf("break on ascii (i=%d, i2=%d).\n", i, i2); - found = 0; - break; - } - } else { - // non-ascii - if (ptr[i2] != pool[i+i2] || - ptr[i2+1] != pool[i+i2+1]) - { - // printf("break on non-ascii (i=%d, i2=%d).\n", i, i2); - found = 0; - break; - } - i2 ++; - } - } - - if (found) break; - - // next iteration: if target is DBCS, skip one more byte. - if (pool[i] < 0) - i++; - } - return found; -} - -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) ? - "增加條件 推文數: ": - "搜尋推文數高於多少" -#ifndef OLDRECOMMEND - " (<0則搜噓文數) " -#endif // OLDRECOMMEND - "的文章: ", - 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), DBCS_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 (currstat != RMAIL && currboard[0] && currbid > 0) - { - time4_t filecreate = dashc(newdirect); - boardheader_t *bp = getbcache(currbid); - assert(bp); - - if (bp->SRexpire) - { - if (bp->SRexpire > now) // invalid expire time. - bp->SRexpire = now; - - if (bp->SRexpire > filecreate) - filetime = -1; - } - } - - 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; - inc = 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) && - (n_recommend > 0 ? - (fhs[i].recommend < n_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 newdirect_new_ln = -1; - -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 '#': - { - char aidc[100]; - aidu_t aidu = 0; - char dirfile[PATHLEN]; - char *sp; - int n = -1; - - if(!getdata(b_lines, 0, "搜尋" AID_DISPLAYNAME ": #", aidc, 20, LCECHO)) - { - move(b_lines, 0); - clrtoeol(); - mode = FULLUPDATE; - break; - } - - if((currmode & MODE_SELECT) || - (currstat == RMAIL)) - { - move(21, 0); - clrtobot(); - move(22, 0); - prints("此狀態下無法使用搜尋" AID_DISPLAYNAME "功\能"); - pressanykey(); - mode = FULLUPDATE; - break; - } - - /* strip leading spaces and '#' */ - sp = aidc; - while(*sp == ' ') - sp ++; - if(*sp == '#') - sp ++; - - if((aidu = aidc2aidu(sp)) > 0) - { - /* search bottom */ - /* FIXME: 置底文但沒列在 .DIR.bottom 的在這段會搜不到, - 在下一段 search board 時才會搜到本體。難解。 */ - { - char buf[FNLEN]; - - snprintf(buf, FNLEN, "%s.bottom", FN_DIR); - setbfile(dirfile, currboard, buf); - if((n = search_aidu(dirfile, aidu)) >= 0) - { - n += getbtotal(currbid); - /* 不可用 bottom_line,因為如果是在 digest mode, - bottom_line 會是文摘的數目,而不是真正的文章數 */ - if(currmode & MODE_DIGEST) - { - newdirect_new_ln = n; - - new_ln = locmem->crs_ln; - /* dirty hack for crs_ln = 1, then HOME pressed */ - - default_ch = KEY_TAB; - mode = DONOTHING; - break; - } - } - } - if(n < 0) - /* search board */ - { - setbfile(dirfile, currboard, FN_DIR); - n = search_aidu(dirfile, aidu); - if(n >= 0 && (currmode & MODE_DIGEST)) - /* switch to normal read mode */ - { - newdirect_new_ln = n; - - new_ln = locmem->crs_ln; - /* dirty hack for crs_ln = 1, then HOME pressed */ - - default_ch = KEY_TAB; - mode = DONOTHING; - break; - } - } - if(n < 0) - /* search digest */ - { - setbfile(dirfile, currboard, fn_mandex); - n = search_aidu(dirfile, aidu); - if(n >= 0 && !(currmode & MODE_DIGEST)) - /* switch to digest mode */ - { - newdirect_new_ln = n; - - new_ln = locmem->crs_ln; - /* dirty hack for crs_ln = 1, then HOME pressed */ - - default_ch = KEY_TAB; - mode = DONOTHING; - break; - } - } - } /* if(aidu > 0) */ - if(n < 0) - { - move(21, 0); - clrtobot(); - move(22, 0); - if(aidu <= 0) - prints("不合法的" AID_DISPLAYNAME ",請確定輸入是正確的"); - else - prints("找不到這個" AID_DISPLAYNAME ",可能是文章已消失,或是你找錯看板了"); - pressanykey(); - mode = FULLUPDATE; - } /* if(n < 0) */ - else - { - new_ln = n + 1; - move(b_lines, 0); - clrtoeol(); - mode = DONOTHING; - } - } - break; - case Ctrl('L'): - redrawwin(); - refresh(); - 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, locmem->crs_ln - locmem->top_ln); - } - else - mode = (*func)(locmem->crs_ln, - &headers[locmem->crs_ln - locmem->top_ln], - currdirect, locmem->crs_ln - locmem->top_ln); - 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; -} - -// recbase: 顯示位置的開頭 -// headers_size:要顯示幾行 -// last_line: 全板 .DIR + 置底 的有效數目 -// bottom_line: 全板 .DIR (無置底) 的有效數目 - -// XXX never return -1! - -static int -get_records_and_bottom(char *direct, fileheader_t* headers, - int recbase, int headers_size, int last_line, int bottom_line) -{ - // n: 置底除外的可顯示數目 - int n = bottom_line - recbase + 1, rv = 0; - - if( last_line < 1) // 完全沒東西 - return 0; - - // 不顯示置底的情形 - if( n >= headers_size || (currmode & (MODE_SELECT | MODE_DIGEST)) ) - { - rv = get_records(direct, headers, sizeof(fileheader_t), - recbase, headers_size); - return rv > 0 ? rv : 0; - } - - //////// 顯示本文+置底: //////// - - // 讀取 .DIR 本文 - if (n > 0) - { - n = get_records(direct, headers, sizeof(fileheader_t), recbase, n); - if (n < 0) n = 0; - rv += n; // rv 為有效本文數 - - recbase = 1; - } else { - // n <= 0 - recbase = 1 + (-n); - } - - // 讀取置底 (注意 recbase 可能超過 bottom_line, 也就是以置底第 n 個開始) - n = last_line - bottom_line +1 - (recbase-1); - if (rv + n > headers_size) - n = headers_size - rv; - - if (n > 0) { - char directbottom[PATHLEN]; - snprintf(directbottom, sizeof(directbottom), "%s.bottom", direct); - n = get_records(directbottom, headers+rv, sizeof(fileheader_t), recbase, n); - if (n < 0) n = 0; - rv += 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 DONOTHING: - break; - - 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, - bottom_line ? bottom_line : last_line); - if(newdirect_new_ln >= 0) - { - locmem->crs_ln = newdirect_new_ln + 1; - newdirect_new_ln = -1; - } - } - recbase = -1; - /* no break */ - - default: // for any unknown keys - 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 or black-hole */ - 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/mbbsd/record.c b/mbbsd/record.c deleted file mode 100644 index d50df1d9..00000000 --- a/mbbsd/record.c +++ /dev/null @@ -1,666 +0,0 @@ -/* $Id$ */ - -#include "bbs.h" - -#undef HAVE_MMAP -#define BUFSIZE 512 - -#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]? 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, FN_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( d0) - { - 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--); - if (n + sizeof(".forward") > sizeof(buf)) - return -1; - memcpy(buf, fpath, n+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; -} - -#ifndef _BBS_UTIL_C_ -void -setaidfile(char *buf, const char *bn, aidu_t aidu) -{ - // try to load by AID - int bid = 0; - int n = 0, fd = 0; - char bfpath[PATHLEN] = ""; - boardheader_t *bh = NULL; - fileheader_t fh; - - buf[0] = 0; - bid = getbnum(bn); - - if (bid <= 0) return; - assert(0<=bid-1 && bid-1brdname, FN_DIR); - n = search_aidu(bfpath, aidu); - - if (n < 0) return; - fd = open(bfpath, O_RDONLY); - if (fd < 0) return; - - lseek(fd, n*sizeof(fileheader_t), SEEK_SET); - memset(&fh, 0, sizeof(fh)); - if (read(fd, &fh, sizeof(fh)) > 0) - { - setbfile(buf, bh->brdname, fh.filename); - if (!dashf(buf)) - buf[0] = 0; - } - close(fd); -} -#endif diff --git a/mbbsd/register.c b/mbbsd/register.c deleted file mode 100644 index e0db7d37..00000000 --- a/mbbsd/register.c +++ /dev/null @@ -1,3040 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#define FN_REGISTER_LOG "register.log" // global registration history -#define FN_JUSTIFY "justify" -#define FN_JUSTIFY_WAIT "justify.wait" -#define FN_REJECT_NOTIFY "justify.reject" - -// New style (Regform2) file names: -#define FN_REGFORM "regform" // registration form in user home -#define FN_REGFORM_LOG "regform.log" // regform history in user home -#define FN_REQLIST "reg.wait" // request list file, in global directory (replacing fn_register) - -//////////////////////////////////////////////////////////////////////////// -// Password Hash -//////////////////////////////////////////////////////////////////////////// - -// prototype of crypt() -char *crypt(const char *key, const char *salt); - -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; -} - -//////////////////////////////////////////////////////////////////////////// -// Value Validation -//////////////////////////////////////////////////////////////////////////// -static int -HaveRejectStr(const char *s, const char **rej) -{ - int i; - char *ptr, *rejectstr[] = - {"幹", "阿", "不", "你媽", "某", "笨", "呆", "..", "xx", - "你管", "管我", "猜", "天才", "超人", - "ㄅ", "ㄆ", "ㄇ", "ㄈ", "ㄉ", "ㄊ", "ㄋ", "ㄌ", "ㄍ", "ㄎ", "ㄏ", - "ㄐ", "ㄑ", "ㄒ", "ㄓ",/*"ㄔ",*/ "ㄕ", "ㄖ", "ㄗ", "ㄘ", "ㄙ", - "ㄧ", "ㄨ", "ㄩ", "ㄚ", "ㄛ", "ㄜ", "ㄝ", "ㄞ", "ㄟ", "ㄠ", "ㄡ", - "ㄢ", "ㄣ", "ㄤ", "ㄥ", "ㄦ", NULL}; - - if( rej != NULL ) - for( i = 0 ; rej[i] != NULL ; ++i ) - if( strstr(s, rej[i]) ) - return 1; - - for( i = 0 ; rejectstr[i] != NULL ; ++i ) - if( strstr(s, rejectstr[i]) ) - return 1; - - if( (ptr = strstr(s, "ㄔ")) != NULL ){ - if( ptr != s && strncmp(ptr - 1, "都市", 4) == 0 ) - return 0; - return 1; - } - return 0; -} - -static int -removespace(char *s) -{ - int i, index; - - for (i = 0, index = 0; s[i]; i++) { - if (s[i] != ' ') - s[index++] = s[i]; - } - s[index] = '\0'; - return index; -} - -int -bad_user_id(const char *userid) -{ - 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; -} - -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}; - - // addr[0] > 0: check if address is starting by Chinese. - if (!removespace(addr) || strlen(addr) < 15) - return "這個地址似乎並不完整"; - if (strstr(addr, "信箱") != NULL || strstr(addr, "郵政") != NULL) - return "抱歉我們不接受郵政信箱"; - if ((strstr(addr, "市") == NULL && strstr(addr, "巿") == NULL && - strstr(addr, "縣") == NULL && strstr(addr, "室") == NULL) || - HaveRejectStr(addr, rejectstr) || - strcmp(&addr[strlen(addr) - 2], "段") == 0 || - strcmp(&addr[strlen(addr) - 2], "路") == 0 || - strcmp(&addr[strlen(addr) - 2], "巷") == 0 || - strcmp(&addr[strlen(addr) - 2], "弄") == 0 || - strcmp(&addr[strlen(addr) - 2], "區") == 0 || - strcmp(&addr[strlen(addr) - 2], "市") == 0 || - strcmp(&addr[strlen(addr) - 2], "街") == 0 ) - return "這個地址似乎並不完整"; - return NULL; -} - -static char * -isvalidphone(char *phone) -{ - int i; - for( i = 0 ; phone[i] != 0 ; ++i ) - if( !isdigit((int)phone[i]) ) - return "請不要加分隔符號"; - if (!removespace(phone) || - strlen(phone) < 9 || - strstr(phone, "00000000") != NULL || - strstr(phone, "22222222") != NULL ) { - return "這個電話號碼並不正確(請含區碼)" ; - } - return NULL; -} - - -//////////////////////////////////////////////////////////////////////////// -// Account Expiring -//////////////////////////////////////////////////////////////////////////// - -/* -------------------------------- */ -/* New policy for allocate new user */ -/* (a) is the worst user currently */ -/* (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 */ - // XXX 目前 new acccount 並不會在 utmp 裡放 str_new... - 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, int expireRange) -{ - 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); - - // 若超過 expireRange 則砍人, - // 不然就 return 0 - if (-val > expireRange) - { - log_usies("DATED", genbuf); - // log_usies("CLEAN", genbuf); - kill_user(uid, urec->userid); - } else val = 0; - } - return val; -} - -//////////////////////////////////////////////////////////////////////////// -// Regcode Support -//////////////////////////////////////////////////////////////////////////// - -#define REGCODE_INITIAL "v6" // always 2 characters - -static char * -getregfile(char *buf) -{ - // not in user's home because s/he could zip his/her home - snprintf(buf, PATHLEN, "jobspool/.regcode.%s", cuser.userid); - return buf; -} - -static char * -makeregcode(char *buf) -{ - char fpath[PATHLEN]; - int fd, i; - // prevent ambigious characters: oOlI - const char *alphabet = "qwertyuipasdfghjkzxcvbnmoQWERTYUPASDFGHJKLZXCVBNM"; - - /* generate a new regcode */ - buf[13] = 0; - buf[0] = REGCODE_INITIAL[0]; - buf[1] = REGCODE_INITIAL[1]; - for( i = 2 ; i < 13 ; ++i ) - buf[i] = alphabet[random() % strlen(alphabet)]; - - getregfile(fpath); - if( (fd = open(fpath, O_WRONLY | O_CREAT, 0600)) == -1 ){ - perror("open"); - exit(1); - } - write(fd, buf, 13); - close(fd); - - return buf; -} - -static char * -getregcode(char *buf) -{ - int fd; - char fpath[PATHLEN]; - - getregfile(fpath); - if( (fd = open(fpath, O_RDONLY)) == -1 ){ - buf[0] = 0; - return buf; - } - read(fd, buf, 13); - close(fd); - buf[13] = 0; - return buf; -} - -void -delregcodefile(void) -{ - char fpath[PATHLEN]; - getregfile(fpath); - unlink(fpath); -} - -//////////////////////////////////////////////////////////////////////////// -// Justify Utilities -//////////////////////////////////////////////////////////////////////////// - -static void -justify_wait(char *userid, char *phone, char *career, - char *rname, char *addr, char *mobile) -{ - char buf[PATHLEN]; - sethomefile(buf, userid, FN_JUSTIFY_WAIT); - if (phone[0] != 0) { - FILE* fn = fopen(buf, "w"); - assert(fn); - fprintf(fn, "%s\n%s\ndummy\n%s\n%s\n%s\n", - phone, career, rname, addr, mobile); - fclose(fn); - } -} - -static void -email_justify(const userec_t *muser) -{ - char tmp[IDLEN + 1], buf[256], genbuf[256]; - /* - * It is intended to use BBSENAME instead of BBSNAME here. - * Because recently many poor users with poor mail clients - * (or evil mail servers) cannot handle/decode Chinese - * subjects (BBSNAME) correctly, so we'd like to use - * BBSENAME here to prevent subject being messed up. - * And please keep BBSENAME short or it may be truncated - * by evil mail servers. - */ - snprintf(buf, sizeof(buf), - " " BBSENAME " - [ %s ]", makeregcode(genbuf)); - - strlcpy(tmp, cuser.userid, sizeof(tmp)); - // XXX dirty, set userid=SYSOP - strlcpy(cuser.userid, str_sysop, sizeof(cuser.userid)); -#ifdef HAVEMOBILE - if (strcmp(muser->email, "m") == 0 || strcmp(muser->email, "M") == 0) - mobile_message(mobile, buf); - else -#endif - bsmtp("etc/registermail", buf, muser->email); - strlcpy(cuser.userid, tmp, sizeof(cuser.userid)); - move(20,0); - clrtobot(); - outs("我們即將寄出認證信 (您應該會在 10 分鐘內收到)\n" - "收到後您可以根據認證信標題的認證碼\n" - "輸入到 (U)ser -> (R)egister 後就可以完成註冊"); - pressanykey(); - return; -} - - -/* 使用者填寫註冊表格 */ -static void -getfield(int line, const char *info, const char *desc, char *buf, int len) -{ - char prompt[STRLEN]; - char genbuf[200]; - - // clear first - move(line+1, 0); clrtoeol(); - move(line, 0); clrtoeol(); - prints(" 原先設定:%-30.30s (%s)", buf, info); - snprintf(prompt, sizeof(prompt), " %s:", desc); - if (getdata_str(line + 1, 0, prompt, genbuf, len, DOECHO, buf)) - strcpy(buf, genbuf); - move(line+1, 0); clrtoeol(); - move(line, 0); clrtoeol(); - prints(" %s:%s", desc, buf); -} - - -int -setupnewuser(const userec_t *user) -{ - char genbuf[50]; - char *fn_fresh = ".fresh"; - userec_t utmp; - time_t clock; - struct stat st; - int fd, uid; - - clock = now; - - // XXX race condition... - if (dosearchuser(user->userid, NULL)) - { - vmsg("手腳不夠快,別人已經搶走了!"); - exit(1); - } - - /* 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); - // tolerate for one year. - check_and_expire_account(uid, &utmp, 365*12*60); - } - } - } - - /* 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; -} - -///////////////////////////////////////////////////////////////////////////// -// New Registration (Phase 1) -///////////////////////////////////////////////////////////////////////////// - -void -new_register(void) -{ - userec_t newuser; - char passbuf[STRLEN]; - int try, id, uid; - char *errmsg = NULL; - -#ifdef HAVE_USERAGREEMENT - more(HAVE_USERAGREEMENT, YEA); - while( 1 ){ - getdata(b_lines, 0, "請問您接受這份使用者條款嗎? (yes/no) ", - passbuf, 4, LCECHO); - if( passbuf[0] == 'y' ) - break; - if( passbuf[0] == 'n' ){ - vmsg("抱歉, 您須要接受使用者條款才能註冊帳號享受我們的服務唷!"); - exit(1); - } - vmsg("請輸入 y表示接受, n表示不接受"); - } -#endif - - // setup newuser - memset(&newuser, 0, sizeof(newuser)); - 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; - strlcpy(newuser.lasthost, fromhost, sizeof(newuser.lasthost)); - -#ifdef DBCSAWARE - if(u_detectDBCSAwareEvilClient()) - newuser.uflag &= ~DBCSAWARE_FLAG; - else - newuser.uflag |= DBCSAWARE_FLAG; -#endif - - 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)) && - // >=: see check_and_expire_account definition - (minute = check_and_expire_account(id, &xuser, 0)) >= 0) - { - if (minute == 999999) // XXX magic number. It should be greater than MAX_USERS at least. - outs("此代號已經有人使用 是不死之身"); - else { - prints("此代號已經有人使用 還有 %d 天才過期 \n", - minute / (60 * 24) + 1); - } - } else - break; - } - - // XXX 記得最後 create account 前還要再檢查一次 acc - - try = 0; - while (1) { - if (++try >= 6) { - vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); - exit(1); - } - move(19, 0); clrtoeol(); - outs(ANSI_COLOR(1;33) - "為避免被偷看,您的密碼並不會顯示在畫面上,直接輸入完後按 Enter 鍵即可。\n" - "另外請注意密碼只有前八個字元有效,超過的將自動忽略。" - ANSI_RESET); - if ((getdata(18, 0, "請設定密碼:", passbuf, - sizeof(passbuf), NOECHO) < 3) || - !strcmp(passbuf, newuser.userid)) { - outs("密碼太簡單,易遭入侵,至少要 4 個字,請重新輸入\n"); - continue; - } - strlcpy(newuser.passwd, passbuf, PASSLEN); - getdata(19, 0, "請檢查密碼:", passbuf, sizeof(passbuf), NOECHO); - if (strncmp(passbuf, newuser.passwd, PASSLEN)) { - outs("密碼輸入錯誤, 請重新輸入密碼.\n"); - continue; - } - passbuf[8] = '\0'; - strlcpy(newuser.passwd, genpasswd(passbuf), PASSLEN); - break; - } - // set-up more information. - - // warning: because currutmp=NULL, we can simply pass newuser.* to getdata. - // DON'T DO THIS IF YOUR currutmp != NULL. - try = 0; - while (strlen(newuser.nickname) < 2) - { - if (++try > 10) { - vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); - exit(1); - } - getdata(19, 0, "綽號暱稱:", newuser.nickname, - sizeof(newuser.nickname), DOECHO); - } - - try = 0; - while (strlen(newuser.realname) < 4) - { - if (++try > 10) { - vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); - exit(1); - } - getdata(20, 0, "真實姓名:", newuser.realname, - sizeof(newuser.realname), DOECHO); - - if ((errmsg = isvalidname(newuser.realname))) - { - memset(newuser.realname, 0, sizeof(newuser.realname)); - vmsg(errmsg); - } - } - - try = 0; - while (strlen(newuser.address) < 8) - { - // do not use isvalidaddr to check, - // because that requires foreign info. - if (++try > 10) { - vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); - exit(1); - } - getdata(21, 0, "聯絡地址:", newuser.address, - sizeof(newuser.address), DOECHO); - } - - try = 0; - while (newuser.year < 40) // magic number 40: see user.c - { - char birthday[sizeof("mmmm/yy/dd ")]; - int y, m, d; - - if (++try > 20) { - vmsg("您嘗試錯誤的輸入太多,請下次再來吧"); - exit(1); - } - getdata(22, 0, "生日 (西元年/月/日, 如 1984/02/29):", birthday, - sizeof(birthday), DOECHO); - - if (ParseDate(birthday, &y, &m, &d)) { - vmsg("日期格式不正確"); - continue; - } else if (y < 1940) { - vmsg("你真的有那麼老嗎?"); - continue; - } - newuser.year = (unsigned char)(y-1900); - newuser.month = (unsigned char)m; - newuser.day = (unsigned char)d; - } - - setupnewuser(&newuser); - - if( (uid = initcuser(newuser.userid)) < 0) { - vmsg("無法建立帳號"); - exit(1); - } - log_usies("REGISTER", fromhost); -} - -void -check_birthday(void) -{ - // check birthday - int changed = 0; - time_t t = (time_t)now; - struct tm tm; - - localtime_r(&t, &tm); - while ( cuser.year < 40 || // magic number 40: see user.c - cuser.year+3 > tm.tm_year) - { - char birthday[sizeof("mmmm/yy/dd ")]; - int y, m, d; - - clear(); - stand_title("輸入生日"); - move(2,0); - outs("本站為配合實行內容分級制度,請您輸入正確的生日資訊。"); - - getdata(5, 0, "生日 (西元年/月/日, 如 1984/02/29):", birthday, - sizeof(birthday), DOECHO); - - if (ParseDate(birthday, &y, &m, &d)) { - vmsg("日期格式不正確"); - continue; - } else if (y < 1940) { - vmsg("你真的有那麼老嗎?"); - continue; - } else if (y+3 > tm.tm_year+1900) { - vmsg("嬰兒/未出生應該無法使用 BBS..."); - continue; - } - cuser.year = (unsigned char)(y-1900); - cuser.month = (unsigned char)m; - cuser.day = (unsigned char)d; - changed = 1; - } - - if (changed) { - clear(); - resolve_over18(); - } -} - -void -check_register(void) -{ - char fn[PATHLEN]; - - check_birthday(); - - if (HasUserPerm(PERM_LOGINOK)) - return; - - setuserfile(fn, FN_REJECT_NOTIFY); - - /* - * 避免使用者被退回註冊單後,在知道退回的原因之前, - * 又送出一次註冊單。 - */ - if (dashf(fn)) - { - more(fn, NA); - move(b_lines-3, 0); - outs("上次註冊單審查失敗。\n" - "請重新申請並照上面指示正確填寫註冊單。"); - while(getans("請輸入 y 繼續: ") != 'y'); - unlink(fn); - } else - if (ISNEWMAIL(currutmp)) - m_read(); - - 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 - } -} - -///////////////////////////////////////////////////////////////////////////// -// User Registration (Phase 2) -///////////////////////////////////////////////////////////////////////////// - -static void -toregister(char *email, char *phone, char *career, - char *rname, char *addr, char *mobile) -{ - FILE *fn = NULL; - - justify_wait(cuser.userid, phone, career, rname, addr, mobile); - - clear(); - stand_title("認證設定"); - if (cuser.userlevel & PERM_NOREGCODE){ - strcpy(email, "x"); - goto REGFORM2; - } - move(1, 0); - outs("您好, 本站認證認證的方式有:\n" - " 1.若您有 E-Mail (本站不接受 yahoo, kimo等免費的 E-Mail)\n" - " 請輸入您的 E-Mail , 我們會寄發含有認證碼的信件給您\n" - " 收到後請到 (U)ser => (R)egister 輸入認證碼, 即可通過認證\n" - "\n" - " 2.若您沒有 E-Mail 或是一直無法收到認證信, 請輸入 x \n" - " 會有站長親自人工審核註冊資料," ANSI_COLOR(1;33) - "但注意這可能會花上數週或更多時間。" ANSI_RESET "\n" - "**********************************************************\n" - "* 注意! *\n" - "* 通常應該會在輸入完成後十分鐘內收到認證信, 若過久未收到 *\n" - "* 請到郵件垃圾桶檢查是否被當作垃圾信(SPAM)了,另外若是 *\n" - "* 輸入後發生認證碼錯誤請重填一次 E-Mail *\n" - "**********************************************************\n"); - -#ifdef HAVEMOBILE - outs(" 3.若您有手機門號且想採取手機簡訊認證的方式 , 請輸入 m \n" - " 我們將會寄發含有認證碼的簡訊給您 \n" - " 收到後請到(U)ser => (R)egister 輸入認證碼, 即可通過認證\n"); -#endif - - while (1) { - email[0] = 0; - getfield(15, "身分認證用", "E-Mail Address", email, 50); - if (strcmp(email, "x") == 0 || strcmp(email, "X") == 0) - break; -#ifdef HAVEMOBILE - else if (strcmp(email, "m") == 0 || strcmp(email, "M") == 0) { - if (isvalidmobile(mobile)) { - char yn[3]; - getdata(16, 0, "請再次確認您輸入的手機號碼正確嘛? [y/N]", - yn, sizeof(yn), LCECHO); - if (yn[0] == 'Y' || yn[0] == 'y') - break; - } else { - move(15, 0); clrtobot(); - move(17, 0); - outs("指定的手機號碼不正確," - "若您無手機門號請選擇其他方式認證"); - } - - } -#endif - else if (isvalidemail(email)) { - char yn[3]; -#ifdef USE_EMAILDB - int email_count = emaildb_check_email(email, strlen(email)); - - if (email_count < 0) { - move(15, 0); clrtobot(); - move(17, 0); - outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); - pressanykey(); - return; - } else if (email_count >= EMAILDB_LIMIT) { - move(15, 0); clrtobot(); - move(17, 0); - outs("指定的 E-Mail 已註冊過多帳號, 請使用其他 E-Mail, 或輸入 x 採手動認證\n"); - outs("但注意手動認證通常會花上數週以上的時間。\n"); - } else { -#endif - getdata(16, 0, "請再次確認您輸入的 E-Mail 位置正確嘛? [y/N]", - yn, sizeof(yn), LCECHO); - if (yn[0] == 'Y' || yn[0] == 'y') - break; -#ifdef USE_EMAILDB - } -#endif - } else { - move(15, 0); clrtobot(); - move(17, 0); - outs("指定的 E-Mail 不正確, 若您無 E-Mail 請輸入 x 由站長手動認證\n"); - outs("但注意手動認證通常會花上數週以上的時間。\n"); - } - } -#ifdef USE_EMAILDB - if (emaildb_update_email(cuser.userid, strlen(cuser.userid), - email, strlen(email)) < 0) { - move(15, 0); clrtobot(); - move(17, 0); - outs("暫時不允許\ email 認證註冊, 請稍後再試\n"); - pressanykey(); - return; - } -#endif - strlcpy(cuser.email, email, sizeof(cuser.email)); - REGFORM2: - if (strcasecmp(email, "x") == 0) { /* 手動認證 */ - if ((fn = fopen(fn_register, "a"))) { - fprintf(fn, "num: %d, %s", usernum, ctime4(&now)); - fprintf(fn, "uid: %s\n", cuser.userid); - fprintf(fn, "name: %s\n", rname); - fprintf(fn, "career: %s\n", career); - fprintf(fn, "addr: %s\n", addr); - fprintf(fn, "phone: %s\n", phone); - fprintf(fn, "mobile: %s\n", mobile); - fprintf(fn, "email: %s\n", email); - fprintf(fn, "----\n"); - fclose(fn); - // save justify information - snprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:", phone, career); - } - // XXX what if we cannot open register form? - } else { - // register by mail of phone - snprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:", phone, career); -#ifdef HAVEMOBILE - if (phone != NULL && email[1] == 0 && tolower(email[0]) == 'm') - sprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:", phone, career); -#endif - email_justify(&cuser); - } -} - - -int -u_register(void) -{ - char rname[20], addr[50], mobile[16]; -#ifdef FOREIGN_REG - char fore[2]; -#endif - char phone[20], career[40], email[50], birthday[11], sex_is[2]; - unsigned char year, mon, day; - char inregcode[14], regcode[50]; - char ans[3], *ptr, *errcode; - char genbuf[200]; - FILE *fn; - - if (cuser.userlevel & PERM_LOGINOK) { - outs("您的身份確認已經完成,不需填寫申請表"); - return XEASY; - } - if ((fn = fopen(fn_register, "r"))) { - int i =0; - while (fgets(genbuf, STRLEN, fn)) { - if ((ptr = strchr(genbuf, '\n'))) - *ptr = '\0'; - if (strncmp(genbuf, "uid: ", 5) != 0) - continue; - i++; - if(strcmp(genbuf + 5, cuser.userid) != 0) - continue; - fclose(fn); - /* idiots complain about this, so bug them */ - clear(); - move(3, 0); - prints(" 您的註冊申請單尚在處理中(處理順位: %d),請耐心等候\n\n", i); - outs(" 如果您已收到註冊碼卻看到這個畫面,那代表您在使用 Email 註冊後\n"); - outs(" " ANSI_COLOR(1;31) "又另外申請了站長直接人工審核的註冊申請單。" - ANSI_RESET "\n\n"); - // outs("該死,都不看說明的...\n"); - outs(" 進入人工審核程序後 Email 註冊自動失效,有註冊碼也沒用,\n"); - outs(" 要等到審核完成 (會多花很多時間,通常起碼數天) ,所以請耐心等候。\n\n"); - - /* 下面是國王的 code 所需要的 message */ -#if 0 - outs(" 另外請注意,若站長審註冊單時您正在站上則會無法審核、自動跳過。\n"); - outs(" 所以等候審核時請勿掛站。若超過兩三天仍未被審到,通常就是這個原因。\n"); -#endif - - vmsg("您的註冊申請單尚在處理中"); - return FULLUPDATE; - } - fclose(fn); - } - strlcpy(rname, cuser.realname, sizeof(rname)); - strlcpy(addr, cuser.address, sizeof(addr)); - strlcpy(email, cuser.email, sizeof(email)); - if (cuser.mobile) - snprintf(mobile, sizeof(mobile), "0%09d", cuser.mobile); - else - mobile[0] = 0; - if (cuser.month == 0 && cuser.day == 0 && cuser.year == 0) - birthday[0] = 0; - else - snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", - 1900 + cuser.year, cuser.month, cuser.day); - sex_is[0] = (cuser.sex % 8) + '1'; - sex_is[1] = 0; - career[0] = phone[0] = '\0'; - sethomefile(genbuf, cuser.userid, FN_JUSTIFY_WAIT); - if ((fn = fopen(genbuf, "r"))) { - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(phone, genbuf, sizeof(phone)); - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(career, genbuf, sizeof(career)); - - fgets(genbuf, sizeof(genbuf), fn); // old version compatible - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(rname, genbuf, sizeof(rname)); - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(addr, genbuf, sizeof(addr)); - - fgets(genbuf, sizeof(genbuf), fn); - chomp(genbuf); - strlcpy(mobile, genbuf, sizeof(mobile)); - - fclose(fn); - } - - if (cuser.userlevel & PERM_NOREGCODE) { - vmsg("您不被允許\使用認證碼認證。請填寫註冊申請單"); - goto REGFORM; - } - - // getregcode(regcode); - - // XXX why check by year? - // birthday is moved to earlier, so let's check email instead. - if (cuser.email[0] && // cuser.year != 0 && /* 已經第一次填過了~ ^^" */ - strcmp(cuser.email, "x") != 0 && /* 上次手動認證失敗 */ - strcmp(cuser.email, "X") != 0) - { - clear(); - stand_title("EMail認證"); - move(2, 0); - - prints("請輸入您的認證碼。(由 %s 開頭無空白的十三碼)\n" - "或輸入 x 來重新填寫 E-Mail 或改由站長手動認證\n", REGCODE_INITIAL); - inregcode[0] = 0; - - do{ - getdata(10, 0, "您的認證碼:", - inregcode, sizeof(inregcode), DOECHO); - if( strcmp(inregcode, "x") == 0 || strcmp(inregcode, "X") == 0 ) - break; - if( strlen(inregcode) != 13 || inregcode[0] == ' ') - vmsg("認證碼輸入不完整,總共應有十三碼,沒有空白字元。"); - else if( inregcode[0] != REGCODE_INITIAL[0] || inregcode[1] != REGCODE_INITIAL[1] ) { - /* old regcode */ - vmsg("輸入的認證碼錯誤," // "或因系統昇級已失效," - "請輸入 x 重填一次 E-Mail"); - } - else - break; - } while( 1 ); - - // make it case insensitive. - if (strcasecmp(inregcode, getregcode(regcode)) == 0) { - int unum; - delregcodefile(); - if ((unum = searchuser(cuser.userid, NULL)) == 0) { - vmsg("系統錯誤,查無此人!"); - u_exit("getuser error"); - exit(0); - } - mail_muser(cuser, "[註冊成功\囉]", "etc/registeredmail"); -#if FOREIGN_REG_DAY > 0 - if(cuser.uflag2 & FOREIGN) - mail_muser(cuser, "[出入境管理局]", "etc/foreign_welcome"); -#endif - cuser.userlevel |= (PERM_LOGINOK | PERM_POST); - outs("\n註冊成功\, 重新上站後將取得完整權限\n" - "請按下任一鍵跳離後重新上站~ :)"); - sethomefile(genbuf, cuser.userid, FN_JUSTIFY_WAIT); - unlink(genbuf); - snprintf(cuser.justify, sizeof(cuser.justify), - "%s:%s:email", phone, career); - sethomefile(genbuf, cuser.userid, FN_JUSTIFY); - log_file(genbuf, LOG_CREAT, cuser.justify); - pressanykey(); - u_exit("registed"); - exit(0); - return QUIT; - } else if (strcasecmp(inregcode, "x") != 0) { - if (regcode[0]) - { - vmsg("認證碼錯誤!"); - return FULLUPDATE; - } - else - { - vmsg("認證碼已過期,請重新註冊。"); - toregister(email, phone, career, rname, addr, mobile); - return FULLUPDATE; - } - } else { - toregister(email, phone, career, rname, addr, mobile); - return FULLUPDATE; - } - } - - REGFORM: - getdata(b_lines - 1, 0, "您確定要填寫註冊單嗎(Y/N)?[N] ", - ans, 3, LCECHO); - if (ans[0] != 'y') - return FULLUPDATE; - - move(2, 0); - clrtobot(); - while (1) { - clear(); - move(1, 0); - prints("%s(%s) 您好,請據實填寫以下的資料:", - cuser.userid, cuser.nickname); -#ifdef FOREIGN_REG - fore[0] = 'y'; - fore[1] = 0; - getfield(2, "Y/n", "是否現在住在台灣", fore, 2); - if (fore[0] == 'n') - fore[0] |= FOREIGN; - else - fore[0] = 0; -#endif - while (1) { - getfield(8, -#ifdef FOREIGN_REG - "請用本名", -#else - "請用中文", -#endif - "真實姓名", rname, 20); - if( (errcode = isvalidname(rname)) == NULL ) - break; - else - vmsg(errcode); - } - - move(11, 0); - outs(" 請盡量詳細的填寫您的服務單位,大專院校請麻煩" - "加" ANSI_COLOR(1;33) "系所" ANSI_RESET ",公司單位請加" ANSI_COLOR(1;33) "職稱" ANSI_RESET ",\n" - " 暫無工作請麻煩填寫" ANSI_COLOR(1;33) "畢業學校" ANSI_RESET "。\n"); - while (1) { - getfield(9, "(畢業)學校(含" ANSI_COLOR(1;33) "系所年級" ANSI_RESET ")或單位職稱", - "服務單位", career, 40); - if( (errcode = isvalidcareer(career)) == NULL ) - break; - else - vmsg(errcode); - } - move(10, 0); clrtobot(); - while (1) { - getfield(10, "含" ANSI_COLOR(1;33) "縣市" ANSI_RESET "及門寢號碼" - "(台北請加" ANSI_COLOR(1;33) "行政區" ANSI_RESET ")", - "目前住址", addr, sizeof(addr)); - if( (errcode = isvalidaddr(addr)) == NULL -#ifdef FOREIGN_REG - || fore[0] -#endif - ) - break; - else - vmsg(errcode); - } - while (1) { - getfield(11, "不加-(), 包括長途區號", "連絡電話", phone, 11); - if( (errcode = isvalidphone(phone)) == NULL ) - break; - else - vmsg(errcode); - } - getfield(12, "只輸入數字 如:0912345678 (可不填)", - "手機號碼", mobile, 20); - while (1) { - getfield(13, "西元/月月/日日 如:1984/02/29", "生日", birthday, sizeof(birthday)); - if (birthday[0] == 0) { - snprintf(birthday, sizeof(birthday), "%04i/%02i/%02i", - 1900 + cuser.year, cuser.month, cuser.day); - mon = cuser.month; - day = cuser.day; - year = cuser.year; - } else { - int y, m, d; - if (ParseDate(birthday, &y, &m, &d)) { - vmsg("您的輸入不正確"); - continue; - } - mon = (unsigned char)m; - day = (unsigned char)d; - year = (unsigned char)(y - 1900); - } - if (year < 40) { - vmsg("您的輸入不正確"); - continue; - } - break; - } - getfield(14, "1.葛格 2.姐接 ", "性別", sex_is, 2); - getdata(20, 0, "以上資料是否正確(Y/N)?(Q)取消註冊 [N] ", - ans, 3, LCECHO); - if (ans[0] == 'q') - return 0; - if (ans[0] == 'y') - break; - } - strlcpy(cuser.realname, rname, sizeof(cuser.realname)); - strlcpy(cuser.address, addr, sizeof(cuser.address)); - strlcpy(cuser.email, email, sizeof(cuser.email)); - cuser.mobile = atoi(mobile); - cuser.sex = (sex_is[0] - '1') % 8; - cuser.month = mon; - cuser.day = day; - cuser.year = year; -#ifdef FOREIGN_REG - if (fore[0]) - cuser.uflag2 |= FOREIGN; - else - cuser.uflag2 &= ~FOREIGN; -#endif - trim(career); - trim(addr); - trim(phone); - - toregister(email, phone, career, rname, addr, mobile); - - // update cuser - passwd_update(usernum, &cuser); - - return FULLUPDATE; -} - -///////////////////////////////////////////////////////////////////////////// -// Administration (SYSOP Validation) -///////////////////////////////////////////////////////////////////////////// - -#define REJECT_REASONS (6) -#define REASON_LEN (60) -static const char *reasonstr[REJECT_REASONS] = { - "輸入真實姓名", - "詳填(畢業)學校『系』『級』或服務單位(含所屬縣市及職稱)", - "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", - "詳填連絡電話 (含區碼, 中間不加 '-', '(', ')' 等符號)", - "精確並完整填寫註冊申請表", - "用中文填寫申請單", -}; - -#define REASON_FIRSTABBREV '0' -#define REASON_IN_ABBREV(x) \ - ((x) >= REASON_FIRSTABBREV && (x) - REASON_FIRSTABBREV < REJECT_REASONS) -#define REASON_EXPANDABBREV(x) reasonstr[(x) - REASON_FIRSTABBREV] - -void -regform_accept(const char *userid, const char *justify) -{ - char buf[PATHLEN]; - int unum = 0; - userec_t muser; - - unum = getuser(userid, &muser); - if (unum == 0) - return; // invalid user - - muser.userlevel |= (PERM_LOGINOK | PERM_POST); - strlcpy(muser.justify, justify, sizeof(muser.justify)); - // manual accept sets email to 'x' - strlcpy(muser.email, "x", sizeof(muser.email)); - - // handle files - sethomefile(buf, muser.userid, FN_JUSTIFY_WAIT); - unlink(buf); - sethomefile(buf, muser.userid, FN_REJECT_NOTIFY); - unlink(buf); - sethomefile(buf, muser.userid, FN_JUSTIFY); - log_filef(buf, LOG_CREAT, "%s\n", muser.justify); - - // update password file - passwd_update(unum, &muser); - - // alert online users? - sendalert(muser.userid, ALERT_PWD_PERM|ALERT_PWD_JUSTIFY); // force to reload perm - -#if FOREIGN_REG_DAY > 0 - if(muser.uflag2 & FOREIGN) - mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); - else -#endif - // last: send notification mail - mail_muser(muser, "[註冊成功\囉]", "etc/registered"); -} - -void -regform_reject(const char *userid, const char *reason) -{ - char buf[PATHLEN]; - FILE *fp = NULL; - int unum = 0; - userec_t muser; - - unum = getuser(userid, &muser); - if (unum == 0) - return; // invalid user - - muser.userlevel &= ~(PERM_LOGINOK | PERM_POST); - - // handle files - sethomefile(buf, muser.userid, FN_JUSTIFY_WAIT); - unlink(buf); - - // update password file - passwd_update(unum, &muser); - - // alert notify users? - sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm - - // last: send notification - mkuserdir(muser.userid); - sethomefile(buf, muser.userid, FN_REJECT_NOTIFY); - fp = fopen(buf, "wt"); - assert(fp); - syncnow(); - fprintf(fp, "%s 註冊失敗。\n", Cdate(&now)); - - // multiple abbrev loop - if (REASON_IN_ABBREV(reason[0])) - { - int i = 0; - for (i = 0; i < REASON_LEN && REASON_IN_ABBREV(reason[i]); i++) - fprintf(fp, "[退回原因] 請%s\n", REASON_EXPANDABBREV(reason[i])); - } else { - fprintf(fp, "[退回原因] %s\n", reason); - } - fclose(fp); - mail_muser(muser, "[註冊失敗]", buf); -} - -// Regform v1 API -// read count entries from regsrc to a temp buffer -FILE * -pull_regform(const char *regfile, char *workfn, int count) -{ - FILE *fp = NULL; - - snprintf(workfn, PATHLEN, "%s.tmp", regfile); - if (dashf(workfn)) { - vmsg("其他 SYSOP 也在審核註冊申請單"); - return NULL; - } - - // count < 0 means unlimited pulling - Rename(regfile, workfn); - if ((fp = fopen(workfn, "r")) == NULL) { - vmsgf("系統錯誤,無法讀取註冊資料檔: %s", workfn); - return NULL; - } - return fp; -} - -// write all left in "remains" to regfn. -void -pump_regform(const char *regfn, FILE *remains) -{ - // restore trailing tickets - char buf[PATHLEN]; - FILE *fout = fopen(regfn, "at"); - if (!fout) - return; - - while (fgets(buf, sizeof(buf), remains)) - fputs(buf, fout); - fclose(fout); -} - -// New Regform UI -static void -prompt_regform_ui() -{ - move(b_lines, 0); - outs(ANSI_COLOR(30;47) " " - ANSI_COLOR(31) "y" ANSI_COLOR(30) "接受 " - ANSI_COLOR(31) "n" ANSI_COLOR(30) "拒絕 " - ANSI_COLOR(31) "d" ANSI_COLOR(30) "刪除 " - ANSI_COLOR(31) "s" ANSI_COLOR(30) "跳過 " - ANSI_COLOR(31) "u" ANSI_COLOR(30) "復原 " - " " - ANSI_COLOR(31) "0-9jk↑↓" ANSI_COLOR(30) "移動 " - ANSI_COLOR(31) "空白/PgDn" ANSI_COLOR(30) "儲存+下頁 " - " " - ANSI_COLOR(31) "q/END" ANSI_COLOR(30) "結束 " - ANSI_RESET); -} - -static void -resolve_reason(char *s, int y) -{ - // should start with REASON_FIRSTABBREV - const char *reason_prompt = - " (0)真實姓名 (1)詳填系級 (2)完整住址" - " (3)詳填電話 (4)確實填寫 (5)中文填寫"; - - s[0] = 0; - move(y, 0); - outs(reason_prompt); outs("\n"); - - do { - getdata(y+1, 0, - "退回原因: ", s, REASON_LEN, DOECHO); - - // convert abbrev reasons (format: single digit, or multiple digites) - if (REASON_IN_ABBREV(s[0])) - { - if (s[1] == 0) // simple replace ment - { - strlcpy(s+2, REASON_EXPANDABBREV(s[0]), - REASON_LEN-2); - s[0] = 0xbd; // '請'[0]; - s[1] = 0xd0; // '請'[1]; - } else { - // strip until all digites - char *p = s; - while (*p) - { - if (!REASON_IN_ABBREV(*p)) - *p = ' '; - p++; - } - strip_blank(s, s); - strlcat(s, " [多重原因]", REASON_LEN); - } - } - - if (strlen(s) < 4) - { - if (vmsg("原因太短。 要取消退回嗎? (y/N): ") == 'y') - { - *s = 0; - return; - } - } - } while (strlen(s) < 4); -} - -//////////////////////////////////////////////////////////////////////////// -// Regform Utilities -//////////////////////////////////////////////////////////////////////////// - -// TODO define and use structure instead, even in reg request file. -typedef struct { - // current format: - // (optional) num: unum, date - // [0] uid: xxxxx (IDLEN=12) - // [1] name: RRRRRR (20) - // [2] career: YYYYYYYYYYYYYYYYYYYYYYYYYY (40) - // [3] addr: TTTTTTTTT (50) - // [4] phone: 02DDDDDDDD (20) - // [5] email: x (50) (deprecated) - // [6] mobile: (deprecated) - // [7] ---- - // lasthost: 16 - char userid[IDLEN+1]; - - char exist; - char online; - char pad [ 5]; // IDLEN(12)+1+1+1+5=20 - - char name [20]; - char career[40]; - char addr [50]; - char phone [20]; -} RegformEntry; - -// regform format utilities -int -load_regform_entry(RegformEntry *pre, FILE *fp) -{ - char buf[STRLEN]; - char *v; - - memset(pre, 0, sizeof(RegformEntry)); - while (fgets(buf, sizeof(buf), fp)) - { - if (buf[0] == '-') - break; - buf[sizeof(buf)-1] = 0; - v = strchr(buf, ':'); - if (v == NULL) - continue; - *v++ = 0; - if (*v == ' ') v++; - chomp(v); - - if (strcmp(buf, "uid") == 0) - strlcpy(pre->userid, v, sizeof(pre->userid)); - else if (strcmp(buf, "name") == 0) - strlcpy(pre->name, v, sizeof(pre->name)); - else if (strcmp(buf, "career") == 0) - strlcpy(pre->career, v, sizeof(pre->career)); - else if (strcmp(buf, "addr") == 0) - strlcpy(pre->addr, v, sizeof(pre->addr)); - else if (strcmp(buf, "phone") == 0) - strlcpy(pre->phone, v, sizeof(pre->phone)); - } - return pre->userid[0] ? 1 : 0; -} - -int -print_regform_entry(const RegformEntry *pre, FILE *fp, int close) -{ - fprintf(fp, "uid: %s\n", pre->userid); - fprintf(fp, "name: %s\n", pre->name); - fprintf(fp, "career: %s\n", pre->career); - fprintf(fp, "addr: %s\n", pre->addr); - fprintf(fp, "phone: %s\n", pre->phone); - if (close) - fprintf(fp, "----\n"); - return 1; -} - -int -append_regform(const RegformEntry *pre, const char *logfn, - const char *varname, const char *varval1, const char *varval2) -{ - FILE *fout = fopen(logfn, "at"); - if (!fout) - return 0; - - print_regform_entry(pre, fout, 0); - if (varname && *varname) - { - syncnow(); - fprintf(fout, "Date: %s\n", Cdate(&now)); - if (!varval1) varval1 = ""; - fprintf(fout, "%s: %s", varname, varval1); - if (varval2) fprintf(fout, " %s", varval2); - fprintf(fout, "\n"); - } - // close it - fprintf(fout, "----\n"); - fclose(fout); - return 1; -} - -//////////////////////////////////////////////////////////////////////////// -// Regform2 API -//////////////////////////////////////////////////////////////////////////// - -// registration queue -int -regq_append(const char *userid) -{ - if (file_append_record(FN_REQLIST, userid) < 0) - return 0; - return 1; -} - -int -regq_find(const char *userid) -{ - return file_find_record(FN_REQLIST, userid); -} - -int -regq_delete(const char *userid) -{ - return file_delete_record(FN_REQLIST, userid, 0); -} - -// user home regform operation -int -regfrm_exist(const char *userid) -{ - char fn[PATHLEN]; - sethomefile(fn, userid, FN_REGFORM); - return dashf(fn) ? 1 : 0; -} - -int -regfrm_load(const char *userid, RegformEntry *pre) -{ - FILE *fp = NULL; - char fn[PATHLEN]; - int ret = 0; - sethomefile(fn, userid, FN_REGFORM); - if (!dashf(fn)) - return 0; - - fp = fopen(fn, "rt"); - if (!fp) - return 0; - ret = load_regform_entry(pre, fp); - fclose(fp); - return ret; -} - -int -regfrm_save(const char *userid, const RegformEntry *pre) -{ - FILE *fp = NULL; - char fn[PATHLEN]; - int ret = 0; - sethomefile(fn, userid, FN_REGFORM); - - fp = fopen(fn, "wt"); - if (!fp) - return 0; - ret = print_regform_entry(pre, fp, 1); - fclose(fp); - return ret; -} - -int -regfrm_trylock(const char *userid) -{ - int fd = 0; - char fn[PATHLEN]; - sethomefile(fn, userid, FN_REGFORM); - if (!dashf(fn)) return 0; - fd = open(fn, O_RDONLY); - if (fd < 0) return 0; - if (flock(fd, LOCK_EX|LOCK_NB) == 0) - return fd; - close(fd); - return 0; -} - -int -regfrm_unlock(int lockfd) -{ - int fd = lockfd; - if (lockfd <= 0) - return 0; - lockfd = flock(fd, LOCK_UN) == 0 ? 1 : 0; - close(fd); - return lockfd; -} - -// regform processors -int -regfrm_accept(RegformEntry *pre) -{ - char justify[REGLEN+1], buf[STRLEN*2]; - char fn[PATHLEN], fnlog[PATHLEN]; - - // dry run! - vmsg("regfrm_accept"); - return 1; - - sethomefile(fn, pre->userid, FN_REGFORM); - - // build justify string - removespace(pre->phone); - removespace(pre->career); - snprintf(justify, sizeof(justify), - "%s:%s:%s", pre->phone, pre->career, cuser.userid); - - // call handler - regform_accept(pre->userid, justify); - - // append current form to history. - sethomefile(fnlog, pre->userid, FN_REGFORM_LOG); - AppendTail(fn, fnlog, 0); - // global history - snprintf(buf, sizeof(buf), "Approved: %s -> %s\nDate: %s\n", - cuser.userid, pre->userid, Cdate(&now)); - file_append_line(FN_REGISTER_LOG, buf); - AppendTail(fn, FN_REGISTER_LOG, 0); - - // remove from queue - unlink(fn); - regq_delete(pre->userid); - return 1; -} - -int -regfrm_reject(RegformEntry *pre, const char *reason) -{ - char buf[STRLEN*2]; - char fn[PATHLEN]; - - // dry run! - vmsg("regfrm_reject"); - return 1; - - sethomefile(fn, pre->userid, FN_REGFORM); - - // call handler - regform_reject(pre->userid, reason); - - // log it - snprintf(buf, sizeof(buf), "Rejected: %s -> %s [%s]\nDate: %s\n", - cuser.userid, pre->userid, reason, Cdate(&now)); - file_append_line(FN_REGISTER_LOG, buf); - AppendTail(fn, FN_REGISTER_LOG, 0); - - // remove from queue - unlink(fn); - regq_delete(pre->userid); - return 1; -} - -int -regfrm_delete(const char *userid) -{ - char fn[PATHLEN]; - sethomefile(fn, userid, FN_REGFORM); - - // dry run! - vmsgf("regfrm_delete (%s)", userid); - return 1; - - // directly delete. - unlink(fn); - - // remove from queue - regq_delete(userid); - return 1; -} - -// working queue -FILE * -regq_init_pull() -{ - FILE *fp = tmpfile(), *src =NULL; - char buf[STRLEN]; - if (!fp) return NULL; - src = fopen(FN_REQLIST, "rt"); - if (!src) { fclose(fp); return NULL; } - while (fgets(buf, sizeof(buf), src)) - fputs(buf, fp); - fclose(src); - rewind(fp); - return fp; -} - -int -regq_pull(FILE *fp, char *uid) -{ - char buf[STRLEN]; - size_t idlen = 0; - uid[0] = 0; - if (fgets(buf, sizeof(buf), fp) == NULL) - return 0; - idlen = strcspn(buf, str_space); - if (idlen < 1) return 0; - if (idlen > IDLEN) idlen = IDLEN; - strlcpy(uid, buf, idlen+1); - return 1; -} - -int -regq_end_pull(FILE *fp) -{ - // no need to unlink because fp is a tmpfile. - if (!fp) return 0; - fclose(fp); - return 1; -} - -// UI part -int -ui_display_regform_single( - const userec_t *xuser, - const RegformEntry *pre, - int tid, char *reason) -{ - int c; - - while (1) - { - move(1, 0); - user_display(xuser, 1); - move(14, 0); - prints(ANSI_COLOR(1;32) - "--------------- 這是第 %2d 份註冊單 ------------------" - ANSI_RESET "\n", tid); - prints(" %-12s: %s\n", "帳號", pre->userid); - prints("0.%-12s: %s%s\n", "真實姓名", pre->name, - xuser->uflag2 & FOREIGN ? " (外籍)" : - ""); - prints("1.%-12s: %s\n", "服務單位", pre->career); - prints("2.%-12s: %s\n", "目前住址", pre->addr); - prints("3.%-12s: %s\n", "連絡電話", pre->phone); - - move(b_lines, 0); - outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] "); - - c = tolower(igetch() & 0xFF); // round to ASCII - if (c == 'y' || c == 'q' || c == 'd' || c == 's') - return c; - if (c == 'n') - { - int n = 0; - move(3, 0); - outs("\n" ANSI_COLOR(1;31) - "請提出退回申請表原因,按 取消:\n" ANSI_RESET); - for (n = 0; n < REJECT_REASONS; n++) - prints("%d) 請%s\n", n, reasonstr[n]); - outs("\n\n\n"); // preserved for prompt - - getdata(3+2+REJECT_REASONS+1, 0,"退回原因: ", - reason, REASON_LEN, DOECHO); - if (reason[0] == 0) - continue; - // interprete reason - return 'n'; - } - else if (REASON_IN_ABBREV(c)) - { - // quick set - sprintf(reason, "%c", c); - return 'n'; - } - return 's'; - } - // shall never reach here - return 's'; -} - -// sample validator -void -regform2_validate_single() -{ - int lfd = 0; - int tid = 0; - char uid[IDLEN+1]; - char rsn[REASON_LEN]; - FILE *fpregq = regq_init_pull(); - RegformEntry re; - - if (!fpregq) - return; - - while (regq_pull(fpregq, uid)) - { - userec_t muser; - int unum = 0; - int abort = 0; - - // check if user exists. - memset(&muser, 0, sizeof(muser)); - unum = getuser(uid, &muser); - - if (unum < 1) - { - regq_delete(uid); - continue; - } - - // check if regform exists. - if (!regfrm_exist(uid)) - { - // TODO delete here? - regq_delete(uid); - continue; - } - - // TODO check if user is already registered -#if 0 - if (muser.userlevel & PERM_LOGINOK) - { - regfrm_delete(uid); - continue; - } -#endif - - // try to lock - lfd = regfrm_trylock(uid); - if (lfd <= 0) - continue; - - // load it - if (!regfrm_load(uid, &re)) - { - regfrm_delete(uid); - regfrm_unlock(lfd); - // regq_delete(uid); // done in regfrm_delete - continue; - } - - tid ++; - // display regform and process - switch(ui_display_regform_single(&muser, &re, tid, rsn)) - { - case 'a': // accept - regfrm_accept(&re); - break; - - case 'd': // delete - regfrm_delete(uid); - break; - - case 'q': // quit - abort = 1; - break; - - case 'n': // reject - regfrm_reject(&re, rsn); - break; - - case 's': // skip - // do nothing. - break; - - default: // shall never reach here - assert(0); - break; - } - - // final processing - regfrm_unlock(lfd); - - if (abort) - break; - } - regq_end_pull(fpregq); - - // finishing - clear(); move(5, 0); - prints("您審了 %d 份註冊單份。", tid); - pressanykey(); -} - -#define FORMS_IN_PAGE (10) - -int -regform2_validate_page(int dryrun) -{ - int unum = 0; - int yMsg = FORMS_IN_PAGE*2+1; - userec_t muser; - RegformEntry forms [FORMS_IN_PAGE]; - char ans [FORMS_IN_PAGE]; - int lfds [FORMS_IN_PAGE]; - char rejects[FORMS_IN_PAGE][REASON_LEN]; // reject reason length - char rsn [REASON_LEN]; - int cforms = 0, // current loaded forms - ci = 0, // cursor index - ch = 0, // input key - i; - int tid = 0; - char uid[IDLEN+1]; - FILE *fpregq = regq_init_pull(); - - if (!fpregq) - return 0; - - while (ch != 'q') - { - // initialize and prepare - memset(ans, 0, sizeof(ans)); - memset(rejects, 0, sizeof(rejects)); - memset(forms, 0, sizeof(forms)); - memset(lfds, 0, sizeof(lfds)); - cforms = 0; - clear(); - - // load forms - while (cforms < FORMS_IN_PAGE) - { - if (!regq_pull(fpregq, uid)) - break; - i = cforms; // align index - - // check if user exists. - memset(&muser, 0, sizeof(muser)); - unum = getuser(uid, &muser); - if (unum < 1) - { - regq_delete(uid); - continue; - } - - // check if regform exists. - if (!regfrm_exist(uid)) - { - // TODO delete here? - regq_delete(uid); - continue; - } - // try to lock - lfds[i] = regfrm_trylock(uid); - if (lfds[i] <= 0) - continue; - - // load it - if (!regfrm_load(uid, &forms[i])) - { - regfrm_delete(uid); - regfrm_unlock(lfds[i]); - // regq_delete(uid); // done in regfrm_delete - continue; - } - - forms[i].exist = 1; - forms[i].online = search_ulist(unum) ? 1 : 0; - - // assign default answers - if (muser.userlevel & PERM_LOGINOK) - ans[i] = 'd'; -#ifdef REGFORM_DISABLE_ONLINE_USER - else if (forms[i].online) - ans[i] = 's'; -#endif // REGFORM_DISABLE_ONLINE_USER - - - // display - move(i*2, 0); - prints(" %2d%s %s%-12s " ANSI_RESET, - i+1, - (unum == 0) ? ANSI_COLOR(1;31) "D" : - ( (muser.userlevel & PERM_LOGINOK) ? - ANSI_COLOR(1;33) "Y" : -#ifdef REGFORM_DISABLE_ONLINE_USER - forms[i].online ? "s" : -#endif - "."), - forms[i].online ? ANSI_COLOR(1;35) : ANSI_COLOR(1), - forms[i].userid); - - prints( ANSI_COLOR(1;31) "%19s " - ANSI_COLOR(1;32) "%-40s" ANSI_RESET"\n", - forms[i].name, forms[i].career); - - move(i*2+1, 0); - prints(" %s %-50s%20s\n", - (muser.userlevel & PERM_NOREGCODE) ? - ANSI_COLOR(1;31) "T" ANSI_RESET : " ", - forms[i].addr, forms[i].phone); - - cforms++, tid ++; - } - - // if no more forms then leave. - if (cforms < 1) - break; - - // adjust cursor if required - if (ci >= cforms) - ci = cforms-1; - - // display page info - { - char msg[STRLEN]; - snprintf(msg, sizeof(msg), - "%s 已顯示 %d 份註冊單 ", // "(%2d%%) ", - dryrun? "(測試模式)" : "", - tid); - prints(ANSI_COLOR(7) "\n%78s" ANSI_RESET "\n", msg); - } - - // handle user input - prompt_regform_ui(); - ch = 0; - while (ch != 'q' && ch != ' ') { - ch = cursor_key(ci*2, 0); - switch (ch) - { - // nav keys - case KEY_UP: - case 'k': - if (ci > 0) ci--; - break; - - case KEY_DOWN: - case 'j': - ch = 'j'; // go next - break; - - // quick nav (assuming to FORMS_IN_PAGE=10) - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - ci = ch - '1'; - if (ci >= cforms) ci = cforms-1; - break; - case '0': - ci = 10-1; - if (ci >= cforms) ci = cforms-1; - break; - - /* - case KEY_HOME: ci = 0; break; - case KEY_END: ci = cforms-1; break; - */ - - // abort - case KEY_END: - case 'q': - ch = 'q'; - if (getans("確定要離開了嗎? (本頁變更將不會儲存) [y/N]: ") != 'y') - { - prompt_regform_ui(); - ch = 0; - continue; - } - break; - - // prepare to go next page - case KEY_PGDN: - case ' ': - ch = ' '; - - { - int blanks = 0; - // solving blank (undecided entries) - for (i = 0, blanks = 0; i < cforms; i++) - if (ans[i] == 0) blanks ++; - - if (!blanks) - break; - - // have more blanks - ch = getans("尚未指定的 %d 個項目要: (S跳過/y通過/n拒絕/e繼續編輯): ", - blanks); - } - - if (ch == 'e') - { - prompt_regform_ui(); - ch = 0; - continue; - } - if (ch == 'y') { - // do nothing. - } else if (ch == 'n') { - // query reject reason - resolve_reason(rsn, yMsg); - if (*rsn == 0) - ch = 's'; - } else ch = 's'; - - // filling answers - for (i = 0; i < cforms; i++) - { - if (ans[i] != 0) - continue; - ans[i] = ch; - if (ch != 'n') - continue; - strlcpy(rejects[i], rsn, REASON_LEN); - } - - ch = ' '; // go to page mode! - break; - - // function keys - case 'y': // accept -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - case 's': // skip - case 'd': // delete - case KEY_DEL: //delete - if (ch == KEY_DEL) ch = 'd'; - - grayout(ci*2, ci*2+1, GRAYOUT_DARK); - move_ansi(ci*2, 4); outc(ch); - ans[ci] = ch; - ch = 'j'; // go next - break; - - case 'u': // undo -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - grayout(ci*2, ci*2+1, GRAYOUT_NORM); - move_ansi(ci*2, 4); outc('.'); - ans[ci] = 0; - ch = 'j'; // go next - break; - - case 'n': // reject -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - // query for reason - resolve_reason(rejects[ci], yMsg); - prompt_regform_ui(); - - if (!rejects[ci][0]) - break; - - move(yMsg, 0); - prints("退回 %s 註冊單原因:\n %s\n", forms[ci].userid, rejects[ci]); - - // do reject - grayout(ci*2, ci*2+1, GRAYOUT_DARK); - move_ansi(ci*2, 4); outc(ch); - ans[ci] = ch; - ch = 'j'; // go next - - break; - } // switch(ch) - - // change cursor - if (ch == 'j' && ++ci >= cforms) - ci = cforms -1; - } // while(ch != QUIT/SAVE) - - // if exit, we still need to skip all read forms - if (ch == 'q') - { - for (i = 0; i < cforms; i++) - ans[i] = 's'; - } - - // page complete (save). - assert(ch == ' ' || ch == 'q'); - - // save/commit if required. - if (dryrun) - { - // prmopt for debug - clear(); - stand_title("測試模式"); - outs("您正在執行測試模式,所以剛審的註冊單並不會生效。\n" - "下面列出的是剛才您審完的結果:\n\n"); - - for (i = 0; i < cforms; i++) - { - char justify[REGLEN]; - if (ans[i] == 'y') - snprintf(justify, sizeof(justify), // build justify string - "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); - - prints("%2d. %-12s - %c %s\n", i+1, forms[i].userid, ans[i], - ans[i] == 'n' ? rejects[i] : - ans[i] == 'y' ? justify : ""); - } - if (ch != 'q') - pressanykey(); - } - else - { - // real functionality - for (i = 0; i < cforms; i++) - { - switch(ans[i]) - { - case 'a': // accept - regfrm_accept(&forms[i]); - break; - - case 'd': // delete - regfrm_delete(uid); - break; - - case 'n': // reject - regfrm_reject(&forms[i], rsn); - break; - - case 's': // skip - // do nothing. - break; - - default: - assert(0); - break; - } - } - } // !dryrun - - // unlock all forms - for (i = 0; i < cforms; i++) - regfrm_unlock(lfds[i]); - - } // while (ch != 'q') - - regq_end_pull(fpregq); - - // finishing - clear(); move(5, 0); - prints("您審了 %d 份註冊單份。", tid); - pressanykey(); - return 0; -} - -///////////////////////////////////////////////////////////////////////////// -// Regform UI -// 處理 Register Form -///////////////////////////////////////////////////////////////////////////// - -/* Auto-Regform-Scan - * 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; -} - -///////////////////////////////////////////////////////////////////////////// -// Traditional Regform UI -///////////////////////////////////////////////////////////////////////////// -// TODO XXX process someone directly, according to target_uid. -int -scan_register_form(const char *regfile, int automode, const char *target_uid) -{ - char genbuf[200]; - char *field[] = { - "uid", "name", "career", "addr", "phone", "email", NULL - }; - char *finfo[] = { - "帳號", "真實姓名", "服務單位", "目前住址", - "連絡電話", "電子郵件信箱", NULL - }; - char *reason[REJECT_REASONS+1] = { - "輸入真實姓名", - "詳填「(畢業)學校及『系』『級』」或「服務單位(含所屬縣市及職稱)」", - "填寫完整的住址資料 (含縣市名稱, 台北市請含行政區域)", - "詳填連絡電話 (含區域碼, 中間不用加 '-', '(', ')'等符號", - "精確並完整填寫註冊申請表", - "用中文填寫申請單", - NULL - }; - char *autoid = "AutoScan"; - userec_t muser; - FILE *fn, *fout, *freg; - char fdata[6][STRLEN]; - char fname[STRLEN] = "", buf[STRLEN]; - char ans[4], *ptr, *uid; - int n = 0, unum = 0, tid = 0; - int nSelf = 0, nAuto = 0; - - uid = cuser.userid; - move(2, 0); - - fn = pull_regform(regfile, fname, -1); - if (!fn) - return -1; - - while( fgets(genbuf, STRLEN, fn) ){ - memset(fdata, 0, sizeof(fdata)); - do { - if( genbuf[0] == '-' ) - break; - if ((ptr = (char *)strstr(genbuf, ": "))) { - *ptr = '\0'; - for (n = 0; field[n]; n++) { - if (strcmp(genbuf, field[n]) == 0) { - strlcpy(fdata[n], ptr + 2, sizeof(fdata[n])); - if ((ptr = (char *)strchr(fdata[n], '\n'))) - *ptr = '\0'; - } - } - } - } while( fgets(genbuf, STRLEN, fn) ); - tid ++; - - if ((unum = getuser(fdata[0], &muser)) == 0) { - move(2, 0); - clrtobot(); - outs("系統錯誤,查無此人\n\n"); - for (n = 0; field[n]; n++) - prints("%s : %s\n", finfo[n], fdata[n]); - pressanykey(); - } else { - if (automode) - uid = autoid; - - if ((!automode || !auto_scan(fdata, ans))) { - uid = cuser.userid; - - move(1, 0); - clrtobot(); - prints("帳號位置 : %d\n", unum); - user_display(&muser, 1); - move(14, 0); - prints(ANSI_COLOR(1;32) "------------- " - "請站長嚴格審核使用者資料,這是第 %d 份" - "------------" ANSI_RESET "\n", tid); - prints(" %-12s: %s\n", finfo[0], fdata[0]); -#ifdef FOREIGN_REG - prints("0.%-12s: %s%s\n", finfo[1], fdata[1], - muser.uflag2 & FOREIGN ? " (外籍)" : ""); -#else - prints("0.%-12s: %s\n", finfo[1], fdata[1]); -#endif - for (n = 2; field[n]; n++) { - prints("%d.%-12s: %s\n", n - 1, finfo[n], fdata[n]); - } - if (muser.userlevel & PERM_LOGINOK) { - ans[0] = getkey("此帳號已經完成註冊, " - "更新(Y/N/Skip)?[N] "); - if (ans[0] != 'y' && ans[0] != 's') - ans[0] = 'd'; - } else { - if (search_ulist(unum) == NULL) - { - move(b_lines, 0); clrtoeol(); - outs("是否接受此資料(Y/N/Q/Del/Skip)?[S] "); - // FIXME if the user got online here - ans[0] = igetch(); - } - else - ans[0] = 's'; - ans[0] = tolower(ans[0]); - if (ans[0] != 'y' && ans[0] != 'n' && - ans[0] != 'q' && ans[0] != 'd' && - !('0' <= ans[0] && ans[0] < ('0' + REJECT_REASONS))) - ans[0] = 's'; - ans[1] = 0; - } - nSelf++; - } else - nAuto++; - - switch (ans[0]) { - case 'q': - if ((freg = fopen(regfile, "a"))) { - for (n = 0; field[n]; n++) - fprintf(freg, "%s: %s\n", field[n], fdata[n]); - fprintf(freg, "----\n"); - while (fgets(genbuf, STRLEN, fn)) - fputs(genbuf, freg); - fclose(freg); - } - case 'd': - break; - - case '0': case '1': case '2': - case '3': case '4': case '5': - /* please confirm match REJECT_REASONS here */ - case 'n': - if (ans[0] == 'n') { - int nf = 0; - move(8, 0); - clrtobot(); - outs("請提出退回申請表原因,按 取消\n"); - for (n = 0; n < REJECT_REASONS; n++) - prints("%d) 請%s\n", n, reason[n]); - outs("\n"); // preserved for prompt - for (nf = 0; field[nf]; nf++) - prints("%s: %s\n", finfo[nf], fdata[nf]); - } else - buf[0] = ans[0]; - - if (ans[0] != 'n' || - getdata(9 + n, 0, "退回原因: ", buf, 60, DOECHO)) - if ((buf[0] - '0') >= 0 && (buf[0] - '0') < n) { - int i; - fileheader_t mhdr; - char title[128], buf1[80]; - FILE *fp; - - sethomepath(buf1, muser.userid); - stampfile(buf1, &mhdr); - strlcpy(mhdr.owner, cuser.userid, sizeof(mhdr.owner)); - strlcpy(mhdr.title, "[註冊失敗]", TTLEN); - mhdr.filemode = 0; - sethomedir(title, muser.userid); - if (append_record(title, &mhdr, sizeof(mhdr)) != -1) { - char rejfn[PATHLEN]; - fp = fopen(buf1, "w"); - - for(i = 0; buf[i] && i < sizeof(buf); i++){ - if (buf[i] >= '0' && buf[i] < '0'+n) - { - fprintf(fp, "[退回原因] 請%s\n", - reason[buf[i] - '0']); - } - } - - fclose(fp); - - // build reject file - sethomefile(rejfn, muser.userid, FN_REJECT_NOTIFY); - Copy(buf1, rejfn); - } - if ((fout = fopen(FN_REGISTER_LOG, "a"))) { - for (n = 0; field[n]; n++) - fprintf(fout, "%s: %s\n", field[n], fdata[n]); - fprintf(fout, "Date: %s\n", Cdate(&now)); - fprintf(fout, "Rejected: %s [%s]\n----\n", - uid, buf); - fclose(fout); - } - break; - } - move(10, 0); - clrtobot(); - outs("取消退回此註冊申請表"); - /* no break? */ - - case 's': - if ((freg = fopen(regfile, "a"))) { - for (n = 0; field[n]; n++) - fprintf(freg, "%s: %s\n", field[n], fdata[n]); - fprintf(freg, "----\n"); - fclose(freg); - } - break; - - default: - outs("以下使用者資料已經更新:\n"); - mail_muser(muser, "[註冊成功\囉]", "etc/registered"); - -#if FOREIGN_REG_DAY > 0 - if(muser.uflag2 & FOREIGN) - mail_muser(muser, "[出入境管理局]", "etc/foreign_welcome"); -#endif - - muser.userlevel |= (PERM_LOGINOK | PERM_POST); - strlcpy(muser.realname, fdata[1], sizeof(muser.realname)); - strlcpy(muser.address, fdata[3], sizeof(muser.address)); - strlcpy(muser.email, fdata[5], sizeof(muser.email)); - snprintf(genbuf, sizeof(genbuf), "%s:%s:%s", - fdata[4], fdata[2], uid); - strlcpy(muser.justify, genbuf, sizeof(muser.justify)); - - passwd_update(unum, &muser); - // XXX TODO notify users? - sendalert(muser.userid, ALERT_PWD_PERM); // force to reload perm - - sethomefile(buf, muser.userid, FN_JUSTIFY); - log_file(buf, LOG_CREAT, genbuf); - - if ((fout = fopen(FN_REGISTER_LOG, "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, FN_JUSTIFY_WAIT); - unlink(genbuf); - break; - } - } - } - - fclose(fn); - unlink(fname); - - clear(); move(5, 0); - prints("您審了 %d 份註冊單,AutoScan 審了 %d 份", nSelf, nAuto); - pressanykey(); - return (0); -} - -///////////////////////////////////////////////////////////////////////////// -// New Regform UI -///////////////////////////////////////////////////////////////////////////// - -// #define REGFORM_DISABLE_ONLINE_USER -// #define FORMS_IN_PAGE (10) - -int -handle_register_form(const char *regfile, int dryrun) -{ - int unum = 0; - int yMsg = FORMS_IN_PAGE*2+1; - FILE *fp = NULL; - userec_t muser; - RegformEntry forms [FORMS_IN_PAGE]; - char ans [FORMS_IN_PAGE]; - char rejects[FORMS_IN_PAGE][REASON_LEN]; // reject reason length - char fname [PATHLEN] = ""; - char justify[REGLEN+1]; - char rsn [REASON_LEN]; - int cforms = 0, // current loaded forms - parsed = 0, // total parsed forms - ci = 0, // cursor index - ch = 0, // input key - i, blanks; - long fsz = 0, fpos = 0; - - // prepare reg tickets - if (dryrun) - { - // directly open regfile to try - fp = fopen(regfile, "rt"); - } else { - fp = pull_regform(regfile, fname, -1); - } - - if (!fp) - return 0; - - // retreieve file info - fpos = ftell(fp); - fseek(fp, 0, SEEK_END); - fsz = ftell(fp); - fseek(fp, fpos, SEEK_SET); - if (!fsz) fsz = 1; - - while (ch != 'q') - { - // initialize and prepare - memset(ans, 0, sizeof(ans)); - memset(rejects, 0, sizeof(rejects)); - memset(forms, 0, sizeof(forms)); - cforms = 0; - - // load forms - while (cforms < FORMS_IN_PAGE && load_regform_entry(&forms[cforms], fp)) - cforms++, parsed ++; - - // if no more forms then leave. - // TODO what if regform error? - if (cforms < 1) - break; - - // adjust cursor if required - if (ci >= cforms) - ci = cforms-1; - - // display them all. - clear(); - for (i = 0; i < cforms; i++) - { - // fetch user information - memset(&muser, 0, sizeof(muser)); - unum = getuser(forms[i].userid, &muser); - forms[i].exist = unum ? 1 : 0; - if (unum) forms[i].online = search_ulist(unum) ? 1 : 0; - - // if already got login level, delete by default. - if (!unum) - ans[i] = 'd'; - else { - if (muser.userlevel & PERM_LOGINOK) - ans[i] = 'd'; -#ifdef REGFORM_DISABLE_ONLINE_USER - else if (forms[i].online) - ans[i] = 's'; -#endif // REGFORM_DISABLE_ONLINE_USER - } - - // print - move(i*2, 0); - prints(" %2d%s %s%-12s " ANSI_RESET, - i+1, - (unum == 0) ? ANSI_COLOR(1;31) "D" : - ( (muser.userlevel & PERM_LOGINOK) ? - ANSI_COLOR(1;33) "Y" : -#ifdef REGFORM_DISABLE_ONLINE_USER - forms[i].online ? "s" : -#endif - "."), - forms[i].online ? ANSI_COLOR(1;35) : ANSI_COLOR(1), - forms[i].userid); - - prints( ANSI_COLOR(1;31) "%19s " - ANSI_COLOR(1;32) "%-40s" ANSI_RESET"\n", - forms[i].name, forms[i].career); - - move(i*2+1, 0); - prints(" %s %-50s%20s\n", - (muser.userlevel & PERM_NOREGCODE) ? - ANSI_COLOR(1;31) "T" ANSI_RESET : " ", - forms[i].addr, forms[i].phone); - } - - // display page info - { - char msg[STRLEN]; - fpos = ftell(fp); - if (fpos > fsz) fsz = fpos*10; - snprintf(msg, sizeof(msg), - "%s 已顯示 %d 份註冊單 (%2d%%) ", - dryrun? "(測試模式)" : "", - parsed, (int)(fpos*100/fsz)); - prints(ANSI_COLOR(7) "\n%78s" ANSI_RESET "\n", msg); - } - - // handle user input - prompt_regform_ui(); - ch = 0; - while (ch != 'q' && ch != ' ') { - ch = cursor_key(ci*2, 0); - switch (ch) - { - // nav keys - case KEY_UP: - case 'k': - if (ci > 0) ci--; - break; - - case KEY_DOWN: - case 'j': - ch = 'j'; // go next - break; - - // quick nav (assuming to FORMS_IN_PAGE=10) - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': - ci = ch - '1'; - if (ci >= cforms) ci = cforms-1; - break; - case '0': - ci = 10-1; - if (ci >= cforms) ci = cforms-1; - break; - - /* - case KEY_HOME: ci = 0; break; - case KEY_END: ci = cforms-1; break; - */ - - // abort - case KEY_END: - case 'q': - ch = 'q'; - if (getans("確定要離開了嗎? (本頁變更將不會儲存) [y/N]: ") != 'y') - { - prompt_regform_ui(); - ch = 0; - continue; - } - break; - - // prepare to go next page - case KEY_PGDN: - case ' ': - ch = ' '; - - // solving blank (undecided entries) - for (i = 0, blanks = 0; i < cforms; i++) - if (ans[i] == 0) blanks ++; - - if (!blanks) - break; - - // have more blanks - ch = getans("尚未指定的 %d 個項目要: (S跳過/y通過/n拒絕/e繼續編輯): ", - blanks); - - if (ch == 'e') - { - prompt_regform_ui(); - ch = 0; - continue; - } - if (ch == 'y') { - // do nothing. - } else if (ch == 'n') { - // query reject reason - resolve_reason(rsn, yMsg); - if (*rsn == 0) - ch = 's'; - } else ch = 's'; - - // filling answers - for (i = 0; i < cforms; i++) - { - if (ans[i] != 0) - continue; - ans[i] = ch; - if (ch != 'n') - continue; - strlcpy(rejects[i], rsn, REASON_LEN); - } - - ch = ' '; // go to page mode! - break; - - // function keys - case 'y': // accept -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - case 's': // skip - case 'd': // delete - case KEY_DEL: //delete - if (ch == KEY_DEL) ch = 'd'; - - grayout(ci*2, ci*2+1, GRAYOUT_DARK); - move_ansi(ci*2, 4); outc(ch); - ans[ci] = ch; - ch = 'j'; // go next - break; - - case 'u': // undo -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - grayout(ci*2, ci*2+1, GRAYOUT_NORM); - move_ansi(ci*2, 4); outc('.'); - ans[ci] = 0; - ch = 'j'; // go next - break; - - case 'n': // reject -#ifdef REGFORM_DISABLE_ONLINE_USER - if (forms[ci].online) - { - vmsg("暫不開放審核在線上使用者。"); - break; - } -#endif - // query for reason - resolve_reason(rejects[ci], yMsg); - prompt_regform_ui(); - - if (!rejects[ci][0]) - break; - - move(yMsg, 0); - prints("退回 %s 註冊單原因:\n %s\n", forms[ci].userid, rejects[ci]); - - // do reject - grayout(ci*2, ci*2+1, GRAYOUT_DARK); - move_ansi(ci*2, 4); outc(ch); - ans[ci] = ch; - ch = 'j'; // go next - - break; - } // switch(ch) - - // change cursor - if (ch == 'j' && ++ci >= cforms) - ci = cforms -1; - } // while(ch != QUIT/SAVE) - - // if exit, we still need to skip all read forms - if (ch == 'q') - { - for (i = 0; i < cforms; i++) - ans[i] = 's'; - } - - // page complete (save). - assert(ch == ' ' || ch == 'q'); - - // save/commit if required. - if (dryrun) - { - // prmopt for debug - clear(); - stand_title("測試模式"); - outs("您正在執行測試模式,所以剛審的註冊單並不會生效。\n" - "下面列出的是剛才您審完的結果:\n\n"); - - for (i = 0; i < cforms; i++) - { - if (ans[i] == 'y') - snprintf(justify, sizeof(justify), // build justify string - "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); - - prints("%2d. %-12s - %c %s\n", i+1, forms[i].userid, ans[i], - ans[i] == 'n' ? rejects[i] : - ans[i] == 'y' ? justify : ""); - } - if (ch != 'q') - pressanykey(); - } - else - { - // real functionality - for (i = 0; i < cforms; i++) - { - if (ans[i] == 'y') - { - // build justify string - snprintf(justify, sizeof(justify), - "%s:%s:%s", forms[i].phone, forms[i].career, cuser.userid); - - regform_accept(forms[i].userid, justify); - // log form to FN_REGISTER_LOG - append_regform(&forms[i], FN_REGISTER_LOG, - "Approved", cuser.userid, NULL); - } - else if (ans[i] == 'n') - { - regform_reject(forms[i].userid, rejects[i]); - // log form to FN_REGISTER_LOG - append_regform(&forms[i], FN_REGISTER_LOG, - "Rejected", cuser.userid, rejects[i]); - } - else if (ans[i] == 's') - { - // append form back to fn_register - append_regform(&forms[i], fn_register, - NULL, NULL, NULL); - } - } - } // !dryrun - - } // while (ch != 'q') - - // cleaning left regforms - if (!dryrun) - { - pump_regform(regfile, fp); - fclose(fp); - unlink(fname); - } else { - // directly close file should be OK. - fclose(fp); - } - - return 0; -} - -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不審/Exp新界面)?[N] ", - ans, sizeof(ans), LCECHO); - if (ans[0] == 'a') - scan_register_form(fn_register, 1, NULL); - else if (ans[0] == 'y') - scan_register_form(fn_register, 0, NULL); - else if (ans[0] == 'e') - { -#ifdef EXP_ADMIN_REGFORM_DRYRUN - int dryrun = 0; - if (getans("你要進行純測試(T)還是真的執行審核(y)?") == 'y') - { - vmsg("進入實際執行模式,所有審核動作都是真的。"); - dryrun = 0; - } else { - vmsg("測試模式。"); - dryrun = 1; - } - handle_register_form(fn_register, dryrun); -#else - // run directly. - handle_register_form(fn_register, 0); -#endif - } - - return 0; -} - -int -cat_register(void) -{ - if (system("cat register.new.tmp >> register.new") == 0 && - unlink("register.new.tmp") == 0) - vmsg("OK 嚕~~ 繼續去奮鬥吧!!"); - else - vmsg("沒辦法CAT過去呢 去檢查一下系統吧!!"); - return 0; -} - -/* vim:sw=4 - */ diff --git a/mbbsd/reversi.c b/mbbsd/reversi.c deleted file mode 100644 index ea64f35a..00000000 --- a/mbbsd/reversi.c +++ /dev/null @@ -1,513 +0,0 @@ -/* $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) -{ - char buf[ANSILINELEN] = ""; - const int nStep = info->history.used; - int i, x, y; - - getyx(&y, &x); - for (i = 2; i <= 21; i++) - { - move(i, 0); - inansistr(buf, sizeof(buf)-1); - fprintf(fp, "%s\n", buf); - } - move(y, x); - - fprintf(fp, "\n"); - fprintf(fp, "按 z 可進入打譜模式\n"); - fprintf(fp, "\n"); - - fprintf(fp, "\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("\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("\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/mbbsd/screen.c b/mbbsd/screen.c deleted file mode 100644 index 8b518070..00000000 --- a/mbbsd/screen.c +++ /dev/null @@ -1,819 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#if !defined(USE_PFTERM) - -#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; -static unsigned char _typeahead = 1; - -#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; - } -} - -int -resizeterm(int w, int h) -{ - screenline_t *new_picture; - - /* 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 0; - } - memcpy(new_picture, big_picture, t_lines * sizeof(screenline_t)); - free(big_picture); - big_picture = new_picture; - return 1; - } - return 0; -} - -void -move(int y, int x) -{ - if (y < 0) y = 0; - if (y >= t_lines) y = t_lines -1; - if (x < 0) x = 0; - if (x >= ANSILINELEN) x = ANSILINELEN -1; - // assert(y>=0); - // assert(x>=0); - cur_col = x; - cur_ln = y; -} - -void -move_ansi(int y, int x) -{ - // take ANSI length in consideration - register screenline_t *slp; - if (y < 0) y = 0; - if (y >= t_lines) y = t_lines -1; - if (x < 0) x = 0; - if (x >= ANSILINELEN) x = ANSILINELEN -1; - - cur_ln = y; - cur_col = x; - - if (y >= scr_lns || x < 1) - return; - - slp = &big_picture[y]; - if (slp->len < 1) - return; - - slp->data[slp->len] = 0; - x += (strlen((char*)slp->data) - strlen_noansi((char*)slp->data)); - cur_col = x; -} - -void -getyx(int *y, int *x) -{ - *y = cur_ln; - *x = cur_col; -} - -void -getyx_ansi(int *py, int *px) -{ - // take ANSI length in consideration - register screenline_t *slp; - int y = cur_ln, x = cur_col; - char c = 0; - - if (y < 0) y = 0; - if (y >= t_lines) y = t_lines -1; - if (x < 0) x = 0; - if (x >= ANSILINELEN) x = ANSILINELEN -1; - - *py = y; *px = x; - - if (y >= scr_lns || x < 1) - return; - - slp = &big_picture[y]; - if (slp->len < 1) - return; - c = slp->data[x]; - *px += (strlen((char*)slp->data) - strlen_noansi((char*)slp->data)); - slp->data[x] = c; -} - -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 -redrawwin(void) -{ - register screenline_t *bp; - register int i, j; - int 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); - -#ifdef DBCSAWARE - if (!(bp->mode & STANDOUT) && - (cuser.uflag & DBCS_NOINTRESC) && - DBCS_RemoveIntrEscape(bp->data, &len)) - { - // if anything changed, dirty whole line. - bp->len = len; - } -#endif // DBCSAWARE - - 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(); -} - -int -typeahead(int fd) -{ - switch(fd) - { - case TYPEAHEAD_NONE: - _typeahead = 0; - break; - case TYPEAHEAD_STDIN: - _typeahead = 1; - break; - default: // shall never reach here - assert(NULL); - break; - } - return 0; -} - -void -refresh(void) -{ - if (num_in_buf() && _typeahead) - return; - doupdate(); -} - -void -doupdate(void) -{ - /* TODO remove unnecessary refresh() call, to save CPU time */ - register screenline_t *bp = big_picture; - register int i, j; - int len; - if ((docls) || (abs(scrollcnt) >= (scr_lns - 3))) { - redrawwin(); - return; - } - if (scrollcnt < 0) { - if (!scrollrevlen) { - redrawwin(); - 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); - -#ifdef DBCSAWARE - if (!(bp->mode & STANDOUT) && - (cuser.uflag & DBCS_NOINTRESC) && - DBCS_RemoveIntrEscape(bp->data, &len)) - { - // if anything changed, dirty whole line. - bp->len = len; - bp->smod = 0; bp->emod = len; - } -#endif // DBCSAWARE - -#if 0 - // disabled now, bugs: - // (1) input number (goto) in bbs list (search_num) - // (2) some empty lines becomes weird (eg, b_config) - // - // more effort to determine ANSI smod - if (bp->smod > 0) - { - int iesc; - for (iesc = bp->smod-1; iesc >= 0; iesc--) - { - if (bp->data[iesc] == ESC_CHR) - { - bp->smod = 0;// iesc; - bp->emod =len -1; - break; - } - } - } -#endif - - 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; imode = 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 == 0) // TODO and contains ANSI - { - // workaround poor ANSI issue - size_t sz = (slp->len > slp->oldlen) ? slp->len : slp->oldlen; - sz = (sz < ANSILINELEN) ? sz : ANSILINELEN; - memset(slp->data, ' ', sz); - slp->len = 0; - return; - } - */ - - 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; -} - - -void newwin (int nlines, int ncols, int y, int x) -{ - int i=0, oy, ox; - getyx(&oy, &ox); - - while (nlines-- > 0) - { - move_ansi(y++, x); - for (i = 0; i < ncols; i++) - outc(' '); - } - move(oy, ox); -} - -/** - * 從目前的行數(scr_ln) clear 到第 line 行 - */ -void -clrtoln(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) -{ - clrtoln(scr_lns); -} - -void -outc(unsigned char c) -{ - register screenline_t *slp = GetCurrentLine(); - register int i; - - // 0xFF is invalid for most cases (even DBCS), - if (c == 0xFF || c == 0x00) - return; - - 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) -{ - if (!str) - return; - while (*str) { - outc(*str++); - } -} - -void -outns(const char *str, int n) -{ - if (!str) - return; - while (*str && n-- > 0) { - outc(*str++); - } -} - -void -outstr(const char *str) -{ - // XXX TODO cannot prepare DBCS-ready environment? - - outs(str); -} - -void -addch(unsigned char c) -{ - outc(c); -} - -void -addstr(const char *s) -{ - outs(s); -} - -void -addnstr(const char *s, int n) -{ - outns(s, n); -} - -void -addstring(const char *s) -{ - outs(s); -} - - -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); - } -} - -// readback -int -instr(char *str) -{ - register screenline_t *slp = GetCurrentLine(); - *str = 0; - if (!slp) - return 0; - slp->data[slp->len] = 0; - strip_ansi(str, (char*)slp->data, STRIP_ALL); - return strlen(str); -} - -int -innstr(char *str, int n) -{ - register screenline_t *slp = GetCurrentLine(); - char buf[ANSILINELEN]; - *str = 0; - if (!slp) - return 0; - slp->data[slp->len] = 0; - strip_ansi(buf, (char*)slp->data, STRIP_ALL); - buf[ANSILINELEN] = 0; - strlcpy(str, buf, n); - return strlen(str); -} - -int -inansistr(char *str, int n) -{ - register screenline_t *slp = GetCurrentLine(); - *str = 0; - if (!slp) - return 0; - slp->data[slp->len] = 0; - strlcpy(str, (char*)slp->data, n); - return strlen(str); -} - -// level: -// -1 - bold out -// 0 - dark text -// 1 - text -// 2 - no highlight (not implemented) -void -grayout(int y, int end, int level) -{ - register screenline_t *slp = NULL; - char buf[ANSILINELEN]; - int i = 0; - - if (y < 0) y = 0; - if (end > b_lines) end = b_lines; - - // TODO change to y <= end someday - // loop lines - for (; y <= end; y ++) - { - // modify by scroll - i = y + roll; - if (i < 0) - i += scr_lns; - else if (i >= scr_lns) - i %= scr_lns; - - slp = &big_picture[i]; - - if (slp->len < 1) - continue; - - if (slp->len >= ANSILINELEN) - slp->len = ANSILINELEN -1; - - // tweak slp - slp->data[slp->len] = 0; - slp->mode &= ~STANDOUT; - slp->mode |= MODIFIED; - slp->oldlen = 0; - slp->sso = slp->eso = 0; - slp->smod = 0; - - // make slp->data a pure string - for (i=0; ilen; i++) - { - if (!slp->data[i]) - slp->data[i] = ' '; - } - - slp->len = strip_ansi(buf, (char*)slp->data, STRIP_ALL); - buf[slp->len] = 0; - - switch(level) - { - case GRAYOUT_DARK: // dark text - case GRAYOUT_BOLD:// bold text - // basically, in current system slp->data will - // not exceed t_columns. buffer overflow is impossible. - // but to make it more robust, let's quick check here. - // of course, t_columns should always be far smaller. - if (strlen((char*)slp->data) > t_columns) - slp->data[t_columns] = 0; - strcpy((char*)slp->data, - level < 0 ? ANSI_COLOR(1) : ANSI_COLOR(1;30;40)); - strcat((char*)slp->data, buf); - strcat((char*)slp->data, ANSI_RESET ANSI_CLRTOEND); - slp->len = strlen((char*)slp->data); - break; - - case GRAYOUT_NORM: // Plain text - memcpy(slp->data, buf, slp->len + 1); - break; - } - slp->emod = slp->len -1; - } -} - -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 scr_dump(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 scr_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); - redrawwin(); -} - -#endif // !defined(USE_PFTERM) - -/* vim:sw=4 - */ diff --git a/mbbsd/stuff.c b/mbbsd/stuff.c deleted file mode 100644 index 2349a68d..00000000 --- a/mbbsd/stuff.c +++ /dev/null @@ -1,828 +0,0 @@ -/* $Id$ */ -#include "bbs.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 const char * const str_dotdir = FN_DIR; - -/* 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; -} - -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 (!isalpha(id[0])) - return 0; - for (i = 1; i < len; i++) - if (!isalnum(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; -} - -int -belong(const char *filelist, const char *key) -{ - return file_exist_record(filelist, key); -} - - -#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; - time_t t; - - 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); - // signed: limited on (2037, ...) - // unsigned: limited on (..., 1970) - // let's restrict inside the boundary. - } while ((endtime.tm_year = atoi(yn) - 1900) < 70 || endtime.tm_year > 135); - 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); - t = mktime(&endtime); - /* saturation check */ - if(t < 0) - t = 1; - if(t > INT_MAX) - t = INT_MAX; - return t; -} - -// synchronize 'now' -void syncnow(void) -{ -#ifdef OUTTA_TIMER - now = SHM->GV2.e.now; -#else - now = time(0); -#endif -} - -#endif - - -#ifndef _BBS_UTIL_C_ -/* 這一區都是有關於畫面處理的, 故 _BBS_UTIL_C_ 不須要 */ - -#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_RESET); - do { - ch = igetch(); - 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[3]; - va_list ap; - va_start(ap, fmt); - vsnprintf(msg , sizeof(msg), 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 , sizeof(msg), 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) - - // what is 200/1431/506/201? -static const char* msg_pressanykey_trail = - ANSI_COLOR(33;46) " [按任意鍵繼續] " 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 - pad -2 ; - /* pad is now those left . */ - if (pad > 0) - { - for (i = 0; i <= pad-2; i += 2) - outs("▄"); - if (i == pad-1) - outs(" "); - } - outs(ANSI_RESET); - } 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 { - i = igetch(); - } 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), 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: SHOWFILE_*, see modes.h - * @return 失敗傳回 0,否則為 1。 - * 2 表示有 PttPrints 碼 - */ -int -show_file(const char *filename, int y, int lines, int mode) -{ - FILE *fp; - char buf[1024]; - int ret = 1; - int strpmode = STRIP_ALL; - - if (mode & SHOWFILE_ALLOW_COLOR) - strpmode = ONLY_COLOR; - if (mode & SHOWFILE_ALLOW_MOVE) - strpmode = NO_RELOAD; - - if (y >= 0) - move(y, 0); - clrtoln(lines + y); - if ((fp = fopen(filename, "r"))) { - while (fgets(buf, sizeof(buf), fp) && lines--) - { - move(y++, 0); - if (mode == SHOWFILE_RAW) - { - outs(buf); - } - else if ((mode & SHOWFILE_ALLOW_STAR) && (strstr(buf, ESC_STR "*") != NULL)) - { - // because Ptt_prints escapes are not so often, - // let's try harder to detect it. - outs(Ptt_prints(buf, sizeof(buf), strpmode)); - ret = 2; - } else { - // ESC is very common... - strip_ansi(buf, buf, strpmode); - outs(buf); - } - } - fclose(fp); - outs(ANSI_RESET); // prevent some broken Welcome file - } else - return 0; - return ret; -} - -void -bell(void) -{ - char c; - - c = Ctrl('G'); - write(1, &c, 1); -} - -int -search_num(int ch, int max) -{ - int clen = 1, y = b_lines - msg_occupied; - char genbuf[10]; - - genbuf[0] = ch; genbuf[1] = 0; - clen = getdata_buf(y, 0, - " 跳至第幾項: ", genbuf, sizeof(genbuf)-1, NUMECHO); - - move(y, 0); clrtoeol(); - genbuf[clen] = '\0'; - 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 , sizeof(msg), fmt, ap); - va_end(ap); - - sethomefile(filename, cuser.userid, "USERLOG"); - return log_filef(filename, LOG_CREAT, "%s: %s %s", cuser.userid, msg, Cdate(&now)); -} - -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, SHOWFILE_ALLOW_ALL); -#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 -DBCS_StringHash(const char *s) -{ - return fnv1a_32_dbcs_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); -} - -/* AIDS */ -aidu_t fn2aidu(char *fn) -{ - aidu_t aidu = 0; - aidu_t type = 0; - aidu_t v1 = 0; - aidu_t v2 = 0; - char *sp = fn; - - if(fn == NULL) - return 0; - - switch(*(sp ++)) - { - case 'M': - type = 0; - break; - case 'G': - type = 1; - break; - default: - return 0; - break; - } - - if(*(sp ++) != '.') - return 0; - v1 = strtoul(sp, &sp, 10); - if(sp == NULL) - return 0; - if(*sp != '.' || *(sp + 1) != 'A') - return 0; - sp += 2; - if(*(sp ++) == '.') - { - v2 = strtoul(sp, &sp, 16); - if(sp == NULL) - return 0; - } - aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffff) << 12) | (v2 & 0xfff); - - return aidu; -} - -/* IMPORTANT: - * size of buf must be at least 8+1 bytes - */ -char *aidu2aidc(char *buf, aidu_t aidu) -{ - const char aidu2aidc_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"; - const int aidu2aidc_tablesize = sizeof(aidu2aidc_table) - 1; - char *sp = buf + 8; - aidu_t v; - - *(sp --) = '\0'; - while(sp >= buf) - { - /* FIXME: 能保證 aidu2aidc_tablesize 是 2 的冪次的話, - 這裡可以改用 bitwise operation 做 */ - v = aidu % aidu2aidc_tablesize; - aidu = aidu / aidu2aidc_tablesize; - *(sp --) = aidu2aidc_table[v]; - } - return buf; -} - -/* IMPORTANT: - * size of fn must be at least FNLEN bytes - */ -char *aidu2fn(char *fn, aidu_t aidu) -{ - aidu_t type = ((aidu >> 44) & 0xf); - aidu_t v1 = ((aidu >> 12) & 0xffffffff); - aidu_t v2 = (aidu & 0xfff); - - if(fn == NULL) - return NULL; - snprintf(fn, FNLEN - 1, "%c.%d.A.%03X", ((type == 0) ? 'M' : 'G'), (unsigned int)v1, (unsigned int)v2); - fn[FNLEN - 1] = '\0'; - return fn; -} - -aidu_t aidc2aidu(char *aidc) -{ - char *sp = aidc; - aidu_t aidu = 0; - - if(aidc == NULL) - return 0; - - while(*sp != '\0' && /* ignore trailing spaces */ *sp != ' ') - { - aidu_t v = 0; - /* FIXME: 查表法會不會比較快? */ - if(*sp >= '0' && *sp <= '9') - v = *sp - '0'; - else if(*sp >= 'A' && *sp <= 'Z') - v = *sp - 'A' + 10; - else if(*sp >= 'a' && *sp <= 'z') - v = *sp - 'a' + 36; - else if(*sp == '-') - v = 62; - else if(*sp == '_') - v = 63; - else - return 0; - aidu <<= 6; - aidu |= (v & 0x3f); - sp ++; - } - - return aidu; -} - -int search_aidu(char *bfile, aidu_t aidu) -{ - char fn[FNLEN]; - int fd; - fileheader_t fhs[64]; - int len, i; - int pos = 0; - int found = 0; - int lastpos = 0; - - if(aidu2fn(fn, aidu) == NULL) - return -1; - if((fd = open(bfile, O_RDONLY, 0)) < 0) - return -1; - - while(!found && (len = read(fd, fhs, sizeof(fhs))) > 0) - { - len /= sizeof(fileheader_t); - for(i = 0; i < len; i ++) - { - int l; - if(strcmp(fhs[i].filename, fn) == 0 || - ((aidu & 0xfff) == 0 && (l = strlen(fhs[i].filename)) > 6 && - strncmp(fhs[i].filename, fn, l) == 0)) - { - if(fhs[i].filemode & FILE_BOTTOM) - { - lastpos = pos; - } - else - { - found = 1; - break; - } - } - pos ++; - } - } - close(fd); - - return (found ? pos : (lastpos ? lastpos : -1)); -} -/* end of AIDS */ diff --git a/mbbsd/syspost.c b/mbbsd/syspost.c deleted file mode 100644 index 9ced9918..00000000 --- a/mbbsd/syspost.c +++ /dev/null @@ -1,146 +0,0 @@ -/* $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) -{ - char genbuf[(NUMPERMS+1) * STRLEN*2] = "", reason[30] = ""; - char title[TTLEN+1]; - char *s = genbuf; - int i, flag = 0; - - // generate log (warning: each line should <= STRLEN*2) - for (i = 0; i < NUMPERMS; i++) { - if (((oldperm >> i) & 1) != ((newperm >> i) & 1)) { - sprintf(s, " 站長" 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]); - s += strlen(s); - flag++; - } - } - - if (!flag) // nothing changed - return; - - clrtobot(); - clear(); - while (!getdata(5, 0, "請輸入理由以示負責:", - reason, sizeof(reason), DOECHO)); - sprintf(s, "\n " ANSI_COLOR(1;37) "站長%s修改權限理由是:%s" ANSI_RESET, - cuser.userid, reason); - - snprintf(title, sizeof(title), "[公安報告] 站長%s修改%s權限報告", - cuser.userid, userid); - - post_msg(GLOBAL_SECURITY, title, genbuf, "[系統安全局]"); -} - -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, "[" BBSMNAME "法院]"); - - 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, "[" BBSMNAME "法院]"); -} - -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/mbbsd/talk.c b/mbbsd/talk.c deleted file mode 100644 index 70bcdb97..00000000 --- a/mbbsd/talk.c +++ /dev/null @@ -1,3654 +0,0 @@ -/* $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 - -#define TALK_MAXCOL (78) -#define TALK_BUFLEN (TALK_MAXCOL+2) -typedef struct twpic { - unsigned short len; - unsigned char data[TALK_BUFLEN]; // bound to specific size -} twpic_t; - -typedef struct talkwin_t { - int curcol, curln; - int sline, eline; - twpic_t *big_picture; -} 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); -} - -int query_online(const char *userid) -{ - userinfo_t *uentp; - - if (!userid || !*userid) - return 0; - - if (!isalnum(*userid)) - return 0; - - if (strchr(userid, '.') || SHM->GV2.e.noonlineuser) - return 0; - - uentp = search_ulist_userid(userid); - - if (!uentp ||!isvisible(currutmp, uentp)) - return 0; - - return 1; -} - -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; iuinfo[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(); -} - -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 { - -#ifndef USE_PFTERM - // workaround poor terminal, made by in2. - move(8 + which, 28); - outs("123456789012345678901234567890"); -#endif // !USE_PFTERM - - 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) { - grayout(0, b_lines-5, GRAYOUT_DARK); - move(b_lines - 4, 0); - clrtobot(); - outs(ANSI_COLOR(1;33;45) "★水球整理程式 " ANSI_RESET "\n" - "提醒您: 可將水球存入信箱(M)後, 到【郵件選單】該信件前按 u,\n" - "系統會將水球紀錄重新整理後寄送給您唷! " ANSI_RESET "\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 - { - int i, iend = twin->eline - twin->sline; - region_scroll_up(twin->sline, twin->eline); - // also scroll up our buffer - for (i = 0; i < iend; i++) - { - memcpy(&twin->big_picture[i], &twin->big_picture[i+1], - sizeof(twpic_t)); - } - // zero last line - memset(&twin->big_picture[iend], 0, sizeof(twpic_t)); - } - move(twin->curln, twin->curcol); -} - -static int -iscompletedbcs(const unsigned char* str) -{ - int isdbcs = 0; - while (*str) - { - if (isdbcs == 1) isdbcs = 2; - else if ((unsigned char)*str > 0x80) isdbcs = 1; - else isdbcs = 0; - str++; - } - - return (isdbcs == 1) ? 0 : 1; -} - -static void -talk_refreshline(talkwin_t *twin) -{ - // dirty screen - twpic_t *line = twin->big_picture + (twin->curln - twin->sline); - int iscomplete = iscompletedbcs(line->data); - int len = strlen((char*)line->data); - - move(twin->curln, 0); - clrtoeol(); - if (!iscomplete) len--; - outs_n((char*)line->data, len); - if (!iscomplete) outc('?'); - move(twin->curln, twin->curcol); -} - -static void -do_talk_char(talkwin_t * twin, int ch, FILE *flog) -{ - twpic_t *line; - char buf[TALK_BUFLEN] = ""; - - line = twin->big_picture+(twin->curln - twin->sline); - - if (isprint2(ch)) { - - - if (line->len < TALK_MAXCOL) - move(twin->curln, twin->curcol); - else - do_talk_nextline(twin); - - // do_talk_nextline may change current line - line = twin->big_picture + (twin->curln -twin->sline); - memmove(line->data + twin->curcol+1, line->data + twin->curcol, - line->len - twin->curcol +1); - line->data[twin->curcol++] = ch; - line->data[++line->len] = 0; - - // dirty screen - talk_refreshline(twin); - - if (line->len < TALK_MAXCOL) - return; - - // max buffer, log it. - strlcpy(buf, (char *)line->data, line->len + 1); - - } else switch (ch) { - - case Ctrl('G'): - bell(); - return; - - case Ctrl('A'): - twin->curcol = 0; - move(twin->curln, twin->curcol); - return; - case Ctrl('E'): - twin->curcol = line->len; - move(twin->curln, twin->curcol); - return; - - - case Ctrl('K'): - line->len = twin->curcol; - memset(line->data+line->len, 0, TALK_MAXCOL - line->len); - move(twin->curln, twin->curcol); - clrtoeol(); - return; - case Ctrl('Y'): - twin->curcol = 0; - line->len = 0; - memset(line->data, 0, TALK_MAXCOL); - move(twin->curln, twin->curcol); - clrtoeol(); - return; - - case Ctrl('M'): - case Ctrl('J'): - strlcpy(buf, (char *)line->data, line->len + 1); - buf[line->len] = 0; - do_talk_nextline(twin); - break; - - case Ctrl('B'): - if (twin->curcol > 0) { - --(twin->curcol); -#ifdef DBCSAWARE - if(twin->curcol > 0 && twin->curcol < line->len && ISDBCSAWARE()) - { - if(getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) - twin->curcol --; - } -#endif - move(twin->curln, twin->curcol); - } - return; - - case Ctrl('F'): - if (twin->curcol < line->len) { - ++(twin->curcol); -#ifdef DBCSAWARE - if(twin->curcol < TALK_MAXCOL && twin->curcol < line->len && ISDBCSAWARE()) - { - if(getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) - twin->curcol++; - } -#endif - move(twin->curln, twin->curcol); - } - return; - - - case Ctrl('P'): - strlcpy(buf, (char *)line->data, line->len + 1); - if (twin->curln > twin->sline) { - --twin->curln; - line = twin->big_picture + (twin->curln -twin->sline); - } - if (twin->curcol > line->len) - twin->curcol = line->len; - -#ifdef DBCSAWARE - // curln may be changed. - if(twin->curcol > 0 && twin->curcol < line->len && - getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) - twin->curcol--; -#endif - move(twin->curln, twin->curcol); - // XXX break here (for log)? - break; - - case Ctrl('N'): - strlcpy(buf, (char *)line->data, line->len + 1); - if (twin->curln < twin->eline) { - ++twin->curln; - line = twin->big_picture + (twin->curln -twin->sline); - } - if (twin->curcol > line->len) - twin->curcol = line->len; - -#ifdef DBCSAWARE - // curln may be changed. - line = twin->big_picture + (twin->curln -twin->sline); - if(twin->curcol > 0 && twin->curcol < line->len && - getDBCSstatus(line->data, twin->curcol) == DBCS_TRAILING) - twin->curcol--; -#endif - move(twin->curln, twin->curcol); - // XXX break here (for log)? - break; - - // complex data change - case Ctrl('H'): - case '\177': - if (twin->curcol > 0) - { - int delta = 1; - -#ifdef DBCSAWARE - if (twin->curcol > 1 && ISDBCSAWARE() && - getDBCSstatus(line->data, twin->curcol-1) == DBCS_TRAILING) - delta++; -#endif - memmove(line->data + twin->curcol-delta, line->data + twin->curcol, - line->len - twin->curcol +1); - line->len -= delta; - twin->curcol -= delta; - // dirty screen - talk_refreshline(twin); - } - return; - - case Ctrl('D'): - if (twin->curcol < line->len) - { - int delta = 1; - -#ifdef DBCSAWARE - if (ISDBCSAWARE() && - getDBCSstatus(line->data, twin->curcol) == DBCS_LEADING) - delta++; -#endif - memmove(line->data + twin->curcol, line->data + twin->curcol+delta, - line->len - twin->curcol -delta+1); - line->len -= delta; - memset(line->data + line->len, 0, TALK_MAXCOL - line->len); - // dirty screen - talk_refreshline(twin); - } - return; - } - - 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; - - // memory allocation - mywin.big_picture = (twpic_t*) malloc ( - sizeof(twpic_t) * (mywin.eline - mywin.sline +1)); - itswin.big_picture = (twpic_t*) malloc ( - sizeof(twpic_t) * (itswin.eline- itswin.sline +1)); - - // reset buffer - for (i = mywin.sline; i <= mywin.eline; i++) - { - mywin.big_picture[i - mywin.sline].len = 0; - memset(mywin.big_picture[i-mywin.sline].data, 0, TALK_BUFLEN); - } - for (i = itswin.sline; i <= itswin.eline; i++) - { - itswin.big_picture[i - itswin.sline].len = 0; - memset(itswin.big_picture[i-itswin.sline].data, 0, TALK_BUFLEN); - } - - clear(); - move(mywin.eline+1, 0); - outs(mid_line); - move(0, 0); - - add_io(fd, 0); - - while (1) { - ch = igetch(); - if (ch == I_OTHERDATA) { - // getyx(&y, &x); - datac = recv(fd, data, sizeof(data), 0); - if (datac <= 0) - break; - for (i = 0; i < datac; i++) - do_talk_char(&itswin, data[i], flog); - // move(y, x); - } 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)); - - fprintf(flog, "[%s]:\n", cuser.userid); - for (i = 0; i < mywin.eline - mywin.sline +1; i++) - if (mywin.big_picture[i].len) - fprintf(flog, "%.*s\n", mywin.big_picture[i].len, mywin.big_picture[i].data); - - fprintf(flog, "[%s]:\n", getuserid(currutmp->destuid)); - for (i = 0; i < itswin.eline - itswin.sline +1; i++) - if (itswin.big_picture[i].len) - fprintf(flog, "%.*s\n", itswin.big_picture[i].len, itswin.big_picture[i].data); - - fclose(flog); - redrawwin(); - more(fpath, NA); - - // force user decide how to deal with the log - while (1) { - getdata(b_lines - 1, 0, "清除(C) 移至備忘錄(M). (c/m)? ", - ans, sizeof(ans), LCECHO); - if (ans[0] == 'c' || ans[0] == 'm') - break; - move(b_lines-2, 0); - prints(ANSI_COLOR(0;1;3%d) "請正確輸入 c 或 m 的指令。" ANSI_RESET, - (now % 7)+1); - if (ans[0] == 0) outs("為避免誤按所以只 ENTER 是不行的。"); - } - - 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; - } - - // free memory - free(mywin.big_picture); - free(itswin.big_picture); - setutmpmode(XINFO); - redrawwin(); -} - -#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, ch; - pid_t pid; - char c; - char genbuf[4]; - unsigned char mode0 = currutmp->mode; - - 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<withme & (1<<(j+i))) - if(withme_str[j/2]) { - outs(withme_str[j/2]); - outc(' '); - } - outc('\n'); - } - } - move(4, 0); - outs("要和他(她) (T)談天(F)下五子棋" -#ifdef USE_CHICKEN_PK - "(P)鬥寵物" -#endif // USE_CHICKEN_PK - "(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; -#ifdef USE_CHICKEN_PK - case 'p': - { - userec_t xuser; - chicken_t xchk; - int error = 0; - - getuser(uin->userid, &xuser); - if (uin->lockmode == CHICKEN || currutmp->lockmode == CHICKEN) - error = 1; - else if (!load_chicken(cuser.userid, &xchk) || - !load_chicken(xuser.userid, &xchk)) - error = 2; - - if (error) { - vmsg(error == 2 ? "並非兩人都養寵物" : - "有一方的寵物正在使用中"); - return; - } - uin->sig = SIG_PK; - } - break; -#endif // USE_CHICKEN_PK - 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; -#ifdef USE_CHICKEN_PK - case SIG_PK: - chickenpk(msgsock); - break; -#endif - 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 - */ -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 當 friendtotalbrc_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<withme&=~(1<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 '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) { - give_money_ui(uentp->userid); - 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); - outs("系統提供 一般 進階 未來 三種模式\n" - "在切換後請正常下線再重新登入, 以確保結構正確\n"); - vmsgf( "目前切換到 %s 水球模式", wm[tmp]); - redrawall = redraw = 1; - } - break; - - case 'r': - if (HasUserPerm(PERM_LOGINOK)) { - if (curredit & EDIT_MAIL) { - /* deny reentrance, which may cause many problems */ - vmsg("你進入使用者列表前就已經在閱\讀信件了"); - } else { - // XXX in fact we should check size here... - // chkmailbox(); - m_read(); - setutmpmode(LUSERS); - } - redrawall = redraw = 1; - } - break; - - case 'N': - if (HasUserPerm(PERM_LOGINOK)) { - char tmp_nick[sizeof(cuser.nickname)]; - // XXX why do so many copy here? - // why not just use cuser.nickname? - // XXX old code forget to initialize. - // will changing to init everytime cause user - // complain? - - strlcpy(tmp_nick, currutmp->nickname, sizeof(cuser.nickname)); - - if (oldgetdata(1, 0, "新的暱稱: ", - tmp_nick, sizeof(tmp_nick), DOECHO) > 0) - { - strlcpy(cuser.nickname, tmp_nick, sizeof(cuser.nickname)); - 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 ){ - 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]; - int idle_type; - char idle_reason[sizeof(currutmp->chatid)]; - - - 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') - idle_type = genbuf[0] - '0'; - else - idle_type = 0; - - if (idle_type == 6) { - if (cuser.userlevel && getdata(b_lines - 1, 0, "發呆的理由:", idle_reason, sizeof(idle_reason), DOECHO)) { - strlcpy(currutmp->chatid, idle_reason, sizeof(currutmp->chatid)); - } else { - idle_type = 0; - } - } - currutmp->destuid = idle_type; - do { - move(b_lines - 2, 0); - clrtobot(); - prints("(鎖定螢幕)發呆原因: %s", (idle_type != 6) ? - IdleTypeTable[idle_type] : idle_reason); - 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("socket 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 && siguserid, 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; -#ifdef USE_CHICKEN_PK - case SIG_PK: - chickenpk(a); - break; -#endif // USE_CHICKEN_PK - 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(){ - char old_board[IDLEN + 1] = ""; - int canRead = 1; - - if (currutmp && currutmp->mode == EDITING) - return; - - // usually crashed as 'assert(currbid == brc_currbid)' - if (currboard[0]) { - strlcpy(old_board, currboard, IDLEN + 1); - currboard = "";// force enter_board - } - - if (enter_board(GLOBAL_NEWBIE) == 0) - canRead = 1; - - if (canRead) - Read(); - - if (canRead && old_board[0]) - enter_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("小主人使用上問題找不到小天使請到新手版(" GLOBAL_NEWBIE ")\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; - - scr_dump(&old_screen); - - TalkToAngel(); - - scr_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/mbbsd/telnet.c b/mbbsd/telnet.c deleted file mode 100644 index 332b3f3a..00000000 --- a/mbbsd/telnet.c +++ /dev/null @@ -1,338 +0,0 @@ -/* - * piaip's simplified implementation of TELNET protocol - */ -#ifdef DEBUG -#define TELOPTS -#define TELCMDS -#endif - -#include "bbs.h" - -#ifdef DETECT_CLIENT -void UpdateClientCode(unsigned char c); // see mbbsd.c -#endif - -unsigned int telnet_handler(unsigned char c) ; -void telnet_init(void); -ssize_t tty_read(unsigned char *buf, size_t max); - -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; -} - -#ifdef DBG_OUTRPT -extern unsigned char fakeEscape; -#endif // DBG_OUTRPT - -/* 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) - UpdateClientCode(IAC); - UpdateClientCode(c); - } - } -#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: -#if 0 // def 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 allow IACs as input. - return 1; - - /* we don't want to process these. or maybe in future. */ - case BREAK: /* break */ -#ifdef DBG_OUTRPT - fakeEscape = !fakeEscape; - return NOP; -#endif - - 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: -#if 0 // def 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; -#if 0 // def 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) { - UpdateClientCode(iac_buf[0]); - if(w==80 && h==24) - UpdateClientCode(1); - else if(w==80) - UpdateClientCode(2); - else if(h==24) - UpdateClientCode(3); - else - UpdateClientCode(4); - UpdateClientCode(IAC); - UpdateClientCode(SE); - } -#endif - } - break; - - default: -#ifdef DETECT_CLIENT - if(cuser.userid[0]==0) { - int i; - for(i=0;i -#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/mbbsd/topsong.c b/mbbsd/topsong.c deleted file mode 100644 index 906dadbf..00000000 --- a/mbbsd/topsong.c +++ /dev/null @@ -1,72 +0,0 @@ -/* $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; -} - - -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/mbbsd/user.c b/mbbsd/user.c deleted file mode 100644 index d235d01f..00000000 --- a/mbbsd/user.c +++ /dev/null @@ -1,1452 +0,0 @@ -/* $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, const 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, in; - unsigned int pbits = cuser.loginview; - - clear(); - move(4, 0); - for (i = 0; i < NUMVIEWFILE && loginview_file[i][0]; i++) - prints(" %c. %-20s %-15s \n", 'A' + i, - loginview_file[i][1], ((pbits >> i) & 1 ? "ˇ" : "X")); - in = i; - - clrtobot(); - while ((i = getkey("請按 [A-N] 切換設定,按 [Return] 結束:"))!='\r') - { - i = i - 'a'; - if (i >= in || 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); - if (currutmp && (currutmp->alerts & ALERT_PWD)) - currutmp->alerts &= ~ALERT_PWD; - - 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) - { - int prev = cuser.badpost--; - cuser.timeremovebadpost = now; - passwd_update(usernum, &cuser); - log_filef("log/cancelbadpost.log", LOG_CREAT, - "%s %s 刪除一篇劣文 (%d -> %d 篇)\n", - Cdate(&now), cuser.userid, prev, cuser.badpost); - } - 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 (%s滿18歲)\n", - get_num_records(genbuf, sizeof(fileheader_t)), - u->exmailbox, u->mobile, - u->year + 1900, u->month, u->day, over18 ? "已" : "未" - ); - -#ifdef ASSESS - prints(" 優 劣 文: 優:%d / 劣:%d\n", - u->goodpost, u->badpost); -#endif // ASSESS - - 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, "作者: [" BBSMNAME "警察局]\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\n" - "請到 " GLOBAL_LAW " 查詢相關法規資訊,並從主選單進入:\n" - "(P)lay【娛樂與休閒】=>(P)ay【Ptt量販店 】=> (1)ViolateLaw 繳罰單\n" - "以繳交罰單。\n", - ctime4(&now), police, crime, reason, result); - fclose(fp); - strcpy(fhdr.title, "[報告] 違法判決報告"); - strcpy(fhdr.owner, "[" BBSMNAME "警察局]"); - 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)) != NULL) - { - 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, - NO_MODMARK_FLAG , - COLORED_MODMARK, -#ifdef DBCSAWARE - DBCSAWARE_FLAG, - DBCS_NOINTRESC, -#endif - DEFBACKUP_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, - }; - - while ( !done ) { - int i = 0, ia = 0, ic = 0; /* general uflags */ - int iax = 0; /* extended flags */ - - clear(); - showtitle("個人化設定", "個人化設定"); - move(2, 0); - outs("您目前的個人化設定: "); - move(4, 0); - - /* print uflag options */ - for (i = 0; masks1[i]; i++, ia++) - { - clrtoeol(); - prints( ANSI_COLOR(1;36) "%c" ANSI_RESET - ". %-40s%s\n", - 'a' + ia, desc1[i], - (cuser.uflag & masks1[i]) ? - ANSI_COLOR(1;36) "是" ANSI_RESET : "否"); - } - ic = i; - /* print uflag2 options */ - for (i = 0; masks2[i]; i++, ia++) - { - clrtoeol(); - prints( ANSI_COLOR(1;36) "%c" ANSI_RESET - ". %-40s%s" ANSI_RESET "\n", - 'a' + ia, desc2[i], - (cuser.uflag2 & masks2[i]) ? - ANSI_COLOR(1;36) "是" ANSI_RESET : "否"); - } - /* extended stuff */ - { - char mindbuf[5]; - static const char *wm[] = - {"一般", "進階", "未來", ""}; - - prints("%c. %-40s%s\n", - '1' + iax++, - "水球模式", - wm[(cuser.uflag2 & WATER_MASK)]); - memcpy(mindbuf, &currutmp->mind, 4); - mindbuf[4] = 0; - prints("%c. %-40s%s\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 - - } - - grayout(1, b_lines-2, GRAYOUT_DARK); - move(b_lines-1, 0); clrtoeol(); - - if(dirty) - { - passwd_update(usernum, &cuser); - outs("設定已儲存。\n"); - } else { - outs("結束設定。\n"); - } - - redrawwin(); // in case we changed output pref (like DBCS) - vmsg("設定完成"); -} - - -void -uinfo_query(userec_t *u, int adminmode, int unum) -{ - userec_t x; - int i = 0, fail; - int ans; - char buf[STRLEN]; - char genbuf[200]; - int y = 0; - int perm_changed; - int mail_changed; - int money_changed; - int tokill = 0; - int changefrom = 0; - - fail = 0; - mail_changed = money_changed = perm_changed = 0; - - { - // verify unum - int xuid = getuser(u->userid, &x); - if (xuid != unum) - { - vmsg("系統錯誤: 使用者資料號碼 (unum) 不合。請至 " GLOBAL_BUGREPORT "報告。"); - return; - } - } - - 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(); - y = 1; - move(y++, 0); - outs(msg_uid); - outs(x.userid); - } - switch (ans) { - case 'c': - Customize(); - return; - - case 'm': - do { - getdata_str(y, 0, "電子信箱 [變動要重新認證]:", buf, - sizeof(x.email), DOECHO, x.email); - // TODO 這裡也要 emaildb_check -#ifdef USE_EMAILDB - if (isvalidemail(buf)) - { - int email_count = emaildb_check_email(buf, strlen(buf)); - if (email_count < 0) - vmsg("暫時不允許\ email 認證, 請稍後再試"); - else if (email_count >= EMAILDB_LIMIT) - vmsg("指定的 E-Mail 已註冊過多帳號, 請使用其他 E-Mail"); - else // valid - break; - } - continue; -#endif - } while (!isvalidemail(buf) && vmsg("認證信箱不能用使用免費信箱")); - y++; - // admins may want to use special names - if (strcmp(buf, x.email) && - (strchr(buf, '@') || adminmode)) { - - // TODO 這裡也要 emaildb_check -#ifdef USE_EMAILDB - if (emaildb_update_email(cuser.userid, strlen(cuser.userid), - buf, strlen(buf)) < 0) { - vmsg("暫時不允許\ email 認證, 請稍後再試"); - break; - } -#endif - strlcpy(x.email, buf, sizeof(x.email)); - mail_changed = 1; - delregcodefile(); - } - break; - - case '7': - violate_law(&x, unum); - return; - case '1': - move(0, 0); - outs("請逐項修改。"); - - getdata_buf(y++, 0, " 暱 稱 :", x.nickname, - sizeof(x.nickname), DOECHO); - if (adminmode) { - getdata_buf(y++, 0, "真實姓名:", - x.realname, sizeof(x.realname), DOECHO); - getdata_buf(y++, 0, "居住地址:", - x.address, sizeof(x.address), DOECHO); - } - buf[0] = 0; - if (x.mobile) - snprintf(buf, sizeof(buf), "%010d", x.mobile); - getdata_buf(y++, 0, "手機號碼:", buf, 11, LCECHO); - x.mobile = atoi(buf); - snprintf(genbuf, sizeof(genbuf), "%d", (u->sex + 1) % 8); - getdata_str(y++, 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(y, 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; - y++; - 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(y, 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); - ++y; - 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(y, 0, mybuf, genbuf + 11, 80 - 11, DOECHO); - ++y; - - 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 tmp; - if (HasUserPerm(PERM_BBSADM)) { - snprintf(genbuf, sizeof(genbuf), "%d", x.money); - if (getdata_str(y++, 0, "銀行帳戶:", buf, 10, DOECHO, genbuf)) - if ((tmp = atol(buf)) != 0) { - if (tmp != x.money) { - money_changed = 1; - changefrom = x.money; - x.money = tmp; - } - } - } - snprintf(genbuf, sizeof(genbuf), "%d", x.exmailbox); - if (getdata_str(y++, 0, "購買信箱數:", buf, 6, - DOECHO, genbuf)) - if ((tmp = atoi(buf)) != 0) - x.exmailbox = (int)tmp; - - getdata_buf(y++, 0, "認證資料:", x.justify, - sizeof(x.justify), DOECHO); - getdata_buf(y++, 0, "最近光臨機器:", - x.lasthost, sizeof(x.lasthost), DOECHO); - - snprintf(genbuf, sizeof(genbuf), "%d", x.numlogins); - if (getdata_str(y++, 0, "上線次數:", buf, 10, DOECHO, genbuf)) - if ((tmp = atoi(buf)) >= 0) - x.numlogins = tmp; - snprintf(genbuf, sizeof(genbuf), "%d", u->numposts); - if (getdata_str(y++, 0, "文章數目:", buf, 10, DOECHO, genbuf)) - if ((tmp = atoi(buf)) >= 0) - x.numposts = tmp; - snprintf(genbuf, sizeof(genbuf), "%d", u->goodpost); - if (getdata_str(y++, 0, "優良文章數:", buf, 10, DOECHO, genbuf)) - if ((tmp = atoi(buf)) >= 0) - x.goodpost = tmp; - snprintf(genbuf, sizeof(genbuf), "%d", u->badpost); - if (getdata_str(y++, 0, "惡劣文章數:", buf, 10, DOECHO, genbuf)) - if ((tmp = atoi(buf)) >= 0) - x.badpost = tmp; - snprintf(genbuf, sizeof(genbuf), "%d", u->vl_count); - if (getdata_str(y++, 0, "違法記錄:", buf, 10, DOECHO, genbuf)) - if ((tmp = atoi(buf)) >= 0) - x.vl_count = tmp; - - snprintf(genbuf, sizeof(genbuf), - "%d/%d/%d", u->five_win, u->five_lose, u->five_tie); - if (getdata_str(y++, 0, "五子棋戰績 勝/敗/和:", buf, 16, DOECHO, - genbuf)) - while (1) { - char *p; - 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(y++, 0, "象棋戰績 勝/敗/和:", buf, 16, DOECHO, - genbuf)) - while (1) { - char *p; - 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(y++, 0, "住在 1)台灣 2)其他:", buf, 2, DOECHO, x.uflag2 & FOREIGN ? "2" : "1")) - if ((tmp = atoi(buf)) > 0){ - if (tmp == 2){ - x.uflag2 |= FOREIGN; - } - else - x.uflag2 &= ~FOREIGN; - } - if (x.uflag2 & FOREIGN) - if (getdata_str(y++, 0, "永久居留權 1)是 2)否:", buf, 2, DOECHO, x.uflag2 & LIVERIGHT ? "1" : "2")){ - if ((tmp = atoi(buf)) > 0){ - if (tmp == 1){ - x.uflag2 |= LIVERIGHT; - x.userlevel |= (PERM_LOGINOK | PERM_POST); - } - else{ - x.uflag2 &= ~LIVERIGHT; - x.userlevel &= ~(PERM_LOGINOK | PERM_POST); - } - } - } -#endif - } - break; - - case '2': - y = 19; - if (!adminmode) { - if (!getdata(y++, 0, "請輸入原密碼:", buf, PASSLEN, NOECHO) || - !checkpasswd(u->passwd, buf)) { - outs("\n\n您輸入的密碼不正確\n"); - fail++; - break; - } - } else { - FILE *fp; - char witness[3][32], title[100]; - int uid; - 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(GLOBAL_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); - } - y = 20; - } - - if (!getdata(y++, 0, "請設定新密碼:", buf, PASSLEN, NOECHO)) { - outs("\n\n密碼設定取消, 繼續使用舊密碼\n"); - fail++; - break; - } - strlcpy(genbuf, buf, PASSLEN); - - move(y+1, 0); - outs("請注意設定密碼只有前八個字元有效,超過的將自動忽略。"); - - getdata(y++, 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': - { - int tmp = setperms(x.userlevel, str_permid); - if (tmp == x.userlevel) - fail++; - else { - perm_changed = 1; - changefrom = x.userlevel; - x.userlevel = tmp; - } - } - break; - - case '4': - tokill = 1; - 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': - chicken_toggle_death(x.userid); - break; - default: - return; - } - - if (fail) { - pressanykey(); - return; - } - if (getans(msg_sure_ny) == 'y') { - if (perm_changed) { - post_change_perm(changefrom, x.userlevel, cuser.userid, x.userid); -#ifdef PLAY_ANGEL - if (x.userlevel & ~changefrom & 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 && !adminmode) { - // wait registration. - x.userlevel &= ~(PERM_LOGINOK | PERM_POST); - } - memcpy(u, &x, sizeof(x)); - if (tokill) { - kill_user(unum, x.userid); - return; - } else - log_usies("SetUser", x.userid); - if (money_changed) { - char title[TTLEN+1]; - char msg[200]; - char reason[50]; - 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, changefrom, x.money, - cuser.userid, reason); - snprintf(title, sizeof(title), - "[公安報告] 站長%s修改%s錢報告", cuser.userid, - x.userid); - post_msg(GLOBAL_SECURITY, title, msg, "[系統安全局]"); - setumoney(unum, x.money); - } - passwd_update(unum, &x); - if(perm_changed) - sendalert(x.userid, ALERT_PWD_PERM); // force to reload perm - - // resolve_over18 only works for cuser - if (!adminmode) - resolve_over18(); - } -} - -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(" " ANSI_COLOR(1;31) "此人違規 尚未繳交罰單" ANSI_RESET); - 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, SHOWFILE_ALLOW_COLOR); - return; - } - } -#endif /* defined(CHESSCOUNTRY) */ - - sethomefile(genbuf, user->userid, fn_plans); - if (!show_file(genbuf, 7, MAX_QUERYLINES, SHOWFILE_ALLOW_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 -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; -} - -/* 列出所有註冊使用者 */ -struct ListAllUsetCtx { - int usercounter; - int totalusers; - unsigned short u_list_special; - int y; -}; - -static int -u_list_CB(void *data, int num, userec_t * uentp) -{ - char permstr[8], *ptr; - register int level; - struct ListAllUsetCtx *ctx = (struct ListAllUsetCtx*) data; - (void)num; - - if (uentp == NULL) { - move(2, 0); - clrtoeol(); - prints(ANSI_COLOR(7) " 使用者代號 %-25s 上站 文章 %s " - "最近光臨日期 " ANSI_COLOR(0) "\n", - "綽號暱稱", - HasUserPerm(PERM_SEEULEVELS) ? "等級" : ""); - ctx->y = 3; - return 0; - } - if (bad_user_id(uentp->userid)) - return 0; - - if ((uentp->userlevel & ~(ctx->u_list_special)) == 0) - return 0; - - if (ctx->y == b_lines) { - int ch; - 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, - ctx->usercounter, ctx->totalusers, ctx->usercounter * 100 / ctx->totalusers); - ch = igetch(); - if (ch == 'q' || ch == 'Q') - return -1; - ctx->y = 3; - } - if (ctx->y == 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); - ctx->usercounter++; - ctx->y++; - return 0; -} - -int -u_list(void) -{ - char genbuf[3]; - struct ListAllUsetCtx data, *ctx = &data; - - setutmpmode(LAUSERS); - ctx->u_list_special = ctx->usercounter = 0; - ctx->totalusers = SHM->number; - if (HasUserPerm(PERM_SEEULEVELS)) { - getdata(b_lines - 1, 0, "觀看 [1]特殊等級 (2)全部?", - genbuf, 3, DOECHO); - if (genbuf[0] != '2') - ctx->u_list_special = PERM_BASIC | PERM_CHAT | PERM_PAGE | PERM_POST | PERM_LOGINOK | PERM_BM; - } - u_list_CB(ctx, 0, NULL); - passwd_apply(ctx, u_list_CB); - move(b_lines, 0); - clrtoeol(); - prints(ANSI_COLOR(34;46) " 已顯示 %d/%d 的使用者(系統容量無上限) " - ANSI_COLOR(31;47) " (請按任意鍵繼續) " ANSI_RESET, ctx->usercounter, ctx->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(); - stand_title("設定自動偵測雙位元字集 (全型中文)"); - move(2, 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 */ - peek_input(0.1, Ctrl('C')); - drop_input(); - - while (1) - { - int ch = 0; - - move(14, 0); - outs("這是偵測區,您的游標會出現在" - ANSI_COLOR(7) "這裡" ANSI_RESET); - move(14, 15*2); - ch = igetch(); - if(ch != KEY_LEFT && ch != KEY_RIGHT && - ch != Ctrl('H') && ch != '\177') - { - move(16, 0); - bell(); - 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. - */ - refresh(); - if(wait_input(0.1, 0)) - // if(igetch() == ch) - // if (num_in_buf() > 0) - { - /* evil dbcs aware client */ - outs("偵測到您的連線程式會自行處理游標移動。\n" - // "若日後因此造成瀏覽上的問題本站恕不處理。\n\n" - "已設定為「讓您的連線程式處理游標移動」\n"); - ret = 1; - } else { - /* good non-dbcs aware client */ - outs("您的連線程式似乎不會多送按鍵," - "這樣 BBS 可以更精準的控制畫面。\n" - "已設定為「讓 BBS 伺服器直接處理游標移動」\n"); - ret = 0; - } - outs( "\n若想改變設定請至 個人設定區 → 個人化設定 → \n" - " 調整「自動偵測雙位元字集(如全型中文)」之設定"); - while(num_in_buf()) - igetch(); - break; - } - } - drop_input(); - pressanykey(); - return ret; -} -#endif - -/* vim:sw=4 - */ diff --git a/mbbsd/var.c b/mbbsd/var.c deleted file mode 100644 index ca50824c..00000000 --- a/mbbsd/var.c +++ /dev/null @@ -1,628 +0,0 @@ -/* $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", "看板人氣排行榜"}, - {NULL, NULL} -}; - -/* message */ -char * const msg_seperator = MSG_SEPERATOR; - -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; - -/* MAX_MODES is defined in common.h */ - -char * const ModeTypeTable[] = { - "發呆", /* IDLE */ - "主選單", /* MMENU */ - "系統維護", /* ADMIN */ - "郵件選單", /* MAIL */ - "交談選單", /* TMENU */ - "使用者選單", /* UMENU */ - "XYZ 選單", /* XMENU */ - "分類看板", /* CLASS */ - "Play選單", /* PMENU */ - "編特別名單", /* NMENU */ - BBSMNAME2 "量販店", /* 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 */ - BBSMNAME2 "查榜系統", /* JCEE */ - "重編文章", /* REEDIT */ - "部落格", /* BLOGGING */ - "看棋", /* CHESSWATCHING */ - "下圍棋", /* UMODE_GO */ - "[系統錯誤]", /* DEBUGSLEEPING */ - "連六棋", /* UMODE_CONN6 */ - "黑白棋", /* REVERSI */ - "BBS-Lua", /* UMODE_BBSLUA */ - "播放動畫", /* UMODE_ASCIIMOVIE */ - "", - "", - "", // 90 - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", // 100 - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", // 110 - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", // 120 - "", - "", - "", - "", - "", - "", - "", - "" -}; - -/* 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 = 0; /* 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; - -/* 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/mbbsd/vice.c b/mbbsd/vice.c deleted file mode 100644 index 14e4e8f4..00000000 --- a/mbbsd/vice.c +++ /dev/null @@ -1,153 +0,0 @@ -/* $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/mbbsd/vote.c b/mbbsd/vote.c deleted file mode 100644 index 7562a7b8..00000000 --- a/mbbsd/vote.c +++ /dev/null @@ -1,1088 +0,0 @@ -/* $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; - -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, FN_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"); - 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); - 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|BRD_HIDE))) { // from ptt2 local modification - 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"); - - 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); - 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-1control); - 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-1bvote != 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 count, tickets; - short curr_page, item_num, max_page; - char inbuf[80], choices[ITEM_PER_PAGE+1], vote[4], *chosen; - time4_t closetime; - - // pos = boaord id, must be at least one int. - // fd should be int. - int pos = 0, i = 0; - int fd = 0; - - 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("你不夠資深喔!"); - 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 下一頁, < 上一頁\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][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-1bvote == 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/mbbsd/voteboard.c b/mbbsd/voteboard.c deleted file mode 100644 index 81018da2..00000000 --- a/mbbsd/voteboard.c +++ /dev/null @@ -1,400 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -#define VOTEBOARD "NewBoard" - -// user -int CheckVoteRestriction(int bid) -{ - if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) - return 1; - - // check first-login - if (cuser.firstlogin > (now - (time4_t)bcache[bid - 1].vote_limit_regtime * 2592000)) - return 0; - if (cuser.numlogins / 10 < (unsigned int)bcache[bid - 1].vote_limit_logins) - return 0; - if (cuser.numposts / 10 < (unsigned int)bcache[bid - 1].vote_limit_posts) - return 0; - if (cuser.badpost > (255 - (unsigned int)bcache[bid - 1].vote_limit_badpost)) - return 0; - - return 1; -} - -// article -int CheckVoteRestrictionFile(const fileheader_t * fhdr) -{ - if ((currmode & MODE_BOARD) || HasUserPerm(PERM_SYSOP)) - return 1; - - // check first-login - if (cuser.firstlogin > (now - (time4_t)fhdr->multi.vote_limits.regtime * 2592000)) - return 0; - if (cuser.numlogins / 10 < (unsigned int)fhdr->multi.vote_limits.logins) - return 0; - if (cuser.numposts / 10 < (unsigned int)fhdr->multi.vote_limits.posts) - return 0; - if (cuser.badpost > (255 - (unsigned int)fhdr->multi.vote_limits.badpost)) - return 0; - - return 1; -} - -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 (!CheckVoteRestrictionFile(fhdr)) - { - move(5, 10); // why 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 (!CheckVoteRestriction(currbid)) - { - move(5, 10); // why move (5, 10)? - vmsg("你不夠資深喔! (可按 i 查看限制)"); - return FULLUPDATE; - } - move(0, 0); - clrtobot(); - outs("您正在使用" BBSNAME "的連署系統\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_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) { - char *suser = sysoplist[i].userid; - clear(); - showplans(suser); - do_send(suser, 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/src/libbbs/Makefile b/src/libbbs/Makefile deleted file mode 100644 index b6eef8e7..00000000 --- a/src/libbbs/Makefile +++ /dev/null @@ -1,24 +0,0 @@ - -SRCROOT= ../.. -.include "$(SRCROOT)/pttbbs.mk" - -CFLAGS+= -I$(SRCROOT)/include - -OBJS= log.o string.o money.o -TARGET= libbbs.a - - -.SUFFIXES: .c .o -.c.o: - $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c - -all: $(TARGET) - -install: - -$(TARGET): $(OBJS) - $(AR) cru $@ $(OBJS) - ranlib $@ - -clean: - rm -f $(OBJS) $(TARGET) diff --git a/src/libbbs/log.c b/src/libbbs/log.c deleted file mode 100644 index e69de29b..00000000 diff --git a/src/libbbs/money.c b/src/libbbs/money.c deleted file mode 100644 index a6d54127..00000000 --- a/src/libbbs/money.c +++ /dev/null @@ -1,37 +0,0 @@ -#include - -#include - -/* 計算贈與稅 */ -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; -} - -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]; -} - diff --git a/src/libbbs/string.c b/src/libbbs/string.c deleted file mode 100644 index 8b137891..00000000 --- a/src/libbbs/string.c +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/libbbsutil/Makefile b/src/libbbsutil/Makefile deleted file mode 100644 index 99cd6143..00000000 --- a/src/libbbsutil/Makefile +++ /dev/null @@ -1,24 +0,0 @@ - -SRCROOT= ../.. -.include "$(SRCROOT)/pttbbs.mk" - -CFLAGS+= -I$(SRCROOT)/include - -OBJS= file.o lock.o log.o net.o sort.o string.o time.o -TARGET= libbbsutil.a - - -.SUFFIXES: .c .o -.c.o: - $(CCACHE) $(DIETCC) $(CC) $(CFLAGS) -c $*.c - -all: $(TARGET) - -install: - -$(TARGET): $(OBJS) - $(AR) cru $@ $(OBJS) - ranlib $@ - -clean: - rm -f $(OBJS) $(TARGET) diff --git a/src/libbbsutil/file.c b/src/libbbsutil/file.c deleted file mode 100644 index 21313283..00000000 --- a/src/libbbsutil/file.c +++ /dev/null @@ -1,297 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libbbsutil.h" - - -/* ----------------------------------------------------- */ -/* 檔案檢查函數:檔案、目錄、屬於 */ -/* ----------------------------------------------------- */ - -/** - * 傳回 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 的 ctime - * @param fname - */ -time4_t -dashc(const char *fname) -{ - struct stat st; - - if (!stat(fname, &st)) - return st.st_ctime; - 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 -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 - -int copy_file_to_dir(const char *src, const char *dst) -{ - char buf[PATH_MAX]; - char *slash; - if ((slash = rindex(src, '/')) == NULL) - snprintf(buf, sizeof(buf), "%s/%s", dst, src); - else - snprintf(buf, sizeof(buf), "%s/%s", dst, slash); - return copy_file_to_file(src, buf); -} - -int copy_dir_to_dir(const char *src, const char *dst) -{ - DIR *dir; - struct dirent *entry; - struct stat st; - char buf[PATH_MAX], buf2[PATH_MAX]; - - 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, sizeof(buf), "%s/%s", src, entry->d_name); - snprintf(buf2, sizeof(buf2), "%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 -Rename(const char *src, const char *dst) -{ - if (rename(src, dst) == 0) - return 0; - if (!strchr(src, ';') && !strchr(dst, ';')) - { - pid_t pid = fork(); - if (pid == 0) - execl("/bin/mv", "mv", "-f", src, dst, (char *)NULL); - else if (pid > 0) - { - int status = -1; - waitpid(pid, &status, 0); - return WEXITSTATUS(status) == 0 ? 0 : -1; - } - else - return -1; - } - 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;} - // flock(dst, LOCK_SH); - - if(off > 0) - lseek(fi, (off_t)off, SEEK_SET); - - while((bytes=read(fi, buf, sizeof(buf)))>0) - { - write(fo, buf, bytes); - } - // flock(dst, LOCK_UN); - close(fo); - close(fi); - return 0; -} - -/** - * @param src file - * @param dst file - * @return 0 if success - */ -int -Link(const char *src, const char *dst) -{ - if (symlink(src, dst) == 0) - return 0; - - return Copy(src, dst); -} - diff --git a/src/libbbsutil/lock.c b/src/libbbsutil/lock.c deleted file mode 100644 index 4b28bab1..00000000 --- a/src/libbbsutil/lock.c +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include - -/** - * lock fd - * @param mode F_WRLCK, F_UNLCK - */ -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); -} - diff --git a/src/libbbsutil/log.c b/src/libbbsutil/log.c deleted file mode 100644 index 9fe3d514..00000000 --- a/src/libbbsutil/log.c +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include -#include -#include - -#include "libbbsutil.h" - -int -log_filef(const char *fn, int log_flag, const char *fmt,...) -{ - char msg[256]; - - va_list ap; - va_start(ap, fmt); - vsnprintf(msg, sizeof(msg), fmt, ap); - va_end(ap); - - return log_file(fn, log_flag, msg); -} - -int -log_file(const char *fn, int log_flag, const char *msg) -{ - int fd; - int flag = O_APPEND | O_WRONLY; - int mode = 0664; - - if (log_flag & LOG_CREAT) - flag |= O_CREAT; - - fd = open(fn, flag, mode); - if( fd < 0 ) - return -1; - - if( write(fd, msg, strlen(msg)) < 0 ){ - close(fd); - return -1; - } - close(fd); - return 0; -} - diff --git a/src/libbbsutil/net.c b/src/libbbsutil/net.c deleted file mode 100644 index 60208a65..00000000 --- a/src/libbbsutil/net.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libbbsutil.h" - -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; -} - -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; -} diff --git a/src/libbbsutil/sort.c b/src/libbbsutil/sort.c deleted file mode 100644 index edecc0a8..00000000 --- a/src/libbbsutil/sort.c +++ /dev/null @@ -1,10 +0,0 @@ - -int cmp_int(const void *a, const void *b) -{ - return *(int*)a - *(int*)b; -} - -int cmp_int_desc(const void * a, const void * b) -{ - return cmp_int(b, a); -} diff --git a/src/libbbsutil/string.c b/src/libbbsutil/string.c deleted file mode 100644 index b2e7612e..00000000 --- a/src/libbbsutil/string.c +++ /dev/null @@ -1,345 +0,0 @@ -#include -#include -#include -#include "fnv_hash.h" - -#include "ansi.h" -#include "libbbsutil.h" - -#define CHAR_LOWER(c) ((c >= 'A' && c <= 'Z') ? c|32 : c) -/* ----------------------------------------------------- */ -/* 字串轉換檢查函數 */ -/* ----------------------------------------------------- */ -/** - * 將字串 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++; - } -} - -int -strip_blank(char *cbuf, char *buf) -{ - for (; *buf; buf++) - if (*buf != ' ') - *cbuf++ = *buf; - *cbuf = 0; - return 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, -}; -/** - * 根據 mode 來 strip 字串 src,並把結果存到 dst - * @param dst - * @param src - * @param mode enum {STRIP_ALL = 0, ONLY_COLOR, NO_RELOAD}; - * STRIP_ALL: 全部吃掉 - * ONLY_COLOR: 吃掉所有跟顏色無關的 (ESC[*m) - * NO_RELOAD: 不 strip (?) - * @return strip 後的長度 - */ -int -strip_ansi(char *dst, const char *src, enum STRIP_FLAG mode) -{ - register int count = 0; -#define isEscapeParam(X) (EscapeFlag[(int)(X)] & 1) -#define isEscapeCommand(X) (EscapeFlag[(int)(X)] & 2) - - for(; *src; ++src) - if( *src != ESC_CHR ){ - if( dst ) - *dst++ = *src; - ++count; - }else{ - const char* p = src + 1; - if( *p != '[' ){ - ++src; - if(*src=='\0') break; - continue; - } - while(isEscapeParam(*++p)); - if( (mode == NO_RELOAD && isEscapeCommand(*p)) || - (mode == ONLY_COLOR && *p == 'm' )){ - register int len = p - src + 1; - if( dst ){ - strncpy(dst, src, len); - dst += len; - } - count += len; - } - src = p; - if(*src=='\0') break; - } - if( dst ) - *dst = 0; - return count; -} - -int -strlen_noansi(const char *s) -{ - // XXX this is almost identical to - // strip_ansi(NULL, s, STRIP_ALL) - register int count = 0, mode = 0; - - if (!s || !*s) - return 0; - - for (; *s; ++s) - { - // 0 - no ansi, 1 - [, 2 - param+cmd - switch (mode) - { - case 0: - if (*s == ESC_CHR) - mode = 1; - else - count ++; - break; - - case 1: - if (*s == '[') - mode = 2; - else - mode = 0; // unknown command - break; - - case 2: - if (isEscapeParam(*s)) - continue; - else if (isEscapeCommand(*s)) - mode = 0; - else - mode = 0; - break; - } - } - return count; -} - -void -strip_nonebig5(unsigned char *str, int maxlen) -{ - int i; - int len=0; - for(i=0;i= 0x80) { - // DBCS lead. - isInDBCS = 1; - } else { - // normal character. - } - } - - if(len) *len = l; - return (oldl != l) ? 1 : 0; -} - -/* ----------------------------------------------------- */ -/* 字串檢查函數:英文、數字、檔名、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 (!isalnum(*p3) && !strchr("@[]-._", *p3)) /* 只允許 alnum 或這些符號 */ - return 1; - p1 = p2 + (*p2 ? 1 : 0); - } - return 0; -} - -/* - * return 1 if /^[0-9]+$/ - * 0 else, 含空字串 - */ -int is_number(const char *p) -{ - if (*p == '\0') - return 0; - - for(; *p; p++) { - if (*p < '0' || '9' < *p) - return 0; - } - return 1; -} - -unsigned -StringHash(const char *s) -{ - return fnv1a_32_strcase(s, FNV1_32_INIT); -} - -/* 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= 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; -} - diff --git a/src/libbbsutil/time.c b/src/libbbsutil/time.c deleted file mode 100644 index 2e0dbdd1..00000000 --- a/src/libbbsutil/time.c +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include "libbbsutil.h" - -static char cdate_buffer[32]; - -/** - * 閏年 - */ -int is_leap_year(int year) -{ - return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); -} - -/** - * 給日期求星座 - * - * @return 1..12 - */ -int getHoroscope(int m, int d) -{ - if (m > 12 || m < 1) - return 1; - - // 摩羯 水瓶 雙魚 牡羊 金牛 雙子 巨蟹 獅子 處女 天秤 天蠍 射手 - 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; -} - -/** - * 23+1 bytes, "12/31/2007 00:00:00 Mon\0" - */ -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; -} - -/** - * 19+1 bytes, "12/31/2007 00:00:00\0" - */ -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; -} - -/** - * 10+1 bytes, "12/31/2007\0" - */ -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; -} - -#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 - -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/staticweb/INSTALL b/staticweb/INSTALL deleted file mode 100644 index 2eadd296..00000000 --- a/staticweb/INSTALL +++ /dev/null @@ -1,43 +0,0 @@ -這篇文章介紹如何使用 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權限, 如: - - Options ExecCGI - - # 下面兩行是使用 mod_perl 的. - AddHandler perl-script .pl - PerlHandler Apache::Registry - diff --git a/staticweb/article.html b/staticweb/article.html deleted file mode 100644 index 8c1a8260..00000000 --- a/staticweb/article.html +++ /dev/null @@ -1,18 +0,0 @@ -[% INCLUDE header.html %] - -
-看板: [% brdname %]
-回上頁
-
-
-
-[% content %]
-
-
-
-回上頁
-批踢踢實業坊 -
- - - diff --git a/staticweb/b2g.pm b/staticweb/b2g.pm deleted file mode 100644 index 7b3b8ddf..00000000 --- a/staticweb/b2g.pm +++ /dev/null @@ -1,13990 +0,0 @@ -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/staticweb/banner.html b/staticweb/banner.html deleted file mode 100644 index 2c59b6bb..00000000 --- a/staticweb/banner.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/staticweb/dir.html b/staticweb/dir.html deleted file mode 100644 index 071cac22..00000000 --- a/staticweb/dir.html +++ /dev/null @@ -1,43 +0,0 @@ -[% INCLUDE header.html %] - - - - - - - -
-[% INCLUDE banner.html %] -
-批踢踢實業坊 | -網頁版精華區首頁 | -[% brdname %]看板首頁 | -[% brdname %]精華區首頁 -
-看板名稱: [% brdname %] -
-
-[% IF !isroot %] -返回上一層
-[% END %] - -[% FOREACH x=dat %] - - -[% x.title %]
-[% END %] -
-[% IF !gb %] -
-
-在這個精華區內翻弄 - - -
-[% END %] -
-製作時間: [% buildtime %]
-批踢踢實業坊 -
- - diff --git a/staticweb/header.html b/staticweb/header.html deleted file mode 100644 index 112efd70..00000000 --- a/staticweb/header.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - 批踢踢實業坊[% IF exttitle %] - [% exttitle %][% END %] - - - - - - diff --git a/staticweb/index.html b/staticweb/index.html deleted file mode 100644 index c0c87174..00000000 --- a/staticweb/index.html +++ /dev/null @@ -1,47 +0,0 @@ -[% INCLUDE header.html %] - - - - - -
-[% INCLUDE banner.html %] -
-批踢踢實業坊 » 批踢踢實業坊之精華區 -[% FOREACH x=class %] -»[% x.title %] -[% END %] -
-
- - [% IF !isroot %] - - - - - - [% END %] - [% FOREACH x=dat %] - - - - - - [% END %] -
- - - - 返回上一層 -
- - [% x.1 %] - [% IF x.0 == -1 %][% ELSE %][% END %] - [% x.2 %] -
-
-
-批踢踢實業坊 -
- - diff --git a/staticweb/index.pl b/staticweb/index.pl deleted file mode 100755 index b135655d..00000000 --- a/staticweb/index.pl +++ /dev/null @@ -1,99 +0,0 @@ -#!/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/staticweb/man.pl b/staticweb/man.pl deleted file mode 100755 index f7f13804..00000000 --- a/staticweb/man.pl +++ /dev/null @@ -1,145 +0,0 @@ -#!/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\-\.\:\/\,@\?=~]+)|$1|gs; - $th{content} =~ s|(ftp://[\w\-\.\:\/\,@~]+)|$1|gs; - $th{content} =~ - s|ptt\.cc|ptt.cc|gs; - $th{content} =~ - s|ptt\.twbbs\.org|ptt.twbbs.org|gs; - $th{content} =~ - s|批踢踢兔|批踢踢兔|gs; - $th{content} =~ - s|發信站: 批踢踢實業坊|發信站: 批踢踢實業坊|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/staticweb/manbuilder.pl b/staticweb/manbuilder.pl deleted file mode 100755 index 806d9a1c..00000000 --- a/staticweb/manbuilder.pl +++ /dev/null @@ -1,105 +0,0 @@ -#!/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/staticweb/search.html b/staticweb/search.html deleted file mode 100644 index d19fdc1d..00000000 --- a/staticweb/search.html +++ /dev/null @@ -1,36 +0,0 @@ -[% INCLUDE header.html %] - - - - - - -
-[% INCLUDE banner.html %] -
-網頁版精華區首頁 -[% brdname %]精華區首頁 -批踢踢部落格 -
-在看板 [% brdname %] 內搜尋 [% key %] (共費時 [% elapsed %] 秒) -
-
- -
    -[% FOREACH x=search %] -
  • [% x.title %] (score: [% x.score %])
  • -[% END %] -
- -
-
-
-在這個精華區內翻弄 - - -
-
-批踢踢實業坊 (PttWeb) -
- - diff --git a/staticweb/styles.css b/staticweb/styles.css deleted file mode 100644 index 87382c7b..00000000 --- a/staticweb/styles.css +++ /dev/null @@ -1,20 +0,0 @@ -#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/web/blog/INSTALL b/web/blog/INSTALL new file mode 100644 index 00000000..1f216292 --- /dev/null +++ b/web/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 + 在中間, 加上這兩行: + 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 * + + ServerName blog.ptt2.cc + DocumentRoot /home/bbs/blog/web + + Options ExecCGI + + + +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/web/blog/blog.pl b/web/blog/blog.pl new file mode 100755 index 00000000..5362f4b5 --- /dev/null +++ b/web/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 = ("\n". + "\n". + "\n"); + $c .= ("\n") + foreach( ['Sunday', 'Sun'], ['Monday', 'Mon'], + ['Tuesday', 'Tue'], ['Wednesday', 'Wed'], + ['Thursday', 'Thu'], ['Friday', 'Fri'], + ['Saturday', 'Sat'] ); + + $week = Day_of_Week($y, $m, 1); + $c .= "\n\n"; + + if( $week == 7 ){ + $week = 0; + } + else{ + $c .= ("\n") + foreach( 1..$week ); + } + foreach( 1..31 ){ + last if( !check_date($y, $m, $_) ); + $c .= "\n" if( $newtr ); + $c .= "\n"; + if( ++$week == 7 ){ + $c .= "\n\n"; + $week = 0; + $newtr = 1; + } + else{ + $newtr = 0; + } + } + + $c .= "\n" if( !$newtr ); + $c .= "
$emonth[$m] $y
[0]\" align=\"center\">". + "$_->[1]
". + " 
"; + + $t = packdate($y, $m, $_); + if( !$article{"$t.title"} ){ + $c .= "$_"; + } + else{ + my $link = $attr{"$fn.loadCalendar"}; + $link =~ s/\[\% key \%\]/$t/g; + $c .= "$_"; + } + + $c .= "
\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 => '批踢踢部落格 ', + 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 "
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 "$ostr";
+}
+
+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/
\n/gs; + } + elsif( /^strict$/i ){ + $c =~ s/\/>/gs; + $c =~ s/\"/"/gs; + $c =~ s/\'/'/gs; +# $c =~ s/ / /gs; + } + elsif( /^ubb$/i ){ + $c =~ s|\[url\](.*?)\[/url\]|$1|gsi; + $c =~ s|\[url=(.*?)\](.*?)\[/url\]|$2|gsi; + $c =~ s|\[email\](.*?)\[/email\]|$1|gsi; + $c =~ s|\[b\](.*?)\[/b\]|$1|gsi; + $c =~ s|\[i\](.*?)\[/i\]|$1|gsi; + $c =~ s|\[img\](.*?)\[/img\]|(null)|gsi; + } + elsif( /^wiki$/i ){ + my $t; + $c =~ s|\[(http://\S+) (.*?)\]| \[$2\] |gi; + $c =~ s|([^\>\"])(http://\S+\.(:?jpg\|gif\|png\|bmp))|$1$2|gsi; + $c =~ s|([^\>\"])(http://\S+)|$1$2|gsi; + $c =~ s|\(\((.*?)\)\)|utf8dump($1, $th{wikibase})|gsie; + $c =~ s|^\-{4,}$|
|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/web/blog/builddb.pl b/web/blog/builddb.pl new file mode 100755 index 00000000..9c805d90 --- /dev/null +++ b/web/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/$outdir/$fn"; + + if( $c =~ m|(.*?)\n\s*\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/web/blog/index.pl b/web/blog/index.pl new file mode 100755 index 00000000..b30ed178 --- /dev/null +++ b/web/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/web/static/INSTALL b/web/static/INSTALL new file mode 100644 index 00000000..2eadd296 --- /dev/null +++ b/web/static/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權限, 如: + + Options ExecCGI + + # 下面兩行是使用 mod_perl 的. + AddHandler perl-script .pl + PerlHandler Apache::Registry + diff --git a/web/static/article.html b/web/static/article.html new file mode 100644 index 00000000..8c1a8260 --- /dev/null +++ b/web/static/article.html @@ -0,0 +1,18 @@ +[% INCLUDE header.html %] + +
+看板: [% brdname %]
+回上頁
+
+
+
+[% content %]
+
+
+
+回上頁
+批踢踢實業坊 +
+ + + diff --git a/web/static/b2g.pm b/web/static/b2g.pm new file mode 100644 index 00000000..7b3b8ddf --- /dev/null +++ b/web/static/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/web/static/banner.html b/web/static/banner.html new file mode 100644 index 00000000..2c59b6bb --- /dev/null +++ b/web/static/banner.html @@ -0,0 +1,10 @@ + diff --git a/web/static/dir.html b/web/static/dir.html new file mode 100644 index 00000000..071cac22 --- /dev/null +++ b/web/static/dir.html @@ -0,0 +1,43 @@ +[% INCLUDE header.html %] + + + + + + + +
+[% INCLUDE banner.html %] +
+批踢踢實業坊 | +網頁版精華區首頁 | +[% brdname %]看板首頁 | +[% brdname %]精華區首頁 +
+看板名稱: [% brdname %] +
+
+[% IF !isroot %] +返回上一層
+[% END %] + +[% FOREACH x=dat %] + + +[% x.title %]
+[% END %] +
+[% IF !gb %] +
+
+在這個精華區內翻弄 + + +
+[% END %] +
+製作時間: [% buildtime %]
+批踢踢實業坊 +
+ + diff --git a/web/static/header.html b/web/static/header.html new file mode 100644 index 00000000..112efd70 --- /dev/null +++ b/web/static/header.html @@ -0,0 +1,15 @@ + + + + + + 批踢踢實業坊[% IF exttitle %] - [% exttitle %][% END %] + + + + + + diff --git a/web/static/index.html b/web/static/index.html new file mode 100644 index 00000000..c0c87174 --- /dev/null +++ b/web/static/index.html @@ -0,0 +1,47 @@ +[% INCLUDE header.html %] + + + + + +
+[% INCLUDE banner.html %] +
+批踢踢實業坊 » 批踢踢實業坊之精華區 +[% FOREACH x=class %] +»[% x.title %] +[% END %] +
+
+ + [% IF !isroot %] + + + + + + [% END %] + [% FOREACH x=dat %] + + + + + + [% END %] +
+ + + + 返回上一層 +
+ + [% x.1 %] + [% IF x.0 == -1 %][% ELSE %][% END %] + [% x.2 %] +
+
+
+批踢踢實業坊 +
+ + diff --git a/web/static/index.pl b/web/static/index.pl new file mode 100755 index 00000000..b135655d --- /dev/null +++ b/web/static/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/web/static/man.pl b/web/static/man.pl new file mode 100755 index 00000000..f7f13804 --- /dev/null +++ b/web/static/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\-\.\:\/\,@\?=~]+)|$1|gs; + $th{content} =~ s|(ftp://[\w\-\.\:\/\,@~]+)|$1|gs; + $th{content} =~ + s|ptt\.cc|ptt.cc|gs; + $th{content} =~ + s|ptt\.twbbs\.org|ptt.twbbs.org|gs; + $th{content} =~ + s|批踢踢兔|批踢踢兔|gs; + $th{content} =~ + s|發信站: 批踢踢實業坊|發信站: 批踢踢實業坊|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/web/static/manbuilder.pl b/web/static/manbuilder.pl new file mode 100755 index 00000000..806d9a1c --- /dev/null +++ b/web/static/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/web/static/search.html b/web/static/search.html new file mode 100644 index 00000000..d19fdc1d --- /dev/null +++ b/web/static/search.html @@ -0,0 +1,36 @@ +[% INCLUDE header.html %] + + + + + + +
+[% INCLUDE banner.html %] +
+網頁版精華區首頁 +[% brdname %]精華區首頁 +批踢踢部落格 +
+在看板 [% brdname %] 內搜尋 [% key %] (共費時 [% elapsed %] 秒) +
+
+ +
    +[% FOREACH x=search %] +
  • [% x.title %] (score: [% x.score %])
  • +[% END %] +
+ +
+
+
+在這個精華區內翻弄 + + +
+
+批踢踢實業坊 (PttWeb) +
+ + diff --git a/web/static/styles.css b/web/static/styles.css new file mode 100644 index 00000000..87382c7b --- /dev/null +++ b/web/static/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 -- cgit v1.2.3