dbsep.cgi 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/perl
  2. #
  3. # Copyright (c) 2008 Digium, Inc.
  4. #
  5. # Tilghman Lesher <dbsep.cgi@the-tilghman.com>
  6. #
  7. # See http://www.asterisk.org for more information about
  8. # the Asterisk project. Please do not directly contact
  9. # any of the maintainers of this project for assistance;
  10. # the project provides a web site, mailing lists and IRC
  11. # channels for your use.
  12. #
  13. # This program is free software, distributed under the terms of
  14. # the GNU General Public License Version 2. See the LICENSE file
  15. # at the top of the source tree.
  16. #
  17. # $Id$
  18. #
  19. use CGI;
  20. use DBI;
  21. use strict;
  22. my ($cgi, $dbh, %cfg, $table, $mode);
  23. # The following settings are expected:
  24. #
  25. # dsn=<some valid dsn>
  26. # dbuser=<user>
  27. # dbpass=<passwd>
  28. # dbschema=<dbname>
  29. # backslash_is_escape={yes|no}
  30. #
  31. open CFG, "</etc/asterisk/dbsep.conf";
  32. while (<CFG>) {
  33. chomp;
  34. next if (m/^[#;]/);
  35. next if (m/^\s*$/);
  36. my ($name,@value) = split '=';
  37. $cfg{lc($name)} = join('=', @value);
  38. }
  39. close CFG;
  40. $cgi = new CGI;
  41. $ENV{PATH_INFO} =~ m/\/([^\/]*)\/([^\/]*)$/;
  42. ($table, $mode) = ($1, lc($2));
  43. #print STDERR "PATH_INFO=$ENV{PATH_INFO}, table=$table, mode=$mode\n";
  44. if ($mode eq 'single') {
  45. # All parameters as POST
  46. my ($sql, $sth, $row, @answer);
  47. $sql = "SELECT * FROM $table WHERE " . join(" AND ", cgi_to_where_clause($cgi, \%cfg));
  48. $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  49. $sth = $dbh->prepare($sql) || throw_error("Invalid query: $sql");
  50. $sth->execute() || throw_error("Invalid query: $sql");
  51. $row = $sth->fetchrow_hashref();
  52. foreach (keys %$row) {
  53. foreach my $item (split /\;/, $row->{$_}) {
  54. push @answer, encode($_) . "=" . encode($item);
  55. }
  56. }
  57. $sth->finish();
  58. $dbh->disconnect();
  59. print "Content-type: text/plain\n\n";
  60. print join("&", @answer) . "\n";
  61. } elsif ($ENV{PATH_INFO} =~ m/multi$/) {
  62. # All parameters as POST
  63. my ($sql, $sth, @answer);
  64. $sql = "SELECT * FROM $table WHERE " . join(" AND ", cgi_to_where_clause($cgi, \%cfg));
  65. $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  66. $sth = $dbh->prepare($sql) || throw_error("Invalid query: $sql");
  67. $sth->execute() || throw_error("Invalid query: $sql");
  68. print "Content-type: text/plain\n\n";
  69. while (my $row = $sth->fetchrow_hashref()) {
  70. @answer = ();
  71. foreach (keys %$row) {
  72. foreach my $item (split /\;/, $row->{$_}) {
  73. push @answer, encode($_) . "=" . encode($item);
  74. }
  75. }
  76. print join("&", @answer) . "\n";
  77. }
  78. $sth->finish();
  79. $dbh->disconnect();
  80. } elsif ($ENV{PATH_INFO} =~ m/update$/) {
  81. # where clause in GET, update parameters in POST
  82. my (%get, @get, $sql, $name, $value, $affected);
  83. foreach (split '&', $ENV{QUERY_STRING}) {
  84. ($name, $value) = split '=';
  85. $name = decode($name);
  86. next if (!isname($name));
  87. $value = escape_value(decode($value));
  88. if ($name =~ m/ /) {
  89. push @get, "$name '$value'";
  90. } else {
  91. push @get, "$name='$value'";
  92. }
  93. $get{$name}++;
  94. }
  95. $sql = "UPDATE $table SET " . join(",", cgi_to_where_clause($cgi, \%cfg, \%get)) . " WHERE " . join(" AND ", @get);
  96. $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  97. $affected = $dbh->do($sql);
  98. $dbh->disconnect();
  99. print "Content-type: text/html\n\n$affected\n";
  100. } elsif ($ENV{PATH_INFO} =~ m/store$/) {
  101. # All parameters as POST
  102. my (@param, $sql, @fields, @values, $affected);
  103. foreach my $param (cgi_to_where_clause($cgi, \%cfg)) {
  104. my ($name, $value) = split /=/, $param;
  105. push @fields, $name;
  106. push @values, $value;
  107. }
  108. $sql = "INSERT INTO $table (" . join(",", @fields) . ") VALUES (" . join(",", @values) . ")";
  109. $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  110. $affected = $dbh->do($sql);
  111. $dbh->disconnect();
  112. print "Content-type: text/html\n\n$affected\n";
  113. } elsif ($ENV{PATH_INFO} =~ m/destroy$/) {
  114. # All parameters as POST
  115. my ($sql, $affected);
  116. $sql = "DELETE FROM $table WHERE " . join(" AND ", cgi_to_where_clause($cgi, \%cfg));
  117. $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  118. $affected = $dbh->do($sql);
  119. $dbh->disconnect();
  120. print "Content-type: text/html\n\n$affected\n";
  121. } elsif ($ENV{PATH_INFO} =~ m/require$/) {
  122. my $result = 0;
  123. my $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  124. my $sql = "SELECT data_type, character_maximum_length FROM information_schema.tables AS t " .
  125. "JOIN information_schema.columns AS c " .
  126. "ON t.table_catalog=c.table_catalog AND " .
  127. "t.table_schema=c.table_schema AND " .
  128. "t.table_name=c.table_name " .
  129. "WHERE c.table_schema='$cfg{dbschema}' AND " .
  130. "c.table_name=? AND c.column_name=?";
  131. my $sth = $dbh->prepare($sql);
  132. foreach my $param (cgi_to_where_clause($cgi, \%cfg)) {
  133. my ($colname, $value) = split /=/, $param;
  134. my ($type, $size) = split /:/, $value;
  135. $sth->execute($table, $colname);
  136. my ($dbtype, $dblen) = $sth->fetchrow_array();
  137. $sth->finish();
  138. if ($type eq 'char') {
  139. if ($dbtype !~ m#char#i) {
  140. print STDERR "REQUIRE: $table: Type of column $colname requires char($size), but column is of type $dbtype instead!\n";
  141. $result = -1;
  142. } elsif ($dblen < $size) {
  143. print STDERR "REQUIRE: $table: Size of column $colname requires $size, but column is only $dblen long!\n";
  144. $result = -1;
  145. }
  146. } elsif ($type eq 'integer') {
  147. if ($dbtype =~ m#char#i and $dblen < $size) {
  148. print STDERR "REQUIRE: $table: Size of column $colname requires $size, but column is only $dblen long!\n";
  149. $result = -1;
  150. } elsif ($dbtype !~ m#int|float|double|dec|num#i) {
  151. print STDERR "REQUIRE: $table: Type of column $colname requires integer($size), but column is of type $dbtype instead!\n";
  152. $result = -1;
  153. }
  154. } # TODO More type checks
  155. }
  156. $dbh->disconnect();
  157. print "Content-type: text/html\n\n$result\n";
  158. } elsif ($ENV{PATH_INFO} =~ m/static$/) {
  159. # file parameter in GET, no POST
  160. my (@get, $filename, $sql, $sth);
  161. @get = split '&', $ENV{QUERY_STRING};
  162. foreach (@get) {
  163. my ($name, $value) = split '=';
  164. if (decode($name) eq 'file') {
  165. $filename = decode($value);
  166. last;
  167. }
  168. }
  169. $sql = "SELECT cat_metric, category, var_name, var_val FROM $table WHERE filename=" . escape_value($filename) . " AND commented=0 ORDER BY cat_metric DESC, var_metric ASC, category, var_name";
  170. $dbh = DBI->connect($cfg{dsn}, $cfg{dbuser}, $cfg{dbpass});
  171. $sth = $dbh->prepare($sql) || throw_error("Invalid query: $sql");
  172. $sth->execute() || throw_error("Invalid query: $sql");
  173. print "Content-type: text/plain\n\n";
  174. while (my $row = $sth->fetchrow_hashref()) {
  175. my @answer = ();
  176. foreach (keys %$row) {
  177. push @answer, encode($_) . "=" . encode($row->{$_});
  178. }
  179. print join("&", @answer) . "\n";
  180. }
  181. $sth->finish();
  182. $dbh->disconnect();
  183. } else {
  184. print "Content-type: text/plain\n\nUnknown query\n";
  185. }
  186. sub encode {
  187. my ($stuff) = @_;
  188. $stuff =~ s/([^a-zA-Z0-9_\.])/uc sprintf("%%%02x",ord($1))/eg;
  189. return $stuff;
  190. }
  191. sub decode {
  192. my ($stuff) = @_;
  193. $stuff =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  194. return $stuff;
  195. }
  196. sub isname {
  197. my ($name) = @_;
  198. if ($name =~ m#[^A-Za-z0-9_ ]#) {
  199. return 0;
  200. } else {
  201. return 1;
  202. }
  203. }
  204. sub escape_value {
  205. my ($value, $cfg) = @_;
  206. if ($cfg->{backslash_is_escape} =~ m/^(no|0|false)$/i) {
  207. $value =~ s#'#''#g;
  208. } else {
  209. $value =~ s#(['\\])#$1$1#g;
  210. }
  211. return $value;
  212. }
  213. sub cgi_to_where_clause {
  214. my ($cgi, $cfg, $get) = @_;
  215. my @param = ();
  216. foreach my $name ($cgi->param()) {
  217. my $value = escape_value($cgi->param($name), $cfg);
  218. # Ensure name isn't funny-like
  219. next if (!isname($name));
  220. next if ($get->{$name});
  221. if ($name =~ m# #) {
  222. push @param, "$name '$value'";
  223. } else {
  224. push @param, "$name='$value'";
  225. }
  226. }
  227. return @param;
  228. }
  229. sub throw_error {
  230. my ($msg) = @_;
  231. print "Content-type: text/plain\n\n$msg\n";
  232. print STDERR $msg . "\n";
  233. exit;
  234. }