#!/bin/sh set -e # make data writable both from kannel and designated shell accounts umask 0007 vardir=/var/lib/kannel/hello sbindir=/usr/local/sbin exit0() { [ -n "$1" ] && echo "$1" exit 0 } exit1() { [ -z "$1" ] || echo "Error: $1" || echo "Internal error!" response="${1+Error: $1}${-Internal error!}" # FIXME: pass error messages via stderr (not stdout) # echo >&2 "$response" echo "$response" exit 1 } findobjects() { set -e type="$1"; [ -n "$type" ] || return1 "Internal error (findobjects: empty type)!"; shift limit="$@" regex="$(echo "$limit" | perl -ne '/^\/(.+)\/$/ and print $1')" args="$(echo "$limit" | perl -ne 's/.*?([a-z0-9]+).*?/ $1/g and print')" [ -d "$vardir/$type" ] || exit0 if [ -z "$*" ]; then cd "$vardir/$type" && find -mindepth 1 -maxdepth 1 -type d -printf '%f\n' elif [ -n "$regex" ]; then cd "$vardir/$type" && find -mindepth 1 -maxdepth 1 -type d -regex "$regex" elif [ -n $args ]; then args_multiline="$(echo "$args" | perl -pe 's/[[:space:]]+/\n/g')" cd "$vardir/$type" && ! find -mindepth 1 -maxdepth 1 -type d -printf '%f\n' \ | grep -Fx "$args_multiline" || true else exit1 "Internal error (findobjects: illegal limit)!" fi } finditems() { set -e type="$1"; [ -n "$type" ] || exit1 "Internal error (finditems: empty type)!"; shift name="$1"; [ -n "$name" ] || exit1 "Internal error (finditems: empty name)!"; shift limit="$@" regex="$(echo "$limit" | perl -ne '/^\/(.+)\/$/ and print $1')" args="$(echo "$limit" | perl -ne 's/.*?([a-z0-9]+).*?/ $1/g and print')" dir="$type/$name" [ -d "$vardir/$dir" ] || exit0 if [ -z "$*" ]; then cd "$vardir/$dir" && find -mindepth 1 -maxdepth 1 -type f -printf '%f\n' elif [ -n "$regex" ]; then cd "$vardir/$dir" && find -mindepth 1 -maxdepth 1 -type f -regex "$regex" elif [ -n $args ]; then args_multiline="$(echo "$args" | perl -pe 's/[[:space:]]+/\n/g')" cd "$vardir/$dir" && ! find -mindepth 1 -maxdepth 1 -type f -printf '%f\n' \ | grep -Fx "$args_multiline" || true else exit1 "Internal error (finditems: illegal limit)!" fi } getitem() { set -e type="$1"; [ -n "$type" ] || exit1 "Internal error (getitem: empty type)!"; shift name="$1"; [ -n "$name" ] || exit1 "Internal error (getitem: empty name)!"; shift item="$1"; [ -n "$item" ] || exit1 "Internal error (getitem: empty item)!"; shift file="$type/$name/$item" [ ! -s "$vardir/$file" ] || cat "$vardir/$file" } additem() { set -e type="$1"; [ -n "$type" ] || exit1 "Internal error (additem: empty type)!"; shift name="$1"; [ -n "$name" ] || exit1 "Internal error (additem: empty name)!"; shift item="$1"; [ -n "$item" ] || exit1 "Internal error (additem: empty item)!"; shift data="$*"; [ -n "$data" ] || exit1 "Internal error (additem: empty data)!"; shift data="$(echo "$data" | perl -0 -pe 's/\s+/ /g; s/^\s+//; s/\s+$//')" truename="$(findobjects "$type" "$name")" || exit1 "$truename" [ -n "$truename" ] || exit1 "No $type named \"$name\"!" file="$type/$truename/$item" # [ ! -s "$vardir/$file" ] || linecount="$(grep -c . "$vardir/$file")" # echo "$data" >> "$vardir/$file" # chgrp --reference="$vardir" "$vardir/$file" # echo "$data${linecount+ (on top of $linecount other values)}" [ ! -s "$vardir/$file" ] || linecount="$(grep -c . "$vardir/$file")" echo "$data" > "$vardir/$file" # chgrp --reference="$vardir" "$vardir/$file" echo "$data" } addobject() { set -e type="$1"; [ -n "$type" ] || exit1 "Internal error (addobject: empty type)!"; shift name="$1"; [ -n "$name" ] || exit1 "Internal error (addobject: empty name)!"; shift fallback="$1"; [ -n "$fallback" ] || exit1 "Internal error (addobject: empty fallback)!"; shift [ -z "$*" ] || exit1 "Internal error (addobject: too many options)!" case "$fallback" in keep|replace|fail) :;; *) exit1 "Internal error (addobject: unknown fallback)!";; esac # if [ -e "$vardir/$type/$name" ]; then oldname="$(findobjects "$type" "$name")" || exit1 "$oldname" if [ -n "$oldname" ]; then case "$fallback" in keep) action="kept" ;; replace) delopbject "$type" "$oldname" ;; # TODO rename) # TODO renameopbject "$type" "$oldname" # TODO ;; fail) exit1 "Adding $type failed ($name already exists)!" ;; esac else mkdir -p "$vardir/$type/$name" # chgrp -R --reference="$vardir" "$vardir/$type" fi } cmd="$1"; shift case "$cmd" in add|create) type="$1"; [ -n "$type" ] || exit1 "Internal error (empty type)!"; shift name="$1"; [ -n "$name" ] || exit1 "Internal error (empty name)!"; shift oldname="$(findobjects "$type" "$name")" || exit1 "$oldname" case "$type" in default) case "$name" in session|prefix) item="$name" data="$1" ;; *) exit1 "unknown default \"$1\"" ;; esac str=$(addobject "$type" "$name" keep) || exit1 "$str" str=$(additem "$type" "$name" "$item" "$data") || exit1 "$str" response="Added $type $name $data" ;; user) session="$(getitem "default" "session" "session")" || exit1 "Internal error: $session" [ -z "$session" ] || session_group="$(getitem "session" "$session" "group")" || exit1 "Internal error: $session_group" plural= for chunk in $@; do case $chunk in +[[:digit:]]*) [ -z "$phone" ] || exit1 "Too many phone numbers (use only a single number, e.g. 40843136 or +4540843136)!" phone="$chunk" ;; [[:digit:]][[:digit:]][[:digit:]][[:digit:]]*) [ -z "$phone" ] || exit1 "Too many phone numbers (use only a single number, e.g. 40843136 or +4540843136)!" [ -z "$session" ] || prefix="$(getitem "session" "$session" "prefix")" || exit1 "Internal error: $prefix" [ -n "$prefix" ] || exit1 "Missing prefix (use international phone numbering, e.g. +4540843136${session+, or add default prefix to session $session})!" # TODO: optionally validate $phone_min and $phone_max phone="$prefix$chunk" ;; [[:alpha:]][[:alnum:]]*) [ -z "$groups" ] || plural=true groups="${groups+$groups }$chunk" ;; *) exit1 "Internal error (unknown user item \"$1\")!" ;; esac done response="Added $type $name" fallback=fail if [ -z "$oldname" ]; then [ -n "$phone" ] || exit1 "Missing phone number (required)!" else [ -n "$phone$groups" ] || exit1 "No items to update (include one or more of phone number and groups)!" fallback=keep response="Updated $type $name" fi str=$(addobject "$type" "$name" "$fallback") || exit1 "$str" [ -z "$phone" ] || dummy="$(additem "$type" "$name" "phone" "$phone")" # TODO: distinguish session group from included ones (i.e. mention explicitly if added) [ -z "$session_group" ] || groups="$session_group $groups" for group in $groups; do # TODO: strip existing, duplicate and session groups, and mention stripping in response dummy="$(additem "$type" "$name" "group" "$group")" done if [ -n "$phone" ]; then if [ -n "$groups" ]; then response="$response with phone $phone and group${plural+s} $groups" else response="$response with phone $phone" fi elif [ -n "$groups" ]; then response="$response with group${plural+s} $groups" fi ;; group) for chunk in $@; do case $chunk in [[:alpha:]][[:alnum:]]*) [ -z "$session" ] || exit1 "Too many sessions (a group can be only in a single session)!" session="$chunk" ;; *) exit1 "Internal error (unknown group item \"$1\")!" ;; esac done response="Added $type $name" fallback=fail if [ -n "$oldname" ]; then [ -n "$session" ] || exit1 "Group already exists (and no session name to update was included)!" fallback=keep response="Updated $type $name" fi str=$(addobject "$type" "$name" "$fallback") || exit1 "$str" [ -z "$session" ] || dummy="$(additem "$type" "$name" "session" "$session")" [ -z "$session" ] || response="$response with session $session" ;; session) for chunk in $@; do case $chunk in +[[:digit:]]*) [ -z "$prefix" ] || exit1 "Too many country prefixes (use only one, e.g. +45)!" prefix="$chunk" ;; [[:alpha:]][[:alnum:]]*) [ -z "$group" ] || exit1 "Too many default groups (use only one)!" # FIXME: fail if group does not exist group="$chunk" ;; *) exit1 "Internal error (unknown session item \"$1\")!" ;; esac done # TODO: distinguish default prefix from included one (i.e. mention explicitly if added) [ -n "$prefix" ] || prefix="$(getitem "default" "prefix" "prefix")" || exit1 "Internal error: $prefix" response="Added $type $name" fallback=fail if [ -n "$oldname" ]; then [ -n "$prefix$group" ] || exit1 "Session already exists (and no default group or country prefix to add/update was included)!" fallback=keep response="Updated $type $name" fi str=$(addobject "$type" "$name" "$fallback") || exit1 "$str" [ -z "$prefix" ] || dummy="$(additem "$type" "$name" "prefix" "$prefix")" [ -z "$group" ] || dummy="$(additem "$type" "$name" "group" "$group")" if [ -n "$prefix" ]; then if [ -n "$group" ]; then response="$response with default country prefix $prefix and default group $group" else response="$response with default country prefix $prefix" fi elif [ -n "$group" ]; then response="$response with default group $group" fi ;; *) exit1 "Cannot add \"$1\" (try \"/help\" to see supported commands)!" ;; esac exit0 "$response" ;; del|delete|remove) case "$1" in default|user|group|session) type="$1"; shift name="$1"; [ -n "$name" ] || exit1 "Internal error (empty name)!"; shift [ -z "$*" ] || exit1 "Internal error (too many options)!" oldname="$(findobjects "$type" "$name")" || exit1 "Internal error${oldname+: $oldname}" [ -n "$oldname" ] || exit1 "No $type named \"$name\"!" items="$(finditems "$type" "$name")" || exit1 "Internal error${items+: $items}" # TODO: find user by phone number for item in $items; do rm "$vardir/$type/$name/$item" || exit1 "Internal error during $type $name item removal!" done # FIXME: remove items of other objects referencing this object rmdir "$vardir/$type/$name" || exit1 "Internal error during $type $name object removal!" exit0 "Deleted $type: $name." ;; *) exit1 "Cannot delete \"$1\" (try \"/help\" to see supported commands)!" ;; esac ;; list|show|view) case "$1" in default|user|group|session) type="$1"; shift response="$(listobject "$type" "$@")" || exit1 "Failed listing $type${response+: $response}" exit0 "Listing $type: $response." ;; *) exit1 "Cannot list \"$1\" (try \"/help\" to see supported commands)!" ;; esac ;; help) cat <