Browse Source

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 năm trước cách đây
mục cha
commit
427dfb25f5
100 tập tin đã thay đổi với 5885 bổ sung0 xóa
  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"
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác