2
0
Эх сурвалжийг харах

Merge '/scripts' of kamailio svn into sip-router/tools

* 'scripts' of kamailio svn: (389 commits)
  - fix kamctl for cr after table changes for 1.5.x, bug #2794168
  - set 0 default values for the new columns
  - small rephrasing, extend man page a bit
  - layout fix in man page for kamdbctl
  - Fixed "kamctl ping": From and Contact in generated OPTIONS didn't contain "sip:"
  - fix purple table name for non mysql databases setup
  - regenerated db table creation for dialog and purplemap
  provided db schemes for purple module
  - update man pages date
  - small layout fix in man pages
  - add man page for kamdbctl command, credits to Frederick Bullik
  - update man page to actual kamctl help text, closes #2009848
  - sync database files with xml source (gw change, r5478)
  fix error introduced by previous commit (dump option for lcr)
  kamctl lcr: new option: dump
  * Fixed errors in 'lcr addgw' parameter handling.
  fix addgw for lcr
  - new column to dispatcher table to hold priority
  - update database schemes after recent lcr extension (ping functionality)
  * Introduced optional aliveness checking of gateways.  Contributed by
  ...
Jan Janak 16 жил өмнө
parent
commit
427dfb25f5
100 өөрчлөгдсөн 5885 нэмэгдсэн , 0 устгасан
  1. 10 0
      tools/db_berkeley/kamailio/acc
  2. 10 0
      tools/db_berkeley/kamailio/active_watchers
  3. 10 0
      tools/db_berkeley/kamailio/address
  4. 10 0
      tools/db_berkeley/kamailio/aliases
  5. 10 0
      tools/db_berkeley/kamailio/carrier_name
  6. 10 0
      tools/db_berkeley/kamailio/carrierfailureroute
  7. 10 0
      tools/db_berkeley/kamailio/carrierroute
  8. 10 0
      tools/db_berkeley/kamailio/cpl
  9. 10 0
      tools/db_berkeley/kamailio/dbaliases
  10. 10 0
      tools/db_berkeley/kamailio/dialog
  11. 10 0
      tools/db_berkeley/kamailio/dialplan
  12. 10 0
      tools/db_berkeley/kamailio/dispatcher
  13. 10 0
      tools/db_berkeley/kamailio/domain
  14. 10 0
      tools/db_berkeley/kamailio/domain_name
  15. 10 0
      tools/db_berkeley/kamailio/domainpolicy
  16. 10 0
      tools/db_berkeley/kamailio/globalblacklist
  17. 10 0
      tools/db_berkeley/kamailio/grp
  18. 10 0
      tools/db_berkeley/kamailio/gw
  19. 10 0
      tools/db_berkeley/kamailio/htable
  20. 10 0
      tools/db_berkeley/kamailio/imc_members
  21. 10 0
      tools/db_berkeley/kamailio/imc_rooms
  22. 10 0
      tools/db_berkeley/kamailio/lcr
  23. 10 0
      tools/db_berkeley/kamailio/location
  24. 10 0
      tools/db_berkeley/kamailio/missed_calls
  25. 10 0
      tools/db_berkeley/kamailio/pdt
  26. 10 0
      tools/db_berkeley/kamailio/presentity
  27. 10 0
      tools/db_berkeley/kamailio/pua
  28. 10 0
      tools/db_berkeley/kamailio/purplemap
  29. 10 0
      tools/db_berkeley/kamailio/re_grp
  30. 10 0
      tools/db_berkeley/kamailio/rls_presentity
  31. 10 0
      tools/db_berkeley/kamailio/rls_watchers
  32. 10 0
      tools/db_berkeley/kamailio/silo
  33. 10 0
      tools/db_berkeley/kamailio/sip_trace
  34. 10 0
      tools/db_berkeley/kamailio/speed_dial
  35. 10 0
      tools/db_berkeley/kamailio/subscriber
  36. 10 0
      tools/db_berkeley/kamailio/trusted
  37. 10 0
      tools/db_berkeley/kamailio/uri
  38. 10 0
      tools/db_berkeley/kamailio/userblacklist
  39. 10 0
      tools/db_berkeley/kamailio/usr_preferences
  40. 92 0
      tools/db_berkeley/kamailio/version
  41. 10 0
      tools/db_berkeley/kamailio/watchers
  42. 10 0
      tools/db_berkeley/kamailio/xcap
  43. 1 0
      tools/dbtext/kamailio/acc
  44. 1 0
      tools/dbtext/kamailio/active_watchers
  45. 1 0
      tools/dbtext/kamailio/address
  46. 1 0
      tools/dbtext/kamailio/aliases
  47. 1 0
      tools/dbtext/kamailio/carrier_name
  48. 1 0
      tools/dbtext/kamailio/carrierfailureroute
  49. 1 0
      tools/dbtext/kamailio/carrierroute
  50. 1 0
      tools/dbtext/kamailio/cpl
  51. 1 0
      tools/dbtext/kamailio/dbaliases
  52. 1 0
      tools/dbtext/kamailio/dialog
  53. 1 0
      tools/dbtext/kamailio/dialplan
  54. 1 0
      tools/dbtext/kamailio/dispatcher
  55. 1 0
      tools/dbtext/kamailio/domain
  56. 1 0
      tools/dbtext/kamailio/domain_name
  57. 1 0
      tools/dbtext/kamailio/domainpolicy
  58. 1 0
      tools/dbtext/kamailio/globalblacklist
  59. 1 0
      tools/dbtext/kamailio/grp
  60. 1 0
      tools/dbtext/kamailio/gw
  61. 1 0
      tools/dbtext/kamailio/htable
  62. 1 0
      tools/dbtext/kamailio/imc_members
  63. 1 0
      tools/dbtext/kamailio/imc_rooms
  64. 1 0
      tools/dbtext/kamailio/lcr
  65. 1 0
      tools/dbtext/kamailio/location
  66. 1 0
      tools/dbtext/kamailio/missed_calls
  67. 1 0
      tools/dbtext/kamailio/pdt
  68. 1 0
      tools/dbtext/kamailio/presentity
  69. 1 0
      tools/dbtext/kamailio/pua
  70. 1 0
      tools/dbtext/kamailio/purplemap
  71. 1 0
      tools/dbtext/kamailio/re_grp
  72. 1 0
      tools/dbtext/kamailio/rls_presentity
  73. 1 0
      tools/dbtext/kamailio/rls_watchers
  74. 1 0
      tools/dbtext/kamailio/silo
  75. 1 0
      tools/dbtext/kamailio/sip_trace
  76. 1 0
      tools/dbtext/kamailio/speed_dial
  77. 1 0
      tools/dbtext/kamailio/subscriber
  78. 1 0
      tools/dbtext/kamailio/trusted
  79. 1 0
      tools/dbtext/kamailio/uri
  80. 1 0
      tools/dbtext/kamailio/userblacklist
  81. 1 0
      tools/dbtext/kamailio/usr_preferences
  82. 42 0
      tools/dbtext/kamailio/version
  83. 1 0
      tools/dbtext/kamailio/watchers
  84. 1 0
      tools/dbtext/kamailio/xcap
  85. 10 0
      tools/dbtextdb/__init__.py
  86. 1199 0
      tools/dbtextdb/dbtextdb.py
  87. 603 0
      tools/dbtextdb/dbtextdb_test.py
  88. 4 0
      tools/dbtextdb/tests/bad_table_auto_dupe
  89. 4 0
      tools/dbtextdb/tests/bad_table_int
  90. 4 0
      tools/dbtextdb/tests/bad_table_long_row
  91. 4 0
      tools/dbtextdb/tests/bad_table_null
  92. 4 0
      tools/dbtextdb/tests/bad_table_short_row
  93. 4 0
      tools/dbtextdb/tests/bad_table_wrong_type
  94. 7 0
      tools/dbtextdb/tests/subscriber
  95. 4 0
      tools/dbtextdb/tests/test
  96. 6 0
      tools/dbtextdb/tests/unsorted_table
  97. 5 0
      tools/dbtextdb/tests/unsorted_table2
  98. 2544 0
      tools/kamctl
  99. 238 0
      tools/kamctl.8
  100. 660 0
      tools/kamctl.base

+ 10 - 0
tools/db_berkeley/kamailio/acc

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) method(str) from_tag(str) to_tag(str) callid(str) sip_code(str) sip_reason(str) time(datetime)
+METADATA_KEY
+4 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|''|''|''|NIL

+ 10 - 0
tools/db_berkeley/kamailio/active_watchers

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) presentity_uri(str) watcher_username(str) watcher_domain(str) to_user(str) to_domain(str) event(str) event_id(str) to_tag(str) from_tag(str) callid(str) local_cseq(int) remote_cseq(int) contact(str) record_route(str) expires(int) status(int) reason(str) version(int) socket_info(str) local_contact(str)
+METADATA_KEY
+1 6 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|'presence'|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|2|NIL|0|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/address

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) grp(int) ip_addr(str) mask(int) port(int)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|0|NIL|32|0

+ 10 - 0
tools/db_berkeley/kamailio/aliases

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) contact(str) received(str) path(str) expires(datetime) q(double) callid(str) cseq(int) last_modified(datetime) flags(int) cflags(int) user_agent(str) socket(str) methods(int)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|NULL|''|NULL|NULL|'2020-05-28 21:32:15'|1.0|'Default-Call-ID'|13|'1900-01-01 00:00:01'|0|0|''|NULL|NULL

+ 10 - 0
tools/db_berkeley/kamailio/carrier_name

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) carrier(str)
+METADATA_KEY
+0 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NULL

+ 10 - 0
tools/db_berkeley/kamailio/carrierfailureroute

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) carrier(int) domain(int) scan_prefix(str) host_name(str) reply_code(str) flags(int) mask(int) next_domain(int) description(str)
+METADATA_KEY
+1 2 8 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|0|0|''|''|''|0|0|0|NULL

+ 10 - 0
tools/db_berkeley/kamailio/carrierroute

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) carrier(int) domain(int) scan_prefix(str) flags(int) mask(int) prob(double) strip(int) rewrite_host(str) rewrite_prefix(str) rewrite_suffix(str) description(str)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|0|0|''|0|0|0|0|''|''|''|NULL

+ 10 - 0
tools/db_berkeley/kamailio/cpl

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) cpl_xml(str) cpl_bin(str)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|''|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/dbaliases

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) alias_username(str) alias_domain(str) username(str) domain(str)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|''

+ 10 - 0
tools/db_berkeley/kamailio/dialog

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) hash_entry(int) hash_id(int) callid(str) from_uri(str) from_tag(str) to_uri(str) to_tag(str) caller_cseq(str) callee_cseq(str) caller_route_set(str) callee_route_set(str) caller_contact(str) callee_contact(str) caller_sock(str) callee_sock(str) state(int) start_time(int) timeout(int) sflags(int) toroute(int)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|0|0|0

+ 10 - 0
tools/db_berkeley/kamailio/dialplan

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) dpid(int) pr(int) match_op(int) match_exp(str) match_len(int) subst_exp(str) repl_exp(str) attrs(str)
+METADATA_KEY
+
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/dispatcher

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) setid(int) destination(str) flags(int) priority(int) description(str)
+METADATA_KEY
+1 3 4 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|0|''|0|0|''

+ 10 - 0
tools/db_berkeley/kamailio/domain

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) domain(str) last_modified(datetime)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|'1900-01-01 00:00:01'

+ 10 - 0
tools/db_berkeley/kamailio/domain_name

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) domain(str)
+METADATA_KEY
+0 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NULL

+ 10 - 0
tools/db_berkeley/kamailio/domainpolicy

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) rule(str) type(str) att(str) val(str) description(str)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/globalblacklist

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) prefix(str) whitelist(int) description(str)
+METADATA_KEY
+
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|0|NULL

+ 10 - 0
tools/db_berkeley/kamailio/grp

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) grp(str) last_modified(datetime)
+METADATA_KEY
+1 2 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|'1900-01-01 00:00:01'

+ 10 - 0
tools/db_berkeley/kamailio/gw

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) gw_name(str) grp_id(int) ip_addr(str) hostname(str) port(int) uri_scheme(int) transport(int) strip(int) tag(str) weight(int) ping(int) flags(int)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NULL|NIL|0|0

+ 10 - 0
tools/db_berkeley/kamailio/htable

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) key_name(str) key_type(int) value_type(int) key_value(str)
+METADATA_KEY
+
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|0|0|''

+ 10 - 0
tools/db_berkeley/kamailio/imc_members

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) room(str) flag(int)
+METADATA_KEY
+1 2 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/imc_rooms

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) name(str) domain(str) flag(int)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/lcr

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) prefix(str) from_uri(str) grp_id(int) priority(int)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NULL|NULL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/location

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) contact(str) received(str) path(str) expires(datetime) q(double) callid(str) cseq(int) last_modified(datetime) flags(int) cflags(int) user_agent(str) socket(str) methods(int)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|NULL|''|NULL|NULL|'2020-05-28 21:32:15'|1.0|'Default-Call-ID'|13|'1900-01-01 00:00:01'|0|0|''|NULL|NULL

+ 10 - 0
tools/db_berkeley/kamailio/missed_calls

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) method(str) from_tag(str) to_tag(str) callid(str) sip_code(str) sip_reason(str) time(datetime)
+METADATA_KEY
+4 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|''|''|''|NIL

+ 10 - 0
tools/db_berkeley/kamailio/pdt

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) sdomain(str) prefix(str) domain(str)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|''

+ 10 - 0
tools/db_berkeley/kamailio/presentity

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) event(str) etag(str) expires(int) received_time(int) body(str) sender(str)
+METADATA_KEY
+1 2 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/pua

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) pres_uri(str) pres_id(str) event(int) expires(int) desired_expires(int) flag(int) etag(str) tuple_id(str) watcher_uri(str) call_id(str) to_tag(str) from_tag(str) cseq(int) record_route(str) contact(str) remote_contact(str) version(int) extra_headers(str)
+METADATA_KEY
+1 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/purplemap

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) sip_user(str) ext_user(str) ext_prot(str) ext_pass(str)
+METADATA_KEY
+1 2 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/re_grp

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) reg_exp(str) group_id(int)
+METADATA_KEY
+2
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|0

+ 10 - 0
tools/db_berkeley/kamailio/rls_presentity

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) rlsubs_did(str) resource_uri(str) content_type(str) presence_state(str) expires(int) updated(int) auth_state(int) reason(str)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/rls_watchers

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) presentity_uri(str) to_user(str) to_domain(str) watcher_username(str) watcher_domain(str) event(str) event_id(str) to_tag(str) from_tag(str) callid(str) local_cseq(int) remote_cseq(int) contact(str) record_route(str) expires(int) status(int) reason(str) version(int) socket_info(str) local_contact(str)
+METADATA_KEY
+4 5 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|'presence'|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|2|NIL|0|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/silo

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) src_addr(str) dst_addr(str) username(str) domain(str) inc_time(int) exp_time(int) snd_time(int) ctype(str) body(str)
+METADATA_KEY
+3 4 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|''|0|0|0|'text/plain'|''

+ 10 - 0
tools/db_berkeley/kamailio/sip_trace

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) time_stamp(datetime) callid(str) traced_user(str) msg(str) method(str) status(str) fromip(str) toip(str) fromtag(str) direction(str)
+METADATA_KEY
+2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|'1900-01-01 00:00:01'|''|''|NIL|''|''|''|''|''|''

+ 10 - 0
tools/db_berkeley/kamailio/speed_dial

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) sd_username(str) sd_domain(str) new_uri(str) fname(str) lname(str) description(str)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|''|''|''|''|''

+ 10 - 0
tools/db_berkeley/kamailio/subscriber

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) password(str) email_address(str) ha1(str) ha1b(str) rpid(str)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|''|''|''|NULL

+ 10 - 0
tools/db_berkeley/kamailio/trusted

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) src_ip(str) proto(str) from_pattern(str) tag(str)
+METADATA_KEY
+1 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NULL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/uri

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) uri_user(str) last_modified(datetime)
+METADATA_KEY
+1 2 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|'1900-01-01 00:00:01'

+ 10 - 0
tools/db_berkeley/kamailio/userblacklist

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) prefix(str) whitelist(int)
+METADATA_KEY
+
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|''|''|0

+ 10 - 0
tools/db_berkeley/kamailio/usr_preferences

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) uuid(str) username(str) domain(str) attribute(str) type(int) value(str) last_modified(datetime)
+METADATA_KEY
+2 3 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|''|0|''|''|0|''|'1900-01-01 00:00:01'

+ 92 - 0
tools/db_berkeley/kamailio/version

@@ -0,0 +1,92 @@
+METADATA_COLUMNS
+table_name(str) table_version(int)
+METADATA_KEY
+0 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|0
+acc|
+acc|4
+active_watchers|
+active_watchers|9
+address|
+address|3
+aliases|
+aliases|1004
+carrierfailureroute|
+carrierfailureroute|2
+carrier_name|
+carrier_name|1
+carrierroute|
+carrierroute|3
+cpl|
+cpl|1
+dbaliases|
+dbaliases|1
+dialog|
+dialog|3
+dialplan|
+dialplan|1
+dispatcher|
+dispatcher|3
+domain|
+domain|1
+domain_name|
+domain_name|1
+domainpolicy|
+domainpolicy|2
+globalblacklist|
+globalblacklist|1
+grp|
+grp|2
+gw|
+gw|9
+htable|
+htable|1
+imc_members|
+imc_members|1
+imc_rooms|
+imc_rooms|1
+lcr|
+lcr|2
+location|
+location|1004
+missed_calls|
+missed_calls|3
+pdt|
+pdt|1
+presentity|
+presentity|3
+pua|
+pua|6
+purplemap|
+purplemap|1
+re_grp|
+re_grp|1
+rls_presentity|
+rls_presentity|0
+rls_watchers|
+rls_watchers|1
+silo|
+silo|5
+sip_trace|
+sip_trace|2
+speed_dial|
+speed_dial|2
+subscriber|
+subscriber|6
+trusted|
+trusted|4
+uri|
+uri|1
+userblacklist|
+userblacklist|1
+usr_preferences|
+usr_preferences|2
+watchers|
+watchers|3
+xcap|
+xcap|3

+ 10 - 0
tools/db_berkeley/kamailio/watchers

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) presentity_uri(str) watcher_username(str) watcher_domain(str) event(str) status(int) reason(str) inserted_time(int)
+METADATA_KEY
+1 4 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|'presence'|NIL|NIL|NIL

+ 10 - 0
tools/db_berkeley/kamailio/xcap

@@ -0,0 +1,10 @@
+METADATA_COLUMNS
+id(int) username(str) domain(str) doc(str) doc_type(int) etag(str) source(int) doc_uri(str) port(int)
+METADATA_KEY
+1 2 
+METADATA_READONLY
+0
+METADATA_LOGFLAGS
+0
+METADATA_DEFAULTS
+NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL|NIL

+ 1 - 0
tools/dbtext/kamailio/acc

@@ -0,0 +1 @@
+id(int,auto) method(string) from_tag(string) to_tag(string) callid(string) sip_code(string) sip_reason(string) time(int) 

+ 1 - 0
tools/dbtext/kamailio/active_watchers

@@ -0,0 +1 @@
+id(int,auto) presentity_uri(string) watcher_username(string) watcher_domain(string) to_user(string) to_domain(string) event(string) event_id(string,null) to_tag(string) from_tag(string) callid(string) local_cseq(int) remote_cseq(int) contact(string) record_route(string,null) expires(int) status(int) reason(string) version(int) socket_info(string) local_contact(string) 

+ 1 - 0
tools/dbtext/kamailio/address

@@ -0,0 +1 @@
+id(int,auto) grp(int) ip_addr(string) mask(int) port(int) 

+ 1 - 0
tools/dbtext/kamailio/aliases

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string,null) contact(string) received(string,null) path(string,null) expires(int) q(double) callid(string) cseq(int) last_modified(int) flags(int) cflags(int) user_agent(string) socket(string,null) methods(int,null) 

+ 1 - 0
tools/dbtext/kamailio/carrier_name

@@ -0,0 +1 @@
+id(int,auto) carrier(string,null) 

+ 1 - 0
tools/dbtext/kamailio/carrierfailureroute

@@ -0,0 +1 @@
+id(int,auto) carrier(int) domain(int) scan_prefix(string) host_name(string) reply_code(string) flags(int) mask(int) next_domain(int) description(string,null) 

+ 1 - 0
tools/dbtext/kamailio/carrierroute

@@ -0,0 +1 @@
+id(int,auto) carrier(int) domain(int) scan_prefix(string) flags(int) mask(int) prob(double) strip(int) rewrite_host(string) rewrite_prefix(string) rewrite_suffix(string) description(string,null) 

+ 1 - 0
tools/dbtext/kamailio/cpl

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) cpl_xml(string,null) cpl_bin(string,null) 

+ 1 - 0
tools/dbtext/kamailio/dbaliases

@@ -0,0 +1 @@
+id(int,auto) alias_username(string) alias_domain(string) username(string) domain(string) 

+ 1 - 0
tools/dbtext/kamailio/dialog

@@ -0,0 +1 @@
+id(int,auto) hash_entry(int) hash_id(int) callid(string) from_uri(string) from_tag(string) to_uri(string) to_tag(string) caller_cseq(string) callee_cseq(string) caller_route_set(string,null) callee_route_set(string,null) caller_contact(string) callee_contact(string) caller_sock(string) callee_sock(string) state(int) start_time(int) timeout(int) sflags(int) toroute(int) 

+ 1 - 0
tools/dbtext/kamailio/dialplan

@@ -0,0 +1 @@
+id(int,auto) dpid(int) pr(int) match_op(int) match_exp(string) match_len(int) subst_exp(string) repl_exp(string) attrs(string) 

+ 1 - 0
tools/dbtext/kamailio/dispatcher

@@ -0,0 +1 @@
+id(int,auto) setid(int) destination(string) flags(int) priority(int) description(string) 

+ 1 - 0
tools/dbtext/kamailio/domain

@@ -0,0 +1 @@
+id(int,auto) domain(string) last_modified(int) 

+ 1 - 0
tools/dbtext/kamailio/domain_name

@@ -0,0 +1 @@
+id(int,auto) domain(string,null) 

+ 1 - 0
tools/dbtext/kamailio/domainpolicy

@@ -0,0 +1 @@
+id(int,auto) rule(string) type(string) att(string,null) val(string,null) description(string) 

+ 1 - 0
tools/dbtext/kamailio/globalblacklist

@@ -0,0 +1 @@
+id(int,auto) prefix(string) whitelist(int) description(string,null) 

+ 1 - 0
tools/dbtext/kamailio/grp

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) grp(string) last_modified(int) 

+ 1 - 0
tools/dbtext/kamailio/gw

@@ -0,0 +1 @@
+id(int,auto) gw_name(string) grp_id(int) ip_addr(string) hostname(string,null) port(int,null) uri_scheme(int,null) transport(int,null) strip(int,null) tag(string,null) weight(int,null) ping(int) flags(int) 

+ 1 - 0
tools/dbtext/kamailio/htable

@@ -0,0 +1 @@
+id(int,auto) key_name(string) key_type(int) value_type(int) key_value(string) 

+ 1 - 0
tools/dbtext/kamailio/imc_members

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) room(string) flag(int) 

+ 1 - 0
tools/dbtext/kamailio/imc_rooms

@@ -0,0 +1 @@
+id(int,auto) name(string) domain(string) flag(int) 

+ 1 - 0
tools/dbtext/kamailio/lcr

@@ -0,0 +1 @@
+id(int,auto) prefix(string,null) from_uri(string,null) grp_id(int) priority(int) 

+ 1 - 0
tools/dbtext/kamailio/location

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string,null) contact(string) received(string,null) path(string,null) expires(int) q(double) callid(string) cseq(int) last_modified(int) flags(int) cflags(int) user_agent(string) socket(string,null) methods(int,null) 

+ 1 - 0
tools/dbtext/kamailio/missed_calls

@@ -0,0 +1 @@
+id(int,auto) method(string) from_tag(string) to_tag(string) callid(string) sip_code(string) sip_reason(string) time(int) 

+ 1 - 0
tools/dbtext/kamailio/pdt

@@ -0,0 +1 @@
+id(int,auto) sdomain(string) prefix(string) domain(string) 

+ 1 - 0
tools/dbtext/kamailio/presentity

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) event(string) etag(string) expires(int) received_time(int) body(string) sender(string) 

+ 1 - 0
tools/dbtext/kamailio/pua

@@ -0,0 +1 @@
+id(int,auto) pres_uri(string) pres_id(string) event(int) expires(int) desired_expires(int) flag(int) etag(string) tuple_id(string,null) watcher_uri(string) call_id(string) to_tag(string) from_tag(string) cseq(int) record_route(string,null) contact(string) remote_contact(string) version(int) extra_headers(string) 

+ 1 - 0
tools/dbtext/kamailio/purplemap

