#!/bin/sh
#
# $Id$
#
# sc: ser control; tool for maintaining ser's databases
#
# History:
# --------
# 2003-02-23 Thomas's start|stop commands checked in
#
# To-DO:
# -----
# - generalization for other than mysql databases
# - front-end to updating administrative mysql password would
#   be a convenient thing to have


# configuration for starting/stopping ser
PID_FILE=/var/run/ser.pid
SYSLOG=1 # 0=output to console, 1=output to syslog
STARTOPTIONS= # for example -dddd

# ser's FIFO server
if [ -z "$SER_FIFO" ]; then
	SER_FIFO=/tmp/ser_fifo
fi
# period in which stats are reprinted
if [ -z "$WATCH_PERIOD" ] ; then
	WATCH_PERIOD=2
fi

# SQL config
if [ -z "$SQL_DB" ] ; then
	SQL_DB=ser
fi
if [ -z "$SQL_HOST" ] ; then
	SQL_HOST=localhost
fi
if [ -z "$SQL_USER" ] ; then
	SQL_USER=ser
fi

# the read-only user for whom password may be stored here
if [ -z "$RO_USER" ] ; then
	RO_USER=serro
fi
if [ -z "$RO_PW" ] ; then
	RO_PW=47serro11
fi

# binaries
GENHA1='gen_ha1'
MYSQL='mysql'
SER='sr'
LAST_LINE='tail -1'

# ACL name verification
VERIFY_ACL=1
ACL_GROUPS="local ld int voicemail free-pstn"

# expiration time for alias table
FOREVER='2020-05-28 21:32:15'
FOREVER_REL=1073741823

VERSION='$Revision$'
#### SQL names

# Usr Loc Table
USRLOC=location
USER_COLUMN=username
CALLID_COLUMN=callid

# subscriber table
TABLE=subscriber
REALM_COLUMN=domain
HA1_COLUMN=HA1
HA1B_COLUMN=HA1B
PASSWORD_COLUMN=password
SUBSCRIBER_COLUMN='username'
EMAIL_COLUMN=email_address
SUB_CREATED_COLUMN=datetime_created
SUB_MODIFIED_COLUMN=datetime_modified
PHP_LIB_COLUMN=phplib_id

# acl table
ACL_TABLE=grp
ACL_USER_COLUMN=username
ACL_GROUP_COLUMN=grp
ACL_MODIFIED_COLUMN=last_modified
ACL_DOMAIN_COLUMN=domain

# aliases table
A_TABLE=aliases
A_USER_COLUMN=username
A_CONTACT_COLUMN=contact
A_EXPIRES_COLUMN=expires
A_Q_COLUMN=q
A_CALLID_COLUMN=callid
A_CSEQ_COLUMN=cseq
A_LAST_MODIFIED_COLUMN=last_modified

FIFO_DBG=0

EGREP="egrep"
#===================================================================


