/* * Copyright (C) 2009 TelTech Systems Inc. * * This file is part of SEMS, a free SIP media server. * * SEMS 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. This program is released under * the GPL with the additional exemption that compiling, linking, * and/or using OpenSSL is allowed. * * For a license to use the SEMS software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * SEMS 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "ModMysql.h" #include "log.h" #include "AmUtils.h" #include "DSMSession.h" #include "AmSession.h" #include "AmPlaylist.h" #include #include SC_EXPORT(SCMysqlModule); SCMysqlModule::SCMysqlModule() { } SCMysqlModule::~SCMysqlModule() { } DSMAction* SCMysqlModule::getAction(const string& from_str) { string cmd; string params; splitCmd(from_str, cmd, params); DEF_CMD("mysql.connect", SCMyConnectAction); DEF_CMD("mysql.disconnect", SCMyDisconnectAction); DEF_CMD("mysql.execute", SCMyExecuteAction); DEF_CMD("mysql.query", SCMyQueryAction); DEF_CMD("mysql.queryGetResult", SCMyQueryGetResultAction); DEF_CMD("mysql.getResult", SCMyGetResultAction); DEF_CMD("mysql.getClientVersion", SCMyGetClientVersion); DEF_CMD("mysql.resolveQueryParams", SCMyResolveQueryParams); DEF_CMD("mysql.saveResult", SCMySaveResultAction); DEF_CMD("mysql.useResult", SCMyUseResultAction); DEF_CMD("mysql.playDBAudio", SCMyPlayDBAudioAction); DEF_CMD("mysql.playDBAudioFront", SCMyPlayDBAudioFrontAction); DEF_CMD("mysql.playDBAudioLooped", SCMyPlayDBAudioLoopedAction); DEF_CMD("mysql.getFileFromDB", SCMyGetFileFromDBAction); DEF_CMD("mysql.putFileToDB", SCMyPutFileToDBAction); DEF_CMD("mysql.escape", SCMyEscapeAction); return NULL; } DSMCondition* SCMysqlModule::getCondition(const string& from_str) { string cmd; string params; splitCmd(from_str, cmd, params); if (cmd == "mysql.hasResult") { return new MyHasResultCondition(params, false); } if (cmd == "mysql.connected") { return new MyConnectedCondition(params, true); } return NULL; } mysqlpp::Connection* getMyDSMSessionConnection(DSMSession* sc_sess) { if (sc_sess->avar.find(MY_AKEY_CONNECTION) == sc_sess->avar.end()) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR("No connection to database"); return NULL; } AmObject* ao = NULL; mysqlpp::Connection* res = NULL; try { if (!isArgAObject(sc_sess->avar[MY_AKEY_CONNECTION])) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR("No connection to database (not AmObject)"); return NULL; } ao = sc_sess->avar[MY_AKEY_CONNECTION].asObject(); } catch (...){ sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR("No connection to database (not AmObject)"); return NULL; } if (NULL == ao || NULL == (res = dynamic_cast(ao))) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR("No connection to database (not mysqlpp::Connection)"); return NULL; } return res; } mysqlpp::StoreQueryResult* getMyDSMQueryResult(DSMSession* sc_sess) { if (sc_sess->avar.find(MY_AKEY_RESULT) == sc_sess->avar.end()) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NORESULT); sc_sess->SET_STRERROR("No result available"); return NULL; } AmObject* ao = NULL; mysqlpp::StoreQueryResult* res = NULL; try { assertArgAObject(sc_sess->avar[MY_AKEY_RESULT]); ao = sc_sess->avar[MY_AKEY_RESULT].asObject(); } catch (...){ sc_sess->SET_ERRNO(DSM_ERRNO_MY_NORESULT); sc_sess->SET_STRERROR("Result object has wrong type"); return NULL; } if (NULL == ao || NULL == (res = dynamic_cast(ao))) { sc_sess->SET_STRERROR("Result object has wrong type"); sc_sess->SET_ERRNO(DSM_ERRNO_MY_NORESULT); return NULL; } return res; } string replaceQueryParams(const string& q, DSMSession* sc_sess, map* event_params) { string res = q; size_t repl_pos = 0; while (repl_posvar[res.substr(rstart+1, rend-rstart-1)]); break; case '#': if (NULL!=event_params) { res.replace(rstart, rend-rstart, (*event_params)[res.substr(rstart+1, rend-rstart-1)]); break; } default: break; } } return res; } string str_between(const string s, char b, char e) { size_t pos1 = s.find(b); if (b == '\0' || pos1 == string::npos) pos1 = 0; else pos1++; size_t pos2 = s.find(e, pos1); if (e == '\0' || pos2 == string::npos) pos2 = s.length(); return s.substr(pos1, pos2-pos1); } EXEC_ACTION_START(SCMyConnectAction) { string f_arg = resolveVars(arg, sess, sc_sess, event_params); string db_url = f_arg.length()?f_arg:sc_sess->var["config.db_url"]; if (db_url.empty() || db_url.length() < 11 || db_url.substr(0, 8) != "mysql://") { ERROR("missing correct db_url config or connect parameter\n"); sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); sc_sess->SET_STRERROR("missing correct db_url config or connect parameter\n"); return false; } // split url db_url = db_url.substr(8); string db_user = str_between(db_url, '\0', ':'); string db_pwd = str_between(db_url, ':', '@'); string db_host = str_between(db_url, '@', '/'); string db_db = str_between(db_url, '/', '\0'); DSMMyConnection* conn = NULL; try { conn = new DSMMyConnection(db_db.c_str(), db_host.c_str(), db_user.c_str(), db_pwd.c_str()); // save connection for later use AmArg c_arg; c_arg.setBorrowedPointer(conn); sc_sess->avar[MY_AKEY_CONNECTION] = c_arg; // for garbage collection sc_sess->transferOwnership(conn); sc_sess->CLR_ERRNO; } catch (const mysqlpp::ConnectionFailed& e) { ERROR("DB connection failed with error %d: '%s'\n",e.errnum(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.errno"] = int2str(e.errnum()); sc_sess->var["db.ereason"] = e.what(); } catch (const mysqlpp::Exception& e) { ERROR("DB connection failed: '%s'\n", e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; EXEC_ACTION_START(SCMyDisconnectAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; try { conn->disconnect(); // connection object might be reused - but its safer to create a new one sc_sess->avar[MY_AKEY_CONNECTION] = AmArg(); sc_sess->CLR_ERRNO; } catch (const mysqlpp::Exception& e) { ERROR("DB disconnect failed: '%s'\n", e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_CONNECTION); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; EXEC_ACTION_START(SCMyResolveQueryParams) { sc_sess->var["db.qstr"] = replaceQueryParams(arg, sc_sess, event_params); } EXEC_ACTION_END; EXEC_ACTION_START(SCMyExecuteAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; string qstr = replaceQueryParams(arg, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); mysqlpp::SimpleResult res = query.execute(); if (res) { sc_sess->CLR_ERRNO; sc_sess->var["db.rows"] = int2str((int)res.rows()); sc_sess->var["db.info"] = res.info(); sc_sess->var["db.insert_id"] = int2str((int)res.insert_id()); } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(res.info()); sc_sess->var["db.info"] = res.info(); } } catch (const mysqlpp::Exception& e) { ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; EXEC_ACTION_START(SCMyQueryAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; string qstr = replaceQueryParams(arg, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); mysqlpp::StoreQueryResult res = query.store(); if (res) { // MySQL++ does not allow working with pointers here, so copy construct it DSMMyStoreQueryResult* m_res = new DSMMyStoreQueryResult(res); // save result for later use AmArg c_arg; c_arg.setBorrowedPointer(m_res); sc_sess->avar[MY_AKEY_RESULT] = c_arg; // for garbage collection sc_sess->transferOwnership(m_res); sc_sess->CLR_ERRNO; sc_sess->var["db.rows"] = int2str((unsigned int)res.num_rows()); } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR("query did not have a result"); } } catch (const mysqlpp::Exception& e) { ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; CONST_ACTION_2P(SCMyQueryGetResultAction, ',', true); EXEC_ACTION_START(SCMyQueryGetResultAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; string qstr = replaceQueryParams(par1, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); mysqlpp::StoreQueryResult res = query.store(); if (res) { unsigned int rowindex_i = 0; string rowindex = resolveVars(par2, sess, sc_sess, event_params); if (rowindex.length()) { if (str2int(rowindex, rowindex_i)) { ERROR("row index '%s' not understood\n", rowindex.c_str()); sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); sc_sess->SET_STRERROR("row index '"+rowindex+"' not understood\n"); return false; } } if (res.size() <= rowindex_i) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); sc_sess->SET_STRERROR("row index out of result rows bounds"); return false; } // get all columns for (size_t i = 0; i < res.field_names()->size(); i++) { sc_sess->var[res.field_name(i)] = (string)res[rowindex_i][res.field_name(i).c_str()]; } sc_sess->CLR_ERRNO; sc_sess->var["db.rows"] = int2str((unsigned int)res.num_rows()); } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); } } catch (const mysqlpp::Exception& e) { ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; CONST_ACTION_2P(SCMyGetResultAction, ',', true); EXEC_ACTION_START(SCMyGetResultAction) { mysqlpp::StoreQueryResult* res = getMyDSMQueryResult(sc_sess); if (NULL == res) return false; if (!res) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NORESULT); sc_sess->SET_STRERROR("No result from query"); return false; } unsigned int rowindex_i = 0; string rowindex = resolveVars(par1, sess, sc_sess, event_params); string colname = resolveVars(par2, sess, sc_sess, event_params); if (rowindex.length()) { if (str2int(rowindex, rowindex_i)) { ERROR("row index '%s' not understood\n", rowindex.c_str()); sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); sc_sess->SET_STRERROR("row index '"+rowindex+"' not understood"); return false; } } if (res->size() <= rowindex_i) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); sc_sess->SET_STRERROR("row index out of result rows bounds"); return false; } DBG("rowindex_i = %d\n", rowindex_i); if (colname.length()) { // get only this column try { sc_sess->var[colname] = (string)(*res)[(int)rowindex_i][colname.c_str()]; } catch (const mysqlpp::BadFieldName& e) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOCOLUMN); sc_sess->SET_STRERROR("bad field name '"+colname+"'"); return false; } } else { // get all columns for (size_t i = 0; i < res->field_names()->size(); i++) { sc_sess->var[res->field_name(i)] = (string)(*res)[rowindex_i][res->field_name(i).c_str()]; } } sc_sess->CLR_ERRNO; } EXEC_ACTION_END; EXEC_ACTION_START(SCMyGetClientVersion) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; sc_sess->var[resolveVars(arg, sess, sc_sess, event_params)] = conn->client_version(); sc_sess->CLR_ERRNO; } EXEC_ACTION_END; MATCH_CONDITION_START(MyHasResultCondition) { mysqlpp::StoreQueryResult* res = getMyDSMQueryResult(sc_sess); if (NULL == res) return false; if (!res || !(res->size())) { return false; } return true; } MATCH_CONDITION_END; MATCH_CONDITION_START(MyConnectedCondition) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; return conn->connected(); } MATCH_CONDITION_END; EXEC_ACTION_START(SCMySaveResultAction) { sc_sess->avar[resolveVars(arg, sess, sc_sess, event_params)] = sc_sess->avar[MY_AKEY_RESULT]; } EXEC_ACTION_END; EXEC_ACTION_START(SCMyUseResultAction) { sc_sess->avar[MY_AKEY_RESULT] = sc_sess->avar[resolveVars(arg, sess, sc_sess, event_params)]; } EXEC_ACTION_END; bool playDBAudio(AmSession* sess, DSMSession* sc_sess, DSMCondition::EventType event, map* event_params, const string& par1, const string& par2, bool looped, bool front) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) EXEC_ACTION_STOP; string qstr = replaceQueryParams(par1, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); mysqlpp::UseQueryResult res = query.use(); if (res) { mysqlpp::Row row = res.fetch_row(); if (!row) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); sc_sess->SET_STRERROR("result does not have row"); EXEC_ACTION_STOP; } FILE *t_file = tmpfile(); if (NULL == t_file) { sc_sess->SET_ERRNO(DSM_ERRNO_FILE); sc_sess->SET_STRERROR("tmpfile() failed: "+string(strerror(errno))); EXEC_ACTION_STOP; } fwrite(row.at(0).data(), 1, row.at(0).size(), t_file); rewind(t_file); DSMDisposableAudioFile* a_file = new DSMDisposableAudioFile(); if (a_file->fpopen(par2, AmAudioFile::Read, t_file)) { sc_sess->SET_ERRNO(DSM_ERRNO_FILE); sc_sess->SET_STRERROR("fpopen failed!"); EXEC_ACTION_STOP; } a_file->loop = looped; sc_sess->addToPlaylist(new AmPlaylistItem(a_file, NULL), front); sc_sess->transferOwnership(a_file); sc_sess->CLR_ERRNO; } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR("query does not have result"); } } catch (const mysqlpp::Exception& e) { ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } return false; } CONST_ACTION_2P(SCMyPlayDBAudioAction, ',', true); EXEC_ACTION_START(SCMyPlayDBAudioAction) { playDBAudio(sess, sc_sess, event, event_params, par1, par2, /*looped = */ false, /*front = */ false); } EXEC_ACTION_END; CONST_ACTION_2P(SCMyPlayDBAudioFrontAction, ',', true); EXEC_ACTION_START(SCMyPlayDBAudioFrontAction) { playDBAudio(sess, sc_sess, event, event_params, par1, par2, /*looped = */ false, /*front = */ true); } EXEC_ACTION_END; CONST_ACTION_2P(SCMyPlayDBAudioLoopedAction, ',', true); EXEC_ACTION_START(SCMyPlayDBAudioLoopedAction) { playDBAudio(sess, sc_sess, event, event_params, par1, par2, /*looped = */ true, /*front = */ false); } EXEC_ACTION_END; CONST_ACTION_2P(SCMyGetFileFromDBAction, ',', true); EXEC_ACTION_START(SCMyGetFileFromDBAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; string qstr = replaceQueryParams(par1, sc_sess, event_params); string fname = resolveVars(par2, sess, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); mysqlpp::UseQueryResult res = query.use(); if (res) { mysqlpp::Row row = res.fetch_row(); if (!row) { sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); sc_sess->SET_STRERROR("result does not have row"); return false; } FILE *t_file = fopen(fname.c_str(), "wb"); if (NULL == t_file) { sc_sess->SET_ERRNO(DSM_ERRNO_FILE); sc_sess->SET_STRERROR("fopen() failed for file '"+fname+"': "+string(strerror(errno))); return false; } fwrite(row.at(0).data(), 1, row.at(0).size(), t_file); fclose(t_file); sc_sess->CLR_ERRNO; } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR("query does not have result"); } } catch (const mysqlpp::Exception& e) { ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; CONST_ACTION_2P(SCMyPutFileToDBAction, ',', true); EXEC_ACTION_START(SCMyPutFileToDBAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; string qstr = replaceQueryParams(par1, sc_sess, event_params); string fname = resolveVars(par2, sess, sc_sess, event_params); size_t fpos = qstr.find("__FILE__"); if (fpos == string::npos) { ERROR("missing __FILE__ in query string '%s'\n", par1.c_str()); sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); sc_sess->SET_STRERROR("missing __FILE__ in query string '"+par1+"'\n"); return false; } try { std::ifstream data_file(fname.c_str(), std::ios::in | std::ios::binary); if (!data_file) { DBG("could not read file '%s'\n", fname.c_str()); sc_sess->SET_ERRNO(DSM_ERRNO_FILE); sc_sess->SET_STRERROR("could not read file '"+fname+"'\n"); return false; } // that one is clever... // (see http://www.gamedev.net/community/forums/topic.asp?topic_id=353162 ) string file_data((std::istreambuf_iterator(data_file)), std::istreambuf_iterator()); if (file_data.empty()) { DBG("could not read file '%s'\n", fname.c_str()); sc_sess->SET_ERRNO(DSM_ERRNO_FILE); sc_sess->SET_STRERROR("could not read file '"+fname+"'\n"); return false; } mysqlpp::Query query = conn->query(); query << qstr.substr(0, fpos) << mysqlpp::escape << file_data << qstr.substr(fpos+8); mysqlpp::SimpleResult res = query.execute(); if (res) { sc_sess->CLR_ERRNO; sc_sess->var["db.rows"] = int2str((int)res.rows()); sc_sess->var["db.info"] = res.info(); sc_sess->var["db.insert_id"] = int2str((int)res.insert_id()); } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(res.info()); sc_sess->var["db.info"] = res.info(); } } catch (const mysqlpp::Exception& e) { ERROR("DB query '%s' failed: '%s'\n", par1.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; CONST_ACTION_2P(SCMyEscapeAction, '=', false); EXEC_ACTION_START(SCMyEscapeAction) { mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); if (NULL == conn) return false; mysqlpp::Query query = conn->query(); string val = resolveVars(par2, sess, sc_sess, event_params); string dstvar = par1; if (dstvar.size() && dstvar[0] == '$') { dstvar = dstvar.substr(1); } string res; query.escape_string(&res, val.c_str(), val.length()); sc_sess->var[dstvar] = res; DBG("escaped: $%s = escape(%s) = %s\n", dstvar.c_str(), val.c_str(), res.c_str()); } EXEC_ACTION_END;