@@ -0,0 +1 @@
+id(int,auto) sip_user(string) ext_user(string) ext_prot(string) ext_pass(string,null) 

+ 1 - 0
tools/dbtext/kamailio/re_grp

@@ -0,0 +1 @@
+id(int,auto) reg_exp(string) group_id(int) 

+ 1 - 0
tools/dbtext/kamailio/rls_presentity

@@ -0,0 +1 @@
+id(int,auto) rlsubs_did(string) resource_uri(string) content_type(string) presence_state(string) expires(int) updated(int) auth_state(int) reason(string) 

+ 1 - 0
tools/dbtext/kamailio/rls_watchers

@@ -0,0 +1 @@
+id(int,auto) presentity_uri(string) to_user(string) to_domain(string) watcher_username(string) watcher_domain(string) event(string) event_id(string,null) to_tag(string) from_tag(string) callid(string) local_cseq(int) remote_cseq(int) contact(string) record_route(string,null) expires(int) status(int) reason(string) version(int) socket_info(string) local_contact(string) 

+ 1 - 0
tools/dbtext/kamailio/silo

@@ -0,0 +1 @@
+id(int,auto) src_addr(string) dst_addr(string) username(string) domain(string) inc_time(int) exp_time(int) snd_time(int) ctype(string) body(string) 

+ 1 - 0
tools/dbtext/kamailio/sip_trace

@@ -0,0 +1 @@
+id(int,auto) time_stamp(int) callid(string) traced_user(string) msg(string) method(string) status(string) fromip(string) toip(string) fromtag(string) direction(string) 

+ 1 - 0
tools/dbtext/kamailio/speed_dial

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) sd_username(string) sd_domain(string) new_uri(string) fname(string) lname(string) description(string) 

+ 1 - 0
tools/dbtext/kamailio/subscriber

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) password(string) email_address(string) ha1(string) ha1b(string) rpid(string,null) 

+ 1 - 0
tools/dbtext/kamailio/trusted

@@ -0,0 +1 @@
+id(int,auto) src_ip(string) proto(string) from_pattern(string,null) tag(string,null) 

+ 1 - 0
tools/dbtext/kamailio/uri

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) uri_user(string) last_modified(int) 

+ 1 - 0
tools/dbtext/kamailio/userblacklist

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) prefix(string) whitelist(int) 

+ 1 - 0
tools/dbtext/kamailio/usr_preferences

@@ -0,0 +1 @@
+id(int,auto) uuid(string) username(string) domain(string) attribute(string) type(int) value(string) last_modified(int) 

+ 42 - 0
tools/dbtext/kamailio/version

@@ -0,0 +1,42 @@
+table_name(string) table_version(int) 
+acc:4
+active_watchers:9
+address:3
+aliases:1004
+carrierfailureroute:2
+carrier_name:1
+carrierroute:3
+cpl:1
+dbaliases:1
+dialog:3
+dialplan:1
+dispatcher:3
+domain:1
+domain_name:1
+domainpolicy:2
+globalblacklist:1
+grp:2
+gw:9
+htable:1
+imc_members:1
+imc_rooms:1
+lcr:2
+location:1004
+missed_calls:3
+pdt:1
+presentity:3
+pua:6
+purplemap:1
+re_grp:1
+rls_presentity:0
+rls_watchers:1
+silo:5
+sip_trace:2
+speed_dial:2
+subscriber:6
+trusted:4
+uri:1
+userblacklist:1
+usr_preferences:2
+watchers:3
+xcap:3

+ 1 - 0
tools/dbtext/kamailio/watchers

@@ -0,0 +1 @@
+id(int,auto) presentity_uri(string) watcher_username(string) watcher_domain(string) event(string) status(int) reason(string,null) inserted_time(int) 

+ 1 - 0
tools/dbtext/kamailio/xcap

@@ -0,0 +1 @@
+id(int,auto) username(string) domain(string) doc(string) doc_type(int) etag(string) source(int) doc_uri(string) port(int) 

+ 10 - 0
tools/dbtextdb/__init__.py

@@ -0,0 +1,10 @@
+#!/usr/bin/python
+#
+# Copyright 2008 Google Inc. All Rights Reserved.
+
+"""Empty __init__ for dbtextdb module.
+
+This file is empty, it only exists to enable importing dbtextdb
+"""
+
+__author__ = '[email protected] (Herman Sheremetyev)'

+ 1199 - 0
tools/dbtextdb/dbtextdb.py

@@ -0,0 +1,1199 @@
+#!/usr/bin/python
+#
+# Copyright 2008 Google Inc. All Rights Reserved.
+
+"""SQL-like access layer for dbtext.
+
+This module provides the glue for kamctl to interact with dbtext files
+using basic SQL syntax thus avoiding special case handling of dbtext.
+
+"""
+
+__author__ = '[email protected] (Herman Sheremetyev)'
+
+import fcntl
+import os
+import shutil
+import sys
+import tempfile
+import time
+
+if 'DBTEXTDB_DEBUG' in os.environ:
+  DEBUG = os.environ['DBTEXTDB_DEBUG']
+else:
+  DEBUG = 0
+
+
+def Debug(msg):
+  """Debug print method."""
+  if DEBUG:
+    print msg
+
+
+class DBText(object):
+  """Provides connection to a dbtext database."""
+
+  RESERVED_WORDS = ['SELECT', 'DELETE', 'UPDATE', 'INSERT', 'SET',
+                    'VALUES', 'INTO', 'FROM', 'ORDER', 'BY', 'WHERE',
+                    'COUNT', 'CONCAT', 'AND', 'AS']
+  ALL_COMMANDS = ['SELECT', 'DELETE', 'UPDATE', 'INSERT']
+  WHERE_COMMANDS = ['SELECT', 'DELETE', 'UPDATE']
+
+  def __init__(self, location):
+    self.location = location  # location of dbtext tables
+    self.tokens = []          # query broken up into tokens
+    self.conditions = {}      # args to the WHERE clause
+    self.columns = []         # columns requested by SELECT
+    self.table = ''           # name of the table being queried
+    self.header = {}          # table header
+    self.orig_data = []       # original table data used to diff after updates
+    self.data = []            # table data as a list of dicts
+    self.count = False        # where or not using COUNT()
+    self.aliases = {}         # column aliases (SELECT AS)
+    self.targets = {}         # target columns-value pairs for INSERT/UPDATE
+    self.args = ''            # query arguments preceeding the ;
+    self.command = ''         # which command are we executing
+    self.strings = []         # list of string literals parsed from the query
+    self.parens = []          # list of parentheses parsed from the query
+    self._str_placeholder = '__DBTEXTDB_PARSED_OUT_STRING__'
+    self._paren_placeholder = '__DBTEXTDB_PARSED_OUT_PARENS__'
+    if not os.path.isdir(location):
+      raise ParseError(location + ' is not a directory')
+
+  def _ParseOrderBy(self):
+    """Parse out the column name to be used for ordering the dataset.
+
+    Raises:
+      ParseError: Invalid ORDER BY clause
+    """
+    self.order_by = ''
+    if 'ORDER' in self.tokens:
+      order_index = self.tokens.index('ORDER')
+      if order_index != len(self.tokens) - 3:
+        raise ParseError('ORDER must be followed with BY and column name')
+      if self.tokens[order_index + 1] != 'BY':
+        raise ParseError('ORDER must be followed with BY')
+      self.order_by = self.tokens[order_index + 2]
+
+      # strip off the order by stuff
+      self.tokens.pop()  # column name
+      self.tokens.pop()  # BY
+      self.tokens.pop()  # ORDER
+
+    elif 'BY' in self.tokens:
+      raise ParseError('BY must be preceeded by ORDER')
+
+    Debug('Order by: ' + self.order_by)
+
+  def _ParseConditions(self):
+    """Parse out WHERE clause.
+
+    Take everything after the WHERE keyword and convert it to a dict of
+    name value pairs corresponding to the columns and their values that
+    should be matched.
+
+    Raises:
+      ParseError: Invalid WHERE clause
+      NotSupportedError: Unsupported syntax
+    """
+    self.conditions = {}
+    Debug('self.tokens = %s' % self.tokens)
+    if 'WHERE' not in self.tokens:
+      return
+
+    if self.command not in self.WHERE_COMMANDS:
+      raise ParseError(self.command + ' cannot have a WHERE clause')
+    if 'OR' in self.tokens:
+      raise NotSupportedError('WHERE clause does not support OR operator')
+
+    where_clause = self.tokens[self.tokens.index('WHERE') + 1:]
+    self.conditions = self._ParsePairs(' '.join(where_clause), 'AND')
+    for cond in self.conditions:
+      self.conditions[cond] = self._EscapeChars(self.conditions[cond])
+    Debug('Conditions are [%s]' % self.conditions)
+
+    # pop off where clause
+    a = self.tokens.pop()
+    while a != 'WHERE':
+      a = self.tokens.pop()
+
+    Debug('self.tokens: %s' % self.tokens)
+
+  def _ParseColumns(self):
+    """Parse out the columns that need to be selected.
+
+    Raises:
+      ParseError: Invalid SELECT syntax
+    """
+    self.columns = []
+    self.count = False
+    self.aliases = {}
+    col_end = 0
+    # this is only valid for SELECT
+    if self.command != 'SELECT':
+      return
+
+    if 'FROM' not in self.tokens:
+      raise ParseError('SELECT must be followed by FROM')
+
+    col_end = self.tokens.index('FROM')
+    if not col_end:  # col_end == 0
+      raise ParseError('SELECT must be followed by column name[s]')
+
+    cols_str = ' '.join(self.tokens[0:col_end])
+    # check if there is a function modifier on the columns
+    if self.tokens[0] == 'COUNT':
+      self.count = True
+      if col_end == 1:
+        raise ParseError('COUNT must be followed by column name[s]')
+      if not self.tokens[1].startswith(self._paren_placeholder):
+        raise ParseError('COUNT must be followed by ()')
+      cols_str = self._ReplaceParens(self.tokens[1])
+
+    cols = cols_str.split(',')
+    for col in cols:
+      if not col.strip():
+        raise ParseError('Extra comma in columns')
+      col_split = col.split()
+      if col_split[0] == 'CONCAT':
+        # found a concat statement, do the same overall steps for those cols
+        self._ParseColumnsConcatHelper(col_split)
+      else:
+        col_split = col.split()
+        if len(col_split) > 2 and col_split[1] != 'AS':
+          raise ParseError('multiple columns must be separated by a comma')
+        elif len(col_split) == 3:
+          if col_split[1] != 'AS':
+            raise ParseError('Invalid column alias, use AS')
+          my_key = self._ReplaceStringLiterals(col_split[2], quotes=True)
+          my_val = self._ReplaceStringLiterals(col_split[0], quotes=True)
+          self.aliases[my_key] = [my_val]
+          self.columns.append(my_key)
+        elif len(col_split) > 3:
+          raise ParseError('multiple columns must be separated by a comma')
+        elif len(col_split) == 2:  # alias
+          my_key = self._ReplaceStringLiterals(col_split[1], quotes=True)
+          my_val = self._ReplaceStringLiterals(col_split[0], quotes=True)
+          self.aliases[my_key] = [my_val]
+          self.columns.append(my_key)
+        else:
+          col = self._ReplaceStringLiterals(col, quotes=True).strip()
+          if not col:  # col == ''
+            raise ParseError('empty column name not allowed')
+
+          self.columns.append(col)
+
+    # pop off all the columns related junk
+    self.tokens = self.tokens[col_end + 1:]
+
+    Debug('Columns: %s' % self.columns)
+    Debug('Aliases: %s' % self.aliases)
+    Debug('self.tokens: %s' % self.tokens)
+
+  def _ParseColumnsConcatHelper(self, col_split):
+    """Handles the columns being CONCAT'd together.
+
+    Args:
+      col_split: ['column', 'column']
+
+    Raises:
+      ParseError: invalid CONCAT()
+    """
+    concat_placeholder = '_'
+    split_len = len(col_split)
+    if split_len == 1:
+      raise ParseError('CONCAT() must be followed by column name[s]')
+    if not col_split[1].startswith(self._paren_placeholder):
+      raise ParseError('CONCAT must be followed by ()')
+    if split_len > 2:
+      if split_len == 4 and col_split[2] != 'AS':
+        raise ParseError('CONCAT() must be followed by an AS clause')
+      if split_len > 5:
+        raise ParseError('CONCAT() AS clause takes exactly 1 arg. '
+                         'Extra args: [%s]' % (col_split[4:]))
+      else:
+        concat_placeholder = self._ReplaceStringLiterals(col_split[-1],
+                                                         quotes=True)
+
+    # make sure this place hodler is unique
+    while concat_placeholder in self.aliases:
+      concat_placeholder += '_'
+    concat_cols_str = self._ReplaceParens(col_split[1])
+    concat_cols = concat_cols_str.split(',')
+    concat_col_list = []
+    for concat_col in concat_cols:
+      if ' ' in concat_col.strip():
+        raise ParseError('multiple columns must be separated by a'
+                         ' comma inside CONCAT()')
+      concat_col = self._ReplaceStringLiterals(concat_col, quotes=True).strip()
+      if not concat_col:
+        raise ParseError('Attempting to CONCAT empty set')
+      concat_col_list.append(concat_col)
+
+    self.aliases[concat_placeholder] = concat_col_list
+    self.columns.append(concat_placeholder)
+
+  def _ParseTable(self):
+    """Parse out the table name (multiple table names not supported).
+
+    Raises:
+      ParseError: Unable to parse table name
+    """
+    table_name = ''
+    if (not self.tokens or  # len == 0
+        (self.tokens[0] in self.RESERVED_WORDS and
+         self.tokens[0] not in ['FROM', 'INTO'])):
+      raise ParseError('Missing table name')
+
+    # SELECT
+    if self.command == 'SELECT':
+      table_name = self.tokens.pop(0)
+
+    # INSERT
+    elif self.command == 'INSERT':
+      table_name = self.tokens.pop(0)
+      if table_name == 'INTO':
+        table_name = self.tokens.pop(0)
+
+    # DELETE
+    elif self.command == 'DELETE':
+      if self.tokens[0] != 'FROM':
+        raise ParseError('DELETE command must be followed by FROM')
+
+      self.tokens.pop(0)  # FROM
+      table_name = self.tokens.pop(0)
+
+    # UPDATE
+    elif self.command == 'UPDATE':
+      table_name = self.tokens.pop(0)
+
+    if not self.table:
+      self.table = table_name
+
+    else:  # multiple queries detected, make sure they're against same table
+      if self.table != table_name:
+        raise ParseError('Table changed between queries! %s -> %s' %
+                         (self.table, table_name))
+    Debug('Table is [%s]' % self.table)
+    Debug('self.tokens is %s' % self.tokens)
+
+  def _ParseTargets(self):
+    """Parse out name value pairs of columns and their values.
+
+    Raises:
+      ParseError: Unable to parse targets
+    """
+    self.targets = {}
+    # UPDATE
+    if self.command == 'UPDATE':
+      if self.tokens.pop(0) != 'SET':
+        raise ParseError('UPDATE command must be followed by SET')
+
+      self.targets = self._ParsePairs(' '.join(self.tokens), ',')
+
+    # INSERT
+    if self.command == 'INSERT':
+      if self.tokens[0] == 'SET':
+        self.targets = self._ParsePairs(' '.join(self.tokens[1:]), ',')
+
+      elif len(self.tokens) == 3 and self.tokens[1] == 'VALUES':
+        if not self.tokens[0].startswith(self._paren_placeholder):
+          raise ParseError('INSERT column names must be inside parens')
+        if not self.tokens[2].startswith(self._paren_placeholder):
+          raise ParseError('INSERT values must be inside parens')
+
+        cols = self._ReplaceParens(self.tokens[0]).split(',')
+        vals = self._ReplaceParens(self.tokens[2]).split(',')
+
+        if len(cols) != len(vals):
+          raise ParseError('INSERT column and value numbers must match')
+        if not cols:  # len == 0
+          raise ParseError('INSERT column number must be greater than 0')
+
+        i = 0
+        while i < len(cols):
+          val = vals[i].strip()
+          if not val:  # val == ''
+            raise ParseError('INSERT values cannot be empty')
+          if ' ' in val:
+            raise ParseError('INSERT values must be comma separated')
+          self.targets[cols[i].strip()] = self._ReplaceStringLiterals(val)
+          i += 1
+
+      else:
+        raise ParseError('Unable to parse INSERT targets')
+
+    for target in self.targets:
+      self.targets[target] = self._EscapeChars(self.targets[target])
+
+    Debug('Targets are [%s]' % self.targets)
+
+  def _EscapeChars(self, value):
+    """Escape necessary chars before inserting into dbtext.
+
+    Args:
+      value: 'string'
+
+    Returns:
+      escaped: 'string' with chars escaped appropriately
+    """
+    # test that the value is string, if not return it as is
+    try:
+      value.find('a')
+    except:
+      return value
+
+    escaped = value
+    escaped = escaped.replace('\\', '\\\\').replace('\0', '\\0')
+    escaped = escaped.replace(':', '\\:').replace('\n', '\\n')
+    escaped = escaped.replace('\r', '\\r').replace('\t', '\\t')
+    return escaped
+
+  def _UnEscapeChars(self, value):
+    """Un-escape necessary chars before returning to user.
+
+    Args:
+      value: 'string'
+
+    Returns:
+      escaped: 'string' with chars escaped appropriately
+    """
+    # test that the value is string, if not return it as is
+    try:
+      value.find('a')
+    except:
+      return value
+
+    escaped = value
+    escaped = escaped.replace('\\:', ':').replace('\\n', '\n')
+    escaped = escaped.replace('\\r', '\r').replace('\\t', '\t')
+    escaped = escaped.replace('\\0', '\0').replace('\\\\', '\\')
+    return escaped
+
+  def Execute(self, query, writethru=True):
+    """Parse and execute the query.
+
+    Args:
+      query: e.g. 'select * from table;'
+      writethru: bool
+
+    Returns:
+      dataset: [{col: val, col: val}, {col: val}, {col: val}]
+
+    Raises:
+      ExecuteError: unable to execute query
+    """
+    # parse the query
+    self.ParseQuery(query)
+
+    # get lock and execute the query
+    self.OpenTable()
+    Debug('Running ' + self.command)
+    dataset = []
+    if self.command == 'SELECT':
+      dataset = self._RunSelect()
+    elif self.command == 'UPDATE':
+      dataset = self._RunUpdate()
+    elif self.command == 'INSERT':
+      dataset = self._RunInsert()
+    elif self.command == 'DELETE':
+      dataset = self._RunDelete()
+
+    if self.command != 'SELECT' and writethru:
+      self.WriteTempTable()
+      self.MoveTableIntoPlace()
+
+    Debug(dataset)
+    return dataset
+
+  def CleanUp(self):
+    """Reset the internal variables (for multiple queries)."""
+    self.tokens = []          # query broken up into tokens
+    self.conditions = {}      # args to the WHERE clause
+    self.columns = []         # columns requested by SELECT
+    self.table = ''           # name of the table being queried
+    self.header = {}          # table header
+    self.orig_data = []       # original table data used to diff after updates
+    self.data = []            # table data as a list of dicts
+    self.count = False        # where or not using COUNT()
+    self.aliases = {}         # column aliases (SELECT AS)
+    self.targets = {}         # target columns-value pairs for INSERT/UPDATE
+    self.args = ''            # query arguments preceeding the ;
+    self.command = ''         # which command are we executing
+    self.strings = []         # list of string literals parsed from the query
+    self.parens = []          # list of parentheses parsed from the query
+
+  def ParseQuery(self, query):
+    """External wrapper for the query parsing routines.
+
+    Args:
+      query: string
+
+    Raises:
+      ParseError: Unable to parse query
+    """
+    self.args = query.split(';')[0]
+    self._Tokenize()
+    self._ParseCommand()
+    self._ParseOrderBy()
+    self._ParseConditions()
+    self._ParseColumns()
+    self._ParseTable()
+    self._ParseTargets()
+
+  def _ParseCommand(self):
+    """Determine the command: SELECT, UPDATE, DELETE or INSERT.
+
+    Raises:
+      ParseError: unable to parse command
+    """
+    self.command = self.tokens[0]
+    # Check that command is valid
+    if self.command not in self.ALL_COMMANDS:
+      raise ParseError('Unsupported command: ' + self.command)
+
+    self.tokens.pop(0)
+    Debug('Command is: %s' % self.command)
+    Debug('self.tokens: %s' % self.tokens)
+
+  def _Tokenize(self):
+    """Turn the string query into a list of tokens.
+
+    Split on '(', ')', ' ', ';', '=' and ','.
+    In addition capitalize any SQL keywords found.
+    """
+    # horrible hack to handle now()
+    time_now = '%s' % int(time.time())
+    time_now = time_now[0:-2] + '00'  # round off the seconds for unittesting
+    while 'now()' in self.args.lower():
+      start = self.args.lower().find('now()')
+      self.args = ('%s%s%s' % (self.args[0:start], time_now,
+                               self.args[start + 5:]))
+    # pad token separators with spaces
+    pad = self.args.replace('(', ' ( ').replace(')', ' ) ')
+    pad = pad.replace(',', ' , ').replace(';', ' ; ').replace('=', ' = ')
+    self.args = pad
+    # parse out all the blocks (string literals and parens)
+    self._ParseOutBlocks()
+    # split remaining into tokens
+    self.tokens = self.args.split()
+
+    # now capitalize
+    i = 0
+    while i < len(self.tokens):
+      if self.tokens[i].upper() in self.RESERVED_WORDS:
+        self.tokens[i] = self.tokens[i].upper()
+
+      i += 1
+
+    Debug('Tokens: %s' % self.tokens)
+
+  def _ParseOutBlocks(self):
+    """Parse out string literals and parenthesized values."""
+    self.strings = []
+    self.parens = []
+
+    # set str placeholder to a value that's not present in the string
+    while self._str_placeholder in self.args:
+      self._str_placeholder = '%s_' % self._str_placeholder
+
+    # set paren placeholder to a value that's not present in the string
+    while self._paren_placeholder in self.args:
+      self._paren_placeholder = '%s_' % self._paren_placeholder
+
+    self.strings = self._ParseOutHelper(self._str_placeholder, ["'", '"'],
+                                        'quotes')
+    self.parens = self._ParseOutHelper(self._paren_placeholder, ['(', ')'],
+                                       'parens')
+    Debug('Strings: %s' % self.strings)
+    Debug('Parens: %s' % self.parens)
+
+  def _ParseOutHelper(self, placeholder, delims, mode):
+    """Replace all text within delims with placeholders.
+
+    Args:
+      placeholder: string
+      delims: list of strings
+      mode: string
+          'parens': if there are 2 delims treat the first as opening
+                    and second as closing, such as with ( and )
+          'quotes': treat each delim as either opening or
+                    closing and require the same one to terminate the block,
+                    such as with ' and "
+
+    Returns:
+      list: [value1, value2, ...]
+
+    Raises:
+      ParseError: unable to parse out delims
+      ExecuteError: Invalid usage
+    """
+    if mode not in ['quotes', 'parens']:
+      raise ExecuteError('_ParseOutHelper: invalid mode ' + mode)
+    if mode == 'parens' and len(delims) != 2:
+      raise ExecuteError('_ParseOutHelper: delims must have 2 values '
+                         'in "parens" mode')
+    values = []
+    started = 0
+    new_args = ''
+    string = ''
+    my_id = 0
+    delim = ''
+    for c in self.args:
+      if c in delims:
+        if not started:
+          if mode == 'parens' and c != delims[0]:
+            raise ParseError('Found closing delimeter %s before '
+                             'corresponding %s' % (c, delims[0]))
+          started += 1
+          delim = c
+        else:
+          if ((mode == 'parens' and c == delim) or
+              (mode == 'quotes' and c != delim)):
+            string = '%s%s' % (string, c)
+            continue  # wait for matching delim
+
+          started -= 1
+          if not started:
+            values.append(string)
+            new_args = '%s %s' % (new_args, '%s%d' % (placeholder, my_id))
+            my_id += 1
+            string = ''
+
+      else:
+        if not started:
+          new_args = '%s%s' % (new_args, c)
+        else:
+          string = '%s%s' % (string, c)
+
+    if started:
+      if mode == 'parens':
+        waiting_for = delims[1]
+      else:
+        waiting_for = delim
+      raise ParseError('Unterminated block, waiting for ' + waiting_for)
+
+    self.args = new_args
+    Debug('Values: %s' % values)
+    return values
+
+  def _ReplaceStringLiterals(self, s, quotes=False):
+    """Replaces string placeholders with real values.
+
+    If quotes is set to True surround the returned value with single quotes
+
+    Args:
+      s: string
+      quotes: bool
+
+    Returns:
+      s: string
+    """
+    if s.strip().startswith(self._str_placeholder):
+      str_index = int(s.split(self._str_placeholder)[1])
+      s = self.strings[str_index]
+      if quotes:
+        s = "'" + s + "'"
+
+    return s
+
+  def _ReplaceParens(self, s):
+    """Replaces paren placeholders with real values.
+
+    Args:
+      s: string
+
+    Returns:
+      s: string
+    """
+    if s.strip().startswith(self._paren_placeholder):
+      str_index = int(s.split(self._paren_placeholder)[1])
+      s = self.parens[str_index].strip()
+
+    return s
+
+  def _RunDelete(self):
+    """Run the DELETE command.
+
+    Go through the rows in self.data matching them
+    against the conditions, if they fit delete the row leaving a placeholder
+    value (in order to keep the iteration process sane).  Afterward clean up
+    any empty values.
+
+    Returns:
+      dataset: [number of affected rows]
+    """
+    i = 0
+    length = len(self.data)
+    affected = 0
+    while i < length:
+      if self._MatchRow(self.data[i]):
+        self.data[i] = None
+        affected += 1
+
+      i += 1
+
+    # clean out the placeholders
+    while None in self.data:
+      self.data.remove(None)
+
+    return [affected]
+
+  def _RunUpdate(self):
+    """Run the UPDATE command.
+
+    Find the matching rows and update based on self.targets
+
+    Returns:
+      affected: [int]
+    Raises:
+      ExecuteError: failed to run UPDATE
+    """
+    i = 0
+    length = len(self.data)
+    affected = 0
+    while i < length:
+      if self._MatchRow(self.data[i]):
+        for target in self.targets:
+          if target not in self.header:
+            raise ExecuteError(target + ' is an invalid column name')
+          if self.header[target]['auto']:
+            raise ExecuteError(target + ' is type auto and connot be updated')
+
+          self.data[i][target] = self._TypeCheck(self.targets[target], target)
+        affected += 1
+
+      i += 1
+
+    return [affected]
+
+  def _RunInsert(self):
+    """Run the INSERT command.
+
+    Build up the row based on self.targets and table defaults, then append to
+    self.data
+
+    Returns:
+      affected: [int]
+    Raises:
+      ExecuteError: failed to run INSERT
+    """
+    new_row = {}
+    cols = self._SortHeaderColumns()
+    for col in cols:
+      if col in self.targets:
+        if self.header[col]['auto']:
+          raise ExecuteError(col + ' is type auto: cannot be modified')
+        new_row[col] = self.targets[col]
+
+      elif self.header[col]['null']:
+        new_row[col] = ''
+
+      elif self.header[col]['auto']:
+        new_row[col] = self._GetNextAuto(col)
+
+      else:
+        raise ExecuteError(col + ' cannot be empty or null')
+
+    self.data.append(new_row)
+    return [1]
+
+  def _GetNextAuto(self, col):
+    """Figure out the next value for col based on existing values.
+
+    Scan all the current values and return the highest one + 1.
+
+    Args:
+      col: string
+
+    Returns:
+      next: int
+
+    Raises:
+      ExecuteError: Failed to get auto inc
+    """
+    highest = 0
+    seen = []
+    for row in self.data:
+      if row[col] > highest:
+        highest = row[col]
+
+      if row[col] not in seen:
+        seen.append(row[col])
+      else:
+        raise ExecuteError('duplicate value %s in %s' % (row[col], col))
+
+    return highest + 1
+
+  def _RunSelect(self):
+    """Run the SELECT command.
+
+    Returns:
+      dataset: []
+
+    Raises:
+      ExecuteError: failed to run SELECT
+    """
+    dataset = []
+    if ['*'] == self.columns:
+      self.columns = self._SortHeaderColumns()
+
+    for row in self.data:
+      if self._MatchRow(row):
+        match = []
+        for col in self.columns:
+          if col in self.aliases:
+            concat = ''
+            for concat_col in self.aliases[col]:
+              if concat_col.startswith("'") and concat_col.endswith("'"):
+                concat += concat_col.strip("'")
+              elif concat_col not in self.header.keys():
+                raise ExecuteError('Table %s does not have a column %s' %
+                                   (self.table, concat_col))
+              else:
+                concat = '%s%s' % (concat, row[concat_col])
+
+            if not concat.strip():
+              raise ExecuteError('Empty CONCAT statement')
+
+            my_match = concat
+
+          elif col.startswith("'") and col.endswith("'"):
+            my_match = col.strip("'")
+          elif col not in self.header.keys():
+            raise ExecuteError('Table %s does not have a column %s' %
+                               (self.table, col))
+          else:
+            my_match = row[col]
+
+          match.append(self._UnEscapeChars(my_match))
+
+        dataset.append(match)
+
+    if self.count:
+      Debug('Dataset: %s' % dataset)
+      dataset = [len(dataset)]
+
+    if self.order_by:
+      if self.order_by not in self.header.keys():
+        raise ExecuteError('Unknown column %s in ORDER BY clause' %
+                           self.order_by)
+      pos = self._PositionByCol(self.order_by)
+      dataset = self._SortMatrixByCol(dataset, pos)
+
+    return dataset
+
+  def _SortMatrixByCol(self, dataset, pos):
+    """Sorts the matrix (array or arrays) based on a given column value.
+
+    That is, if given matrix that looks like:
+
+    [[1, 2, 3], [6, 5, 4], [3, 2, 1]]
+
+    given pos = 0 produce:
+
+    [[1, 2, 3], [3, 2, 1], [6, 5, 4]]
+
+    given pos = 1 produce:
+
+    [[1, 2, 3], [3, 2, 1], [6, 5, 4]]
+
+    given pos = 2 produce:
+
+    [[3, 2, 1], [1, 2, 3], [6, 5, 4]]
+
+    Works for both integer and string values of column.
+
+    Args:
+      dataset: [[], [], ...]
+      pos: int
+
+    Returns:
+      sorted: [[], [], ...]
+    """
+    # prepend value in pos to the beginning of every row
+    i = 0
+    while i < len(dataset):
+      dataset[i].insert(0, dataset[i][pos])
+      i += 1
+
+    # sort the matrix, which is done on the row we just prepended
+    dataset.sort()
+
+    # strip away the first value
+    i = 0
+    while i < len(dataset):
+      dataset[i].pop(0)
+      i += 1
+
+    return dataset
+
+  def _MatchRow(self, row):
+    """Matches the row against self.conditions.
+
+    Args:
+      row: ['val', 'val']
+
+    Returns:
+      Bool
+    """
+    match = True
+    # when there are no conditions we match everything
+    if not self.conditions:
+      return match
+
+    for condition in self.conditions:
+      cond_val = self.conditions[condition]
+      if condition not in self.header.keys():
+        match = False
+        break
+      else:
+        if cond_val != row[condition]:
+          match = False
+          break
+
+    return match
+
+  def _ProcessHeader(self):
+    """Parse out the header information.
+
+    Returns:
+      {col_name: {'type': string, 'null': string, 'auto': string, 'pos': int}}
+    """
+    header = self.fd.readline().strip()
+    cols = {}
+    pos = 0
+    for col in header.split():
+      col_name = col.split('(')[0]
+      col_type = col.split('(')[1].split(')')[0].split(',')[0]
+      col_null = False
+      col_auto = False
+      if ',' in col.split('(')[1].split(')')[0]:
+        if col.split('(')[1].split(')')[0].split(',')[1].lower() == 'null':
+          col_null = True
+        if col.split('(')[1].split(')')[0].split(',')[1].lower() == 'auto':
+          col_auto = True
+
+      cols[col_name] = {}
+      cols[col_name]['type'] = col_type
+      cols[col_name]['null'] = col_null
+      cols[col_name]['auto'] = col_auto
+      cols[col_name]['pos'] = pos
+      pos += 1
+
+    return cols
+
+  def _GetData(self):
+    """Reads table data into memory as a list of dicts keyed on column names.
+
+    Returns:
+      data: [{row}, {row}, ...]
+    Raises:
+      ExecuteError: failed to get data
+    """
+    data = []
+    row_num = 0
+    for row in self.fd:
+      row = row.rstrip('\n')
+      row_dict = {}
+      i = 0
+      field_start = 0
+      field_num = 0
+      while i < len(row):
+        if row[i] == ':':
+          # the following block is executed again after the while is done
+          val = row[field_start:i]
+          col = self._ColByPosition(field_num)
+          val = self._TypeCheck(val, col)
+          row_dict[col] = val
+
+          field_start = i + 1  # skip the colon itself
+          field_num += 1
+        if row[i] == '\\':
+          i += 2  # skip the next char since it's escaped
+        else:
+          i += 1
+
+      # handle the last field since we won't hit a : at the end
+      # sucks to duplicate the code outside the loop but I can't think
+      # of a better way :(
+
+      val = row[field_start:i]
+      col = self._ColByPosition(field_num)
+      val = self._TypeCheck(val, col)
+      row_dict[col] = val
+
+      # verify that all columns were created
+      for col in self.header:
+        if col not in row_dict:
+          raise ExecuteError('%s is missing from row %d in %s' %
+                             (col, row_num, self.table))
+
+      row_num += 1
+      data.append(row_dict)
+
+    return data
+
+  def _TypeCheck(self, val, col):
+    """Verify type of val based on the header.
+
+    Make sure the value is returned in quotes if it's a string
+    and as '' when it's empty and Null
+
+    Args:
+      val: string
+      col: string
+
+    Returns:
+      val: string
+
+    Raises:
+      ExecuteError: invalid value or column
+    """
+    if not val and not self.header[col]['null']:
+      raise ExecuteError(col + ' cannot be empty or null')
+
+    if (self.header[col]['type'].lower() == 'int' or
+        self.header[col]['type'].lower() == 'double'):
+      try:
+        if val:
+          val = eval(val)
+      except NameError, e:
+        raise ExecuteError('Failed to parse %s in %s '
+                           '(unable to convert to type %s): %s' %
+                           (col, self.table, self.header[col]['type'], e))
+      except SyntaxError, e:
+        raise ExecuteError('Failed to parse %s in %s '
+                           '(unable to convert to type %s): %s' %
+                           (col, self.table, self.header[col]['type'], e))
+
+    return val
+
+  def _ColByPosition(self, pos):
+    """Returns column name based on position.
+
+    Args:
+      pos: int
+
+    Returns:
+      column: string
+
+    Raises:
+      ExecuteError: invalid column
+    """
+    for col in self.header:
+      if self.header[col]['pos'] == pos:
+        return col
+
+    raise ExecuteError('Header does not contain column %d' % pos)
+
+  def _PositionByCol(self, col):
+    """Returns position of the column based on the name.
+
+    Args:
+      col: string
+
+    Returns:
+      pos: int
+
+    Raises:
+      ExecuteError: invalid column
+    """
+    if col not in self.header.keys():
+      raise ExecuteError(col + ' is not a valid column name')
+
+    return self.header[col]['pos']
+
+  def _SortHeaderColumns(self):
+    """Sort column names by position.
+
+    Returns:
+      sorted: [col1, col2, ...]
+
+    Raises:
+      ExecuteError: unable to sort header
+    """
+    cols = self.header.keys()
+    sorted_cols = [''] * len(cols)
+    for col in cols:
+      pos = self.header[col]['pos']
+      sorted_cols[pos] = col
+
+    if '' in sorted_cols:
+      raise ExecuteError('Unable to sort header columns: %s' % cols)
+
+    return sorted_cols
+
+  def OpenTable(self):
+    """Opens the table file and places its content into memory.
+
+    Raises:
+      ExecuteError: unable to open table
+    """
+    # if we already have a header assume multiple queries on same table
+    # (can't use self.data in case the table was empty to begin with)
+    if self.header:
+      return
+
+    try:
+      self.fd = open(os.path.join(self.location, self.table), 'r')
+      self.header = self._ProcessHeader()
+
+      if self.command in ['INSERT', 'DELETE', 'UPDATE']:
+        fcntl.flock(self.fd, fcntl.LOCK_EX)
+
+      self.data = self._GetData()
+      self.orig_data = self.data[:]  # save a copy of the data before modifying
+
+    except IOError, e:
+      raise ExecuteError('Unable to open table %s: %s' % (self.table, e))
+
+    Debug('Header is: %s' % self.header)
+
+    # type check the conditions
+    for cond in self.conditions:
+      if cond not in self.header.keys():
+        raise ExecuteError('unknown column %s in WHERE clause' % cond)
+      self.conditions[cond] = self._TypeCheck(self.conditions[cond], cond)
+
+    # type check the targets
+    for target in self.targets:
+      if target not in self.header.keys():
+        raise ExecuteError('unknown column in targets:  %s' % target)
+      self.targets[target] = self._TypeCheck(self.targets[target], target)
+
+    Debug('Type checked conditions: %s' % self.conditions)
+
+    Debug('Data is:')
+    for row in self.data:
+      Debug('=======================')
+      Debug(row)
+    Debug('=======================')
+
+  def WriteTempTable(self):
+    """Write table header and data.
+
+    First write header and data to a temp file,
+    then move the tmp file to replace the original table file.
+    """
+    self.temp_file = tempfile.NamedTemporaryFile()
+    Debug('temp_file: ' + self.temp_file.name)
+    # write header
+    columns = self._SortHeaderColumns()
+    header = ''
+    for col in columns:
+      header = '%s %s' % (header, col)
+      header = '%s(%s' % (header, self.header[col]['type'])
+      if self.header[col]['null']:
+        header = '%s,null)' % header
+      elif self.header[col]['auto']:
+        header = '%s,auto)' % header
+      else:
+        header = '%s)' % header
+
+    self.temp_file.write(header.strip() + '\n')
+
+    # write data
+    for row in self.data:
+      row_str = ''
+      for col in columns:
+        row_str = '%s:%s' % (row_str, row[col])
+
+      self.temp_file.write(row_str[1:] + '\n')
+
+    self.temp_file.flush()
+
+  def MoveTableIntoPlace(self):
+    """Replace the real table with the temp one.
+
+    Diff the new data against the original and replace the table when they are
+    different.
+    """
+    if self.data != self.orig_data:
+      temp_file = self.temp_file.name
+      table_file = os.path.join(self.location, self.table)
+      Debug('Copying %s to %s' % (temp_file, table_file))
+      shutil.copy(self.temp_file.name, self.location + '/' + self.table)
+
+  def _ParsePairs(self, s, delimeter):
+    """Parses out name value pairs from a string.
+
+    String contains name=value pairs
+    separated by a delimiter (such as "and" or ",")
+
+    Args:
+      s: string
+      delimeter: string
+
+    Returns:
+      my_dict: dictionary
+
+    Raises:
+      ParseError: unable to parse pairs
+    """
+    my_dict = {}
+    Debug('parse pairs: [%s]' % s)
+    pairs = s.split(delimeter)
+    for pair in pairs:
+      if '=' not in pair:
+        raise ParseError('Invalid condition pair: ' + pair)
+
+      split = pair.split('=')
+      Debug('split: %s' % split)
+      if len(split) != 2:
+        raise ParseError('Invalid condition pair: ' + pair)
+
+      col = split[0].strip()
+      if not col or not split[1].strip() or ' ' in col:
+        raise ParseError('Invalid condition pair: ' + pair)
+
+      val = self._ReplaceStringLiterals(split[1].strip())
+      my_dict[col] = val
+
+    return my_dict
+
+
+class Error(Exception):
+  """DBText error."""
+
+
+class ParseError(Error):
+  """Parse error."""
+
+
+class NotSupportedError(Error):
+  """Not Supported error."""
+
+
+class ExecuteError(Error):
+  """Execute error."""
+
+
+def main(argv):
+
+  if len(argv) < 2:
+    print 'Usage %s query' % argv[0]
+    sys.exit(1)
+
+  if 'DBTEXT_PATH' not in os.environ or not os.environ['DBTEXT_PATH']:
+    print 'DBTEXT_PATH must be set'
+    sys.exit(1)
+  else:
+    location = os.environ['DBTEXT_PATH']
+
+  try:
+    conn = DBText(location)
+    dataset = conn.Execute(' '.join(argv[1:]))
+    if dataset:
+      for row in dataset:
+        if conn.command != 'SELECT':
+          print 'Updated %s, rows affected: %d' % (conn.table, row)
+        else:
+          print row
+  except Error, e:
+    print e
+    sys.exit(1)
+
+
+if __name__ == '__main__':
+  main(sys.argv)