usage() {
CMD=`basename $0`
if [ "0$VERIFY_ACL" -eq 1 ] ; then
	EXTRA_TEXT="ACL privileges are: $ACL_GROUPS"
fi
cat <<EOF
$0 $VERSION
parameter usage: 
           * subscribers *
 add <username> <password> <email> .. add a new subscriber (*)
 passwd <username> <passwd> ......... change user's password (*)
 rm <username> ...................... delete a user (*)
 mail <username> .................... send an email to a user
 alias show [<alias>] ............... show aliases
 alias rm <alias> ................... remove an alias
 alias add <alias> <uri> ............ add an aliases 

           * access control lists *
 acl show [<username>] .............. show user membership
 acl grant <username> <group> ....... grant user membership (*)
 acl revoke <username> [<group>] .... grant user membership(s) (*)

           * usrloc *
 ul show [<username>]................ show in-RAM online users
 ul rm <username> ................... delete user's UsrLoc entries
 ul add <username> <uri> ............ introduce a permanent UrLoc entry
 showdb [<username>] ................ show online users flushed in DB

           * control and diagnostics *
 moni ... show internal status      start .... start ser
 ps ..... show runnig processes 	stop ..... stop ser
 fifo ... send raw FIFO commands    restart .. restart ser
 ping <uri> .. ping a URI (OPTIONS)
 cisco_restart <uri> .. restart a Cisco phone (NOTIFY(


   Commands labeled with (*) will prompt for a MySQL password.
   If the variable PW is set, the password will not be prompted.

     $EXTRA_TEXT

EOF
}

# check the parameter if it is a valid SIP URI
# quite simplified now -- it captures just very basic
# errors
check_uri() {
	echo "$1" | $EGREP "^sip:([a-zA-Z0-9_]+@)?.*\..*" 
}


#params: none
# output: PW
prompt_pw() {
	if [ -z "$PW" ] ; then
		savetty=`stty -g`
		printf "MySql password: "
		stty -echo
    	read PW
		stty $savetty
    	echo
	fi
}

print_status() {
	echo $1 | grep "^[1-6][0-9][0-9]" > /dev/null
	if [ "$?" -eq 0 ] ; then 
		echo $1
	else
		echo "200 OK"
	fi
}

# process output from FIFO server; if everything is ok
# skip the first "ok" line and proceed to returned 
# parameters
filter_fl()
{
#	tail +2
	
	awk 'BEGIN {line=0;IGNORECASE=1;}
		{line++}
		line==1 && /^200 ok/ { next }
		{ print }'
}


fifo_cmd()
{
	if [ "0${FIFO_DBG}" -eq 1 ] ; then
		echo "entering fifo_cmd $*"
	fi
	if [ "$#" -lt 1 ]; then
		echo "ERROR: fifo_cmd must take at least command name as parameter"
		exit
	fi
	name=ser_receiver_$$
	path=/tmp/$name
	if [ ! -w $SER_FIFO ]; then
		echo "Error opening ser's FIFO $SER_FIFO"
		echo "Make sure you have line fifo=$SER_FIFO in your config"
		exit 1
	fi
	mkfifo $path
	if [ $? -ne 0 ] ; then
		echo "error opening read fifo $path"
		exit 1
	fi
	chmod a+w $path

	# construct the command now
	CMD=":$1:$name\n";
	shift
	while [ -n "$1" ] ; do
		CMD="${CMD}${1}\n"
		shift
	done
	CMD="${CMD}\n"

	trap "rm -f $path; kill 0" 2

	# start reader now so that it is ready for replies
	# immediately after a request was sent out
	cat < $path | filter_fl &

	# issue FIFO request (printf taken to deal with \n)
	printf "$CMD" > $SER_FIFO

	# wait for the reader to complete
	wait
	rm $path

	if [ "0${FIFO_DBG}" -eq 1 ] ; then
		printf "FIFO command was:\n$CMD"
	fi
}


# $1 = name $2=path $3=attempt
print_stats() {

echo "[cycle #: $3; if constant make sure server lives and fifo is on]"

cat < $2 | filter_fl &
cat > $SER_FIFO <<EOF
:version:$1

EOF
wait

cat < $2 | filter_fl &
cat > $SER_FIFO << EOF
:uptime:$1

EOF
wait
echo

echo Transaction Statistics
cat < $2 | filter_fl &
cat > $SER_FIFO <<EOF
:t_stats:$1

EOF
wait
echo

echo Stateless Server Statistics
cat < $2 | filter_fl &
cat > $SER_FIFO <<EOF
:sl_stats:$1

EOF
wait
echo

echo UsrLoc Stats
cat < $2 | filter_fl &
cat > $SER_FIFO <<EOF
:ul_stats:$1

EOF
wait
}


# input: sql query, optional mysql command-line params
sql_query() {
	# if password not yet queried, query it now
	if [ -z "$PW" ] ; then
		savetty=`stty -g`
		printf "MySql password: "
		stty -echo
    	read PW >&2
		stty $savetty
    	echo >&2
	fi
	$MYSQL $2 -h $SQL_HOST -u $SQL_USER "-p$PW" -e "$1 ;" $SQL_DB
}

# input: sql query, optional mysql command-line params
sql_ro_query() {
	$MYSQL $2 -h $SQL_HOST -u $RO_USER "-p$RO_PW" \
		-e "$1 ;" $SQL_DB
}


usrloc() {
	if [ "$#" -lt 2 ] ; then
		echo "usrloc: too few parameters"
		exit 1
	fi
	if [ "$1" = "alias" ] ; then
		UL_TABLE="$A_TABLE"
	elif [ "$1" = "ul" ] ; then
		UL_TABLE="$USRLOC"
	else
		echo "usrloc: unknown table name"
		exit 1
	fi
	shift

	case $1 in 
		show)
			if [ $# -eq 2 ] ; then
				fifo_cmd ul_show_contact $UL_TABLE $2
			elif [ $# -eq 1 ] ; then
				printf "Dumping all contacts may take long: are you sure you want to proceed? [Y|N] "
				read answer
				if [ "$answer" = "y" -o "$answer" = "Y" ] ; then
					fifo_cmd ul_dump
				fi
			else
				echo "wrong number of params for usrloc show"
				usage
				exit 1
			fi
			exit $?
			;;
		add)
			if [ $# -ne 3 ] ; then
				usage
				exit 1
			fi
			shift
			check_uri "$2"
			if [ "$?" -ne "0" ] ; then
				echo "$2 is not a valid URI"
				exit 1
			fi

			fifo_cmd ul_add "$UL_TABLE" "$1" "$2" "$FOREVER_REL" "1.00" "0"
			exit $?
			;;
		rm)
            if [ $# -ne 2 ] ; then
                usage
                exit 1
            fi
			shift
			fifo_cmd ul_rm $UL_TABLE $1

            ;;

		*)
			usage
			exit 1
			;;
	esac
}

acl() {
	case $1 in
		show)
			if [ $# -eq 2 ] ; then
				is_user $2
				if [ $? -ne 0 ] ; then
					echo non-existent user
					exit 1;
				fi
				CLAUSE=" WHERE $ACL_USER_COLUMN='$2' "
			elif [ $# -ne 1 ] ; then
				usage
				exit 1
			fi
			QUERY="select * FROM $ACL_TABLE $CLAUSE ; "
			sql_ro_query "$QUERY"

			;;

		grant)
			if [ $# -lt 3 ] ; then
				usage
				exit 1
			fi
			prompt_pw
			is_user $2
			if [ $? -ne 0 ] ; then
				echo non-existent user
				exit 1
			fi
			SIP_USER="$2"
			shift 2
			while [ $# -gt 0 ] ; do

				if [ $VERIFY_ACL -eq 1 ] ; then
					found=0
					for i in $ACL_GROUPS ; do
						if [ "$1" = "$i" ] ; then
							found=1
							break
						fi
					done	
					if [ $found -eq 0 ] ; then
						echo "Invalid privilege: $1 ignored"
						shift
						continue
					fi
				fi

        		QUERY="insert into $ACL_TABLE \
                	($ACL_USER_COLUMN,$ACL_GROUP_COLUMN,$ACL_MODIFIED_COLUMN, $ACL_DOMAIN_COLUMN ) \
                	values ('$SIP_USER','$1', now(), '$SIP_DOMAIN' );"
				sql_query "$QUERY"
				if [ $? -ne 0 ] ; then
					echo "SQL Error"
					exit 1
				fi
				shift
			done

			$0 acl show $SIP_USER

			;;

		revoke)
			if [ $# -eq 3 ] ; then
				CLAUSE=" and $ACL_GROUP_COLUMN='$3' "
			elif [ $# -ne 2 ] ; then
				usage
				exit 1
			fi	

			QUERY="delete from $ACL_TABLE where \
				$ACL_TABLE.$ACL_USER_COLUMN='$2' $CLAUSE"
			sql_query "$QUERY"

			$0 acl show $2

			;;

		*)
			usage
			exit 1
			;;
	esac
}

# params: user
# output: false if exists, true otherwise
is_user() {
	QUERY="select count(*) from $TABLE \
		where $SUBSCRIBER_COLUMN='$1' and $REALM_COLUMN='$SIP_DOMAIN';"
	CNT=`sql_ro_query "$QUERY" | grep -v ERROR | $LAST_LINE`
	if [ "0$CNT" -eq 0 ] ; then
		false
	else
		true
	fi

}


# params: user, password
# output: HA1, HA1B
credentials()
{
	HA1=`$GENHA1 $1 $SIP_DOMAIN $2`
	if [ $? -ne 0 ] ; then
		echo "HA1 calculation failed"
		exit 1
	fi
	HA1B=`$GENHA1 "$1@$SIP_DOMAIN" $SIP_DOMAIN $2`
	if [ $? -ne 0 ] ; then
		echo "HA1B calculation failed"
		exit 1
	fi
}

#================================================================

if [ -z "$SIP_DOMAIN" ] ; then
	echo
	echo "You need to set environment variable SIP_DOMAIN (e.g. to 'foobar.com') first"
	echo
#	This confuses new users cause its easy to miss the information above
#	usage
	exit 1
fi

# if the script calls itself ...
export PW

case $1 in

	start)
		DIR=`dirname $0`
		echo
		printf "Starting SER : "
		if [ -r $PID_FILE ] ; then
			echo "PID file exists! ($PID_FILE) already running?"
			exit 1
		else
			if [ $SYSLOG = 1 ] ; then
				$DIR/ser -P $PID_FILE $STARTOPTIONS 1>/dev/null 2>/dev/null
			else
			 	$DIR/ser -P $PID_FILE -E $STARTOPTIONS
			fi
			sleep 1
			echo "started pid(`cat $PID_FILE`)"
		fi
		exit 0
	;;

	stop)
		printf "Stopping SER : "
		if [ -r $PID_FILE ] ; then
			kill `cat $PID_FILE`
			echo "stopped"
		else
			echo No PID file found!
			exit 1
		fi
		exit 0
	;;

	restart)
	        DIR=`dirname $0`
		printf "Stopping SER : "
		if [ -r $PID_FILE ] ; then
			kill `cat $PID_FILE`
			echo "stopped"
		else
			echo No PID file found! SER problably not running
			exit 1
		fi
		sleep 2
		printf "Starting SER : "
		if [ -r $PID_FILE ] ; then
			echo "PID file exists! ($PID_FILE) already running?"
			exit 1
		else
			if [ $SYSLOG = 1 ] ; then
			        $DIR/ser -P $PID_FILE $STARTOPTIONS 1>/dev/null 2>/dev/null
			else
			        $DIR/ser -P $PID_FILE -E $STARTOPTIONS
			fi
			sleep 1
			echo "started pid(`cat $PID_FILE`)"
		fi
		exit 0
	;;

	passwd)
		if [ $# -ne 3 ] ; then
			usage
			exit 1
		fi
		shift
		credentials $1 $2
		prompt_pw

		is_user $1
		if [ $? -ne 0 ] ; then
			echo non-existent user
			exit 1
		fi
		QUERY="update $TABLE \
			set $HA1_COLUMN='$HA1', $HA1B_COLUMN='$HA1B', $PASSWORD_COLUMN='$2' \
			, $SUB_MODIFIED_COLUMN=now() \
			WHERE $SUBSCRIBER_COLUMN='$1' and $REALM_COLUMN='$SIP_DOMAIN';"
		sql_query "$QUERY"
		if [ $? -ne 0 ] ; then
			echo "password change failed"
		else
			echo "password change succeeded"
		fi

		;;		
			
		

	add)
		if [ $# -ne 4 ] ; then
			usage
			exit 1
		fi
		shift
		credentials $1 $2
		prompt_pw
        is_user $1
        if [ $? -eq 0 ] ; then
            echo user already exists
            exit 1
        fi

		QUERY="insert into $TABLE \
				($SUBSCRIBER_COLUMN,$REALM_COLUMN,$HA1_COLUMN,\
				$HA1B_COLUMN,$PASSWORD_COLUMN,$EMAIL_COLUMN, $SUB_CREATED_COLUMN,  \
				$PHP_LIB_COLUMN ) \
				values ('$1','$SIP_DOMAIN','$HA1','$HA1B','$2', '$3', now(), '$HA1' );";
		sql_query "$QUERY"
		if [ $? -ne 0 ] ; then
			echo "introducing a new user to the database failed"
		else
			echo "new user added"
		fi

		;;

	monitor|console|moni|con)
		name=ser_receiver_$$
		path=/tmp/$name
		if [ ! -w $SER_FIFO ]; then
			echo "Error opening ser's FIFO $SER_FIFO"
			echo "Make sure you have line fifo=$SER_FIFO in your config"
			exit 1
		fi
		mkfifo $path
		if [ $? -ne 0 ] ; then
			echo "error opening read fifo $path"
			exit 1
		fi
		trap "rm $path;  clear; echo sc monitor ^C-ed; exit 1" 2
		attempt=0
		clear
		while [ 1 -eq 1 ]; do
			attempt=`expr $attempt + 1`
			#clear
			tput cup 0 0
			print_stats $name $path $attempt
			sleep $WATCH_PERIOD
		done
		rm $path
		exit 0
		;;

	mail)
		if [ $# -ne 2 ] ; then
			usage
			exit 1
		fi
		shift
		QUERY="select $TABLE.$EMAIL_COLUMN from $TABLE where  \
			$TABLE.$SUBSCRIBER_COLUMN='$1'"
		EA=`sql_ro_query "$QUERY" "-B" | grep -v ERROR | $LAST_LINE`
		if [ $? -ne 0 ] ; then
			echo "MySql query failed"
			exit 1
		fi
		echo "Write email to $1: $EA now ..."
		mail -s "Message from $SIP_DOMAIN SIP admin" $EA
		if [ $? -eq 0 ] ; then
			echo message sent
		else
			echo sending message failed
		fi

		;;

	alias|ul)
		usrloc "$@"
		;;

	online)
		fifo_cmd ul_dump |grep aor| awk '{print $3}' | sort | sort -mu
		exit $?
		;;


	showdb|userdb)
		if [ $# -eq 2 ] ; then
			is_user $2
			if [ $? -ne 0 ] ; then
				echo non-existent user
				exit 1;
			fi
		elif [ $# -ne 1  ] ; then
			usage
			exit 1
		fi

		shift

		QUERY1="select $TABLE.$EMAIL_COLUMN from $TABLE where  \
			$TABLE.$SUBSCRIBER_COLUMN='$1'"
		QUERY2="select $USRLOC.* from $USRLOC where \
			$USRLOC.$USER_COLUMN='$1' order by expires desc"
		QUERY3="select $USRLOC.$USER_COLUMN, $TABLE.$EMAIL_COLUMN, $USRLOC.$CALLID_COLUMN \
			from $TABLE, $USRLOC where  \
			$TABLE.$SUBSCRIBER_COLUMN=$USRLOC.$USER_COLUMN  order by $USRLOC.$USER_COLUMN" 

		if [ $# -eq 1 ] ; then
			sql_ro_query "$QUERY1"
			sql_ro_query "$QUERY2"
		else
			sql_ro_query "$QUERY3"
		fi
		echo "Note: Due to usage of cache, server's list " \
			"may differ from DB list."

		;;

	rm)
        if [ $# -ne 2 ] ; then
            usage
            exit 1
        fi
		shift 
		prompt_pw 

        is_user $1
        if [ $? -ne 0 ] ; then
            echo non-existent user
            exit 1
        fi

		# begin with remove all user's privileges
		$0 acl revoke $1  > /dev/null 2>&1

		# destroy the user now
        QUERY="delete from $TABLE where $TABLE.$SUBSCRIBER_COLUMN='$1'"
		sql_query "$QUERY"

		# and also all his contacts
		$0 ul rm $1   > /dev/null 2>&1
        ;;
			
	ps)
		fifo_cmd ps
		;;

	acl)
		shift
		acl "$@"
		;;

	fifo)
		shift
		fifo_cmd "$@"
		;;

	ping)
		# error handling is hacked -- filter_fl should not
		# consume positive status -- that should be done by
		# calling app
		if [ "$#" -ne 2 ] ; then	
			usage
			exit 1
		fi
		RET=`fifo_cmd t_uac_dlg OPTIONS "$2" "." \
			"From: sip:daemon@$SIP_DOMAIN" \
			"To: <$2>" "Contact: <sip:daemon@!!>" "." "." \
			| head -1 ` 
		print_status $RET
		;;

	cisco_restart)
		if [ "$#" -ne 2 ] ; then	
			usage
			exit 1
		fi
		RET=`fifo_cmd t_uac_dlg NOTIFY "$2" "." \
			"From: sip:daemon@$SIP_DOMAIN" \
			"To: <$2>" "Event: check-sync" \
			"Contact: <sip:daemon@!!>" "." "." |
			head -1 `
		print_status $RET
		;;

	version)
		echo  "$0 $VERSION"
		;;
		
	*)
		usage
		exit 1
		;;

esac