reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
ReTurnConfig.cxx
Go to the documentation of this file.
1#include <fstream>
2#include <iostream>
3#include <string>
4#include <sstream>
5
6#ifndef WIN32
7#include <csignal>
8#endif
9
10#include <sys/stat.h>
11
12#include <asio/placeholders.hpp>
13#include <functional>
14#include "ReTurnConfig.hxx"
15
16#include "ReTurnSubsystem.hxx"
17#include "rutil/ParseBuffer.hxx"
18#include <rutil/Logger.hxx>
19#ifdef USE_SSL
20#include <openssl/ssl.h>
22#endif
23
24#define RESIPROCATE_SUBSYSTEM ReTurnSubsystem::RETURN
25
26#ifdef PACKAGE_VERSION
27#define SOFTWARE_STRING "reTURNServer " PACKAGE_VERSION " (RFC5389)"
28#else
29#define SOFTWARE_STRING "reTURNServer (RFC5389)"
30#endif
31
32using namespace std;
33using namespace resip;
34
35#ifdef BOOST_ASIO_HAS_STD_CHRONO
36using namespace std::chrono;
37#else
38#include <chrono>
39using namespace std::chrono;
40#endif
41
42namespace reTurn {
43
45 mSoftwareName(SOFTWARE_STRING),
46 mPadSoftwareName(true),
47 mTurnPort(3478),
48 mTlsTurnPort(5349),
49 mAltStunPort(0), // Note: The default is to disable RFC3489 binding support
50 mTurnAddress(asio::ip::make_address("0.0.0.0")),
51 mTurnV6Address(asio::ip::make_address("::0")),
52 mAltStunAddress(asio::ip::make_address("0.0.0.0")),
53 mAuthenticationRealm("reTurn"),
54 mUserDatabaseCheckInterval(60),
55 mNonceLifetime(3600), // 1 hour - at least 1 hours is recommended by the RFC
56 mAllocationPortRangeMin(49152), // must be even - This default range is the Dynamic and/or Private Port range - recommended by RFC
57 mAllocationPortRangeMax(65535), // must be odd
58 mDefaultAllocationLifetime(600), // 10 minutes
59 mMaxAllocationLifetime(3600), // 1 hour
60 mMaxAllocationsPerUser(0), // 0 - no max
61 mTlsServerCertificateFilename("server.pem"),
62 mTlsServerPrivateKeyFilename(""),
63 mTlsTempDhFilename("dh2048.pem"),
64 mTlsPrivateKeyPassword(""),
65 mTlsHandshakeTimeout(30),
66 mTcpConnectionIdleTimeout(60),
67 mMaxTcpConnectionsPerIp(0), // 0 - no max
68#ifdef USE_SSL
69 // The same defaults as repro (resip::BaseSecurity::OpenSSLCTXSetOptions)
70 mOpenSSLCTXSetOptions(SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION),
71#else
72 mOpenSSLCTXSetOptions(0),
73#endif
74 mOpenSSLCTXClearOptions(0),
75 mUsersDatabaseFilename(""),
76 mUserDatabaseHashedPasswords(false),
77 mDaemonize(false),
78 mPidFile(""),
79 mRunAsUser(""),
80 mRunAsGroup("")
81{
82}
83
84void ReTurnConfig::parseConfig(int argc, char** argv, const resip::Data& defaultConfigFilename)
85{
86 resip::ConfigParse::parseConfig(argc, argv, defaultConfigFilename);
87
89 mPadSoftwareName = getConfigBool("PadSoftwareName", true);
93 mTurnAddress = asio::ip::make_address(getConfigData("TurnAddress", "0.0.0.0").c_str());
94 mTurnV6Address = asio::ip::make_address(getConfigData("TurnV6Address", "::0").c_str());
95 mAltStunAddress = asio::ip::make_address(getConfigData("AltStunAddress", "0.0.0.0").c_str());
97 mUserDatabaseCheckInterval = getConfigUnsignedShort("UserDatabaseCheckInterval", 60);
111#ifdef USE_SSL
112 // As repro: a list of SSL_OP_* names replaces the default
113 for (const char* name : { "OpenSSLCTXSetOptions", "OpenSSLCTXClearOptions" })
114 {
115 std::set<Data> values;
116 if (getConfigValue(name, values))
117 {
118 long& options = (Data(name) == "OpenSSLCTXSetOptions") ? mOpenSSLCTXSetOptions : mOpenSSLCTXClearOptions;
119 options = 0;
120 for (const Data& value : values)
121 {
122 options |= parseOpenSSLCTXOption(value); // throws std::invalid_argument for an unknown name
123 }
124 }
125 }
126#endif
128 mDaemonize = getConfigBool("Daemonize", mDaemonize);
129 mPidFile = getConfigData("PidFile", mPidFile);
130 mRunAsUser = getConfigData("RunAsUser", mRunAsUser);
131 mRunAsGroup = getConfigData("RunAsGroup", mRunAsGroup);
132
134 {
135 // padding size to a multiple of 4, to help compatibility with older clients
136 while(mSoftwareName.size() % 4)
137 {
138 mSoftwareName += ' ';
139 }
140 }
141
142 // TODO: For ShortTermCredentials use mAuthenticationCredentials[username] = password;
143
144
145 // LongTermCredentials
146 mUsersDatabaseFilename = getConfigData("UserDatabaseFile", "");
148 {
149 throw ConfigParse::Exception("Missing user database option! Expected \"UserDatabaseFile = file location\".", __FILE__, __LINE__);
150 }
151
156
158}
159
163
164void
165ReTurnConfig::addUser(const resip::Data& username, const resip::Data& password, const resip::Data& realm)
166{
167 UserAuthData newUser(
169 UserAuthData::createFromHex(username, realm, password)
170 : UserAuthData::createFromPassword(username, realm, password)
171 );
172 mRealmUsersAuthenticaionCredentials[std::make_pair(username, realm)] = newUser.getHa1();
173 RealmUsers& realmUsers(mUsers[realm]);
174 realmUsers.insert(pair<resip::Data,UserAuthData>(username, newUser));
175}
176
177void
178ReTurnConfig::authParse(const resip::Data& accountDatabaseFilename)
179{
180 std::ifstream accountDatabaseFile(accountDatabaseFilename.c_str());
181 std::string sline;
182 int lineNbr = 0, userCount = 0;
183 if(!accountDatabaseFile)
184 {
185 throw ReTurnConfig::Exception("Error opening/reading user database file!", __FILE__, __LINE__);
186 }
187
188 mUsers.clear();
189 mRealmUsersAuthenticaionCredentials.clear();
190
191 while(std::getline(accountDatabaseFile, sline))
192 {
193 AccountState accountState;
194 Data username;
195 Data password;
196 Data realm;
197 Data state;
198 Data line(sline);
199 ParseBuffer pb(line);
200
201 lineNbr++;
202
203 // Jump over empty lines.
204 if(line.size() == 0)
205 {
206 continue;
207 }
208
209 pb.skipWhitespace();
210 if(!pb.eof() && *pb.position() == '#')
211 {
212 // Line is commented out, skip it
213 continue;
214 }
215
216 const char * anchor = pb.position();
217
218 pb.skipToOneOf(" :");
219
220 if (pb.eof())
221 {
222 ErrLog(<< "Missing or invalid credentials at line " << lineNbr);
223 continue;
224 }
225
226 pb.data(username, anchor);
227
228 pb.skipToChar(':');
229 if (!pb.eof())
230 {
231 pb.skipChar(':');
232 pb.skipWhitespace();
233 }
234
235 anchor = pb.position();
236 pb.skipToOneOf(" :");
237
238 if (pb.eof())
239 {
240 ErrLog(<< "Missing or invalid credentials at line " << lineNbr);
241 continue;
242 }
243
244 pb.data(password, anchor);
245
246 pb.skipToChar(':');
247 if (!pb.eof())
248 {
249 pb.skipChar(':');
250 pb.skipWhitespace();
251 }
252
253 anchor = pb.position();
254 pb.skipToOneOf(" :");
255
256 if (pb.eof())
257 {
258 ErrLog(<< "Missing or invalid credentials at line " << lineNbr);
259 continue;
260 }
261
262 pb.data(realm, anchor);
263
264 pb.skipToChar(':');
265 if (!pb.eof())
266 {
267 pb.skipChar(':');
268 pb.skipWhitespace();
269 }
270
271 anchor = pb.position();
272 // Include the CR: getline only strips the LF, so a file with CRLF line
273 // endings would otherwise leave a trailing CR on this last field.
274 pb.skipToOneOf(" \t\r\n");
275
276 pb.data(state, anchor);
277 state.lowercase();
278
279 if (state.size() != 0)
280 {
281 if(state == "authorized")
282 {
283 accountState = AUTHORIZED;
284 }
285 else if(state == "restricted")
286 {
287 accountState = RESTRICTED;
288 }
289 else if(state == "refused")
290 {
291 accountState = REFUSED;
292 }
293 else
294 {
295 ErrLog(<< "Invalid state value at line " << lineNbr << ", state= " << state);
296 continue;
297 }
298 }
299 else
300 {
301 ErrLog(<< "Missing state value at line " << lineNbr);
302 continue;
303 }
304
305 if(accountState != REFUSED)
306 {
307 try {
308 addUser(username, password, realm);
309 } catch (ConfigParse::Exception& ex) {
310 ErrLog(<< "Exception adding user: " << username << ", cause: " << ex << ", skipping record");
311 }
312 }
313 userCount++;
314 }
315
316 InfoLog(<< "Processed " << userCount << " user(s) from " << lineNbr << " line(s) in " << accountDatabaseFilename);
317 accountDatabaseFile.close();
318
319 if(mUsers.find(mAuthenticationRealm) == mUsers.end())
320 {
321 WarningLog(<<"AuthenticationRealm = " << mAuthenticationRealm << " but no users defined for this realm in " << accountDatabaseFilename);
322 }
323}
324
325void
326ReTurnConfig::printHelpText(int argc, char **argv)
327{
328 std::cerr << "Command line format is:" << std::endl;
329 std::cerr << " " << removePath(argv[0]) << " [<ConfigFilename>] [--<ConfigValueName>=<ConfigValue>] [--<ConfigValueName>=<ConfigValue>] ..." << std::endl;
330 std::cerr << "Sample Command lines:" << std::endl;
331 std::cerr << " " << removePath(argv[0]) << " reTurnServer.config --LogLevel=INFO" << std::endl;
332}
333
334// LongTermAuthentication
335bool
336ReTurnConfig::isUserNameValid(const resip::Data& username, const resip::Data& realm) const
337{
338 return getUser(username, realm).get() != NULL;
339}
340
341Data
342ReTurnConfig::getHa1ForUsername(const Data& username, const resip::Data& realm) const
343{
345 std::map<RealmUserPair, resip::Data>::const_iterator it = mRealmUsersAuthenticaionCredentials.find(std::make_pair(username, realm));
346 if(it != mRealmUsersAuthenticaionCredentials.end())
347 {
348 return it->second;
349 }
350 else
351 {
352 return Data::Empty;
353 }
354}
355
356std::unique_ptr<UserAuthData>
357ReTurnConfig::getUser(const resip::Data& userName, const resip::Data& realm) const
358{
360 std::unique_ptr<UserAuthData> ret;
361 std::map<resip::Data,RealmUsers>::const_iterator it = mUsers.find(realm);
362 if(it == mUsers.end())
363 return ret;
364
365 RealmUsers realmUsers = it->second;
366 RealmUsers::const_iterator it2 = realmUsers.find(userName);
367 if(it2 == realmUsers.end())
368 return ret;
369
370 return std::unique_ptr<UserAuthData>(new UserAuthData(it2->second));
371}
372
373bool ReTurnUserFileScanner::mHup = false;
374
375ReTurnUserFileScanner::ReTurnUserFileScanner(asio::io_context& ioService, ReTurnConfig& reTurnConfig)
376 : mLoadedTime(time(0)),
377 mReTurnConfig(reTurnConfig),
378 mLoopInterval(3),
379 mNextFileCheck(mLoadedTime + reTurnConfig.mUserDatabaseCheckInterval),
380 mTimer(ioService, seconds(mLoopInterval))
381{
382 mHup = false;
383#ifndef WIN32
384 // After all parsing is done, we can set up the signal handler
385 // For the moment, HUP will re-read the users file.
386 // It won't re-read the whole config
387 signal(SIGHUP, onSignal);
388#endif
389}
390
391void
393{
394 int timerInterval = mLoopInterval;
395
396 if(timerInterval > 0)
397 {
398 mTimer.expires_after(seconds(timerInterval));
399 mTimer.async_wait(std::bind(&ReTurnUserFileScanner::timeout, this, std::placeholders::_1));
400 }
401}
402
403bool
404ReTurnUserFileScanner::hasUserFileChanged()
405{
406 StackLog(<<"checking user database freshness");
407
408 time_t latestFileTS = 0;
409#ifdef WIN32
410 StackLog(<<"not yet implemented on Windows");
411#else
412 struct stat user_stat;
413 int ret = stat(mReTurnConfig.mUsersDatabaseFilename.c_str(), &user_stat);
414 if(ret == 0)
415 {
416 latestFileTS = user_stat.st_mtime;
417 }
418 else
419 {
420 ErrLog(<<"Call to stat failed, not checking " << mReTurnConfig.mUsersDatabaseFilename << " freshness, errno = " << errno);
421 }
422#endif
423
424 return latestFileTS > mLoadedTime;
425}
426
427void
428ReTurnUserFileScanner::timeout(const asio::error_code& e)
429{
430 bool mustReload = mHup;
431
432 if(!mustReload && mReTurnConfig.mUserDatabaseCheckInterval > 0)
433 {
434 time_t now = time(0);
435 if(now >= mNextFileCheck)
436 {
437 mustReload = hasUserFileChanged();
438 mNextFileCheck = now + mReTurnConfig.mUserDatabaseCheckInterval;
439 }
440 }
441
442 if(mustReload)
443 {
444 InfoLog(<<"change in user database detected, reloading...");
445 WriteLock lock(mReTurnConfig.mUserDataMutex);
446 try
447 {
448 mReTurnConfig.authParse(mReTurnConfig.mUsersDatabaseFilename);
449 InfoLog(<<"user database reload completed");
450 mLoadedTime = time(0);
451 mNextFileCheck = mLoadedTime + mReTurnConfig.mUserDatabaseCheckInterval;
452 }
453 catch (ReTurnConfig::Exception&)
454 {
455 WarningLog(<<"user reload failed, will check again in " << mReTurnConfig.mUserDatabaseCheckInterval << " second(s).");
456 mNextFileCheck = time(0) + mReTurnConfig.mUserDatabaseCheckInterval;
457 }
458 }
459
460 // clear any signal
461 mHup = false;
462
463 // set the timer again:
464 start();
465}
466
467void
468ReTurnUserFileScanner::onSignal(int signum)
469{
470#ifndef WIN32
471 if(signum == SIGHUP)
472 {
473 InfoLog(<<"HUP signal received, scheduling a users.txt reload");
474 mHup = true;
475 // also rotate the log file
476 Log::reset();
477 }
478 else
479 {
480 WarningLog(<<"received unexpected signal number " << signum);
481 }
482#endif
483}
484
485} // namespace
486
487
488/* ====================================================================
489
490 Copyright (c) 2008-2026, SIP Spectrum, Inc. https://www.sipspectrum.com
491 Copyright (c) 2007-2008, Plantronics, Inc.
492 All rights reserved.
493
494 Redistribution and use in source and binary forms, with or without
495 modification, are permitted provided that the following conditions are
496 met:
497
498 1. Redistributions of source code must retain the above copyright
499 notice, this list of conditions and the following disclaimer.
500
501 2. Redistributions in binary form must reproduce the above copyright
502 notice, this list of conditions and the following disclaimer in the
503 documentation and/or other materials provided with the distribution.
504
505 3. Neither the name of Plantronics nor the names of its contributors
506 may be used to endorse or promote products derived from this
507 software without specific prior written permission.
508
509 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
510 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
511 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
512 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
513 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
514 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
515 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
516 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
517 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
518 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
519 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
520
521 ==================================================================== */
#define StackLog(args_)
Definition Logger.hxx:125
#define InfoLog(args_)
Definition Logger.hxx:131
#define WarningLog(args_)
Definition Logger.hxx:134
#define ErrLog(args_)
Definition Logger.hxx:137
#define SOFTWARE_STRING
unsigned short mAllocationPortRangeMax
std::unique_ptr< UserAuthData > getUser(const resip::Data &userName, const resip::Data &realm) const
unsigned long mMaxTcpConnectionsPerIp
unsigned short mAltStunPort
asio::ip::address mAltStunAddress
unsigned long mTlsHandshakeTimeout
resip::Data mTlsTempDhFilename
bool isUserNameValid(const resip::Data &username, const resip::Data &realm) const
void authParse(const resip::Data &accountDatabaseFilename)
unsigned short mTlsTurnPort
resip::Data mSoftwareName
resip::Data mUsersDatabaseFilename
resip::Data mTlsPrivateKeyPassword
asio::ip::address mTurnAddress
void addUser(const resip::Data &username, const resip::Data &password, const resip::Data &realm)
unsigned short mAllocationPortRangeMin
resip::Data mAuthenticationRealm
unsigned long mDefaultAllocationLifetime
resip::Data mTlsServerCertificateFilename
resip::RWMutex mUserDataMutex
void printHelpText(int argc, char **argv)
unsigned long mMaxAllocationsPerUser
unsigned short mTurnPort
unsigned long mTcpConnectionIdleTimeout
virtual void parseConfig(int argc, char **argv, const resip::Data &defaultConfigFilename)
unsigned long mMaxAllocationLifetime
resip::Data getHa1ForUsername(const resip::Data &username, const resip::Data &realm) const
unsigned long mNonceLifetime
resip::Data mTlsServerPrivateKeyFilename
asio::ip::address mTurnV6Address
ReTurnUserFileScanner(asio::io_context &ioService, ReTurnConfig &reTurnConfig)
static UserAuthData createFromHex(const resip::Data &userName, const resip::Data &realm, const resip::Data &ha1Hex)
static UserAuthData createFromPassword(const resip::Data &userName, const resip::Data &realm, const resip::Data &password)
unsigned short getConfigUnsignedShort(const resip::Data &name, int defaultValue) const
bool AddBasePathIfRequired(Data &filename) const
virtual void parseConfig(int argc, char **argv, const resip::Data &defaultConfigFilename)
resip::Data removePath(const resip::Data &fileAndPath) const
resip::Data getConfigData(const resip::Data &name, const resip::Data &defaultValue, bool useDefaultIfEmpty=false) const
bool getConfigValue(const resip::Data &name, resip::Data &value) const
unsigned long getConfigUnsignedLong(const resip::Data &name, unsigned long defaultValue) const
bool getConfigBool(const resip::Data &name, bool defaultValue) const
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
static const Data Empty
Constant that represents a zero-length data.
Definition Data.hxx:882
const char * c_str() const
Returns a null-terminated string representing.
Definition Data.cxx:1097
Data & lowercase()
Converts this Data to lowercase.
Definition Data.cxx:1635
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
static void reset()
Frees logger stream.
Definition Log.cxx:957
Provides string-parsing functionality with protection from buffer overflow.
CurrentPosition skipWhitespace()
CurrentPosition skipToChar(char c)
CurrentPosition position() const
CurrentPosition skipToOneOf(const char *cs)
CurrentPosition skipChar()
void data(Data &data, const char *start) const
make the passed in data share memory with the buffer (uses Data::Share)
std::map< resip::Data, reTurn::UserAuthData > RealmUsers
dcm! – add UnusedChecking(_enum) below;
long parseOpenSSLCTXOption(const Data &optionName)
Converts an OpenSSL option name (eg.