+ 603 - 0
tools/dbtextdb/dbtextdb_test.py

@@ -0,0 +1,603 @@
+#!/usr/bin/python
+#
+# Copyright 2008 Google Inc. All Rights Reserved.
+
+"""Test for dbtext_query."""
+
+__author__ = '[email protected] (Herman Sheremetyev)'
+
+import time
+import unittest
+from dbtextdb import *
+
+
+class DBTextTest(unittest.TestCase):
+
+  def setUp(self):
+    self.time_now = '%s' % int(time.time())
+    self.time_now = self.time_now[0:-2] + '00'
+
+  def testParseQuery(self):
+    db_conn = DBText('./tests')
+    # bad command
+    query_bad_command = 'selecta * from table;'
+    self.assertRaises(ParseError, db_conn.ParseQuery, query_bad_command)
+    #  normal query
+    query_normal = 'select * from subscriber;'
+    db_conn.ParseQuery(query_normal)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.columns == ['*'])
+    db_conn.CleanUp()
+    # normal query with condition
+    query_normal_cond = 'select * from subscriber where column="value";'
+    db_conn.ParseQuery(query_normal_cond)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.columns == ['*'])
+    self.assert_(db_conn.strings == ['value'])
+    self.assert_(not db_conn.count)
+    self.assert_(db_conn.conditions == {'column': 'value'})
+    db_conn.CleanUp()
+    # normal query with multiple conditions
+    query_normal_cond = ('select * from subscriber where column="value1" and '
+                         'col2=" another value " and col3= foo and a="";')
+    db_conn.ParseQuery(query_normal_cond)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.columns == ['*'])
+    self.assert_(db_conn.strings == ['value1', ' another value ', ''])
+    self.assertEqual(db_conn.conditions, {'column': 'value1',
+                                          'col2': ' another value ',
+                                          'col3': 'foo', 'a': ''})
+    db_conn.CleanUp()
+    # normal query with count
+    query_normal_count = 'select count(*) from subscriber;'
+    db_conn.ParseQuery(query_normal_count)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.columns == ['*'])
+    self.assert_(db_conn.count == True)
+    db_conn.CleanUp()
+    # normal query with now()
+    query_normal_count = 'select count(*) from subscriber where time=now();'
+    db_conn.ParseQuery(query_normal_count)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.columns == ['*'])
+    self.assert_(db_conn.count == True)
+    self.assertEqual(db_conn.conditions, {'time': self.time_now})
+    db_conn.CleanUp()
+    # normal delete query
+    query_normal_delete = 'delete from subscriber where foo = 2;'
+    db_conn.ParseQuery(query_normal_delete)
+    self.assert_(db_conn.command == 'DELETE')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.conditions, {'foo': '2'})
+    db_conn.CleanUp()
+    # normal insert values query with no into
+    query_normal_insert_values = ('insert subscriber (col1, col2, col3) '
+                                  'values (1, "foo", "");')
+    db_conn.ParseQuery(query_normal_insert_values)
+    self.assert_(db_conn.command == 'INSERT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.targets, {'col1': '1', 'col2': 'foo', 'col3': ''})
+    db_conn.CleanUp()
+    # normal insert values query with into
+    query_normal_insert_into_values = ('insert into subscriber (col1, col2) '
+                                       'values (1, "foo");')
+    db_conn.ParseQuery(query_normal_insert_into_values)
+    self.assert_(db_conn.command == 'INSERT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.targets, {'col1': '1', 'col2': 'foo'})
+    db_conn.CleanUp()
+    # normal insert values query with now()
+    query_normal_insert_into_values = ('insert into subscriber (a, b, c) '
+                                       'values (NOW(), "foo", now());')
+    db_conn.ParseQuery(query_normal_insert_into_values)
+    self.assert_(db_conn.command == 'INSERT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.targets, {'a': self.time_now, 'b': 'foo',
+                                       'c': self.time_now})
+    db_conn.CleanUp()
+    # bad insert: missing table
+    bad_insert_query_missing_table = ('insert into (col1, col2) '
+                                      'values (1, "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_missing_table)
+    db_conn.CleanUp()
+    # bad insert: missing parens
+    bad_insert_query_missing_parens = ('insert into test col1, col2 '
+                                       'values (1, "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_missing_parens)
+    db_conn.CleanUp()
+    # bad insert: missing paren
+    bad_insert_query_missing_paren = ('insert into test (col1, col2) '
+                                      'values 1, "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_missing_paren)
+    db_conn.CleanUp()
+    # bad insert: missing quote
+    bad_insert_query_missing_quote = ('insert into test (col1, col2) '
+                                      '(values 1, "foo);')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_missing_quote)
+    db_conn.CleanUp()
+    # bad insert: missing values
+    bad_insert_query_missing_values = ('insert into test (col1, col2) '
+                                       '( 1, "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_missing_values)
+    db_conn.CleanUp()
+    # bad insert: mislplaced values
+    bad_insert_query_misplaced_values = ('insert into test values (col1, col2) '
+                                         '( 1, "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_misplaced_values)
+    db_conn.CleanUp()
+    # bad insert: extra values
+    bad_insert_query_extra_values = ('insert into test values (col1, col2) '
+                                     ' values values ( 1, "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_extra_values)
+    db_conn.CleanUp()
+    # bad insert: extra paren set
+    bad_insert_query_extra_paren_set = ('insert into test values (col1, col2) '
+                                        ' values ( 1, "foo")();')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_extra_paren_set)
+    db_conn.CleanUp()
+    # bad insert: mismatched value pairs
+    bad_insert_query_mismatched_vals = ('insert into test values (col1, col2) '
+                                        ' values ("foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_insert_query_mismatched_vals)
+    db_conn.CleanUp()
+    # normal insert set  query with no into
+    query_normal_insert_set = ('insert subscriber set col= 1, col2 ="\'f\'b";')
+    db_conn.ParseQuery(query_normal_insert_set)
+    self.assert_(db_conn.command == 'INSERT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.targets, {'col': '1', 'col2': '\'f\'b'})
+    db_conn.CleanUp()
+    # normal update
+    query_normal_update = ('update subscriber set col1= 1, col2 ="foo";')
+    db_conn.ParseQuery(query_normal_update)
+    self.assert_(db_conn.command == 'UPDATE')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.targets, {'col1': '1', 'col2': 'foo'})
+    db_conn.CleanUp()
+    # normal update with condition
+    query_normal_update_cond = ('update subscriber set col1= 1, col2 ="foo" '
+                                'where   foo = "bar" and id=1 and a="";')
+    db_conn.ParseQuery(query_normal_update_cond)
+    self.assert_(db_conn.command == 'UPDATE')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assertEqual(db_conn.targets, {'col1': '1', 'col2': 'foo'})
+    self.assertEqual(db_conn.conditions, {'foo': 'bar', 'id': '1', 'a': ''})
+    db_conn.CleanUp()
+    # bad update: extra parens
+    bad_update_query_extra_paren = ('update test set (col1 = "foo");')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_extra_paren)
+    db_conn.CleanUp()
+    # bad update: missing table
+    bad_update_query_missing_table = ('update SET col1 = "foo";')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_missing_table)
+    db_conn.CleanUp()
+    # bad update: missing set
+    bad_update_query_missing_set = ('update test sett col1 = "foo";')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_missing_set)
+    db_conn.CleanUp()
+    # bad update: missing val
+    bad_update_query_missing_val = ('update test set col1 =;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_missing_val)
+    db_conn.CleanUp()
+    # bad update: missing comma
+    bad_update_query_missing_comma = ('update test set col1 = "foo" crap =5;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_missing_comma)
+    db_conn.CleanUp()
+    # bad update: missing equal
+    bad_update_query_missing_equal = ('update test set col1 = "foo", and 5;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_missing_equal)
+    db_conn.CleanUp()
+    # bad update: missing col
+    bad_update_query_missing_col = ('update test set col1 = "foo", = 5;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_missing_col)
+    db_conn.CleanUp()
+    # bad update: double col
+    bad_update_query_double_col = ('update test set col1 = "foo", and a = 5;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_update_query_double_col)
+    db_conn.CleanUp()
+    # normal query with multiple columns
+    query_normal_count = 'select col1, "col 2",col3  , "col4" from subscriber;'
+    db_conn.ParseQuery(query_normal_count)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.strings == ['col 2', 'col4'])
+    self.assert_(db_conn.columns == ['col1', "'col 2'", 'col3', "'col4'"])
+    db_conn.CleanUp()
+    # normal query with ORDER BY
+    query_normal_order_by = ('select col1, col2 from test'
+                             ' ORDER by col1;')
+    db_conn.ParseQuery(query_normal_order_by)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'test')
+    self.assert_(db_conn.columns == ['col1', 'col2'])
+    self.assert_(db_conn.order_by == 'col1')
+    db_conn.CleanUp()
+    # normal query with ORDER BY with conditions
+    query_normal_order_by_cond = ('select col1, col2 from test where col="asdf"'
+                                  ' and col2  = "foo" ORDER by col;')
+    db_conn.ParseQuery(query_normal_order_by_cond)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'test')
+    self.assert_(db_conn.columns == ['col1', 'col2'])
+    self.assert_(db_conn.conditions == {'col': 'asdf', 'col2': 'foo'})
+    self.assert_(db_conn.order_by == 'col')
+    db_conn.CleanUp()
+    # normal query with CONCAT
+    query_normal_concat = ('select concat(uname,"@", domain) as email_addr '
+                           'from subscriber where id=3;')
+    db_conn.ParseQuery(query_normal_concat)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'subscriber')
+    self.assert_(db_conn.columns == ['email_addr'])
+    self.assert_(db_conn.conditions == {'id': '3'})
+    self.assert_(db_conn.aliases == {'email_addr': ['uname', "'@'", 'domain']})
+    db_conn.CleanUp()
+    # normal query with multiple CONCAT
+    query_normal_mult_concat = ('select concat(uname,"@", domain) as email,'
+                                ' foo as "bar" from table where id=3;')
+    db_conn.ParseQuery(query_normal_mult_concat)
+    self.assert_(db_conn.command == 'SELECT')
+    self.assert_(db_conn.table == 'table')
+    self.assert_(db_conn.columns == ['email', "'bar'"])
+    self.assert_(db_conn.conditions == {'id': '3'})
+    self.assert_(db_conn.aliases == {"'bar'": ['foo'],
+                                     'email': ['uname', "'@'", 'domain']})
+    db_conn.CleanUp()
+    # bad query with CONCAT missing AS
+    bad_query_concat_no_as = ('select concat(col1,col2) from test'
+                              ' ORDER by col1 col2;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_concat_no_as)
+    db_conn.CleanUp()
+    # bad query with CONCAT missing AS arg
+    bad_query_concat_no_as_arg = ('select concat(col1,col2) as from test'
+                                  ' ORDER by col1 col2;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_concat_no_as_arg)
+    db_conn.CleanUp()
+    # bad query with CONCAT missing paren
+    bad_query_concat_no_paren = ('select concat(col1,col2  as foo from test'
+                                 ' ORDER by col1 col2;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_concat_no_paren)
+    db_conn.CleanUp()
+    # bad query with ORDER BY multiple columns
+    bad_query_mult_order_by = ('select col1, col2 from test'
+                               ' ORDER by col1 col2;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_mult_order_by)
+    db_conn.CleanUp()
+    # bad select query: missing FROM
+    bad_query_missing_from = 'select * subscriber;'
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_missing_from)
+    db_conn.CleanUp()
+    # bad select query: missing comma in columns
+    bad_query_missing_comma = 'select col1 col2 col3 from subscriber;'
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_missing_comma)
+    db_conn.CleanUp()
+    # bad select query: extra comma in columns
+    bad_query_extra_comma = 'select col1,col2, from subscriber;'
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_extra_comma)
+    db_conn.CleanUp()
+    bad_query_extra_comma = 'select col1,,col2 from subscriber;'
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_extra_comma)
+    db_conn.CleanUp()
+    bad_query_extra_comma = 'select ,col1,col2 from subscriber;'
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_extra_comma)
+    db_conn.CleanUp()
+    # bad conditions: missing AND
+    bad_query_missing_and = ('select * from subscriber where column = asdf '
+                             ' something=missing_and;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_missing_and)
+    db_conn.CleanUp()
+    # bad conditions: missing value
+    bad_query_missing_value = ('select * from subscriber where column = asdf'
+                               ' and something=;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_missing_value)
+    db_conn.CleanUp()
+    # bad query: unterminated string
+    bad_query_unterm_str = ('select * from test where column ="asdf;')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_query_unterm_str)
+    db_conn.CleanUp()
+    # bad select query: missing table
+    bad_select_query_missing_table = ('select * from where column ="asdf";')
+    self.assertRaises(ParseError, db_conn.ParseQuery,
+                      bad_select_query_missing_table)
+    db_conn.CleanUp()
+
+  def testOpenTable(self):
+    # check that header is retrieved and parsed correctly
+    query = ('select * from test;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    db_conn.OpenTable()
+    self.assertEqual(db_conn.header, {'col2': {'auto': False, 'null': True,
+                                               'type': 'string', 'pos': 2},
+                                      'id': {'auto': True, 'null': False,
+                                             'type': 'int', 'pos': 1},
+                                      'col1': {'auto': False, 'null': False,
+                                               'type': 'string', 'pos': 0}})
+
+    # check that data is retrieved and parsed correctly
+    query = ('select * from test;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    db_conn.OpenTable()
+    self.assertEqual(db_conn.data,
+                     [{'col1': 'item1\\:', 'id': 1, 'col2': 'item2'},
+                      {'col1': 'it\\:em1\\\\', 'id': 2, 'col2': ''},
+                      {'col1': '\\:item3', 'id': 3, 'col2': 'asdf\\:'}])
+
+    # missing table
+    query = ('select * from non_existent_table;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    self.assertRaises(ExecuteError, db_conn.OpenTable)
+
+    # type string value in type int column
+    query = ('select * from bad_table_wrong_type;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    self.assertRaises(ExecuteError, db_conn.OpenTable)
+
+    # row has fewer fields than header
+    query = ('select * from bad_table_short_row;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    self.assertRaises(ExecuteError, db_conn.OpenTable)
+
+    # row has more fields than header
+    query = ('select * from bad_table_long_row;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    self.assertRaises(ExecuteError, db_conn.OpenTable)
+
+    # value mismatch: non-null column is null
+    query = ('select * from bad_table_null;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    self.assertRaises(ExecuteError, db_conn.OpenTable)
+
+    # value mismatch: int column is string
+    query = ('select * from bad_table_int;')
+    db_conn = DBText('./tests')
+    db_conn.ParseQuery(query)
+    self.assertRaises(ExecuteError, db_conn.OpenTable)
+
+  def testExecute(self):
+    db_conn = DBText('./tests')
+    writethru = False
+
+    # test count
+    query = ("select count(*) from subscriber where username='monitor' and"
+             " domain='test.com';")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [2])
+    db_conn.CleanUp()
+
+    query = ('select count(*) from subscriber where '
+             "username='test2';")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [1])
+    db_conn.CleanUp()
+
+    query = ('select count(*) from subscriber where '
+             "username='test1';")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [3])
+    db_conn.CleanUp()
+
+    # test concat
+    query = ("select concat(username, '@', domain) as email_addr from "
+             'subscriber where id = 3;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [['[email protected]']])
+    db_conn.CleanUp()
+
+    # test select
+    query = ("select * from subscriber where username='test2' and"
+             " domain='test.com';")
+    expected_result = [[3, 'test2', 'test.com', 'password', '', '',
+                        '[email protected]', 1202336327,
+                        '9fe9bfa1315b8202838838c3807a0a32',
+                        'fac1f260ebda200719de4aa29880ee05', '', '']]
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, expected_result)
+    db_conn.CleanUp()
+
+    query = ('select * from subscriber where id = 3;')
+    expected_result = [[3, 'test2', 'test.com', 'password', '', '',
+                        '[email protected]', 1202336327,
+                        '9fe9bfa1315b8202838838c3807a0a32',
+                        'fac1f260ebda200719de4aa29880ee05', '', '']]
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, expected_result)
+    db_conn.CleanUp()
+
+    # test order by
+    query = ('select * from test order by non_existent_column;')
+    self.assertRaises(ExecuteError, db_conn.Execute, query, writethru)
+    db_conn.CleanUp()
+
+    query = ('select * from unsorted_table order by id;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [[1, 'fred', 'test.com', 2125551234],
+                              [2, 'james', 'test4.com', 2125551231],
+                              [3, 'mike', 'test2.com', 2125551239],
+                              [4, 'alex', 'test1.com', 2125551237],
+                              [5, 'john', 'test.com', 2125551240]])
+    db_conn.CleanUp()
+
+    query = ('select * from unsorted_table order by user;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [[4, 'alex', 'test1.com', 2125551237],
+                              [1, 'fred', 'test.com', 2125551234],
+                              [2, 'james', 'test4.com', 2125551231],
+                              [5, 'john', 'test.com', 2125551240],
+                              [3, 'mike', 'test2.com', 2125551239]])
+    db_conn.CleanUp()
+
+    query = ('select * from unsorted_table order by domain;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [[1, 'fred', 'test.com', 2125551234],
+                              [5, 'john', 'test.com', 2125551240],
+                              [4, 'alex', 'test1.com', 2125551237],
+                              [3, 'mike', 'test2.com', 2125551239],
+                              [2, 'james', 'test4.com', 2125551231]])
+    db_conn.CleanUp()
+
+    query = ('select * from unsorted_table order by number;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [[2, 'james', 'test4.com', 2125551231],
+                              [1, 'fred', 'test.com', 2125551234],
+                              [4, 'alex', 'test1.com', 2125551237],
+                              [3, 'mike', 'test2.com', 2125551239],
+                              [5, 'john', 'test.com', 2125551240]])
+    db_conn.CleanUp()
+
+    # test delete
+    query = ('delete from unsorted_table where id = 3;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(result, [1])
+    self.assertEqual(db_conn.data, [{'id': 1, 'user': 'fred', 'domain':
+                                     'test.com', 'number': 2125551234},
+                                    {'id': 4, 'user': 'alex', 'domain':
+                                     'test1.com', 'number': 2125551237},
+                                    {'id': 2, 'user': 'james', 'domain':
+                                     'test4.com', 'number': 2125551231},
+                                    {'id': 5, 'user': 'john', 'domain':
+                                     'test.com', 'number': 2125551240}])
+    db_conn.CleanUp()
+
+    query = ('delete from unsorted_table where id = 5;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'id': 1, 'user': 'fred', 'domain':
+                                     'test.com', 'number': 2125551234},
+                                    {'id': 4, 'user': 'alex', 'domain':
+                                     'test1.com', 'number': 2125551237},
+                                    {'id': 2, 'user': 'james', 'domain':
+                                     'test4.com', 'number': 2125551231},
+                                    {'id': 3, 'user': 'mike', 'domain':
+                                     'test2.com', 'number': 2125551239}])
+    db_conn.CleanUp()
+
+    # test insert with auto increment
+    query = ("insert into unsorted_table set user='jake', domain='test.com',"
+             'number = 2125551456;')
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'id': 1, 'user': 'fred', 'domain':
+                                     'test.com', 'number': 2125551234},
+                                    {'id': 4, 'user': 'alex', 'domain':
+                                     'test1.com', 'number': 2125551237},
+                                    {'id': 2, 'user': 'james', 'domain':
+                                     'test4.com', 'number': 2125551231},
+                                    {'id': 3, 'user': 'mike', 'domain':
+                                     'test2.com', 'number': 2125551239},
+                                    {'id': 5, 'user': 'john', 'domain':
+                                     'test.com', 'number': 2125551240},
+                                    {'id': 6, 'user': 'jake', 'domain':
+                                     'test.com', 'number': 2125551456}])
+    db_conn.CleanUp()
+
+    # test insert with null value
+    query = ("insert into test set col1='asdf';")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'col2': 'item2', 'id': 1, 'col1':
+                                     'item1\\:'},
+                                    {'col2': '', 'id': 2, 'col1':
+                                     'it\\:em1\\\\'},
+                                    {'col2': 'asdf\\:', 'id': 3, 'col1':
+                                     '\\:item3'},
+                                    {'col2': '', 'id': 4, 'col1': 'asdf'}])
+    db_conn.CleanUp()
+
+    # test insert with null value alternate syntax
+    query = ("insert test ( col1) values ('asdf');")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'col2': 'item2', 'id': 1, 'col1':
+                                     'item1\\:'},
+                                    {'col2': '', 'id': 2, 'col1':
+                                     'it\\:em1\\\\'},
+                                    {'col2': 'asdf\\:', 'id': 3, 'col1':
+                                     '\\:item3'},
+                                    {'col2': '', 'id': 4, 'col1': 'asdf'}])
+    db_conn.CleanUp()
+
+    # test insert with colon inside value
+    query = ("insert into test set col1='as:df';")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'col2': 'item2', 'id': 1, 'col1':
+                                     'item1\\:'},
+                                    {'col2': '', 'id': 2, 'col1':
+                                     'it\\:em1\\\\'},
+                                    {'col2': 'asdf\\:', 'id': 3, 'col1':
+                                     '\\:item3'},
+                                    {'col2': '', 'id': 4, 'col1': 'as\:df'}])
+    db_conn.CleanUp()
+
+    # test insert with escaped colon inside value
+    query = ("insert into test set col1='as\:df';")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'col2': 'item2', 'id': 1, 'col1':
+                                     'item1\\:'},
+                                    {'col2': '', 'id': 2, 'col1':
+                                     'it\\:em1\\\\'},
+                                    {'col2': 'asdf\\:', 'id': 3, 'col1':
+                                     '\\:item3'},
+                                    {'col2': '', 'id': 4, 'col1': 'as\\\\\\:df'}])
+    db_conn.CleanUp()
+
+    # bad insert with non-null column not provided
+    query = ("insert test ( col2) values ('asdf');")
+    self.assertRaises(ExecuteError, db_conn.Execute, query, writethru)
+    db_conn.CleanUp()
+
+    # bad insert with auto column forced
+    query = ("insert test (col1, id) values ('asdf', 4);")
+    self.assertRaises(ExecuteError, db_conn.Execute, query, writethru)
+    db_conn.CleanUp()
+
+    # test update with null value
+    query = ("update test set col2='' where id = 3;")
+    result = db_conn.Execute(query, writethru)
+    self.assertEqual(db_conn.data, [{'col2': 'item2', 'id': 1, 'col1':
+                                     'item1\\:'},
+                                    {'col2': '', 'id': 2, 'col1':
+                                     'it\\:em1\\\\'},
+                                    {'col2': '', 'id': 3, 'col1':
+                                     '\\:item3'}])
+    db_conn.CleanUp()
+
+
+if __name__ == '__main__':
+  unittest.main()

+ 4 - 0
tools/dbtextdb/tests/bad_table_auto_dupe

@@ -0,0 +1,4 @@
+col1(string) id(int) col2(string,null) col3(string)
+item1\::1:item2:asdf
+it\:em1:2:\::lk
+\:item3:2:asdf\::asdf

+ 4 - 0
tools/dbtextdb/tests/bad_table_int

@@ -0,0 +1,4 @@
+col1(string) id(int) col2(string,null) col3(string)
+item1\::string:item2:asdf
+it\:em1:2:\::
+\:item3:3:asdf\::asdf

+ 4 - 0
tools/dbtextdb/tests/bad_table_long_row

@@ -0,0 +1,4 @@
+col1(string) id(int) col2(string,null) col3(string)
+item1\::1:item2:asdf
+it\:em1:2:\::lk:
+\:item3:3:asdf\::asdf

+ 4 - 0
tools/dbtextdb/tests/bad_table_null

@@ -0,0 +1,4 @@
+col1(string) id(int) col2(string,null) col3(string)
+item1\::1:item2:asdf
+it\:em1:2:\::
+\:item3:3:asdf\::asdf

+ 4 - 0
tools/dbtextdb/tests/bad_table_short_row

@@ -0,0 +1,4 @@
+col1(string) id(int) col2(string,null) col3(string)
+item1\::1:item2:asdf
+it\:em1:2:\:
+\:item3:3:asdf\::asdf

+ 4 - 0
tools/dbtextdb/tests/bad_table_wrong_type

@@ -0,0 +1,4 @@
+col1(string) id(int) col2(string,null)
+item1\::1:item2
+it\:em1:2a:
+\:item3:3:asdf\:

+ 7 - 0
tools/dbtextdb/tests/subscriber

@@ -0,0 +1,7 @@
+id(int,auto) username(string) domain(string) password(string) first_name(string,null) last_name(string,null) email_address(string) datetime_created(int,null) ha1(string) ha1b(string) timezone(string,null) rpid(string,null)
+1:monitor:test.com:monitor:::[email protected]:1202336326:88bb9163d2b15a3c62def3975fdca72d:27b0e1a4da287171989894532c9f783e::
+2:test1:test.com:password:::[email protected]:1202336326:fb43223b02f6331f917cc2491235aad8:9b090c888afe748e3bf5b8681e71f29e::
+3:test2:test.com:password:::[email protected]:1202336327:9fe9bfa1315b8202838838c3807a0a32:fac1f260ebda200719de4aa29880ee05::
+4:monitor:test.com:monitor:::[email protected]:1202336326:88bb9163d2b15a3c62def3975fdca72d:27b0e1a4da287171989894532c9f783e::
+5:test1:test.com:password:::[email protected]:1202336375:fb43223b02f6331f917cc2491235aad8:9b090c888afe748e3bf5b8681e71f29e::
+6:test1:test.com:password:::[email protected]:1202336375:fb43223b02f6331f917cc2491235aad8:9b090c888afe748e3bf5b8681e71f29e::

+ 4 - 0
tools/dbtextdb/tests/test

@@ -0,0 +1,4 @@
+col1(string) id(int,auto) col2(string,null)
+item1\::1:item2
+it\:em1\\:2:
+\:item3:3:asdf\:

+ 6 - 0
tools/dbtextdb/tests/unsorted_table

@@ -0,0 +1,6 @@
+id(int,auto) user(string) domain(string,null) number(int)
+1:fred:test.com:2125551234
+4:alex:test1.com:2125551237
+2:james:test4.com:2125551231
+3:mike:test2.com:2125551239
+5:john:test.com:2125551240

+ 5 - 0
tools/dbtextdb/tests/unsorted_table2

@@ -0,0 +1,5 @@
+id(int,auto) user(string) domain(string,null) number(int)
+1:fred:test.com:2125551234
+4:alex:test1.com:2125551237
+2:james:test4.com:2125551231
+5:john:test.com:2125551240

+ 2544 - 0
tools/kamctl

