app_sql_postgres.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2002, Christos Ricudis
  5. *
  6. * Christos Ricudis <ricudis@itc.auth.gr>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*! \file
  19. *
  20. * \brief Connect to PostgreSQL
  21. *
  22. * \ingroup applications
  23. */
  24. #include <stdlib.h>
  25. #include <unistd.h>
  26. #include <string.h>
  27. #include <sys/types.h>
  28. #include <stdio.h>
  29. #include <unistd.h>
  30. #include "asterisk.h"
  31. ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  32. #include "asterisk/file.h"
  33. #include "asterisk/logger.h"
  34. #include "asterisk/channel.h"
  35. #include "asterisk/pbx.h"
  36. #include "asterisk/module.h"
  37. #include "asterisk/linkedlists.h"
  38. #include "asterisk/chanvars.h"
  39. #include "asterisk/lock.h"
  40. #include "libpq-fe.h"
  41. static char *tdesc = "Simple PostgreSQL Interface";
  42. static char *app = "PGSQL";
  43. static char *synopsis = "Do several SQLy things";
  44. static char *descrip =
  45. "PGSQL(): Do several SQLy things\n"
  46. "Syntax:\n"
  47. " PGSQL(Connect var option-string)\n"
  48. " Connects to a database. Option string contains standard PostgreSQL\n"
  49. " parameters like host=, dbname=, user=. Connection identifer returned\n"
  50. " in ${var}\n"
  51. " PGSQL(Query var ${connection_identifier} query-string)\n"
  52. " Executes standard SQL query contained in query-string using established\n"
  53. " connection identified by ${connection_identifier}. Reseult of query is\n"
  54. " is stored in ${var}.\n"
  55. " PGSQL(Fetch statusvar ${result_identifier} var1 var2 ... varn)\n"
  56. " Fetches a single row from a result set contained in ${result_identifier}.\n"
  57. " Assigns returned fields to ${var1} ... ${varn}. ${statusvar} is set TRUE\n"
  58. " if additional rows exist in reseult set.\n"
  59. " PGSQL(Clear ${result_identifier})\n"
  60. " Frees memory and datastructures associated with result set.\n"
  61. " PGSQL(Disconnect ${connection_identifier})\n"
  62. " Disconnects from named connection to PostgreSQL.\n" ;
  63. /*
  64. Syntax of SQL commands :
  65. Connect var option-string
  66. Connects to a database using the option-string and stores the
  67. connection identifier in ${var}
  68. Query var ${connection_identifier} query-string
  69. Submits query-string to database backend and stores the result
  70. identifier in ${var}
  71. Fetch statusvar ${result_identifier} var1 var2 var3 ... varn
  72. Fetches a row from the query and stores end-of-table status in
  73. ${statusvar} and columns in ${var1}..${varn}
  74. Clear ${result_identifier}
  75. Clears data structures associated with ${result_identifier}
  76. Disconnect ${connection_identifier}
  77. Disconnects from named connection
  78. EXAMPLES OF USE :
  79. exten => s,2,PGSQL(Connect connid host=localhost user=asterisk dbname=credit)
  80. exten => s,3,PGSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
  81. exten => s,4,PGSQL(Fetch fetchid ${resultid} datavar1 datavar2)
  82. exten => s,5,GotoIf(${fetchid}?6:8)
  83. exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
  84. exten => s,7,Goto(s,4)
  85. exten => s,8,PGSQL(Clear ${resultid})
  86. exten => s,9,PGSQL(Disconnect ${connid})
  87. */
  88. STANDARD_LOCAL_USER;
  89. LOCAL_USER_DECL;
  90. #define AST_PGSQL_ID_DUMMY 0
  91. #define AST_PGSQL_ID_CONNID 1
  92. #define AST_PGSQL_ID_RESID 2
  93. #define AST_PGSQL_ID_FETCHID 3
  94. struct ast_PGSQL_id {
  95. int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
  96. int identifier;
  97. void *data;
  98. AST_LIST_ENTRY(ast_PGSQL_id) entries;
  99. } *ast_PGSQL_id;
  100. AST_LIST_HEAD(PGSQLidshead,ast_PGSQL_id) PGSQLidshead;
  101. static void *find_identifier(int identifier,int identifier_type) {
  102. struct PGSQLidshead *headp;
  103. struct ast_PGSQL_id *i;
  104. void *res=NULL;
  105. int found=0;
  106. headp=&PGSQLidshead;
  107. if (AST_LIST_LOCK(headp)) {
  108. ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
  109. } else {
  110. AST_LIST_TRAVERSE(headp,i,entries) {
  111. if ((i->identifier==identifier) && (i->identifier_type==identifier_type)) {
  112. found=1;
  113. res=i->data;
  114. break;
  115. }
  116. }
  117. if (!found) {
  118. ast_log(LOG_WARNING,"Identifier %d, identifier_type %d not found in identifier list\n",identifier,identifier_type);
  119. }
  120. AST_LIST_UNLOCK(headp);
  121. }
  122. return(res);
  123. }
  124. static int add_identifier(int identifier_type,void *data) {
  125. struct ast_PGSQL_id *i,*j;
  126. struct PGSQLidshead *headp;
  127. int maxidentifier=0;
  128. headp=&PGSQLidshead;
  129. i=NULL;
  130. j=NULL;
  131. if (AST_LIST_LOCK(headp)) {
  132. ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
  133. return(-1);
  134. } else {
  135. i=malloc(sizeof(struct ast_PGSQL_id));
  136. AST_LIST_TRAVERSE(headp,j,entries) {
  137. if (j->identifier>maxidentifier) {
  138. maxidentifier=j->identifier;
  139. }
  140. }
  141. i->identifier=maxidentifier+1;
  142. i->identifier_type=identifier_type;
  143. i->data=data;
  144. AST_LIST_INSERT_HEAD(headp,i,entries);
  145. AST_LIST_UNLOCK(headp);
  146. }
  147. return(i->identifier);
  148. }
  149. static int del_identifier(int identifier,int identifier_type) {
  150. struct ast_PGSQL_id *i;
  151. struct PGSQLidshead *headp;
  152. int found=0;
  153. headp=&PGSQLidshead;
  154. if (AST_LIST_LOCK(headp)) {
  155. ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
  156. } else {
  157. AST_LIST_TRAVERSE(headp,i,entries) {
  158. if ((i->identifier==identifier) &&
  159. (i->identifier_type==identifier_type)) {
  160. AST_LIST_REMOVE(headp,i,entries);
  161. free(i);
  162. found=1;
  163. break;
  164. }
  165. }
  166. AST_LIST_UNLOCK(headp);
  167. }
  168. if (found==0) {
  169. ast_log(LOG_WARNING,"Could not find identifier %d, identifier_type %d in list to delete\n",identifier,identifier_type);
  170. return(-1);
  171. } else {
  172. return(0);
  173. }
  174. }
  175. static int aPGSQL_connect(struct ast_channel *chan, void *data) {
  176. char *s1;
  177. char s[100] = "";
  178. char *optionstring;
  179. char *var;
  180. int l;
  181. int res;
  182. PGconn *karoto;
  183. int id;
  184. char *stringp=NULL;
  185. res=0;
  186. l=strlen(data)+2;
  187. s1=malloc(l);
  188. strncpy(s1, data, l -1);
  189. stringp=s1;
  190. strsep(&stringp," "); /* eat the first token, we already know it :P */
  191. var=strsep(&stringp," ");
  192. optionstring=strsep(&stringp,"\n");
  193. karoto = PQconnectdb(optionstring);
  194. if (PQstatus(karoto) == CONNECTION_BAD) {
  195. ast_log(LOG_WARNING,"Connection to database using '%s' failed. postgress reports : %s\n", optionstring,
  196. PQerrorMessage(karoto));
  197. res=-1;
  198. } else {
  199. ast_log(LOG_WARNING,"adding identifier\n");
  200. id=add_identifier(AST_PGSQL_ID_CONNID,karoto);
  201. snprintf(s, sizeof(s), "%d", id);
  202. pbx_builtin_setvar_helper(chan,var,s);
  203. }
  204. free(s1);
  205. return res;
  206. }
  207. static int aPGSQL_query(struct ast_channel *chan, void *data) {
  208. char *s1,*s2,*s3,*s4;
  209. char s[100] = "";
  210. char *querystring;
  211. char *var;
  212. int l;
  213. int res,nres;
  214. PGconn *karoto;
  215. PGresult *PGSQLres;
  216. int id,id1;
  217. char *stringp=NULL;
  218. res=0;
  219. l=strlen(data)+2;
  220. s1=malloc(l);
  221. s2=malloc(l);
  222. strncpy(s1, data, l - 1);
  223. stringp=s1;
  224. strsep(&stringp," "); /* eat the first token, we already know it :P */
  225. s3=strsep(&stringp," ");
  226. while (1) { /* ugly trick to make branches with break; */
  227. var=s3;
  228. s4=strsep(&stringp," ");
  229. id=atoi(s4);
  230. querystring=strsep(&stringp,"\n");
  231. if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
  232. ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_query\n",id);
  233. res=-1;
  234. break;
  235. }
  236. PGSQLres=PQexec(karoto,querystring);
  237. if (PGSQLres==NULL) {
  238. ast_log(LOG_WARNING,"aPGSQL_query: Connection Error (connection identifier = %d, error message : %s)\n",id,PQerrorMessage(karoto));
  239. res=-1;
  240. break;
  241. }
  242. if (PQresultStatus(PGSQLres) == PGRES_BAD_RESPONSE ||
  243. PQresultStatus(PGSQLres) == PGRES_NONFATAL_ERROR ||
  244. PQresultStatus(PGSQLres) == PGRES_FATAL_ERROR) {
  245. ast_log(LOG_WARNING,"aPGSQL_query: Query Error (connection identifier : %d, error message : %s)\n",id,PQcmdStatus(PGSQLres));
  246. res=-1;
  247. break;
  248. }
  249. nres=PQnfields(PGSQLres);
  250. id1=add_identifier(AST_PGSQL_ID_RESID,PGSQLres);
  251. snprintf(s, sizeof(s), "%d", id1);
  252. pbx_builtin_setvar_helper(chan,var,s);
  253. break;
  254. }
  255. free(s1);
  256. free(s2);
  257. return(res);
  258. }
  259. static int aPGSQL_fetch(struct ast_channel *chan, void *data) {
  260. char *s1,*s2,*fetchid_var,*s4,*s5,*s6,*s7;
  261. char s[100];
  262. char *var;
  263. int l;
  264. int res;
  265. PGresult *PGSQLres;
  266. int id,id1,i,j,fnd;
  267. int *lalares=NULL;
  268. int nres;
  269. struct ast_var_t *variables;
  270. struct varshead *headp;
  271. char *stringp=NULL;
  272. headp=&chan->varshead;
  273. res=0;
  274. l=strlen(data)+2;
  275. s7=NULL;
  276. s1=malloc(l);
  277. s2=malloc(l);
  278. strncpy(s1, data, l - 1);
  279. stringp=s1;
  280. strsep(&stringp," "); /* eat the first token, we already know it :P */
  281. fetchid_var=strsep(&stringp," ");
  282. while (1) { /* ugly trick to make branches with break; */
  283. var=fetchid_var; /* fetchid */
  284. fnd=0;
  285. AST_LIST_TRAVERSE(headp,variables,entries) {
  286. if (strncasecmp(ast_var_name(variables),fetchid_var,strlen(fetchid_var))==0) {
  287. s7=ast_var_value(variables);
  288. fnd=1;
  289. break;
  290. }
  291. }
  292. if (fnd==0) {
  293. s7="0";
  294. pbx_builtin_setvar_helper(chan,fetchid_var,s7);
  295. }
  296. s4=strsep(&stringp," ");
  297. id=atoi(s4); /* resultid */
  298. if ((PGSQLres=find_identifier(id,AST_PGSQL_ID_RESID))==NULL) {
  299. ast_log(LOG_WARNING,"Invalid result identifier %d passed in aPGSQL_fetch\n",id);
  300. res=-1;
  301. break;
  302. }
  303. id=atoi(s7); /*fetchid */
  304. if ((lalares=find_identifier(id,AST_PGSQL_ID_FETCHID))==NULL) {
  305. i=0; /* fetching the very first row */
  306. } else {
  307. i=*lalares;
  308. free(lalares);
  309. del_identifier(id,AST_PGSQL_ID_FETCHID); /* will re-add it a bit later */
  310. }
  311. if (i<PQntuples(PGSQLres)) {
  312. nres=PQnfields(PGSQLres);
  313. ast_log(LOG_WARNING,"ast_PGSQL_fetch : nres = %d i = %d ;\n",nres,i);
  314. for (j=0;j<nres;j++) {
  315. s5=strsep(&stringp," ");
  316. if (s5==NULL) {
  317. ast_log(LOG_WARNING,"ast_PGSQL_fetch : More tuples (%d) than variables (%d)\n",nres,j);
  318. break;
  319. }
  320. s6=PQgetvalue(PGSQLres,i,j);
  321. if (s6==NULL) {
  322. ast_log(LOG_WARNING,"PWgetvalue(res,%d,%d) returned NULL in ast_PGSQL_fetch\n",i,j);
  323. break;
  324. }
  325. ast_log(LOG_WARNING,"===setting variable '%s' to '%s'\n",s5,s6);
  326. pbx_builtin_setvar_helper(chan,s5,s6);
  327. }
  328. lalares=malloc(sizeof(int));
  329. *lalares = ++i; /* advance to the next row */
  330. id1 = add_identifier(AST_PGSQL_ID_FETCHID,lalares);
  331. } else {
  332. ast_log(LOG_WARNING,"ast_PGSQL_fetch : EOF\n");
  333. id1 = 0; /* no more rows */
  334. }
  335. snprintf(s, sizeof(s), "%d", id1);
  336. ast_log(LOG_WARNING,"Setting var '%s' to value '%s'\n",fetchid_var,s);
  337. pbx_builtin_setvar_helper(chan,fetchid_var,s);
  338. break;
  339. }
  340. free(s1);
  341. free(s2);
  342. return(res);
  343. }
  344. static int aPGSQL_reset(struct ast_channel *chan, void *data) {
  345. char *s1,*s3;
  346. int l;
  347. PGconn *karoto;
  348. int id;
  349. char *stringp=NULL;
  350. l=strlen(data)+2;
  351. s1=malloc(l);
  352. strncpy(s1, data, l - 1);
  353. stringp=s1;
  354. strsep(&stringp," "); /* eat the first token, we already know it :P */
  355. s3=strsep(&stringp," ");
  356. id=atoi(s3);
  357. if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
  358. ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_reset\n",id);
  359. } else {
  360. PQreset(karoto);
  361. }
  362. free(s1);
  363. return(0);
  364. }
  365. static int aPGSQL_clear(struct ast_channel *chan, void *data) {
  366. char *s1,*s3;
  367. int l;
  368. PGresult *karoto;
  369. int id;
  370. char *stringp=NULL;
  371. l=strlen(data)+2;
  372. s1=malloc(l);
  373. strncpy(s1, data, l - 1);
  374. stringp=s1;
  375. strsep(&stringp," "); /* eat the first token, we already know it :P */
  376. s3=strsep(&stringp," ");
  377. id=atoi(s3);
  378. if ((karoto=find_identifier(id,AST_PGSQL_ID_RESID))==NULL) {
  379. ast_log(LOG_WARNING,"Invalid result identifier %d passed in aPGSQL_clear\n",id);
  380. } else {
  381. PQclear(karoto);
  382. del_identifier(id,AST_PGSQL_ID_RESID);
  383. }
  384. free(s1);
  385. return(0);
  386. }
  387. static int aPGSQL_disconnect(struct ast_channel *chan, void *data) {
  388. char *s1,*s3;
  389. int l;
  390. PGconn *karoto;
  391. int id;
  392. char *stringp=NULL;
  393. l=strlen(data)+2;
  394. s1=malloc(l);
  395. strncpy(s1, data, l - 1);
  396. stringp=s1;
  397. strsep(&stringp," "); /* eat the first token, we already know it :P */
  398. s3=strsep(&stringp," ");
  399. id=atoi(s3);
  400. if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
  401. ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_disconnect\n",id);
  402. } else {
  403. PQfinish(karoto);
  404. del_identifier(id,AST_PGSQL_ID_CONNID);
  405. }
  406. free(s1);
  407. return(0);
  408. }
  409. static int aPGSQL_debug(struct ast_channel *chan, void *data) {
  410. ast_log(LOG_WARNING,"Debug : %s\n",(char *)data);
  411. return(0);
  412. }
  413. static int PGSQL_exec(struct ast_channel *chan, void *data)
  414. {
  415. struct localuser *u;
  416. int result;
  417. if (ast_strlen_zero(data)) {
  418. ast_log(LOG_WARNING, "APP_PGSQL requires an argument (see manual)\n");
  419. return -1;
  420. }
  421. LOCAL_USER_ADD(u);
  422. result=0;
  423. if (strncasecmp("connect",data,strlen("connect"))==0) {
  424. result=(aPGSQL_connect(chan,data));
  425. } else if (strncasecmp("query",data,strlen("query"))==0) {
  426. result=(aPGSQL_query(chan,data));
  427. } else if (strncasecmp("fetch",data,strlen("fetch"))==0) {
  428. result=(aPGSQL_fetch(chan,data));
  429. } else if (strncasecmp("reset",data,strlen("reset"))==0) {
  430. result=(aPGSQL_reset(chan,data));
  431. } else if (strncasecmp("clear",data,strlen("clear"))==0) {
  432. result=(aPGSQL_clear(chan,data));
  433. } else if (strncasecmp("debug",data,strlen("debug"))==0) {
  434. result=(aPGSQL_debug(chan,data));
  435. } else if (strncasecmp("disconnect",data,strlen("disconnect"))==0) {
  436. result=(aPGSQL_disconnect(chan,data));
  437. } else {
  438. ast_log(LOG_WARNING, "Unknown APP_PGSQL argument : %s\n",(char *)data);
  439. result=-1;
  440. }
  441. LOCAL_USER_REMOVE(u);
  442. return result;
  443. }
  444. int unload_module(void)
  445. {
  446. int res;
  447. res = ast_unregister_application(app);
  448. STANDARD_HANGUP_LOCALUSERS;
  449. return res;
  450. }
  451. int load_module(void)
  452. {
  453. struct PGSQLidshead *headp;
  454. headp=&PGSQLidshead;
  455. AST_LIST_HEAD_INIT(headp);
  456. return ast_register_application(app, PGSQL_exec, synopsis, descrip);
  457. }
  458. char *description(void)
  459. {
  460. return tdesc;
  461. }
  462. int usecount(void)
  463. {
  464. int res;
  465. STANDARD_USECOUNT(res);
  466. return res;
  467. }
  468. char *key()
  469. {
  470. return ASTERISK_GPL_KEY;
  471. }