|
reSIProcate/repro
9694
|
00001 #include <cassert> 00002 #include <fcntl.h> 00003 00004 #ifdef HAVE_CONFIG_H 00005 #include "config.h" 00006 #endif 00007 00008 #ifdef USE_MYSQL 00009 00010 #ifdef WIN32 00011 #include <errmsg.h> 00012 #else 00013 #include <mysql/errmsg.h> 00014 #endif 00015 00016 #include "rutil/Data.hxx" 00017 #include "rutil/DataStream.hxx" 00018 #include "rutil/Logger.hxx" 00019 #include "rutil/ParseBuffer.hxx" 00020 00021 #include "repro/AbstractDb.hxx" 00022 #include "repro/MySqlDb.hxx" 00023 00024 00025 using namespace resip; 00026 using namespace repro; 00027 using namespace std; 00028 00029 #define RESIPROCATE_SUBSYSTEM Subsystem::REPRO 00030 00031 extern "C" 00032 { 00033 void mysqlThreadEnd(void*) 00034 { 00035 mysql_thread_end(); 00036 } 00037 } 00038 00039 // This class helps ensure that each thread using the MySQL API's 00040 // initialize by calling mysql_thread_init before calling any mySQL functions 00041 class MySQLInitializer 00042 { 00043 public: 00044 MySQLInitializer() 00045 { 00046 ThreadIf::tlsKeyCreate(mThreadStorage, mysqlThreadEnd); 00047 } 00048 ~MySQLInitializer() 00049 { 00050 ThreadIf::tlsKeyDelete(mThreadStorage); 00051 } 00052 void setInitialized() 00053 { 00054 ThreadIf::tlsSetValue(mThreadStorage, (void*) true); 00055 } 00056 bool isInitialized() 00057 { 00058 // Note: if value is not set yet then 0 (false) is returned 00059 return ThreadIf::tlsGetValue(mThreadStorage) != 0; 00060 } 00061 00062 private: 00063 ThreadIf::TlsKey mThreadStorage; 00064 }; 00065 static MySQLInitializer g_MySQLInitializer; 00066 00067 MySqlDb::MySqlDb(const Data& server, 00068 const Data& user, 00069 const Data& password, 00070 const Data& databaseName, 00071 unsigned int port, 00072 const Data& customUserAuthQuery) : 00073 mDBServer(server), 00074 mDBUser(user), 00075 mDBPassword(password), 00076 mDBName(databaseName), 00077 mDBPort(port), 00078 mCustomUserAuthQuery(customUserAuthQuery), 00079 mConn(0), 00080 mConnected(false) 00081 { 00082 InfoLog( << "Using MySQL DB with server=" << server << ", user=" << user << ", dbName=" << databaseName << ", port=" << port); 00083 00084 for (int i=0;i<MaxTable;i++) 00085 { 00086 mResult[i]=0; 00087 } 00088 00089 mysql_library_init(0, 0, 0); 00090 if(!mysql_thread_safe()) 00091 { 00092 ErrLog( << "Repro uses MySQL from multiple threads - you MUST link with a thread safe version of the mySQL client library!"); 00093 } 00094 else 00095 { 00096 connectToDatabase(); 00097 } 00098 } 00099 00100 00101 MySqlDb::~MySqlDb() 00102 { 00103 disconnectFromDatabase(); 00104 } 00105 00106 void 00107 MySqlDb::initialize() const 00108 { 00109 if(!g_MySQLInitializer.isInitialized()) 00110 { 00111 g_MySQLInitializer.setInitialized(); 00112 mysql_thread_init(); 00113 } 00114 } 00115 00116 void 00117 MySqlDb::disconnectFromDatabase() const 00118 { 00119 if(mConn) 00120 { 00121 for (int i=0;i<MaxTable;i++) 00122 { 00123 if (mResult[i]) 00124 { 00125 mysql_free_result(mResult[i]); 00126 mResult[i]=0; 00127 } 00128 } 00129 00130 mysql_close(mConn); 00131 mConn = 0; 00132 mConnected = false; 00133 } 00134 } 00135 00136 int 00137 MySqlDb::connectToDatabase() const 00138 { 00139 // Disconnect from database first (if required) 00140 disconnectFromDatabase(); 00141 00142 // Now try to connect 00143 assert(mConn == 0); 00144 assert(mConnected == false); 00145 00146 mConn = mysql_init(0); 00147 if(mConn == 0) 00148 { 00149 ErrLog( << "MySQL init failed: insufficient memory."); 00150 return CR_OUT_OF_MEMORY; 00151 } 00152 00153 MYSQL* ret = mysql_real_connect(mConn, 00154 mDBServer.c_str(), // hostname 00155 mDBUser.c_str(), // user 00156 mDBPassword.c_str(), // password 00157 mDBName.c_str(), // DB 00158 mDBPort, // port 00159 0, // unix socket file 00160 0); // client flags 00161 00162 if (ret == 0) 00163 { 00164 int rc = mysql_errno(mConn); 00165 ErrLog( << "MySQL connect failed: error=" << rc << ": " << mysql_error(mConn)); 00166 mysql_close(mConn); 00167 mConn = 0; 00168 mConnected = false; 00169 return rc; 00170 } 00171 else 00172 { 00173 mConnected = true; 00174 return 0; 00175 } 00176 } 00177 00178 int 00179 MySqlDb::query(const Data& queryCommand, MYSQL_RES** result) const 00180 { 00181 int rc = 0; 00182 00183 initialize(); 00184 00185 DebugLog( << "MySqlDb::query: executing query: " << queryCommand); 00186 00187 Lock lock(mMutex); 00188 if(mConn == 0 || !mConnected) 00189 { 00190 rc = connectToDatabase(); 00191 } 00192 if(rc == 0) 00193 { 00194 assert(mConn!=0); 00195 assert(mConnected); 00196 rc = mysql_query(mConn,queryCommand.c_str()); 00197 if(rc != 0) 00198 { 00199 rc = mysql_errno(mConn); 00200 if(rc == CR_SERVER_GONE_ERROR || 00201 rc == CR_SERVER_LOST) 00202 { 00203 // First failure is a connection error - try to re-connect and then try again 00204 rc = connectToDatabase(); 00205 if(rc == 0) 00206 { 00207 // OK - we reconnected - try query again 00208 rc = mysql_query(mConn,queryCommand.c_str()); 00209 if( rc != 0) 00210 { 00211 ErrLog( << "MySQL query failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00212 } 00213 } 00214 } 00215 else 00216 { 00217 ErrLog( << "MySQL query failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00218 } 00219 } 00220 } 00221 00222 // Now store result - if pointer to result pointer was supplied and no errors 00223 if(rc == 0 && result) 00224 { 00225 *result = mysql_store_result(mConn); 00226 if(*result == 0) 00227 { 00228 rc = mysql_errno(mConn); 00229 if(rc != 0) 00230 { 00231 ErrLog( << "MySQL store result failed: error=" << rc << ": " << mysql_error(mConn)); 00232 } 00233 } 00234 } 00235 00236 if(rc != 0) 00237 { 00238 ErrLog( << " SQL Command was: " << queryCommand) ; 00239 } 00240 return rc; 00241 } 00242 00243 int 00244 MySqlDb::singleResultQuery(const Data& queryCommand, std::vector<Data>& fields) const 00245 { 00246 MYSQL_RES* result=0; 00247 int rc = query(queryCommand, &result); 00248 00249 if(rc == 0) 00250 { 00251 if(result == 0) 00252 { 00253 return rc; 00254 } 00255 00256 MYSQL_ROW row = mysql_fetch_row(result); 00257 if(row) 00258 { 00259 for(unsigned int i = 0; i < result->field_count; i++) 00260 { 00261 fields.push_back(Data(row[i])); 00262 } 00263 } 00264 else 00265 { 00266 rc = mysql_errno(mConn); 00267 if(rc != 0) 00268 { 00269 ErrLog( << "MySQL fetch row failed: error=" << rc << ": " << mysql_error(mConn)); 00270 } 00271 } 00272 mysql_free_result(result); 00273 } 00274 return rc; 00275 } 00276 00277 resip::Data& 00278 MySqlDb::escapeString(const resip::Data& str, resip::Data& escapedStr) const 00279 { 00280 escapedStr.truncate2(mysql_real_escape_string(mConn, (char*)escapedStr.getBuf(str.size()*2+1), str.c_str(), str.size())); 00281 return escapedStr; 00282 } 00283 00284 bool 00285 MySqlDb::addUser(const AbstractDb::Key& key, const AbstractDb::UserRecord& rec) 00286 { 00287 Data command; 00288 { 00289 DataStream ds(command); 00290 ds << "REPLACE INTO users SET user='" << rec.user 00291 << "', domain='" << rec.domain 00292 << "', realm='" << rec.realm 00293 << "', passwordHash='" << rec.passwordHash 00294 << "', name='" << rec.name 00295 << "', email='" << rec.email 00296 << "', forwardAddress='" << rec.forwardAddress 00297 << "'"; 00298 } 00299 return query(command, 0) == 0; 00300 } 00301 00302 00303 void 00304 MySqlDb::eraseUser(const AbstractDb::Key& key ) 00305 { 00306 Data command; 00307 { 00308 DataStream ds(command); 00309 ds << "DELETE FROM users "; 00310 userWhereClauseToDataStream(key, ds); 00311 } 00312 query(command, 0); 00313 } 00314 00315 00316 AbstractDb::UserRecord 00317 MySqlDb::getUser( const AbstractDb::Key& key ) const 00318 { 00319 AbstractDb::UserRecord ret; 00320 00321 Data command; 00322 { 00323 DataStream ds(command); 00324 ds << "SELECT user, domain, realm, passwordHash, name, email, forwardAddress FROM users "; 00325 userWhereClauseToDataStream(key, ds); 00326 } 00327 00328 MYSQL_RES* result=0; 00329 if(query(command, &result) != 0) 00330 { 00331 return ret; 00332 } 00333 00334 if (result==0) 00335 { 00336 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00337 return ret; 00338 } 00339 00340 MYSQL_ROW row = mysql_fetch_row(result); 00341 if (row) 00342 { 00343 ret.user = Data(row[0]); 00344 ret.domain = Data(row[1]); 00345 ret.realm = Data(row[2]); 00346 ret.passwordHash = Data(row[3]); 00347 ret.name = Data(row[4]); 00348 ret.email = Data(row[5]); 00349 ret.forwardAddress = Data(row[6]); 00350 } 00351 00352 mysql_free_result(result); 00353 00354 return ret; 00355 } 00356 00357 00358 resip::Data 00359 MySqlDb::getUserAuthInfo( const AbstractDb::Key& key ) const 00360 { 00361 std::vector<Data> ret; 00362 00363 Data command; 00364 { 00365 DataStream ds(command); 00366 Data user; 00367 Data domain; 00368 getUserAndDomainFromKey(key, user, domain); 00369 ds << "SELECT passwordHash FROM users WHERE user = '" << user << "' AND domain = '" << domain << "' "; 00370 00371 // Note: domain is empty when querying for HTTP admin user - for this special user, 00372 // we will only check the repro db, by not adding the UNION statement below 00373 if(!mCustomUserAuthQuery.empty() && !domain.empty()) 00374 { 00375 ds << " UNION " << mCustomUserAuthQuery; 00376 ds.flush(); 00377 command.replace("$user", user); 00378 command.replace("$domain", domain); 00379 } 00380 } 00381 00382 if(singleResultQuery(command, ret) != 0 || ret.size() == 0) 00383 { 00384 return Data::Empty; 00385 } 00386 00387 DebugLog( << "Auth password is " << ret.front()); 00388 00389 return ret.front(); 00390 } 00391 00392 00393 AbstractDb::Key 00394 MySqlDb::firstUserKey() 00395 { 00396 // free memory from previous search 00397 if (mResult[UserTable]) 00398 { 00399 mysql_free_result(mResult[UserTable]); 00400 mResult[UserTable] = 0; 00401 } 00402 00403 Data command("SELECT user, domain FROM users"); 00404 00405 if(query(command, &mResult[UserTable]) != 0) 00406 { 00407 return Data::Empty; 00408 } 00409 00410 if(mResult[UserTable] == 0) 00411 { 00412 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00413 return Data::Empty; 00414 } 00415 00416 return nextUserKey(); 00417 } 00418 00419 00420 AbstractDb::Key 00421 MySqlDb::nextUserKey() 00422 { 00423 if(mResult[UserTable] == 0) 00424 { 00425 return Data::Empty; 00426 } 00427 00428 MYSQL_ROW row = mysql_fetch_row(mResult[UserTable]); 00429 if (!row) 00430 { 00431 mysql_free_result(mResult[UserTable]); 00432 mResult[UserTable] = 0; 00433 return Data::Empty; 00434 } 00435 Data user(row[0]); 00436 Data domain(row[1]); 00437 00438 return user+"@"+domain; 00439 } 00440 00441 00442 bool 00443 MySqlDb::dbWriteRecord(const Table table, 00444 const resip::Data& pKey, 00445 const resip::Data& pData) 00446 { 00447 Data command; 00448 00449 // Check if there is a secondary key or not and get it's value 00450 char* secondaryKey; 00451 unsigned int secondaryKeyLen; 00452 Data escapedKey; 00453 if(AbstractDb::getSecondaryKey(table, pKey, pData, (void**)&secondaryKey, &secondaryKeyLen) == 0) 00454 { 00455 Data sKey(Data::Share, secondaryKey, secondaryKeyLen); 00456 DataStream ds(command); 00457 ds << "REPLACE INTO " << tableName(table) 00458 << " SET attr='" << escapeString(pKey, escapedKey) 00459 << "', attr2='" << escapeString(sKey, escapedKey) 00460 << "', value='" << pData.base64encode() 00461 << "'"; 00462 } 00463 else 00464 { 00465 DataStream ds(command); 00466 ds << "REPLACE INTO " << tableName(table) 00467 << " SET attr='" << escapeString(pKey, escapedKey) 00468 << "', value='" << pData.base64encode() 00469 << "'"; 00470 } 00471 00472 return query(command, 0) == 0; 00473 } 00474 00475 bool 00476 MySqlDb::dbReadRecord(const Table table, 00477 const resip::Data& pKey, 00478 resip::Data& pData) const 00479 { 00480 Data command; 00481 Data escapedKey; 00482 { 00483 DataStream ds(command); 00484 ds << "SELECT value FROM " << tableName(table) 00485 << " WHERE attr='" << escapeString(pKey, escapedKey) 00486 << "'"; 00487 } 00488 00489 MYSQL_RES* result = 0; 00490 if(query(command, &result) != 0) 00491 { 00492 return false; 00493 } 00494 00495 if (result == 0) 00496 { 00497 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00498 return false; 00499 } 00500 else 00501 { 00502 bool success = false; 00503 MYSQL_ROW row=mysql_fetch_row(result); 00504 if(row) 00505 { 00506 pData = Data(Data::Share, row[0], (Data::size_type)strlen(row[0])).base64decode(); 00507 success = true; 00508 } 00509 mysql_free_result(result); 00510 return success; 00511 } 00512 } 00513 00514 00515 void 00516 MySqlDb::dbEraseRecord(const Table table, 00517 const resip::Data& pKey, 00518 bool isSecondaryKey) // allows deleting records from a table that supports secondary keying using a secondary key 00519 { 00520 Data command; 00521 { 00522 DataStream ds(command); 00523 Data escapedKey; 00524 ds << "DELETE FROM " << tableName(table); 00525 if(isSecondaryKey) 00526 { 00527 ds << " WHERE attr2='" << escapeString(pKey, escapedKey) << "'"; 00528 } 00529 else 00530 { 00531 ds << " WHERE attr='" << escapeString(pKey, escapedKey) << "'"; 00532 } 00533 } 00534 query(command, 0); 00535 } 00536 00537 00538 resip::Data 00539 MySqlDb::dbNextKey(const Table table, bool first) 00540 { 00541 if(first) 00542 { 00543 // free memory from previous search 00544 if (mResult[table]) 00545 { 00546 mysql_free_result(mResult[table]); 00547 mResult[table] = 0; 00548 } 00549 00550 Data command; 00551 { 00552 DataStream ds(command); 00553 ds << "SELECT attr FROM " << tableName(table); 00554 } 00555 00556 if(query(command, &mResult[table]) != 0) 00557 { 00558 return Data::Empty; 00559 } 00560 00561 if (mResult[table] == 0) 00562 { 00563 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00564 return Data::Empty; 00565 } 00566 } 00567 else 00568 { 00569 if (mResult[table] == 0) 00570 { 00571 return Data::Empty; 00572 } 00573 } 00574 00575 MYSQL_ROW row = mysql_fetch_row(mResult[table]); 00576 if (!row) 00577 { 00578 mysql_free_result(mResult[table]); 00579 mResult[table] = 0; 00580 return Data::Empty; 00581 } 00582 00583 return Data(row[0]); 00584 } 00585 00586 00587 bool 00588 MySqlDb::dbNextRecord(const Table table, 00589 const resip::Data& key, 00590 resip::Data& data, 00591 bool forUpdate, // specifying to add SELECT ... FOR UPDATE so the rows are locked 00592 bool first) // return false if no more 00593 { 00594 if(first) 00595 { 00596 // free memory from previous search 00597 if (mResult[table]) 00598 { 00599 mysql_free_result(mResult[table]); 00600 mResult[table] = 0; 00601 } 00602 00603 Data command; 00604 { 00605 DataStream ds(command); 00606 ds << "SELECT value FROM " << tableName(table); 00607 if(!key.empty()) 00608 { 00609 Data escapedKey; 00610 // dbNextRecord is used to iterator through database tables that support duplication records 00611 // it is only appropriate for MySQL tables that contain the attr2 non-unique index (secondary key) 00612 ds << " WHERE attr2='" << escapeString(key, escapedKey) << "'"; 00613 } 00614 if(forUpdate) 00615 { 00616 ds << " FOR UPDATE"; 00617 } 00618 } 00619 00620 if(query(command, &mResult[table]) != 0) 00621 { 00622 return false; 00623 } 00624 00625 if (mResult[table] == 0) 00626 { 00627 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn)); 00628 return false; 00629 } 00630 } 00631 00632 if (mResult[table] == 0) 00633 { 00634 return false; 00635 } 00636 00637 MYSQL_ROW row = mysql_fetch_row(mResult[table]); 00638 if (!row) 00639 { 00640 mysql_free_result(mResult[table]); 00641 mResult[table] = 0; 00642 return false; 00643 } 00644 00645 data = Data(Data::Share, row[0], (Data::size_type)strlen(row[0])).base64decode(); 00646 00647 return true; 00648 } 00649 00650 bool 00651 MySqlDb::dbBeginTransaction(const Table table) 00652 { 00653 Data command("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"); 00654 if(query(command, 0) == 0) 00655 { 00656 command = "START TRANSACTION"; 00657 return query(command, 0) == 0; 00658 } 00659 return false; 00660 } 00661 00662 bool 00663 MySqlDb::dbCommitTransaction(const Table table) 00664 { 00665 Data command("COMMIT"); 00666 return query(command, 0) == 0; 00667 } 00668 00669 bool 00670 MySqlDb::dbRollbackTransaction(const Table table) 00671 { 00672 Data command("ROLLBACK"); 00673 return query(command, 0) == 0; 00674 } 00675 00676 static const char usersavp[] = "usersavp"; 00677 static const char routesavp[] = "routesavp"; 00678 static const char aclsavp[] = "aclsavp"; 00679 static const char configsavp[] = "configsavp"; 00680 static const char staticregsavp[] = "staticregsavp"; 00681 static const char filtersavp[] = "filtersavp"; 00682 static const char siloavp[] = "siloavp"; 00683 00684 const char* 00685 MySqlDb::tableName(Table table) const 00686 { 00687 switch (table) 00688 { 00689 case UserTable: 00690 assert(false); // usersavp is not used! 00691 return usersavp; 00692 case RouteTable: 00693 return routesavp; 00694 case AclTable: 00695 return aclsavp; 00696 case ConfigTable: 00697 return configsavp; 00698 case StaticRegTable: 00699 return staticregsavp; 00700 case FilterTable: 00701 return filtersavp; 00702 case SiloTable: 00703 return siloavp; 00704 default: 00705 assert(0); 00706 } 00707 return 0; 00708 } 00709 00710 void 00711 MySqlDb::userWhereClauseToDataStream(const Key& key, DataStream& ds) const 00712 { 00713 Data user; 00714 Data domain; 00715 getUserAndDomainFromKey(key, user, domain); 00716 ds << " WHERE user='" << user 00717 << "' AND domain='" << domain 00718 << "'"; 00719 } 00720 00721 void 00722 MySqlDb::getUserAndDomainFromKey(const Key& key, Data& user, Data& domain) const 00723 { 00724 ParseBuffer pb(key); 00725 const char* start = pb.position(); 00726 pb.skipToOneOf("@"); 00727 pb.data(user, start); 00728 const char* anchor = pb.skipChar(); 00729 pb.skipToEnd(); 00730 pb.data(domain, anchor); 00731 } 00732 00733 #endif // USE_MYSQL 00734 00735 /* ==================================================================== 00736 * The Vovida Software License, Version 1.0 00737 * 00738 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. 00739 * 00740 * Redistribution and use in source and binary forms, with or without 00741 * modification, are permitted provided that the following conditions 00742 * are met: 00743 * 00744 * 1. Redistributions of source code must retain the above copyright 00745 * notice, this list of conditions and the following disclaimer. 00746 * 00747 * 2. Redistributions in binary form must reproduce the above copyright 00748 * notice, this list of conditions and the following disclaimer in 00749 * the documentation and/or other materials provided with the 00750 * distribution. 00751 * 00752 * 3. The names "VOCAL", "Vovida Open Communication Application Library", 00753 * and "Vovida Open Communication Application Library (VOCAL)" must 00754 * not be used to endorse or promote products derived from this 00755 * software without prior written permission. For written 00756 * permission, please contact vocal@vovida.org. 00757 * 00758 * 4. Products derived from this software may not be called "VOCAL", nor 00759 * may "VOCAL" appear in their name, without prior written 00760 * permission of Vovida Networks, Inc. 00761 * 00762 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED 00763 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00764 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND 00765 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA 00766 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES 00767 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, 00768 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 00769 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 00770 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 00771 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00772 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 00773 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 00774 * DAMAGE. 00775 * 00776 * ==================================================================== 00777 */
1.7.5.1