@@ -0,0 +1,2544 @@
+#!/bin/bash
+#
+# $Id$
+#
+# control tool for maintaining Kamailio
+#
+#===================================================================
+
+PATH=$PATH:/usr/local/sbin/
+
+# for testing only, please don't enable this in production environments
+# as this introduce security risks
+TEST="false"
+
+### include config files
+if [ -f /etc/kamailio/kamctlrc ]; then
+	. /etc/kamailio/kamctlrc
+fi
+if [ -f /usr/local/etc/kamailio/kamctlrc ]; then
+	. /usr/local/etc/kamailio/kamctlrc
+fi
+if [ -f ~/.kamctlrc ]; then
+	. ~/.kamctlrc
+fi
+
+if [ $TEST = "true" ]; then
+	if [ -f ./kamctlrc ]; then
+		. ./kamctlrc
+	fi
+fi
+
+
+### force values for variables in this section
+# you better set the variables in ~/.kamctlrc
+if [ -z "$ETCDIR" ] ; then
+	ETCDIR="/usr/local/etc/kamailio"
+fi
+
+### version for this script
+VERSION='$Revision$'
+
+if [ -z "$MYDIR" ] ; then
+	MYDIR=`dirname $0`
+fi
+
+if [ -z "$MYLIBDIR" ] ; then
+	MYLIBDIR="/usr/local/lib/kamailio/kamctl"
+	if [ ! -d "$MYLIBDIR" ]; then
+		MYLIBDIR=$MYDIR
+	fi
+fi
+
+##### ------------------------------------------------ #####
+### load base functions
+#
+if [ -f "$MYLIBDIR/kamctl.base" ]; then
+	. "$MYLIBDIR/kamctl.base"
+else
+	echo -e "Cannot load core functions '$MYLIBDIR/kamctl.base' - exiting ...\n"
+	exit -1
+fi
+
+#
+##### ------------------------------------------------ #####
+### DBENGINE
+#
+DBENGINELOADED=0
+case $DBENGINE in
+	MYSQL|mysql|MySQL)
+		if [ -f "$MYLIBDIR/kamctl.mysql" ]; then
+			. "$MYLIBDIR/kamctl.mysql"
+			DBENGINELOADED=1
+		fi
+		;;
+	PGSQL|pgsql|postgres|postgresql|POSTGRESQL)
+		if [ -f "$MYLIBDIR/kamctl.pgsql" ]; then
+			. "$MYLIBDIR/kamctl.pgsql"
+			DBENGINELOADED=1
+		fi
+		;;
+	ORACLE|oracle|Oracle)
+		if [ -f "$MYLIBDIR/kamctl.oracle" ]; then
+			. "$MYLIBDIR/kamctl.oracle"
+			DBENGINELOADED=1
+		fi
+		;;
+
+	DBTEXT|dbtext|textdb)
+		if [ -f "$MYLIBDIR/kamctl.dbtext" ]; then
+			. "$MYLIBDIR/kamctl.dbtext"
+			DBENGINELOADED=1
+		fi
+		;;
+	DB_BERKELEY|db_berkeley|BERKELEY|berkeley)
+		if [ -f "$MYLIBDIR/kamctl.db_berkeley" ]; then
+			. "$MYLIBDIR/kamctl.db_berkeley"
+			DBENGINELOADED=1
+		fi
+		;;
+
+esac
+
+if [ $DBENGINELOADED -eq 1 ] ; then
+	mdbg "database engine '$DBENGINE' loaded"
+elif [ -n "$DBENGINE" ] ; then
+	mwarn "database engine not found - tried '$DBENGINE'"
+fi
+
+#
+##### ------------------------------------------------ #####
+### CTLENGINE
+#
+CTLENGINELOADED=0
+if [ -z "$CTLENGINE" ] ; then
+	CTLENGINE="FIFO"
+fi
+case $CTLENGINE in
+	FIFO|fifo)
+		if [ -f "$MYLIBDIR/kamctl.fifo" ]; then
+			. "$MYLIBDIR/kamctl.fifo"
+			CTLENGINELOADED=1
+		fi
+		;;
+	UNIXSOCK|unixsock)
+		if [ -f "$MYLIBDIR/kamctl.unixsock" ]; then
+			. "$MYLIBDIR/kamctl.unixsock"
+			CTLENGINELOADED=1
+		fi
+		;;
+esac
+
+if [ $CTLENGINELOADED -eq 1 ] ; then
+	mdbg "Control engine '$CTLENGINE' loaded"
+else
+	mwarn "no control engine found - tried '$CTLENGINE'"
+fi
+
+#
+##### ------------------------------------------------ #####
+### common functions
+#
+usage() {
+	CMD=`basename $0`
+	if [ "0$VERIFY_ACL" -eq 1 ] ; then
+		EXTRA_TEXT="ACL privileges are: $ACL_GROUPS"
+	fi
+	cat <<EOF
+$0 $VERSION
+
+Existing commands:
+EOF
+for f in $USAGE_FUNCTIONS
+do
+	$f
+done
+	echo
+
+}
+
+require_dbengine() {
+	if [ $DBENGINELOADED -eq 0 ] ; then 
+		merr "This command requires a database engine - none was loaded"
+		exit -1
+	fi
+}
+
+require_ctlengine() {
+	if [ $CTLENGINELOADED -eq 0 ] ; then 
+		merr "This command requires a control engine - none was loaded"
+		exit -1
+	fi
+}
+
+#
+##### ------------------------------------------------ #####
+### combined functions (require db and/or ctl)
+#
+#
+
+#
+##### ------------------------------------------------ #####
+### helper functions (require db and/or ctl)
+#
+
+lower() {
+	echo $1 | tr [A-Z] [a-z]
+}
+
+# params: user
+# output: false if exists, true otherwise
+is_user() {
+	set_user $1
+	
+	QUERY="select count(*) from $SUB_TABLE where \
+$SUBSCRIBER_COLUMN='$OSERUSER' and $REALM_COLUMN='$OSERDOMAIN';"
+
+	CNT=`$DBROCMD "$QUERY" "$DBRAWPARAMS" | $EGREP -v ERROR | $LAST_LINE`
+	mdbg "is_user: user counter=$CNT"
+	if [ "$CNT" = "0" ] ; then
+		false
+	else
+		true
+	fi
+}
+
+
+# params: table, column, value
+# output: false if exists, true otherwise
+is_value_in_db() {
+	TABLE=$1
+	COL=$2
+	VALUE=$3
+	
+	QUERY="select count(*) from $TABLE where $COL='$VALUE';"
+	CNT=`$DBROCMD "$QUERY" "$DBRAWPARAMS" | $EGREP -v ERROR | $LAST_LINE`
+	mdbg "is_value_in_db: counter=$CNT"
+	if [ "$CNT" = "0" ] ; then
+		false
+	else
+		true
+	fi
+}
+
+#
+##### ------------------------------------------------ #####
+### ACL Management
+#
+acl() {
+	require_dbengine
+	case $1 in
+		show)
+			if [ $# -eq 2 ] ; then
+				is_user $2 
+				if [ $? -ne 0 ] ; then
+					mecho "Non-existent user '$2'. Still proceeding? [Y|N] "
+					read answer
+					if [ "$answer" = "y" -o "$answer" = "Y" ] ; then
+						minfo "Proceeding with non-local user"
+					else
+						exit 1
+					fi
+				fi
+				set_user $2
+				CLAUSE=" WHERE $ACL_USER_COLUMN='$OSERUSER' AND \
+					$ACL_DOMAIN_COLUMN='$OSERDOMAIN' "
+			elif [ $# -ne 1 ] ; then
+				usage_acl
+				exit 1
+			fi
+			QUERY="select * FROM $ACL_TABLE $CLAUSE ; "
+			$DBROCMD "$QUERY"
+
+			;;
+
+		grant)
+			if [ $# -lt 3 ] ; then
+				usage
+				exit 1
+			fi
+			is_user $2 
+			if [ $? -ne 0 ] ; then
+				mecho "Non-existent user '$2'. Still proceeding? [Y|N] "
+				read answer
+				if [ "$answer" = "y" -o "$answer" = "Y" ] ; then
+					minfo "Proceeding with non-local user"
+				else
+					exit 1
+				fi
+			fi
+			set_user $2
+			shift 2
+			acl_inserted=0
+			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
+						mwarn "Invalid privilege: acl '$1' ignored"
+						shift
+						continue
+					fi
+				fi
+
+				QUERY="insert into $ACL_TABLE ($ACL_USER_COLUMN,\
+$ACL_GROUP_COLUMN,$ACL_MODIFIED_COLUMN,$ACL_DOMAIN_COLUMN ) values \
+('$OSERUSER','$1', now(), '$OSERDOMAIN' );"
+				$DBCMD "$QUERY"
+				if [ $? -ne 0 ] ; then
+					merr "acl - SQL Error"
+					exit 1
+				fi
+				acl_inserted=1
+				shift
+			done
+
+			if [ $acl_inserted -eq 1 ] ; then
+				$0 acl show "$OSERUSER@$OSERDOMAIN"
+			fi
+
+			;;
+
+		revoke)
+			if [ $# -eq 3 ] ; then
+				CLAUSE=" and $ACL_GROUP_COLUMN='$3' "
+			elif [ $# -ne 2 ] ; then
+				merr "acl - wrong number of parameters"
+				usage_acl
+				exit 1
+			fi	
+
+			set_user $2
+
+			QUERY="delete from $ACL_TABLE where \
+$ACL_TABLE.$ACL_USER_COLUMN='$OSERUSER' AND $ACL_DOMAIN_COLUMN='$OSERDOMAIN' \
+$CLAUSE;"
+			$DBCMD "$QUERY"
+
+			$0 acl show "$2"
+
+			;;
+
+		*)
+			merr "acl - invalid commad '$1'"
+			usage_acl
+			exit 1
+			;;
+	esac
+}
+
+
+#
+##### ------------------------------------------------ #####
+### alias management
+#
+check_ul_alias() {
+	require_ctlengine
+	RES=`$CTLCMD ul_show_contact "$ALS_TABLE" "$1@$2"`
+	RET="$?"
+	ALIAS_UL_EXISTS=0
+	if [ $RET -ne 0 ] ; then
+		merr "Kamailio $CTLENGINE not accessible: $RET"
+		exit 1
+	fi
+	echo "$RES" | $EGREP "^404" > /dev/null
+	if [ $? -ne 0 ] ; then
+		echo "$RES" | $EGREP "^400" > /dev/null
+		if [ $? -eq 0 ] ; then
+			merr "400; check if you use aliases in Kamailio"
+			exit 1
+		fi
+		echo "$RES" | $EGREP "^200" > /dev/null
+		if [ $? -eq 0 ] ; then
+			ALIAS_UL_EXISTS=1
+		fi
+		# other errors
+		merr "$RES"
+		exit 1
+	fi
+}
+
+check_db_alias() {
+	require_dbengine
+
+	ALIAS_DB_EXISTS=0
+
+	QUERY="select count(*) from $DA_TABLE where $DA_ALIAS_USER_COLUMN='$1' \
+and $DA_ALIAS_DOMAIN_COLUMN='$2';"
+	CNT=`$DBROCMD "$QUERY" | $EGREP -v ERROR | $LAST_LINE`
+	mdbg "check_db_alias: alias counter=$CNT"
+	if [ "$CNT" = "0" ] ; then
+		ALIAS_DB_EXISTS=0
+	else
+		ALIAS_DB_EXISTS=1
+	fi
+}
+
+#
+# check for alias duplicates
+#   params: user domain
+#   output: false if exists, true otherwise
+check_alias() {
+	ALIAS_EXISTS=0
+
+	if [ "$ENABLE_ALIASES" = "1" ] ; then
+		check_ul_alias "$1" "$2"
+		if [ "$ALIAS_UL_EXISTS" = "0" ] ; then
+			ALIAS_EXISTS=0
+		else
+			ALIAS_EXISTS=1
+		fi
+	elif  [ "$ENABLE_ALIASES" = "2" ] ; then
+		check_db_alias "$1" "$2"
+		if [ "$ALIAS_DB_EXISTS" = "0" ] ; then
+			ALIAS_EXISTS=0
+		else
+			ALIAS_EXISTS=1
+		fi
+	fi
+}
+
+# db-based aliases
+alias_db() {
+	if [ "$#" -lt 2 ] ; then
+		merr "alias_db - too few parameters"
+		echo
+		usage_alias_db
+		exit 1
+	fi
+
+	require_dbengine
+
+	shift
+
+	case $1 in 
+		list)
+			if [ $# -eq 2 ] ; then
+				# print aliases for user
+				check_aor "$2"
+				if [ "$?" -ne "0" ] ; then
+					merr "alias_db - <$2> is not a valid AoR (user@domain)"
+					exit 1
+				fi
+				
+				set_user $2
+				
+				CLAUSE="WHERE $DA_USER_COLUMN='$OSERUSER' AND \
+$DA_DOMAIN_COLUMN='$OSERDOMAIN'"
+				mecho "Dumping aliases for user=<$2>"
+				echo
+				QUERY="SELECT CONCAT($DA_ALIAS_USER_COLUMN,\
+'@',$DA_ALIAS_DOMAIN_COLUMN) AS ALIAS FROM $DA_TABLE $CLAUSE;"
+				$DBROCMD "$QUERY"
+									# | $AWK 'BEGIN {line=0;}
+									#		/^\+/ { next }
+									#		{	if(line==0) print "ALIASES";
+									#			else print line ")\t" $1 "@" $2;
+									#			line++; }'
+			elif [ $# -eq 1 ] ; then
+				mecho "Dumping all aliases may take long: do you want to proceed? [Y|N] "
+				read answer
+				if [ "$answer" = "y" -o "$answer" = "Y" ] ; then
+					mecho "Dumping all aliases..."
+					echo
+				else
+					exit 1
+				fi
+				QUERY="SELECT $DA_ALIAS_USER_COLUMN, $DA_ALIAS_DOMAIN_COLUMN,\
+$DA_USER_COLUMN, $DA_DOMAIN_COLUMN FROM $DA_TABLE;"
+				$DBROCMD "$QUERY"
+					# | $AWK 'BEGIN {line=0;}
+					#	/^\+/ { next }
+					#	{	line++;
+					#		if(line==1) print "SIP-ID               \tALIAS\n";
+					#		else print $3 "@" $4 "\t" $1 "@" $2 }'
+			else
+				merr "alias_db - wrong number of params for command [list]"
+				echo
+				usage_alias_db
+				exit 1
+			fi
+
+			exit $?
+			;;
+		show)
+			if [ $# -ne 2 ] ; then
+				merr "alias_db - wrong number of params for command [show]"
+				usage_alias_db
+				exit 1
+			fi
+			
+			check_aor "$2"
+			if [ "$?" -ne "0" ] ; then
+				merr "alias_db - $2 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+			
+			set_user $2
+			
+			CLAUSE="WHERE $DA_ALIAS_USER_COLUMN='$OSERUSER' AND \
+$DA_ALIAS_DOMAIN_COLUMN='$OSERDOMAIN'"
+			QUERY="SELECT CONCAT($DA_USER_COLUMN,'@',$DA_DOMAIN_COLUMN) \
+AS 'SIP-ID' FROM $DA_TABLE $CLAUSE ; "
+			$DBROCMD "$QUERY"
+			#TMP_UUID=`sql_ro_query "$QUERY" | $AWK 'BEGIN {line=0;}
+			#								/^\+/ { next } 
+			#								{ line++;
+			#								  if(line==2) print $1 "@" $2;}'`
+			#
+			#if [ "$TMP_UUID" = "" ] ; then
+			#	mecho "non-existent alias <$2>"
+			#	exit 1
+			#fi
+			#
+			#echo "Details for alias <$2>"
+			#echo
+			#echo "SIP-ID: $TMP_UUID"
+			#echo
+			#exit $?
+			;;
+		add)
+			if [ $# -ne 3 ] ; then
+				usage_alias_db
+				exit 1
+			fi
+			shift
+			check_aor "$1"
+			if [ "$?" -ne "0" ] ; then
+				err "alias_db - $1 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+
+			check_aor "$2"
+			if [ "$?" -ne "0" ] ; then
+				err "alias_db - $2 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+			
+			set_user $1
+			TMP_OSERUSER=$OSERUSER
+			TMP_OSERDOMAIN=$OSERDOMAIN
+			set_user $2
+			
+			if is_value_in_db $DA_TABLE $DA_ALIAS_USER_COLUMN $TMP_OSERUSER; then
+				minfo "$TMP_OSERUSER alias already in $DA_TABLE table"
+				exit 0
+			fi
+
+			QUERY="INSERT INTO $DA_TABLE ($DA_USER_COLUMN,$DA_DOMAIN_COLUMN,\
+$DA_ALIAS_USER_COLUMN,$DA_ALIAS_DOMAIN_COLUMN) VALUES ('$OSERUSER',\
+'$OSERDOMAIN','$TMP_OSERUSER','$TMP_OSERDOMAIN' );"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "alias_db - SQL Error"
+				exit 1
+			fi
+			
+			exit $?
+			;;
+		rm)
+			if [ $# -ne 2 ] ; then
+				merr "alias_db - wrong numbers of parameters"
+				usage_alias_db
+				exit 1
+			fi
+			
+			shift
+			
+			check_aor "$1"
+			if [ "$?" -ne "0" ] ; then
+				merr "alias_db - $1 is not a valid URI"
+				exit 1
+			fi
+
+			set_user $1
+			CLAUSE="WHERE $DA_ALIAS_USER_COLUMN='$OSERUSER' AND \
+$DA_ALIAS_DOMAIN_COLUMN='$OSERDOMAIN'"
+			QUERY="DELETE FROM $DA_TABLE $CLAUSE;"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "alias_db - SQL Error"
+				exit 1
+			fi
+			
+			exit $?
+			;;
+			
+		help)
+			usage_alias_db "alone"
+			;;
+			
+		*)
+			usage_alias_db
+			exit 1
+			;;
+	esac
+} # end db-aliases
+
+
+#
+##### ------------------------------------------------ #####
+### AVP management
+#
+# avp list [-T table] [-u <sip-id|uuid>]
+#     [-a attribute] [-v value] [-t type] ... list AVPs
+# avp add [-T table] <sip-id|uuid>
+#     <attribute> <type> <value> ............ add AVP (*)
+# avp rm [-T table]  [-u <sip-id|uuid>]
+#     [-a attribute] [-v value] [-t type] ... remove AVP (*)
+
+avpops() {
+	require_dbengine
+	if [ "$#" -lt 2 ] ; then
+		merr "avp - too few parameters"
+		minfo "see '$0 avp help'"
+		exit 1
+	fi
+	if [ "$1" = "avp" ] ; then
+		shift
+	else
+		merr "avp - unknown command $1"
+		minfo "see '$0 avp help'"
+		exit 1
+	fi
+
+	case $1 in 
+		list)
+			shift
+			CLAUSE=""
+			while [ "$#" != "0" ] 
+			do
+				TMP_ARG=$1
+				shift
+				case $TMP_ARG in 
+					-T)
+						if [ -z "$1" ] ; then
+							merr "avp list - table name parameter missing"
+							exit 1
+						fi
+						AVP_TABLE=$1
+					;;
+					-u)
+						if [ -z "$1" ] ; then
+							merr "avp list - user id or uuid parameter missing"
+							exit 1
+						fi
+						is_aor "$1"
+						if [ "$?" -eq "0" ] ; then
+							set_user $1
+							if [ "$CLAUSE" = "" ] ; then
+								CLAUSE=" WHERE $AVP_USER_COLUMN='$OSERUSER' \
+AND $AVP_DOMAIN_COLUMN='$OSERDOMAIN'"
+							else
+								CLAUSE="$CLAUSE AND \
+$AVP_USER_COLUMN='$OSERUSER' AND $AVP_DOMAIN_COLUMN='$OSERDOMAIN'"
+							fi
+						else
+							if [ "$CLAUSE" = "" ] ; then
+								CLAUSE=" WHERE $AVP_UUID_COLUMN='$1'"
+							else
+								CLAUSE="$CLAUSE AND $AVP_UUID_COLUMN='$1'"
+							fi
+						fi				
+					;;
+					-a)
+						if [ -z "$1" ] ; then
+							merr "avp list - attribute name parameter missing"
+							exit 1
+						fi
+						if [ "$CLAUSE" = "" ] ; then
+							CLAUSE=" WHERE $AVP_ATTRIBUTE_COLUMN='$1'"
+						else
+							CLAUSE="$CLAUSE AND $AVP_ATTRIBUTE_COLUMN='$1'"
+						fi
+					;;
+					-v)
+						if [ -z "$1" ] ; then
+							merr "avp list - value parameter missing"
+							exit 1
+						fi
+						if [ "$CLAUSE" = "" ] ; then
+							CLAUSE=" WHERE $AVP_VALUE_COLUMN='$1'"
+						else
+							CLAUSE="$CLAUSE AND $AVP_VALUE_COLUMN='$1'"
+						fi
+					;;
+					-t)
+						if [ -z "$1" ] ; then
+							merr "avp list - type parameter missing"
+							exit 1
+						fi
+						if [ "$CLAUSE" = "" ] ; then
+							CLAUSE=" WHERE $AVP_TYPE_COLUMN='$1'"
+						else
+							CLAUSE="$CLAUSE AND $AVP_TYPE_COLUMN='$1'"
+						fi
+					;;
+					*)
+						merr "avp list - unknown parameter $1"
+						exit 1
+					;;
+				esac
+				shift
+			done
+			
+			QUERY="SELECT $AVP_UUID_COLUMN,$AVP_USER_COLUMN,\
+$AVP_DOMAIN_COLUMN,$AVP_ATTRIBUTE_COLUMN,$AVP_TYPE_COLUMN,$AVP_VALUE_COLUMN \
+FROM $AVP_TABLE $CLAUSE;"
+			mdbg "Query: $QUERY"
+			mecho "Dumping AVPs"
+			echo
+			$DBROCMD "$QUERY"
+			# | $AWK 'BEGIN {line=0;}
+			#		/^\+/ { next }
+			#		{	if(line==0) print "##   UUID   \tUserID     \tAttribute     \tType     \tValue\n";
+			#			else {
+			#				ORS_BAK=ORS;
+			#				ORS="";
+			#				print line ")  " $1  $2 "@" $3 "\t" $4 "\t\"" $5;
+			#				for (i=6;i<=NF;++i) print FS $i;
+			#				ORS=ORS_BAK;
+			#				print "\"";
+			#			}
+			#			line++;
+			#		}'
+			
+			exit $?
+			;;
+			
+		add)
+			shift
+			if [ $# -ne 4 ] ; then
+				if [ $# -ne 6 ] ; then
+					merr "avp add - bad number of parameters"
+					exit 1
+				fi
+			fi
+			if [ $# -eq 6 ] ; then
+				if [ "$1" = "-T" ] ; then
+					AVP_TABLE=$2
+					shift
+					shift
+				else
+					mecho "avp add - unknown parameter '$1'"
+					exit 1					
+				fi
+			fi
+						
+			is_aor "$1"
+			if [ "$?" -eq "0" ] ; then
+				set_user $1
+			else
+				AVP_UUID=$1
+			fi
+
+			QUERY="INSERT INTO $AVP_TABLE \
+($AVP_UUID_COLUMN,$AVP_USER_COLUMN,$AVP_DOMAIN_COLUMN,$AVP_ATTRIBUTE_COLUMN,\
+$AVP_TYPE_COLUMN,$AVP_VALUE_COLUMN,$AVP_MODIFIED_COLUMN) \
+VALUES ('$AVP_UUID','$OSERUSER','$OSERDOMAIN','$2',$3,'$4',NOW());"
+			# echo "Query: $QUERY"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "avp add - SQL Error"
+				exit 1
+			else
+				echo
+				mecho "avp add - attribute added"
+			fi
+			
+			exit $?
+			;;
+
+		rm)
+			shift
+			CLAUSE=""
+			while [ "$#" != "0" ] 
+			do
+				TMP_ARG=$1
+				shift
+				case $TMP_ARG in 
+					-T)
+						if [ -z "$1" ] ; then
+							merr "avp rm - table name parameter missing"
+							exit 1
+						fi
+						AVP_TABLE=$1
+					;;
+					-u)
+						if [ -z "$1" ] ; then
+							merr "avp rm - user id or uuid parameter missing"
+							exit 1
+						fi
+						is_aor "$1"
+						if [ "$?" -eq "0" ] ; then
+							set_user $1
+							if [ "$CLAUSE" = "" ] ; then
+								CLAUSE="WHERE $AVP_USER_COLUMN='$OSERUSER' \
+AND $AVP_DOMAIN_COLUMN='$OSERDOMAIN'"
+							else
+								CLAUSE="$CLAUSE AND \
+$AVP_USER_COLUMN='$OSERUSER' AND $AVP_DOMAIN_COLUMN='$OSERDOMAIN'"
+							fi
+						else
+							if [ "$CLAUSE" = "" ] ; then
+								CLAUSE="WHERE $AVP_UUID_COLUMN='$1'"
+							else
+								CLAUSE="$CLAUSE AND $AVP_UUID_COLUMN='$1'"
+							fi
+						fi				
+					;;
+					-a)
+						if [ -z "$1" ] ; then
+							merr "avp rm - attribute name parameter missing"
+							exit 1
+						fi
+						if [ "$CLAUSE" = "" ] ; then
+							CLAUSE="WHERE $AVP_ATTRIBUTE_COLUMN='$1'"
+						else
+							CLAUSE="$CLAUSE AND $AVP_ATTRIBUTE_COLUMN='$1'"
+						fi
+					;;
+					-v)
+						if [ -z "$1" ] ; then
+							merr "avp rm - value parameter missing"
+							exit 1
+						fi
+						if [ "$CLAUSE" = "" ] ; then
+							CLAUSE="WHERE $AVP_VALUE_COLUMN='$1'"
+						else
+							CLAUSE="$CLAUSE AND $AVP_VALUE_COLUMN='$1'"
+						fi
+					;;
+					-t)
+						if [ -z "$1" ] ; then
+							merr "avp rm - type parameter missing"
+							exit 1
+						fi
+						if [ "$CLAUSE" = "" ] ; then
+							CLAUSE="WHERE $AVP_TYPE_COLUMN='$1'"
+						else
+							CLAUSE="$CLAUSE AND $AVP_TYPE_COLUMN='$1'"
+						fi
+					;;
+					*)
+						merr "avp rm - unknown parameter $1"
+						exit 1
+					;;
+				esac
+				shift
+			done
+			QUERY="DELETE FROM $AVP_TABLE $CLAUSE;"
+			mdbg "Query: $QUERY"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "avp rm - SQL Error"
+				exit 1
+			else
+				echo
+				mecho "avp rm - AVP(s) deleted"
+			fi
+			
+			exit $?
+			;;
+			
+		help)
+			usage_avp
+			;;
+			
+		*)
+			merr "avp - unknown command"
+			usage
+			exit 1
+			;;
+	esac
+} # end avpops()
+
+#
+##### ------------------------------------------------ #####
+### cisco restart
+#
+cisco_restart() {
+	require_ctlengine
+	myhost=`get_my_host`
+	RET=`$CTLCMD t_uac_dlg NOTIFY "$1" "." \
+		"From: sip:daemon@$myhost" \
+		"To: <$1>" "Event: check-sync" \
+		"Contact: <sip:daemon@!!>" "." "." |
+		head -1 `
+	print_status $RET
+}
+
+#
+##### ------------------------------------------------ #####
+### DB operations
+#
+db_ops() {
+	require_dbengine
+	case $1 in
+		exec|query)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing query parameter"
+				exit 1
+			fi
+			$DBCMD "$1"
+			;;
+		roexec|roquery)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing query parameter"
+				exit 1
+			fi
+			$DBROCMD "$1"
+			;;
+		run)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing query parameter"
+				exit 1
+			fi
+			eval QUERY=\$$1
+			if [ -z "$QUERY" ] ; then
+				merr "missing query value"
+				exit 1
+			fi
+			$DBCMD "$QUERY"
+			;;
+		rorun)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing query parameter"
+				exit 1
+			fi
+			eval QUERY=\$$1
+			if [ -z "$QUERY" ] ; then
+				merr "missing query value"
+				exit 1
+			fi
+			$DBROCMD "$QUERY"
+			;;
+		show)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing table parameter"
+				exit 1
+			fi
+			QUERY="select * FROM $1;"
+			$DBROCMD "$QUERY"
+			;;
+		*)
+			usage_db_ops
+			exit 1
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### domain management
+#
+domain() {
+	case $1 in
+		reload)
+			require_ctlengine
+			$CTLCMD domain_reload
+			;;
+		show)
+			require_ctlengine
+			$CTLCMD domain_dump
+			;;
+		showdb)
+			require_dbengine
+			QUERY="select * FROM $DOMAIN_TABLE ; "
+			$DBROCMD "$QUERY"
+			;;
+		add)
+			require_dbengine
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing domain parameter"
+				exit 1
+			fi
+			if is_value_in_db $DOMAIN_TABLE $DO_DOMAIN_COLUMN $1; then
+				minfo "$1 already in $DOMAIN_TABLE table"
+				exit 0
+			fi
+			QUERY="insert into $DOMAIN_TABLE ($DO_DOMAIN_COLUMN, \
+			$DO_LAST_MODIFIED_COLUMN) VALUES ('$1',now());"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "domain - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 domain reload' to synchronize cache and database"
+			;;
+		rm)
+			require_dbengine
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing domain parameter"
+				exit 1
+			fi
+			QUERY="delete from $DOMAIN_TABLE where domain='$1';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "domain - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 domain reload' to synchronize cache and database"
+			;;
+		*)
+			usage_domain
+			exit 1
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### trusted management
+#
+trusted() {
+	case $1 in
+		reload)
+			require_ctlengine
+			$CTLCMD trusted_reload
+			;;
+		dump)
+			require_ctlengine
+			$CTLCMD trusted_dump
+			;;
+		show)
+			require_dbengine
+			QUERY="select * FROM $TRUSTED_TABLE ; "
+			$DBROCMD "$QUERY"
+			;;
+		add)
+			require_dbengine
+			shift
+			if [ $# -lt 2 ] ; then
+				usage_trusted
+				exit 1
+			fi
+			if is_value_in_db $TRUSTED_TABLE src_ip $1; then
+				minfo "$1 already in $TRUSTED_TABLE table"
+				exit 0
+			fi
+			case $2 in
+				any|udp|tcp|tls|sctp|none)
+					;;
+				*)
+					merr "unknown protocol"
+					exit 1
+			esac
+			PATTERN=""
+			if [ ! -z "$3" ]; then
+				PATTERN="$3"
+			fi
+
+			QUERY="insert into $TRUSTED_TABLE \
+				( $TRUSTED_SRC_IP_COLUMN, $TRUSTED_PROTO_COLUMN, \
+				$TRUSTED_FROM_PATTERN_COLUMN, $TRUSTED_TAG_COLUMN) \
+				VALUES ('$1', '$2', '$PATTERN', '$4');"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "trusted - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 trusted reload' to synchronize cache and database"
+			;;
+		rm)
+			require_dbengine
+			shift
+			if [ $# -ne 1 ] ; then
+				usage_trusted
+				exit 1
+			fi
+			QUERY="delete from $TRUSTED_TABLE where $TRUSTED_SRC_IP_COLUMN='$1';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "trusted - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 trusted reload' to synchronize cache and database"
+			;;
+		*)
+			usage_trusted
+			exit 1
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### LCR management
+#
+lcr() {
+	require_dbengine
+	require_ctlengine
+	case $1 in
+		show)
+			mecho "lcr routes"
+			QUERY="select * FROM $LCR_TABLE ORDER BY $LCR_PREFIX_COLUMN; "
+			$DBROCMD "$QUERY"
+			mecho "lcr gateways"
+			QUERY="select $LCR_GW_GWNAME_COLUMN, $LCR_GW_IP_COLUMN,\
+ $LCR_GW_HOSTNAME_COLUMN, $LCR_GW_PORT_COLUMN, $LCR_GW_URIS_COLUMN, $LCR_GW_PROTO_COLUMN,\
+$LCR_GW_GRPID_COLUMN, $LCR_GW_STRIP_COLUMN, $LCR_GW_TAG_COLUMN, \
+$LCR_GW_FLAGS_COLUMN, $LCR_GW_WEIGHT_COLUMN, $LCR_GW_PING_COLUMN FROM $GW_TABLE ORDER BY $LCR_GW_GRPID_COLUMN; "
+			$DBROCMD "$QUERY"
+			;;
+		reload)
+			$CTLCMD lcr_reload
+			;;
+		dump)
+			$CTLCMD lcr_gw_dump
+			$CTLCMD lcr_lcr_dump
+			;;
+		addroute)
+			shift
+			if [ $# -ne 4 ] ; then
+				merr "lcr - too few parameters"
+				usage_lcr
+				exit 1
+			fi
+			QUERY="insert into $LCR_TABLE \
+				($LCR_PREFIX_COLUMN, $LCR_FROMURI_COLUMN, \
+					$LCR_GRPID_COLUMN, $LCR_PRIO_COLUMN) \
+				VALUES ('$1', '$2', $3, $4);"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+			merr "lcr - SQL Error"
+				exit 1
+			fi
+			$CTLCMD lcr_reload
+			;;
+		rmroute)
+			shift
+			if [ $# -ne 4 ] ; then
+				merr "too few parameters"
+				usage_lcr
+				exit 1
+			fi
+			QUERY="delete from $LCR_TABLE where $LCR_PREFIX_COLUMN='$1' AND \
+				$LCR_FROMURI_COLUMN='$2' AND $LCR_GRPID_COLUMN=$3 AND \
+				$LCR_PRIO_COLUMN=$4;"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "lcr - SQL Error"
+				exit 1
+			fi
+			$CTLCMD lcr_reload
+			;;
+		addgw)
+			shift
+			if [ $# -lt 6 ] ; then
+				merr "lcr - too few parameters"
+				usage_lcr
+				exit 1
+			fi
+			if [ $# -gt 6 ] ; then
+				GW_FLAGS=$7
+				if [ $# -gt 7 ] ; then
+					GW_TAG=$8
+				else
+					GW_TAG=""
+					GW_STRIP=0
+				fi
+				if [ $# -gt 8 ] ; then
+					STRIP=$9
+				else
+					STRIP=0
+				fi
+				if [ $# -gt 9 ] ; then
+					GW_WEIGHT=${10}
+				else
+					GW_WEIGHT=1
+				fi
+				if [ $# -gt 10 ] ; then
+					GW_HOSTNAME=${10}
+				else
+					GW_HOSTNAME=""
+				fi
+				if [ $# -gt 11 ] ; then
+					GW_PING=${11}
+				else
+					GW_PING=0
+				fi
+				if [ $# -gt 12 ] ; then
+				    merr "lcr - too many parameters"
+				    usage_lcr
+				    exit 1
+				fi
+			else
+				GW_FLAGS=0
+				GW_TAG=""
+				STRIP=0
+				GW_WEIGHT=1
+				GW_HOSTNAME=""
+				GW_PING=0
+			fi
+			GW_NAME=$1
+			GW_IP=$2
+			GW_PORT=$3
+			GW_URI_SCHEME=$4
+			if   [ $GW_URI_SCHEME = 'sip' ]; then
+				GW_URI_SCHEME=1
+			elif [ $GW_URI_SCHEME = 'sips' ]; then
+				GW_URI_SCHEME=2
+			fi
+			GW_TRANSPORT=$5
+			if   [ $GW_TRANSPORT = 'udp' ]; then
+				GW_TRANSPORT=1
+			elif [ $GW_TRANSPORT = 'tcp' ]; then
+				GW_TRANSPORT=2
+			elif [ $GW_TRANSPORT = 'tls' ]; then
+				GW_TRANSPORT=3
+			elif [ $GW_TRANSPORT = 'sctp' ]; then
+				GW_TRANSPORT=4
+			fi
+			GW_GRP_ID=$6
+			QUERY="insert into $GW_TABLE \
+				($LCR_GW_GWNAME_COLUMN, $LCR_GW_GRPID_COLUMN,\
+					$LCR_GW_IP_COLUMN,$LCR_GW_PORT_COLUMN,$LCR_GW_URIS_COLUMN,\
+					$LCR_GW_PROTO_COLUMN,$LCR_GW_STRIP_COLUMN,\
+					$LCR_GW_TAG_COLUMN,$LCR_GW_FLAGS_COLUMN,$LCR_GW_WEIGHT_COLUMN, $LCR_GW_PING_COLUMN, $LCR_GW_HOSTNAME_COLUMN) \
+				VALUES ('$GW_NAME', $GW_GRP_ID,\
+					'$GW_IP', $GW_PORT, $GW_URI_SCHEME,\
+					$GW_TRANSPORT, $STRIP, '$GW_TAG', $GW_FLAGS, $GW_WEIGHT, $GW_PING, '$GW_HOSTNAME');"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "lcr - SQL Error"
+				exit 1
+			fi
+			$CTLCMD lcr_reload
+			;;
+		rmgw)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing gateway to be removed"
+				exit 1
+			fi
+			QUERY="delete from $GW_TABLE where $LCR_GW_GWNAME_COLUMN='$1';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "lcr - SQL Error"
+				exit 1
+			fi
+			$CTLCMD lcr_reload
+			;;
+		*)
+			usage_lcr
+			exit 1
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### CARRIERROUTE management
+#
+cr() {
+	require_dbengine
+	require_ctlengine
+	case $1 in
+		show)
+			mecho "cr carrier names"
+			QUERY="select * FROM $CARRIER_NAME_TABLE ORDER BY $CARRIERROUTE_CARRIER_NAME_ID_COLUMN; "
+			$DBROCMD "$QUERY"
+			mecho "cr domain names"
+			QUERY="select * FROM $DOMAIN_NAME_TABLE ORDER BY $CARRIERROUTE_DOMAIN_NAME_ID_COLUMN; "
+			$DBROCMD "$QUERY"
+			mecho "cr routes"
+			QUERY="select * FROM $CARRIERROUTE_TABLE ORDER BY \
+				$CARRIERROUTE_CARRIERROUTE_CARRIER_COLUMN,\
+				$CARRIERROUTE_CARRIERROUTE_SCAN_PREFIX_COLUMN,\
+				$CARRIERROUTE_CARRIERROUTE_DOMAIN_COLUMN,\
+				$CARRIERROUTE_CARRIERROUTE_PROB_COLUMN;"
+			$DBROCMD "$QUERY"
+			;;
+		reload)
+			$CTLCMD cr_reload_routes
+			;;
+
+		dump)
+			$CTLCMD cr_dump
+			;;
+
+		addcn)
+			shift
+			if [ $# -ne 2 ] ; then
+				merr "cr - missing carrier id or name"
+				exit 1
+			fi
+			QUERY="insert into $CARRIER_NAME_TABLE
+				( $CARRIERROUTE_CARRIER_NAME_ID_COLUMN, \
+				$CARRIERROUTE_CARRIER_NAME_CARRIER_COLUMN) \
+				VALUES ($1, '$2');"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "cr - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 cr reload' to synchronize cache and database"
+			;;
+
+		rmcn)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "cr - missing carrier id to be removed"
+				exit 1
+			fi
+			QUERY="delete from $CARRIER_NAME_TABLE where $CARRIERROUTE_CARRIER_NAME_ID_COLUMN='$1';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "cr - SQL Error"
+				exit 1 
+			fi
+			minfo "execute '$0 cr reload' to synchronize cache and database"
+			;;
+
+		adddn)
+			shift
+			if [ $# -ne 2 ] ; then
+				merr "cr - missing domain id or name"
+				exit 1
+			fi
+			QUERY="insert into $DOMAIN_NAME_TABLE
+				( $CARRIERROUTE_DOMAIN_NAME_ID_COLUMN, \
+				$CARRIERROUTE_DOMAIN_NAME_DOMAIN_COLUMN) \
+				VALUES ($1, '$2');"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "cr - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 cr reload' to synchronize cache and database"
+			;;
+
+		rmdn)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "cr - missing domain id to be removed"
+				exit 1
+			fi
+			QUERY="delete from $DOMAIN_NAME_TABLE where $CARRIERROUTE_DOMAIN_NAME_ID_COLUMN='$1';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "cr - SQL Error"
+				exit 1 
+			fi
+			minfo "execute '$0 cr reload' to synchronize cache and database"
+			;;
+
+		addcarrier)
+			shift
+			if [ $# -lt 4 ] ; then
+				merr "cr - too few parameters"
+				exit 1
+			fi
+			PROB=1
+			STRIP=0
+			REWRITE_PREFIX=
+			REWRITE_SUFFIX=
+			COMMENT=
+			FLAGS=0
+			MASK=0
+			if [ $# -gt 4 ] ; then
+				PROB=$5
+				if [ $# -gt 5 ] ; then
+					STRIP=$6
+					if [ $# -gt 6 ] ; then
+						REWRITE_PREFIX=$7
+						if [ $# -gt 7 ] ; then
+							REWRITE_SUFFIX=$8
+							if [ $# -gt 8 ] ; then
+								COMMENT=$9
+								if [ $# -gt 9 ] ; then
+									FLAGS=${10}
+									if [ $# -gt 10 ] ; then
+										MASK=${11}
+									fi
+								fi
+							fi
+						fi
+					fi
+				fi
+			fi
+			CARRIER=$1
+			SCAN_PREFIX=$2
+			DOMAIN=$3
+			REWRITE_HOST=$4
+			echo $FLAGS
+			echo $MASK
+			QUERY="insert into $CARRIERROUTE_TABLE \
+				( $CARRIERROUTE_CARRIERROUTE_CARRIER_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_SCAN_PREFIX_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_DOMAIN_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_PROB_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_STRIP_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_REWRITE_HOST_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_REWRITE_PREFIX_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_REWRITE_SUFFIX_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_COMMENT_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_FLAGS_COLUMN, \
+				$CARRIERROUTE_CARRIERROUTE_MASK_COLUMN ) \
+				VALUES ($CARRIER, '$SCAN_PREFIX', '$DOMAIN', $PROB, $STRIP, \
+				'$REWRITE_HOST', '$REWRITE_PREFIX', '$REWRITE_SUFFIX', '$COMMENT', \
+				$FLAGS, $MASK);"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "cr - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 cr reload' to synchronize cache and database"
+			;;
+
+		rmcarrier)
+			shift
+			if [ $# -ne 3 ] ; then
+				merr "cr - too few parameters"
+				exit 1
+			fi
+			CARRIER=$1
+			SCAN_PREFIX=$2
+			DOMAIN=$3
+			QUERY="delete from $CARRIERROUTE_TABLE where $CARRIERROUTE_CARRIERROUTE_CARRIER_COLUMN='$CARRIER' AND \
+				$CARRIERROUTE_CARRIERROUTE_SCAN_PREFIX_COLUMN='$SCAN_PREFIX' AND \
+				$CARRIERROUTE_CARRIERROUTE_DOMAIN_COLUMN=$DOMAIN ;"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "cr - SQL Error"
+				exit 1
+			fi
+			minfo "execute '$0 cr reload' to synchronize cache and database"
+			;;
+
+		*)
+			usage_cr
+			exit 1
+	esac
+}
+
+
+#
+##### ------------------------------------------------ #####
+### DISPATCHER management
+#
+dispatcher() {
+	require_dbengine
+	require_ctlengine
+	case $1 in
+		show)
+			mecho "dispatcher gateways"
+			QUERY="select * FROM $DISPATCHER_TABLE ORDER BY $DISPATCHER_SETID_COLUMN; "
+			$DBROCMD "$QUERY"
+			;;
+		addgw)
+			shift
+			if [ $# -lt 3 ] ; then
+				merr "too few parameters"
+				usage_dispatcher
+				exit 1
+			fi
+
+			if [ $# -gt 3 ] ; then
+				DISPATCHER_DESCRIPTION=$4
+			else
+				DISPATCHER_DESCRIPTION=""
+			fi 
+
+			DISPATCHER_SETID=$1
+			DISPATCHER_DESTINATION=$2
+			DISPATCHER_FLAGS=$3
+
+			QUERY="insert into $DISPATCHER_TABLE \
+				( $DISPATCHER_SETID_COLUMN, $DISPATCHER_DESTINATION_COLUMN, $DISPATCHER_FLAGS_COLUMN, $DISPATCHER_DESCRIPTION_COLUMN ) \
+				VALUES ($DISPATCHER_SETID,'$DISPATCHER_DESTINATION',$DISPATCHER_FLAGS,'$DISPATCHER_DESCRIPTION');"
+			$DBCMD "$QUERY"
+
+			if [ $? -ne 0 ] ; then
+				merr "dispatcher - SQL Error"
+				exit 1
+			fi
+
+			$CTLCMD ds_reload
+			;;
+		rmgw)
+			shift
+			if [ $# -ne 1 ] ; then
+				merr "missing gateway id to be removed"
+				exit 1
+			fi
+
+			QUERY="delete from $DISPATCHER_TABLE where $DISPATCHER_ID_COLUMN='$1';"
+			$DBCMD "$QUERY"
+
+			if [ $? -ne 0 ] ; then
+				merr "dispatcher - SQL Error"
+				exit 1
+			fi
+
+			$CTLCMD ds_reload
+			;;
+		reload)
+			$CTLCMD ds_reload
+			;;
+
+		dump)
+			$CTLCMD ds_list
+			;;
+
+		*)
+			usage_dispatcher
+			exit 1
+
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### DIALPLAN management
+#
+dialplan() {
+	require_dbengine
+	require_ctlengine
+	case $1 in
+		show)
+			shift
+			if [ $# -gt 0 ] ; then
+				mecho "dialplan $1 tables"
+				QUERY="select * FROM $DIALPLAN_TABLE WHERE $DIALPLAN_DPID_COLUMN=$1 ORDER BY $DIALPLAN_PR_COLUMN ; "
+			else
+				mecho "dialplan tables"
+				QUERY="select * FROM $DIALPLAN_TABLE ORDER BY $DIALPLAN_DPID_COLUMN, $DIALPLAN_PR_COLUMN; "
+			fi
+			$DBROCMD "$QUERY"
+			;;
+
+		addrule)
+			shift
+			if [ $# -lt 8 ] ; then
+				merr "too few parameters"
+				usage_dialplan
+				exit 1
+			fi
+
+			DIALPLAN_DPID=$1
+			DIALPLAN_PR=$2
+			DIALPLAN_MATCH_OP=$3
+			case $DIALPLAN_MATCH_OP in
+				equal)
+					DIALPLAN_MATCH_OP=0
+					;;
+				regexp)
+					DIALPLAN_MATCH_OP=1
+					;;
+				*)
+					merr "dialplan - unexpected $DIALPLAN_MATCH_OP for operating matching. Use 'equal' or 'regexp'!"
+					exit 1
+			esac
+			DIALPLAN_MATCH_EXP=$4
+			DIALPLAN_MATCH_LEN=$5
+			DIALPLAN_SUBST_EXP=$6
+			DIALPLAN_REPL_EXP=$7
+			DIALPLAN_ATTRS=$8
+
+			QUERY="insert into $DIALPLAN_TABLE \
+				( $DIALPLAN_DPID_COLUMN, $DIALPLAN_PR_COLUMN, $DIALPLAN_MATCH_OP_COLUMN, \
+					$DIALPLAN_MATCH_EXP_COLUMN, $DIALPLAN_MATCH_LEN_COLUMN, \
+					$DIALPLAN_SUBST_EXP_COLUMN, $DIALPLAN_REPL_EXP_COLUMN, \
+					$DIALPLAN_ATTRS_COLUMN ) \
+				VALUES ( $DIALPLAN_DPID, $DIALPLAN_PR, $DIALPLAN_MATCH_OP, \
+					'$DIALPLAN_MATCH_EXP', $DIALPLAN_MATCH_LEN, '$DIALPLAN_SUBST_EXP', \
+					'$DIALPLAN_REPL_EXP', '$DIALPLAN_ATTRS')";
+			mecho "$QUERY"
+			$DBCMD "$QUERY"
+
+			if [ $? -ne 0 ] ; then
+				merr "dialplan - SQL Error"
+				exit 1
+			fi
+
+			$CTLCMD dp_reload
+			;;
+
+		rm)
+			QUERY="delete from $DIALPLAN_TABLE; "
+			$DBCMD "$QUERY"
+
+			if [ $? -ne 0 ] ; then
+				merr "dialplan - SQL Error"
+				exit 1
+			fi
+
+			$CTLCMD dp_reload
+			;;
+
+		rmdpid)
+			shift
+			if [ $# -lt 1 ] ; then
+				merr "too few parameters"
+				usage_dialplan
+				exit 1
+			fi
+
+			DIALPLAN_DPID=$1
+
+			QUERY="delete from $DIALPLAN_TABLE where $DIALPLAN_DPID_COLUMN=$DIALPLAN_DPID; "
+			$DBCMD "$QUERY"
+
+			if [ $? -ne 0 ] ; then
+				merr "dialplan - SQL Error"
+				exit 1
+			fi
+
+			$CTLCMD dp_reload
+			;;
+
+		rmrule)
+			shift
+			if [ $# -lt 2 ] ; then
+				merr "too few parameters"
+				usage_dialplan
+				exit 1
+			fi
+
+			DIALPLAN_DPID=$1
+			DIALPLAN_PR=$2
+
+			QUERY="delete from $DIALPLAN_TABLE where $DIALPLAN_DPID_COLUMN=$DIALPLAN_DPID AND $DIALPLAN_PR_COLUMN=$DIALPLAN_PR; "
+			$DBCMD "$QUERY"
+
+			if [ $? -ne 0 ] ; then
+				merr "dialplan - SQL Error"
+				exit 1
+			fi
+
+			$CTLCMD dp_reload
+			;;
+
+		reload)
+			$CTLCMD dp_reload
+			;;
+
+		*)
+			usage_dialplan
+			exit 1
+
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### openser_start
+#
+openser_start() {
+	echo
+	minfo "Starting Kamailio : "
+	if [ -r $PID_FILE ] ; then
+		ps axw | $EGREP kamailio
+		ls -l $PID_FILE
+		minfo "PID file exists ($PID_FILE)! Kamailio already running?"
+		exit 1
+	fi
+	
+	if [ ! -x "$OSERBIN" ] ; then
+		echo
+		merr "Kamailio binaries not found at $OSERBIN"
+		merr "set OSERBIN to the path of kamailio in $0 or ~/.kamctlrc"
+		exit 1
+	fi
+	if [ $SYSLOG = 1 ] ; then
+		$OSERBIN -P $PID_FILE $STARTOPTIONS 1>/dev/null 2>/dev/null
+	else
+	 	$OSERBIN -P $PID_FILE -E $STARTOPTIONS
+	fi
+	sleep 3
+	if [ ! -s $PID_FILE ] ; then
+		echo
+		merr "PID file $PID_FILE does not exist -- Kamailio start failed"
+		exit 1
+	fi
+	minfo "started (pid: `cat $PID_FILE`)"
+}
+
+#
+##### ------------------------------------------------ #####
+### openser_stop
+#
+openser_stop() {
+	echo
+	minfo "Stopping Kamailio : "
+	if [ -r $PID_FILE ] ; then
+		kill `cat $PID_FILE`
+		minfo "stopped"
+	else
+		echo
+		merr "No PID file found ($PID_FILE)! Kamailio probably not running"
+		minfo "check with 'ps axw | $EGREP kamailio'"
+		exit 1
+	fi
+}
+
+#
+##### ------------------------------------------------ #####
+### options_ping
+#
+options_ping() {
+	myhost=`get_my_host`
+	require_ctlengine
+	CMD="t_uac_dlg OPTIONS \"$1\" \".\" \".\" \"From:sip:daemon@$myhost"$'\r\n'"To:<$1>"$'\r\n'"Contact:sip:daemon@$myhost"$'\r\n'"\""
+	RET=`$CTLCMD $CMD | head -1`
+	print_status $RET
+}
+
+#
+##### ------------------------------------------------ #####
+### rpid management
+#
+rpid() {
+	if [ "$#" -lt 2 ] ; then
+		merr "rpid - too few parameters"
+		exit 1
+	fi
+	shift;
+	require_dbengine
+	case $1 in
+		show)
+			if [ $# -eq 2 ] ; then
+				set_user $2
+				is_user $2 
+				if [ $? -ne 0 ] ; then
+					merr "rpid - invalid user '$2'"
+					exit 1;
+				fi
+				CLAUSE=" WHERE $SUBSCRIBER_COLUMN='$OSERUSER' AND \
+$REALM_COLUMN='$OSERDOMAIN' "
+			elif [ $# -ne 1 ] ; then
+				usage_rpid
+				exit 1
+			fi
+			QUERY="select $SUBSCRIBER_COLUMN, $RPID_COLUMN FROM $SUB_TABLE \
+$CLAUSE ; "
+			$DBROCMD "$QUERY"
+			;;
+
+		add|rm)
+		    MODE=$1;
+
+			if [ "$MODE" = "add" ] ; then
+			    ARG_NUM=3;
+			else
+			    ARG_NUM=2;
+			fi
+			
+			if [ $# -lt $ARG_NUM ] ; then
+				usage_rpid
+				exit 1
+			fi
+
+			set_user $2
+			is_user $2 
+			if [ $? -ne 0 ] ; then
+				merr "rpid - invalid user '$2'"
+				exit 1
+			fi
+			shift 2
+
+			if [ "$MODE" = "add" ] ; then
+			        RPID_VAL="'$1'";
+			else
+			        RPID_VAL=NULL;
+			fi
+
+			QUERY="UPDATE $SUB_TABLE SET $RPID_COLUMN=$RPID_VAL \
+WHERE $SUBSCRIBER_COLUMN='$OSERUSER' AND $REALM_COLUMN='$OSERDOMAIN';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "rpid - SQL Error"
+				exit 1
+			fi
+
+			$0 rpid show "$OSERUSER@$OSERDOMAIN"
+
+			;;
+
+		*)
+			usage_rpid
+			exit 1
+			;;
+	esac
+}
+
+#
+##### ------------------------------------------------ #####
+### SPEEDDIAL management
+#
+speeddial() {
+	if [ "$#" -lt 2 ] ; then
+		merr "speeddial - too few parameters"
+		echo
+		usage_speeddial
+		exit 1
+	fi
+
+	require_dbengine
+	shift
+
+	case $1 in 
+		list)
+			if [ $# -eq 2 ] ; then
+				# print speed-dials for user
+				check_aor "$2"
+				if [ "$?" -ne "0" ] ; then
+					merr "speeddial - <$2> is not a valid AoR (user@domain)"
+					exit 1
+				fi
+				
+				set_user $2
+				
+				CLAUSE="WHERE $SD_USER_COLUMN='$OSERUSER' AND \
+$SD_DOMAIN_COLUMN='$OSERDOMAIN'"
+				mecho "Dumping speed-dials for user=<$2>"
+				echo
+				QUERY="SELECT CONCAT($SD_SD_USER_COLUMN,'@',\
+$SD_SD_DOMAIN_COLUMN) AS 'Short number', $SD_NEW_URI_COLUMN AS 'New URI',\
+$SD_DESC_COLUMN FROM $SD_TABLE $CLAUSE;"
+				$DBROCMD "$QUERY"
+					#| $AWK 'BEGIN {line=0;}
+					#	/^\+/ { next }
+		#{ if(line==0) print "##   SpeedDial   \tNew-URI     \tDescription\n";
+					#	else {
+					#		ORS_BAK=ORS;
+					#		ORS="";
+					#		print line ")  " $1 "@" $2 "\t" $3 "\t\"" $4;
+					#		for (i=5;i<=NF;++i) print FS $i;
+					#		ORS=ORS_BAK;
+					#		print "\"";
+					#	}
+					#	line++;
+					#}'
+			elif [ $# -eq 1 ] ; then
+				mecho "Dumping all speed-dials may take long: do you want to proceed? [Y|N] "
+				read answer
+				if [ "$answer" = "y" -o "$answer" = "Y" ] ; then
+					mecho "Dumping all speed-dials..."
+					echo
+				else
+					exit 1
+				fi
+				QUERY="SELECT CONCAT($SD_SD_USER_COLUMN,'@',\
+$SD_SD_DOMAIN_COLUMN) AS 'Short number', CONCAT($SD_USER_COLUMN,'@',\
+$SD_DOMAIN_COLUMN) AS 'Owner', $SD_NEW_URI_COLUMN AS 'New URI',\
+$SD_DESC_COLUMN FROM $SD_TABLE;"
+				$DBROCMD "$QUERY"
+				#| $AWK 'BEGIN {line=0;}
+				#	/^\+/ { next }
+				#	{	line++;
+	#if(line==1) print "SIP-ID     \tSpeedDial  \tNew-URI    \tDescritpion\n";
+				#		else {
+				#			ORS_BAK=ORS;
+				#			ORS="";
+				#			print $3 "@" $4 "\t" $1 "@" $2 "\t" $5 "\t\"" $6;
+				#			for (i=7;i<=NF;++i) print FS $i;
+				#			ORS=ORS_BAK;
+				#			print "\"";
+				#		}
+				#	}'
+			else
+				merr "speeddial - wrong number of params for command [list]"
+				usage_speeddial
+				exit 1
+			fi
+
+			exit $?
+			;;
+		show)
+			if [ $# -ne 2 ] ; then
+				merr "speeddial - wrong number of params for command [show]"
+				usage_speeddial
+				exit 1
+			fi
+			
+			check_aor "$2"
+			if [ "$?" -ne "0" ] ; then
+				merr "speeddial - $2 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+			
+			set_user $2
+			
+			CLAUSE="WHERE $SD_SD_USER_COLUMN='$OSERUSER' AND \
+$SD_SD_DOMAIN_COLUMN='$OSERDOMAIN'"
+			QUERY="SELECT CONCAT($SD_USER_COLUMN,'@',$SD_DOMAIN_COLUMN) \
+AS 'Owner', $SD_NEW_URI_COLUMN AS 'New URI', $SD_DESC_COLUMN FROM \
+$SD_TABLE $CLAUSE ; "
+			mecho "Details for speeddial <$2>"
+			$DBROCMD "$QUERY"
+			# | $AWK 'BEGIN {line=0;} /^\+/ { next } 
+			# { 
+			#	  if(line==0) print "##  SIP-ID    \tNew-URI   \tDescritpion\n";
+			#	  else {
+			#		  ORS_BAK=ORS;usage_openser_monitor() {
+			#		  ORS="";
+			#		  print line ") " $1 "@" $2 "\t" $3 "\t\"" $4;
+			#		  for (i=5;i<=NF;++i) print FS $i;
+			#		  ORS=ORS_BAK;
+			#		  print "\"";
+			#	  }
+			#	  line++;
+			# }'
+
+			exit $?
+			;;
+		add)
+			if [ $# -ne 4 ] ; then
+				if [ $# -ne 5 ] ; then
+					merr "speeddial - wrong number of parameters"
+					usage_speeddial
+					exit 1
+				fi
+			fi
+			shift
+			check_aor "$1"
+			if [ "$?" -ne "0" ] ; then
+				merr "speeddial - $1 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+
+			check_aor "$2"
+			if [ "$?" -ne "0" ] ; then
+				merr "speeddial - $2 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+			
+			check_sipaor "$3"
+			if [ "$?" -ne "0" ] ; then
+				merr "speeddial - $3 is not a valid SIP AoR (sip:user@domain)"
+				exit 1
+			fi
+			
+			set_user $1
+			TMP_OSERUSER=$OSERUSER
+			TMP_OSERDOMAIN=$OSERDOMAIN
+			set_user $2
+			
+			QUERY="INSERT INTO $SD_TABLE ($SD_USER_COLUMN,$SD_DOMAIN_COLUMN,\
+$SD_SD_USER_COLUMN,$SD_SD_DOMAIN_COLUMN,$SD_NEW_URI_COLUMN,$SD_DESC_COLUMN) \
+VALUES ('$TMP_OSERUSER','$TMP_OSERDOMAIN','$OSERUSER','$OSERDOMAIN','$3','$4');"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "speeddial - SQL Error"
+				exit 1
+			fi
+			mecho "ok - spedd dial added"
+			echo
+			exit $?
+			;;
+		rm)
+			if [ $# -ne 3 ] ; then
+				merr "speeddial rm - invalid number of parameters"
+				usage_speeddial
+				exit 1
+			fi
+			
+			shift
+			
+			check_aor "$1"
+			if [ "$?" -ne "0" ] ; then
+				merr "speeddial - $1 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+			
+			check_aor "$2"
+			if [ "$?" -ne "0" ] ; then
+				merr "speeddial - $2 is not a valid AoR (user@domain)"
+				exit 1
+			fi
+
+			set_user $1
+			TMP_OSERUSER=$OSERUSER
+			TMP_OSERDOMAIN=$OSERDOMAIN
+			set_user $2
+
+			CLAUSE="WHERE $SD_USER_COLUMN='$TMP_OSERUSER' AND \
+$SD_DOMAIN_COLUMN='$TMP_OSERDOMAIN' AND $SD_SD_USER_COLUMN='$OSERUSER' AND \
+$SD_SD_DOMAIN_COLUMN='$OSERDOMAIN'"
+			QUERY="DELETE FROM $SD_TABLE $CLAUSE;"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "speeddial - SQL Error"
+				exit 1
+			fi
+			
+			mecho "ok - spedd dial deleted"
+			echo
+
+			;;
+			
+		help)
+			usage_speeddial
+			;;
+			
+		*)
+			merr "speeddial - unknown command"
+			usage_speeddial
+			exit 1
+			;;
+	esac
+} # end speed_dial()
+
+#
+##### ================================================ #####
+### subscriber management
+#
+subscriber() {
+	if [ "$#" -lt 2 ] ; then
+		merr "too few parameters"
+		usage_subscriber
+		exit 1
+	fi
+
+	require_dbengine
+	
+	case $1 in
+		add)
+			if [ $# -ne 3 ] ; then
+				usage_subscriber
+				exit 1
+			fi
+			shift
+			credentials $1 $2
+			is_user $1
+			if [ $? -eq 0 ] ; then
+				minfo "user '$1' already exists"
+				exit 1
+			fi
+			set_user $1
+			check_alias $OSERUSER $OSERDOMAIN
+			if [ "$ALIAS_EXISTS" = "1" ] ; then
+				minfo "user '$1' already exists as alias"
+				exit 1
+			fi
+
+			if [ "$STORE_PLAINTEXT_PW" = "1" ] ; then
+				PASS="$2"
+			else
+				PASS=""
+			fi
+
+			QUERY="insert into $SUB_TABLE ($SUBSCRIBER_COLUMN,\
+				$REALM_COLUMN,$HA1_COLUMN,$HA1B_COLUMN,$PASSWORD_COLUMN) \
+				values ('$OSERUSER','$OSERDOMAIN','$HA1','$HA1B','$PASS');";
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "introducing the new user '$1' to the database failed"
+			else
+				mecho "new user '$1' added"
+			fi
+			;;
+
+		passwd)
+			if [ $# -ne 3 ] ; then
+				usage_subscriber
+				exit 1
+			fi
+			shift
+			credentials $1 $2
+
+			is_user $1
+			if [ $? -ne 0 ] ; then
+				merr "non-existent user '$1'"
+				exit 1
+			fi
+
+			if [ "$STORE_PLAINTEXT_PW" = "1" ] ; then
+				PASS="$2"
+			else
+				PASS=""
+			fi
+
+			QUERY="update $SUB_TABLE set $HA1_COLUMN='$HA1', \
+$HA1B_COLUMN='$HA1B', $PASSWORD_COLUMN='$PASS' \
+WHERE $SUBSCRIBER_COLUMN='$OSERUSER' and $REALM_COLUMN='$OSERDOMAIN';"
+			$DBCMD "$QUERY"
+			if [ $? -ne 0 ] ; then
+				merr "password change failed"
+			else
+				minfo "password change succeeded"
+			fi
+			;;
+
+		rm)
+			if [ $# -ne 2 ] ; then
+				usage_subscriber
+				exit 1
+			fi
+
+			require_ctlengine
+			shift 
+
+			is_user $1 
+			if [ $? -ne 0 ] ; then
+				merr "non-existent user '$1'"
+				exit 1
+			fi
+
+			# begin with remove all user's privileges
+			acl revoke $1  > /dev/null 2>&1
+
+			# destroy db-aliases
+			QUERY="delete from $DA_TABLE where $DA_USER_COLUMN='$OSERUSER' \
+and $DA_DOMAIN_COLUMN='$OSERDOMAIN';"
+			$DBCMD "$QUERY"
+
+
+			# destroy the user now
+			QUERY="delete from $SUB_TABLE where $SUBSCRIBER_COLUMN='$OSERUSER' \
+and $REALM_COLUMN='$OSERDOMAIN';"
+			$DBCMD "$QUERY"
+
+			# and also all his contacts
+			$0 ul rm $1   > /dev/null 2>&1
+			;;
+	esac
+
+}
+
+#
+##### ================================================ #####
+### USRLOC management
+#
+usrloc() {
+	if [ "$#" -lt 2 ] ; then
+		merr "usrloc - too few parameters"
+		usage_usrloc
+		exit 1
+	fi
+
+	require_ctlengine
+
+	if [ "$1" = "alias" ] ; then
+		USRLOC_TABLE="$ALS_TABLE"
+		if [ -z "$USRLOC_TABLE" ] ; then
+			USRLOC_TABLE=aliases
+		fi
+		CHECK_SUB=1
+	elif [ "$1" = "ul" ] ; then
+		USRLOC_TABLE="$UL_TABLE"
+		if [ -z "$USRLOC_TABLE" ] ; then
+			USRLOC_TABLE=location
+		fi
+		CHECK_SUB=0
+	elif [ "$1" = "usrloc" ] ; then
+		USRLOC_TABLE="$UL_TABLE"
+		if [ -z "$USRLOC_TABLE" ] ; then
+			USRLOC_TABLE=location
+		fi
+		CHECK_SUB=0
+	else
+		merr "usrloc - unknown subcommand '$1'"
+		usage_usrloc
+		exit 1
+	fi
+	shift
+
+	case $1 in 
+		show)
+			if [ $# -eq 2 ] ; then
+				if [ "$2" = "--brief" ] ; then
+					$CTLCMD ul_dump brief
+				else
+					set_user $2
+					$CTLCMD ul_show_contact \
+						$USRLOC_TABLE "$OSERUSER@$OSERDOMAIN"
+				fi
+			elif [ $# -eq 1 ] ; then
+				$CTLCMD ul_dump
+			else
+				merr "wrong number of params"
+				usage_usrloc
+				exit 1
+			fi
+			exit $?
+			;;
+		add)
+			if [ $# -eq 3 ] ; then
+				# expires 0 means persistent contact
+				UL_EXPIRES=0
+				UL_FLAGS=0
+				BR_FLAGS=0
+			elif [ $# -eq 4 ] ; then
+				UL_EXPIRES=$4
+				UL_FLAGS=0
+				BR_FLAGS=0
+			else
+				usage_usrloc
+				exit 1
+			fi
+			shift
+			check_uri "$2"
+				
+			if [ "$?" -ne "0" ] ; then
+				merr "$2 is not a valid URI"
+				exit 1
+			fi
+
+			set_user $1
+			if [ "$CHECK_SUB" -ne 0 ] ; then
+				is_user $1 
+				if [ $? -eq 0 ] ; then
+					merr "overlap of alias with an existing subscriber name"
+					exit 1;
+				fi
+			fi
+
+			check_alias $OSERUSER $OSERDOMAIN
+			if [ "$ALIAS_EXISTS" = "1" ] ; then
+				if [ "$CHECK_SUB" -ne 0 ] ; then
+					minfo "alias already defined"
+				else
+					merr "AOR is an alias"
+				fi
+				exit 1
+			fi
+
+			$CTLCMD ul_add "$USRLOC_TABLE" "$OSERUSER@$OSERDOMAIN" "$2" \
+"$UL_EXPIRES" "1.00" "0" "$UL_FLAGS" "$BR_FLAGS" "$ALL_METHODS"
+			exit $?
+			;;
+		rm)
+			if [ $# -eq 2 ] ; then
+				shift
+				set_user $1
+				$CTLCMD ul_rm $USRLOC_TABLE "$OSERUSER@$OSERDOMAIN"
+
+			elif [ $# -eq 3 ] ; then
+				shift
+				set_user $1
+				check_uri "$2"
+				if [ "$?" -ne "0" ] ; then
+					merr "$2 is not a valid SIP URI (sip:[user@]domain)"
+					exit 1
+				fi
+
+				$CTLCMD ul_rm_contact $USRLOC_TABLE "$OSERUSER@$OSERDOMAIN" "$2"
+
+			else
+				merr "wrong number of params"
+				usage_usrloc
+				exit 1
+			fi
+			;;
+
+		*)
+			usage_usrloc
+			exit 1
+			;;
+	esac
+}
+
+##### ================================================ #####
+### TLS CA management
+#
+
+tls_ca() {
+
+	if [ "$1" = "rootCA" ] ; then
+		if [ -z $2 ] ; then
+			# use default
+			CA_BASE=$ETCDIR/tls
+		else
+			CA_BASE=`(cd $2;pwd)`
+		fi
+
+		if [ ! -d $CA_BASE ] ; then
+			merr "Config directory ($CA_BASE) does not exist"
+			exit 1
+		fi
+
+		CA_CONF='ca.conf'
+		CA_PATH=$CA_BASE/rootCA
+		if [ ! -f $CA_BASE/$CA_CONF  ] ; then
+			merr "root CA config file ($CA_BASE/$CA_CONF) does not exist"
+			exit 1
+		fi
+
+		if [ -d $CA_PATH ] ; then
+			mwarn "root CA directory ($CA_PATH) exists! Remove it (y/n)?"
+			read X
+			if [ "$X" != "y" -a "$X" != "Y" ] ; then
+				exit 1
+			fi
+		fi
+
+		mecho "Creating directory $CA_PATH and its sub-tree"
+		mkdir -p $CA_PATH
+		if [ $? -ne 0 ] ; then
+			merr "Failed to create root directory $CA_PATH"
+			exit 1
+		fi
+		rm -fr $CA_PATH/*
+		mkdir $CA_PATH/private
+		mkdir $CA_PATH/certs
+		touch $CA_PATH/index.txt
+		echo 01 >$CA_PATH/serial
+
+		mecho "Creating CA self-signed certificate"
+		( cd $CA_PATH; openssl req -config $CA_BASE/$CA_CONF -x509 -newkey \
+			rsa:2048 -days 365 -out ./cacert.pem -outform PEM )
+		if [ $? -ne 0 ] ; then
+			merr "Failed to create self-signed certificate"
+			exit 1
+		fi
+
+		mecho "Protecting CA private key"
+		chmod 600 $CA_PATH/private/cakey.pem
+
+		mecho "DONE"
+		minfo "Private key can be found in $CA_PATH/private/cakey.pem"
+		minfo "Certificate can be found in $CA_PATH/cacert.pem"
+
+	elif [ "$1" = "userCERT" ] ; then
+
+		if [ -z $2 ] ; then
+			merr "Missing user name parameter"
+			exit 1
+		fi
+
+		if [ -z $3 ] ; then
+			# use default
+			CA_BASE=$ETCDIR/tls
+		else
+			CA_BASE=`(cd $3;pwd)`
+		fi
+
+		if [ ! -d $CA_BASE ] ; then
+			merr "Config directory ($CA_BASE) does not exist"
+			exit 1
+		fi
+
+		USER_DIR=$CA_BASE/$2
+		USER_CFG=$CA_BASE/$2.conf
+		USER=$2
+		REQ_CFG=$CA_BASE/request.conf
+
+		if [ ! -f $USER_CFG ] ; then
+			merr "User config file $USER_CFG not found"
+			exit 1
+		fi
+
+		if [ ! -f $REQ_CFG ] ; then
+			merr "Request config file $REQ_CFG not found"
+			exit 1
+		fi
+
+		mecho "Using config file $USER_CFG"
+
+		if [ -d $USER_DIR ] ; then
+			mwarn "User CERT directory ($USER_DIR) exists! Remove it (y/n)?"
+			read X
+			if [ "$X" != "y" -a "$X" != "Y" ] ; then
+				exit 1
+			fi
+		fi
+
+		mecho "Creating directory $USER_DIR"
+		mkdir -p $USER_DIR
+		if [ $? -ne 0 ] ; then
+			merr "Failed to create user directory $USER_DIR "
+			exit 1
+		fi
+		rm -fr $USER_DIR/*
+
+		mecho "Creating user certificate request"
+		openssl req  -config $USER_CFG -out $USER_DIR/$USER-cert_req.pem \
+			-keyout $USER_DIR/$USER-privkey.pem -new -nodes
+		if [ $? -ne 0 ] ; then
+			merr "Failed to generate certificate request"
+			exit 1
+		fi
+
+		mecho "Signing certificate request"
+		( cd $CA_BASE ; openssl ca -config $REQ_CFG -in \
+			$USER_DIR/$USER-cert_req.pem -out $USER_DIR/$USER-cert.pem )
+		if [ $? -ne 0 ] ; then
+			merr "Failed to generate certificate request"
+			exit 1
+		fi
+
+		mecho "Generating CA list"
+		cat $CA_BASE/rootCA/cacert.pem >> $USER_DIR/$USER-calist.pem
+
+		mecho "DONE"
+		minfo "Private key is locate at $USER_DIR/$USER-privkey.pem "
+		minfo "Certificate is locate at $USER_DIR/$USER-cert.pem "
+		minfo "CA-List is locate at $USER_DIR/$USER-calist.pem "
+
+	else
+		merr "unknown TLS command $1"
+		usage_tls
+		exit 1
+	fi
+}
+
+#
+##### ================================================ #####
+### main command switch
+#
+case $1 in
+	acl)
+		shift
+		acl "$@"
+		;;
+
+	add)
+		subscriber "$@"
+		;;
+
+	passwd)
+		subscriber "$@"
+		;;
+
+	rm)
+		subscriber "$@"
+		;;
+
+	alias|ul|usrloc)
+		usrloc "$@"
+		;;
+
+	alias_db|aliasdb)
+		alias_db "$@"
+		;;
+		
+	avp)
+		avpops "$@"
+		;;
+
+	cisco_restart)
+		if [ "$#" -ne 2 ] ; then	
+			usage_cisco_restart
+			exit 1
+		fi
+		cisco_restart $2
+		;;
+
+	db)
+		shift
+		db_ops "$@"
+		;;
+
+	showdb|userdb)
+		usage
+		exit 1
+		;;
+
+	domain)
+		shift
+		domain "$@"
+		;;
+
+	trusted)
+		shift
+		trusted "$@"
+		;;
+
+	fifo|unixsock)
+		require_ctlengine
+		shift
+		$CTLCMD "$@"
+		;;
+
+	lcr)
+		shift
+		lcr "$@"
+		;;
+
+	cr)
+		shift
+		cr "$@"
+		;;
+
+	trusted)
+		shift
+		trusted "$@"
+		;;
+
+	dispatcher)
+		shift
+		dispatcher "$@"
+		;;
+
+	dialplan)
+		shift
+		dialplan "$@"
+		;;
+
+	monitor|console|moni|con)
+		require_ctlengine
+		$OPENSER_MONITOR "$@"
+		;;
+
+	online)
+		require_ctlengine
+		$CTLCMD ul_dump | $EGREP -i aor | awk '{print $2}' | sort | sort -mu
+		exit $?
+		;;
+
+	ping)
+		# error handling is hacked -- filter_fl should not
+		# consume positive status -- that should be done by
+		# calling app
+		if [ "$#" -ne 2 ] ; then
+			usage_ping
+			exit 1
+		fi
+		options_ping $2
+		;;
+
+	ps)
+		require_ctlengine
+		$CTLCMD ps
+		;;
+
+	restart)
+		openser_stop
+		sleep 2
+		openser_start
+		;;
+	
+	rpid)
+		rpid "$@"
+		;;
+
+	speeddial|speed_dial)
+		speeddial "$@"
+		;;
+
+	tls)
+		shift
+		tls_ca "$@"
+		;;
+
+	start)
+		openser_start
+		;;
+
+	stop)
+		openser_stop
+		;;
+
+	version)
+		echo  "$0 $VERSION"
+		;;
+		
+	*)
+		usage
+		exit 1
+		;;
+esac
+

+ 238 - 0
tools/kamctl.8

@@ -0,0 +1,238 @@
+.\" $Id$
+.TH kamctl 8 05.02.2009 Kamailio "Kamailio" 
+.\" Process with
+.\" groff -man -Tascii kamctl.8 
+.\"
+.SH NAME
+kamctl \- Kamailio control tool
+.SH SYNOPSIS
+.B kamctl
+.BI command
+[
+.BI parameters
+]
+
+.SH DESCRIPTION
+.B kamctl
+is a shell script to control
+.B Kamailio SIP server
+It can be used to manage users, domains, aliases and other server options.
+
+
+.SH COMMANDS
+.TP 16
+.I Daemon Commands:
+.TP
+.B start
+Start Kamalio 
+.TP
+.B restart
+Restart Kamalio 
+.TP
+.B stop
+Stop Kamalio 
+.TP
+.B online 
+Display online users
+.TP
+.B monitor
+Show server's internal status
+.TP
+.B ping <uri> 
+Ping <uri> with SIP OPTIONS
+
+
+.TP 16
+.I Access control list (acl)  managment commands:
+.TP
+.B acl show [<username>]
+Show user membership
+.TP 
+.B acl grant <username> <group>
+Grant user membership (*)
+.TP
+.B acl revoke <username> [<group>] 
+Grant user membership(s) (*)
+
+.TP 16
+.I  Least cost routes (lcr) managment command:
+.TP             
+.B lcr show 
+Show gateways and routes tables
+.TP
+.B lcr dump
+Show in memory gateways and routes tables
+.TP
+.B lcr reload 
+Reload lcr gateways and routes
+.TP
+.B lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> <flags> <tag> <strip> <weight> <hostname> <ping>
+Add a gateway with flags, tag, strip, weight, hostname, and ping (flags, tag, strip, weight, hostname, and ping are optional)
+.TP
+.B lcr rmgw <gw_name> 
+Delete a gateway
+.TP
+.B lcr addroute <prefix> <from> <grp_id> <prio>
+Add a route ( use '' to match anything in <from> )
+.TP
+.B lcr rmroute <prefix> <from> <grp_id> <prio>
+Delete a route
+
+.TP 16
+.I Carrierroute tables('cr') managment commands:
+.TP
+.B cr show 
+Show tables
+.TP
+.B cr reload 
+Reload tables
+.TP
+.B cr dump 
+Show in memory tables
+.TP
+.B cr addrt <routing_tree_id> <routing_tree> 
+Add a tree
+.TP
+.B cr rmrt  <routing_tree> 
+Remove a tree
+.TP
+.B cr addcarrier <carrier> <scan_prefix> <domain> <rewrite_host> <prob> <strip> <rewrite_prefix> <rewrite_suffix> <flags> <mask> <comment> 
+Add a carrier (prob, strip, rewrite_prefix, rewrite_suffix, flags, mask and comment are optional arguments)
+.TP
+.B cr rmcarrier  <carrier> <scan_prefix> <domain> 
+Remove a carrier
+
+.TP 16
+.I Remote-Party-ID (RPID) managment commands:
+.TP
+.B rpid add <username> <rpid>
+Add rpid for a user (*)
+.TP
+.B rpid rm <username>
+Set rpid to NULL for a user (*)
+.TP
+.B rpid show <username>
+Show rpid of a user
+
+.TP 16
+.I Subscriber managment commands:
+.TP
+.B add <username> <password> 
+ Add a new subscriber (*)
+.TP
+.B passwd <username> <passwd>
+Change user's password (*)
+.TP
+.B rm <username> 
+Delete a user (*)
+
+.TP 16
+.I Commands to manage 'trusted':
+.TP
+.B trusted show 
+Show db content
+.TP
+.B trusted dump 
+Show cache content
+.TP
+.B trusted reload
+Reload db table into cache
+.TP
+.B trusted add <src_ip> <proto> <from_pattern> <tag>
+Add a new entry (from_pattern and tag are optional arguments)
+.TP
+.B trusted rm <src_ip>
+Remove all entres for the given src_ip
+
+.TP 16
+.I Dispatcher managment commands:
+.TP
+.B dispatcher show 
+Show dispatcher gateways
+.TP
+.B dispatcher reload 
+Reload dispatcher gateways
+.TP
+.B dispatcher dump 
+Show in memory dispatcher gateways
+.TP
+.B dispatcher addgw <setid> <destination> <flags> <description>
+Add gateway
+.TP
+.B dispatcher rmgw <id> 
+Delete gateway
+
+.TP 16
+.I Cisco restart command:
+.TP
+.B cisco_restart <uri> 
+Restart phone configured for <uri>
+
+.TP 16
+.I User location('ul') or aliases managment commands:
+.B ul show [<username>]
+Show in-RAM online users
+.TP
+.B ul show --brief
+Show in-RAM online users in short format
+.TP
+.B ul rm <username> [<contact URI>]
+Delete user's usrloc entries
+.TP
+.B ul add <username> <uri>
+Introduce a permanent usrloc entry
+.TP
+.B ul add <username> <uri> <expires>
+Introduce a temporary usrloc entry
+
+.TP 16
+.I Fifo commands:
+.TP
+.B fifo 
+Send raw FIFO command
+
+
+.SH FILES
+.PD 0
+.I /etc/kamailio/.kamctlrc
+.br
+.I /usr/local/etc/kamailio/.kamctlrc
+.br
+.I ~/.kamctlrc
+.br
+
+.SH NOTES
+.PP
+Commands labeled with (*) will prompt for a MySQL password.
+If the environment variable PW is set, the password will not be prompted.
+.PP
+IP addresses must be entered in dotted quad format e.g. 1.2.3.4
+<uri_scheme> and <transport> must be entered in integer or text,
+e.g. transport '2' is identical to transport 'tcp'.
+.br
+scheme: 1=sip, 2=sips;   transport: 1=udp, 2=tcp, 3=tls
+.br
+Examples:
+.br
+lcr addgw level3 1.2.3.4 5080 sip tcp 1
+.br
+lcr addroute +1 '' 1 1
+
+.SH AUTHORS
+
+see 
+.B /usr/share/doc/kamailio/AUTHORS
+
+.SH SEE ALSO
+.BR kamailio(8),
+.BR kamailio.cfg(5)
+.PP
+Full documentation on Kamailio is available at
+.I http://www.kamailio.org/.
+.PP
+Mailing lists:
+.nf 
[email protected] - Kamailio user community
+.nf 
[email protected] - Kamailio development, new features and unstable version
+

+ 660 - 0
tools/kamctl.base

@@ -0,0 +1,660 @@
+#
+# $Id$
+#
+# control tool for maintaining Kamailio
+#
+#===================================================================
+
+##### ----------------------------------------------- #####
+### path to useful tools
+locate_tool() {
+	TOOLPATH=""
+	while [ -n "$1" ]
+	do
+		if [ -x /usr/bin/which ] ; then
+			TOOLPATH=`which $1`
+			if [ -n "$TOOLPATH" ]; then
+				return
+			fi
+		fi
+		# look in common locations
+		if [ -x "/usr/bin/$1" ] ; then
+			TOOLPATH="/usr/bin/$1"
+			return
+		fi
+		if [ -x "/bin/$1" ] ; then
+			TOOLPATH="/bin/$1"
+			return
+		fi
+		if [ -x "/usr/local/bin/$1" ] ; then
+			TOOLPATH="/usr/local/bin/$1"
+			return
+		fi
+		shift
+	done
+	return
+}
+
+if [ -z "$EGREP" ] ; then
+	locate_tool egrep
+	if [ -z "$TOOLPATH" ] ; then
+		# now error, but we can look for alternative names if it is the case
+		echo "error: 'egrep' tool not found: set EGREP variable to correct tool path"
+		exit
+	fi
+	EGREP="$TOOLPATH"
+fi
+if [ -z "$AWK" ] ; then
+	locate_tool awk
+	if [ -z "$TOOLPATH" ] ; then
+		# now error, but we can look for alternative names if it is the case
+		echo "error: 'awk' tool not found: set AWK variable to correct tool path"
+		exit
+	fi
+	AWK="$TOOLPATH"
+fi
+if [ -z "$MD5" ]; then
+	locate_tool md5sum md5
+	if [ -z "$TOOLPATH" ] ; then
+		# now error, but we can look for alternative names if it is the case
+		echo "error: 'md5sum' or 'md5' tool not found: set MD5 variable to correct tool path"
+		exit
+	fi
+	MD5="$TOOLPATH"
+fi
+if [ -z "$LAST_LINE" ] ; then
+	locate_tool tail
+	if [ -z "$TOOLPATH" ] ; then
+		# now error, but we can look for alternative names if it is the case
+		echo "error: 'tail' tool not found: set LAST_LINE variable to correct tool path"
+		exit
+	fi
+	LAST_LINE="$TOOLPATH -n 1"
+fi
+if [ -z "$EXPR" ] ; then
+	locate_tool expr
+	if [ -z "$TOOLPATH" ] ; then
+		# now error, but we can look for alternative names if it is the case
+		echo "error: 'expr' tool not found: set EXPR variable to correct tool path"
+		exit
+	fi
+	EXPR="$TOOLPATH"
+fi
+
+##### ------------------------------------------------ #####
+### configuration for starting/stopping kamailio
+if [ -z "$PID_FILE" ] ; then
+	PID_FILE=/var/run/kamailio.pid
+fi
+if [ -z "$SYSLOG" ] ; then
+	SYSLOG=1 # 0=output to console, 1=output to syslog
+fi
+if [ -z "$STARTOPTIONS" ] ; then
+	STARTOPTIONS= # for example -dddd
+fi
+if [ -z "$DIR" ] ; then
+	DIR=`dirname $0`
+fi
+if [ -z "$OSERBIN" ] ; then
+	OSERBIN=$DIR/kamailio
+fi
+
+##### ------------------------------------------------ #####
+### aliases configuration
+#
+ENABLE_ALIASES=0
+if [ "$ALIASES_TYPE" = "UL" ] ; then
+	ENABLE_ALIASES=1
+else
+	if [ "$ALIASES_TYPE" = "DB" ] ; then
+		ENABLE_ALIASES=2
+	fi
+fi
+
+##### ------------------------------------------------ #####
+### ACL name verification
+if [ -z "$VERIFY_ACL" ] ; then
+	VERIFY_ACL=1
+fi
+if [ -z "$ACL_GROUPS" ] ; then
+	ACL_GROUPS="local ld int voicemail free-pstn"
+fi
+
+##### ----------------------------------------------- #####
+#### Defined values
+ALL_METHODS=4294967295
+USERNAME_RE="[-a-zA-Z0-9&=\+\$,;\?/_\.\!~\*'\(\)]+"
+
+##### ----------------------------------------------- #####
+#### database tables for SQL databases and DBTEXT
+
+# UsrLoc Table
+if [ -z "$UL_TABLE" ] ; then
+	UL_TABLE=location
+fi
+USER_COLUMN=username
+DOMAIN_COLUMN=domain
+CALLID_COLUMN=callid
+
+# subscriber table
+if [ -z "$SUB_TABLE" ] ; then
+	SUB_TABLE=subscriber
+fi
+REALM_COLUMN=domain
+HA1_COLUMN=ha1
+HA1B_COLUMN=ha1b
+PASSWORD_COLUMN=password
+RPID_COLUMN=rpid
+SUBSCRIBER_COLUMN='username'
+PHP_LIB_COLUMN=phplib_id
+
+if [ -z "$STORE_PLAINTEXT_PW" ] ; then
+	STORE_PLAINTEXT_PW=1
+fi
+
+# acl table
+if [ -z "$ACL_TABLE" ] ; then
+	ACL_TABLE=grp
+fi
+ACL_USER_COLUMN=username
+ACL_DOMAIN_COLUMN=domain
+ACL_GROUP_COLUMN=grp
+ACL_MODIFIED_COLUMN=last_modified
+
+# aliases table
+if [ -z "$ALS_TABLE" ] ; then
+	ALS_TABLE=aliases
+fi
+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
+
+# domain table
+if [ -z "$DOMAIN_TABLE" ] ; then
+	DOMAIN_TABLE=domain
+fi
+DO_DOMAIN_COLUMN=domain
+DO_LAST_MODIFIED_COLUMN=last_modified
+
+# trusted table
+if [ -z "$TRUSTED_TABLE" ] ; then
+	TRUSTED_TABLE=trusted
+fi
+
+# lcr tables
+if [ -z "$LCR_TABLE" ] ; then
+	LCR_TABLE=lcr
+fi
+LCR_PREFIX_COLUMN=prefix
+LCR_FROMURI_COLUMN=from_uri
+LCR_GRPID_COLUMN=grp_id
+LCR_PRIO_COLUMN=priority
+
+# gw table
+if [ -z "$GW_TABLE" ] ; then
+	GW_TABLE=gw
+fi
+LCR_GW_GWNAME_COLUMN=gw_name
+LCR_GW_GRPID_COLUMN=grp_id
+LCR_GW_IP_COLUMN=ip_addr
+LCR_GW_HOSTNAME_COLUMN=hostname
+LCR_GW_PORT_COLUMN=port
+LCR_GW_URIS_COLUMN=uri_scheme
+LCR_GW_PROTO_COLUMN=transport
+LCR_GW_STRIP_COLUMN=strip
+LCR_GW_TAG_COLUMN=tag
+LCR_GW_FLAGS_COLUMN=flags
+LCR_GW_WEIGHT_COLUMN=weight
+LCR_GW_PING_COLUMN=ping
+
+# carrier_name table
+if [ -z "$CARRIER_NAME_TABLE" ] ; then
+	CARRIER_NAME_TABLE=carrier_name
+fi
+CARRIERROUTE_CARRIER_NAME_ID_COLUMN=id
+CARRIERROUTE_CARRIER_NAME_CARRIER_COLUMN=carrier
+
+# domain_name table
+if [ -z "$DOMAIN_NAME_TABLE" ] ; then
+	DOMAIN_NAME_TABLE=domain_name
+fi
+CARRIERROUTE_DOMAIN_NAME_ID_COLUMN=id
+CARRIERROUTE_DOMAIN_NAME_DOMAIN_COLUMN=domain
+
+# carrierroute table
+if [ -z "$CARRIERROUTE_TABLE" ] ; then
+	CARRIERROUTE_TABLE=carrierroute
+fi
+CARRIERROUTE_CARRIERROUTE_PREFIX_COLUMN=id
+CARRIERROUTE_CARRIERROUTE_CARRIER_COLUMN=carrier
+CARRIERROUTE_CARRIERROUTE_SCAN_PREFIX_COLUMN=scan_prefix
+CARRIERROUTE_CARRIERROUTE_DOMAIN_COLUMN=domain
+CARRIERROUTE_CARRIERROUTE_PROB_COLUMN=prob
+CARRIERROUTE_CARRIERROUTE_STRIP_COLUMN=strip
+CARRIERROUTE_CARRIERROUTE_REWRITE_HOST_COLUMN=rewrite_host
+CARRIERROUTE_CARRIERROUTE_REWRITE_PREFIX_COLUMN=rewrite_prefix
+CARRIERROUTE_CARRIERROUTE_REWRITE_SUFFIX_COLUMN=rewrite_suffix
+CARRIERROUTE_CARRIERROUTE_COMMENT_COLUMN=description
+CARRIERROUTE_CARRIERROUTE_FLAGS_COLUMN=flags
+CARRIERROUTE_CARRIERROUTE_MASK_COLUMN=mask
+
+# URI table
+if [ -z "$URI_TABLE" ] ; then
+	URI_TABLE=uri
+fi
+URIUSER_COLUMN=uri_user
+MODIFIED_COLUMN=last_modified
+
+# dbaliases table
+if [ -z "$DA_TABLE" ] ; then
+	DA_TABLE=dbaliases
+fi
+DA_USER_COLUMN=username
+DA_DOMAIN_COLUMN=domain
+DA_ALIAS_USER_COLUMN=alias_username
+DA_ALIAS_DOMAIN_COLUMN=alias_domain
+
+# speeddial table
+if [ -z "$SD_TABLE" ] ; then
+	SD_TABLE=speed_dial
+fi
+SD_USER_COLUMN=username
+SD_DOMAIN_COLUMN=domain
+SD_SD_USER_COLUMN=sd_username
+SD_SD_DOMAIN_COLUMN=sd_domain
+SD_NEW_URI_COLUMN=new_uri
+SD_DESC_COLUMN=description
+
+# avp table
+if [ -z "$AVP_TABLE" ] ; then
+	AVP_TABLE=usr_preferences
+fi
+
+AVP_UUID_COLUMN=uuid
+AVP_USER_COLUMN=username
+AVP_DOMAIN_COLUMN=domain
+AVP_ATTRIBUTE_COLUMN=attribute
+AVP_VALUE_COLUMN=value
+AVP_TYPE_COLUMN=type
+AVP_MODIFIED_COLUMN=last_modified
+
+# trusted table
+if [ -z "$TRUSTED_TABLE" ] ; then
+	TRUSTED_TABLE=trusted
+fi
+
+TRUSTED_SRC_IP_COLUMN=src_ip
+TRUSTED_PROTO_COLUMN=proto
+TRUSTED_FROM_PATTERN_COLUMN=from_pattern
+TRUSTED_TAG_COLUMN=tag
+
+# dispatcher tables  
+if [ -z "$DISPATCHER_TABLE" ] ; then
+	DISPATCHER_TABLE=dispatcher
+fi
+DISPATCHER_ID_COLUMN=id
+DISPATCHER_SETID_COLUMN=setid
+DISPATCHER_DESTINATION_COLUMN=destination
+DISPATCHER_FLAGS_COLUMN=flags
+DISPATCHER_DESCRIPTION_COLUMN=description
+
+# dialplan tables
+if [ -z "$DIALPLAN_TABLE" ] ; then
+	DIALPLAN_TABLE=dialplan
+fi
+DIALPLAN_ID_COLUMN=id
+DIALPLAN_DPID_COLUMN=dpid
+DIALPLAN_PR_COLUMN=pr
+DIALPLAN_MATCH_OP_COLUMN=match_op
+DIALPLAN_MATCH_EXP_COLUMN=match_exp
+DIALPLAN_MATCH_LEN_COLUMN=match_len
+DIALPLAN_SUBST_EXP_COLUMN=subst_exp
+DIALPLAN_REPL_EXP_COLUMN=repl_exp
+DIALPLAN_ATTRS_COLUMN=attrs
+
+#
+##### ------------------------------------------------ #####
+### usage functions
+#
+
+usage_base() {
+	echo
+	mecho " -- command 'start|stop|restart'"
+	echo
+cat <<EOF
+ restart ............................ restart Kamailio
+ start .............................. start Kamailio
+ stop ............................... stop Kamailio
+EOF
+}
+
+usage_tls() {
+	echo
+	mecho " -- command 'tls'"
+	echo
+cat <<EOF
+ tls rootCA [<etcdir>] .......... creates new rootCA
+ tls userCERT <user> [<etcdir>] ... creates user certificate
+ default <etcdir> is $ETCDIR/tls
+EOF
+}
+
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_base"
+
+usage_acl() {
+	echo
+	mecho " -- command 'acl' - manage access control lists (acl)"
+	echo
+cat <<EOF
+ acl show [<username>] .............. show user membership
+ acl grant <username> <group> ....... grant user membership (*)
+ acl revoke <username> [<group>] .... grant user membership(s) (*)
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_acl"
+
+usage_lcr() {
+	echo
+	mecho " -- command 'lcr' - manage least cost routes (lcr)"
+	echo
+cat <<EOF
+   * IP addresses must be entered in dotted quad format e.g. 1.2.3.4   *
+   * <uri_scheme> and <transport> must be entered in integer or text,  *
+   * e.g. transport '2' is identical to transport 'tcp'.               *
+   *   scheme: 1=sip, 2=sips;   transport: 1=udp, 2=tcp, 3=tls, 4=sctp *
+   * Examples:  lcr addgw level3 1.2.3.4 5080 sip tcp 1                *
+   *            lcr addroute +1 '' 1 1                                 *
+ lcr show .......... show gateways and routes tables
+ lcr dump .......... show in memory gateways and routes tables
+ lcr reload ........ reload lcr gateways and routes
+ lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> <flags> <tag> <strip> <weight> <hostname> <ping>
+           ......... add a gateway with flags, tag, strip, weight, hostname, and ping
+           ......... (flags, tag, strip, weight, hostname, and ping are optional)
+ lcr rmgw <gw_name> delete a gateway
+ lcr addroute <prefix> <from> <grp_id> <prio>
+           ......... add a route ( use '' to match anything in <from> )
+ lcr rmroute <prefix> <from> <grp_id> <prio>
+           ......... delete a route
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_lcr"
+
+usage_cr() {
+	echo
+	mecho " -- command 'cr' - manage carrierroute tables"
+	echo
+cat <<EOF
+ cr show ....................................................... show tables
+ cr reload ..................................................... reload tables
+ cr dump ....................................................... show in memory tables
+ cr addcn <carrier id> <carrier name> .......................... add a carrier name
+ cr rmcn  <carrier id> ......................................... rm a carrier name
+ cr adddn <domain id> <domain name> ............................ add a domain name
+ cr rmdn  <domain id> .......................................... rm a domain name
+ cr addcarrier <carrier> <scan_prefix> <domain> <rewrite_host> ................
+               <prob> <strip> <rewrite_prefix> <rewrite_suffix> ...............
+               <flags> <mask> <comment> .........................add a carrier
+               (prob, strip, rewrite_prefix, rewrite_suffix,...................
+                flags, mask and comment are optional arguments) ...............
+ cr rmcarrier  <carrier> <scan_prefix> <domain> ................ rm a carrier
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_cr"
+
+usage_rpid() {
+	echo
+	mecho " -- command 'rpid' - manage Remote-Party-ID (RPID)"
+	echo
+cat <<EOF
+ rpid add <username> <rpid> ......... add rpid for a user (*)
+ rpid rm <username> ................. set rpid to NULL for a user (*)
+ rpid show <username> ............... show rpid of a user
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_rpid"
+
+usage_subscriber() {
+	echo
+	mecho " -- command 'add|passwd|rm' - manage subscribers"
+	echo
+cat <<EOF
+ add <username> <password> .......... add a new subscriber (*)
+ passwd <username> <passwd> ......... change user's password (*)
+ rm <username> ...................... delete a user (*)
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_subscriber"
+
+usage_trusted() {
+	echo
+	mecho " -- command 'add|dump|reload|rm|show' - manage trusted"
+	echo
+cat <<EOF
+ trusted show ...................... show db content
+ trusted dump ...................... show cache content
+ trusted reload .................... reload db table into cache
+ trusted add <src_ip> <proto> <from_pattern> <tag>
+             ....................... add a new entry
+	     ....................... (from_pattern and tag are optional arguments)
+ trusted rm <src_ip> ............... remove all entres for the given src_ip
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_trusted"
+
+usage_dispatcher() {
+	echo
+	mecho " -- command 'dispatcher' - manage dispatcher"
+	echo
+cat <<EOF
+   * Examples:  dispatcher addgw 1 sip:1.2.3.1:5050 1 'outbound gateway'
+   *            dispatcher addgw 2 sip:1.2.3.4:5050 3 ''
+   *            dispatcher rmgw 4
+ dispatcher show ..................... show dispatcher gateways
+ dispatcher reload ................... reload dispatcher gateways
+ dispatcher dump ..................... show in memory dispatcher gateways
+ dispatcher addgw <setid> <destination> <flags> <description>
+            .......................... add gateway
+ dispatcher rmgw <id> ................ delete gateway
+EOF
+}
+USAGE_FUNCTIONS="$USAGE_FUNCTIONS usage_dispatcher"
+
+usage_dialplan() {
+	echo
+	mecho " -- command 'dialplan' - manage dialplans"
+	echo
+cat <<EOF
+ dialplan show <dpid> .............. show dialplan tables
+ dialplan reload ................... reload dialplan tables
+ dialplan addrule <dpid> <prio> <match_op> <match_exp>
+ 		<match_len> <subst_exp> <repl_exp> <attrs>
+		.................... add a rule
+ dialplan rm ....................... removes the entire dialplan table
+ dialplan rmdpid <dpid> ............ removes all the gived dpid entries
+ dialplan rmrule <dpid> <prio> ..... removes all the gived dpid/prio entries
+EOF
+}
+
+##### ----------------------------------------------- #####
+#### Common functions
+
+
+mdbg() {
+	if [ "0$VERBOSE" -ne 0 ] ; then
+		if [ -t 1 -a -z "$NOHLPRINT" ] ; then
+			echo -e "\033[1m$1\033[0m"
+		else
+			echo "$1"
+		fi
+	fi
+}
+
+mwarn() {
+	if [ -t 1 -a -z "$NOHLPRINT" ] ; then
+		echo -e '\E[37;32m'"\033[1mWARNING: $1\033[0m"
+	else
+		echo "** WARNING: $1"
+	fi
+}
+
+minfo() {
+	if [ -t 1 -a -z "$NOHLPRINT" ] ; then
+		echo -e '\E[37;33m'"\033[1mINFO: $1\033[0m"
+	else
+		echo "** INFO: $1"
+	fi
+}
+
+mecho() {
+	if [ -t 1 -a -z "$NOHLPRINT" ] ; then
+		echo -e "\033[1m$1\033[0m"
+	else
+		echo "$1"
+	fi
+}
+
+merr() {
+	if [ -t 1 -a -z "$NOHLPRINT" ] ; then
+		echo -e '\E[37;31m'"\033[1mERROR: $1\033[0m"
+	else
+		echo "** ERROR: $1"
+	fi
+}
+
+
+# determine host name, typically for use in printing UAC
+# messages; we use today a simplistic but portable uname -n way --
+# no domain name is displayed ; fifo_uac expands !! to host
+# address only for optional header fields; uname output without
+# domain is sufficient for informational header fields such as
+# From
+#
+get_my_host() {
+	if [ -z "$SIP_DOMAIN" ]; then
+		uname -n
+	else
+		echo "$SIP_DOMAIN"
+	fi
+}
+
+# calculate name and domain of current user
+set_user() {
+
+	OSERUSER=`echo $1|$AWK -F@ '{print $1}'`
+	OSERDOMAIN=`echo $1|$AWK -F@ '{print $2}'`
+
+	if [ -z "$OSERDOMAIN" ] ; then
+		OSERDOMAIN="$SIP_DOMAIN"
+	fi
+
+	if [ -z "$OSERDOMAIN" ] ; then
+		merr "domain unknown: use usernames with domain or set default domain \
+in SIP_DOMAIN"
+		exit 1
+	fi
+}
+
+# check the parameter if it is a valid address of record (user@domain)
+check_aor() {
+	echo "$1" | $EGREP "^$USERNAME_RE@.*\..*" >/dev/null
+	if [ $? -ne 0 ] ; then 
+		echo "error: invalid AoR: $1" > /dev/stderr
+		exit 1
+	fi
+}
+
+# check the parameter if it is a valid address of record (user@domain)
+is_aor() {
+	echo "$1" | $EGREP "^$USERNAME_RE@.*\..*" >/dev/null
+	if [ $? -ne 0 ] ; then 
+		false
+	else
+		true
+	fi
+}
+
+# check the parameter if it is a valid SIP address of record (sip:user@domain)
+check_sipaor() {
+	echo "$1" | $EGREP "^sip(s)?:$USERNAME_RE@.*\..*" >/dev/null
+	if [ $? -ne 0 ] ; then 
+		echo "error: invalid SIP AoR: $1" > /dev/stderr
+		exit 1
+	fi
+}
+
+# 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(s)?:($USERNAME_RE@)?.*\..*"  > /dev/null
+	if [ $? -ne 0 ] ; then 
+		echo "error: invalid SIP URI: $1" > /dev/stderr
+		exit 1
+	fi
+}
+
+print_status() {
+	echo $1 | $EGREP "^[1-6][0-9][0-9]" > /dev/null
+	if [ "$?" -eq 0 ] ; then 
+		echo $1
+	else
+		echo "200 OK"
+	fi
+}
+
+# process output from FIFO/Unixsock 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++}
+		NR == 1 && /^200 OK/ { next }
+		/^$/ { next }
+		{ print }'
+}
+
+# params: user, realm, password
+# output: HA1
+_gen_ha1()
+{
+	HA1=`echo -n "$1:$2:$3" | $MD5 | $AWK '{ print $1 }'`
+	if [ $? -ne 0 ] ; then
+		echo "HA1 calculation failed"
+		exit 1
+	fi
+}
+
+# params: user, realm, password
+# output: HA1B
+_gen_ha1b()
+{
+	HA1B=`echo -n "$1@$2:$2:$3" | $MD5 | $AWK '{ print $1 }'`
+	if [ $? -ne 0 ] ; then
+		echo "HA1B calculation failed"
+		exit 1
+	fi
+}
+
+# params: user, realm, password
+# output: PHPLIB_ID
+_gen_phplib_id()
+{
+	NOW=`date`;
+	PHPLIB_ID=`echo -n "$1$2:$3:$NOW" | $MD5 | $AWK '{ print $1 }'`
+}
+
+# params: user, password
+# output: HA1, HA1B
+credentials()
+{
+	set_user $1
+	_gen_ha1 "$OSERUSER" "$OSERDOMAIN" "$2"
+	_gen_ha1b "$OSERUSER" "$OSERDOMAIN" "$2"
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно