reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
MySqlDb.cxx
Go to the documentation of this file.
1#include "rutil/ResipAssert.h"
2#include <fcntl.h>
3
4#ifdef HAVE_CONFIG_H
5#include "config.h"
6#endif
7
8#ifdef USE_MYSQL
9
10#ifdef WIN32
11#include <errmsg.h>
12#else
13#include <mysql/errmsg.h>
14#endif
15
16#include "rutil/Data.hxx"
17#include "rutil/DataStream.hxx"
18#include "rutil/Logger.hxx"
19#include "rutil/ParseBuffer.hxx"
20
21#include "repro/AbstractDb.hxx"
22#include "repro/MySqlDb.hxx"
23#include "repro/UserStore.hxx"
24
26
27
28using namespace resip;
29using namespace repro;
30using namespace std;
31
32#define RESIPROCATE_SUBSYSTEM Subsystem::REPRO
33
34extern "C"
35{
36 void mysqlThreadEnd(void*)
37 {
39 }
40}
41
42// This class helps ensure that each thread using the MySQL API's
43// initialize by calling mysql_thread_init before calling any mySQL functions
45{
46 public:
48 {
49 ThreadIf::tlsKeyCreate(mThreadStorage, mysqlThreadEnd);
50 }
52 {
53 ThreadIf::tlsKeyDelete(mThreadStorage);
54 }
55 void setInitialized()
56 {
57 ThreadIf::tlsSetValue(mThreadStorage, (void*) true);
58 }
59 bool isInitialized()
60 {
61 // Note: if value is not set yet then 0 (false) is returned
62 return ThreadIf::tlsGetValue(mThreadStorage) != 0;
63 }
64
65 private:
67};
69
70MySqlDb::MySqlDb(const resip::ConfigParse& config,
71 const Data& server,
72 const Data& user,
73 const Data& password,
74 const Data& databaseName,
75 unsigned int port,
78 mDBServer(server),
79 mDBUser(user),
80 mDBPassword(password),
81 mDBName(databaseName),
82 mDBPort(port),
83 mCustomUserAuthQuery(customUserAuthQuery),
84 mConn(0)
85{
86 InfoLog( << "Using MySQL DB with server=" << server << ", user=" << user << ", dbName=" << databaseName << ", port=" << port);
87
88 for (int i=0;i<MaxTable;i++)
89 {
90 mResult[i]=0;
91 }
92
93 mysql_library_init(0, 0, 0);
95 {
96 ErrLog( << "Repro uses MySQL from multiple threads - you MUST link with a thread safe version of the mySQL client library!");
97 }
98 else
99 {
100 connectToDatabase();
101 }
102}
103
104
105MySqlDb::~MySqlDb()
106{
107 disconnectFromDatabase();
108}
109
110void
111MySqlDb::initialize() const
112{
113 if(!g_MySQLInitializer.isInitialized())
114 {
115 g_MySQLInitializer.setInitialized();
117 }
118}
119
120void
121MySqlDb::disconnectFromDatabase() const
122{
123 if(mConn)
124 {
125 for (int i=0;i<MaxTable;i++)
126 {
127 if (mResult[i])
128 {
129 mysql_free_result(mResult[i]);
130 mResult[i]=0;
131 }
132 }
133
134 mysql_close(mConn);
135 mConn = 0;
136 setConnected(false);
137 }
138}
139
140int
141MySqlDb::connectToDatabase() const
142{
143 // Disconnect from database first (if required)
144 disconnectFromDatabase();
145
146 // Now try to connect
147 resip_assert(mConn == 0);
148 resip_assert(isConnected() == false);
149
150 mConn = mysql_init(0);
151 if(mConn == 0)
152 {
153 ErrLog( << "MySQL init failed: insufficient memory.");
154 return CR_OUT_OF_MEMORY;
155 }
156
158 mDBServer.c_str(), // hostname
159 mDBUser.c_str(), // user
160 mDBPassword.c_str(), // password
161 mDBName.c_str(), // DB
162 mDBPort, // port
163 0, // unix socket file
164 CLIENT_MULTI_RESULTS); // client flags (enable multiple results, since some custom stored procedures might require this)
165
166 if (ret == 0)
167 {
168 int rc = mysql_errno(mConn);
169 ErrLog( << "MySQL connect failed: error=" << rc << ": " << mysql_error(mConn));
170 mysql_close(mConn);
171 mConn = 0;
172 setConnected(false);
173 return rc;
174 }
175 else
176 {
177 setConnected(true);
178 return 0;
179 }
180}
181
182int
183MySqlDb::query(const Data& queryCommand, MYSQL_RES** result) const
184{
185 int rc = 0;
186
187 initialize();
188
189 DebugLog( << "MySqlDb::query: executing query: " << queryCommand);
190
191 Lock lock(mMutex);
192 if(mConn == 0 || !isConnected())
193 {
194 rc = connectToDatabase();
195 }
196 if(rc == 0)
197 {
198 resip_assert(mConn!=0);
199 resip_assert(isConnected());
200 rc = mysql_query(mConn,queryCommand.c_str());
201 if(rc != 0)
202 {
203 rc = mysql_errno(mConn);
204 if(rc == CR_SERVER_GONE_ERROR ||
206 {
207 // First failure is a connection error - try to re-connect and then try again
208 rc = connectToDatabase();
209 if(rc == 0)
210 {
211 // OK - we reconnected - try query again
212 rc = mysql_query(mConn,queryCommand.c_str());
213 if( rc != 0)
214 {
215 ErrLog( << "MySQL query failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
216 }
217 }
218 }
219 else
220 {
221 ErrLog( << "MySQL query failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
222 }
223 }
224 }
225
226 // Now store result - if pointer to result pointer was supplied and no errors
227 if(rc == 0 && result)
228 {
229 *result = mysql_store_result(mConn);
230 if(*result == 0)
231 {
232 rc = mysql_errno(mConn);
233 if(rc != 0)
234 {
235 ErrLog( << "MySQL store result failed: error=" << rc << ": " << mysql_error(mConn));
236 }
237 }
238 }
239
240 if(rc != 0)
241 {
242 ErrLog( << " SQL Command was: " << queryCommand) ;
243 }
244 return rc;
245}
246
247int
248MySqlDb::query(const Data& queryCommand) const
249{
250 return query(queryCommand, 0);
251}
252
253int
254MySqlDb::singleResultQuery(const Data& queryCommand, std::vector<Data>& fields) const
255{
256 StackLog(<<"executing query: " << queryCommand);
257 MYSQL_RES* result=0;
258 int rc = query(queryCommand, &result);
259
260 if(rc == 0)
261 {
262 if(result == 0)
263 {
264 return rc;
265 }
266
268 if(row)
269 {
270 for(unsigned int i = 0; i < result->field_count; i++)
271 {
272 fields.push_back(Data(row[i]));
273 }
274 }
275 else
276 {
277 rc = mysql_errno(mConn);
278 if(rc != 0)
279 {
280 ErrLog( << "MySQL fetch row failed: error=" << rc << ": " << mysql_error(mConn));
281 }
282 else
283 {
284 DebugLog(<<"singleResultQuery: no rows returned by query");
285 }
286 }
288 }
289 return rc;
290}
291
293MySqlDb::escapeString(const resip::Data& str, resip::Data& escapedStr) const
294{
295 escapedStr.truncate2(mysql_real_escape_string(mConn, (char*)escapedStr.getBuf(str.size()*2+1), str.c_str(), str.size()));
296 return escapedStr;
297}
298
299bool
300MySqlDb::addUser(const AbstractDb::Key& key, const AbstractDb::UserRecord& rec)
301{
302 Data command;
303 {
304 DataStream ds(command);
305 ds << "INSERT INTO " << tableName(UserTable) << " (user, domain, realm, passwordHash, passwordHashAlt, name, email, forwardAddress)"
306 << " VALUES('"
307 << rec.user << "', '"
308 << rec.domain << "', '"
309 << rec.realm << "', '"
310 << rec.passwordHash << "', '"
311 << rec.passwordHashAlt << "', '"
312 << rec.name << "', '"
313 << rec.email << "', '"
314 << rec.forwardAddress << "')"
315 << " ON DUPLICATE KEY UPDATE"
316 << " user='" << rec.user
317 << "', domain='" << rec.domain
318 << "', realm='" << rec.realm
319 << "', passwordHash='" << rec.passwordHash
320 << "', passwordHashAlt='" << rec.passwordHashAlt
321 << "', name='" << rec.name
322 << "', email='" << rec.email
323 << "', forwardAddress='" << rec.forwardAddress
324 << "'";
325 }
326 return query(command, 0) == 0;
327}
328
329
331MySqlDb::getUser( const AbstractDb::Key& key ) const
332{
334
335 Data command;
336 {
337 DataStream ds(command);
338 ds << "SELECT user, domain, realm, passwordHash, passwordHashAlt, name, email, forwardAddress FROM " << tableName(UserTable) << " ";
339 userWhereClauseToDataStream(key, ds);
340 }
341
342 MYSQL_RES* result=0;
343 if(query(command, &result) != 0)
344 {
345 return ret;
346 }
347
348 if (result==0)
349 {
350 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
351 return ret;
352 }
353
355 if (row)
356 {
357 int col = 0;
358 ret.user = Data(row[col++]);
359 ret.domain = Data(row[col++]);
360 ret.realm = Data(row[col++]);
361 ret.passwordHash = Data(row[col++]);
362 ret.passwordHashAlt = Data(row[col++]);
363 ret.name = Data(row[col++]);
364 ret.email = Data(row[col++]);
365 ret.forwardAddress = Data(row[col++]);
366 }
367
369
370 return ret;
371}
372
373
375MySqlDb::getUserAuthInfo( const AbstractDb::Key& key ) const
376{
377 std::vector<Data> ret;
378
379 Data command;
380 {
381 DataStream ds(command);
382 Data user;
383 Data domain;
384 UserStore::getUserAndDomainFromKey(key, user, domain);
385 ds << "SELECT passwordHash FROM " << tableName(UserTable) << " WHERE user = '" << user << "' AND domain = '" << domain << "' ";
386
387 // Note: domain is empty when querying for HTTP admin user - for this special user,
388 // we will only check the repro db, by not adding the UNION statement below
389 if(!mCustomUserAuthQuery.empty() && !domain.empty())
390 {
391 ds << " UNION " << mCustomUserAuthQuery;
392 ds.flush();
393 command.replace("$user", user);
394 command.replace("$domain", domain);
395 }
396 }
397
398 if(singleResultQuery(command, ret) != 0 || ret.size() == 0)
399 {
400 return Data::Empty;
401 }
402
403 DebugLog( << "Auth password is " << ret.front());
404
405 return ret.front();
406}
407
408
410MySqlDb::firstUserKey()
411{
412 // free memory from previous search
413 if (mResult[UserTable])
414 {
415 mysql_free_result(mResult[UserTable]);
416 mResult[UserTable] = 0;
417 }
418
419 Data command;
420 {
421 DataStream ds(command);
422 ds << "SELECT user, domain FROM " << tableName(UserTable);
423 }
424
425 if(query(command, &mResult[UserTable]) != 0)
426 {
427 return Data::Empty;
428 }
429
430 if(mResult[UserTable] == 0)
431 {
432 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
433 return Data::Empty;
434 }
435
436 return nextUserKey();
437}
438
439
441MySqlDb::nextUserKey()
442{
443 if(mResult[UserTable] == 0)
444 {
445 return Data::Empty;
446 }
447
448 MYSQL_ROW row = mysql_fetch_row(mResult[UserTable]);
449 if (!row)
450 {
451 mysql_free_result(mResult[UserTable]);
452 mResult[UserTable] = 0;
453 return Data::Empty;
454 }
455 Data user(row[0]);
456 Data domain(row[1]);
457
458 return UserStore::buildKey(user, domain);
459}
460
461
462bool
463MySqlDb::addTlsPeerIdentity(const AbstractDb::Key& key, const AbstractDb::TlsPeerIdentityRecord& rec)
464{
465 Data command;
466 {
467 DataStream ds(command);
468 ds << "INSERT INTO " << tableName(TlsPeerIdentityTable) << " (peerName, tlsPeerIdentity)"
469 << " VALUES('"
470 << rec.peerName << "', '"
471 << rec.authorizedIdentity << "')"
472 << " ON DUPLICATE KEY UPDATE"
473 << " peerName='" << rec.peerName
474 << "', authorizedIdentity ='" << rec.authorizedIdentity
475 << "'";
476 }
477 return query(command, 0) == 0;
478}
479
480
482MySqlDb::getTlsPeerIdentity( const AbstractDb::Key& key ) const
483{
485
486 Data command;
487 {
488 DataStream ds(command);
489 ds << "SELECT peerName, authorizedIdentity FROM " << tableName(TlsPeerIdentityTable) << " ";
490 tlsPeerIdentityWhereClauseToDataStream(key, ds);
491 }
492
493 MYSQL_RES* result=0;
494 if(query(command, &result) != 0)
495 {
496 return ret;
497 }
498
499 if (result==0)
500 {
501 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
502 return ret;
503 }
504
506 if (row)
507 {
508 int col = 0;
509 ret.peerName = Data(row[col++]);
510 ret.authorizedIdentity = Data(row[col++]);
511 }
512
514
515 return ret;
516}
517
518
520MySqlDb::firstTlsPeerIdentityKey()
521{
522 // free memory from previous search
523 if (mResult[TlsPeerIdentityTable])
524 {
525 mysql_free_result(mResult[TlsPeerIdentityTable]);
526 mResult[TlsPeerIdentityTable] = 0;
527 }
528
529 Data command;
530 {
531 DataStream ds(command);
532 ds << "SELECT peerName, authorizedIdentity FROM " << tableName(TlsPeerIdentityTable);
533 }
534
535 if(query(command, &mResult[TlsPeerIdentityTable]) != 0)
536 {
537 return Data::Empty;
538 }
539
540 if(mResult[TlsPeerIdentityTable] == 0)
541 {
542 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
543 return Data::Empty;
544 }
545
546 return nextTlsPeerIdentityKey();
547}
548
549
551MySqlDb::nextTlsPeerIdentityKey()
552{
553 if(mResult[TlsPeerIdentityTable] == 0)
554 {
555 return Data::Empty;
556 }
557
558 MYSQL_ROW row = mysql_fetch_row(mResult[TlsPeerIdentityTable]);
559 if (!row)
560 {
561 mysql_free_result(mResult[TlsPeerIdentityTable]);
562 mResult[TlsPeerIdentityTable] = 0;
563 return Data::Empty;
564 }
565 Data peerName(row[0]);
566 Data authorizedIdentity(row[1]);
567
568 return TlsPeerIdentityStore::buildKey(peerName, authorizedIdentity);
569}
570
571
572bool
573MySqlDb::dbWriteRecord(const Table table,
574 const resip::Data& pKey,
575 const resip::Data& pData)
576{
577 Data command;
578
579 // Check if there is a secondary key or not and get it's value
580 char* secondaryKey;
581 unsigned int secondaryKeyLen;
583 if(AbstractDb::getSecondaryKey(table, pKey, pData, (void**)&secondaryKey, &secondaryKeyLen) == 0)
584 {
586 Data sKey(Data::Share, secondaryKey, secondaryKeyLen);
587 DataStream ds(command);
588 ds << "REPLACE INTO " << tableName(table)
589 << " SET attr='" << escapeString(pKey, escapedKey)
590 << "', attr2='" << escapeString(sKey, escapedSKey)
591 << "', value='" << pData.base64encode()
592 << "'";
593 }
594 else
595 {
596 DataStream ds(command);
597 ds << "REPLACE INTO " << tableName(table)
598 << " SET attr='" << escapeString(pKey, escapedKey)
599 << "', value='" << pData.base64encode()
600 << "'";
601 }
602
603 return query(command, 0) == 0;
604}
605
606bool
607MySqlDb::dbReadRecord(const Table table,
608 const resip::Data& pKey,
609 resip::Data& pData) const
610{
611 Data command;
613 {
614 DataStream ds(command);
615 ds << "SELECT value FROM " << tableName(table)
616 << " WHERE attr='" << escapeString(pKey, escapedKey)
617 << "'";
618 }
619
620 MYSQL_RES* result = 0;
621 if(query(command, &result) != 0)
622 {
623 return false;
624 }
625
626 if (result == 0)
627 {
628 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
629 return false;
630 }
631 else
632 {
633 bool success = false;
635 if(row)
636 {
637 pData = Data(Data::Share, row[0], (Data::size_type)strlen(row[0])).base64decode();
638 success = true;
639 }
641 return success;
642 }
643}
644
645
647MySqlDb::dbNextKey(const Table table, bool first)
648{
649 if(first)
650 {
651 // free memory from previous search
652 if (mResult[table])
653 {
654 mysql_free_result(mResult[table]);
655 mResult[table] = 0;
656 }
657
658 Data command;
659 {
660 DataStream ds(command);
661 ds << "SELECT attr FROM " << tableName(table);
662 }
663
664 if(query(command, &mResult[table]) != 0)
665 {
666 return Data::Empty;
667 }
668
669 if (mResult[table] == 0)
670 {
671 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
672 return Data::Empty;
673 }
674 }
675 else
676 {
677 if (mResult[table] == 0)
678 {
679 return Data::Empty;
680 }
681 }
682
684 if (!row)
685 {
686 mysql_free_result(mResult[table]);
687 mResult[table] = 0;
688 return Data::Empty;
689 }
690
691 return Data(row[0]);
692}
693
694
695bool
696MySqlDb::dbNextRecord(const Table table,
697 const resip::Data& key,
698 resip::Data& data,
699 bool forUpdate, // specifying to add SELECT ... FOR UPDATE so the rows are locked
700 bool first) // return false if no more
701{
702 if(first)
703 {
704 // free memory from previous search
705 if (mResult[table])
706 {
707 mysql_free_result(mResult[table]);
708 mResult[table] = 0;
709 }
710
711 Data command;
712 {
713 DataStream ds(command);
714 ds << "SELECT value FROM " << tableName(table);
715 if(!key.empty())
716 {
718 // dbNextRecord is used to iterator through database tables that support duplication records
719 // it is only appropriate for MySQL tables that contain the attr2 non-unique index (secondary key)
720 ds << " WHERE attr2='" << escapeString(key, escapedKey) << "'";
721 }
722 if(forUpdate)
723 {
724 ds << " FOR UPDATE";
725 }
726 }
727
728 if(query(command, &mResult[table]) != 0)
729 {
730 return false;
731 }
732
733 if (mResult[table] == 0)
734 {
735 ErrLog( << "MySQL store result failed: error=" << mysql_errno(mConn) << ": " << mysql_error(mConn));
736 return false;
737 }
738 }
739
740 if (mResult[table] == 0)
741 {
742 return false;
743 }
744
746 if (!row)
747 {
748 mysql_free_result(mResult[table]);
749 mResult[table] = 0;
750 return false;
751 }
752
753 data = Data(Data::Share, row[0], (Data::size_type)strlen(row[0])).base64decode();
754
755 return true;
756}
757
758bool
759MySqlDb::dbBeginTransaction(const Table table)
760{
761 Data command("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ");
762 if(query(command, 0) == 0)
763 {
764 command = "START TRANSACTION";
765 return query(command, 0) == 0;
766 }
767 return false;
768}
769
770void
771MySqlDb::userWhereClauseToDataStream(const Key& key, DataStream& ds) const
772{
773 Data user;
774 Data domain;
775 UserStore::getUserAndDomainFromKey(key, user, domain);
776 ds << " WHERE user='" << user
777 << "' AND domain='" << domain
778 << "'";
779}
780
781void
782MySqlDb::tlsPeerIdentityWhereClauseToDataStream(const Key& key, DataStream& ds) const
783{
784 Data peerName;
785 Data authorizedIdentity;
786 TlsPeerIdentityStore::getTlsPeerIdentityFromKey(key, peerName, authorizedIdentity);
787 ds << " WHERE peerName='" << peerName
788 << "' AND authorizedIdentity='" << authorizedIdentity
789 << "'";
790}
791
792#endif // USE_MYSQL
793
794/* ====================================================================
795 * The Vovida Software License, Version 1.0
796 *
797 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
798 *
799 * Redistribution and use in source and binary forms, with or without
800 * modification, are permitted provided that the following conditions
801 * are met:
802 *
803 * 1. Redistributions of source code must retain the above copyright
804 * notice, this list of conditions and the following disclaimer.
805 *
806 * 2. Redistributions in binary form must reproduce the above copyright
807 * notice, this list of conditions and the following disclaimer in
808 * the documentation and/or other materials provided with the
809 * distribution.
810 *
811 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
812 * and "Vovida Open Communication Application Library (VOCAL)" must
813 * not be used to endorse or promote products derived from this
814 * software without prior written permission. For written
815 * permission, please contact vocal@vovida.org.
816 *
817 * 4. Products derived from this software may not be called "VOCAL", nor
818 * may "VOCAL" appear in their name, without prior written
819 * permission of Vovida Networks, Inc.
820 *
821 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
822 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
823 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
824 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
825 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
826 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
827 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
828 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
829 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
830 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
831 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
832 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
833 * DAMAGE.
834 *
835 * ====================================================================
836 */
#define DebugLog(args_)
Definition Logger.hxx:128
#define StackLog(args_)
Definition Logger.hxx:125
#define InfoLog(args_)
Definition Logger.hxx:131
#define ErrLog(args_)
Definition Logger.hxx:137
#define resip_assert(x)
This file needs to work for both C and C++ code.
An iostream that operates on an existing Data.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
Data base64decode() const
Performs RFC 3548 Base 64 decoding of the contents of this data.
Definition Data.cxx:2607
int replace(const Data &match, const Data &target, int max=INT_MAX)
Replaces up to max occurrences of the bytes match with target.
Definition Data.cxx:1964
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
size_t size_type
Definition Data.hxx:94
Data & truncate2(size_type len)
Shortens the size of this Data so length is at most of {len}.
Definition Data.cxx:940
pthread_key_t TlsKey
Definition ThreadIf.hxx:85
This Class is used to implement a primitive form of RPC using loose XML formatting.
dcm! – add UnusedChecking(_enum) below;
std::unique_lock< Mutex > Lock
A convenience class to lock a Lockable (such as a Mutex) on object creation, and unlock on destructio...
Definition Lock.hxx:28
void list()
Definition userAdmin.cxx:66