hbbs

bbs.hlirc.net
Log | Files | Refs | README | LICENSE

commit b89d87f758a80bd967a8dcaac1e1ba3a3262e7e1
Author: hhvn <hayden@haydenvh.com>
Date:   Sun, 31 Jan 2021 23:13:36 +0000

initial commit

Diffstat:
Abin/interface.sh | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/login.sh | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/wrapper.sh | 4++++
Aconfig.sh | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aetc/banner_full | 2++
Aetc/less | 0
Aetc/lesskey | 8++++++++
Ahbbs.sh | 32++++++++++++++++++++++++++++++++
Ainclude/common.sh | 37+++++++++++++++++++++++++++++++++++++
Ainclude/userinfo.sh | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 561 insertions(+), 0 deletions(-)

diff --git a/bin/interface.sh b/bin/interface.sh @@ -0,0 +1,290 @@ +#!/bin/sh + +k=$(tput setaf $key___colour) +s=$(tput sgr0) + +set_mode(){ + mode=$1 + page=0 +} + +parse_in(){ + cmd=$(echo "$1" | grep -o '^.') + arg=$(echo "$1" | sed 's/^.[[:space:]]*//') +} + +pager(){ + rows=$(echo "$(tput lines) - ${1:-0} - ${2:-0}" | bc) + remove=$(echo "$rows * $page" | bc) + head -n -$remove | head -n $rows +} + +menu_boards(){ + echo "$banner" + echo + printf '%- 20s Posts Description\n' 'Board' + echo ================================================================================ + for b in $boards + do + b2=$(echo "$b" | tr '[:upper:]' '[:lower:]') + printf '%- 20s [%s% 4d%s] %s\n' \ + "$b" \ + "$(tput setaf $num___colour)" \ + "$(awk '$4 != "root"' < $datadir/boards/$b2/list | wc -l)" \ + "${s}" \ + "$(eval "echo \$board_${b2}_desc")" + done | pager 5 $(echo "$banner" | wc -l) + printf '=======================================================================Page:%- 3d=\n' "$page" + while prompt "Ne${k}x${s}t ${k}p${s}revious ${k}u${s}sers ${k}s${s}elect rel${k}o${s}ad ${k}h${s}elp" input + do + parse_in "$input" + case "$cmd" in + x) page=$(($page+1)); break ;; + p) page=$(($page-1)); break ;; + s) + [ "$arg" = "" ] && prompt "board" arg + board=$(echo "$boards" | tr ' ' '\n' | grep -i "$arg" | head -n 1 | tr '[:upper:]' '[:lower:]') + [ "$board" = "" ] && error "no such board" || { + set_mode list + break + } + ;; + u) set_mode users; break ;; + h) cat <<- EOF + <cmd> <arg> + + x - go to the next page + p - go to the previous page + s - select <arg> as board + u - go to the users menu + h - show this help + o - reload the current menu + + (help varies depending on current menu) + EOF + ;; + *) error "unrecognized command" ;; + esac + done +} + +menu_list(){ + echo "$banner" + echo + [ ! -f $datadir/boards/$board/list ] && echo "1 0 01-Jan-1970 root empty" > $datadir/boards/$board/list + echo " ID Date User Subject " + echo ================================================================================ + awk -v "n=1" -v "c0=${s}" -v "c1=$(tput setaf $num___colour)" -v "c2=$(tput setaf $date__colour)" -F" " \ + '$1 != n {print "--------------------------------------------------------------------------------"} + 1 {printf("%s% 3s.%- 3s%s [%s%s%s] %- 10s | %s\n", c1, $1, $2, c0, c2, $3, c0, $4, $5); n=$1}' \ + < $datadir/boards/$board/list | pager 5 $(echo "$banner" | wc -l) + printf '=%- 10s============================================================Page:%- 3d=\n' "$(echo "$board" | tr '[:lower:]' '[:upper:]')" "$page" | tr ' ' '=' | sed 's/==$/ =/;s/:=/: /' + while prompt "Ne${k}x${s}t ${k}p${s}revious ${k}b${s}oards rel${k}o${s}ad ${k}r${s}ead ${k}n${s}ew ${k}h${s}elp" input + do + parse_in "$input" + case "$cmd" in + x) page=$(($page+1)); break ;; + p) page=$(($page-1)); break ;; + b) set_mode boards; break ;; + r) + [ "$arg" = "" ] && prompt "id" arg + grep "^${arg%%.*} ${arg##*.}" < $datadir/boards/$board/list >/dev/null && { + id=$arg + set_mode "read" + break + } || error "no such id" + ;; + n) + [ "$arg" = "" ] && prompt "Subject" subject || subject="$arg" + info "Type \".\" on it's own to finish the post" + file="$datadir/boards/$board/$(echo "$(find $datadir/boards/$board/ -type f | wc -l) + 1" | bc).post" + while prompt "" line + do + [ "$line" = "." ] && break + echo "$line" >> $file + done + awk -v "data=1 0 $(date +%d-%b-%Y) $user $subject $(basename $file)" -F" " \ + 'BEGIN {print data}; $4 != "root" {print $1 + 1 "\t" $2 "\t" $3 "\t" $4 "\t" $5 "\t" $6 "\t" $7}' \ + < $datadir/boards/$board/list > $datadir/boards/$board/list.new + mv $datadir/boards/$board/list.new $datadir/boards/$board/list + ok "message sent" + anykey + break + ;; + h) cat <<- EOF + <cmd> <arg> + + x - go to the next page + p - go to the previous page + r - read the message/thread with <arg> id + n - create new thread with <arg> subject + b - go to the boards menu + h - show this help + + (help varies depending on current menu) + EOF + ;; + *) error "unrecognized command" ;; + esac + done +} + +pager2(){ + lfile=$(cat) + len=$(echo "$lfile" | wc -l) + [ $(($len-1)) -gt $(tput cols) ] && echo "$lfile" | less \ + -k$cwd/etc/less \ + -Ps"${s}Re${k}t${s}urn pager(less)-${k}h${s}elp" \ + -Pm"${s}Re${k}t${s}urn pager(less)-${k}h${s}elp" \ + -PM"${s}Re${k}t${s}urn pager(less)-${k}h${s}elp" \ + -Ph"${s}Re${k}t${s}urn" +} + +menu_read(){ + file=$(awk -v "id1=${id%%.*}" -v "id2=${id##*.}" -F" " '$1 == id1 && $2 == id2 {print $6 " " $7}' < $datadir/boards/$board/list) + id2=${id##*.} + cat $datadir/boards/$board/$(echo "$file" | sed -E 's/.* (.)/\1/') + pager2 < $datadir/boards/$board/$(echo "$file" | sed -E 's/.* (.)/\1/') + while prompt "($(($id2+1))/$(awk -v "file=$(echo "$file" | sed 's/ .*//')" -F" " '$6 == file' < $datadir/boards/$board/list | wc -l)) Ne${k}x${s}t ${k}p${s}revious rep${k}l${s}y ${k}b${s}oards b${k}a${s}ck ${k}h${s}elp" input + do + parse_in "$input" + case "$cmd" in + x) + id=$(awk -v "file=$(echo "$file" | sed 's/ .*//')" -v "id2=$id2" -F" " '$6 == file && ($2 - 1) == id2 {print $1"."$2}' < $datadir/boards/$board/list) + [ "$id" = "" ] && { + error "already on last message in thread" + } || break + ;; + p) + id=$(awk -v "file=$(echo "$file" | sed 's/ .*//')" -v "id2=$id2" -F" " '$6 == file && ($2 + 1) == id2 {print $1"."$2}' < $datadir/boards/$board/list) + [ "$id" = "" ] && { + error "already on first message in thread" + } || break + ;; + b) set_mode boards; break ;; + l) + info "Type \".\" on it's own to finish the post" + ofile="$file" + file="$datadir/boards/$board/$(echo "$(find $datadir/boards/$board/ -type f | wc -l) + 1" | bc).post" + while prompt "" line + do + [ "$line" = "." ] && break + echo "$line" >> $file + done + lnum=$(echo "$(awk -v "file=$(echo "$ofile" | sed 's/ .*//')" -F" " '$6 == file' < $datadir/boards/$board/list | wc -l) - 1" | bc) + anykey + awk -v "n=1" -v "file=$(basename "$file")" -v "lnum=$lnum" -v "user=$user" -v "date=$(date +%d-%b-%Y)" -v "ofile=$(echo "$ofile" | sed 's/ .*//')" -F" " ' + $6 == ofile && $2 != lnum {print; n = $1} + $6 == ofile && $2 == lnum {print; print n "\t" lnum + 1 "\t" date "\t" user "\t_\t" ofile "\t" file} + $6 != ofile + ' < $datadir/boards/$board/list > $datadir/boards/$board/list.new + mv $datadir/boards/$board/list.new $datadir/boards/$board/list + set_mode list + ok "message sent" + anykey + break + ;; + a) set_mode list; break ;; + h) cat <<- EOF + <cmd> <arg> + + x - go to the next page + p - go to the previous page + l - reply to the thread + b - go to the boards menu + a - go back to $board listing + h - show this help + + (help varies depending on current menu) + EOF + ;; + *) error "unrecognized command" ;; + esac + done +} + +menu_users(){ + echo "$banner" + echo + echo ================================================================================ + ls $datadir/users/ | pager 4 $(echo "$banner" | wc -l) + printf '=======================================================================Page:%- 3d=\n' "$page" + while prompt "Ne${k}x${s}t ${k}p${s}revious ${k}b${s}oards ${k}i${s}nfo ${k}e${s}dit ${k}h${s}elp" input + do + parse_in "$input" + case "$cmd" in + x) page=$(($page+1)); break ;; + p) page=$(($page-1)); break ;; + i) + [ ! -f $datadir/users/${arg:-$user} ] && error "no such user" && break + clear + echo "Created: $(userget "${arg:-$user}" creation)" + echo "Email: $(userget "${arg:-$user}" email)" + [ "$(userget "${arg:-$user}" nick)" != "" ] && echo "Nick: $(userget "${arg:-$user}" nick)" + echo + cat $datadir/profile/${arg:-$user} + tput sgr0 + anykey + break + ;; + e) + prompt "email (empty to remain unchanged)" email + prompt "usual nick on irc (empty=unchanged)" nick + prompt "password (empty=unchanged)" passwd + [ "$password" != "" ] && { + prompt "confirm password" passwd2 + [ "$passwd" = "$passwd2" ] && { + userset "$user" passwd "$(echo "$passwd" | sha512sum)" + ok "password updated" + } || error "passwords do not match" + } + [ "$email" != "" ] && userset "$user" email "$email" + [ "$nick" != "" ] && userset "$user" nick "$nick" + prompt "Edit bio (${k}y${s}es/${k}n${s}o/from ${k}u${s}ri)" edit + [ "$edit" = "y" ] && { + printf '' >$datadir/profile/$user + info "Type EOF by itself on a line to finish the profile" + while prompt "" line + do + [ "$line" = "EOF" ] && break + echo "$line" >> $datadir/profile/$user + done + } + [ "$(echo "$edit" | grep -o '^.')" = "u" ] && curl "$(echo "$edit" | sed 's/^.//' | tr -d ' ')" > $datadir/profile/$user + ok "profile updated" + anykey + break + ;; + b) set_mode boards; break ;; + h) cat <<- EOF + <cmd> <arg> + + x - go to the next page + p - go to the previous page + i - view information about yourself, or <arg> + e - edit your profile + b - go to the boards menu + e - edit your info + h - show this help + + (help varies depending on current menu) + EOF + ;; + esac + done +} + +interface(){ + . $cwd/config.sh + while true + do + clear + case "$mode" in + boards) menu_boards ;; + users) menu_users ;; + list) menu_list ;; + read) menu_read ;; + *) set_mode "boards" + esac + done +} diff --git a/bin/login.sh b/bin/login.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +. $cwd/include/userinfo.sh +. $cwd/include/common.sh + +login(){ + clear + echo "$banner" + echo "$start_message" + + prompt "login" user + + [ "$user" = "new" ] && { + clear + echo "Registration" + echo + info "Only a-z0-9 are accepted in usernames" + prompt "user" user + origuser="$user" + user=$(echo "$user" | tr -dc 'a-z0-9') + echo "$origuser" | grep '[^a-z0-9]' >/dev/null && info "username changed to: $user" + checknuser "$user" 2>/dev/null || { + anykey + return 1 + } + info "Password must be atleast 8 characters long" + info "Passwords are hashed with sha512sum" + prompt "passwd" passwd + echo "$passwd" | grep '........' >/dev/null || { + error "password too short" + anykey + return 1 + } + prompt "email (verification only)" email + echo "$email" | grep '[a-zA-Z0-9+.]*@[A-Za-z0-9.]*.[A-Za-z0-9.]*' >/dev/null || { + error "doesn't look like an email" + anykey + return 1 + } + vercode=$(head -n 5 /dev/urandom | base64 | tr -d '\n' | cut -c-10) + ( + echo "This is the bbs auto-mailer" + echo + echo "Your verification code is:" + echo "$vercode" + echo + echo "If this message is not intended for you, please ignore it" + ) | mail -s "$bbs_addr verification code" -r "$email_addr" -- "$email" + userset "$user" "creation" "$(date +%Y-%m-%d)" + userset "$user" "expiry" "$(dateadd $(date +%Y-%m-%d) +2d)" + userset "$user" "email" "$email" + userset "$user" "passwd" "$(echo "$passwd" | sha512sum)" + userset "$user" "vercode" "$vercode" + touch $datadir/profile/$user + ok "account \"$user\" created" + info "Your account expires in 2 days unless verified" + echo "Login to your account" + echo + anykey + return 1 + } || { + prompt "passwd" passwd + check "$user" "$(echo "$passwd" | sha512sum)" + } +} diff --git a/bin/wrapper.sh b/bin/wrapper.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cwd=$(dirname $0) +$cwd/../hbbs.sh diff --git a/config.sh b/config.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +banner=' + _ _ _ _____ ___ ____ _ _ ____ _____ + / // / / / /_ __/ / ,.\ .`.../ / |/ / / __/ /_ _/ + / / / /_ __//_ / ``/ / /__ __ / || / / __/ / / +/_//_/ /___/ /____/ /_/ \\\\ `.__/ /_/ /_/|_/ /___/ /_/' # type '\\' for '\' + +start_message=' +Welcome to the bbs. If you do not have an account, +login as "new" to register. There'\''s no guest view. +' + +bbs_addr="bbs.hlirc.net" +email_addr="bbs@hlirc.net" +boards="QUOTES SANDBOX GENERAL ADMIN FILES" +board_quotes_desc="Quotes from the irc server" +board_sandbox_desc="Send test messages here" +board_general_desc="General discussions/ideas" +board_admin_desc="The Administration" +board_files_desc="Links to files/torrents" +term="xterm-256color" +datadir="/var/bbs" +error_colour=88 +info__colour=239 +ok____colour=34 +num___colour=23 +key___colour=12 +date__colour=9 + +# Sample xinetd configuration: +# +# service telnet +# { +# socket_type = stream +# protocol = tcp +# only_from = 0.0.0.0/0 ::/0 +# +# wait = no +# user = bbs +# group = bbs +# server = /usr/share/hbbs/bin/wrapper.sh +# instances = 12 +# +# log_type = FILE /var/log/hbbs +# log_on_success = PID HOST EXIT DURATION +# log_on_failure = HOST +# banner_fail = /usr/share/hbbs/etc/banner_full +# +# disable = no +# } +# service finger +# { +# socket_type = stream +# protocol = tcp +# only_from = 0.0.0.0/0 ::/0 +# +# wait = no +# user = bbs +# group = bbs +# server = /usr/share/hbbs/bin/wrapper.sh +# server_args = -f +# instances = 5 +# +# log_type = FILE /var/log/hbbs-finger +# log_on_success = PID HOST EXIT DURATION +# log_on_failure = HOST +# banner_fail = /usr/share/hbbs/etc/banner_full +# +# disable = no +# } diff --git a/etc/banner_full b/etc/banner_full @@ -0,0 +1,2 @@ +The BBS is likely full or there is some other such error. +Please try again later. diff --git a/etc/less b/etc/less Binary files differ. diff --git a/etc/lesskey b/etc/lesskey @@ -0,0 +1,8 @@ +#command + +t quit +:e forw-line +^X^V forw-line +:n forw-line +:p forw-line +:d forw-line diff --git a/hbbs.sh b/hbbs.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +trap 'die "SIGINT received"' 2 + +export cwd=$(dirname $0) +. $cwd/include/common.sh +. $cwd/config.sh + +mkdir $datadir -p || die "cannot write to data dir" +touch $datadir/.test || die "cannot write to data dir" +mkdir -p $datadir/profile $datadir/boards $datadir/users $(for b in $(echo "$boards" | tr '[:upper:]' '[:lower:]'); do echo $datadir/boards/$b; done) + +[ "$1" = "-f" ] && cat | bin finger + +info "Setting terminal to $term" +export TERM="$term" +info "Your terminal is $(tput lines) tall" +info "Your terminal is $(tput cols) wide" +anykey +sleep 0.1 + +count=0 +while ! bin login +do + anykey + count=$(($count+1)) + [ $count -eq 5 ] && die "five failed login attempts" +done + +echo +anykey +bin interface diff --git a/include/common.sh b/include/common.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +bin(){ + f=$1 + . $cwd/bin/$f.sh + shift + $f $@ +} + +die(){ + error $1 + exit 1 +} + +error(){ + echo "[$(tput setaf $error_colour)ERROR$(tput sgr0)]: $1" +} + +ok(){ + echo "[$(tput setaf $ok____colour)OK $(tput sgr0)]: $1" +} + +info(){ + echo "[$(tput setaf $info__colour)INFO $(tput sgr0)]: $1" +} + +prompt(){ + printf "$1: " + read $2 +} + +anykey(){ + tput sc + printf "Press enter to continue..." + read null + tput rc; tput el +} diff --git a/include/userinfo.sh b/include/userinfo.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +userset(){ + touch $datadir/users/$1 + awk -v "var=$2" -v "val=$3" -F" " ' + $1 == var { + ; + } + + $1 != var { + print + } + + END { + print var "\t" val + }' < $datadir/users/$1 > $datadir/users/$1.new 2>/dev/null + mv $datadir/users/$1.new $datadir/users/$1 2>/dev/null +} + +userget(){ + awk -v "var=$2" -F" " ' + $1 == var { + print $2 + }' < $datadir/users/$1 2>/dev/null +} + +checknuser(){ + [ "$(userget $1 expiry)" != "" ] && [ $(datediff $(userget $1 expiry) 2021-02-02) -gt -1 ] && rm $datadir/users/$1 + [ -f $datadir/users/$1 ] && error "user already exists" && return 1 + [ "$1" = "new" ] && error "user \"new\" reserved" && return 1 + [ "$1" = "new" ] && error "user \"admin\" reserved" && return 1 + [ "$1" = "new" ] && error "user \"root\" reserved" && return 1 + return 0 +} + +check(){ + [ ! -f $datadir/users/$1 ] && { + error "no such account" + return 1 + } + [ "$(userget $1 passwd)" != "$2" ] && error "incorrect passwd" && return 1 + [ "$(userget $1 expiry)" != "" ] && [ $(datediff $(userget $1 expiry) $(date +%Y-%m-%d)) -gt -1 ] && { + error "user has expired" + rm $datadir/users/$1 + return 1 + } + [ "$(userget $1 expiry)" != "" ] && { + prompt "verification code" vercode + [ "$(userget $1 vercode)" = "$vercode" ] && ok "verification successful" && userset $1 expiry "" || error "incorrect verification code" + } + ok "login success" +}