00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039 #include "asterisk.h"
00040
00041 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 320393 $")
00042
00043 #include <sys/types.h>
00044 #include <stdio.h>
00045 #include <string.h>
00046 #include <stdlib.h>
00047 #include <unistd.h>
00048 #include <time.h>
00049
00050 #include <libpq-fe.h>
00051
00052 #include "asterisk/config.h"
00053 #include "asterisk/options.h"
00054 #include "asterisk/channel.h"
00055 #include "asterisk/cdr.h"
00056 #include "asterisk/module.h"
00057 #include "asterisk/logger.h"
00058 #include "asterisk.h"
00059
00060 #define DATE_FORMAT "%Y-%m-%d %T"
00061
00062 static char *name = "pgsql";
00063 static char *config = "cdr_pgsql.conf";
00064 static char *pghostname = NULL, *pgdbname = NULL, *pgdbuser = NULL, *pgpassword = NULL, *pgdbport = NULL, *table = NULL, *encoding = NULL;
00065 static int connected = 0;
00066
00067 AST_MUTEX_DEFINE_STATIC(pgsql_lock);
00068
00069 static PGconn *conn = NULL;
00070
00071 static int pgsql_log(struct ast_cdr *cdr)
00072 {
00073 struct tm tm;
00074 time_t t = cdr->start.tv_sec;
00075 char sqlcmd[2048] = "", timestr[128];
00076 char *pgerror;
00077 PGresult *result;
00078
00079 ast_mutex_lock(&pgsql_lock);
00080
00081 ast_localtime(&t, &tm, NULL);
00082 strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
00083
00084 if ((!connected) && pghostname && pgdbuser && pgpassword && pgdbname) {
00085 conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
00086 if (PQstatus(conn) != CONNECTION_BAD) {
00087 connected = 1;
00088 if (PQsetClientEncoding(conn, encoding)) {
00089 #ifdef HAVE_PGSQL_pg_encoding_to_char
00090 ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
00091 #else
00092 ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
00093 #endif
00094 }
00095 } else {
00096 pgerror = PQerrorMessage(conn);
00097 ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. Calls will not be logged!\n", pghostname);
00098 ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
00099 PQfinish(conn);
00100 conn = NULL;
00101 }
00102 }
00103
00104 if (connected) {
00105 char *clid=NULL, *dcontext=NULL, *channel=NULL, *dstchannel=NULL, *lastapp=NULL, *lastdata=NULL;
00106 char *src=NULL, *dst=NULL, *uniqueid=NULL, *userfield=NULL;
00107 int pgerr;
00108
00109
00110 if ((clid = alloca(strlen(cdr->clid) * 2 + 1)) != NULL)
00111 PQescapeStringConn(conn, clid, cdr->clid, strlen(cdr->clid), &pgerr);
00112 if ((dcontext = alloca(strlen(cdr->dcontext) * 2 + 1)) != NULL)
00113 PQescapeStringConn(conn, dcontext, cdr->dcontext, strlen(cdr->dcontext), &pgerr);
00114 if ((channel = alloca(strlen(cdr->channel) * 2 + 1)) != NULL)
00115 PQescapeStringConn(conn, channel, cdr->channel, strlen(cdr->channel), &pgerr);
00116 if ((dstchannel = alloca(strlen(cdr->dstchannel) * 2 + 1)) != NULL)
00117 PQescapeStringConn(conn, dstchannel, cdr->dstchannel, strlen(cdr->dstchannel), &pgerr);
00118 if ((lastapp = alloca(strlen(cdr->lastapp) * 2 + 1)) != NULL)
00119 PQescapeStringConn(conn, lastapp, cdr->lastapp, strlen(cdr->lastapp), &pgerr);
00120 if ((lastdata = alloca(strlen(cdr->lastdata) * 2 + 1)) != NULL)
00121 PQescapeStringConn(conn, lastdata, cdr->lastdata, strlen(cdr->lastdata), &pgerr);
00122 if ((uniqueid = alloca(strlen(cdr->uniqueid) * 2 + 1)) != NULL)
00123 PQescapeStringConn(conn, uniqueid, cdr->uniqueid, strlen(cdr->uniqueid), &pgerr);
00124 if ((userfield = alloca(strlen(cdr->userfield) * 2 + 1)) != NULL)
00125 PQescapeStringConn(conn, userfield, cdr->userfield, strlen(cdr->userfield), &pgerr);
00126 if ((src = alloca(strlen(cdr->src) * 2 + 1)) != NULL)
00127 PQescapeStringConn(conn, src, cdr->src, strlen(cdr->src), &pgerr);
00128 if ((dst = alloca(strlen(cdr->dst) * 2 + 1)) != NULL)
00129 PQescapeStringConn(conn, dst, cdr->dst, strlen(cdr->dst), &pgerr);
00130
00131
00132 if ((!clid) || (!dcontext) || (!channel) || (!dstchannel) || (!lastapp) || (!lastdata) || (!uniqueid) || (!userfield) || (!src) || (!dst)) {
00133 ast_log(LOG_ERROR, "cdr_pgsql: Out of memory error (insert fails)\n");
00134 ast_mutex_unlock(&pgsql_lock);
00135 return -1;
00136 }
00137
00138 if (option_debug > 1)
00139 ast_log(LOG_DEBUG, "cdr_pgsql: inserting a CDR record.\n");
00140
00141 snprintf(sqlcmd,sizeof(sqlcmd),"INSERT INTO %s (calldate,clid,src,dst,dcontext,channel,dstchannel,"
00142 "lastapp,lastdata,duration,billsec,disposition,amaflags,accountcode,uniqueid,userfield) VALUES"
00143 " ('%s','%s','%s','%s','%s', '%s','%s','%s','%s',%ld,%ld,'%s',%ld,'%s','%s','%s')",
00144 table, timestr, clid, src, dst, dcontext, channel, dstchannel, lastapp, lastdata,
00145 cdr->duration,cdr->billsec,ast_cdr_disp2str(cdr->disposition),cdr->amaflags, cdr->accountcode, uniqueid, userfield);
00146
00147 if (option_debug > 2)
00148 ast_log(LOG_DEBUG, "cdr_pgsql: SQL command executed: %s\n",sqlcmd);
00149
00150
00151
00152
00153 if (PQstatus(conn) == CONNECTION_OK) {
00154 connected = 1;
00155 } else {
00156 ast_log(LOG_ERROR, "cdr_pgsql: Connection was lost... attempting to reconnect.\n");
00157 PQreset(conn);
00158 if (PQstatus(conn) == CONNECTION_OK) {
00159 ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
00160 connected = 1;
00161 } else {
00162 pgerror = PQerrorMessage(conn);
00163 ast_log(LOG_ERROR, "cdr_pgsql: Unable to reconnect to database server %s. Calls will not be logged!\n", pghostname);
00164 ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
00165 PQfinish(conn);
00166 conn = NULL;
00167 connected = 0;
00168 ast_mutex_unlock(&pgsql_lock);
00169 return -1;
00170 }
00171 }
00172 result = PQexec(conn, sqlcmd);
00173 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
00174 pgerror = PQresultErrorMessage(result);
00175 ast_log(LOG_ERROR,"cdr_pgsql: Failed to insert call detail record into database!\n");
00176 ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
00177 ast_log(LOG_ERROR,"cdr_pgsql: Connection may have been lost... attempting to reconnect.\n");
00178 PQreset(conn);
00179 if (PQstatus(conn) == CONNECTION_OK) {
00180 ast_log(LOG_ERROR, "cdr_pgsql: Connection reestablished.\n");
00181 connected = 1;
00182 PQclear(result);
00183 result = PQexec(conn, sqlcmd);
00184 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
00185 pgerror = PQresultErrorMessage(result);
00186 ast_log(LOG_ERROR,"cdr_pgsql: HARD ERROR! Attempted reconnection failed. DROPPING CALL RECORD!\n");
00187 ast_log(LOG_ERROR,"cdr_pgsql: Reason: %s\n", pgerror);
00188 }
00189 }
00190 ast_mutex_unlock(&pgsql_lock);
00191 PQclear(result);
00192 return -1;
00193 }
00194 PQclear(result);
00195 }
00196 ast_mutex_unlock(&pgsql_lock);
00197 return 0;
00198 }
00199
00200 static int my_unload_module(void)
00201 {
00202 PQfinish(conn);
00203 if (pghostname)
00204 free(pghostname);
00205 if (pgdbname)
00206 free(pgdbname);
00207 if (pgdbuser)
00208 free(pgdbuser);
00209 if (pgpassword)
00210 free(pgpassword);
00211 if (pgdbport)
00212 free(pgdbport);
00213 if (table)
00214 free(table);
00215 if (encoding) {
00216 free(encoding);
00217 }
00218 ast_cdr_unregister(name);
00219 return 0;
00220 }
00221
00222 static int process_my_load_module(struct ast_config *cfg)
00223 {
00224 struct ast_variable *var;
00225 char *pgerror;
00226 const char *tmp;
00227
00228 if (!(var = ast_variable_browse(cfg, "global")))
00229 return 0;
00230
00231 if (!(tmp = ast_variable_retrieve(cfg,"global","hostname"))) {
00232 ast_log(LOG_WARNING,"PostgreSQL server hostname not specified. Assuming unix socket connection\n");
00233 tmp = "";
00234 }
00235
00236 if (!(pghostname = ast_strdup(tmp)))
00237 return -1;
00238
00239 if (!(tmp = ast_variable_retrieve(cfg, "global", "dbname"))) {
00240 ast_log(LOG_WARNING,"PostgreSQL database not specified. Assuming asterisk\n");
00241 tmp = "asteriskcdrdb";
00242 }
00243
00244 if (!(pgdbname = ast_strdup(tmp)))
00245 return -1;
00246
00247 if (!(tmp = ast_variable_retrieve(cfg, "global", "user"))) {
00248 ast_log(LOG_WARNING,"PostgreSQL database user not specified. Assuming asterisk\n");
00249 tmp = "asterisk";
00250 }
00251
00252 if (!(pgdbuser = ast_strdup(tmp)))
00253 return -1;
00254
00255 if (!(tmp = ast_variable_retrieve(cfg, "global", "password"))) {
00256 ast_log(LOG_WARNING,"PostgreSQL database password not specified. Assuming blank\n");
00257 tmp = "";
00258 }
00259
00260 if (!(pgpassword = ast_strdup(tmp)))
00261 return -1;
00262
00263 if (!(tmp = ast_variable_retrieve(cfg,"global","port"))) {
00264 ast_log(LOG_WARNING,"PostgreSQL database port not specified. Using default 5432.\n");
00265 tmp = "5432";
00266 }
00267
00268 if (!(pgdbport = ast_strdup(tmp)))
00269 return -1;
00270
00271 if (!(tmp = ast_variable_retrieve(cfg, "global", "table"))) {
00272 ast_log(LOG_WARNING,"CDR table not specified. Assuming cdr\n");
00273 tmp = "cdr";
00274 }
00275
00276 if (!(table = ast_strdup(tmp)))
00277 return -1;
00278
00279 if (!(tmp = ast_variable_retrieve(cfg, "global", "encoding"))) {
00280 tmp = "LATIN9";
00281 }
00282
00283 if (!(encoding = ast_strdup(tmp))) {
00284 return -1;
00285 }
00286
00287 if (option_debug) {
00288 if (ast_strlen_zero(pghostname))
00289 ast_log(LOG_DEBUG, "cdr_pgsql: using default unix socket\n");
00290 else
00291 ast_log(LOG_DEBUG, "cdr_pgsql: got hostname of %s\n", pghostname);
00292 ast_log(LOG_DEBUG, "cdr_pgsql: got port of %s\n", pgdbport);
00293 ast_log(LOG_DEBUG, "cdr_pgsql: got user of %s\n", pgdbuser);
00294 ast_log(LOG_DEBUG, "cdr_pgsql: got dbname of %s\n", pgdbname);
00295 ast_log(LOG_DEBUG, "cdr_pgsql: got password of %s\n", pgpassword);
00296 ast_log(LOG_DEBUG, "cdr_pgsql: got sql table name of %s\n", table);
00297 }
00298
00299 conn = PQsetdbLogin(pghostname, pgdbport, NULL, NULL, pgdbname, pgdbuser, pgpassword);
00300 if (PQstatus(conn) != CONNECTION_BAD) {
00301 if (option_debug)
00302 ast_log(LOG_DEBUG, "Successfully connected to PostgreSQL database.\n");
00303 connected = 1;
00304 if (PQsetClientEncoding(conn, encoding)) {
00305 #ifdef HAVE_PGSQL_pg_encoding_to_char
00306 ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default '%s'\n", encoding, pg_encoding_to_char(PQclientEncoding(conn)));
00307 #else
00308 ast_log(LOG_WARNING, "Failed to set encoding to '%s'. Encoding set to default.\n", encoding);
00309 #endif
00310 }
00311 } else {
00312 pgerror = PQerrorMessage(conn);
00313 ast_log(LOG_ERROR, "cdr_pgsql: Unable to connect to database server %s. CALLS WILL NOT BE LOGGED!!\n", pghostname);
00314 ast_log(LOG_ERROR, "cdr_pgsql: Reason: %s\n", pgerror);
00315 connected = 0;
00316 }
00317
00318 return ast_cdr_register(name, ast_module_info->description, pgsql_log);
00319 }
00320
00321 static int my_load_module(void)
00322 {
00323 struct ast_config *cfg;
00324 int res;
00325
00326 if (!(cfg = ast_config_load(config))) {
00327 ast_log(LOG_WARNING, "Unable to load config for PostgreSQL CDR's: %s\n", config);
00328 return AST_MODULE_LOAD_DECLINE;
00329 }
00330
00331 res = process_my_load_module(cfg);
00332 ast_config_destroy(cfg);
00333
00334 return res;
00335 }
00336
00337 static int load_module(void)
00338 {
00339 return my_load_module();
00340 }
00341
00342 static int unload_module(void)
00343 {
00344 return my_unload_module();
00345 }
00346
00347 static int reload(void)
00348 {
00349 int res;
00350 ast_mutex_lock(&pgsql_lock);
00351 my_unload_module();
00352 res = my_load_module();
00353 ast_mutex_unlock(&pgsql_lock);
00354 return res;
00355 }
00356
00357 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "PostgreSQL CDR Backend",
00358 .load = load_module,
00359 .unload = unload_module,
00360 .reload = reload,
00361 );