Asterisk - The Open Source Telephony Project GIT-master-7e7a603
cel_tds.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2008, Digium, Inc.
5 *
6 * See http://www.asterisk.org for more information about
7 * the Asterisk project. Please do not directly contact
8 * any of the maintainers of this project for assistance;
9 * the project provides a web site, mailing lists and IRC
10 * channels for your use.
11 *
12 * This program is free software, distributed under the terms of
13 * the GNU General Public License Version 2. See the LICENSE file
14 * at the top of the source tree.
15 */
16
17/*! \file
18 *
19 * \brief FreeTDS CEL logger
20 * http://www.freetds.org/
21 * \ingroup cel_drivers
22 */
23
24/*! \verbatim
25 *
26 * Table Structure for `cel`
27 *
28
29CREATE TABLE [dbo].[cel] (
30 [accountcode] [varchar] (20) NULL ,
31 [cidname] [varchar] (80) NULL ,
32 [cidnum] [varchar] (80) NULL ,
33 [cidani] [varchar] (80) NULL ,
34 [cidrdnis] [varchar] (80) NULL ,
35 [ciddnid] [varchar] (80) NULL ,
36 [exten] [varchar] (80) NULL ,
37 [context] [varchar] (80) NULL ,
38 [channame] [varchar] (80) NULL ,
39 [appname] [varchar] (80) NULL ,
40 [appdata] [varchar] (80) NULL ,
41 [eventtime] [datetime] NULL ,
42 [eventtype] [varchar] (32) NULL ,
43 [uniqueid] [varchar] (32) NULL ,
44 [linkedid] [varchar] (32) NULL ,
45 [amaflags] [varchar] (16) NULL ,
46 [userfield] [varchar] (32) NULL ,
47 [peer] [varchar] (32) NULL
48) ON [PRIMARY]
49
50\endverbatim
51
52*/
53
54/*** MODULEINFO
55 <depend>freetds</depend>
56 <support_level>extended</support_level>
57 ***/
58
59#include "asterisk.h"
60
61#include <time.h>
62#include <math.h>
63
64#include "asterisk/config.h"
65#include "asterisk/channel.h"
66#include "asterisk/cel.h"
67#include "asterisk/module.h"
68#include "asterisk/logger.h"
69
70#include <sqlfront.h>
71#include <sybdb.h>
72
73#ifdef FREETDS_PRE_0_62
74#warning "You have older TDS, you should upgrade!"
75#endif
76
77#define DATE_FORMAT "%Y/%m/%d %T"
78
79#define TDS_BACKEND_NAME "CEL TDS logging backend"
80
81static char *config = "cel_tds.conf";
82
92 );
93 DBPROCESS *dbproc;
94 unsigned int connected:1;
95};
96
98
99static struct cel_tds_config *settings;
100
101static char *anti_injection(const char *, int);
102static void get_date(char *, size_t len, struct timeval);
103
104static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
105 __attribute__((format(printf, 2, 3)));
106
107static int mssql_connect(void);
108static int mssql_disconnect(void);
109
110static void tds_log(struct ast_event *event)
111{
112 char start[80];
113 char *accountcode_ai, *clidnum_ai, *exten_ai, *context_ai, *clid_ai, *channel_ai, *app_ai, *appdata_ai, *uniqueid_ai, *linkedid_ai, *cidani_ai, *cidrdnis_ai, *ciddnid_ai, *peer_ai, *userfield_ai;
114 RETCODE erc;
115 int attempt = 1;
116 struct ast_cel_event_record record = {
118 };
119
120 if (ast_cel_fill_record(event, &record)) {
121 return;
122 }
123
125
126 accountcode_ai = anti_injection(record.account_code, 20);
127 clidnum_ai = anti_injection(record.caller_id_num, 80);
128 clid_ai = anti_injection(record.caller_id_name, 80);
129 cidani_ai = anti_injection(record.caller_id_ani, 80);
130 cidrdnis_ai = anti_injection(record.caller_id_rdnis, 80);
131 ciddnid_ai = anti_injection(record.caller_id_dnid, 80);
132 exten_ai = anti_injection(record.extension, 80);
133 context_ai = anti_injection(record.context, 80);
134 channel_ai = anti_injection(record.channel_name, 80);
135 app_ai = anti_injection(record.application_name, 80);
136 appdata_ai = anti_injection(record.application_data, 80);
137 uniqueid_ai = anti_injection(record.unique_id, 32);
138 linkedid_ai = anti_injection(record.linked_id, 32);
139 userfield_ai = anti_injection(record.user_field, 32);
140 peer_ai = anti_injection(record.peer, 32);
141
142 get_date(start, sizeof(start), record.event_time);
143
144retry:
145 /* Ensure that we are connected */
146 if (!settings->connected) {
147 ast_log(LOG_NOTICE, "Attempting to reconnect to %s (Attempt %d)\n", settings->connection, attempt);
148 if (mssql_connect()) {
149 /* Connect failed */
150 if (attempt++ < 3) {
151 goto retry;
152 }
153 goto done;
154 }
155 }
156
157 erc = dbfcmd(settings->dbproc,
158 "INSERT INTO %s "
159 "("
160 "accountcode,"
161 "cidnum,"
162 "cidname,"
163 "cidani,"
164 "cidrdnis,"
165 "ciddnid,"
166 "exten,"
167 "context,"
168 "channel,"
169 "appname,"
170 "appdata,"
171 "eventtime,"
172 "eventtype,"
173 "amaflags, "
174 "uniqueid,"
175 "linkedid,"
176 "userfield,"
177 "peer"
178 ") "
179 "VALUES "
180 "("
181 "'%s'," /* accountcode */
182 "'%s'," /* clidnum */
183 "'%s'," /* clid */
184 "'%s'," /* cid-ani */
185 "'%s'," /* cid-rdnis */
186 "'%s'," /* cid-dnid */
187 "'%s'," /* exten */
188 "'%s'," /* context */
189 "'%s'," /* channel */
190 "'%s'," /* app */
191 "'%s'," /* appdata */
192 "%s, " /* eventtime */
193 "'%s'," /* eventtype */
194 "'%s'," /* amaflags */
195 "'%s'," /* uniqueid */
196 "'%s'," /* linkedid */
197 "'%s'," /* userfield */
198 "'%s'" /* peer */
199 ")",
200 settings->table, accountcode_ai, clidnum_ai, clid_ai, cidani_ai, cidrdnis_ai,
201 ciddnid_ai, exten_ai, context_ai, channel_ai, app_ai, appdata_ai, start,
203 ? record.user_defined_name : record.event_name,
204 ast_channel_amaflags2string(record.amaflag), uniqueid_ai, linkedid_ai,
205 userfield_ai, peer_ai);
206
207 if (erc == FAIL) {
208 if (attempt++ < 3) {
209 ast_log(LOG_NOTICE, "Failed to build INSERT statement, retrying...\n");
211 goto retry;
212 } else {
213 ast_log(LOG_ERROR, "Failed to build INSERT statement, no CEL was logged.\n");
214 goto done;
215 }
216 }
217
218 if (dbsqlexec(settings->dbproc) == FAIL) {
219 if (attempt++ < 3) {
220 ast_log(LOG_NOTICE, "Failed to execute INSERT statement, retrying...\n");
222 goto retry;
223 } else {
224 ast_log(LOG_ERROR, "Failed to execute INSERT statement, no CEL was logged.\n");
225 goto done;
226 }
227 }
228
229 /* Consume any results we might get back (this is more of a sanity check than
230 * anything else, since an INSERT shouldn't return results). */
231 while (dbresults(settings->dbproc) != NO_MORE_RESULTS) {
232 while (dbnextrow(settings->dbproc) != NO_MORE_ROWS);
233 }
234
235done:
237
238 ast_free(accountcode_ai);
239 ast_free(clidnum_ai);
240 ast_free(clid_ai);
241 ast_free(cidani_ai);
242 ast_free(cidrdnis_ai);
243 ast_free(ciddnid_ai);
244 ast_free(exten_ai);
245 ast_free(context_ai);
246 ast_free(channel_ai);
247 ast_free(app_ai);
248 ast_free(appdata_ai);
249 ast_free(uniqueid_ai);
250 ast_free(linkedid_ai);
251 ast_free(userfield_ai);
252 ast_free(peer_ai);
253
254 return;
255}
256
257static char *anti_injection(const char *str, int len)
258{
259 /* Reference to http://www.nextgenss.com/papers/advanced_sql_injection.pdf */
260 char *buf;
261 char *buf_ptr, *srh_ptr;
262 char *known_bad[] = {"select", "insert", "update", "delete", "drop", ";", "--", "\0"};
263 int idx;
264
265 if (!(buf = ast_calloc(1, len + 1))) {
266 ast_log(LOG_ERROR, "Out of memory\n");
267 return NULL;
268 }
269
270 buf_ptr = buf;
271
272 /* Escape single quotes */
273 for (; *str && strlen(buf) < len; str++) {
274 if (*str == '\'') {
275 *buf_ptr++ = '\'';
276 }
277 *buf_ptr++ = *str;
278 }
279 *buf_ptr = '\0';
280
281 /* Erase known bad input */
282 for (idx = 0; *known_bad[idx]; idx++) {
283 while ((srh_ptr = strcasestr(buf, known_bad[idx]))) {
284 memmove(srh_ptr, srh_ptr + strlen(known_bad[idx]), strlen(srh_ptr + strlen(known_bad[idx])) + 1);
285 }
286 }
287 return buf;
288}
289
290static void get_date(char *dateField, size_t len, struct timeval when)
291{
292 /* To make sure we have date variable if not insert null to SQL */
293 if (!ast_tvzero(when)) {
294 struct ast_tm tm;
295 ast_localtime(&when, &tm, NULL);
296 ast_strftime(dateField, len, "'" DATE_FORMAT "'", &tm);
297 } else {
298 ast_copy_string(dateField, "null", len);
299 }
300}
301
302static int execute_and_consume(DBPROCESS *dbproc, const char *fmt, ...)
303{
304 va_list ap;
305 char *buffer;
306
307 va_start(ap, fmt);
308 if (ast_vasprintf(&buffer, fmt, ap) < 0) {
309 va_end(ap);
310 return 1;
311 }
312 va_end(ap);
313
314 if (dbfcmd(dbproc, buffer) == FAIL) {
315 ast_free(buffer);
316 return 1;
317 }
318
319 ast_free(buffer);
320
321 if (dbsqlexec(dbproc) == FAIL) {
322 return 1;
323 }
324
325 /* Consume the result set (we don't really care about the result, though) */
326 while (dbresults(dbproc) != NO_MORE_RESULTS) {
327 while (dbnextrow(dbproc) != NO_MORE_ROWS);
328 }
329
330 return 0;
331}
332
333static int mssql_disconnect(void)
334{
335 if (settings->dbproc) {
336 dbclose(settings->dbproc);
338 }
339 settings->connected = 0;
340
341 return 0;
342}
343
344static int mssql_connect(void)
345{
346 LOGINREC *login;
347
348 if ((login = dblogin()) == NULL) {
349 ast_log(LOG_ERROR, "Unable to allocate login structure for db-lib\n");
350 return -1;
351 }
352
353 DBSETLAPP(login, "TSQL");
354 DBSETLUSER(login, (char *) settings->username);
355 DBSETLPWD(login, (char *) settings->password);
356
358 DBSETLCHARSET(login, (char *) settings->charset);
359 }
360
362 DBSETLNATLANG(login, (char *) settings->language);
363 }
364
365 if ((settings->dbproc = dbopen(login, (char *) settings->connection)) == NULL) {
366 ast_log(LOG_ERROR, "Unable to connect to %s\n", settings->connection);
367 dbloginfree(login);
368 return -1;
369 }
370
371 dbloginfree(login);
372
373 if (dbuse(settings->dbproc, (char *) settings->database) == FAIL) {
374 ast_log(LOG_ERROR, "Unable to select database %s\n", settings->database);
375 goto failed;
376 }
377
378 if (execute_and_consume(settings->dbproc, "SELECT 1 FROM [%s]", settings->table)) {
379 ast_log(LOG_ERROR, "Unable to find table '%s'\n", settings->table);
380 goto failed;
381 }
382
383 settings->connected = 1;
384
385 return 0;
386
387failed:
388 dbclose(settings->dbproc);
390 return -1;
391}
392
393static int tds_unload_module(void)
394{
396
397 if (settings) {
401
404 }
405
406 dbexit();
407
408 return 0;
409}
410
411static int tds_error_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr)
412{
413 ast_log(LOG_ERROR, "%s (%d)\n", dberrstr, dberr);
414
415 if (oserr != DBNOERR) {
416 ast_log(LOG_ERROR, "%s (%d)\n", oserrstr, oserr);
417 }
418
419 return INT_CANCEL;
420}
421
422static int tds_message_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line)
423{
424 ast_debug(1, "Msg %d, Level %d, State %d, Line %d\n", msgno, severity, msgstate, line);
425 ast_log(LOG_NOTICE, "%s\n", msgtext);
426
427 return 0;
428}
429
430static int tds_load_module(int reload)
431{
432 struct ast_config *cfg;
433 const char *ptr = NULL;
434 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
435
436 cfg = ast_config_load(config, config_flags);
437 if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
438 ast_log(LOG_NOTICE, "Unable to load TDS config for CELs: %s\n", config);
439 return 0;
440 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
441 return 0;
442 }
443
444 if (!ast_variable_browse(cfg, "global")) {
445 /* nothing configured */
447 ast_log(LOG_NOTICE, "cel_tds has no global category, nothing to configure.\n");
448 return 0;
449 }
450
452
453 /* Clear out any existing settings */
455
456 ptr = ast_variable_retrieve(cfg, "global", "connection");
457 if (ptr) {
458 ast_string_field_set(settings, connection, ptr);
459 } else {
460 ast_log(LOG_ERROR, "Failed to connect: Database connection name not specified.\n");
461 goto failed;
462 }
463
464 ptr = ast_variable_retrieve(cfg, "global", "dbname");
465 if (ptr) {
466 ast_string_field_set(settings, database, ptr);
467 } else {
468 ast_log(LOG_ERROR, "Failed to connect: Database dbname not specified.\n");
469 goto failed;
470 }
471
472 ptr = ast_variable_retrieve(cfg, "global", "user");
473 if (ptr) {
474 ast_string_field_set(settings, username, ptr);
475 } else {
476 ast_log(LOG_ERROR, "Failed to connect: Database dbuser not specified.\n");
477 goto failed;
478 }
479
480 ptr = ast_variable_retrieve(cfg, "global", "password");
481 if (ptr) {
482 ast_string_field_set(settings, password, ptr);
483 } else {
484 ast_log(LOG_ERROR, "Failed to connect: Database password not specified.\n");
485 goto failed;
486 }
487
488 ptr = ast_variable_retrieve(cfg, "global", "charset");
489 if (ptr) {
491 }
492
493 ptr = ast_variable_retrieve(cfg, "global", "language");
494 if (ptr) {
496 }
497
498 ptr = ast_variable_retrieve(cfg, "global", "table");
499 if (ptr) {
501 } else {
502 ast_log(LOG_NOTICE, "Table name not specified, using 'cel' by default.\n");
504 }
505
507
508 if (mssql_connect()) {
509 /* We failed to connect (mssql_connect takes care of logging it) */
510 goto failed;
511 }
512
515
516 return 1;
517
518failed:
521
522 return 0;
523}
524
525static int reload(void)
526{
527 return tds_load_module(1);
528}
529
530static int load_module(void)
531{
532 if (dbinit() == FAIL) {
533 ast_log(LOG_ERROR, "Failed to initialize FreeTDS db-lib\n");
535 }
536
537 dberrhandle(tds_error_handler);
538 dbmsghandle(tds_message_handler);
539
541
542 if (!settings) {
543 dbexit();
545 }
546
547 if (!tds_load_module(0)) {
550 settings = NULL;
551 dbexit();
552 ast_log(LOG_WARNING,"cel_tds module had config problems; declining load\n");
554 }
555
556 /* Register MSSQL CEL handler */
558 ast_log(LOG_ERROR, "Unable to register MSSQL CEL handling\n");
561 settings = NULL;
562 dbexit();
564 }
565
567}
568
569static int unload_module(void)
570{
571 return tds_unload_module();
572}
573
575 .support_level = AST_MODULE_SUPPORT_EXTENDED,
576 .load = load_module,
577 .unload = unload_module,
578 .reload = reload,
579 .load_pri = AST_MODPRI_CDR_DRIVER,
580 .requires = "cel",
const char * str
Definition: app_jack.c:147
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_vasprintf(ret, fmt, ap)
A wrapper for vasprintf()
Definition: astmm.h:278
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
static char * table
Definition: cdr_odbc.c:55
Call Event Logging API.
int ast_cel_backend_unregister(const char *name)
Unregister a CEL backend.
Definition: cel.c:1769
@ AST_CEL_USER_DEFINED
a user-defined event, the event name field should be set
Definition: cel.h:69
int ast_cel_fill_record(const struct ast_event *event, struct ast_cel_event_record *r)
Fill in an ast_cel_event_record from a CEL event.
Definition: cel.c:821
#define AST_CEL_EVENT_RECORD_VERSION
struct ABI version
Definition: cel.h:143
int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
Register a CEL backend.
Definition: cel.c:1781
#define TDS_BACKEND_NAME
Definition: cel_tds.c:79
static char * anti_injection(const char *, int)
Definition: cel_tds.c:257
static int mssql_connect(void)
Definition: cel_tds.c:344
static void tds_log(struct ast_event *event)
Definition: cel_tds.c:110
static struct cel_tds_config * settings
Definition: cel_tds.c:99
static void get_date(char *, size_t len, struct timeval)
Definition: cel_tds.c:290
static char * config
Definition: cel_tds.c:81
static int tds_error_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr)
Definition: cel_tds.c:411
static int execute_and_consume(DBPROCESS *dbproc, const char *fmt,...)
Definition: cel_tds.c:302
static int tds_message_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line)
Definition: cel_tds.c:422
static ast_mutex_t tds_lock
Definition: cel_tds.c:97
static int mssql_disconnect(void)
Definition: cel_tds.c:333
static int tds_unload_module(void)
Definition: cel_tds.c:393
static int load_module(void)
Definition: cel_tds.c:530
static int unload_module(void)
Definition: cel_tds.c:569
static int reload(void)
Definition: cel_tds.c:525
static int tds_load_module(int reload)
Definition: cel_tds.c:430
#define DATE_FORMAT
Definition: cel_tds.c:77
static char language[MAX_LANGUAGE]
Definition: chan_iax2.c:324
charset
Definition: chan_unistim.c:336
General Asterisk PBX channel definitions.
const char * ast_channel_amaflags2string(enum ama_flags flags)
Convert the enum representation of an AMA flag to a string representation.
Definition: channel.c:4373
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
char * strcasestr(const char *, const char *)
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
#define CONFIG_STATUS_FILEUNCHANGED
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:783
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
@ CONFIG_FLAG_FILEUNCHANGED
Support for logging to various files, console and syslog Configuration in file logger....
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
#define ast_mutex_unlock(a)
Definition: lock.h:190
#define ast_mutex_lock(a)
Definition: lock.h:189
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:520
enum ast_security_event_severity severity
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:317
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODPRI_CDR_DRIVER
Definition: module.h:331
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
#define NULL
Definition: resample.c:96
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define ast_calloc_with_stringfields(n, type, size)
Allocate a structure with embedded stringfields in a single allocation.
Definition: stringfields.h:432
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
Helper struct for getting the fields out of a CEL event.
Definition: cel.h:138
const char * caller_id_dnid
Definition: cel.h:157
const char * application_data
Definition: cel.h:162
const char * account_code
Definition: cel.h:163
const char * caller_id_rdnis
Definition: cel.h:156
const char * extension
Definition: cel.h:158
const char * caller_id_num
Definition: cel.h:154
const char * channel_name
Definition: cel.h:160
const char * linked_id
Definition: cel.h:166
const char * peer
Definition: cel.h:169
enum ast_cel_event_type event_type
Definition: cel.h:149
const char * unique_id
Definition: cel.h:165
const char * user_defined_name
Definition: cel.h:152
const char * context
Definition: cel.h:159
const char * application_name
Definition: cel.h:161
struct timeval event_time
Definition: cel.h:150
uint32_t version
struct ABI version
Definition: cel.h:148
const char * user_field
Definition: cel.h:168
const char * caller_id_ani
Definition: cel.h:155
const char * caller_id_name
Definition: cel.h:153
const char * event_name
Definition: cel.h:151
An event.
Definition: event.c:81
Structure used to handle boolean flags.
Definition: utils.h:199
DBPROCESS * dbproc
Definition: cel_tds.c:93
const ast_string_field charset
Definition: cel_tds.c:92
const ast_string_field language
Definition: cel_tds.c:92
const ast_string_field database
Definition: cel_tds.c:92
const ast_string_field password
Definition: cel_tds.c:92
const ast_string_field username
Definition: cel_tds.c:92
const ast_string_field table
Definition: cel_tds.c:92
unsigned int connected
Definition: cel_tds.c:94
const ast_string_field connection
Definition: cel_tds.c:92
Definition: astman.c:222
int done
Definition: test_amihooks.c:48
Time-related functions and macros.
int ast_tvzero(const struct timeval t)
Returns true if the argument is 0,0.
Definition: time.h:117
DB * dbopen(char *fname, int flags, int mode, DBTYPE type, const void *openinfo) const