summaryrefslogtreecommitdiff
path: root/localsmsadmin
blob: 9628ef7fd56f8d7a22b235fd0a886e2ddf0044ec (plain)
  1. #!/bin/sh
  2. set -e
  3. # TODO: convert data handling to DBD::AnyData
  4. # make data writable both from kannel and designated shell accounts
  5. umask 0007
  6. vardir=/var/lib/kannel/hello
  7. sbindir=/usr/local/sbin
  8. exit0() {
  9. [ -n "$1" ] && echo "$1"
  10. exit 0
  11. }
  12. exit1() {
  13. response="${1:+Error: }${1:-Internal error!}"
  14. # FIXME: pass error messages via stderr (not stdout)
  15. # echo >&2 "$response"
  16. echo "$response"
  17. exit 1
  18. }
  19. findobjects() {
  20. set -e
  21. type="$1"; [ -n "$type" ] || return1 "Internal error (findobjects: empty type)!"; shift
  22. limit="$@"
  23. regex="$(echo "$limit" | perl -ne '/^\/(.+)\/$/ and print $1')"
  24. args="$(echo "$limit" | perl -ne 's/.*?([a-z0-9]+).*?/ $1/g and print')"
  25. [ -d "$vardir/$type" ] || exit0
  26. if [ -z "$*" ]; then
  27. cd "$vardir/$type" && find -mindepth 1 -maxdepth 1 -type d -printf '%f\n'
  28. elif [ -n "$regex" ]; then
  29. cd "$vardir/$type" && find -mindepth 1 -maxdepth 1 -type d -regex "$regex"
  30. elif [ -n "$args" ]; then
  31. args_multiline="$(echo "$args" | perl -pe 's/[[:space:]]+/\n/g')"
  32. cd "$vardir/$type" && ! find -mindepth 1 -maxdepth 1 -type d -printf '%f\n' \
  33. | grep -Fx "$args_multiline" || true
  34. else
  35. exit1 "Internal error (findobjects: illegal limit)!"
  36. fi
  37. }
  38. finditems() {
  39. set -e
  40. type="$1"; [ -n "$type" ] || exit1 "Internal error (finditems: empty type)!"; shift
  41. name="$1"; [ -n "$name" ] || exit1 "Internal error (finditems: empty name)!"; shift
  42. limit="$@"
  43. regex="$(echo "$limit" | perl -ne '/^\/(.+)\/$/ and print $1')"
  44. args="$(echo "$limit" | perl -ne 's/.*?([a-z0-9]+).*?/ $1/g and print')"
  45. dir="$type/$name"
  46. [ -d "$vardir/$dir" ] || exit0
  47. if [ -z "$*" ]; then
  48. cd "$vardir/$dir" && find -mindepth 1 -maxdepth 1 -type f -printf '%f\n'
  49. elif [ -n "$regex" ]; then
  50. cd "$vardir/$dir" && find -mindepth 1 -maxdepth 1 -type f -regex "$regex"
  51. elif [ -n $args ]; then
  52. args_multiline="$(echo "$args" | perl -pe 's/[[:space:]]+/\n/g')"
  53. cd "$vardir/$dir" && ! find -mindepth 1 -maxdepth 1 -type f -printf '%f\n' \
  54. | grep -Fx "$args_multiline" || true
  55. else
  56. exit1 "Internal error (finditems: illegal limit)!"
  57. fi
  58. }
  59. getitem() {
  60. set -e
  61. set -x
  62. type="$1"; [ -n "$type" ] || exit1 "Internal error (getitem: empty type)!"; shift
  63. name="$1"; [ -n "$name" ] || exit1 "Internal error (getitem: empty name)!"; shift
  64. item="$1"; [ -n "$item" ] || exit1 "Internal error (getitem: empty item)!"; shift
  65. file="$type/$name/$item"
  66. [ ! -s "$vardir/$file" ] || cat "$vardir/$file"
  67. }
  68. additem() {
  69. set -e
  70. type="$1"; [ -n "$type" ] || exit1 "Internal error (additem: empty type)!"; shift
  71. name="$1"; [ -n "$name" ] || exit1 "Internal error (additem: empty name)!"; shift
  72. item="$1"; [ -n "$item" ] || exit1 "Internal error (additem: empty item)!"; shift
  73. data="$*"; [ -n "$data" ] || exit1 "Internal error (additem: empty data)!"; shift
  74. data="$(echo "$data" | perl -0 -pe 's/\s+/ /g; s/^\s+//; s/\s+$//')"
  75. truename="$(findobjects "$type" "$name")" || exit1 "$truename"
  76. [ -n "$truename" ] || exit1 "No $type named \"$name\"!"
  77. file="$type/$truename/$item"
  78. # [ ! -s "$vardir/$file" ] || linecount="$(grep -c . "$vardir/$file")"
  79. # echo "$data" >> "$vardir/$file"
  80. # chgrp --reference="$vardir" "$vardir/$file"
  81. # echo "$data${linecount+ (on top of $linecount other values)}"
  82. [ ! -s "$vardir/$file" ] || linecount="$(grep -c . "$vardir/$file")"
  83. echo "$data" > "$vardir/$file"
  84. # chgrp --reference="$vardir" "$vardir/$file"
  85. echo "$data"
  86. }
  87. addobject() {
  88. set -e
  89. type="$1"; [ -n "$type" ] || exit1 "Internal error (addobject: empty type)!"; shift
  90. name="$1"; [ -n "$name" ] || exit1 "Internal error (addobject: empty name)!"; shift
  91. fallback="$1"; [ -n "$fallback" ] || exit1 "Internal error (addobject: empty fallback)!"; shift
  92. [ -z "$*" ] || exit1 "Internal error (addobject: too many options)!"
  93. case "$fallback" in
  94. keep|replace|fail) :;;
  95. *) exit1 "Internal error (addobject: unknown fallback)!";;
  96. esac
  97. # if [ -e "$vardir/$type/$name" ]; then
  98. oldname="$(findobjects "$type" "$name")" || exit1 "$oldname"
  99. if [ -n "$oldname" ]; then
  100. case "$fallback" in
  101. keep)
  102. action="kept"
  103. ;;
  104. replace)
  105. delopbject "$type" "$oldname"
  106. ;;
  107. # TODO rename)
  108. # TODO renameopbject "$type" "$oldname"
  109. # TODO ;;
  110. fail)
  111. exit1 "Adding $type failed ($name already exists)!"
  112. ;;
  113. esac
  114. else
  115. mkdir -p "$vardir/$type/$name"
  116. # chgrp -R --reference="$vardir" "$vardir/$type"
  117. fi
  118. }
  119. cmd="$1"; shift
  120. case "$cmd" in
  121. add|create)
  122. type="$1"; [ -n "$type" ] || exit1 "Internal error (empty type)!"; shift
  123. case "$type" in
  124. help)
  125. cat <<EOF
  126. Usage:
  127. /add user NICK [+PREFIX]NUMBER [GROUP ...]
  128. /add group GROUP [USER ...]
  129. /add session SESSION [PREFIX] [GROUP]
  130. /add default prefix|session VALUE
  131. Add or update object and embedded items
  132. EOF
  133. exit 0
  134. ;;
  135. esac
  136. name="$1"; [ -n "$name" ] || exit1 "Internal error (empty name)!"; shift
  137. oldname="$(findobjects "$type" "$name")" || exit1 "$oldname"
  138. case "$type" in
  139. default)
  140. case "$name" in
  141. session|prefix)
  142. item="$name"
  143. data="$1"
  144. ;;
  145. *)
  146. exit1 "unknown default \"$1\""
  147. ;;
  148. esac
  149. str=$(addobject "$type" "$name" keep) || exit1 "$str"
  150. str=$(additem "$type" "$name" "$item" "$data") || exit1 "$str"
  151. response="Added $type $name $data"
  152. ;;
  153. user)
  154. session="$(getitem "default" "session" "session")" || exit1 "Internal error: $session"
  155. [ -z "$session" ] || session_group="$(getitem "session" "$session" "group")" || exit1 "Internal error: $session_group"
  156. plural=
  157. for chunk in $@; do
  158. case $chunk in
  159. +[[:digit:]]*)
  160. [ -z "$phone" ] || exit1 "Too many phone numbers (use only a single number, e.g. 40843136 or +4540843136)!"
  161. phone="$chunk"
  162. ;;
  163. [[:digit:]][[:digit:]][[:digit:]][[:digit:]]*)
  164. [ -z "$phone" ] || exit1 "Too many phone numbers (use only a single number, e.g. 40843136 or +4540843136)!"
  165. [ -z "$session" ] || prefix="$(getitem "session" "$session" "prefix")" || exit1 "Internal error: $prefix"
  166. [ -n "$prefix" ] || exit1 "Missing prefix (use international phone numbering, e.g. +4540843136${session+, or add default prefix to session $session})!"
  167. # TODO: optionally validate $phone_min and $phone_max
  168. phone="$prefix$chunk"
  169. ;;
  170. [[:alpha:]][[:alnum:]]*)
  171. [ -z "$groups" ] || plural=true
  172. groups="${groups+$groups }$chunk"
  173. ;;
  174. *)
  175. exit1 "Internal error (unknown user item \"$1\")!"
  176. ;;
  177. esac
  178. done
  179. response="Added $type $name"
  180. fallback=fail
  181. if [ -z "$oldname" ]; then
  182. [ -n "$phone" ] || exit1 "Missing phone number (required)!"
  183. else
  184. [ -n "$phone$groups" ] || exit1 "No items to update (include one or more of phone number and groups)!"
  185. fallback=keep
  186. response="Updated $type $name"
  187. fi
  188. str=$(addobject "$type" "$name" "$fallback") || exit1 "$str"
  189. [ -z "$phone" ] || dummy="$(additem "$type" "$name" "phone" "$phone")"
  190. # TODO: distinguish session group from included ones (i.e. mention explicitly if added)
  191. [ -z "$session_group" ] || groups="$session_group $groups"
  192. for group in $groups; do
  193. # TODO: strip existing, duplicate and session groups, and mention stripping in response
  194. dummy="$(additem "$type" "$name" "group" "$group")"
  195. done
  196. if [ -n "$phone" ]; then
  197. if [ -n "$groups" ]; then
  198. response="$response with phone $phone and group${plural+s} $groups"
  199. else
  200. response="$response with phone $phone"
  201. fi
  202. elif [ -n "$groups" ]; then
  203. response="$response with group${plural+s} $groups"
  204. fi
  205. ;;
  206. group)
  207. for chunk in $@; do
  208. case $chunk in
  209. [[:alpha:]][[:alnum:]]*)
  210. [ -z "$session" ] || exit1 "Too many sessions (a group can be only in a single session)!"
  211. session="$chunk"
  212. ;;
  213. *)
  214. exit1 "Internal error (unknown group item \"$1\")!"
  215. ;;
  216. esac
  217. done
  218. response="Added $type $name"
  219. fallback=fail
  220. if [ -n "$oldname" ]; then
  221. [ -n "$session" ] || exit1 "Group already exists (and no session name to update was included)!"
  222. fallback=keep
  223. response="Updated $type $name"
  224. fi
  225. str=$(addobject "$type" "$name" "$fallback") || exit1 "$str"
  226. [ -z "$session" ] || dummy="$(additem "$type" "$name" "session" "$session")"
  227. [ -z "$session" ] || response="$response with session $session"
  228. ;;
  229. session)
  230. for chunk in $@; do
  231. case $chunk in
  232. +[[:digit:]]*)
  233. [ -z "$prefix" ] || exit1 "Too many country prefixes (use only one, e.g. +45)!"
  234. prefix="$chunk"
  235. ;;
  236. [[:alpha:]][[:alnum:]]*)
  237. [ -z "$group" ] || exit1 "Too many default groups (use only one)!"
  238. # FIXME: fail if group does not exist
  239. group="$chunk"
  240. ;;
  241. *)
  242. exit1 "Internal error (unknown session item \"$1\")!"
  243. ;;
  244. esac
  245. done
  246. # TODO: distinguish default prefix from included one (i.e. mention explicitly if added)
  247. [ -n "$prefix" ] || prefix="$(getitem "default" "prefix" "prefix")" || exit1 "Internal error: $prefix"
  248. response="Added $type $name"
  249. fallback=fail
  250. if [ -n "$oldname" ]; then
  251. [ -n "$prefix$group" ] || exit1 "Session already exists (and no default group or country prefix to add/update was included)!"
  252. fallback=keep
  253. response="Updated $type $name"
  254. fi
  255. str=$(addobject "$type" "$name" "$fallback") || exit1 "$str"
  256. [ -z "$prefix" ] || dummy="$(additem "$type" "$name" "prefix" "$prefix")"
  257. [ -z "$group" ] || dummy="$(additem "$type" "$name" "group" "$group")"
  258. if [ -n "$prefix" ]; then
  259. if [ -n "$group" ]; then
  260. response="$response with default country prefix $prefix and default group $group"
  261. else
  262. response="$response with default country prefix $prefix"
  263. fi
  264. elif [ -n "$group" ]; then
  265. response="$response with default group $group"
  266. fi
  267. ;;
  268. *)
  269. exit1 "Cannot add \"$1\" (try \"/help\" to see supported commands)!"
  270. ;;
  271. esac
  272. exit0 "$response"
  273. ;;
  274. del|delete|remove)
  275. case "$1" in
  276. help)
  277. cat <<EOF
  278. Usage:
  279. /del user NICK|NUMBER
  280. /del group GROUP
  281. /del session SESSION
  282. /del default prefix|session
  283. Remove object and embedded items
  284. EOF
  285. exit 0
  286. ;;
  287. default|user|group|session)
  288. type="$1"; shift
  289. name="$1"; [ -n "$name" ] || exit1 "Internal error (empty name)!"; shift
  290. [ -z "$*" ] || exit1 "Internal error (too many options)!"
  291. oldname="$(findobjects "$type" "$name")" || exit1 "Internal error${oldname:+ $oldname}"
  292. [ -n "$oldname" ] || exit1 "No $type named \"$name\"!"
  293. items="$(finditems "$type" "$name")" || exit1 "Internal error${items:+ $items}"
  294. # TODO: find user by phone number
  295. for item in $items; do
  296. rm "$vardir/$type/$name/$item" || exit1 "Internal error during $type $name item removal!"
  297. done
  298. # FIXME: remove items of other objects referencing this object
  299. rmdir "$vardir/$type/$name" || exit1 "Internal error during $type $name object removal!"
  300. exit0 "Deleted $type: $name."
  301. ;;
  302. *)
  303. exit1 "Cannot delete \"$1\" (try \"/help\" to see supported commands)!"
  304. ;;
  305. esac
  306. ;;
  307. find)
  308. case "$1" in
  309. help)
  310. cat <<EOF
  311. Usage:
  312. /find user keyword
  313. /find group keyword
  314. /find session keyword
  315. /find default keyword
  316. Find and return name of object if it exist
  317. EOF
  318. exit 0
  319. ;;
  320. default|user|group|session)
  321. type="$1"; shift
  322. name="$1"; [ -n "$name" ] || exit1 "Internal error (empty name)!"; shift
  323. [ -z "$*" ] || exit1 "Internal error (too many options)!"
  324. oldname="$(findobjects "$type" "$name")" || exit1 "Internal error${oldname:+ $oldname}"
  325. [ -n "$oldname" ] || exit1 "No $type named \"$name\"!"
  326. exit0 "$name"
  327. ;;
  328. *)
  329. exit1 "Cannot find \"$1\" (try \"/help\" to see supported commands)!"
  330. ;;
  331. esac
  332. ;;
  333. mission)
  334. case "$1" in
  335. help)
  336. cat <<EOF
  337. Usage:
  338. /mission AGENT @VICTIM msg...
  339. Send AGENT contact info on VICTIM.
  340. When AGENT responds "done", msg is sent on behalf of VICTIM.
  341. EOF
  342. #TODO: Store mission as an object:
  343. # Has an owner
  344. # Can have subscribers who get progress info
  345. # msg is stored in mission, is optional, and originator is mission owner (i.e. sending on behalf of victim is optional)
  346. exit 0
  347. ;;
  348. *)
  349. agent="$1"; [ -n "$agent" ] || exit1 "Missing agent (try \"/mission help\" for usage info)!"; shift
  350. #FIXME: strip leading @ from victim
  351. victim="$1"; [ -n "$victim" ] || exit1 "Missing victim (try \"/mission help\" for usage info)!"; shift
  352. [ -n "$*" ] || exit1 "Missing message (try \"/mission help\" for usage info)!"
  353. agentname="$(findobjects user "$agent")"; [ -n "$agentname" ] || exit1 "Unknown agent \"$agent\" (try \"/mission help\" for usage info)!"
  354. agentphone="$(getitem user "$agent" phone)"; [ -n "$agentphone" ] || exit1 "Failed resolving agent phone number."
  355. victimname="$(findobjects user "$victim")"; [ -n "$victimname" ] || exit1 "Unknown victim \"$victim\" (try \"/mission help\" for usage info)!"
  356. victimphone="$(getitem user "$victim" phone)"; [ -n "$victimphone" ] || exit1 "Failed resolving victim phone number."
  357. task="Contact $victimname ($victimphone). When done send text: done"
  358. str=$(additem user "$agentname" "pendingmission" "@$victimname $*") || exit1 "$str"
  359. $sbindir/localsendsms "$agentphone" "$task"
  360. cat <<EOF
  361. Mission initiated:
  362. $agentname: $task
  363. ($@)
  364. EOF
  365. exit 0
  366. ;;
  367. esac
  368. ;;
  369. help)
  370. cat <<EOF
  371. Commands:
  372. /ping [...]
  373. /add user|group|session|default ...
  374. /del user|group|session|default ...
  375. /mission AGENT @VICTIM msg...
  376. /COMMAND help
  377. /help
  378. try e.g. "/del help".
  379. EOF
  380. exit 0
  381. ;;
  382. status)
  383. response="$($sbindir/localshowkannelstatus)"
  384. exit0 "$response"
  385. ;;
  386. *)
  387. exit1 "unknown command \"$cmd\"!"
  388. ;;
  389. esac