#!/bin/sh # -*- shell-script -*- # $Id: borewiki-admin.sh 1333 2007-04-09 10:57:15Z too $ # # Author: Tomi Ollila -- too ät iki piste fi # # Created: Fri Jun 16 20:18:08 EEST 2006 too # Last Modified: Mon Apr 09 13:34:46 EEST 2007 too # # Licensed under GPL v2 (until v3 is ready and if good enough). PATH=/bin:/usr/bin:/sbin:/usr/sbin export PATH #LANG=C LC_ALL=C; export LANG LC_ALL #POSIXLY_CORRECT=1; export POSIXLY_CORRECT ne () { echo "$@" | tr -d \\012; } e2 () { echo "$@" >&2; } ee () { e2 "$@"; exit 1; } me () { cut -d: -f2-; } me2 () { me >&2; } mee () { me2; exit 1; } erun () { e2 = "$@"; "$@"; } usage () { e2; e2 Usage: $0 $cc "$@"; ee; } needvar () { [ x"$1" != x ] || { shift; "$@"; }; } #secstoday () { eval expr `date "+ 3600 \* %H + 60 \* %M + %S"`; } #hhmmss () { date +%H:%M:%S; } yyyymmddhhmmss () { date +%Y%m%d-%H%M%S; } logfile= log () { [ x"$logfile" = x ] && { echo Not logged message: $*; } \ || echo `date` "[$$]": "$*" | tee -a $logfile } varsfromcgi () { [ -x BoreWiki.cgi ] || ee BoreWiki.cgi does not exist or is not executable. # change 'eval' to 'ee' below, when testing... eval `sed -n -e '1,/Begin shared var.../d' -e '/End shared var.../q' \ -e 's/ *= */=/; s/^my \\$//p;' BoreWiki.cgi` [ v"$version" != v0.981 ] && ee BoreWiki version mismatch. } trylock_sh () { ln -s $$ "$1.wlock" \ || ee Can not get write lock on file $1. Try again later. trap "rm -f '$1.wlock'" 0 } havepwuser () { ! awk '/^passwd: / && $2 == "'"$2"'" { exit 1; }' "$1" } haveuser () { ! awk '/^passwd: / && $3 == "'"$2"'" { exit 1; }' "$1" } checknotabs () { d=`dirname "$1"` [ -d "$d" ] || { mkdir -p "$d"; chmod 700 "$d"; } [ -f "$1" ] || touch "$1" #awk '/\t/ {exit 1}' "$1" this may not work on old awk. perl -ne '/\t/ && exit 1' "$1" \ || ee Tab characters in file $1. Please remove. } chkpwuser () { # pwuser may have [a-z0-9] z=`echo $1 | tr -d '[a-z0-9\012]'` [ x"$z" = x ] || ee pwuser $1 has illegal characters. } chkusername () { # username may have [a-zA-Z0-9.] z=`echo $1 | tr -d '[a-zA-Z0-9.\012]'` [ x"$z" = x ] || ee Username $1 has illegal characters. } chkapaths () { p=`echo $* | tr -d ' [a-zA-Z0-9]-_./'` [ x"$p" != x ] && ee Access paths in $* has illegal chars: $p for i in "$@" do case $i in *' '*) ee Spaces in access path "'"$i"'". ;; /|/*/) p="$p $i" ;; /*) p="$p $i/" ;; *) ee Access path "'"$i"'" does not start with "'/'". ;; esac done } hashedpw () { echo "$1" | perl -le ' use Digest::MD5 qw(md5_hex); chomp($upw = ); open(I, "< /dev/urandom") || die "Opening /dev/urandom failed: $!\n"; die "Unable to read random data" unless (read(I, $x, 4, 0) == 4); $salt = unpack("H8", $x); if ($upw eq ".") { die "Unable to read random data" unless (read(I, $x, 16, 0) == 16); $hpw = unpack("H32", $x); } else { $hpw = md5_hex("${salt}${upw}${salt}"); } print $salt, " ", $hpw;' } askpasswd () { me <<. : : The security of the password given below may not be too good : (the strengthness of the hashing is not proven any way; also : you are probably entering the password over unencrypted link : when authenticating to BoreWiki). : If you want to give "disabled" password, enter '.' below. : . stty -echo ne Enter password '(or . ): ' read pw; echo stty echo; case $pw in .) return ;; ?|??|???|????|?????) ee Password too short ;; esac stty -echo ne Again: '' read pw2; echo stty echo [ x"$pw" = x"$pw2" ] || ee Passwords do not match. } updatecnffile () { new=$1 old=$2; shift 2 [ -f "$old" ] || rcs -i -U -ko -t-'BoreWiki config file.' "$old" > "$cnf_passwd.$$" updatecnffile "$cnf_passwd.$$" "$cnf_passwd" Added user $pwuser "($user)". log User $pwuser "($user)" added. } cmd_chpasswd () # Change password of a user. { needvar "$1" usage pwuser chkpwuser "$1" varsfromcgi checknotabs "$cnf_passwd" havepwuser "$cnf_passwd" $1 || ee User $1 does not exist. askpasswd trylock_sh "$cnf_passwd" pwdata=`hashedpw "$pw"` sed "/^passwd: *$1 / s/[^ ]* *[^ ]\{32\} */$pwdata /" \ "$cnf_passwd" > "$cnf_passwd.$$" updatecnffile "$cnf_passwd.$$" "$cnf_passwd" Changed user $1 password. log User $1 password changed. } removecookies () { # FIXME: use mv $cnf_cookies $cnf_cookies.new and continue from there. # or touch $cnf_cookies.ax; mv $cnf_cookies.ax $cnf_cookies.new .. # also use trylock_sh to get lock between admin.sh runs. rm -f $cnf_cookies sleep 1 rm -f $cnf_cookies echo Note: known cookies of all users removed. } cmd_chaccess () # Change access paths of a user. { needvar "$2" usage pwuser access-path [access-path...] chkpwuser "$1" varsfromcgi checknotabs "$cnf_passwd" havepwuser "$cnf_passwd" $1 || ee User $1 does not exist. user=$1; shift chkapaths "$@" trylock_sh "$cnf_passwd" awk "/^passwd: *$user /"' { print $1, $2, $3, $4, $5 "'"$p"'"; next } { print }' "$cnf_passwd" > "$cnf_passwd.$$" updatecnffile "$cnf_passwd.$$" "$cnf_passwd" Changed user $user access paths. removecookies $user log User $user access paths changed. } cmd_hashedpasswd () # Create hashed password to be merged elsewhere. { askpasswd hashedpw $pw } cmd_mergepasswd () # Merge in password created elsewhere. { needvar "$3" usage pwuser salt hashedpassword chkpwuser "$1" varsfromcgi checknotabs "$cnf_passwd" havepwuser "$cnf_passwd" $1 || ee User $1 does not exist. #awk '/^[0-9a-f]{8} [0-9a-f]{32}$/ { exit 1 }' || [ x"`echo $2 $3 | sed 's/^[0-9a-f]\{8\} [0-9a-f]\{32\}$//'`" = x ] \ || ee Passwd data $2 $3 has illegal characters/format. trylock_sh "$cnf_passwd" sed "/^passwd: *$1 / s/[^ ]* *[^ ]\{32\} */$2 $3 /" \ "$cnf_passwd" > "$cnf_passwd.$$" updatecnffile "$cnf_passwd.$$" "$cnf_passwd" Merged in user $1 password. log User $1 password merged. } cmd_backup () # Take backup of created pages (RCS files) + CGI and this tool. { varsfromcgi tarfile=backup-bw-`yyyymmddhhmmss`.tar.gz { echo BoreWiki.cgi borewiki-admin.sh | tr ' ' \\012 find . -name '*.bw.rst,v' -print | sed 's|^\./||' } | gtar --posix -T - -zcvf $tarfile echo; echo Created backup file $tarfile.; echo } cmd_snapshot () # Archive snapshot of current files (html or rsts from backup). { needvar "$1" usage backup-filename or "'html'". if [ x"$1" = xhtml ] then tarfile=snapshot-html-bw-`yyyymmddhhmmss`.tar.gz find . -name '*.html' | sed 's/\.html//' | while read line do [ -f $line.bw.rst,v ] && echo $line.html done | gtar --posix -T - -zcvf $tarfile else tarfile=snapshot-rst-bw-`yyyymmddhhmmss`.tar.gz [ -f "$1" ] || ee backup file "'$1'" does not exist. trap "rm -rf $1.x" 0 mkdir $1.x tar -C $1.x -zxvf $1 cd $1.x find . -name '*.bw.rst,v' -exec co '{}' ';' find . -name '*.bw.rst' | gtar --posix -T - -zcvf ../$tarfile cd .. fi echo; echo Created backup file $tarfile.; echo } cmd_delpwrev () # Delete (interactively) old passwd file from version control. { needvar "$1" usage revision to delete or '' - '' for revision log. varsfromcgi [ x"$1" = x- ] && { rlog "$cnf_passwd"; exit 0; } trylock_sh "$cnf_passwd" co -p"$1" "$cnf_passwd" || exit 1 ne Do you want to delete this revision '(yes/NO)? ' read ans [ xans = xyes ] && rcs -o"$1" "$cnf_passwd" || ee No deletions done. log Deleted revision $1 from "$cnf_passwd". } cmd_renamepage () # Rename page to a different name (in same directory). { needvar "$2" usage oldname newname case $1 in /*) ee $1: no absolute paths allowed. ;; esac case $2 in /*) ee $2: no absolute paths allowed. ;; esac for sx in bw.rst,v rst htm html do test -f "$1.$sx" || ee File "'$1.$sx'" does not exist. test -f "$2.$sx" && ee File "'$2.$sx'" already exists. done mv "$1".bw.rst,v "$2".bw.rst,v mv "$1".rst "$2".rst mv "$1".html "$2".html sed "s|.cgi?$1|.cgi?$2|" "$1".htm > "$2".htm rm "$1".htm } xsetlogfile () { case $1 in l*) ;; e*) logfile=$errfile ;; s*) logfile=$savelogfile ;; *) ee $1: unknown log file. esac } rlmvs () { mv -f $logfile$1 $logfile$2 2>/dev/null mv -f $errfile$1 $errfile$2 2>/dev/null mv -f $savelogfile$1 $savelogfile$2 2>/dev/null } cmd_rotatelogs () # Rotate logs written by BoreWiki programs. { varsfromcgi j=21 for i in 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 do rlmvs .$i .$j j=$i; done rlmvs '' .1 } cmd_viewlog () # View logs written by BoreWiki programs. { needvar "$1" usage '(l*|e*|s*) -- log or (std)err or savelog' varsfromcgi xsetlogfile $1 ${PAGER:-less} $logfile } cmd_logger () # Add log line { needvar "$2" usage '(l*|e*|s*) log message ...' varsfromcgi xsetlogfile $1 shift log "$@" } xrlog () { test -f "$1" && { rlog "$1"; exit 0; }; } cmd_rlog () # See revision log of a page { needvar "$1" usage page xrlog "$1" xrlog "$1".bw.rst,v xrlog "$1".bw.rst case $1 in *.) set x `echo $1 | sed 's/.$//'`; shift ;; *.htm|*.html) set x `echo $1 | sed 's/.html\\?$//'`; shift ;; *) echo $1: No such page esac xrlog "$1" xrlog "$1".bw.rst,v xrlog "$1".bw.rst } cmd_mkdir () # Create directory ... { needvar "$1" usage directory case $1 in /*) ee Absolute paths not allowed. ;; *./*) ee Path component may not have ./ ;; esac mkdir -p "$1" } _cmd_othercmds () # List some shell commands that are also needed/useful. { me < [args]' echo echo $0 commands available: echo sed -n '/^cmd_/ { s/cmd_/ /; s/ () [ -#]*/ / s/\(.\{15\}\) */\1/p; }' $0 echo exit 0 } cmd=$1; shift cc= for i in `sed -n 's/^cmd_\([a-z0-9_]*\) (.*/\1/p' $0` do case $i in $cmd*) cp=$cc; cc="$i $cc";; esac done [ x"$cc" = x ] && { echo $0: $cmd -- command not found.; exit 1; } [ x"$cp" != x ] && { echo $0: $cmd -- ambiquous command: matches $cc; exit 1; } cmd=$cc cd `dirname $0` [ -x BoreWiki.cgi ] || ee BoreWiki.cgi not in `dirname $0`. Bailing out. cmd_$cc "$@"