From 829a2d67b2b31ce3887c43e51b0bfb4d0f62760b Mon Sep 17 00:00:00 2001 From: Deon George Date: Sun, 7 Apr 2019 15:36:28 +1000 Subject: [PATCH] Initial commit --- Dockerfile | 57 ++++++++++++++++++ golded.sh | 3 + init | 38 ++++++++++++ logrotate.d/mbse | 7 +++ mbse.cron | 1 + python-mbse/mbse/__init__.py | 0 python-mbse/mbse/cmd/__init__.py | 0 python-mbse/mbse/cmd/pwfix.py | 79 ++++++++++++++++++++++++ python-mbse/mbse/fmt/__init__.py | 0 python-mbse/mbse/fmt/users_data.py | 96 ++++++++++++++++++++++++++++++ python-mbse/mbse/parser.py | 35 +++++++++++ python-mbse/setup.py | 12 ++++ supervisord.d/bbs.conf | 3 + supervisord.d/cron.conf | 2 + supervisord.d/ssh-server.conf | 2 + supervisord.d/web-server.conf | 2 + supervisord.d/xinetd.conf | 2 + xinetd.d/binkp | 10 ++++ xinetd.d/fido | 10 ++++ xinetd.d/telnet | 15 +++++ xinetd.d/tfido | 10 ++++ zt-gpg-key | 52 ++++++++++++++++ 22 files changed, 436 insertions(+) create mode 100644 Dockerfile create mode 100755 golded.sh create mode 100755 init create mode 100644 logrotate.d/mbse create mode 100644 mbse.cron create mode 100644 python-mbse/mbse/__init__.py create mode 100644 python-mbse/mbse/cmd/__init__.py create mode 100644 python-mbse/mbse/cmd/pwfix.py create mode 100644 python-mbse/mbse/fmt/__init__.py create mode 100644 python-mbse/mbse/fmt/users_data.py create mode 100644 python-mbse/mbse/parser.py create mode 100644 python-mbse/setup.py create mode 100644 supervisord.d/bbs.conf create mode 100644 supervisord.d/cron.conf create mode 100644 supervisord.d/ssh-server.conf create mode 100644 supervisord.d/web-server.conf create mode 100644 supervisord.d/xinetd.conf create mode 100644 xinetd.d/binkp create mode 100644 xinetd.d/fido create mode 100644 xinetd.d/telnet create mode 100644 xinetd.d/tfido create mode 100644 zt-gpg-key diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..abc4ab4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +# NAME leenooks/mbse +# VERSION 1.0.7.12 + +FROM debian:stretch-slim + +RUN apt-get update \ + && apt-get install -y \ + curl python-pip supervisor xinetd telnetd openssh-server webfs procps less \ + zip tar arj unrar-free lhasa arc zoo lrzsz unzip logrotate joe \ + && rm -rf /var/lib/apt/lists/* /tmp/* + +RUN mkdir -p /run/sshd + +# Add in Leenooks' apt repository +RUN curl -s http://apt.leenooks.net/setup.sh | sh + +RUN apt-get update \ + && apt-get install --allow-unauthenticated -yqq mbse makenl goldedplus \ + && rm -rf /var/lib/apt/lists/* /tmp/* + +ENV MBSE_ROOT /opt/mbse +WORKDIR ${MBSE_ROOT} + +RUN mkdir ${MBSE_ROOT}/template; \ + for dir in share home etc var log tmp ftp html; do \ + mv ${MBSE_ROOT}/${dir} ${MBSE_ROOT}/template; \ + ln -s data/${dir} ${MBSE_ROOT}/${dir}; \ + done + +EXPOSE 23 80 24554 60177 60179 + +VOLUME [ "${MBSE_ROOT}/data" ] + +COPY python-mbse /root/python-mbse +RUN cd /root/python-mbse; pip install . + +COPY xinetd.d /etc/xinetd.d +COPY supervisord.d /etc/supervisor/conf.d/ +COPY logrotate.d /etc/logrotate.d/ +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf", "-n"] +COPY mbse.cron /etc/cron.d/mbse + +COPY golded.sh /usr/bin +COPY init /sbin/init +ENTRYPOINT [ "/sbin/init" ] + +RUN ln -sf /usr/share/zoneinfo/Australia/Melbourne /etc/localtime + +# Add ZeroTier +RUN echo "deb http://download.zerotier.com/debian/stretch stretch main" > /etc/apt/sources.list.d/zerotier.list +COPY zt-gpg-key /tmp/ +RUN apt-key add /tmp/zt-gpg-key +RUN apt-get update \ + && apt-get install -yq zerotier-one \ + && rm -rf /var/lib/apt/lists/* /tmp/* + +VOLUME [ "/var/lib/zerotier-one" ] diff --git a/golded.sh b/golded.sh new file mode 100755 index 0000000..5e05c90 --- /dev/null +++ b/golded.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +gedlnx -C${MBSE_ROOT}/etc/golded.inc diff --git a/init b/init new file mode 100755 index 0000000..d473f3a --- /dev/null +++ b/init @@ -0,0 +1,38 @@ +#!/bin/sh + +: ${MBSE_ROOT=/opt/mbse} + +PATH=${MBSE_ROOT}/bin:$PATH +export PATH + +if [ ! -d "${MBSE_ROOT}/etc" ]; then + echo "* Installing MBSE data files into ${MBSE_ROOT}/data" + cp -a ${MBSE_ROOT}/template/* ${MBSE_ROOT}/data +fi + +if [ -f "${MBSE_ROOT}/data/etc/users.data" ]; then + echo "* Restoring passwords" + mbse-pwfix +fi + +for keytype in rsa ecdsa ed25519; do + if [ ! -f /etc/ssh/ssh_host_${keytype}_key ]; then + echo "* Generating ${keytype} ssh key" + /usr/libexec/openssh/sshd-keygen ${keytype} + fi +done + +# clear out state and lockfiles from previous instance +rm -f ${MBSE_ROOT}/data/var/sema/* +rm -f ${MBSE_ROOT}/data/var/run/* +rm -f ${MBSE_ROOT}/data/tmp/* + +chown -R mbse:bbs ${MBSE_ROOT}/data + +if [ -x /usr/sbin/zerotier-one -a -n "${ENABLE_ZT}" ]; then + echo "* Starting ZeroTier" + mkdir /dev/net && mknod /dev/net/tun -m 666 c 10 200 + /usr/sbin/zerotier-one -d +fi + +exec "$@" diff --git a/logrotate.d/mbse b/logrotate.d/mbse new file mode 100644 index 0000000..59319a5 --- /dev/null +++ b/logrotate.d/mbse @@ -0,0 +1,7 @@ +/opt/mbse/data/log/*.log { + daily + compress + rotate 31 + olddir /opt/mbse/data/log/old + su mbse bbs +} diff --git a/mbse.cron b/mbse.cron new file mode 100644 index 0000000..0375b81 --- /dev/null +++ b/mbse.cron @@ -0,0 +1 @@ +00 06 * * * mbse /opt/mbse/etc/maint diff --git a/python-mbse/mbse/__init__.py b/python-mbse/mbse/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-mbse/mbse/cmd/__init__.py b/python-mbse/mbse/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-mbse/mbse/cmd/pwfix.py b/python-mbse/mbse/cmd/pwfix.py new file mode 100644 index 0000000..adf6c09 --- /dev/null +++ b/python-mbse/mbse/cmd/pwfix.py @@ -0,0 +1,79 @@ +import argparse +import logging +import os +import pwd +import subprocess + +from mbse.parser import parse_struct +import mbse.fmt.users_data as users_data + +def parse_args(): + p = argparse.ArgumentParser() + + p.add_argument('--mbse-root', '-r', default='/opt/mbse') + p.add_argument('--users', '-u') + p.add_argument('--shell', '-S') + p.add_argument('--home', '-H') + + return p.parse_args() + + +def main(): + args = parse_args() + logging.basicConfig(level='INFO') + log = logging.getLogger(__name__) + + if args.users is None: + args.users = os.path.join(args.mbse_root, 'etc', 'users.data') + + if args.shell is None: + args.shell = os.path.join(args.mbse_root, 'bin', 'mbsebbs') + + if args.home is None: + args.home = os.path.join(args.mbse_root, 'home') + + with open(args.users, 'r') as fd: + hdr = parse_struct(users_data.fmt_userhdr, fd) + + while True: + try: + user = parse_struct(users_data.fmt_userrec, fd) + + # skip deleted users + if user['flags1'] & users_data.FLAG_DELETED: + continue + + user['Name'] = user['Name'].rstrip('\0') + user['sUserName'] = user['sUserName'].rstrip('\0') + user['Password'] = user['Password'].rstrip('\0') + + # check if users exists + try: + pwd.getpwnam(user['Name']) + log.info('user %s already exists', user['Name']) + except KeyError: + log.info('creating user %s', user['Name']) + subprocess.check_call([ + 'useradd', + '-c', user['sUserName'], + '-d', os.path.join(args.home, user['Name']), + '-s', args.shell, + '-g', 'bbs', + '-M', '-N', + user['Name'] + ]) + + log.info('fixing permissions on home directory') + subprocess.check_call([ + 'chown', '-R', user['Name'], + os.path.join(args.home, user['Name'])]) + + log.info('setting password for user %s', user['Name']) + p = subprocess.Popen(['chpasswd'], stdin=subprocess.PIPE) + + p.communicate(input=user['Name']+':'+user['Password']) + except EOFError: + break +if __name__ == '__main__': + main() + diff --git a/python-mbse/mbse/fmt/__init__.py b/python-mbse/mbse/fmt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-mbse/mbse/fmt/users_data.py b/python-mbse/mbse/fmt/users_data.py new file mode 100644 index 0000000..18858e6 --- /dev/null +++ b/python-mbse/mbse/fmt/users_data.py @@ -0,0 +1,96 @@ +fmt_userhdr = ( + ('hdrsize', 'I'), + ('recsize', 'I'), +) + +fmt_securityrec = ( + ('level', 'I'), + ('flags', 'I'), + ('notflags', 'I'), +) + +fmt_userrec = ( + ('sUserName', '36s'), + ('Name', '9s'), + ('xPassword', 'I'), + ('sVoicePhone', '20s'), + ('sDataPhone', '20s'), + ('sLocation', '28s'), + ('sAddress', ('41s',)*3), + ('sDataOfBirth', '12s'), + ('tFirstLoginDate', 'I'), + ('tLastLoginDate', 'I'), + ('Security', None, fmt_securityrec), + ('sComment', '81s'), + ('sExpiryDate', '12s'), + ('ExpirySec', None, fmt_securityrec), + ('sSex', '8s'), + + ('flags1', 'B'), + ('flags2', 'B'), + + ('iTotalCalls', 'I'), + ('iTimeLeft', 'I'), + ('iConnectTime', 'I'), + ('iTimeUsed', 'I'), + ('xScreenLen', 'I'), + ('tLastPwdChange', 'I'), + ('xHangUps', 'I'), + ('Credit', 'I'), + ('Paged', 'I'), + ('MsgEditor', 'I'), + ('LastPktNum', 'I'), + ('Archiver', '6s'), + ('iLastFileArea', 'I'), + ('iLastFileGroup', 'I'), + ('sProtocol', '21s'), + ('Downloads', 'I'), + ('Uploads', 'I'), + ('UploadK', 'I'), + ('DownloadK', 'I'), + ('DownloadKToday', 'I'), + ('UploadKToday', 'I'), + ('iLastMsgArea', 'I'), + ('iTransferTime', 'I'), + ('iLastMsgGroup', 'I'), + ('iPosted', 'I'), + ('iLanguage', 'I'), + ('sHandle', '36s'), + ('iStatus', 'I'), + ('DownloadsToday', 'I'), + ('CrtDef', 'I'), + ('Protocol', 'I'), + ('flags3', 'B'), + ('Password', '15s'), + ('Charset', 'I'), + ('OLRext', 'I'), + ('OLRlast', '12s'), +) + +# flags2 +FLAG_HIDDEN = 1 +FLAG_HOTKEYS = 1 << 1 +FLAG_XGRAPHMODE = 1 << 2 +FLAG_DELETED = 1 << 3 +FLAG_NEVERDELETE = 1 << 4 +FLAG_XCHAT = 1 << 5 +FLAG_LOCKEDOUT = 1 << 6 +FLAG_DONOTDISTURB = 1 << 7 + +# flags2 +FLAG_CLS = 1 +FLAG_MORE = 1 << 1 +FLAG_XFSMSGED = 1 << 2 +FLAG_MAILSCAN = 1 << 3 +FLAG_GUEST = 1 << 4 +FLAG_OL_EXTINFO = 1 << 5 + +# flags3 +FLAG_IEMSI = 1 +FLAG_IEMNU = 1 << 1 +FLAG_IETAB = 1 << 2 +FLAG_IEASCII8 = 1 << 3 +FLAG_IENEWS = 1 << 4 +FLAG_IEFILE = 1 << 5 +FLAG_EMAIL = 1 << 6 +FLAG_FSEMACS = 1 << 7 diff --git a/python-mbse/mbse/parser.py b/python-mbse/mbse/parser.py new file mode 100644 index 0000000..c018862 --- /dev/null +++ b/python-mbse/mbse/parser.py @@ -0,0 +1,35 @@ +import struct + +def read_one(name, spec, fd): + rsize = struct.calcsize(spec) + raw = fd.read(rsize) + + if not len(raw): + raise EOFError() + if len(raw) != rsize: + raise ValueError('read %d bytes, expected %d' % (len(raw), rsize)) + + data = struct.unpack(spec, raw)[0] + + return data + +def parse_struct(spec, fd): + parsed = {} + + for fldspec in spec: + try: + fld, spec, sub = fldspec + except ValueError: + sub = None + fld, spec = fldspec + + if sub: + data = parse_struct(sub, fd) + elif isinstance(spec, tuple): + data = [read_one(fld, s, fd) for s in spec] + else: + data = read_one(fld, spec, fd) + + parsed[fld] = data + + return parsed diff --git a/python-mbse/setup.py b/python-mbse/setup.py new file mode 100644 index 0000000..b81f4d6 --- /dev/null +++ b/python-mbse/setup.py @@ -0,0 +1,12 @@ +import setuptools + +setuptools.setup( + version='1.0.0', + name='mbse', + packages=setuptools.find_packages(), + entry_points={ + 'console_scripts': [ + 'mbse-pwfix = mbse.cmd.pwfix:main', + ], + } +) diff --git a/supervisord.d/bbs.conf b/supervisord.d/bbs.conf new file mode 100644 index 0000000..aa45049 --- /dev/null +++ b/supervisord.d/bbs.conf @@ -0,0 +1,3 @@ +[program:mbtask] +command=%(ENV_MBSE_ROOT)s/bin/mbtask -nd +user=mbse diff --git a/supervisord.d/cron.conf b/supervisord.d/cron.conf new file mode 100644 index 0000000..c072909 --- /dev/null +++ b/supervisord.d/cron.conf @@ -0,0 +1,2 @@ +[program:cron] +command=/usr/sbin/cron -f diff --git a/supervisord.d/ssh-server.conf b/supervisord.d/ssh-server.conf new file mode 100644 index 0000000..1642af3 --- /dev/null +++ b/supervisord.d/ssh-server.conf @@ -0,0 +1,2 @@ +[program:sshd] +command=/usr/sbin/sshd -D diff --git a/supervisord.d/web-server.conf b/supervisord.d/web-server.conf new file mode 100644 index 0000000..54720ad --- /dev/null +++ b/supervisord.d/web-server.conf @@ -0,0 +1,2 @@ +[program:webfs] +command=/usr/bin/webfsd -Fp 80 -r /opt/mbse/data/html -f index.html diff --git a/supervisord.d/xinetd.conf b/supervisord.d/xinetd.conf new file mode 100644 index 0000000..43b94f8 --- /dev/null +++ b/supervisord.d/xinetd.conf @@ -0,0 +1,2 @@ +[program:xinetd] +command=/usr/sbin/xinetd -dontfork diff --git a/xinetd.d/binkp b/xinetd.d/binkp new file mode 100644 index 0000000..d412008 --- /dev/null +++ b/xinetd.d/binkp @@ -0,0 +1,10 @@ +service binkp +{ + socket_type = stream + protocol = tcp + wait = no + user = mbse + instances = 10 + server = /opt/mbse/bin/mbcico + server_args = -t ibn +} diff --git a/xinetd.d/fido b/xinetd.d/fido new file mode 100644 index 0000000..8048d9b --- /dev/null +++ b/xinetd.d/fido @@ -0,0 +1,10 @@ +service fido +{ + socket_type = stream + protocol = tcp + wait = no + user = mbse + instances = 10 + server = /opt/mbse/bin/mbcico + server_args = -t ifc +} diff --git a/xinetd.d/telnet b/xinetd.d/telnet new file mode 100644 index 0000000..5c8d927 --- /dev/null +++ b/xinetd.d/telnet @@ -0,0 +1,15 @@ +service telnet +{ + disable = no + protocol = tcp + instances = 10 + flags = REUSE + log_on_failure += USERID + socket_type = stream + user = root + server = /usr/sbin/in.telnetd + server_args = -L /opt/mbse/bin/mblogin + wait = no +} + + diff --git a/xinetd.d/tfido b/xinetd.d/tfido new file mode 100644 index 0000000..b9693dd --- /dev/null +++ b/xinetd.d/tfido @@ -0,0 +1,10 @@ +service tfido +{ + socket_type = stream + protocol = tcp + wait = no + user = mbse + instances = 10 + server = /opt/mbse/bin/mbcico + server_args = -t itn +} diff --git a/zt-gpg-key b/zt-gpg-key new file mode 100644 index 0000000..dc7d645 --- /dev/null +++ b/zt-gpg-key @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +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 +=JgAv +-----END PGP PUBLIC KEY BLOCK-----