/* * Copyright (C) 2013-15 Robert Boisvert * * This file is part of the mohqueue module for Kamailio, a free SIP server. * * The mohqueue module is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * The mohqueue module is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "mohq.h" #include "mohq_db.h" #include "mohq_funcs.h" /********** * mohqueue definitions **********/ str MOHQCSTR_ID = STR_STATIC_INIT ("id"); str MOHQCSTR_URI = STR_STATIC_INIT ("uri"); str MOHQCSTR_MDIR = STR_STATIC_INIT ("mohdir"); str MOHQCSTR_MFILE = STR_STATIC_INIT ("mohfile"); str MOHQCSTR_NAME = STR_STATIC_INIT ("name"); str MOHQCSTR_DEBUG = STR_STATIC_INIT ("debug"); static str *mohq_columns [] = { &MOHQCSTR_ID, &MOHQCSTR_URI, &MOHQCSTR_MDIR, &MOHQCSTR_MFILE, &MOHQCSTR_NAME, &MOHQCSTR_DEBUG, NULL }; /********** * mohqcalls definitions **********/ str CALLCSTR_CALL = STR_STATIC_INIT ("call_id"); str CALLCSTR_CNTCT = STR_STATIC_INIT ("call_contact"); str CALLCSTR_FROM = STR_STATIC_INIT ("call_from"); str CALLCSTR_MOHQ = STR_STATIC_INIT ("mohq_id"); str CALLCSTR_STATE = STR_STATIC_INIT ("call_status"); str CALLCSTR_TIME = STR_STATIC_INIT ("call_time"); static str *call_columns [] = { &CALLCSTR_STATE, &CALLCSTR_CALL, &CALLCSTR_MOHQ, &CALLCSTR_FROM, &CALLCSTR_CNTCT, &CALLCSTR_TIME, NULL }; /********** * local function declarations **********/ void set_call_key (db_key_t *, int, int); void set_call_val (db_val_t *, int, int, void *); /********** * local functions **********/ /********** * Fill Call Keys * * INPUT: * Arg (1) = row pointer * Arg (2) = column count * OUTPUT: none **********/ void fill_call_keys (db_key_t *prkeys, int ncnt) { int nidx; for (nidx = 0; nidx < ncnt; nidx++) { set_call_key (prkeys, nidx, nidx); } return; } /********** * Fill Call Values * * INPUT: * Arg (1) = row pointer * Arg (2) = call struct pointer * Arg (3) = column count * OUTPUT: none **********/ void fill_call_vals (db_val_t *prvals, call_lst *pcall, int ncnt) { int nstate = pcall->call_state / 100; set_call_val (prvals, CALLCOL_STATE, CALLCOL_STATE, &nstate); if (!ncnt) { return; } set_call_val (prvals, CALLCOL_MOHQ, CALLCOL_MOHQ, &pcall->pmohq->mohq_id); set_call_val (prvals, CALLCOL_CALL, CALLCOL_CALL, pcall->call_id); set_call_val (prvals, CALLCOL_FROM, CALLCOL_FROM, pcall->call_from); set_call_val (prvals, CALLCOL_CNTCT, CALLCOL_CNTCT, pcall->call_contact); set_call_val (prvals, CALLCOL_TIME, CALLCOL_TIME, &pcall->call_time); return; } /********** * Set Call Column Key * * INPUT: * Arg (1) = row pointer * Arg (2) = column number * Arg (3) = column id * OUTPUT: none **********/ void set_call_key (db_key_t *prkeys, int ncol, int ncolid) { prkeys [ncol] = call_columns [ncolid]; return; } /********** * Set Call Column Value * * INPUT: * Arg (1) = row pointer * Arg (2) = column number * Arg (3) = column id * Arg (4) = value pointer * OUTPUT: none **********/ void set_call_val (db_val_t *prvals, int ncol, int ncolid, void *pdata) { /********** * fill based on column **********/ switch (ncolid) { case CALLCOL_MOHQ: case CALLCOL_STATE: prvals [ncol].val.int_val = *((int *)pdata); prvals [ncol].type = DB1_INT; prvals [ncol].nul = 0; break; case CALLCOL_CALL: case CALLCOL_CNTCT: case CALLCOL_FROM: prvals [ncol].val.string_val = (char *)pdata; prvals [ncol].type = DB1_STRING; prvals [ncol].nul = 0; break; case CALLCOL_TIME: prvals [ncol].val.time_val = *((time_t *)pdata); prvals [ncol].type = DB1_DATETIME; prvals [ncol].nul = 0; break; } return; } /********** * external functions **********/ /********** * Add Call Record * * INPUT: * Arg (1) = call index * OUTPUT: none **********/ void add_call_rec (int ncall_idx) { /********** * o fill column names and values * o insert new record **********/ char *pfncname = "add_call_rec: "; db1_con_t *pconn = mohq_dbconnect (); if (!pconn) { return; } db_func_t *pdb = pmod_data->pdb; pdb->use_table (pconn, &pmod_data->pcfg->db_ctable); db_key_t prkeys [CALL_COLCNT]; fill_call_keys (prkeys, CALL_COLCNT); db_val_t prvals [CALL_COLCNT]; call_lst *pcall = &pmod_data->pcall_lst [ncall_idx]; pcall->call_time = time (0); fill_call_vals (prvals, pcall, CALL_COLCNT); if (pdb->insert (pconn, prkeys, prvals, CALL_COLCNT) < 0) { LM_WARN ("%sUnable to add new row to %s\n", pfncname, pmod_data->pcfg->db_ctable.s); } mohq_dbdisconnect (pconn); return; } /********** * Clear Call Records * * INPUT: * Arg (1) = connection pointer * OUTPUT: none **********/ void clear_calls (db1_con_t *pconn) { /********** * delete all records **********/ char *pfncname = "clear_calls: "; db_func_t *pdb = pmod_data->pdb; pdb->use_table (pconn, &pmod_data->pcfg->db_ctable); if (pdb->delete (pconn, 0, 0, 0, 0) < 0) { LM_WARN ("%sUnable to delete all rows from %s\n", pfncname, pmod_data->pcfg->db_ctable.s); } return; } /********** * Delete Call Record * * INPUT: * Arg (1) = call pointer * OUTPUT: none **********/ void delete_call_rec (call_lst *pcall) { /********** * o setup to delete based on call ID * o delete record **********/ char *pfncname = "delete_call_rec: "; db1_con_t *pconn = mohq_dbconnect (); if (!pconn) { return; } db_func_t *pdb = pmod_data->pdb; pdb->use_table (pconn, &pmod_data->pcfg->db_ctable); db_key_t prkeys [1]; set_call_key (prkeys, 0, CALLCOL_CALL); db_val_t prvals [1]; set_call_val (prvals, 0, CALLCOL_CALL, pcall->call_id); if (pdb->delete (pconn, prkeys, 0, prvals, 1) < 0) { LM_WARN ("%sUnable to delete row from %s\n", pfncname, pmod_data->pcfg->db_ctable.s); } mohq_dbdisconnect (pconn); return; } /********** * Connect to DB * * INPUT: none * OUTPUT: DB connection pointer; NULL=failed **********/ db1_con_t *mohq_dbconnect (void) { str *pdb_url = &pmod_data->pcfg->db_url; db1_con_t *pconn = pmod_data->pdb->init (pdb_url); if (!pconn) { LM_ERR ("Unable to connect to DB %s!\n", pdb_url->s); } return pconn; } /********** * Disconnect from DB * * INPUT: * Arg (1) = connection pointer * OUTPUT: none **********/ void mohq_dbdisconnect (db1_con_t *pconn) { pmod_data->pdb->close (pconn); return; } /********** * Update Call Record * * INPUT: * Arg (1) = call pointer * OUTPUT: none **********/ void update_call_rec (call_lst *pcall) { /********** * o setup to update based on call ID * o update record **********/ char *pfncname = "update_call_rec: "; db1_con_t *pconn = mohq_dbconnect (); if (!pconn) { return; } db_func_t *pdb = pmod_data->pdb; pdb->use_table (pconn, &pmod_data->pcfg->db_ctable); db_key_t pqkeys [1]; set_call_key (pqkeys, 0, CALLCOL_CALL); db_val_t pqvals [1]; set_call_val (pqvals, 0, CALLCOL_CALL, pcall->call_id); db_key_t pukeys [1]; set_call_key (pukeys, 0, CALLCOL_STATE); db_val_t puvals [1]; fill_call_vals (puvals, pcall, CALLCOL_STATE); if (pdb->update (pconn, pqkeys, 0, pqvals, pukeys, puvals, 1, 1) < 0) { LM_WARN ("%sUnable to update row in %s\n", pfncname, pmod_data->pcfg->db_ctable.s); } mohq_dbdisconnect (pconn); return; } /********** * Update Debug Record * * INPUT: * Arg (1) = MOH queue pointer * Arg (2) = debug flag * OUTPUT: none **********/ void update_debug (mohq_lst *pqueue, int bdebug) { /********** * o setup to update based on queue name * o update record **********/ char *pfncname = "update_debug: "; db1_con_t *pconn = mohq_dbconnect (); if (!pconn) { return; } db_func_t *pdb = pmod_data->pdb; pdb->use_table (pconn, &pmod_data->pcfg->db_qtable); db_key_t pqkeys [1] = { mohq_columns [MOHQCOL_NAME] }; db_val_t pqvals [1]; pqvals->val.string_val = pqueue->mohq_name; pqvals->type = DB1_STRING; pqvals->nul = 0; db_key_t pukeys [1] = { mohq_columns [MOHQCOL_DEBUG] }; db_val_t puvals [1]; puvals->val.int_val = bdebug; puvals->type = DB1_INT; puvals->nul = 0; if (pdb->update (pconn, pqkeys, 0, pqvals, pukeys, puvals, 1, 1) < 0) { LM_WARN ("%sUnable to update row in %s\n", pfncname, pmod_data->pcfg->db_qtable.s); } mohq_dbdisconnect (pconn); return; } /********** * Update Message Queue List * * INPUT: * Arg (1) = connection pointer * OUTPUT: none **********/ void update_mohq_lst (db1_con_t *pconn) { /********** * o reset checked flag on all queues * o read queues from table **********/ char *pfncname = "update_mohq_lst: "; if (!pconn) { return; } db_func_t *pdb = pmod_data->pdb; mohq_lst *pqlst = pmod_data->pmohq_lst; int nidx; for (nidx = 0; nidx < pmod_data->mohq_cnt; nidx++) { pqlst [nidx].mohq_flags &= ~MOHQF_CHK; } pdb->use_table (pconn, &pmod_data->pcfg->db_qtable); db_key_t prkeys [MOHQ_COLCNT]; for (nidx = 0; nidx < MOHQ_COLCNT; nidx++) { prkeys [nidx] = mohq_columns [nidx]; } db1_res_t *presult = NULL; if (pdb->query (pconn, 0, 0, 0, prkeys, 0, MOHQ_COLCNT, 0, &presult)) { LM_ERR ("%stable query (%s) failed!\n", pfncname, pmod_data->pcfg->db_qtable.s); return; } db_row_t *prows = RES_ROWS (presult); int nrows = RES_ROW_N (presult); db_val_t *prowvals = NULL; char *ptext, *puri; mohq_lst *pnewlst; for (nidx = 0; nidx < nrows; nidx++) { /********** * check URI **********/ prowvals = ROW_VALUES (prows + nidx); char *pqname = (char *)VAL_STRING (prowvals + MOHQCOL_NAME); puri = (char *)VAL_STRING (prowvals + MOHQCOL_URI); struct sip_uri puri_parsed [1]; if (parse_uri (puri, strlen (puri), puri_parsed)) { LM_ERR ("Queue,Field (%s,%.*s): %s is not a valid URI!\n", pqname, STR_FMT (&MOHQCSTR_URI), puri); continue; } /********** * check MOHDIR **********/ char *pmohdir; if (VAL_NULL (prowvals + MOHQCOL_MDIR)) { pmohdir = pmod_data->pcfg->mohdir; } else { pmohdir = (char *)VAL_STRING (prowvals + MOHQCOL_MDIR); if (!*pmohdir) { pmohdir = pmod_data->pcfg->mohdir; } else { /********** * mohdir * o exists? * o directory? **********/ struct stat psb [1]; if (lstat (pmohdir, psb)) { LM_ERR ("Queue,Field (%s,%.*s): Unable to find %s!\n", pqname, STR_FMT (&MOHQCSTR_MDIR), pmohdir); continue; } else { if ((psb->st_mode & S_IFMT) != S_IFDIR) { LM_ERR ("Queue,Field (%s,%.*s): %s is not a directory!\n", pqname, STR_FMT (&MOHQCSTR_MDIR), pmohdir); continue; } } } } /********** * check for MOH files **********/ rtpmap **pmohfiles = find_MOH (pmohdir, (char *)VAL_STRING (prowvals + MOHQCOL_MFILE)); if (!pmohfiles [0]) { LM_ERR ("Queue,Field (%s,%.*s): Unable to find MOH files (%s/%s.*)!\n", pqname, STR_FMT (&MOHQCSTR_MDIR), pmohdir, (char *)VAL_STRING (prowvals + MOHQCOL_MFILE)); continue; } /********** * find matching queues **********/ int bfnd = 0; int nidx2; for (nidx2 = 0; nidx2 < pmod_data->mohq_cnt; nidx2++) { if (!strcasecmp (pqlst [nidx2].mohq_uri, puri)) { /********** * o data the same? * o mark as found **********/ if (strcmp (pqlst [nidx2].mohq_mohdir, pmohdir)) { strcpy (pqlst [nidx2].mohq_mohdir, pmohdir); LM_INFO ("Queue,Field (%s,%.*s): Changed\n", pqname, STR_FMT (&MOHQCSTR_MDIR)); } ptext = (char *)VAL_STRING (prowvals + MOHQCOL_MFILE); if (strcmp (pqlst [nidx2].mohq_mohfile, ptext)) { strcpy (pqlst [nidx2].mohq_mohfile, ptext); LM_INFO ("Queue,Field (%s,%.*s): Changed\n", pqname, STR_FMT (&MOHQCSTR_MFILE)); } ptext = (char *)VAL_STRING (prowvals + MOHQCOL_NAME); if (strcmp (pqlst [nidx2].mohq_name, ptext)) { strcpy (pqlst [nidx2].mohq_name, ptext); LM_INFO ("Queue,Field (%s,%.*s): Changed\n", pqname, STR_FMT (&MOHQCSTR_NAME)); } int bdebug = VAL_INT (prowvals + MOHQCOL_DEBUG) ? MOHQF_DBG : 0; if ((pqlst [nidx2].mohq_flags & MOHQF_DBG) != bdebug) { if (bdebug) { pqlst [nidx2].mohq_flags |= MOHQF_DBG; } else { pqlst [nidx2].mohq_flags &= ~MOHQF_DBG; } LM_INFO ("Queue,Field (%s,%.*s): Changed\n", pqname, STR_FMT (&MOHQCSTR_DEBUG)); } bfnd = -1; pqlst [nidx2].mohq_flags |= MOHQF_CHK; break; } } /********** * add new queue **********/ if (!bfnd) { /********** * o allocate new list * o copy old list * o add new row * o release old list * o adjust pointers to new list **********/ int nsize = pmod_data->mohq_cnt + 1; pnewlst = (mohq_lst *) shm_malloc (sizeof (mohq_lst) * nsize); if (!pnewlst) { LM_ERR ("%sUnable to allocate shared memory!\n", pfncname); return; } pmod_data->mohq_cnt = nsize; if (--nsize) { memcpy (pnewlst, pqlst, sizeof (mohq_lst) * nsize); } pnewlst [nsize].mohq_id = prowvals [MOHQCOL_ID].val.int_val; pnewlst [nsize].mohq_flags = MOHQF_CHK; strcpy (pnewlst [nsize].mohq_uri, puri); strcpy (pnewlst [nsize].mohq_mohdir, pmohdir); strcpy (pnewlst [nsize].mohq_mohfile, (char *)VAL_STRING (prowvals + MOHQCOL_MFILE)); strcpy (pnewlst [nsize].mohq_name, (char *)VAL_STRING (prowvals + MOHQCOL_NAME)); if (VAL_INT (prowvals + MOHQCOL_DEBUG)) { pnewlst [nsize].mohq_flags |= MOHQF_DBG; } LM_INFO ("Added new queue (%s)\n", pnewlst [nsize].mohq_name); if (nsize) { shm_free (pmod_data->pmohq_lst); } pmod_data->pmohq_lst = pnewlst; pqlst = pnewlst; } } /********** * find deleted queues **********/ for (nidx = 0; nidx < pmod_data->mohq_cnt; nidx++) { /********** * o exists? * o if not last, replace current with last queue **********/ if (pqlst [nidx].mohq_flags & MOHQF_CHK) { continue; } LM_INFO ("Removed queue (%s)\n", pqlst [nidx].mohq_name); if (nidx != (pmod_data->mohq_cnt - 1)) { memcpy (&pqlst [nidx], &pqlst [pmod_data->mohq_cnt - 1], sizeof (mohq_lst)); } --pmod_data->mohq_cnt; --nidx; } return; }