├── .gitignore ├── .travis.yml ├── AUTHORS ├── AppSubsystem.cxx ├── AppSubsystem.hxx ├── B2BCallManager.cxx ├── B2BCallManager.hxx ├── CDRFile.cxx ├── CDRFile.hxx ├── COPYING ├── ChangeLog ├── Makefile.am ├── MyConversationManager.cxx ├── MyConversationManager.hxx ├── MyMessageDecorator.cxx ├── MyMessageDecorator.hxx ├── MyUserAgent.cxx ├── MyUserAgent.hxx ├── NEWS ├── README ├── RegistrationForwarder.cxx ├── RegistrationForwarder.hxx ├── SubscriptionForwarder.cxx ├── SubscriptionForwarder.hxx ├── build ├── resip-ld ├── resip-src └── travis │ ├── bootstrap │ ├── build │ └── configure ├── configure.ac ├── playback_prompt.wav ├── reConServer.8 ├── reConServer.config ├── reConServer.cxx ├── reConServer.hxx ├── reConServerConfig.cxx ├── reConServerConfig.hxx ├── reConServer_readme.txt ├── reconserver.init ├── reconserver.service └── record_prompt.wav /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.o 3 | *.lo 4 | *.la 5 | /Makefile 6 | /Makefile.in 7 | /aclocal.m4 8 | /build-aux 9 | /config.log 10 | /config.status 11 | /configure 12 | /autom4te.cache 13 | /INSTALL 14 | 15 | /reConServer 16 | /playback_prompt.h 17 | /record_prompt.h 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: xenial 3 | before_install: 4 | - sudo apt-get update -qq 5 | - sudo apt-get install -qq autotools-dev 6 | - sudo add-apt-repository -y "deb http://http.us.debian.org/debian/ jessie main" 7 | - sudo add-apt-repository -y "deb http://http.us.debian.org/debian/ jessie-backports main" 8 | - sudo apt-get update -qq 9 | - sudo apt-get install -qq --force-yes -o Dpkg::Options::="--force-overwrite" -o Dpkg::Options::="--force-confnew" -t jessie-backports librecon-1.10-dev 10 | compiler: 11 | - gcc 12 | - clang 13 | script: 14 | - ./build/travis/bootstrap 15 | - ./build/travis/configure 16 | - ./build/travis/build 17 | 18 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Scott Godin 2 | Daniel Pocock 3 | Catalin Constantin Usurelu 4 | -------------------------------------------------------------------------------- /AppSubsystem.cxx: -------------------------------------------------------------------------------- 1 | #include "AppSubsystem.hxx" 2 | 3 | AppSubsystem AppSubsystem::RECONSERVER("RECONSERVER"); 4 | 5 | /* ==================================================================== 6 | * 7 | * Copyright 2017 Daniel Pocock http://danielpocock.com All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions 11 | * are met: 12 | * 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 16 | * 2. Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in 18 | * the documentation and/or other materials provided with the 19 | * distribution. 20 | * 21 | * 3. Neither the name of the author(s) nor the names of any contributors 22 | * may be used to endorse or promote products derived from this software 23 | * without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 26 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 29 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 | * SUCH DAMAGE. 36 | * 37 | * 38 | */ 39 | -------------------------------------------------------------------------------- /AppSubsystem.hxx: -------------------------------------------------------------------------------- 1 | #if !defined(AppSubsystem_hxx) 2 | #define AppSubsystem_hxx 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | This class is used in the logging subsystem to identify 9 | logging messages generated from the reConServer. 10 | */ 11 | 12 | class AppSubsystem : public resip::Subsystem 13 | { 14 | public: 15 | // Add new systems below 16 | static AppSubsystem RECONSERVER; 17 | 18 | private: 19 | explicit AppSubsystem(const char* rhs) : resip::Subsystem(rhs) {}; 20 | explicit AppSubsystem(const resip::Data& rhs); 21 | AppSubsystem& operator=(const resip::Data& rhs); 22 | }; 23 | 24 | #endif 25 | 26 | /* ==================================================================== 27 | * 28 | * Copyright 2017 Daniel Pocock http://danielpocock.com All rights reserved. 29 | * 30 | * Redistribution and use in source and binary forms, with or without 31 | * modification, are permitted provided that the following conditions 32 | * are met: 33 | * 34 | * 1. Redistributions of source code must retain the above copyright 35 | * notice, this list of conditions and the following disclaimer. 36 | * 37 | * 2. Redistributions in binary form must reproduce the above copyright 38 | * notice, this list of conditions and the following disclaimer in 39 | * the documentation and/or other materials provided with the 40 | * distribution. 41 | * 42 | * 3. Neither the name of the author(s) nor the names of any contributors 43 | * may be used to endorse or promote products derived from this software 44 | * without specific prior written permission. 45 | * 46 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 47 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 48 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 49 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 50 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 51 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 52 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 53 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 54 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 55 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 56 | * SUCH DAMAGE. 57 | * 58 | * 59 | */ 60 | 61 | -------------------------------------------------------------------------------- /B2BCallManager.cxx: -------------------------------------------------------------------------------- 1 | 2 | #include "B2BCallManager.hxx" 3 | 4 | #ifdef HAVE_CONFIG_H 5 | #include "config.h" 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "MyUserAgent.hxx" 17 | 18 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 19 | 20 | using namespace resip; 21 | using namespace recon; 22 | using namespace reconserver; 23 | 24 | B2BCall::B2BCall(const recon::ConversationHandle& conv, const recon::ParticipantHandle& a, const recon::ParticipantHandle b, const resip::SipMessage& msg, const resip::Data& originZone, const resip::Data& destinationZone, const resip::Data& b2bCallID) 25 | : mConversation(conv), 26 | mPartA(a), 27 | mPartB(b), 28 | mOriginZone(originZone), 29 | mDestinationZone(destinationZone), 30 | mB2BCallID(b2bCallID), 31 | mCaller(msg.header(h_From).uri().getAor()), 32 | mCallee(msg.header(h_RequestLine).uri().getAor()), 33 | mResponseCode(-1), 34 | mStart(ResipClock::getTimeMs()), 35 | mConnect(0), 36 | mFinish(0) 37 | { 38 | } 39 | 40 | B2BCallManager::B2BCallManager(MediaInterfaceMode mediaInterfaceMode, int defaultSampleRate, int maxSampleRate, ReConServerConfig& config, resip::SharedPtr b2bCallLogger) 41 | : MyConversationManager(false, mediaInterfaceMode, defaultSampleRate, maxSampleRate, false), 42 | mB2BCallLogger(b2bCallLogger) 43 | { 44 | config.getConfigValue("B2BUAInternalHosts", mInternalHosts); 45 | config.getConfigValue("B2BUAInternalTLSNames", mInternalTLSNames); 46 | mInternalAllPrivate = config.getConfigBool("B2BUAInternalAllPrivate", false); 47 | 48 | if(mInternalHosts.empty() && mInternalTLSNames.empty() && !mInternalAllPrivate) 49 | { 50 | WarningLog(<<"None of the options B2BUAInternalHosts, B2BUAInternalTLSNames or B2BUAInternalAllPrivate specified"); 51 | } 52 | 53 | config.getConfigValue("B2BUAInternalMediaAddress", mInternalMediaAddress); 54 | if(mInternalMediaAddress.empty()) 55 | { 56 | WarningLog(<<"B2BUAInternalMediaAddress not specified, using same media address for internal and external zones"); 57 | } 58 | 59 | std::set replicatedHeaderNames; 60 | if(config.getConfigValue("B2BUAReplicateHeaders", replicatedHeaderNames)) 61 | { 62 | std::set::const_iterator it = replicatedHeaderNames.begin(); 63 | for( ; it != replicatedHeaderNames.end(); it++) 64 | { 65 | const resip::Data& headerName(*it); 66 | resip::Headers::Type hType = resip::Headers::getType(headerName.data(), (int)headerName.size()); 67 | if(hType == resip::Headers::UNKNOWN) 68 | { 69 | mReplicatedHeaders.push_back(headerName.c_str()); 70 | InfoLog(<<"Will replicate header '"<participantA()) 110 | { 111 | target = call->participantB(); 112 | } 113 | else if(partHandle == call->participantB()) 114 | { 115 | target = call->participantA(); 116 | } 117 | Data tone(Data("tone:") + buttons[dtmf] + Data(";duration=") + Data(duration) + Data(";participant-only=") + Data(target)); 118 | Uri _tone(tone); 119 | StackLog(<< "sending tone to conversation: " << _tone); 120 | ConversationManager::createMediaResourceParticipant(call->conversation(), _tone); 121 | } 122 | else 123 | { 124 | WarningLog(<< "Participant " << partHandle << " sent a DTMF signal, not known in any existing call"); 125 | } 126 | } 127 | 128 | void 129 | B2BCallManager::onIncomingParticipant(ParticipantHandle partHandleA, const SipMessage& msg, bool autoAnswer, ConversationProfile& conversationProfile) 130 | { 131 | InfoLog(<< "onIncomingParticipant: handle=" << partHandleA << "auto=" << autoAnswer << " msg=" << msg.brief()); 132 | mRemoteParticipantHandles.push_back(partHandleA); 133 | // Create a new conversation for each new participant 134 | ConversationHandle conv = createConversation(); 135 | addParticipant(conv, partHandleA); 136 | const Uri& reqUri = msg.header(h_RequestLine).uri(); 137 | SharedPtr profile; 138 | bool internalSource = isSourceInternal(msg); 139 | Data originZoneName; 140 | Data destinationZoneName; 141 | if(internalSource) 142 | { 143 | originZoneName = "internal"; 144 | destinationZoneName = "external"; 145 | DebugLog(<<"INVITE request from zone: internal"); 146 | Uri uri(msg.header(h_RequestLine).uri()); 147 | uri.param(p_lr); 148 | NameAddrs route; 149 | route = msg.header(h_Routes); 150 | route.pop_front(); // remove ourselves 151 | MyUserAgent *ua = dynamic_cast(getUserAgent()); 152 | resip_assert(ua); 153 | SharedPtr externalProfile = ua->getDefaultOutgoingConversationProfile(); 154 | profile.reset(new ConversationProfile(*externalProfile.get())); 155 | profile->setServiceRoute(route); 156 | } 157 | else 158 | { 159 | DebugLog(<<"INVITE request from zone: external"); 160 | originZoneName = "external"; 161 | destinationZoneName = "internal"; 162 | SharedPtr internalProfile = getInternalConversationProfile(); 163 | profile.reset(new ConversationProfile(*internalProfile.get())); 164 | // Look up the user in mUsers 165 | const Data& callerUri = msg.header(h_From).uri().getAor(); 166 | // If found in mUsers, put the credentials into the profile 167 | if(mUsers.find(callerUri) != mUsers.end()) 168 | { 169 | const Data& callerUsername = msg.header(h_From).uri().user(); 170 | const Data& callerRealm = msg.header(h_From).uri().host(); 171 | DebugLog(<<"found credential for authenticating " << callerUri << " in realm " << callerRealm << " and added it to user profile"); 172 | profile->clearDigestCredentials(); 173 | profile->setDigestCredential(callerRealm, callerUsername, mUsers.find(callerUri)->second.mPassword); 174 | } 175 | else 176 | { 177 | DebugLog(<<"didn't find individual credential for authenticating " << callerUri); 178 | } 179 | } 180 | static ExtensionHeader h_X_CID("X-CID"); 181 | std::multimap extraHeaders; 182 | Data b2bCallID; 183 | if(msg.exists(h_X_CID) && internalSource) 184 | { 185 | const ParserContainer& pc = msg.header(h_X_CID); 186 | ParserContainer::const_iterator v = pc.begin(); 187 | for( ; v != pc.end(); v++) 188 | { 189 | b2bCallID = v->value(); 190 | extraHeaders.insert(std::pair(h_X_CID.getName(), b2bCallID)); 191 | } 192 | } 193 | else 194 | { 195 | // no correlation header exists in incoming message, copy A-leg Call-ID header to B-leg h_X_CID 196 | b2bCallID = msg.header(h_CallId).value(); 197 | extraHeaders.insert(std::pair(h_X_CID.getName(), b2bCallID)); 198 | } 199 | std::vector::const_iterator it = mReplicatedHeaders.begin(); 200 | for( ; it != mReplicatedHeaders.end(); it++) 201 | { 202 | const ExtensionHeader h(*it); 203 | if(msg.exists(h)) 204 | { 205 | // Replicate the header and value into the outgoing INVITE 206 | const ParserContainer& pc = msg.header(h); 207 | ParserContainer::const_iterator v = pc.begin(); 208 | for( ; v != pc.end(); v++) 209 | { 210 | extraHeaders.insert(std::pair(h.getName(), v->value())); 211 | } 212 | } 213 | } 214 | NameAddr outgoingCaller = msg.header(h_From); 215 | profile->setDefaultFrom(outgoingCaller); 216 | SharedPtr _profile(profile); 217 | ParticipantHandle partHandleB = ConversationManager::createRemoteParticipant(conv, NameAddr(reqUri), ForkSelectAutomatic, _profile, extraHeaders); 218 | SharedPtr call(new B2BCall(conv, partHandleA, partHandleB, msg, originZoneName, destinationZoneName, b2bCallID)); 219 | mCallsByConversation[call->conversation()] = call; 220 | mCallsByParticipant[call->participantA()] = call; 221 | mCallsByParticipant[call->participantB()] = call; 222 | } 223 | 224 | void 225 | B2BCallManager::onParticipantTerminated(ParticipantHandle partHandle, unsigned int statusCode) 226 | { 227 | InfoLog(<< "onParticipantTerminated: handle=" << partHandle); 228 | if(mCallsByParticipant.find(partHandle) != mCallsByParticipant.end()) 229 | { 230 | SharedPtr call = mCallsByParticipant[partHandle]; 231 | if(partHandle == call->participantB()) 232 | { 233 | rejectParticipant(call->participantA(), statusCode); 234 | } 235 | else 236 | { 237 | rejectParticipant(call->participantB(), statusCode); 238 | } 239 | destroyConversation(call->conversation()); 240 | mCallsByParticipant.erase(call->participantA()); 241 | mCallsByParticipant.erase(call->participantB()); 242 | mCallsByConversation.erase(call->conversation()); 243 | call->onFinish(statusCode); 244 | if(mB2BCallLogger.get()) 245 | { 246 | mB2BCallLogger->log(call); 247 | } 248 | } 249 | else 250 | { 251 | WarningLog(<< "Participant " << partHandle << " terminated, not known in any existing call"); 252 | } 253 | } 254 | 255 | void 256 | B2BCallManager::onParticipantProceeding(ParticipantHandle partHandle, const SipMessage& msg) 257 | { 258 | InfoLog(<< "onParticipantProceeding: handle=" << partHandle << " msg=" << msg.brief()); 259 | } 260 | 261 | void 262 | B2BCallManager::onParticipantAlerting(ParticipantHandle partHandle, const SipMessage& msg) 263 | { 264 | InfoLog(<< "onParticipantAlerting: handle=" << partHandle << " msg=" << msg.brief()); 265 | if(mCallsByParticipant.find(partHandle) != mCallsByParticipant.end()) 266 | { 267 | SharedPtr call = mCallsByParticipant[partHandle]; 268 | if(call->participantB() == partHandle) 269 | { 270 | alertParticipant(call->participantA(), false); 271 | } 272 | else 273 | { 274 | WarningLog(<<"Unexpected alerting signal from A-leg of call, partHandle = " << partHandle); 275 | } 276 | } 277 | else 278 | { 279 | WarningLog(<< "Participant " << partHandle << " alerting, not known in any existing call"); 280 | } 281 | } 282 | 283 | void 284 | B2BCallManager::onParticipantConnected(ParticipantHandle partHandle, const SipMessage& msg) 285 | { 286 | InfoLog(<< "onParticipantConnected: handle=" << partHandle << " msg=" << msg.brief()); 287 | if(mCallsByParticipant.find(partHandle) != mCallsByParticipant.end()) 288 | { 289 | SharedPtr call = mCallsByParticipant[partHandle]; 290 | if(call->participantB() == partHandle) 291 | { 292 | answerParticipant(call->participantA()); 293 | call->onConnect(); 294 | } 295 | else 296 | { 297 | WarningLog(<<"Unexpected connected signal from A-leg of call, partHandle = " << partHandle); 298 | } 299 | } 300 | else 301 | { 302 | WarningLog(<< "Participant " << partHandle << " connected, not known in any existing call"); 303 | } 304 | } 305 | 306 | resip::SharedPtr 307 | B2BCallManager::getIncomingConversationProfile(const resip::SipMessage& msg, resip::SharedPtr defaultProfile) 308 | { 309 | DebugLog(<<"getIncomingConversationProfile: defaultProfile.get() == " << defaultProfile.get()); 310 | if(isSourceInternal(msg)) 311 | { 312 | DebugLog(<<"getIncomingConversationProfile: returning profile for internal call leg"); 313 | return getInternalConversationProfile(); 314 | } 315 | return defaultProfile; 316 | } 317 | 318 | resip::SharedPtr 319 | B2BCallManager::getInternalConversationProfile() 320 | { 321 | MyUserAgent *ua = dynamic_cast(getUserAgent()); 322 | resip_assert(ua); 323 | if(!mInternalMediaAddress.empty()) 324 | { 325 | SharedPtr p = ua->getConversationProfileByMediaAddress(mInternalMediaAddress); 326 | if(p.get()) 327 | { 328 | return p; 329 | } 330 | } 331 | return ua->getDefaultOutgoingConversationProfile(); 332 | } 333 | 334 | bool 335 | B2BCallManager::isSourceInternal(const SipMessage& msg) 336 | { 337 | if(mInternalAllPrivate && msg.getSource().isPrivateAddress()) 338 | { 339 | DebugLog(<<"Matched internal host by IP in private network (RFC 1918 / RFC 4193)"); 340 | return true; 341 | } 342 | 343 | Data sourceAddr = Tuple::inet_ntop(msg.getSource()); 344 | if(std::find(mInternalHosts.begin(), mInternalHosts.end(), sourceAddr) != mInternalHosts.end()) 345 | { 346 | DebugLog(<<"Matched internal host by IP: " << sourceAddr); 347 | return true; 348 | } 349 | 350 | const std::list& peerNames = msg.getTlsPeerNames(); 351 | std::list::const_iterator it = peerNames.begin(); 352 | for( ; it != peerNames.end() ; it++) 353 | { 354 | const Data& peerName = *it; 355 | if(std::find(mInternalTLSNames.begin(), mInternalTLSNames.end(), peerName) != mInternalTLSNames.end()) 356 | { 357 | DebugLog(<<"Matched internal host by TLS name: " << peerName); 358 | } 359 | } 360 | 361 | DebugLog(<<"Didn't match internal host for source " << sourceAddr); 362 | return false; 363 | } 364 | 365 | void 366 | B2BCallManager::loadUserCredentials(Data filename) 367 | { 368 | if(!mUsers.empty()) 369 | { 370 | WarningLog(<<"loadUserCredentials called but mUsers already populated"); 371 | return; 372 | } 373 | 374 | InfoLog(<< "trying to load user credentials from file " << filename); 375 | 376 | std::ifstream usersFile(filename.c_str()); 377 | if(!usersFile) 378 | { 379 | ErrLog(<< "failed to open users file: " << filename << ", aborting"); 380 | throw std::runtime_error("Error opening/reading users file"); 381 | } 382 | 383 | std::string sline; 384 | while(getline(usersFile, sline)) 385 | { 386 | Data line(sline); 387 | Data uri; 388 | Data password; 389 | UserCredentials creds; 390 | ParseBuffer pb(line); 391 | 392 | pb.skipWhitespace(); 393 | const char * anchor = pb.position(); 394 | if(pb.eof() || *anchor == '#') continue; // if line is a comment or blank then skip it 395 | 396 | // Look for end of name 397 | pb.skipToOneOf("\t"); 398 | pb.data(uri, anchor); 399 | if(mUsers.find(uri) != mUsers.end()) 400 | { 401 | ErrLog(<< "URI '" << uri << "' repeated in users file"); 402 | throw std::runtime_error("URI repeated in users file"); 403 | } 404 | pb.skipChar('\t'); 405 | 406 | if(!pb.eof()) 407 | { 408 | pb.skipWhitespace(); 409 | if(pb.eof()) 410 | break; 411 | 412 | anchor = pb.position(); 413 | pb.skipToOneOf("\r\n "); 414 | pb.data(password, anchor); 415 | if(!password.empty()) 416 | { 417 | creds.mUsername = uri; 418 | creds.mPassword = password; 419 | mUsers[uri] = creds; 420 | DebugLog(<< "Loaded user URI '" << uri << "'"); 421 | } 422 | if(!pb.eof()) 423 | pb.skipChar(); 424 | } 425 | } 426 | InfoLog(<<"Loaded " << mUsers.size() << " users"); 427 | } 428 | 429 | /* ==================================================================== 430 | * 431 | * Copyright 2014 Daniel Pocock http://danielpocock.com All rights reserved. 432 | * 433 | * Redistribution and use in source and binary forms, with or without 434 | * modification, are permitted provided that the following conditions 435 | * are met: 436 | * 437 | * 1. Redistributions of source code must retain the above copyright 438 | * notice, this list of conditions and the following disclaimer. 439 | * 440 | * 2. Redistributions in binary form must reproduce the above copyright 441 | * notice, this list of conditions and the following disclaimer in 442 | * the documentation and/or other materials provided with the 443 | * distribution. 444 | * 445 | * 3. Neither the name of the author(s) nor the names of any contributors 446 | * may be used to endorse or promote products derived from this software 447 | * without specific prior written permission. 448 | * 449 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 450 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 451 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 452 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 453 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 454 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 455 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 456 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 457 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 458 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 459 | * SUCH DAMAGE. 460 | * 461 | * ==================================================================== 462 | * 463 | * 464 | */ 465 | 466 | -------------------------------------------------------------------------------- /B2BCallManager.hxx: -------------------------------------------------------------------------------- 1 | #ifndef B2BCALLMANAGER_HXX 2 | #define B2BCALLMANAGER_HXX 3 | 4 | #include 5 | 6 | #if defined(HAVE_CONFIG_H) 7 | #include "config.h" 8 | #endif 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "reConServerConfig.hxx" 18 | #include "MyConversationManager.hxx" 19 | 20 | namespace reconserver 21 | { 22 | 23 | class B2BCall 24 | { 25 | public: 26 | B2BCall(const recon::ConversationHandle& conv, const recon::ParticipantHandle& a, const recon::ParticipantHandle b, const resip::SipMessage& msg, const resip::Data& originZone, const resip::Data& destinationZone, const resip::Data& b2bCallID); 27 | 28 | void onConnect() { mConnect = resip::ResipClock::getTimeMs(); }; 29 | void onFinish(const int responseCode = 200) { mFinish = resip::ResipClock::getTimeMs(); mResponseCode = responseCode; }; 30 | 31 | const recon::ConversationHandle& conversation() { return mConversation; }; 32 | const recon::ParticipantHandle& participantA() { return mPartA; }; 33 | const recon::ParticipantHandle& participantB() { return mPartB; }; 34 | 35 | const resip::Data& getOriginZone() const { return mOriginZone; }; 36 | const resip::Data& getDestinationZone() const { return mDestinationZone; }; 37 | const resip::Data& getB2BCallID() const { return mB2BCallID; }; 38 | const resip::Data& getCaller() const { return mCaller; }; 39 | const resip::Data& getCallee() const { return mCallee; }; 40 | int getResponseCode() const { return mResponseCode; }; 41 | const uint64_t& getStart() const { return mStart; }; 42 | const uint64_t& getConnect() const { return mConnect; }; 43 | bool answered() const { return mConnect != 0; }; 44 | const uint64_t& getFinish() const { return mFinish; }; 45 | 46 | private: 47 | const recon::ConversationHandle mConversation; 48 | const recon::ParticipantHandle mPartA; 49 | const recon::ParticipantHandle mPartB; 50 | 51 | const resip::Data mOriginZone; 52 | const resip::Data mDestinationZone; 53 | 54 | const resip::Data mB2BCallID; 55 | 56 | const resip::Data mCaller; 57 | const resip::Data mCallee; 58 | 59 | int mResponseCode; 60 | 61 | // times are in milliseconds since the UNIX epoch 62 | uint64_t mStart; 63 | uint64_t mConnect; 64 | uint64_t mFinish; 65 | }; 66 | 67 | class B2BCallLogger 68 | { 69 | public: 70 | virtual void log(resip::SharedPtr call) = 0; 71 | }; 72 | 73 | class B2BCallManager : public MyConversationManager 74 | { 75 | public: 76 | 77 | B2BCallManager(recon::ConversationManager::MediaInterfaceMode mediaInterfaceMode, int defaultSampleRate, int maxSampleRate, ReConServerConfig& config, resip::SharedPtr b2bCallLogger = resip::SharedPtr()); 78 | 79 | virtual void onDtmfEvent(recon::ParticipantHandle partHandle, int dtmf, int duration, bool up); 80 | virtual void onIncomingParticipant(recon::ParticipantHandle partHandle, const resip::SipMessage& msg, bool autoAnswer, recon::ConversationProfile& conversationProfile); 81 | virtual void onParticipantTerminated(recon::ParticipantHandle partHandle, unsigned int statusCode); 82 | virtual void onParticipantProceeding(recon::ParticipantHandle partHandle, const resip::SipMessage& msg); 83 | virtual void onParticipantAlerting(recon::ParticipantHandle partHandle, const resip::SipMessage& msg); 84 | virtual void onParticipantConnected(recon::ParticipantHandle partHandle, const resip::SipMessage& msg); 85 | 86 | resip::SharedPtr getIncomingConversationProfile(const resip::SipMessage& msg, resip::SharedPtr defaultProfile); 87 | 88 | void loadUserCredentials(resip::Data filename); 89 | 90 | protected: 91 | resip::SharedPtr getInternalConversationProfile(); 92 | virtual bool isSourceInternal(const resip::SipMessage& msg); 93 | 94 | struct UserCredentials 95 | { 96 | resip::Data mUsername; 97 | resip::Data mPassword; 98 | }; 99 | 100 | resip::SharedPtr mB2BCallLogger; 101 | std::vector mInternalHosts; 102 | std::vector mInternalTLSNames; 103 | bool mInternalAllPrivate; 104 | resip::Data mInternalMediaAddress; 105 | std::vector mReplicatedHeaders; 106 | 107 | std::map mUsers; 108 | 109 | std::map > mCallsByConversation; 110 | std::map > mCallsByParticipant; 111 | }; 112 | 113 | } 114 | 115 | #endif 116 | 117 | 118 | /* ==================================================================== 119 | * 120 | * Copyright 2014 Daniel Pocock http://danielpocock.com All rights reserved. 121 | * 122 | * Redistribution and use in source and binary forms, with or without 123 | * modification, are permitted provided that the following conditions 124 | * are met: 125 | * 126 | * 1. Redistributions of source code must retain the above copyright 127 | * notice, this list of conditions and the following disclaimer. 128 | * 129 | * 2. Redistributions in binary form must reproduce the above copyright 130 | * notice, this list of conditions and the following disclaimer in 131 | * the documentation and/or other materials provided with the 132 | * distribution. 133 | * 134 | * 3. Neither the name of the author(s) nor the names of any contributors 135 | * may be used to endorse or promote products derived from this software 136 | * without specific prior written permission. 137 | * 138 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 139 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 140 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 141 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 142 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 143 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 144 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 145 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 146 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 147 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 148 | * SUCH DAMAGE. 149 | * 150 | * ==================================================================== 151 | * 152 | * 153 | */ 154 | 155 | -------------------------------------------------------------------------------- /CDRFile.cxx: -------------------------------------------------------------------------------- 1 | #include "CDRFile.hxx" 2 | 3 | #ifdef HAVE_CONFIG_H 4 | #include "config.h" 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 16 | 17 | using namespace resip; 18 | using namespace recon; 19 | using namespace reconserver; 20 | 21 | CDRFile::CDRFile(const resip::Data& filename) 22 | : mSep(',') 23 | { 24 | mFile.open(filename.c_str(), std::ios::app); 25 | } 26 | 27 | CDRFile::~CDRFile() 28 | { 29 | mFile.close(); 30 | } 31 | 32 | void 33 | CDRFile::log(SharedPtr call) 34 | { 35 | logString(call->getB2BCallID()); 36 | logString(call->getCaller()); 37 | logString(call->getCallee()); 38 | logString(call->getOriginZone()); 39 | logString(call->getDestinationZone()); 40 | logTimestamp(call->getStart()); 41 | Data disposition; 42 | if(call->answered()) 43 | { 44 | logTimestamp(call->getConnect()); 45 | disposition = "ANSWERED"; 46 | } 47 | else 48 | { 49 | logString(Data::Empty); 50 | switch(call->getResponseCode()) 51 | { 52 | case 486: 53 | disposition = "BUSY"; 54 | break; 55 | case 487: 56 | disposition = "NO ANSWER"; 57 | break; 58 | default: 59 | disposition = "FAILED"; 60 | } 61 | } 62 | logTimestamp(call->getFinish()); 63 | logTimediff(call->getFinish() - call->getStart()); 64 | if(call->answered()) 65 | { 66 | logTimediff(call->getFinish() - call->getConnect()); 67 | } 68 | else 69 | { 70 | logTimediff(0); 71 | } 72 | logString(disposition); 73 | logNumeric(call->getResponseCode(), true); 74 | } 75 | 76 | void 77 | CDRFile::logString(const resip::Data& s, bool last, bool quote) 78 | { 79 | if(quote) 80 | { 81 | mFile << '"' << s << '"'; 82 | } 83 | else 84 | { 85 | mFile << s; 86 | } 87 | if(last) 88 | { 89 | mFile << std::endl; 90 | } 91 | else 92 | { 93 | mFile << mSep; 94 | } 95 | } 96 | 97 | void 98 | CDRFile::logTimestamp(const uint64_t& t, bool last) 99 | { 100 | const time_t timeInSeconds = (time_t)(t / 1000); 101 | const int millis = t % 1000; 102 | 103 | char datebuf[256]; 104 | const unsigned int datebufSize = 256; 105 | struct tm localTimeResult; 106 | strftime (datebuf, 107 | datebufSize, 108 | "%Y%m%d-%H%M%S", /* guaranteed to fit in 256 chars, 109 | hence don't check return code */ 110 | #ifdef WIN32 111 | localtime (&timeInSeconds)); // Thread safe call on Windows 112 | #else 113 | localtime_r (&timeInSeconds, &localTimeResult)); // Thread safe version of localtime on linux 114 | #endif 115 | 116 | char msbuf[5]; 117 | /* Dividing (without remainder) by 1000 rounds the microseconds 118 | measure to the nearest millisecond. */ 119 | snprintf(msbuf, 5, ".%3.3ld", millis); 120 | 121 | int datebufCharsRemaining = datebufSize - (int)strlen(datebuf); 122 | #if defined(WIN32) && defined(_M_ARM) 123 | // There is a bug under ARM with strncat - we use strcat instead - buffer is plenty large accomdate our timestamp, no 124 | // real need to be safe here anyway. 125 | strcat(datebuf, msbuf); 126 | #else 127 | strncat (datebuf, msbuf, datebufCharsRemaining - 1); 128 | #endif 129 | datebuf[datebufSize - 1] = '\0'; /* Just in case strncat truncated msbuf, 130 | thereby leaving its last character at 131 | the end, instead of a null terminator */ 132 | 133 | logString(Data(datebuf), last, false); 134 | } 135 | 136 | void 137 | CDRFile::logTimediff(const uint64_t& d, bool last) 138 | { 139 | logString(Data((UInt64)(d / 1000)), last, false); 140 | } 141 | 142 | void 143 | CDRFile::logNumeric(int s, bool last) 144 | { 145 | logString(Data((Int32)s), last, false); 146 | } 147 | 148 | 149 | /* ==================================================================== 150 | * 151 | * Copyright 2017 Daniel Pocock http://danielpocock.com All rights reserved. 152 | * 153 | * Redistribution and use in source and binary forms, with or without 154 | * modification, are permitted provided that the following conditions 155 | * are met: 156 | * 157 | * 1. Redistributions of source code must retain the above copyright 158 | * notice, this list of conditions and the following disclaimer. 159 | * 160 | * 2. Redistributions in binary form must reproduce the above copyright 161 | * notice, this list of conditions and the following disclaimer in 162 | * the documentation and/or other materials provided with the 163 | * distribution. 164 | * 165 | * 3. Neither the name of the author(s) nor the names of any contributors 166 | * may be used to endorse or promote products derived from this software 167 | * without specific prior written permission. 168 | * 169 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 170 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 171 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 172 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 173 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 174 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 175 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 176 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 177 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 178 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 179 | * SUCH DAMAGE. 180 | * 181 | * 182 | */ 183 | -------------------------------------------------------------------------------- /CDRFile.hxx: -------------------------------------------------------------------------------- 1 | #ifndef CDRFILE_HXX 2 | #define CDRFILE_HXX 3 | 4 | #if defined(HAVE_CONFIG_H) 5 | #include "config.h" 6 | #endif 7 | 8 | #include "B2BCallManager.hxx" 9 | 10 | #include 11 | #include 12 | 13 | #include "rutil/Data.hxx" 14 | 15 | namespace reconserver 16 | { 17 | 18 | class CDRFile : public B2BCallLogger 19 | { 20 | public: 21 | CDRFile(const resip::Data& filename); 22 | virtual ~CDRFile(); 23 | virtual void log(resip::SharedPtr call); 24 | 25 | private: 26 | void logString(const resip::Data& s, bool last = false, bool quote = true); 27 | void logTimestamp(const uint64_t& t, bool last = false); 28 | void logTimediff(const uint64_t& d, bool last = false); 29 | void logNumeric(int s, bool last = false); 30 | 31 | char mSep; 32 | std::ofstream mFile; 33 | }; 34 | 35 | } 36 | 37 | #endif 38 | 39 | 40 | /* ==================================================================== 41 | * 42 | * Copyright 2017 Daniel Pocock http://danielpocock.com All rights reserved. 43 | * 44 | * Redistribution and use in source and binary forms, with or without 45 | * modification, are permitted provided that the following conditions 46 | * are met: 47 | * 48 | * 1. Redistributions of source code must retain the above copyright 49 | * notice, this list of conditions and the following disclaimer. 50 | * 51 | * 2. Redistributions in binary form must reproduce the above copyright 52 | * notice, this list of conditions and the following disclaimer in 53 | * the documentation and/or other materials provided with the 54 | * distribution. 55 | * 56 | * 3. Neither the name of the author(s) nor the names of any contributors 57 | * may be used to endorse or promote products derived from this software 58 | * without specific prior written permission. 59 | * 60 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 61 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 62 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 63 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 64 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 65 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 66 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 67 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 68 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 69 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 70 | * SUCH DAMAGE. 71 | * 72 | * 73 | */ 74 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | /* ==================================================================== 2 | * 3 | * Copyright (C) 2013 Scott Godin, Daniel Pocock, Catalin Constantin Usurelu 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in 14 | * the documentation and/or other materials provided with the 15 | * distribution. 16 | * 17 | * 3. Neither the name of the author(s) nor the names of any contributors 18 | * may be used to endorse or promote products derived from this software 19 | * without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | * 33 | * ==================================================================== 34 | * 35 | * 36 | */ 37 | 38 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2 | 2013-06-18: copied from testUA in resiprocate source tree 3 | 4 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | EXTRA_DIST = reConServer_readme.txt 3 | EXTRA_DIST += reConServer.config 4 | EXTRA_DIST += *.wav 5 | 6 | sbin_PROGRAMS = reConServer 7 | 8 | dist_man_MANS = reConServer.8 9 | 10 | reConServer_SOURCES = reConServer.cxx 11 | reConServer_SOURCES += reConServer.hxx 12 | reConServer_SOURCES += reConServerConfig.cxx 13 | reConServer_SOURCES += reConServerConfig.hxx 14 | reConServer_SOURCES += AppSubsystem.cxx 15 | reConServer_SOURCES += AppSubsystem.hxx 16 | reConServer_SOURCES += B2BCallManager.cxx 17 | reConServer_SOURCES += B2BCallManager.hxx 18 | reConServer_SOURCES += CDRFile.cxx 19 | reConServer_SOURCES += CDRFile.hxx 20 | reConServer_SOURCES += MyConversationManager.cxx 21 | reConServer_SOURCES += MyConversationManager.hxx 22 | reConServer_SOURCES += MyMessageDecorator.cxx 23 | reConServer_SOURCES += MyMessageDecorator.hxx 24 | reConServer_SOURCES += MyUserAgent.cxx 25 | reConServer_SOURCES += MyUserAgent.hxx 26 | reConServer_SOURCES += RegistrationForwarder.cxx 27 | reConServer_SOURCES += RegistrationForwarder.hxx 28 | reConServer_SOURCES += SubscriptionForwarder.cxx 29 | reConServer_SOURCES += SubscriptionForwarder.hxx 30 | reConServer_SOURCES += playback_prompt.h 31 | reConServer_SOURCES += record_prompt.h 32 | 33 | LDADD = -lrecon 34 | LDADD += -lreflow 35 | LDADD += -lsipXsdp 36 | LDADD += -lsipXmediaProcessing 37 | LDADD += -lsipXmedia 38 | LDADD += -lsipXport 39 | LDADD += -lsipXtack 40 | LDADD += -ldum 41 | LDADD += -lresip 42 | LDADD += -lrutil 43 | LDADD += -lssl -lcrypto -lpthread 44 | 45 | BUILT_SOURCES = playback_prompt.h record_prompt.h 46 | 47 | playback_prompt.h: 48 | echo "#ifndef _playback_prompt_h_" > playback_prompt.h 49 | echo "#define _playback_prompt_h_" >> playback_prompt.h 50 | xxd -i -u playback_prompt.wav >> playback_prompt.h 51 | sed -i 's/_wav//g' playback_prompt.h 52 | echo "#endif" >> playback_prompt.h 53 | 54 | record_prompt.h: 55 | echo "#ifndef _record_prompt_h_" > record_prompt.h 56 | echo "#define _record_prompt_h_" >> record_prompt.h 57 | xxd -i -u record_prompt.wav >> record_prompt.h 58 | sed -i 's/_wav//g' record_prompt.h 59 | echo "#endif" >> record_prompt.h 60 | 61 | 62 | -------------------------------------------------------------------------------- /MyConversationManager.cxx: -------------------------------------------------------------------------------- 1 | 2 | #include "MyConversationManager.hxx" 3 | 4 | #ifdef HAVE_CONFIG_H 5 | #include "config.h" 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Test Prompts for cache testing 13 | #include "playback_prompt.h" 14 | #include "record_prompt.h" 15 | 16 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 17 | 18 | using namespace resip; 19 | using namespace recon; 20 | using namespace reconserver; 21 | 22 | MyConversationManager::MyConversationManager(bool localAudioEnabled, MediaInterfaceMode mediaInterfaceMode, int defaultSampleRate, int maxSampleRate, bool autoAnswerEnabled) 23 | : ConversationManager(localAudioEnabled, mediaInterfaceMode, defaultSampleRate, maxSampleRate), 24 | mLocalAudioEnabled(localAudioEnabled), 25 | mAutoAnswerEnabled(autoAnswerEnabled) 26 | { 27 | } 28 | 29 | void 30 | MyConversationManager::startup() 31 | { 32 | if(mLocalAudioEnabled) 33 | { 34 | // Create initial local participant and conversation 35 | addParticipant(createConversation(), createLocalParticipant()); 36 | resip::Uri uri("tone:dialtone;duration=1000"); 37 | createMediaResourceParticipant(mConversationHandles.front(), uri); 38 | } 39 | else 40 | { 41 | // If no local audio - just create a starter conversation 42 | // FIXME - do we really need an empty conversation on startup? 43 | // If in B2BUA mode, this will never be used 44 | createConversation(); 45 | } 46 | 47 | // Load 2 items into cache for testing 48 | { 49 | resip::Data buffer(Data::Share, (const char*)playback_prompt, sizeof(playback_prompt)); 50 | resip::Data name("playback"); 51 | addBufferToMediaResourceCache(name, buffer, 0); 52 | } 53 | { 54 | resip::Data buffer(Data::Share, (const char *)record_prompt, sizeof(record_prompt)); 55 | resip::Data name("record"); 56 | addBufferToMediaResourceCache(name, buffer, 0); 57 | } 58 | } 59 | 60 | ConversationHandle 61 | MyConversationManager::createConversation() 62 | { 63 | ConversationHandle convHandle = ConversationManager::createConversation(); 64 | mConversationHandles.push_back(convHandle); 65 | return convHandle; 66 | } 67 | 68 | ParticipantHandle 69 | MyConversationManager::createRemoteParticipant(ConversationHandle convHandle, NameAddr& destination, ParticipantForkSelectMode forkSelectMode) 70 | { 71 | ParticipantHandle partHandle = ConversationManager::createRemoteParticipant(convHandle, destination, forkSelectMode); 72 | mRemoteParticipantHandles.push_back(partHandle); 73 | return partHandle; 74 | } 75 | 76 | ParticipantHandle 77 | MyConversationManager::createMediaResourceParticipant(ConversationHandle convHandle, const Uri& mediaUrl) 78 | { 79 | ParticipantHandle partHandle = ConversationManager::createMediaResourceParticipant(convHandle, mediaUrl); 80 | mMediaParticipantHandles.push_back(partHandle); 81 | return partHandle; 82 | } 83 | 84 | ParticipantHandle 85 | MyConversationManager::createLocalParticipant() 86 | { 87 | ParticipantHandle partHandle = ConversationManager::createLocalParticipant(); 88 | mLocalParticipantHandles.push_back(partHandle); 89 | return partHandle; 90 | } 91 | 92 | void 93 | MyConversationManager::onConversationDestroyed(ConversationHandle convHandle) 94 | { 95 | InfoLog(<< "onConversationDestroyed: handle=" << convHandle); 96 | mConversationHandles.remove(convHandle); 97 | } 98 | 99 | void 100 | MyConversationManager::onParticipantDestroyed(ParticipantHandle partHandle) 101 | { 102 | InfoLog(<< "onParticipantDestroyed: handle=" << partHandle); 103 | // Remove from whatever list it is in 104 | mRemoteParticipantHandles.remove(partHandle); 105 | mLocalParticipantHandles.remove(partHandle); 106 | mMediaParticipantHandles.remove(partHandle); 107 | } 108 | 109 | void 110 | MyConversationManager::onDtmfEvent(ParticipantHandle partHandle, int dtmf, int duration, bool up) 111 | { 112 | InfoLog(<< "onDtmfEvent: handle=" << partHandle << " tone=" << dtmf << " dur=" << duration << " up=" << up); 113 | } 114 | 115 | void 116 | MyConversationManager::onIncomingParticipant(ParticipantHandle partHandle, const SipMessage& msg, bool autoAnswer, ConversationProfile& conversationProfile) 117 | { 118 | InfoLog(<< "onIncomingParticipant: handle=" << partHandle << "auto=" << autoAnswer << " msg=" << msg.brief()); 119 | mRemoteParticipantHandles.push_back(partHandle); 120 | if(mAutoAnswerEnabled) 121 | { 122 | // If there are no conversations, then create one 123 | if(mConversationHandles.empty()) 124 | { 125 | ConversationHandle convHandle = createConversation(); 126 | // ensure a local participant is in the conversation - create one if one doesn't exist 127 | if(mLocalAudioEnabled && mLocalParticipantHandles.empty()) 128 | { 129 | createLocalParticipant(); 130 | } 131 | addParticipant(convHandle, mLocalParticipantHandles.front()); 132 | } 133 | addParticipant(mConversationHandles.front(), partHandle); 134 | answerParticipant(partHandle); 135 | } 136 | } 137 | 138 | void 139 | MyConversationManager::onRequestOutgoingParticipant(ParticipantHandle partHandle, const SipMessage& msg, ConversationProfile& conversationProfile) 140 | { 141 | InfoLog(<< "onRequestOutgoingParticipant: handle=" << partHandle << " msg=" << msg.brief()); 142 | /* 143 | if(mConvHandles.empty()) 144 | { 145 | ConversationHandle convHandle = createConversation(); 146 | addParticipant(convHandle, partHandle); 147 | }*/ 148 | } 149 | 150 | void 151 | MyConversationManager::onParticipantTerminated(ParticipantHandle partHandle, unsigned int statusCode) 152 | { 153 | InfoLog(<< "onParticipantTerminated: handle=" << partHandle); 154 | } 155 | 156 | void 157 | MyConversationManager::onParticipantProceeding(ParticipantHandle partHandle, const SipMessage& msg) 158 | { 159 | InfoLog(<< "onParticipantProceeding: handle=" << partHandle << " msg=" << msg.brief()); 160 | } 161 | 162 | void 163 | MyConversationManager::onRelatedConversation(ConversationHandle relatedConvHandle, ParticipantHandle relatedPartHandle, 164 | ConversationHandle origConvHandle, ParticipantHandle origPartHandle) 165 | { 166 | InfoLog(<< "onRelatedConversation: relatedConvHandle=" << relatedConvHandle << " relatedPartHandle=" << relatedPartHandle 167 | << " origConvHandle=" << origConvHandle << " origPartHandle=" << origPartHandle); 168 | mConversationHandles.push_back(relatedConvHandle); 169 | mRemoteParticipantHandles.push_back(relatedPartHandle); 170 | } 171 | 172 | void 173 | MyConversationManager::onParticipantAlerting(ParticipantHandle partHandle, const SipMessage& msg) 174 | { 175 | InfoLog(<< "onParticipantAlerting: handle=" << partHandle << " msg=" << msg.brief()); 176 | } 177 | 178 | void 179 | MyConversationManager::onParticipantConnected(ParticipantHandle partHandle, const SipMessage& msg) 180 | { 181 | InfoLog(<< "onParticipantConnected: handle=" << partHandle << " msg=" << msg.brief()); 182 | } 183 | 184 | void 185 | MyConversationManager::onParticipantRedirectSuccess(ParticipantHandle partHandle) 186 | { 187 | InfoLog(<< "onParticipantRedirectSuccess: handle=" << partHandle); 188 | } 189 | 190 | void 191 | MyConversationManager::onParticipantRedirectFailure(ParticipantHandle partHandle, unsigned int statusCode) 192 | { 193 | InfoLog(<< "onParticipantRedirectFailure: handle=" << partHandle << " statusCode=" << statusCode); 194 | } 195 | 196 | void 197 | MyConversationManager::displayInfo() 198 | { 199 | Data output; 200 | 201 | if(!mConversationHandles.empty()) 202 | { 203 | output = "Active conversation handles: "; 204 | std::list::iterator it; 205 | for(it = mConversationHandles.begin(); it != mConversationHandles.end(); it++) 206 | { 207 | output += Data(*it) + " "; 208 | } 209 | InfoLog(<< output); 210 | } 211 | if(!mLocalParticipantHandles.empty()) 212 | { 213 | output = "Local Participant handles: "; 214 | std::list::iterator it; 215 | for(it = mLocalParticipantHandles.begin(); it != mLocalParticipantHandles.end(); it++) 216 | { 217 | output += Data(*it) + " "; 218 | } 219 | InfoLog(<< output); 220 | } 221 | if(!mRemoteParticipantHandles.empty()) 222 | { 223 | output = "Remote Participant handles: "; 224 | std::list::iterator it; 225 | for(it = mRemoteParticipantHandles.begin(); it != mRemoteParticipantHandles.end(); it++) 226 | { 227 | output += Data(*it) + " "; 228 | } 229 | InfoLog(<< output); 230 | } 231 | if(!mMediaParticipantHandles.empty()) 232 | { 233 | output = "Media Participant handles: "; 234 | std::list::iterator it; 235 | for(it = mMediaParticipantHandles.begin(); it != mMediaParticipantHandles.end(); it++) 236 | { 237 | output += Data(*it) + " "; 238 | } 239 | InfoLog(<< output); 240 | } 241 | } 242 | 243 | /* ==================================================================== 244 | 245 | Copyright (c) 2007-2008, Plantronics, Inc. 246 | All rights reserved. 247 | 248 | Redistribution and use in source and binary forms, with or without 249 | modification, are permitted provided that the following conditions are 250 | met: 251 | 252 | 1. Redistributions of source code must retain the above copyright 253 | notice, this list of conditions and the following disclaimer. 254 | 255 | 2. Redistributions in binary form must reproduce the above copyright 256 | notice, this list of conditions and the following disclaimer in the 257 | documentation and/or other materials provided with the distribution. 258 | 259 | 3. Neither the name of Plantronics nor the names of its contributors 260 | may be used to endorse or promote products derived from this 261 | software without specific prior written permission. 262 | 263 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 264 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 265 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 266 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 267 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 268 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 269 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 270 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 271 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 272 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 273 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 274 | 275 | ==================================================================== */ 276 | 277 | -------------------------------------------------------------------------------- /MyConversationManager.hxx: -------------------------------------------------------------------------------- 1 | #ifndef MYCONVERSATIONMANAGER_HXX 2 | #define MYCONVERSATIONMANAGER_HXX 3 | 4 | #include 5 | 6 | #if defined(HAVE_CONFIG_H) 7 | #include "config.h" 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | namespace reconserver 14 | { 15 | 16 | class MyConversationManager : public recon::ConversationManager 17 | { 18 | public: 19 | 20 | MyConversationManager(bool localAudioEnabled, recon::ConversationManager::MediaInterfaceMode mediaInterfaceMode, int defaultSampleRate, int maxSampleRate, bool autoAnswerEnabled); 21 | virtual ~MyConversationManager() {}; 22 | 23 | virtual void startup(); 24 | 25 | virtual recon::ConversationHandle createConversation(); 26 | virtual recon::ParticipantHandle createRemoteParticipant(recon::ConversationHandle convHandle, resip::NameAddr& destination, recon::ConversationManager::ParticipantForkSelectMode forkSelectMode = ForkSelectAutomatic); 27 | virtual recon::ParticipantHandle createMediaResourceParticipant(recon::ConversationHandle convHandle, const resip::Uri& mediaUrl); 28 | virtual recon::ParticipantHandle createLocalParticipant(); 29 | virtual void onConversationDestroyed(recon::ConversationHandle convHandle); 30 | virtual void onParticipantDestroyed(recon::ParticipantHandle partHandle); 31 | virtual void onDtmfEvent(recon::ParticipantHandle partHandle, int dtmf, int duration, bool up); 32 | virtual void onIncomingParticipant(recon::ParticipantHandle partHandle, const resip::SipMessage& msg, bool autoAnswer, recon::ConversationProfile& conversationProfile); 33 | virtual void onRequestOutgoingParticipant(recon::ParticipantHandle partHandle, const resip::SipMessage& msg, recon::ConversationProfile& conversationProfile); 34 | virtual void onParticipantTerminated(recon::ParticipantHandle partHandle, unsigned int statusCode); 35 | virtual void onParticipantProceeding(recon::ParticipantHandle partHandle, const resip::SipMessage& msg); 36 | virtual void onRelatedConversation(recon::ConversationHandle relatedConvHandle, recon::ParticipantHandle relatedPartHandle, 37 | recon::ConversationHandle origConvHandle, recon::ParticipantHandle origPartHandle); 38 | virtual void onParticipantAlerting(recon::ParticipantHandle partHandle, const resip::SipMessage& msg); 39 | virtual void onParticipantConnected(recon::ParticipantHandle partHandle, const resip::SipMessage& msg); 40 | virtual void onParticipantRedirectSuccess(recon::ParticipantHandle partHandle); 41 | virtual void onParticipantRedirectFailure(recon::ParticipantHandle partHandle, unsigned int statusCode); 42 | virtual void displayInfo(); 43 | 44 | protected: 45 | std::list mConversationHandles; 46 | std::list mLocalParticipantHandles; 47 | std::list mRemoteParticipantHandles; 48 | std::list mMediaParticipantHandles; 49 | bool mLocalAudioEnabled; 50 | bool mAutoAnswerEnabled; 51 | }; 52 | 53 | } 54 | 55 | #endif 56 | 57 | /* ==================================================================== 58 | 59 | Copyright (c) 2007-2008, Plantronics, Inc. 60 | All rights reserved. 61 | 62 | Redistribution and use in source and binary forms, with or without 63 | modification, are permitted provided that the following conditions are 64 | met: 65 | 66 | 1. Redistributions of source code must retain the above copyright 67 | notice, this list of conditions and the following disclaimer. 68 | 69 | 2. Redistributions in binary form must reproduce the above copyright 70 | notice, this list of conditions and the following disclaimer in the 71 | documentation and/or other materials provided with the distribution. 72 | 73 | 3. Neither the name of Plantronics nor the names of its contributors 74 | may be used to endorse or promote products derived from this 75 | software without specific prior written permission. 76 | 77 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 78 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 79 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 80 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 81 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 82 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 83 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 84 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 85 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 86 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 87 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 88 | 89 | ==================================================================== */ 90 | 91 | -------------------------------------------------------------------------------- /MyMessageDecorator.cxx: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "MyMessageDecorator.hxx" 13 | 14 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 15 | 16 | using namespace resip; 17 | using namespace reconserver; 18 | 19 | MyMessageDecorator::MyMessageDecorator() 20 | { 21 | } 22 | 23 | void 24 | MyMessageDecorator::decorateMessage(SipMessage &msg, 25 | const Tuple &source, 26 | const Tuple &destination, 27 | const Data& sigcompId) 28 | { 29 | StackLog(<<"Got a message for decoration"); 30 | 31 | SdpContents *sdp = dynamic_cast(msg.getContents()); 32 | if(sdp) 33 | { 34 | StackLog(<<"SDP found, checking message"); 35 | 36 | /* If we are bound to 0.0.0.0 and no NAT traversal mode has 37 | been enabled then at this point, the SDP will usually have 38 | 0.0.0.0 as the connection IP. 39 | 40 | This is generally not desirable. 41 | 42 | Under legacy SIP RFC 2543, this would signify putting a call on hold. 43 | 44 | Under RFC 3264, it is also suggested that this special 45 | address can be used in the connection line by a UA 46 | that is yet to discover which address it should use. 47 | This could be the case if ICE/TURN is not yet complete. 48 | 49 | However, for the vast majority of cases, it is desirable 50 | to replace 0.0.0.0 with the source IP that will be used 51 | for the outgoing SIP packet. The SIP stack only knows 52 | this at the last moment before sending it to the wire 53 | and that is why it is substituted here in a MessageDecorator. 54 | */ 55 | SdpContents::Session::Connection& c = sdp->session().connection(); 56 | StackLog(<<"session connection address = " << c.getAddress()); 57 | if(c.getAddress() == "0.0.0.0") 58 | { 59 | Data newAddr = Tuple::inet_ntop(source); 60 | StackLog(<<"replacing session connection address with " << newAddr); 61 | c.setAddress(newAddr); 62 | } 63 | std::list& mc = sdp->session().media().front().getMediumConnections(); 64 | std::list::iterator it = mc.begin(); 65 | for( ; it != mc.end(); it++) 66 | { 67 | SdpContents::Session::Connection& _mc = *it; 68 | if(_mc.getAddress() == "0.0.0.0") 69 | { 70 | Data newAddr = Tuple::inet_ntop(source); 71 | StackLog(<<"replacing media stream connection address with " << newAddr); 72 | _mc.setAddress(newAddr); 73 | } 74 | } 75 | } 76 | } 77 | 78 | /* ==================================================================== 79 | * 80 | * Copyright 2014 Daniel Pocock http://danielpocock.com All rights reserved. 81 | * 82 | * Redistribution and use in source and binary forms, with or without 83 | * modification, are permitted provided that the following conditions 84 | * are met: 85 | * 86 | * 1. Redistributions of source code must retain the above copyright 87 | * notice, this list of conditions and the following disclaimer. 88 | * 89 | * 2. Redistributions in binary form must reproduce the above copyright 90 | * notice, this list of conditions and the following disclaimer in 91 | * the documentation and/or other materials provided with the 92 | * distribution. 93 | * 94 | * 3. Neither the name of the author(s) nor the names of any contributors 95 | * may be used to endorse or promote products derived from this software 96 | * without specific prior written permission. 97 | * 98 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 99 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 100 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 101 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 102 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 103 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 104 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 105 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 106 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 107 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 108 | * SUCH DAMAGE. 109 | * 110 | * ==================================================================== 111 | * 112 | * 113 | */ 114 | 115 | -------------------------------------------------------------------------------- /MyMessageDecorator.hxx: -------------------------------------------------------------------------------- 1 | #ifndef MYMESSAGEDECORATOR_HXX 2 | #define MYMESSAGEDECORATOR_HXX 3 | 4 | #if defined(HAVE_CONFIG_H) 5 | #include "config.h" 6 | #endif 7 | 8 | #include "rutil/Data.hxx" 9 | #include "resip/stack/MessageDecorator.hxx" 10 | 11 | namespace reconserver 12 | { 13 | 14 | class MyMessageDecorator : public resip::MessageDecorator 15 | { 16 | public: 17 | MyMessageDecorator(); 18 | virtual ~MyMessageDecorator() {} 19 | 20 | virtual void decorateMessage(resip::SipMessage &msg, 21 | const resip::Tuple &source, 22 | const resip::Tuple &destination, 23 | const resip::Data& sigcompId); 24 | virtual void rollbackMessage(resip::SipMessage& msg) {}; 25 | virtual resip::MessageDecorator* clone() const { return new MyMessageDecorator(); }; 26 | 27 | }; 28 | 29 | } 30 | 31 | #endif 32 | 33 | /* ==================================================================== 34 | * 35 | * Copyright 2014 Daniel Pocock http://danielpocock.com All rights reserved. 36 | * 37 | * Redistribution and use in source and binary forms, with or without 38 | * modification, are permitted provided that the following conditions 39 | * are met: 40 | * 41 | * 1. Redistributions of source code must retain the above copyright 42 | * notice, this list of conditions and the following disclaimer. 43 | * 44 | * 2. Redistributions in binary form must reproduce the above copyright 45 | * notice, this list of conditions and the following disclaimer in 46 | * the documentation and/or other materials provided with the 47 | * distribution. 48 | * 49 | * 3. Neither the name of the author(s) nor the names of any contributors 50 | * may be used to endorse or promote products derived from this software 51 | * without specific prior written permission. 52 | * 53 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 54 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 55 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 56 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 57 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 58 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 59 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 60 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 61 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 62 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 63 | * SUCH DAMAGE. 64 | * 65 | * ==================================================================== 66 | * 67 | * 68 | */ 69 | 70 | -------------------------------------------------------------------------------- /MyUserAgent.cxx: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "B2BCallManager.hxx" 14 | #include "MyUserAgent.hxx" 15 | 16 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 17 | 18 | using namespace resip; 19 | using namespace recon; 20 | using namespace reconserver; 21 | using namespace std; 22 | 23 | 24 | MyUserAgent::MyUserAgent(ConfigParse& configParse, ConversationManager* conversationManager, SharedPtr profile) : 25 | UserAgent(conversationManager, profile), 26 | mMaxRegLoops(1000) 27 | { 28 | mRegistrationForwarder.reset(new RegistrationForwarder(configParse, getSipStack())); 29 | mSubscriptionForwarder.reset(new SubscriptionForwarder(configParse, getSipStack())); 30 | MessageFilterRuleList ruleList; 31 | MessageFilterRule::MethodList methodList; 32 | methodList.push_back(resip::INVITE); 33 | methodList.push_back(resip::CANCEL); 34 | methodList.push_back(resip::BYE); 35 | methodList.push_back(resip::ACK); 36 | methodList.push_back(resip::REFER); 37 | methodList.push_back(resip::PUBLISH); 38 | methodList.push_back(resip::OPTIONS); 39 | methodList.push_back(resip::PRACK); 40 | ruleList.push_back(MessageFilterRule(resip::MessageFilterRule::SchemeList(), 41 | resip::MessageFilterRule::Any, 42 | methodList) ); 43 | 44 | // We have to be more selective about NOTIFY because some of the 45 | // Event types must fall through to the SubscriptionForwarder 46 | MessageFilterRule::MethodList methodList2; 47 | methodList2.push_back(resip::NOTIFY); 48 | MessageFilterRule::EventList eventList; 49 | eventList.push_back("refer"); // FIXME 50 | ruleList.push_back(MessageFilterRule(resip::MessageFilterRule::SchemeList(), 51 | resip::MessageFilterRule::Any, 52 | methodList2, 53 | eventList) ); 54 | 55 | getDialogUsageManager().setMessageFilterRuleList(ruleList); 56 | } 57 | 58 | void 59 | MyUserAgent::onApplicationTimer(unsigned int id, unsigned int durationMs, unsigned int seq) 60 | { 61 | InfoLog(<< "onApplicationTimeout: id=" << id << " dur=" << durationMs << " seq=" << seq); 62 | } 63 | 64 | void 65 | MyUserAgent::onSubscriptionTerminated(SubscriptionHandle handle, unsigned int statusCode) 66 | { 67 | InfoLog(<< "onSubscriptionTerminated: handle=" << handle << " statusCode=" << statusCode); 68 | } 69 | 70 | void 71 | MyUserAgent::onSubscriptionNotify(SubscriptionHandle handle, const Data& notifyData) 72 | { 73 | InfoLog(<< "onSubscriptionNotify: handle=" << handle << " data=" << endl << notifyData); 74 | } 75 | 76 | resip::SharedPtr 77 | MyUserAgent::getIncomingConversationProfile(const resip::SipMessage& msg) 78 | { 79 | B2BCallManager *b2bcm = getB2BCallManager(); 80 | SharedPtr defaultProfile = UserAgent::getIncomingConversationProfile(msg); 81 | if(b2bcm) 82 | { 83 | return b2bcm->getIncomingConversationProfile(msg, defaultProfile); 84 | } 85 | return defaultProfile; 86 | } 87 | 88 | void 89 | MyUserAgent::process(int timeoutMs) 90 | { 91 | // Keep calling process() as long as there appear to be messages 92 | // available from the stack 93 | for(int i = 0; i < mMaxRegLoops && mRegistrationForwarder->process() ; i++); 94 | for(int i = 0; i < mMaxRegLoops && mSubscriptionForwarder->process() ; i++); 95 | 96 | UserAgent::process(timeoutMs); 97 | } 98 | 99 | B2BCallManager* 100 | MyUserAgent::getB2BCallManager() 101 | { 102 | return dynamic_cast(getConversationManager()); 103 | } 104 | 105 | /* ==================================================================== 106 | * 107 | * Copyright 2016 Daniel Pocock http://danielpocock.com All rights reserved. 108 | * 109 | * Redistribution and use in source and binary forms, with or without 110 | * modification, are permitted provided that the following conditions 111 | * are met: 112 | * 113 | * 1. Redistributions of source code must retain the above copyright 114 | * notice, this list of conditions and the following disclaimer. 115 | * 116 | * 2. Redistributions in binary form must reproduce the above copyright 117 | * notice, this list of conditions and the following disclaimer in 118 | * the documentation and/or other materials provided with the 119 | * distribution. 120 | * 121 | * 3. Neither the name of the author(s) nor the names of any contributors 122 | * may be used to endorse or promote products derived from this software 123 | * without specific prior written permission. 124 | * 125 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 126 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 127 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 128 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 129 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 130 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 131 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 132 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 133 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 134 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 135 | * SUCH DAMAGE. 136 | * 137 | * ==================================================================== 138 | * 139 | * 140 | */ 141 | 142 | -------------------------------------------------------------------------------- /MyUserAgent.hxx: -------------------------------------------------------------------------------- 1 | #ifndef MYUSERAGENT_HXX 2 | #define MYUSERAGENT_HXX 3 | 4 | #include 5 | 6 | #if defined(HAVE_CONFIG_H) 7 | #include "config.h" 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "B2BCallManager.hxx" 16 | #include "RegistrationForwarder.hxx" 17 | #include "SubscriptionForwarder.hxx" 18 | 19 | namespace reconserver 20 | { 21 | 22 | class MyUserAgent : public recon::UserAgent 23 | { 24 | public: 25 | MyUserAgent(resip::ConfigParse& configParse, recon::ConversationManager* conversationManager, resip::SharedPtr profile); 26 | virtual void onApplicationTimer(unsigned int id, unsigned int durationMs, unsigned int seq); 27 | virtual void onSubscriptionTerminated(recon::SubscriptionHandle handle, unsigned int statusCode); 28 | virtual void onSubscriptionNotify(recon::SubscriptionHandle handle, const resip::Data& notifyData); 29 | virtual resip::SharedPtr getIncomingConversationProfile(const resip::SipMessage& msg); 30 | virtual void process(int timeoutMs); 31 | 32 | private: 33 | friend class B2BCallManager; 34 | 35 | unsigned int mMaxRegLoops; 36 | resip::SharedPtr mRegistrationForwarder; 37 | resip::SharedPtr mSubscriptionForwarder; 38 | 39 | B2BCallManager *getB2BCallManager(); 40 | }; 41 | 42 | } 43 | 44 | #endif 45 | 46 | /* ==================================================================== 47 | * 48 | * Copyright 2016 Daniel Pocock http://danielpocock.com All rights reserved. 49 | * 50 | * Redistribution and use in source and binary forms, with or without 51 | * modification, are permitted provided that the following conditions 52 | * are met: 53 | * 54 | * 1. Redistributions of source code must retain the above copyright 55 | * notice, this list of conditions and the following disclaimer. 56 | * 57 | * 2. Redistributions in binary form must reproduce the above copyright 58 | * notice, this list of conditions and the following disclaimer in 59 | * the documentation and/or other materials provided with the 60 | * distribution. 61 | * 62 | * 3. Neither the name of the author(s) nor the names of any contributors 63 | * may be used to endorse or promote products derived from this software 64 | * without specific prior written permission. 65 | * 66 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 67 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 68 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 69 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 70 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 71 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 72 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 73 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 74 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 75 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 76 | * SUCH DAMAGE. 77 | * 78 | * ==================================================================== 79 | * 80 | * 81 | */ 82 | 83 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 2 | 2013-06-18: copied from testUA in resiprocate source tree 3 | 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | To prepare your machine for building the project, you need to install 3 | the build dependencies. On a Debian or Ubuntu system, you may use a 4 | command like this: 5 | 6 | $ sudo apt-get install \ 7 | libresiprocate-dev librecon-dev libsipxtapi-dev \ 8 | libresiprocate-turn-client-dev 9 | 10 | 11 | NOTE: You must have at least reSIProcate v1.8.10 12 | 13 | To build this project, note that CPPFLAGS are necessary: 14 | 15 | $ git clone $REPOSITORY 16 | $ cd reConServer 17 | $ autoreconf --install 18 | $ ./configure CPPFLAGS="-I/usr/include/sipxtapi -DDEFAULT_BRIDGE_MAX_IN_OUTPUTS=10 -D__pingtel_on_posix__ -D_linux -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DRESIP_TOOLCHAIN_GNU -DRESIP_OSTYPE_LINUX -DRESIP_ARCH_X86_64 -DHAVE_sockaddr_in_len -DUSE_CARES -DUSE_SSL -DUSE_IPV6 -DHAVE_EPOLL" 19 | $ autoreconf --install 20 | $ make 21 | 22 | 23 | To run the project, see the original testua_readme.txt 24 | 25 | 26 | G.722 notes 27 | ----------- 28 | 29 | Earlier versions of the sipXtapi code (and packages) don't fully support 30 | G.722 sample rate specification in the SDP. This is a special case explained 31 | in RFC 3551. 32 | 33 | Use sipXtapi package version 3.3.0~test14 or later to have this work correctly. 34 | 35 | -------------------------------------------------------------------------------- /RegistrationForwarder.cxx: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "RegistrationForwarder.hxx" 13 | 14 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 15 | 16 | using namespace resip; 17 | using namespace reconserver; 18 | 19 | RegistrationForwarder::RegistrationForwarder(ConfigParse& configParse, SipStack& stack) : 20 | mStack(stack), 21 | mShutdownState(Running) 22 | { 23 | mStack.registerTransactionUser(*this); 24 | MessageFilterRuleList ruleList; 25 | MessageFilterRule::MethodList methodList; 26 | methodList.push_back(resip::REGISTER); 27 | ruleList.push_back(MessageFilterRule(resip::MessageFilterRule::SchemeList(), 28 | resip::MessageFilterRule::Any, 29 | methodList) ); 30 | setMessageFilterRuleList(ruleList); 31 | 32 | mMaxExpiry = configParse.getConfigInt("B2BUARegistrationForwardMaxExpiry", 60); 33 | mPath = configParse.getConfigData("B2BUARegistrationForwardPath", ""); 34 | const Data& nextHop = configParse.getConfigData("B2BUANextHop", ""); 35 | const Data& registrationRoute = configParse.getConfigData("B2BUARegistrationForwardRoute", nextHop); 36 | if(registrationRoute.empty()) 37 | { 38 | CritLog(<<"Please specify B2BUARegistrationForwardRoute"); 39 | throw ConfigParse::Exception("Please specify B2BUARegistrationForwardRoute", __FILE__, __LINE__); 40 | } 41 | mRegistrationRoute = NameAddr(registrationRoute); 42 | } 43 | 44 | RegistrationForwarder::~RegistrationForwarder() 45 | { 46 | } 47 | 48 | bool 49 | RegistrationForwarder::process(resip::Lockable* mutex) 50 | { 51 | if (mFifo.messageAvailable()) 52 | { 53 | resip::PtrLock lock(mutex); 54 | internalProcess(std::auto_ptr(mFifo.getNext())); 55 | } 56 | return mFifo.messageAvailable(); 57 | } 58 | 59 | const Data& 60 | RegistrationForwarder::name() const 61 | { 62 | static Data n("RegistrationForwarder"); 63 | return n; 64 | } 65 | 66 | void 67 | RegistrationForwarder::internalProcess(std::auto_ptr msg) 68 | { 69 | // After a Stack ShutdownMessage has been received, don't do anything else in this TU 70 | if (mShutdownState == Shutdown) 71 | { 72 | return; 73 | } 74 | 75 | { 76 | TransactionUserMessage* tuMsg = dynamic_cast(msg.get()); 77 | if (tuMsg) 78 | { 79 | InfoLog (<< "TU unregistered "); 80 | resip_assert(mShutdownState == RemovingTransactionUser); 81 | resip_assert(tuMsg->type() == TransactionUserMessage::TransactionUserRemoved); 82 | mShutdownState = Shutdown; 83 | return; 84 | } 85 | } 86 | 87 | Data tid = Data::Empty; 88 | { 89 | SipMessage* sipMsg = dynamic_cast(msg.get()); 90 | if (sipMsg) 91 | { 92 | tid = sipMsg->getTransactionId(); 93 | bool garbage=false; 94 | Data reason; 95 | 96 | if(!sipMsg->header(h_From).isWellFormed()) 97 | { 98 | garbage=true; 99 | reason.append("Malformed From, ",16); 100 | } 101 | 102 | if(!sipMsg->header(h_To).isWellFormed()) 103 | { 104 | garbage=true; 105 | reason.append("Malformed To, ",14); 106 | } 107 | 108 | if(!sipMsg->header(h_CallId).isWellFormed()) 109 | { 110 | garbage=true; 111 | reason.append("Malformed Call-Id, ",19); 112 | } 113 | 114 | if(garbage) 115 | { 116 | if(sipMsg->isRequest() && sipMsg->method()!=ACK) 117 | { 118 | // .bwc. Either we need to trim the last comma off, or make this 119 | // a proper sentence fragment. This is more fun. 120 | reason.append("fix your code!",14); 121 | SipMessage failure; 122 | //makeResponse(failure, *sipMsg, 400, reason); 123 | //sendResponse(failure); 124 | } 125 | InfoLog (<< "Malformed header in message (" << reason << ") - rejecting/discarding: " << *sipMsg); 126 | 127 | // .bwc. Only forge a response when appropriate, but return in any 128 | // case. 129 | return; 130 | } 131 | 132 | // This should be a REGISTER request or response 133 | // - update contact 134 | // - add next hop to route set 135 | // - add via 136 | // - add record route 137 | // - forward 138 | // - reduce hop count 139 | if(sipMsg->isRequest() && sipMsg->method()==REGISTER) 140 | { 141 | DebugLog(<<"Handling a REGISTER request"); 142 | if(!sipMsg->header(h_Supporteds).find(Token(Symbols::Path))) 143 | { 144 | sipMsg->header(h_Supporteds).push_back(Token(Symbols::Path)); 145 | } 146 | if(sipMsg->header(h_Expires).value() > mMaxExpiry) 147 | { 148 | sipMsg->header(h_Expires).value() = mMaxExpiry; 149 | } 150 | if(!mPath.empty()) 151 | { 152 | sipMsg->header(h_Paths).push_front(NameAddr(mPath)); 153 | } 154 | sipMsg->header(h_Routes).clear(); // remove ourselves 155 | sipMsg->header(h_Routes).push_front(mRegistrationRoute); 156 | // must clear tlsDomain, otherwise the stack tries to send over TLS 157 | // even if the route is not a secure transport 158 | sipMsg->setTlsDomain(""); 159 | mStack.send(*sipMsg, this); 160 | } 161 | else if(sipMsg->isResponse() && sipMsg->header(h_CSeq).method() == REGISTER) 162 | { 163 | DebugLog(<<"Handling a REGISTER response"); 164 | mStack.send(*sipMsg, this); 165 | } 166 | else 167 | { 168 | WarningLog(<<"Message is not a REGISTER request or response, ignoring it"); 169 | return; 170 | } 171 | } 172 | 173 | } 174 | } 175 | 176 | 177 | /* ==================================================================== 178 | * 179 | * Copyright 2016 Daniel Pocock http://danielpocock.com All rights reserved. 180 | * 181 | * Redistribution and use in source and binary forms, with or without 182 | * modification, are permitted provided that the following conditions 183 | * are met: 184 | * 185 | * 1. Redistributions of source code must retain the above copyright 186 | * notice, this list of conditions and the following disclaimer. 187 | * 188 | * 2. Redistributions in binary form must reproduce the above copyright 189 | * notice, this list of conditions and the following disclaimer in 190 | * the documentation and/or other materials provided with the 191 | * distribution. 192 | * 193 | * 3. Neither the name of the author(s) nor the names of any contributors 194 | * may be used to endorse or promote products derived from this software 195 | * without specific prior written permission. 196 | * 197 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 198 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 199 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 200 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 201 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 202 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 203 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 204 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 205 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 206 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 207 | * SUCH DAMAGE. 208 | * 209 | * ==================================================================== 210 | * 211 | * 212 | */ 213 | 214 | -------------------------------------------------------------------------------- /RegistrationForwarder.hxx: -------------------------------------------------------------------------------- 1 | #ifndef REGISTRATIONFORWARDER_HXX 2 | #define REGISTRATIONFORWARDER_HXX 3 | 4 | #if defined(HAVE_CONFIG_H) 5 | #include "config.h" 6 | #endif 7 | 8 | #include "rutil/ConfigParse.hxx" 9 | #include "rutil/Data.hxx" 10 | #include "resip/stack/NameAddr.hxx" 11 | #include "resip/stack/SipStack.hxx" 12 | #include "resip/stack/TransactionUser.hxx" 13 | 14 | namespace reconserver 15 | { 16 | 17 | class RegistrationForwarder : public resip::TransactionUser 18 | { 19 | public: 20 | RegistrationForwarder(resip::ConfigParse& cp, resip::SipStack& stack); 21 | virtual ~RegistrationForwarder(); 22 | 23 | bool process(resip::Lockable* mutex = 0); 24 | 25 | virtual const resip::Data& name() const; 26 | 27 | private: 28 | void internalProcess(std::auto_ptr); 29 | 30 | resip::SipStack& mStack; 31 | 32 | unsigned int mMaxExpiry; 33 | resip::Data mPath; 34 | resip::NameAddr mRegistrationRoute; 35 | 36 | typedef enum 37 | { 38 | Running, 39 | ShutdownRequested, // while ending usages 40 | RemovingTransactionUser, // while removing TU from stack 41 | Shutdown, // after TU has been removed from stack 42 | Destroying // while calling destructor 43 | } ShutdownState; 44 | ShutdownState mShutdownState; 45 | }; 46 | 47 | } 48 | 49 | #endif 50 | 51 | /* ==================================================================== 52 | * 53 | * Copyright 2016 Daniel Pocock http://danielpocock.com All rights reserved. 54 | * 55 | * Redistribution and use in source and binary forms, with or without 56 | * modification, are permitted provided that the following conditions 57 | * are met: 58 | * 59 | * 1. Redistributions of source code must retain the above copyright 60 | * notice, this list of conditions and the following disclaimer. 61 | * 62 | * 2. Redistributions in binary form must reproduce the above copyright 63 | * notice, this list of conditions and the following disclaimer in 64 | * the documentation and/or other materials provided with the 65 | * distribution. 66 | * 67 | * 3. Neither the name of the author(s) nor the names of any contributors 68 | * may be used to endorse or promote products derived from this software 69 | * without specific prior written permission. 70 | * 71 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 72 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 73 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 74 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 75 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 76 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 77 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 78 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 79 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 80 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 81 | * SUCH DAMAGE. 82 | * 83 | * ==================================================================== 84 | * 85 | * 86 | */ 87 | 88 | -------------------------------------------------------------------------------- /SubscriptionForwarder.cxx: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "SubscriptionForwarder.hxx" 14 | 15 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 16 | 17 | using namespace resip; 18 | using namespace reconserver; 19 | 20 | SubscriptionForwarder::SubscriptionForwarder(ConfigParse& configParse, SipStack& stack) : 21 | mStack(stack), 22 | mShutdownState(Running) 23 | { 24 | mStack.registerTransactionUser(*this); 25 | 26 | MessageFilterRuleList ruleList; 27 | 28 | // we handle any SUBSCRIBE 29 | MessageFilterRule::MethodList methodList1; 30 | methodList1.push_back(resip::SUBSCRIBE); 31 | ruleList.push_back(MessageFilterRule(resip::MessageFilterRule::SchemeList(), 32 | resip::MessageFilterRule::Any, 33 | methodList1) ); 34 | 35 | // we handle NOTIFY for the Event types specified in this rule 36 | MessageFilterRule::MethodList methodList2; 37 | methodList2.push_back(resip::NOTIFY); 38 | MessageFilterRule::EventList eventList; 39 | eventList.push_back("presence"); 40 | eventList.push_back("message-summary"); 41 | ruleList.push_back(MessageFilterRule(resip::MessageFilterRule::SchemeList(), 42 | resip::MessageFilterRule::Any, 43 | methodList2, 44 | eventList) ); 45 | 46 | setMessageFilterRuleList(ruleList); 47 | 48 | mPath = configParse.getConfigData("B2BUARegistrationForwardPath", ""); 49 | const Data& nextHop = configParse.getConfigData("B2BUANextHop", ""); 50 | const Data& subscriptionRoute = configParse.getConfigData("B2BUASubscriptionForwardRoute", nextHop); 51 | if(subscriptionRoute.empty()) 52 | { 53 | CritLog(<<"Please specify B2BUASubscriptionForwardRoute"); 54 | throw ConfigParse::Exception("Please specify B2BUASubscriptionForwardRoute", __FILE__, __LINE__); 55 | } 56 | mSubscriptionRoute = NameAddr(subscriptionRoute); 57 | mMissingEventHack = configParse.getConfigBool("B2BUASubscriptionMissingEventHack", false); 58 | mOverrideContactWithAor = configParse.getConfigBool("B2BUASubscriptionOverrideContactWithAor", false); 59 | } 60 | 61 | SubscriptionForwarder::~SubscriptionForwarder() 62 | { 63 | } 64 | 65 | bool 66 | SubscriptionForwarder::process(resip::Lockable* mutex) 67 | { 68 | if (mFifo.messageAvailable()) 69 | { 70 | resip::PtrLock lock(mutex); 71 | internalProcess(std::auto_ptr(mFifo.getNext())); 72 | } 73 | return mFifo.messageAvailable(); 74 | } 75 | 76 | const Data& 77 | SubscriptionForwarder::name() const 78 | { 79 | static Data n("SubscriptionForwarder"); 80 | return n; 81 | } 82 | 83 | void 84 | SubscriptionForwarder::internalProcess(std::auto_ptr msg) 85 | { 86 | // After a Stack ShutdownMessage has been received, don't do anything else in this TU 87 | if (mShutdownState == Shutdown) 88 | { 89 | return; 90 | } 91 | 92 | { 93 | TransactionUserMessage* tuMsg = dynamic_cast(msg.get()); 94 | if (tuMsg) 95 | { 96 | InfoLog (<< "TU unregistered "); 97 | resip_assert(mShutdownState == RemovingTransactionUser); 98 | resip_assert(tuMsg->type() == TransactionUserMessage::TransactionUserRemoved); 99 | mShutdownState = Shutdown; 100 | return; 101 | } 102 | } 103 | 104 | Data tid = Data::Empty; 105 | { 106 | SipMessage* sipMsg = dynamic_cast(msg.get()); 107 | if (sipMsg) 108 | { 109 | tid = sipMsg->getTransactionId(); 110 | bool garbage=false; 111 | Data reason; 112 | 113 | if(!sipMsg->header(h_From).isWellFormed()) 114 | { 115 | garbage=true; 116 | reason.append("Malformed From, ",16); 117 | } 118 | 119 | if(!sipMsg->header(h_To).isWellFormed()) 120 | { 121 | garbage=true; 122 | reason.append("Malformed To, ",14); 123 | } 124 | 125 | if(!sipMsg->header(h_CallId).isWellFormed()) 126 | { 127 | garbage=true; 128 | reason.append("Malformed Call-Id, ",19); 129 | } 130 | 131 | if(garbage) 132 | { 133 | if(sipMsg->isRequest() && sipMsg->method()!=ACK) 134 | { 135 | // .bwc. Either we need to trim the last comma off, or make this 136 | // a proper sentence fragment. This is more fun. 137 | reason.append("fix your code!",14); 138 | SipMessage failure; 139 | //makeResponse(failure, *sipMsg, 400, reason); 140 | //sendResponse(failure); 141 | } 142 | InfoLog (<< "Malformed header in message (" << reason << ") - rejecting/discarding: " << *sipMsg); 143 | 144 | // .bwc. Only forge a response when appropriate, but return in any 145 | // case. 146 | return; 147 | } 148 | 149 | // This should be a SUBSCRIBE or NOTIFY request or response 150 | // - update contact 151 | // - add next hop to route set 152 | // - add via 153 | // - add record route 154 | // - forward 155 | // - reduce hop count 156 | if(sipMsg->isRequest() && sipMsg->method()==SUBSCRIBE) 157 | { 158 | DebugLog(<<"Handling a SUBSCRIBE request"); 159 | sipMsg->header(h_RecordRoutes).push_front(NameAddr(mPath)); 160 | sipMsg->header(h_Routes).clear(); // remove ourselves 161 | sipMsg->header(h_Routes).push_front(mSubscriptionRoute); 162 | // some phones send SUBSCRIBE without the Event header, workaround: 163 | if(!sipMsg->exists(h_Event) && mMissingEventHack) 164 | { 165 | sipMsg->header(h_Event).value() = "presence"; 166 | } 167 | // must clear tlsDomain, otherwise the stack tries to send over TLS 168 | // even if the route is not a secure transport 169 | sipMsg->setTlsDomain(""); 170 | Uri& uri = sipMsg->header(h_Contacts).front().uri(); 171 | /*if(uri.exists(p_transport)) 172 | { 173 | StackLog(<<"removing transport parameter from Contact header"); 174 | uri.remove(p_transport); 175 | }*/ 176 | // replace Contact header with From header value so the presence 177 | // server will route the NOTIFY to the registration server which 178 | // will then set the route correctly. 179 | if(mOverrideContactWithAor) 180 | { 181 | uri = sipMsg->header(h_From).uri(); 182 | } 183 | mStack.send(*sipMsg, this); 184 | } 185 | else if(sipMsg->isRequest() && sipMsg->method()==NOTIFY) // FIXME - check event type 186 | { 187 | DebugLog(<<"Handling a NOTIFY request"); 188 | //sipMsg->header(h_RecordRoutes).push_front(NameAddr(mPath)); 189 | sipMsg->header(h_Routes).pop_front(); // remove ourselves 190 | //sipMsg->header(h_Routes).push_front(mSubscriptionRoute); 191 | // must clear tlsDomain, otherwise the stack tries to send over TLS 192 | // even if the route is not a secure transport 193 | //sipMsg->setTlsDomain(""); 194 | mStack.send(*sipMsg, this); 195 | } 196 | else if(sipMsg->isResponse() && (sipMsg->header(h_CSeq).method() == SUBSCRIBE || 197 | sipMsg->header(h_CSeq).method() == NOTIFY)) 198 | { 199 | DebugLog(<<"Handling a response"); 200 | //sipMsg->header(h_Vias).pop_front(); 201 | sipMsg->header(h_Vias).front().remove(p_rport); // FIXME - working around rport issue between edge proxy and SBC 202 | mStack.send(*sipMsg, this); 203 | } 204 | else 205 | { 206 | WarningLog(<<"Message is not a supported request or response, ignoring it"); 207 | return; 208 | } 209 | } 210 | 211 | } 212 | } 213 | 214 | 215 | /* ==================================================================== 216 | * 217 | * Copyright 2016 Daniel Pocock http://danielpocock.com All rights reserved. 218 | * 219 | * Redistribution and use in source and binary forms, with or without 220 | * modification, are permitted provided that the following conditions 221 | * are met: 222 | * 223 | * 1. Redistributions of source code must retain the above copyright 224 | * notice, this list of conditions and the following disclaimer. 225 | * 226 | * 2. Redistributions in binary form must reproduce the above copyright 227 | * notice, this list of conditions and the following disclaimer in 228 | * the documentation and/or other materials provided with the 229 | * distribution. 230 | * 231 | * 3. Neither the name of the author(s) nor the names of any contributors 232 | * may be used to endorse or promote products derived from this software 233 | * without specific prior written permission. 234 | * 235 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 236 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 237 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 238 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 239 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 240 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 241 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 242 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 243 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 244 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 245 | * SUCH DAMAGE. 246 | * 247 | * ==================================================================== 248 | * 249 | * 250 | */ 251 | 252 | -------------------------------------------------------------------------------- /SubscriptionForwarder.hxx: -------------------------------------------------------------------------------- 1 | #ifndef SUBSCRIPTIONFORWARDER_HXX 2 | #define SUBSCRIPTIONFORWARDER_HXX 3 | 4 | #if defined(HAVE_CONFIG_H) 5 | #include "config.h" 6 | #endif 7 | 8 | #include "rutil/ConfigParse.hxx" 9 | #include "rutil/Data.hxx" 10 | #include "resip/stack/NameAddr.hxx" 11 | #include "resip/stack/SipStack.hxx" 12 | #include "resip/stack/TransactionUser.hxx" 13 | 14 | namespace reconserver 15 | { 16 | 17 | class SubscriptionForwarder : public resip::TransactionUser 18 | { 19 | public: 20 | SubscriptionForwarder(resip::ConfigParse& cp, resip::SipStack& stack); 21 | virtual ~SubscriptionForwarder(); 22 | 23 | bool process(resip::Lockable* mutex = 0); 24 | 25 | virtual const resip::Data& name() const; 26 | 27 | private: 28 | void internalProcess(std::auto_ptr); 29 | 30 | resip::SipStack& mStack; 31 | 32 | unsigned int mMaxExpiry; 33 | resip::Data mPath; 34 | resip::NameAddr mSubscriptionRoute; 35 | bool mMissingEventHack; 36 | bool mOverrideContactWithAor; 37 | 38 | typedef enum 39 | { 40 | Running, 41 | ShutdownRequested, // while ending usages 42 | RemovingTransactionUser, // while removing TU from stack 43 | Shutdown, // after TU has been removed from stack 44 | Destroying // while calling destructor 45 | } ShutdownState; 46 | ShutdownState mShutdownState; 47 | }; 48 | 49 | } 50 | 51 | #endif 52 | 53 | /* ==================================================================== 54 | * 55 | * Copyright 2016 Daniel Pocock http://danielpocock.com All rights reserved. 56 | * 57 | * Redistribution and use in source and binary forms, with or without 58 | * modification, are permitted provided that the following conditions 59 | * are met: 60 | * 61 | * 1. Redistributions of source code must retain the above copyright 62 | * notice, this list of conditions and the following disclaimer. 63 | * 64 | * 2. Redistributions in binary form must reproduce the above copyright 65 | * notice, this list of conditions and the following disclaimer in 66 | * the documentation and/or other materials provided with the 67 | * distribution. 68 | * 69 | * 3. Neither the name of the author(s) nor the names of any contributors 70 | * may be used to endorse or promote products derived from this software 71 | * without specific prior written permission. 72 | * 73 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 74 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 75 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 76 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 77 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 78 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 79 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 80 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 81 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 82 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 83 | * SUCH DAMAGE. 84 | * 85 | * ==================================================================== 86 | * 87 | * 88 | */ 89 | 90 | -------------------------------------------------------------------------------- /build/resip-ld: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this is a convenience script to help configure the linker to run 4 | # binaries linked against a local build of the reSIProcate source tree, 5 | # typically when using an unreleased version of the reSIProcate tree 6 | # from the repository 7 | 8 | if [ -z "$RESIP_SRC" ]; 9 | then 10 | echo "Please set RESIP_SRC" 11 | exit 1 12 | fi 13 | 14 | export LD_LIBRARY_PATH="${RESIP_SRC}/rutil/.libs:${RESIP_SRC}/resip/stack/.libs:${RESIP_SRC}/resip/dum/.libs:${RESIP_SRC}/reTurn/client/.libs:${RESIP_SRC}/reflow/.libs:${RESIP_SRC}/resip/recon/.libs:${LD_LIBRARY_PATH}" 15 | 16 | -------------------------------------------------------------------------------- /build/resip-src: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this is a convenience script to help configure the tree for a build 4 | # against a reSIProcate source tree, typically when using an unreleased 5 | # version of the reSIProcate tree from the repository 6 | 7 | if [ -z "$RESIP_SRC" ]; 8 | then 9 | echo "Please set RESIP_SRC" 10 | exit 1 11 | fi 12 | 13 | ./configure \ 14 | CPPFLAGS="-I/usr/include/sipxtapi -D__pingtel_on_posix__ -D_linux_ -D_REENTRANT -D_FILE_OFFS -DDEFAULT_BRIDGE_MAX_IN_OUTPUTS=20 -DUSE_SSL -I${RESIP_SRC} -I${RESIP_SRC}/resip" \ 15 | CXXFLAGS="-fpermissive" \ 16 | LDFLAGS="-L${RESIP_SRC}/rutil/.libs -L${RESIP_SRC}/resip/stack/.libs -L${RESIP_SRC}/resip/dum/.libs -L${RESIP_SRC}/reTurn/client/.libs -L${RESIP_SRC}/reflow/.libs -L${RESIP_SRC}/resip/recon/.libs" 17 | 18 | -------------------------------------------------------------------------------- /build/travis/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | autoreconf --install 6 | 7 | -------------------------------------------------------------------------------- /build/travis/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | make 6 | 7 | make check 8 | 9 | -------------------------------------------------------------------------------- /build/travis/configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ./configure \ 6 | CPPFLAGS="-I/usr/include/sipxtapi -D__pingtel_on_posix__ -D_linux_ -D_REENTRANT -D_FILE_OFFS -DDEFAULT_BRIDGE_MAX_IN_OUTPUTS=20 -DUSE_SSL" \ 7 | CXXFLAGS="-fpermissive" 8 | 9 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | 2 | AC_INIT(reConServer,0.16.0) 3 | AC_CONFIG_SRCDIR(reConServer.cxx) 4 | 5 | AC_CONFIG_AUX_DIR([build-aux]) 6 | AC_CONFIG_MACRO_DIR([m4]) 7 | AM_INIT_AUTOMAKE 8 | 9 | AC_PROG_CC 10 | AC_PROG_CXX 11 | 12 | #AC_SEARCH_LIBS doesn't really support C++, ... 13 | #AC_SEARCH_LIBS(, dum, , [ 14 | # AC_MSG_ERROR([unable to find dum(), install libdum and/or set LDFLAGS]) 15 | #]) 16 | 17 | AC_OUTPUT(Makefile) 18 | 19 | -------------------------------------------------------------------------------- /playback_prompt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resiprocate/reConServer/b3a1c47cce7a71c12fffc77e863ed1d673a08a2b/playback_prompt.wav -------------------------------------------------------------------------------- /reConServer.8: -------------------------------------------------------------------------------- 1 | .TH reConServer 8 "August 2013" 2 | .SH NAME 3 | reConServer \- reConServer SIP conferencing server 4 | .SH SYNOPSIS 5 | .B 6 | reConServer [OPTIONS...] 7 | 8 | .SH DESCRIPTION 9 | .B reConServer 10 | is an open-source, free SIP conferencing server. SIP is changing the way 11 | people communicate using the Internet. It is not only about making phone 12 | calls over the Net. The SIP protocol and its extensions defines the way of 13 | establishing, modifying and ending interactive sessions, no matter if they 14 | are voice, video, IM or a combination of them. At the heart of SIP 15 | architecture, there are certain services which needs to be provided at some 16 | place of the network. 17 | .B reConServer 18 | provides SIP conferencing services. These services enable audio conferencing 19 | between multiple participants. 20 | 21 | .SH OPTIONS 22 | .TP 23 | \fB\-l\fR, \fB\-\-log\-type\fR=\fBcout\fR|\fBsyslog\fR|\fBcerr\fR 24 | Set where to send logging messages. The default \fBcout\fR sends the 25 | messages to standard output, \fBcerr\fR uses the standard error pipe. 26 | With \fBsyslog\fR the messages are send to the system log using the logging 27 | facility \fBlocal6\fR. See 28 | .BR syslogd (8) 29 | or 30 | .BR syslog.conf (5) 31 | for more information. 32 | .TP 33 | \fB\-v\fR, \fB\-\-log\-level\fR=\fBSTACK\fR|\fBDEBUG\fR|\fBINFO\fR|\fBWARNING\fR|\fBALERT\fR 34 | Set the minimum level a logging message needs to have in order to by 35 | written. The severity rises from left to right in the above order. The 36 | default is to log messages with level \fBINFO\fR or above. 37 | .TP 38 | \fB\-r\fR, \fB\-\-record\-route\fR=\fIsip:example.com\fR 39 | Activate record routing. The argument is the URI used in the Record-Route 40 | header field. 41 | .TP 42 | \fB\-\-udp\fR=\fI5060\fR 43 | Specify the port to use for the UDP transport. Set to 0 to turn 44 | UDP off. Defaults to port 5060. 45 | .TP 46 | \fB\-\-tcp\fR=\fI5060\fR 47 | Specify the port to use for the TCP transport. Set to 0 to turn TCP 48 | off. Defaults to port 5060. 49 | .TP 50 | \fB\-t\fR, \fB\-\-tls\-domain\fR=\fIexample.com\fR 51 | Act as a TLS server for specified domain. 52 | .TP 53 | \fB\-\-tls\fR=\fI5061\fR 54 | Set the port to use for the TLS transport. Set to 0 to turn TLS off. 55 | Defaults to port 5061. 56 | .TP 57 | \fB\-\-dtls\fR=\fI0\fR 58 | Set the port to use for the DTLS transport. Set to 0 to turn DTLS off 59 | which is the default. 60 | .TP 61 | \fB\-\-enable\-cert\-server 62 | Run a cert server. 63 | .TP 64 | \fB\-c\fR, \fB\-\-cert\-path\fR=\fISTRING\fR 65 | Set the path for certificates. Defaults to \fB~/.sipCerts\fR. 66 | .TP 67 | \fB\-\-enable\-v6\fR 68 | Enable IPv6. 69 | .TP 70 | \fB\-\-disable\-v4\fR 71 | Disable IPv4. 72 | .TP 73 | \fB\-\-disable\-auth\fR 74 | Disable DIGEST challenges for certain SIP requests. 75 | .TP 76 | \fB\-\-disable\-web\-auth\fR 77 | Disable authentication for the web administration server. 78 | .TP 79 | \fB\-\-disable\-reg\fR 80 | Disable the registrar. 81 | .TP 82 | \fB\-i\fR, \fB\-\-interfaces\fR=\fIsip:10.1.1.1:5065;transport=tls\fR 83 | Specify interfaces to add transports to. Each transport is given as a 84 | SIP-URI with the IP address and port of the local interface as the 85 | hostport part and the transport protocol as a \fBtransport\fR parameter. 86 | Several transports are separated by a comma. 87 | 88 | For example, to set up two transports, one for TLS and one for UDP use 89 | .B sip:192.168.1.200:5060;transport=tls,sip:192.168.1.200:5060;transport=udp 90 | .TP 91 | \fB\-d\fR, \fB\-\-domains\fR=\fIexample.com,foo.com\fR 92 | Specify the list of domains this proxy is authorative for separated by 93 | comma. 94 | .TP 95 | \fB\-R\fR, \fB\-\-route\fR=\fIsip:p1.example.com,sip:p2.example.com\fR 96 | Specify a route set where all requests this proxy is authorative for are 97 | sent to. Using this option overides the routes set through the web 98 | interface. 99 | .TP 100 | \fB\-\-reqChainName\fR=\fISTRING\fR 101 | Name of request chain used for processing requests. Currently, the only 102 | chain available is \fBdefault\fR. 103 | .TP 104 | \fB\-\-http\fR=\fI5080\fR 105 | Specify the port used by the HTTP server. Defaults to 5080. 106 | .TP 107 | \fB\-\-recursive\-redirect\fR 108 | Enable to handle of 3xx responses in the proxy. 109 | .TP 110 | \fB\-\-q\-value\fR 111 | Enable q-value processing. The q-value can be given in registrations and is 112 | used by the location server to order forwarding for multiple registrations. 113 | Without this option a request is forked to all registrations. 114 | .TP 115 | \fB\-\-q\-value\-behavior\fR=\fBFULL_SEQUENTIAL\fR|\fBEQUAL_Q_PARALLEL\fR|\fBFULL_PARALLEL\fR 116 | Specify forking behavior if \fB\-\-q\-value\fR is given. 117 | With \fBFULL_SEQUENTIAL\fR one target is called after another in the order 118 | of their q values. Using \fBEQUAL_Q_PARALLEL\fR (the default), the request 119 | is sent in 120 | batches of all registrations with the same q value. The batches are again 121 | ordered by the q value. 122 | \fBFULL_PARALLEL\fR causes the request to be sent in parallel to all 123 | registrations. 124 | .TP 125 | \fB\-\-q\-value\-cancel\-btw\-fork\-groups\fR 126 | If given, groups of parallel forks are canceled after the period specified 127 | by the \fB\-\-q\-value\-ms\-before\-cancel\fR option if no response was 128 | received. 129 | .TP 130 | \fB\-\-q\-value\-wait\-for\-terminate\-btw\-fork\-groups\fR 131 | If given, the proxy waits for groups of parallel forks to terminate before 132 | a new group is started. Otherwise, the next group is started once the 133 | waiting time has passed. 134 | .TP 135 | \fB\-\-q\-value\-ms\-between\-fork\-groups\fR=\fIINT\fR 136 | Specify the number of milliseconds to wait for a response from a group of 137 | parallel forks before starting another group. 138 | .TP 139 | \fB\-\-q\-value\-ms\-before\-cancel\fR=\fIINT\fR 140 | Specify the number of milliseconds to wait before cancelling a group of 141 | parallel forks if the \fB\-\-q\-value\-cancel\-btw\-fork\-groups\fR option 142 | is given. 143 | .TP 144 | \fB\-e\fR, \fB\-\-enum\-suffix\fR=\fIe164.arpa\fR 145 | Specify enum suffix to search. 146 | .TP 147 | \fB\-b\fR, \fB\-\-allow\-bad\-reg\fR 148 | Allow To tag in registrations. 149 | .TP 150 | \fB\-\-timer\-C\fR=\fI180\fR 151 | Specify the length of timer C in sec which specifies the time a proxy 152 | waits before generating a timeout response. Set to 0 or a negative value 153 | to turn timer C of completely. 154 | .TP 155 | \fB\-\-admin\-password\fR=\fISTRING\fR 156 | Set the web administrator password. 157 | .PP 158 | Help options: 159 | .TP 160 | \fB\-?\fR, \fB\-\-help\fR 161 | Show a help message. 162 | .TP 163 | \fB\-\-usage 164 | Display brief usage message. 165 | .TP 166 | \fB\-\-version\fR 167 | Display the version information. 168 | 169 | .SH SEE ALSO 170 | Repro web site at 171 | .B https://https://github.com/resiprocate/reConServer 172 | 173 | .\".SH AUTHORS 174 | 175 | .\".SH BUGS 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /reConServer.config: -------------------------------------------------------------------------------- 1 | ######################################################## 2 | # reConServer configuration file 3 | ######################################################## 4 | 5 | ######################################################## 6 | # Logging settings 7 | ######################################################## 8 | 9 | # Logging Type: syslog|cerr|cout|file 10 | LoggingType = file 11 | 12 | # Logging level: NONE|CRIT|ERR|WARNING|INFO|DEBUG|STACK 13 | LoggingLevel = WARNING 14 | 15 | # Log Filename 16 | LogFilename = /var/log/reConServer/reConServer.log 17 | 18 | # Log file Max Size 19 | LogFileMaxLines = 0 20 | 21 | # The B2BUA CDR log filename 22 | CDRLogFile = /var/log/reConServer/cdr.csv 23 | 24 | # Specify the HOMER SIP capture server hostname 25 | # If CaptureHost is commented/not defined, there is no default value and 26 | # reConServer doesn't attempt to send any HEP packets. 27 | #CaptureHost = localhost 28 | 29 | # Specify the HOMER SIP capture server UDP port 30 | # If not defined, the default value, 9060, is used 31 | #CapturePort = 9060 32 | 33 | # Specify the HOMER Capture Agent ID 34 | # The default value is 2002 35 | CaptureAgentID = 2002 36 | 37 | ######################################################## 38 | # Transport settings 39 | ######################################################## 40 | 41 | 42 | # Local IP Address to bind SIP transports to. 43 | # In general the IP address to bind to is queried from the host OS.This switch allows specification 44 | # of the IP address for OS's that cannot be queried, or for machines that have mulitple NICs. 45 | #IPAddress = 192.168.1.106 46 | #IPAddress = 2001:5c0:1000:a::6d 47 | IPAddress = 48 | 49 | # Local port number to use for SIP messaging over TCP - 0 to disable 50 | TCPPort = 5062 51 | 52 | # Local port number to use for SIP messaging over UDP - 0 to disable 53 | UDPPort = 5062 54 | 55 | # Local port number to use for TLS SIP messaging - 0 to disable 56 | # By default, reConServer will listen for TLS connections on port 5063, use this switch to specify 57 | # something different. Note SIP certificiates must be present in executable directory for windows 58 | # hosts and ~/.sipCerts directory on linux hosts. 59 | TLSPort = 0 60 | 61 | # Alternate and more flexible method to specify transports to bind to. If specified here 62 | # then IPAddress, and port settings above are ignored. 63 | # Possible settings are: 64 | # TransportInterface = : - Note: For IPv6 addresses last colon separates 65 | # IP Address and Port - square bracket notation 66 | # is not used. 67 | # TransportType = <'TCP'|'UDP'|'TLS'|'DTLS'|'WS'|'WSS'> - default is UDP if missing 68 | # TransportTlsDomain = - only required if transport is TLS, DTLS or WSS 69 | # TransportTlsCertificate = - only for TLS, DTLS or WSS 70 | # TransportTlsPrivateKey = - only for TLS, DTLS or WSS 71 | # TransportTlsPrivatePassPhrase = - only for TLS, DTLS or WSS 72 | # when private key has passwd 73 | # TransportTlsClientVerification = <'None'|'Optional'|'Mandatory'> - default is None 74 | # TransportTlsConnectionMethod = <'TLSv1'|'SSLv23'> - default is SSLv23 75 | # 76 | # TransportRcvBufLen = - currently only applies to UDP transports, 77 | # leave empty to use OS default 78 | # Example: 79 | # Transport1Interface = 192.168.1.106:5060 80 | # Transport1Type = TCP 81 | # 82 | # Transport2Interface = 192.168.1.106:5060 83 | # Transport2Type = UDP 84 | # Transport2RcvBufLen = 10000 85 | # 86 | # Transport3Interface = 192.168.1.106:5061 87 | # Transport3Type = TLS 88 | # Transport3TlsDomain = sipdomain.com 89 | # Transport3TlsCertificate = /etc/ssl/crt/sipdomain.com.crt 90 | # Transport3TlsPrivateKey = /etc/ssl/private/sipdomain.com.key 91 | # Transport3TlsPrivateKeyPassPhrase = password 92 | # Transport3TlsClientVerification = Mandatory 93 | # 94 | # Transport4Interface = 2666:f0d0:1008:88::4:5060 95 | # Transport4Type = UDP 96 | 97 | # Transport5Interface = 192.168.1.106:5062 98 | # Transport5Type = WS 99 | 100 | # Transport6Interface = 192.168.1.106:5063 101 | # Transport6Type = WSS 102 | # Transport6TlsDomain = sipdomain.com 103 | # Transport6TlsClientVerification = None 104 | 105 | 106 | # Local port number to start allocating from for RTP media 107 | MediaPortStart = 17384 108 | 109 | 110 | # URI of a proxy server to use a SIP outbound proxy. 111 | # By default reConServer does not use an outbound proxy. Use this switch to route all 112 | # outbound, out-of-dialog requests through a fixed proxy despite the destination URI. 113 | OutboundProxyUri = 114 | 115 | # By default, no secure media is offered in outbound SIP requests. Use this option to 116 | # change that behaviour. Note: Inbound secure media is always accepted. 117 | # None - do not include any secure media in offer 118 | # Srtp - use SRTP with keying outside of media stream (SDES key negotiation) 119 | # via SDP. RTP/AVP profile is used, and transport capability of RTP/SAVP is 120 | # listed, in order to implement best-effort SRTP. Note: The crypo attribute 121 | # is provided outside of the SDP capability, as this is required by SNOM for 122 | # optional SRTP offers. 123 | # SrtpReq - use SRTP with keying outside of media stream (SDES key negotiation) 124 | # via SDP. RTP/SAVP profile is used to indicate that SRTP is mandatory. 125 | # SrtpDtls - use SRTP with DTLS key negotiation. RTP/AVP is use as a default, and a 126 | # transport capability of UDP/TLS/RTP/SAVP is listed, in order to impelement 127 | # best-effort DTLS-SRTP. 128 | # SrtpDtlsReq - use SRTP with DTLS key negotiation. UDP/TLS/RTP/SAVP profile is used to 129 | # indicate that Dtls-Srtp use is mandatory. 130 | #SecureMediaMode = 131 | 132 | # By default, no NAT traversal strategies are used. Use this switch to specify one: 133 | # None - do not use any NAT traversal strategy (this is the default) 134 | # Bind - use Binding discovery on a STUN server, to discover and use "public" address 135 | # and port in SDP negotiations 136 | # UdpAlloc - Use a TURN server as a media relay. Communicate to the TURN 137 | # server over UDP and Allocate a UDP relay address and port to 138 | # use in SDP negotiations 139 | # TcpAlloc - Use a TURN server as a media relay. Communicate to the TURN 140 | # server over TCP and Allocate a UDP relay address and port to 141 | # use in SDP negotiations 142 | # TlsAlloc - Use a TURN server as a media relay. Communicate to the TURN 143 | # server over TLS and Allocate a UDP relay address and port to 144 | #NatTraversalMode = 145 | 146 | # If the peer's SDP contains an RFC 1918 private IP address and the peer 147 | # sends to us from a different IP/port, it is usually a sign that the peer 148 | # is behind NAT and we should send to the IP/port they are sending from 149 | # and ignore the IP/port in their SDP. Comedia mode enables this behavior, 150 | # it is essential for users behind NAT who don't fully support STUN or TURN. 151 | ForceCOMedia = true 152 | 153 | # Hostname of the NAT STUN/TURN server. If NatTraversalMode is used then you MUST specify the 154 | # STUN/TURN server name/address. 155 | NatTraversalServerHostname = 156 | 157 | # Port of the NAT STUN/TURN server. If NatTraversalMode is used then you MUST specify the STUN/TURN 158 | # server port. 159 | # Default for UDP and TCP is 3478 and for TURN over TLS it is 5349 160 | NatTraversalServerPort = 3478 161 | 162 | # Add rport to the Via header we insert in requests 163 | # This is enabled by default but in some situations, such as working around 164 | # bug #137, it may be desirable to disable it. 165 | AddViaRport = true 166 | 167 | ######################################################## 168 | # Authentication settings 169 | ######################################################## 170 | 171 | # STUN/TURN username to use for NAT server. Use this option if the STUN/TURN server requires 172 | # authentication. 173 | StunUsername = 174 | 175 | # STUN/TURN password to use for NAT server. Use this option if the STUN/TURN server requires 176 | # authentication. 177 | StunPassword = 178 | 179 | ######################################################## 180 | # UNIX related settings 181 | ######################################################## 182 | 183 | # Must be true or false, default = false, not supported on Windows 184 | Daemonize = false 185 | 186 | # On UNIX it is normal to create a PID file 187 | # if unspecified, no attempt will be made to create a PID file 188 | #PidFile = /var/run/reConServer/reConServer.pid 189 | 190 | # UNIX account information to run process as 191 | #RunAsUser = recon 192 | #RunAsGroup = recon 193 | 194 | ######################################################## 195 | # Misc settings 196 | ######################################################## 197 | 198 | # Value of server and user agent headers for local UAS 199 | # 200 | # Default value is "reConServer PACKAGE_VERSION" if PACKAGE_VERSION is defined 201 | # during compilation and "reConServer" otherwise 202 | # 203 | #ServerText = reConServer 204 | 205 | # This option is used to specify the SIP URI for this instance of reConServer. reConServer uses 206 | # this setting (-ip is not specified) in order to find the regisration server. If 207 | # nothing is specified, then the default of sip:noreg@ will be used. 208 | SIPUri = 209 | 210 | # SIP password of this SIP user 211 | # Use this switch in cases where the proxy digest challenges sip messaging. 212 | Password = 213 | 214 | # Comma separated list of DNS servers, overrides default OS detected list (leave blank 215 | # for default) 216 | DNSServers = 217 | 218 | # Domain name to use for TLS server connections. 219 | # By default, reConServer will query the OS for a local hostname for TLS, use this switch 220 | # to override the OS queried result. 221 | TLSDomain = 222 | 223 | # Whether we accept the subjectAltName email address as if it was a SIP 224 | # address (when checking the validity of a client certificate) 225 | # Very few commercial CAs offer support for SIP addresses in subjectAltName 226 | # For many purposes, an email address subjectAltName may be considered 227 | # equivalent within a specific domain. 228 | # Currently, this accepts such certs globally (for any incoming connection), 229 | # not just for connections from the local users. 230 | TLSUseEmailAsSIP = false 231 | 232 | # Path to load certificates from (optional, there is no default) 233 | # Note that reConServer loads ALL root certificates found by any of the settings 234 | # 235 | # CADirectory 236 | # CAFile 237 | # CertificatePath 238 | # 239 | # Setting one option does not disable the other options. 240 | # 241 | # Path to load root certificates from 242 | # Iff this directory is specified, all files in the directory 243 | # will be loaded as root certificates, prefixes and suffixes are 244 | # not considered 245 | # Note that reConServer loads ALL root certificates found by the settings 246 | # CertificatePath, CADirectory and CAFile. Setting one option does 247 | # not disable the other options. 248 | # On Debian, the typical location is /etc/ssl/certs 249 | # On Red Hat/CentOS, there isn't a directory like this. 250 | #CADirectory = /etc/ssl/certs 251 | 252 | # Specify a single file containing one or more root certificates 253 | # and possible chain/intermediate certificates to be loaded 254 | # Iff this filename is specified, the certificates in the file will 255 | # be loaded as root certificates 256 | # 257 | # This option is typically used to load a bundle of certificates 258 | # such as /etc/ssl/certs/ca-certificates.crt on Debian and 259 | # /etc/pki/tls/cert.pem on Red Hat/CentOS 260 | # 261 | # Note that reConServer loads ALL root certificates found by the settings 262 | # CertificatePath, CADirectory and CAFile. Setting one option does 263 | # not disable the other options. 264 | # 265 | # Uncomment for Debian/Ubuntu: 266 | #CAFile = /etc/ssl/certs/ca-certificates.crt 267 | # Uncomment for Fedora, Red Hat, CentOS: 268 | #CAFile = /etc/pki/tls/cert.pem 269 | 270 | # Certificates in this location have to match one of the filename 271 | # patterns expected by the legacy reSIProcate SSL code: 272 | # 273 | # domain_cert_NAME.pem, root_cert_NAME.pem, ... 274 | # 275 | # For domain certificates, it is recommended to use the options 276 | # for individual transports, such as TransportXTlsCertificate and 277 | # TransportXTlsPrivateKey and not set CertificatePath at all. 278 | # 279 | CertificatePath = 280 | 281 | # Set this to disable registration with SIP Proxy. 282 | # By default, if a SIP uri is specified, reConServer will attempt to register with it 283 | DisableRegistration = false 284 | 285 | # Enabling autoanswer will cause reConServer to automatically answer any inbound SIP calls 286 | # and place them in the lowest numbered conversation currently created. 287 | EnableAutoAnswer = true 288 | 289 | # Set this to disable sending of keepalives. 290 | # By default, reConServer will enable UDP CRLF keepalives every 30 seconds and TCP keepalives 291 | # every 180 seconds. Use this switch to disable CRLF keepalives. 292 | DisableKeepAlives = false 293 | 294 | # Local audio support - enables requirement for local audio hardware. 295 | # Note: if local audio support is disabled, then local participants cannot be created. 296 | EnableLocalAudio = false 297 | 298 | # Keyboard control from stdin 299 | # Only permitted when run in the foreground (not as a daemon process) 300 | KeyboardInput = true 301 | 302 | # Whether to use a global media interface (bridge) for all conferences/conversations or 303 | # a distinct media interface instance for each conference/conversation 304 | # The global approach allows participants to be engaged in more than one concurrent 305 | # conversation but it also limits the total number of participants in the system 306 | # at one time. 307 | # For servers hosting multiple conferences, the global media interface is usually 308 | # not desirable so it is disabled by default 309 | GlobalMediaInterface = false 310 | 311 | # The default and maximum sample rate to use when creating flow graphs 312 | # in sipXtapi. 313 | # For cases where narrowband audio is used (G.711a or G.711u), 314 | # the default value, 8000hz, is appropriate. 315 | # For users wanting HD audio (for example, with the G.722 or Opus codecs), 316 | # it is highly desirable to set these to the value associated with 317 | # the codec, for example: 318 | # 319 | # G.722: 16000 320 | # Opus: 48000 321 | # 322 | # If the internal sample rate (specified here) is not consistent with 323 | # the sample rate used by the participants the sipXtapi media stack 324 | # will convert the sample rate of the signals on the fly but this has an 325 | # impact on CPU load. 326 | # For sipXtapi resampling to work, sipXtapi must be linked against libspeexdsp 327 | # (even if not using speex). 328 | DefaultSampleRate = 8000 329 | MaximumSampleRate = 8000 330 | 331 | # Enable the G.722 codec 332 | # G.722 is a high-definition (HD) voice codec supported by many 333 | # desk phones and softphones. It uses the same amount of bandwidth 334 | # as G.711 while increasing the audio spectrum from 3khz to about 7khz. 335 | # To use it, it is usually desirable to set the sample rate to 16000 336 | # instead of the default 8000 337 | # It is disabled by default. 338 | #EnableG722 = true 339 | 340 | # Enable the Opus codec 341 | # Opus is a variable bit-rate voice codec supported by many 342 | # modern softphones and WebRTC browsers. 343 | # To use it, it is highly desirable to set the sample rate to 48000 344 | # instead of the default 8000 345 | # It is disabled by default. 346 | #EnableOpus = true 347 | 348 | # Specify the application to run 349 | # The default value is "None" - in this mode, the creation and manipulation 350 | # of conversations/conferences is done manually using the console 351 | # or if auto answer is enabled, all callers simply end up in a single bridge. 352 | # One additional application is provided: 353 | # B2BUA: with this application, every incoming SIP call automatically 354 | # creates a new conversation and a new outgoing call (B-leg). 355 | # The incoming call will receive answer and alerting signals 356 | # as they are received from the B-leg. DTMF tones 357 | # are relayed from either leg to the other. 358 | #Application = B2BUA 359 | 360 | # Specify where the B2BUA should route calls 361 | # If B2BUA mode is enabled, the B2BUA will create the B-leg of the call 362 | # using the Request URI, From and To headers of the A-leg and a Route 363 | # header for the next hop specified here. The next hop URI should include 364 | # the "sip:" prefix and the ";lr" parameter to specify loose routing. 365 | #B2BUANextHop = sip:sip-host.example.org;transport=tcp;lr 366 | 367 | # Specify headers that will be copied to the outgoing INVITE 368 | # A comma-separated list of extension headers that will be copied 369 | # from the incoming INVITE to the outgoing INVITE 370 | #B2BUAReplicateHeaders = X-Customer-ID, X-Ticket-Number 371 | 372 | # Specify IP addresses of hosts in the internal zone for the B2BUA 373 | #B2BUAInternalHosts = 192.168.1.100, 192.168.1.101 374 | 375 | # Specify TLS names (in certificates) of hosts in the internal zone for B2BUA 376 | #B2BUAInternalTLSNames = sip.example.org 377 | 378 | # Specify whether all hosts connecting to us directly (not through NAT) 379 | # with private IP addresses (RFC 1918 and RFC 4193) should be considered as 380 | # part of the internal zone 381 | #B2BUAInternalAllPrivate = false 382 | 383 | # Specify the local address to use for media on the internal zone 384 | # If not specified, will try to use the default address for the external zone 385 | #B2BUAInternalMediaAddress = 10.0.1.1 386 | 387 | # Specify the secure media mode for B2BUA-initiated connections in 388 | # the internal zone. Accepts the same values as SecureMediaMode above. 389 | # Default value is the setting chosen for SecureMediaMode. 390 | #B2BUAInternalSecureMediaMode = 391 | 392 | # Specify the maximum expiry time for REGISTER requests forwarded 393 | # to the internal registrar. If the Expires header value exceeds 394 | # this limit, it will be reduced to the limit. 395 | # Default value is 60 396 | #B2BUARegistrationForwardMaxExpiry = 60 397 | 398 | # Specify the URI to place in the Path header for REGISTER requests 399 | # forwarded to the internal registrar. 400 | #B2BUARegistrationForwardPath = sip:sip-sbc.example.org;transport=tcp;lr 401 | 402 | # Specify the URI of the proxy where REGISTER requests should be 403 | # forwarded. 404 | # If not set, defaults to the value specified in B2BUANextHop 405 | #B2BUARegistrationForwardRoute = sip:reg-server.example.org:15060;transport=tcp;lr 406 | 407 | # Some phones send a SUBSCRIBE without an Event header. If this 408 | # flag is set, the following header will be added: Event: presence 409 | #B2BUASubscriptionMissingEventHack = false 410 | 411 | # Some presence servers (for example, old versions of Asterisk) do not 412 | # correctly set the Route headers when sending a NOTIFY. In these cases, 413 | # if there is a separate registration proxy, it will be able to add the 414 | # missing Route headers. To ensure the presence server sends the NOTIFY 415 | # to the registration proxy, set this flag to true. 416 | #B2BUASubscriptionOverrideContactWithAor = false 417 | 418 | # Specify the name of a file containing From addresses of users and the 419 | # passwords they use to authenticate to hosts in the internal zone. 420 | # If hosts in the internal zone are configured to trust requests from the 421 | # B2BUA's IP or domain then requests won't be challenged and this file 422 | # is not required. 423 | #B2BUAUsersFilename = b2busers.txt 424 | -------------------------------------------------------------------------------- /reConServer.cxx: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #ifdef WIN32 7 | #include 8 | #else 9 | /** 10 | Linux (POSIX) implementation of _kbhit(). 11 | Morgan McGuire, morgan@cs.brown.edu 12 | */ 13 | #include 14 | #include 15 | #include 16 | #ifndef __GNUC__ 17 | #include 18 | #endif 19 | #include 20 | 21 | int _kbhit() { 22 | static const int STDIN = 0; 23 | static bool initialized = false; 24 | 25 | if (! initialized) { 26 | // Use termios to turn off line buffering 27 | termios term; 28 | tcgetattr(STDIN, &term); 29 | term.c_lflag &= ~ICANON; 30 | tcsetattr(STDIN, TCSANOW, &term); 31 | setbuf(stdin, NULL); 32 | initialized = true; 33 | } 34 | 35 | int bytesWaiting; 36 | ioctl(STDIN, FIONREAD, &bytesWaiting); 37 | return bytesWaiting; 38 | } 39 | #endif 40 | 41 | #include "resip/stack/InteropHelper.hxx" 42 | #include "recon/UserAgent.hxx" 43 | #include "AppSubsystem.hxx" 44 | #include 45 | 46 | #include 47 | 48 | #include "reConServerConfig.hxx" 49 | #include "reConServer.hxx" 50 | #include "MyMessageDecorator.hxx" 51 | #include "MyConversationManager.hxx" 52 | #include "B2BCallManager.hxx" 53 | #include "CDRFile.hxx" 54 | #include "RegistrationForwarder.hxx" 55 | 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #include 63 | #include 64 | 65 | using namespace reconserver; 66 | using namespace recon; 67 | using namespace resip; 68 | using namespace flowmanager; 69 | using namespace std; 70 | 71 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 72 | 73 | void sleepSeconds(unsigned int seconds) 74 | { 75 | #ifdef WIN32 76 | Sleep(seconds*1000); 77 | #else 78 | sleep(seconds); 79 | #endif 80 | } 81 | 82 | static bool finished = false; 83 | NameAddr uri("sip:noreg@127.0.0.1"); 84 | bool autoAnswerEnabled = false; // If enabled then reConServer will automatically answer incoming calls by adding to lowest numbered conversation 85 | SharedPtr conversationProfile; 86 | 87 | static void 88 | signalHandler(int signo) 89 | { 90 | std::cerr << "Shutting down" << endl; 91 | finished = true; 92 | } 93 | 94 | int main(int argc, char** argv) 95 | { 96 | ReConServerProcess proc; 97 | return proc.main(argc, argv); 98 | } 99 | 100 | 101 | 102 | 103 | ReConServerProcess::ReConServerProcess() 104 | { 105 | } 106 | 107 | ReConServerProcess::~ReConServerProcess() 108 | { 109 | } 110 | 111 | void ReConServerProcess::processCommandLine(Data& commandline, MyConversationManager& myConversationManager, MyUserAgent& myUserAgent) 112 | { 113 | Data command; 114 | #define MAX_ARGS 5 115 | Data arg[MAX_ARGS]; 116 | ParseBuffer pb(commandline); 117 | pb.skipWhitespace(); 118 | if(pb.eof()) return; 119 | const char *start = pb.position(); 120 | pb.skipToOneOf(ParseBuffer::Whitespace); 121 | pb.data(command, start); 122 | 123 | // Get arguments (up to MAX_ARGS) 124 | int currentArg = 0; 125 | while(!pb.eof() && currentArg < MAX_ARGS) 126 | { 127 | pb.skipWhitespace(); 128 | if(!pb.eof()) 129 | { 130 | const char *start = pb.position(); 131 | pb.skipToOneOf(ParseBuffer::Whitespace); 132 | pb.data(arg[currentArg++], start); 133 | } 134 | } 135 | 136 | // Process commands 137 | if(isEqualNoCase(command, "quit") || isEqualNoCase(command, "q") || isEqualNoCase(command, "exit")) 138 | { 139 | finished=true; 140 | return; 141 | } 142 | if(isEqualNoCase(command, "createconv") || isEqualNoCase(command, "cc")) 143 | { 144 | myConversationManager.createConversation(); 145 | return; 146 | } 147 | if(isEqualNoCase(command, "destroyconv") || isEqualNoCase(command, "dc")) 148 | { 149 | unsigned long handle = arg[0].convertUnsignedLong(); 150 | if(handle != 0) 151 | { 152 | myConversationManager.destroyConversation(handle); 153 | } 154 | else 155 | { 156 | InfoLog( << "Invalid command format: <'destroyconv'|'dc'> "); 157 | } 158 | return; 159 | } 160 | if(isEqualNoCase(command, "joinconv") || isEqualNoCase(command, "jc")) 161 | { 162 | unsigned long handleSrc = arg[0].convertUnsignedLong(); 163 | unsigned long handleDest = arg[1].convertUnsignedLong(); 164 | if(handleSrc != 0 && handleDest != 0) 165 | { 166 | myConversationManager.joinConversation(handleSrc, handleDest); 167 | } 168 | else 169 | { 170 | InfoLog( << "Invalid command format: <'joinconv'|'jc'> "); 171 | } 172 | return; 173 | } 174 | if(isEqualNoCase(command, "createlocal") || isEqualNoCase(command, "clp")) 175 | { 176 | myConversationManager.createLocalParticipant(); 177 | return; 178 | } 179 | if(isEqualNoCase(command, "createremote") || isEqualNoCase(command, "crp")) 180 | { 181 | unsigned long handle = arg[0].convertUnsignedLong(); 182 | ConversationManager::ParticipantForkSelectMode mode = ConversationManager::ForkSelectAutomatic; 183 | if(handle != 0 && !arg[1].empty()) 184 | { 185 | if(!arg[2].empty() && isEqualNoCase(arg[2], "manual")) 186 | { 187 | mode = ConversationManager::ForkSelectManual; 188 | } 189 | try 190 | { 191 | NameAddr dest(arg[1]); 192 | myConversationManager.createRemoteParticipant(handle, dest, mode); 193 | } 194 | catch(...) 195 | { 196 | NameAddr dest(uri); 197 | dest.uri().user() = arg[1]; 198 | myConversationManager.createRemoteParticipant(handle, dest, mode); 199 | } 200 | } 201 | else 202 | { 203 | InfoLog( << "Invalid command format: <'createremote'|'crp'> [<'manual'>] (last arg is fork select mode, 'auto' is default)."); 204 | } 205 | return; 206 | } 207 | if(isEqualNoCase(command, "createmedia") || isEqualNoCase(command, "cmp")) 208 | { 209 | unsigned long handle = arg[0].convertUnsignedLong(); 210 | unsigned long duration = arg[2].convertUnsignedLong(); 211 | if(handle != 0 && !arg[1].empty()) 212 | { 213 | try 214 | { 215 | Uri url(arg[1]); 216 | if(duration != 0) 217 | { 218 | url.param(p_duration) = duration; 219 | } 220 | myConversationManager.createMediaResourceParticipant(handle, url); 221 | } 222 | catch(resip::BaseException& e) 223 | { 224 | InfoLog( << "Invalid url format: <'createmedia'|'cmp'> []: " << e); 225 | } 226 | catch(...) 227 | { 228 | InfoLog( << "Invalid url format: <'createmedia'|'cmp'> []"); 229 | } 230 | } 231 | else 232 | { 233 | //myConversationManager.createMediaResourceParticipant(1, Uri("http://www.sillyhumor.com/answer/helloo.wav")); 234 | InfoLog( << "Invalid command format: <'createmedia'|'cmp'> []"); 235 | } 236 | return; 237 | } 238 | if(isEqualNoCase(command, "destroypart") || isEqualNoCase(command, "dp")) 239 | { 240 | unsigned long handle = arg[0].convertUnsignedLong(); 241 | if(handle != 0) 242 | { 243 | myConversationManager.destroyParticipant(handle); 244 | } 245 | else 246 | { 247 | InfoLog( << "Invalid command format: <'destroypart'|'dp'> "); 248 | } 249 | return; 250 | } 251 | if(isEqualNoCase(command, "addpart") || isEqualNoCase(command, "ap")) 252 | { 253 | unsigned long convHandle = arg[0].convertUnsignedLong(); 254 | unsigned long partHandle = arg[1].convertUnsignedLong(); 255 | if(convHandle != 0 && partHandle != 0) 256 | { 257 | myConversationManager.addParticipant(convHandle, partHandle); 258 | } 259 | else 260 | { 261 | InfoLog( << "Invalid command format: <'addpart'|'ap'> "); 262 | } 263 | return; 264 | } 265 | if(isEqualNoCase(command, "remotepart") || isEqualNoCase(command, "rp")) 266 | { 267 | unsigned long convHandle = arg[0].convertUnsignedLong(); 268 | unsigned long partHandle = arg[1].convertUnsignedLong(); 269 | if(convHandle != 0 && partHandle != 0) 270 | { 271 | myConversationManager.removeParticipant(convHandle, partHandle); 272 | } 273 | else 274 | { 275 | InfoLog( << "Invalid command format: <'removepart'|'rp'> "); 276 | } 277 | return; 278 | } 279 | if(isEqualNoCase(command, "movepart") || isEqualNoCase(command, "mp")) 280 | { 281 | unsigned long partHandle = arg[0].convertUnsignedLong(); 282 | unsigned long srcConvHandle = arg[1].convertUnsignedLong(); 283 | unsigned long dstConvHandle = arg[2].convertUnsignedLong(); 284 | if(partHandle != 0 && srcConvHandle != 0 && dstConvHandle != 0) 285 | { 286 | myConversationManager.moveParticipant(partHandle, srcConvHandle, dstConvHandle); 287 | } 288 | else 289 | { 290 | InfoLog( << "Invalid command format: <'movepart'|'mp'> "); 291 | } 292 | return; 293 | } 294 | if(isEqualNoCase(command, "partcontrib") || isEqualNoCase(command, "pc")) 295 | { 296 | unsigned long convHandle = arg[0].convertUnsignedLong(); 297 | unsigned long partHandle = arg[1].convertUnsignedLong(); 298 | if(partHandle != 0 && convHandle != 0) 299 | { 300 | myConversationManager.modifyParticipantContribution(convHandle, partHandle, arg[2].convertUnsignedLong(), arg[3].convertUnsignedLong()); 301 | } 302 | else 303 | { 304 | InfoLog( << "Invalid command format: <'partcontrib'|'pc'> (gain in percentage)"); 305 | } 306 | return; 307 | } 308 | if(isEqualNoCase(command, "bridgematrix") || isEqualNoCase(command, "bm")) 309 | { 310 | myConversationManager.outputBridgeMatrix(); 311 | return; 312 | } 313 | if(isEqualNoCase(command, "alert") || isEqualNoCase(command, "al")) 314 | { 315 | unsigned long partHandle = arg[0].convertUnsignedLong(); 316 | bool early = true; 317 | if(partHandle != 0) 318 | { 319 | if(!arg[1].empty() && isEqualNoCase(arg[1], "noearly")) 320 | { 321 | early = false; 322 | } 323 | myConversationManager.alertParticipant(partHandle, early); 324 | } 325 | else 326 | { 327 | InfoLog( << "Invalid command format: <'alert'|'al'> [<'noearly'>] (last arg is early flag, enabled by default)"); 328 | } 329 | return; 330 | } 331 | if(isEqualNoCase(command, "answer") || isEqualNoCase(command, "an")) 332 | { 333 | unsigned long partHandle = arg[0].convertUnsignedLong(); 334 | if(partHandle != 0) 335 | { 336 | myConversationManager.answerParticipant(partHandle); 337 | } 338 | else 339 | { 340 | InfoLog( << "Invalid command format: <'answer'|'an'> "); 341 | } 342 | return; 343 | } 344 | if(isEqualNoCase(command, "reject") || isEqualNoCase(command, "rj")) 345 | { 346 | unsigned long partHandle = arg[0].convertUnsignedLong(); 347 | unsigned long status = arg[1].convertUnsignedLong(); 348 | if(partHandle != 0) 349 | { 350 | if(status == 0) status = 486; 351 | myConversationManager.rejectParticipant(partHandle, status); 352 | } 353 | else 354 | { 355 | InfoLog( << "Invalid command format: <'reject'|'rj'> [] (default status code is 486)"); 356 | } 357 | return; 358 | } 359 | if(isEqualNoCase(command, "redirect") || isEqualNoCase(command, "rd")) 360 | { 361 | unsigned long partHandle = arg[0].convertUnsignedLong(); 362 | if(partHandle != 0 && !arg[1].empty()) 363 | { 364 | try 365 | { 366 | NameAddr dest(arg[1]); 367 | myConversationManager.redirectParticipant(partHandle, dest); 368 | } 369 | catch(...) 370 | { 371 | NameAddr dest(uri); 372 | dest.uri().user() = arg[1]; 373 | myConversationManager.redirectParticipant(partHandle, dest); 374 | } 375 | } 376 | else 377 | { 378 | InfoLog( << "Invalid command format: <'redirect'|'rd'> "); 379 | } 380 | return; 381 | } 382 | if(isEqualNoCase(command, "redirectTo") || isEqualNoCase(command, "rt")) 383 | { 384 | unsigned long partHandle = arg[0].convertUnsignedLong(); 385 | unsigned long destPartHandle = arg[1].convertUnsignedLong(); 386 | if(partHandle != 0 && destPartHandle != 0) 387 | { 388 | myConversationManager.redirectToParticipant(partHandle, destPartHandle); 389 | } 390 | else 391 | { 392 | InfoLog( << "Invalid command format: <'redirectTo'|'rt'> "); 393 | } 394 | return; 395 | } 396 | if(isEqualNoCase(command, "volume") || isEqualNoCase(command, "sv")) 397 | { 398 | unsigned long volume = arg[0].convertUnsignedLong(); 399 | myConversationManager.setSpeakerVolume(volume); 400 | InfoLog( << "Speaker volume set to " << volume); 401 | return; 402 | } 403 | if(isEqualNoCase(command, "gain") || isEqualNoCase(command, "sg")) 404 | { 405 | unsigned long gain = arg[0].convertUnsignedLong(); 406 | myConversationManager.setMicrophoneGain(gain); 407 | InfoLog( << "Microphone gain set to " << gain); 408 | return; 409 | } 410 | if(isEqualNoCase(command, "mute") || isEqualNoCase(command, "mm")) 411 | { 412 | bool enable = arg[0].convertUnsignedLong() != 0; 413 | myConversationManager.muteMicrophone(enable); 414 | InfoLog( << "Microphone mute " << (enable ? "enabled" : "disabled")); 415 | return; 416 | } 417 | if(isEqualNoCase(command, "echocanel") || isEqualNoCase(command, "aec")) 418 | { 419 | bool enable = arg[0].convertUnsignedLong() != 0; 420 | myConversationManager.enableEchoCancel(enable); 421 | InfoLog( << "Echo cancellation " << (enable ? "enabled" : "disabled")); 422 | return; 423 | } 424 | if(isEqualNoCase(command, "autogain") || isEqualNoCase(command, "agc")) 425 | { 426 | bool enable = arg[0].convertUnsignedLong() != 0; 427 | myConversationManager.enableAutoGainControl(enable); 428 | InfoLog( << "Automatic gain control " << (enable ? "enabled" : "disabled")); 429 | return; 430 | } 431 | if(isEqualNoCase(command, "noisereduction") || isEqualNoCase(command, "nr")) 432 | { 433 | bool enable = arg[0].convertUnsignedLong() != 0; 434 | myConversationManager.enableNoiseReduction(enable); 435 | return; 436 | } 437 | if(isEqualNoCase(command, "subscribe") || isEqualNoCase(command, "cs")) 438 | { 439 | unsigned int subTime = arg[2].convertUnsignedLong(); 440 | if(!arg[0].empty() && !arg[1].empty() && subTime != 0 && !arg[3].empty() && !arg[4].empty()) 441 | { 442 | try 443 | { 444 | NameAddr dest(arg[1]); 445 | Mime mime(arg[3], arg[4]); 446 | myUserAgent.createSubscription(arg[0], dest, subTime, mime); 447 | } 448 | catch(...) 449 | { 450 | NameAddr dest(uri); 451 | Mime mime(arg[3], arg[4]); 452 | dest.uri().user() = arg[1]; 453 | myUserAgent.createSubscription(arg[0], dest, subTime, mime); 454 | } 455 | } 456 | else 457 | { 458 | InfoLog( << "Invalid command format: <'subscribe'|'cs'> "); 459 | } 460 | return; 461 | } 462 | if(isEqualNoCase(command, "destsub") || isEqualNoCase(command, "ds")) 463 | { 464 | unsigned int subHandle = arg[0].convertUnsignedLong(); 465 | 466 | if(subHandle > 0) 467 | { 468 | myUserAgent.destroySubscription(subHandle); 469 | } 470 | else 471 | { 472 | InfoLog( << "Invalid command format: <'destsub'|'ds'> "); 473 | } 474 | return; 475 | } 476 | if(isEqualNoCase(command, "autoans") || isEqualNoCase(command, "aa")) 477 | { 478 | bool enable = arg[0].convertUnsignedLong() != 0; 479 | autoAnswerEnabled = enable; 480 | InfoLog( << "Autoanswer " << (enable ? "enabled" : "disabled")); 481 | return; 482 | } 483 | if(isEqualNoCase(command, "setcodecs") || isEqualNoCase(command, "sc")) 484 | { 485 | Data codecId; 486 | std::list idList; 487 | ParseBuffer pb(arg[0]); 488 | pb.skipWhitespace(); 489 | while(!pb.eof()) 490 | { 491 | const char *start = pb.position(); 492 | pb.skipToOneOf(ParseBuffer::Whitespace, ","); // white space or "," 493 | pb.data(codecId, start); 494 | idList.push_back(codecId.convertUnsignedLong()); 495 | if(!pb.eof()) 496 | { 497 | pb.skipChar(','); 498 | } 499 | } 500 | unsigned int numCodecIds = idList.size(); 501 | if(numCodecIds > 0) 502 | { 503 | unsigned int* codecIdArray = new unsigned int[numCodecIds]; 504 | unsigned int index = 0; 505 | std::list::iterator it = idList.begin(); 506 | for(;it != idList.end(); it++) 507 | { 508 | codecIdArray[index++] = (*it); 509 | } 510 | Data ipAddress(conversationProfile->sessionCaps().session().connection().getAddress()); 511 | // Note: Technically modifying the conversation profile at runtime like this is not 512 | // thread safe. But it should be fine for this test consoles purposes. 513 | myConversationManager.buildSessionCapabilities(ipAddress, numCodecIds, codecIdArray, conversationProfile->sessionCaps()); 514 | delete [] codecIdArray; 515 | } 516 | return; 517 | } 518 | if(isEqualNoCase(command, "securemedia") || isEqualNoCase(command, "sm")) 519 | { 520 | ConversationProfile::SecureMediaMode secureMediaMode = ConversationProfile::NoSecureMedia; 521 | bool secureMediaRequired = false; 522 | if(isEqualNoCase(arg[0], "Srtp")) 523 | { 524 | secureMediaMode = ConversationProfile::Srtp; 525 | } 526 | else if(isEqualNoCase(arg[0], "SrtpReq")) 527 | { 528 | secureMediaMode = ConversationProfile::Srtp; 529 | secureMediaRequired = true; 530 | } 531 | #ifdef USE_SSL 532 | else if(isEqualNoCase(arg[0], "SrtpDtls")) 533 | { 534 | secureMediaMode = ConversationProfile::SrtpDtls; 535 | } 536 | else if(isEqualNoCase(arg[0], "SrtpDtlsReq")) 537 | { 538 | secureMediaMode = ConversationProfile::SrtpDtls; 539 | secureMediaRequired = true; 540 | } 541 | #endif 542 | else 543 | { 544 | arg[0] = "None"; // for display output only 545 | } 546 | // Note: Technically modifying the conversation profile at runtime like this is not 547 | // thread safe. But it should be fine for this test consoles purposes. 548 | conversationProfile->secureMediaMode() = secureMediaMode; 549 | conversationProfile->secureMediaRequired() = secureMediaRequired; 550 | InfoLog( << "Secure media mode set to: " << arg[0]); 551 | return; 552 | } 553 | if(isEqualNoCase(command, "natmode") || isEqualNoCase(command, "nm")) 554 | { 555 | ConversationProfile::NatTraversalMode natTraversalMode = ConversationProfile::NoNatTraversal; 556 | if(isEqualNoCase(arg[0], "Bind")) 557 | { 558 | natTraversalMode = ConversationProfile::StunBindDiscovery; 559 | } 560 | else if(isEqualNoCase(arg[0], "UdpAlloc")) 561 | { 562 | natTraversalMode = ConversationProfile::TurnUdpAllocation; 563 | } 564 | else if(isEqualNoCase(arg[0], "TcpAlloc")) 565 | { 566 | natTraversalMode = ConversationProfile::TurnTcpAllocation; 567 | } 568 | #ifdef USE_SSL 569 | else if(isEqualNoCase(arg[0], "TlsAlloc")) 570 | { 571 | natTraversalMode = ConversationProfile::TurnTlsAllocation; 572 | } 573 | #endif 574 | else 575 | { 576 | arg[0] = "None"; // for display output only 577 | } 578 | // Note: Technically modifying the conversation profile at runtime like this is not 579 | // thread safe. But it should be fine for this test consoles purposes. 580 | conversationProfile->natTraversalMode() = natTraversalMode; 581 | InfoLog( << "NAT traversal mode set to: " << arg[0]); 582 | return; 583 | } 584 | if(isEqualNoCase(command, "natserver") || isEqualNoCase(command, "ns")) 585 | { 586 | Data natTraversalServerHostname; 587 | unsigned short natTraversalServerPort = 3478; 588 | // Read server and port 589 | ParseBuffer pb(arg[0]); 590 | pb.skipWhitespace(); 591 | const char *start = pb.position(); 592 | pb.skipToOneOf(ParseBuffer::Whitespace, ":"); // white space or ":" 593 | pb.data(natTraversalServerHostname, start); 594 | if(!pb.eof()) 595 | { 596 | pb.skipChar(':'); 597 | start = pb.position(); 598 | pb.skipToOneOf(ParseBuffer::Whitespace); // white space 599 | Data port; 600 | pb.data(port, start); 601 | natTraversalServerPort = port.convertUnsignedLong(); 602 | } 603 | // Note: Technically modifying the conversation profile at runtime like this is not 604 | // thread safe. But it should be fine for this test consoles purposes. 605 | conversationProfile->natTraversalServerHostname() = natTraversalServerHostname; 606 | conversationProfile->natTraversalServerPort() = natTraversalServerPort; 607 | InfoLog( << "NAT traversal STUN/TURN server set to: " << natTraversalServerHostname << ":" << natTraversalServerPort); 608 | return; 609 | } 610 | if(isEqualNoCase(command, "natuser") || isEqualNoCase(command, "nu")) 611 | { 612 | // Note: Technically modifying the conversation profile at runtime like this is not 613 | // thread safe. But it should be fine for this test consoles purposes. 614 | conversationProfile->stunUsername() = arg[0]; 615 | InfoLog( << "STUN/TURN user set to: " << arg[0]); 616 | return; 617 | } 618 | if(isEqualNoCase(command, "natpwd") || isEqualNoCase(command, "np")) 619 | { 620 | // Note: Technically modifying the conversation profile at runtime like this is not 621 | // thread safe. But it should be fine for this test consoles purposes. 622 | conversationProfile->stunPassword() = arg[0]; 623 | InfoLog( << "STUN/TURN password set to: " << arg[0]); 624 | return; 625 | } 626 | if(isEqualNoCase(command, "starttimer") || isEqualNoCase(command, "st")) 627 | { 628 | unsigned int timerId = arg[0].convertUnsignedLong(); 629 | unsigned int durationMs = arg[1].convertUnsignedLong(); 630 | unsigned int seqNumber = arg[2].convertUnsignedLong(); 631 | 632 | if(durationMs > 0) 633 | { 634 | myUserAgent.startApplicationTimer(timerId, durationMs, seqNumber); 635 | InfoLog( << "Application Timer started for " << durationMs << "ms"); 636 | } 637 | else 638 | { 639 | InfoLog( << "Invalid command format: <'starttimer'|'st'> "); 640 | } 641 | return; 642 | } 643 | if(isEqualNoCase(command, "info") || isEqualNoCase(command, "i")) 644 | { 645 | myConversationManager.displayInfo(); 646 | return; 647 | } 648 | if(isEqualNoCase(command, "dns") || isEqualNoCase(command, "ld")) 649 | { 650 | InfoLog( << "DNS cache (at WARNING log level):"); 651 | myUserAgent.logDnsCache(); 652 | return; 653 | } 654 | if(isEqualNoCase(command, "cleardns") || isEqualNoCase(command, "cd")) 655 | { 656 | myUserAgent.clearDnsCache(); 657 | InfoLog( << "DNS cache has been cleared."); 658 | return; 659 | } 660 | 661 | #ifdef USE_SSL 662 | Data setSecureMediaMode(" setSecureMediaMode <'securemedia'|'sm'> <'None'|'Srtp'|'SrtpReq'|'SrtpDtls'|'SrtpDtlsReq'>"); 663 | Data setNATTraversalMode(" setNATTraversalMode <'natmode'|'nm'> <'None'|'Bind'|'UdpAlloc'|'TcpAlloc'|'TlsAlloc'>" ); 664 | #else 665 | Data setSecureMediaMode(" setSecureMediaMode <'securemedia'|'sm'> <'None'|'Srtp'|'SrtpReq'>"); 666 | Data setNATTraversalMode(" setNATTraversalMode <'natmode'|'nm'> <'None'|'Bind'|'UdpAlloc'|'TcpAlloc'>" ); 667 | #endif 668 | 669 | InfoLog( << "Possible commands are: " << endl 670 | << " createConversation: <'createconv'|'cc'>" << endl 671 | << " destroyConversation: <'destroyconv'|'dc'> " << endl 672 | << " joinConversation: <'joinconv'|'jc'> " << endl 673 | << endl 674 | << " createLocalParticipant: <'createlocal'|'clp'>" << endl 675 | << " createRemoteParticipant: <'createremote'|'crp'> [<'manual'>] (last arg is fork select mode, 'auto' is default)" << endl 676 | << " createMediaResourceParticipant: <'createmedia'|'cmp'> []" << endl 677 | << " destroyParticipant: <'destroypart'|'dp'> " << endl 678 | << endl 679 | << " addPartcipant: <'addpart'|'ap'> " << endl 680 | << " removePartcipant: <'removepart'|'rp'> " << endl 681 | << " moveParticipant: <'movepart'|'mp'> " << endl 682 | << " modifyParticipantContribution: <'partcontrib'|'pc'> (gain in percentage)" << endl 683 | << " outputBridgeMatrix: <'bridgematrix'|'bm'>" << endl 684 | << " alertPartcipant: <'alert'|'al'> [<'noearly'>] (last arg is early flag, enabled by default)" << endl 685 | << " answerParticipant: <'answer'|'an'> " << endl 686 | << " rejectParticipant: <'reject'|'rj'> [] (default status code is 486)" << endl 687 | << " redirectPartcipant: <'redirect'|'rd'> " << endl 688 | << " redirectToPartcipant: <'redirectTo'|'rt'> " << endl 689 | << endl 690 | << " setSpeakerVolume: <'volume'|'sv'> " << endl 691 | << " setMicrophoneGain: <'gain'|'sg'> " << endl 692 | << " muteMicrophone: <'mute'|'mm'> <'0'|'1'> (1 to enable/mute)" << endl 693 | << " enableEchoCancel: <'echocancel'|'aec'> <'0'|'1'> (1 to enable)" << endl 694 | << " enableAutoGainControl: <'autogain'|'agc'> <'0'|'1'> (1 to enable)" << endl 695 | << " enableNoiseReduction: <'noisereduction'|'nr'> <'0'|'1'> (1 to enable)" << endl 696 | << endl 697 | << " createSubscription: <'subscribe'|'cs'> " << endl 698 | << " destroySubscription: <'destsub'|'ds'> " << endl 699 | << endl 700 | << " setAutoAnswer <'autoans'|'aa'> <'0'|'1'> (1 to enable (default))" << endl 701 | << " setCodecs <'setcodecs'|'sc'> [,]+ (comma separated list)" << endl 702 | << setSecureMediaMode << endl 703 | << setNATTraversalMode << endl 704 | << " setNATTraversalServer <'natserver'|'ns'> " << endl 705 | << " setNATUsername <'natuser'|'nu'> " << endl 706 | << " setNATPassword <'natpwd'|'np'> " << endl 707 | << " startApplicationTimer: <'starttimer'|'st'> " << endl 708 | << " displayInfo: <'info'|'i'>" << endl 709 | << " logDnsCache: <'dns'|'ld'>" << endl 710 | << " clearDnsCache: <'cleardns'|'cd'>" << endl 711 | << " exitProgram: <'exit'|'quit'|'q'>"); 712 | } 713 | 714 | #define KBD_BUFFER_SIZE 256 715 | void ReConServerProcess::processKeyboard(char input, MyConversationManager& myConversationManager, MyUserAgent& myUserAgent) 716 | { 717 | static char buffer[KBD_BUFFER_SIZE]; 718 | static int bufferpos = 0; 719 | 720 | if(input == 13 || input == 10) // enter 721 | { 722 | Data db(buffer,bufferpos); 723 | #ifdef WIN32 724 | cout << endl; 725 | #endif 726 | processCommandLine(db, myConversationManager, myUserAgent); 727 | bufferpos = 0; 728 | } 729 | else if(input == 8 || input == 127) // backspace 730 | { 731 | if(bufferpos > 0) 732 | { 733 | #ifdef WIN32 734 | cout << input << ' ' << input; 735 | #else 736 | // note: This is bit of a hack and may not be portable to all linux terminal types 737 | cout << "\b\b\b \b\b\b"; 738 | fflush(stdout); 739 | #endif 740 | bufferpos--; 741 | } 742 | } 743 | else 744 | { 745 | if(bufferpos == KBD_BUFFER_SIZE) 746 | { 747 | cout << endl; 748 | bufferpos = 0; 749 | } 750 | else 751 | { 752 | #ifdef WIN32 753 | cout << input; 754 | #endif 755 | buffer[bufferpos++] = (char)input; 756 | } 757 | } 758 | } 759 | 760 | int 761 | ReConServerProcess::main (int argc, char** argv) 762 | { 763 | installSignalHandler(); 764 | 765 | #if defined(WIN32) && defined(_DEBUG) && defined(LEAK_CHECK) 766 | resip::FindMemoryLeaks fml; 767 | { 768 | #endif 769 | 770 | Data defaultConfigFilename("reConServer.config"); 771 | ReConServerConfig reConServerConfig; 772 | try 773 | { 774 | reConServerConfig.parseConfig(argc, argv, defaultConfigFilename); 775 | } 776 | catch(std::exception& e) 777 | { 778 | ErrLog(<< "Exception parsing configuration: " << e.what()); 779 | exit(-1); 780 | } 781 | 782 | Data pidFile = reConServerConfig.getConfigData("PidFile", "", true); 783 | bool daemonize = reConServerConfig.getConfigBool("Daemonize", false); 784 | mKeyboardInput = reConServerConfig.getConfigBool("KeyboardInput", !daemonize); 785 | if(daemonize && mKeyboardInput) 786 | { 787 | ErrLog(<< "Ignoring KeyboardInput=true setting as we are running as a daemon"); 788 | mKeyboardInput = false; 789 | } 790 | setPidFile(pidFile); 791 | // Daemonize if necessary 792 | if(daemonize) 793 | { 794 | ReConServerProcess::daemonize(); 795 | } 796 | 797 | autoAnswerEnabled = reConServerConfig.getConfigBool("EnableAutoAnswer", false); 798 | bool registrationDisabled = reConServerConfig.getConfigBool("DisableRegistration", false); 799 | bool keepAlivesDisabled = reConServerConfig.getConfigBool("DisableKeepAlives", false); 800 | Data password = reConServerConfig.getConfigData("Password", "", true); 801 | Data dnsServers = reConServerConfig.getConfigData("DNSServers", "", true);; 802 | Data address = reConServerConfig.getConfigData("IPAddress", DnsUtil::getLocalIpAddress(), true); 803 | ConversationProfile::SecureMediaMode secureMediaMode = reConServerConfig.getConfigSecureMediaMode("SecureMediaMode", ConversationProfile::NoSecureMedia); 804 | bool secureMediaRequired = reConServerConfig.isSecureMediaModeRequired(); 805 | ConversationProfile::NatTraversalMode natTraversalMode = reConServerConfig.getConfigNatTraversalMode("NatTraversalMode", ConversationProfile::NoNatTraversal); 806 | bool forceCOMedia = reConServerConfig.getConfigBool("ForceCOMedia", true); 807 | Data natTraversalServerHostname = reConServerConfig.getConfigData("NatTraversalServerHostname", "", true); 808 | unsigned short natTraversalServerPort = reConServerConfig.getConfigUnsignedShort("NatTraversalServerPort", 3478); 809 | Data stunUsername = reConServerConfig.getConfigData("StunUsername", "", true); 810 | Data stunPassword = reConServerConfig.getConfigData("StunPassword", "", true); 811 | bool addViaRport = reConServerConfig.getConfigBool("AddViaRport", true); 812 | unsigned short tcpPort = reConServerConfig.getConfigUnsignedShort("TCPPort", 5062); 813 | unsigned short udpPort = reConServerConfig.getConfigUnsignedShort("UDPPort", 5062); 814 | unsigned short tlsPort = reConServerConfig.getConfigUnsignedShort("TLSPort", 5063); 815 | unsigned short mediaPortStart = reConServerConfig.getConfigUnsignedShort("MediaPortStart", 17384); 816 | Data tlsDomain = reConServerConfig.getConfigData("TLSDomain", DnsUtil::getLocalHostName(), true); 817 | NameAddr outboundProxy = reConServerConfig.getConfigNameAddr("OutboundProxyUri", NameAddr(), true); 818 | #ifdef PACKAGE_VERSION 819 | Data serverText = reConServerConfig.getConfigData("ServerText", "reConServer " PACKAGE_VERSION); 820 | #else 821 | Data serverText = reConServerConfig.getConfigData("ServerText", "reConServer"); 822 | #endif 823 | uri = reConServerConfig.getConfigNameAddr("SIPUri", uri, true); 824 | Data loggingType = reConServerConfig.getConfigData("LoggingType", "cout", true); 825 | Data loggingLevel = reConServerConfig.getConfigData("LoggingLevel", "INFO", true); 826 | Data loggingFilename = reConServerConfig.getConfigData("LogFilename", "reConServer.log", true); 827 | unsigned int loggingFileMaxLineCount = reConServerConfig.getConfigUnsignedLong("LogFileMaxLines", 50000); 828 | Data cdrLogFilename = reConServerConfig.getConfigData("CDRLogFile", "", true); 829 | Data captureHost = reConServerConfig.getConfigData("CaptureHost", ""); 830 | int capturePort = reConServerConfig.getConfigInt("CapturePort", 9060); 831 | int captureAgentID = reConServerConfig.getConfigInt("CaptureAgentID", 2002); 832 | bool localAudioEnabled = reConServerConfig.getConfigBool("EnableLocalAudio", !daemonize); // Defaults to false for daemon process 833 | Data runAsUser = reConServerConfig.getConfigData("RunAsUser", "", true); 834 | Data runAsGroup = reConServerConfig.getConfigData("RunAsGroup", "", true); 835 | ConversationManager::MediaInterfaceMode mediaInterfaceMode = reConServerConfig.getConfigBool("GlobalMediaInterface", false) 836 | ? ConversationManager::sipXGlobalMediaInterfaceMode : ConversationManager::sipXConversationMediaInterfaceMode; 837 | unsigned int defaultSampleRate = reConServerConfig.getConfigUnsignedLong("DefaultSampleRate", 8000); 838 | unsigned int maximumSampleRate = reConServerConfig.getConfigUnsignedLong("MaximumSampleRate", 8000); 839 | bool enableG722 = reConServerConfig.getConfigBool("EnableG722", false); 840 | bool enableOpus = reConServerConfig.getConfigBool("EnableOpus", false); 841 | ReConServerConfig::Application application = reConServerConfig.getConfigApplication("Application", ReConServerConfig::None); 842 | 843 | 844 | // build a list of codecs in priority order 845 | // Used by ConversationManager::buildSessionCapabilities(...) to create 846 | // our local SDP 847 | std::vector _codecIds; 848 | if(enableOpus) 849 | { 850 | _codecIds.push_back(SdpCodec::SDP_CODEC_OPUS); // Opus 851 | } 852 | if(enableG722) 853 | { 854 | _codecIds.push_back(SdpCodec::SDP_CODEC_G722); // 9 - G.722 855 | } 856 | _codecIds.push_back(SdpCodec::SDP_CODEC_ILBC); // 108 - iLBC 857 | _codecIds.push_back(SdpCodec::SDP_CODEC_ILBC_20MS); // 109 - Internet Low Bit Rate Codec, 20ms (RFC3951) 858 | _codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX_24); // 99 - speex NB 24,600bps 859 | _codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX_15); // 98 - speex NB 15,000bps 860 | _codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX); // 96 - speex NB 8,000bps 861 | _codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX_5); // 97 - speex NB 5,950bps 862 | _codecIds.push_back(SdpCodec::SDP_CODEC_GSM); // 3 - GSM 863 | //_codecIds.push_back(SdpCodec::SDP_CODEC_L16_44100_MONO); // PCM 16 bit/sample 44100 samples/sec. 864 | _codecIds.push_back(SdpCodec::SDP_CODEC_PCMU); // 0 - pcmu 865 | _codecIds.push_back(SdpCodec::SDP_CODEC_PCMA); // 8 - pcma 866 | _codecIds.push_back(SdpCodec::SDP_CODEC_G729); // 18 - G.729 867 | _codecIds.push_back(SdpCodec::SDP_CODEC_TONES); // 110 - telephone-event 868 | unsigned int *codecIds = &_codecIds[0]; 869 | unsigned int numCodecIds = _codecIds.size(); 870 | 871 | Log::initialize(loggingType, loggingLevel, argv[0], loggingFilename.c_str()); 872 | GenericLogImpl::MaxLineCount = loggingFileMaxLineCount; 873 | 874 | // Setup logging for the sipX media stack 875 | // It is bridged to the reSIProcate logger 876 | SipXHelper::setupLoggingBridge("reConServer"); 877 | //UserAgent::setLogLevel(Log::Warning, UserAgent::SubsystemAll); 878 | //UserAgent::setLogLevel(Log::Info, UserAgent::SubsystemRecon); 879 | 880 | initNetwork(); 881 | 882 | InfoLog( << "reConServer settings:"); 883 | InfoLog( << " No Keepalives = " << (keepAlivesDisabled ? "true" : "false")); 884 | InfoLog( << " Autoanswer = " << (autoAnswerEnabled ? "true" : "false")); 885 | InfoLog( << " Do not register = " << (registrationDisabled ? "true" : "false")); 886 | InfoLog( << " Local IP Address = " << address); 887 | InfoLog( << " SIP URI = " << uri); 888 | InfoLog( << " SIP Password = " << password); 889 | InfoLog( << " Override DNS Servers = " << dnsServers); 890 | InfoLog( << " Secure Media Mode = " << secureMediaMode); 891 | InfoLog( << " NAT Traversal Mode = " << natTraversalMode); 892 | InfoLog( << " NAT Server = " << natTraversalServerHostname << ":" << natTraversalServerPort); 893 | InfoLog( << " STUN/TURN user = " << stunUsername); 894 | InfoLog( << " STUN/TURN password = " << stunPassword); 895 | InfoLog( << " TCP Port = " << tcpPort); 896 | InfoLog( << " UDP Port = " << udpPort); 897 | InfoLog( << " Media Port Range Start = " << mediaPortStart); 898 | #ifdef USE_SSL 899 | InfoLog( << " TLS Port = " << tlsPort); 900 | InfoLog( << " TLS Domain = " << tlsDomain); 901 | #endif 902 | InfoLog( << " Outbound Proxy = " << outboundProxy); 903 | InfoLog( << " Local Audio Enabled = " << (localAudioEnabled ? "true" : "false")); 904 | InfoLog( << " Global Media Interface = " << 905 | ((mediaInterfaceMode == ConversationManager::sipXGlobalMediaInterfaceMode) ? "true" : "false")); 906 | InfoLog( << " Default sample rate = " << defaultSampleRate); 907 | InfoLog( << " Maximum sample rate = " << maximumSampleRate); 908 | InfoLog( << " Enable G.722 codec = " << (enableG722 ? "true" : "false")); 909 | InfoLog( << " Enable Opus codec = " << (enableOpus ? "true" : "false")); 910 | InfoLog( << " Log Type = " << loggingType); 911 | InfoLog( << " Log Level = " << loggingLevel); 912 | InfoLog( << " Log Filename = " << loggingFilename); 913 | InfoLog( << " Daemonize = " << (daemonize ? "true" : "false")); 914 | InfoLog( << " KeyboardInput = " << (mKeyboardInput ? "true" : "false")); 915 | InfoLog( << " PidFile = " << pidFile); 916 | InfoLog( << " Run as user = " << runAsUser); 917 | InfoLog( << " Run as group = " << runAsGroup); 918 | InfoLog( << "type help or '?' for list of accepted commands." << endl); 919 | 920 | ////////////////////////////////////////////////////////////////////////////// 921 | // Setup UserAgentMasterProfile 922 | ////////////////////////////////////////////////////////////////////////////// 923 | 924 | SharedPtr profile(new UserAgentMasterProfile); 925 | 926 | Data certPath; 927 | reConServerConfig.getConfigValue("CertificatePath", certPath); 928 | if(!certPath.empty()) 929 | { 930 | profile->certPath() = certPath; 931 | } 932 | Data caDir; 933 | reConServerConfig.getConfigValue("CADirectory", caDir); 934 | if(!caDir.empty()) 935 | { 936 | profile->rootCertDirectories().push_back(caDir); 937 | } 938 | Data caFile; 939 | reConServerConfig.getConfigValue("CAFile", caFile); 940 | if(!caFile.empty()) 941 | { 942 | profile->rootCertBundles().push_back(caFile); 943 | } 944 | 945 | if(!captureHost.empty()) 946 | { 947 | SharedPtr agent(new HepAgent(captureHost, capturePort, captureAgentID)); 948 | profile->setTransportSipMessageLoggingHandler(SharedPtr(new HEPSipMessageLoggingHandler(agent))); 949 | profile->setRTCPEventLoggingHandler(SharedPtr(new HEPRTCPEventLoggingHandler(agent))); 950 | } 951 | 952 | // Add transports 953 | try 954 | { 955 | bool useEmailAsSIP = reConServerConfig.getConfigBool("TLSUseEmailAsSIP", false); 956 | 957 | // Check if advanced transport settings are provided 958 | ConfigParse::NestedConfigMap m = reConServerConfig.getConfigNested("Transport"); 959 | DebugLog(<<"Found " << m.size() << " interface(s) defined in the advanced format"); 960 | if(!m.empty()) 961 | { 962 | // Sample config file format for advanced transport settings 963 | // Transport1Interface = 192.168.1.106:5061 964 | // Transport1Type = TLS 965 | // Transport1TlsDomain = sipdomain.com 966 | // Transport1TlsCertificate = /etc/ssl/crt/sipdomain.com.pem 967 | // Transport1TlsPrivateKey = /etc/ssl/private/sipdomain.com.pem 968 | // Transport1TlsPrivateKeyPassPhrase = 969 | // Transport1TlsClientVerification = None 970 | // Transport1RcvBufLen = 2000 971 | 972 | const char *anchor; 973 | for(ConfigParse::NestedConfigMap::iterator it = m.begin(); 974 | it != m.end(); 975 | it++) 976 | { 977 | int idx = it->first; 978 | SipConfigParse tc(it->second); 979 | Data transportPrefix = "Transport" + idx; 980 | DebugLog(<< "checking values for transport: " << idx); 981 | Data interfaceSettings = tc.getConfigData("Interface", Data::Empty, true); 982 | 983 | // Parse out interface settings 984 | ParseBuffer pb(interfaceSettings); 985 | anchor = pb.position(); 986 | pb.skipToEnd(); 987 | pb.skipBackToChar(':'); // For IPv6 the last : should be the port 988 | pb.skipBackChar(); 989 | if(!pb.eof()) 990 | { 991 | Data ipAddr; 992 | Data portData; 993 | pb.data(ipAddr, anchor); 994 | pb.skipChar(); 995 | anchor = pb.position(); 996 | pb.skipToEnd(); 997 | pb.data(portData, anchor); 998 | if(!DnsUtil::isIpAddress(ipAddr)) 999 | { 1000 | CritLog(<< "Malformed IP-address found in " << transportPrefix << "Interface setting: " << ipAddr); 1001 | } 1002 | int port = portData.convertInt(); 1003 | if(port == 0) 1004 | { 1005 | CritLog(<< "Invalid port found in " << transportPrefix << " setting: " << port); 1006 | } 1007 | TransportType tt = Tuple::toTransport(tc.getConfigData("Type", "UDP")); 1008 | if(tt == UNKNOWN_TRANSPORT) 1009 | { 1010 | CritLog(<< "Unknown transport type found in " << transportPrefix << "Type setting: " << tc.getConfigData("Type", "UDP")); 1011 | } 1012 | Data tlsDomain = tc.getConfigData("TlsDomain", Data::Empty); 1013 | Data tlsCertificate = tc.getConfigData("TlsCertificate", Data::Empty); 1014 | Data tlsPrivateKey = tc.getConfigData("TlsPrivateKey", Data::Empty); 1015 | Data tlsPrivateKeyPassPhrase = tc.getConfigData("TlsPrivateKeyPassPhrase", Data::Empty); 1016 | SecurityTypes::TlsClientVerificationMode cvm = tc.getConfigClientVerificationMode("TlsClientVerification", SecurityTypes::None); 1017 | SecurityTypes::SSLType sslType = SecurityTypes::NoSSL; 1018 | #ifdef USE_SSL 1019 | sslType = tc.getConfigSSLType("TlsConnectionMethod", SecurityTypes::SSLv23); 1020 | #endif 1021 | 1022 | int rcvBufLen = tc.getConfigInt("RcvBufLen", 0); 1023 | 1024 | profile->addTransport(tt, 1025 | port, 1026 | DnsUtil::isIpV6Address(ipAddr) ? V6 : V4, 1027 | StunEnabled, 1028 | ipAddr, // interface to bind to 1029 | tlsDomain, 1030 | tlsPrivateKeyPassPhrase, // private key passphrase 1031 | sslType, // sslType 1032 | 0, // transport flags 1033 | tlsCertificate, tlsPrivateKey, 1034 | cvm, // tls client verification mode 1035 | useEmailAsSIP, rcvBufLen); 1036 | 1037 | } 1038 | else 1039 | { 1040 | CritLog(<< "Port not specified in " << transportPrefix << " setting: expected format is :"); 1041 | return false; 1042 | } 1043 | } 1044 | } 1045 | else 1046 | { 1047 | DebugLog(<<"Using legacy transport configuration"); 1048 | if(udpPort) 1049 | { 1050 | profile->addTransport(UDP, udpPort, V4, StunEnabled, address, Data::Empty, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP); 1051 | } 1052 | if(tcpPort) 1053 | { 1054 | profile->addTransport(TCP, tcpPort, V4, StunEnabled, address, Data::Empty, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP); 1055 | } 1056 | #ifdef USE_SSL 1057 | if(tlsPort) 1058 | { 1059 | profile->addTransport(TLS, tlsPort, V4, StunEnabled, address, tlsDomain, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP); 1060 | } 1061 | #endif 1062 | } 1063 | } 1064 | catch (BaseException& e) 1065 | { 1066 | std::cerr << "Likely a port is already in use" << endl; 1067 | InfoLog (<< "Caught: " << e); 1068 | return false; 1069 | } 1070 | 1071 | // The following settings are used to avoid a kernel panic seen on an ARM embedded platform. 1072 | // The kernel panic happens when either binding a udp socket to port 0 (OS selected), 1073 | // or calling connect without first binding to a specific port. There is code in the 1074 | // resip transport selector that uses a utility UDP socket in order to determine 1075 | // which interface should be used to route to a particular destination. This code calls 1076 | // connect with no bind. By setting a fixed transport interface here that 1077 | // code will not be used. 1078 | // The following line can be safely removed for other platforms 1079 | //profile->setFixedTransportInterface(address); 1080 | 1081 | // Settings 1082 | profile->setDefaultRegistrationTime(3600); 1083 | profile->setDefaultFrom(uri); 1084 | profile->setDigestCredential(uri.uri().host(), uri.uri().user(), password); 1085 | 1086 | // DNS Servers 1087 | ParseBuffer pb(dnsServers); 1088 | Data dnsServer; 1089 | while(!dnsServers.empty() && !pb.eof()) 1090 | { 1091 | pb.skipWhitespace(); 1092 | const char *start = pb.position(); 1093 | pb.skipToOneOf(ParseBuffer::Whitespace, ";,"); // allow white space 1094 | pb.data(dnsServer, start); 1095 | if(DnsUtil::isIpV4Address(dnsServer)) 1096 | { 1097 | InfoLog( << "Adding DNS Server: " << dnsServer); 1098 | profile->addAdditionalDnsServer(dnsServer); 1099 | } 1100 | else 1101 | { 1102 | ErrLog( << "Tried to add dns server, but invalid format: " << dnsServer); 1103 | } 1104 | if(!pb.eof()) 1105 | { 1106 | pb.skipChar(); 1107 | } 1108 | } 1109 | 1110 | // Disable Statisitics Manager 1111 | profile->statisticsManagerEnabled() = false; 1112 | 1113 | // Add ENUM Suffixes from setting string - use code similar to dns server 1114 | //profile->addEnumSuffix(enumSuffix); 1115 | 1116 | if(!keepAlivesDisabled) 1117 | { 1118 | profile->setKeepAliveTimeForDatagram(30); 1119 | profile->setKeepAliveTimeForStream(180); 1120 | } 1121 | 1122 | // Support Methods, etc. 1123 | profile->validateContentEnabled() = false; 1124 | profile->validateContentLanguageEnabled() = false; 1125 | profile->validateAcceptEnabled() = false; 1126 | 1127 | profile->clearSupportedLanguages(); 1128 | profile->addSupportedLanguage(Token("en")); 1129 | 1130 | profile->clearSupportedMimeTypes(); 1131 | profile->addSupportedMimeType(INVITE, Mime("application", "sdp")); 1132 | profile->addSupportedMimeType(INVITE, Mime("multipart", "mixed")); 1133 | profile->addSupportedMimeType(INVITE, Mime("multipart", "signed")); 1134 | profile->addSupportedMimeType(INVITE, Mime("multipart", "alternative")); 1135 | profile->addSupportedMimeType(OPTIONS,Mime("application", "sdp")); 1136 | profile->addSupportedMimeType(OPTIONS,Mime("multipart", "mixed")); 1137 | profile->addSupportedMimeType(OPTIONS, Mime("multipart", "signed")); 1138 | profile->addSupportedMimeType(OPTIONS, Mime("multipart", "alternative")); 1139 | profile->addSupportedMimeType(PRACK, Mime("application", "sdp")); 1140 | profile->addSupportedMimeType(PRACK, Mime("multipart", "mixed")); 1141 | profile->addSupportedMimeType(PRACK, Mime("multipart", "signed")); 1142 | profile->addSupportedMimeType(PRACK, Mime("multipart", "alternative")); 1143 | profile->addSupportedMimeType(UPDATE, Mime("application", "sdp")); 1144 | profile->addSupportedMimeType(UPDATE, Mime("multipart", "mixed")); 1145 | profile->addSupportedMimeType(UPDATE, Mime("multipart", "signed")); 1146 | profile->addSupportedMimeType(UPDATE, Mime("multipart", "alternative")); 1147 | profile->addSupportedMimeType(NOTIFY, Mime("message", "sipfrag")); 1148 | profile->addSupportedMimeType(INFO, Mime("application", "dtmf-relay")); 1149 | 1150 | profile->clearSupportedMethods(); 1151 | profile->addSupportedMethod(INVITE); 1152 | profile->addSupportedMethod(ACK); 1153 | profile->addSupportedMethod(CANCEL); 1154 | profile->addSupportedMethod(OPTIONS); 1155 | profile->addSupportedMethod(BYE); 1156 | profile->addSupportedMethod(REFER); 1157 | profile->addSupportedMethod(NOTIFY); 1158 | profile->addSupportedMethod(SUBSCRIBE); 1159 | profile->addSupportedMethod(UPDATE); 1160 | profile->addSupportedMethod(PRACK); 1161 | profile->addSupportedMethod(INFO); 1162 | //profile->addSupportedMethod(MESSAGE); 1163 | 1164 | profile->clearSupportedOptionTags(); 1165 | profile->addSupportedOptionTag(Token(Symbols::Replaces)); 1166 | profile->addSupportedOptionTag(Token(Symbols::Timer)); 1167 | profile->addSupportedOptionTag(Token(Symbols::NoReferSub)); 1168 | profile->addSupportedOptionTag(Token(Symbols::AnswerMode)); 1169 | profile->addSupportedOptionTag(Token(Symbols::TargetDialog)); 1170 | //profile->addSupportedOptionTag(Token(Symbols::C100rel)); // Automatically added by calling setUacReliableProvisionalMode 1171 | 1172 | profile->setUacReliableProvisionalMode(MasterProfile::Supported); 1173 | 1174 | profile->clearSupportedSchemes(); 1175 | profile->addSupportedScheme("sip"); 1176 | #ifdef USE_SSL 1177 | profile->addSupportedScheme("sips"); 1178 | #endif 1179 | 1180 | // Have stack add Allow/Supported/Accept headers to INVITE dialog establishment messages 1181 | profile->clearAdvertisedCapabilities(); // Remove Profile Defaults, then add our preferences 1182 | profile->addAdvertisedCapability(Headers::Allow); 1183 | //profile->addAdvertisedCapability(Headers::AcceptEncoding); // This can be misleading - it might specify what is expected in response 1184 | profile->addAdvertisedCapability(Headers::AcceptLanguage); 1185 | profile->addAdvertisedCapability(Headers::Supported); 1186 | profile->setMethodsParamEnabled(true); 1187 | 1188 | //profile->setOverrideHostAndPort(mContact); 1189 | if(!outboundProxy.uri().host().empty()) 1190 | { 1191 | profile->setOutboundProxy(outboundProxy.uri()); 1192 | } 1193 | 1194 | profile->setUserAgent(serverText); 1195 | profile->rtpPortRangeMin() = mediaPortStart; 1196 | profile->rtpPortRangeMax() = mediaPortStart + 101; // Allows 100 media streams 1197 | 1198 | if(natTraversalMode == ConversationProfile::NoNatTraversal) 1199 | { 1200 | StackLog(<<"NAT traversal features not enabled, " 1201 | "adding message decorator for SDP connection address"); 1202 | SharedPtr md(new MyMessageDecorator()); 1203 | profile->setOutboundDecorator(md); 1204 | } 1205 | 1206 | ////////////////////////////////////////////////////////////////////////////// 1207 | // Setup ConversationProfile 1208 | ////////////////////////////////////////////////////////////////////////////// 1209 | 1210 | conversationProfile = SharedPtr(new ConversationProfile(profile)); 1211 | if(uri.uri().user() != "noreg" && !registrationDisabled) 1212 | { 1213 | conversationProfile->setDefaultRegistrationTime(3600); 1214 | } 1215 | else 1216 | { 1217 | conversationProfile->setDefaultRegistrationTime(0); 1218 | } 1219 | conversationProfile->setDefaultRegistrationRetryTime(120); // 2 mins 1220 | conversationProfile->setDefaultFrom(uri); 1221 | conversationProfile->setDigestCredential(uri.uri().host(), uri.uri().user(), password); 1222 | 1223 | #if 0 // Now auto-built 1224 | 1225 | // Create Session Capabilities and assign to coversation Profile 1226 | // Note: port, sessionId and version will be replaced in actual offer/answer int port = 16384; 1227 | // Build s=, o=, t=, and c= lines 1228 | SdpContents::Session::Origin origin("-", 0 /* sessionId */, 0 /* version */, SdpContents::IP4, address); // o= 1229 | SdpContents::Session session(0, origin, "-" /* s= */); 1230 | session.connection() = SdpContents::Session::Connection(SdpContents::IP4, address); // c= 1231 | session.addTime(SdpContents::Session::Time(0, 0)); 1232 | 1233 | // Build Codecs and media offering 1234 | SdpContents::Session::Medium medium("audio", port, 1, "RTP/AVP"); 1235 | if(enableOpus) 1236 | { 1237 | // Note: the other constructors (e.g. g722 above) are 1238 | // invoked incorrectly, payload format should be the second 1239 | // argument or the rate is used as the payload format and 1240 | // the desired rate is not used at all 1241 | SdpContents::Session::Codec opuscodec("OPUS", 96, 48000); 1242 | opuscodec.encodingParameters() = Data("2"); 1243 | medium.addCodec(opuscodec); 1244 | } 1245 | if(enableG722) 1246 | { 1247 | SdpContents::Session::Codec g722codec("G722", 8000); 1248 | g722codec.payloadType() = 9; /* RFC3551 */ ; 1249 | medium.addCodec(g722codec); 1250 | } 1251 | SdpContents::Session::Codec speexCodec("SPEEX", 8000); 1252 | speexCodec.payloadType() = 110; 1253 | speexCodec.parameters() = Data("mode=3"); 1254 | medium.addCodec(speexCodec); 1255 | SdpContents::Session::Codec gsmCodec("GSM", 8000); 1256 | gsmCodec.payloadType() = 3; /* RFC3551 */ ; 1257 | medium.addCodec(gsmCodec); 1258 | SdpContents::Session::Codec g711ucodec("PCMU", 8000); 1259 | g711ucodec.payloadType() = 0; /* RFC3551 */ ; 1260 | medium.addCodec(g711ucodec); 1261 | SdpContents::Session::Codec g711acodec("PCMA", 8000); 1262 | g711acodec.payloadType() = 8; /* RFC3551 */ ; 1263 | medium.addCodec(g711acodec); 1264 | SdpContents::Session::Codec g729codec("G729", 8000); 1265 | g729codec.payloadType() = 18; /* RFC3551 */ ; 1266 | medium.addCodec(g729codec); 1267 | medium.addAttribute("ptime", Data(20)); // 20 ms of speech per frame (note G711 has 10ms samples, so this is 2 samples per frame) 1268 | medium.addAttribute("sendrecv"); 1269 | 1270 | SdpContents::Session::Codec toneCodec("telephone-event", 8000); 1271 | toneCodec.payloadType() = 102; 1272 | toneCodec.parameters() = Data("0-15"); 1273 | medium.addCodec(toneCodec); 1274 | session.addMedium(medium); 1275 | 1276 | conversationProfile->sessionCaps().session() = session; 1277 | #endif 1278 | 1279 | // Setup NatTraversal Settings 1280 | conversationProfile->natTraversalMode() = natTraversalMode; 1281 | conversationProfile->forceCOMedia() = forceCOMedia; 1282 | conversationProfile->natTraversalServerHostname() = natTraversalServerHostname; 1283 | conversationProfile->natTraversalServerPort() = natTraversalServerPort; 1284 | conversationProfile->stunUsername() = stunUsername; 1285 | conversationProfile->stunPassword() = stunPassword; 1286 | InteropHelper::setRportEnabled(addViaRport); 1287 | conversationProfile->setRportEnabled(addViaRport); 1288 | 1289 | // Secure Media Settings 1290 | conversationProfile->secureMediaMode() = secureMediaMode; 1291 | conversationProfile->secureMediaRequired() = secureMediaRequired; 1292 | conversationProfile->secureMediaDefaultCryptoSuite() = ConversationProfile::SRTP_AES_CM_128_HMAC_SHA1_80; 1293 | 1294 | ////////////////////////////////////////////////////////////////////////////// 1295 | // Create ConverationManager and UserAgent 1296 | ////////////////////////////////////////////////////////////////////////////// 1297 | { 1298 | switch(application) 1299 | { 1300 | case ReConServerConfig::None: 1301 | mConversationManager.reset(new MyConversationManager(localAudioEnabled, mediaInterfaceMode, defaultSampleRate, maximumSampleRate, autoAnswerEnabled)); 1302 | break; 1303 | case ReConServerConfig::B2BUA: 1304 | { 1305 | SharedPtr b2bCallLogger; 1306 | if(!cdrLogFilename.empty()) 1307 | { 1308 | b2bCallLogger.reset(new CDRFile(cdrLogFilename)); 1309 | } 1310 | mConversationManager.reset(new B2BCallManager(mediaInterfaceMode, defaultSampleRate, maximumSampleRate, reConServerConfig, b2bCallLogger)); 1311 | } 1312 | break; 1313 | default: 1314 | assert(0); 1315 | } 1316 | mUserAgent.reset(new MyUserAgent(reConServerConfig, mConversationManager.get(), profile)); 1317 | mConversationManager->buildSessionCapabilities(address, numCodecIds, codecIds, conversationProfile->sessionCaps()); 1318 | mUserAgent->addConversationProfile(conversationProfile); 1319 | 1320 | if(application == ReConServerConfig::B2BUA) 1321 | { 1322 | Data internalMediaAddress; 1323 | reConServerConfig.getConfigValue("B2BUAInternalMediaAddress", internalMediaAddress); 1324 | if(!internalMediaAddress.empty()) 1325 | { 1326 | SharedPtr internalProfile(new ConversationProfile(conversationProfile)); 1327 | Data b2BUANextHop = reConServerConfig.getConfigData("B2BUANextHop", "", true); 1328 | if(b2BUANextHop.empty()) 1329 | { 1330 | CritLog(<<"Please specify B2BUANextHop"); 1331 | throw ConfigParse::Exception("Please specify B2BUANextHop", __FILE__, __LINE__); 1332 | } 1333 | NameAddrs route; 1334 | route.push_front(NameAddr(b2BUANextHop)); 1335 | internalProfile->setServiceRoute(route); 1336 | internalProfile->secureMediaMode() = reConServerConfig.getConfigSecureMediaMode("B2BUAInternalSecureMediaMode", secureMediaMode); 1337 | internalProfile->setDefaultFrom(uri); 1338 | internalProfile->setDigestCredential(uri.uri().host(), uri.uri().user(), password); 1339 | mConversationManager->buildSessionCapabilities(internalMediaAddress, numCodecIds, codecIds, internalProfile->sessionCaps()); 1340 | mUserAgent->addConversationProfile(internalProfile, false); 1341 | } 1342 | else 1343 | { 1344 | WarningLog(<<"B2BUAInternalMediaAddress not specified, using same media address for internal and external zones"); 1345 | } 1346 | } 1347 | 1348 | ////////////////////////////////////////////////////////////////////////////// 1349 | // Startup and run... 1350 | ////////////////////////////////////////////////////////////////////////////// 1351 | 1352 | mUserAgent->startup(); 1353 | mConversationManager->startup(); 1354 | 1355 | //mUserAgent->createSubscription("message-summary", uri, 120, Mime("application", "simple-message-summary")); // thread safe 1356 | 1357 | // Drop privileges (can do this now that sockets are bound) 1358 | if(!runAsUser.empty()) 1359 | { 1360 | InfoLog( << "Trying to drop privileges, configured uid = " << runAsUser << " gid = " << runAsGroup); 1361 | dropPrivileges(runAsUser, runAsGroup); 1362 | } 1363 | 1364 | mainLoop(); 1365 | 1366 | mUserAgent->shutdown(); 1367 | } 1368 | InfoLog(<< "reConServer is shutdown."); 1369 | OsSysLog::shutdown(); 1370 | ::sleepSeconds(2); 1371 | 1372 | #if defined(WIN32) && defined(_DEBUG) && defined(LEAK_CHECK) 1373 | } // end FML scope 1374 | #endif 1375 | } 1376 | 1377 | void 1378 | ReConServerProcess::doWait() 1379 | { 1380 | mUserAgent->process(50); 1381 | } 1382 | 1383 | void 1384 | ReConServerProcess::onLoop() 1385 | { 1386 | if(mKeyboardInput) 1387 | { 1388 | int input; 1389 | while(_kbhit() != 0) 1390 | { 1391 | #ifdef WIN32 1392 | input = _getch(); 1393 | processKeyboard(input, *mConversationManager, *mUserAgent); 1394 | #else 1395 | input = fgetc(stdin); 1396 | fflush(stdin); 1397 | //cout << "input: " << input << endl; 1398 | processKeyboard(input, *mConversationManager, *mUserAgent); 1399 | #endif 1400 | } 1401 | } 1402 | } 1403 | 1404 | void 1405 | ReConServerProcess::onReload() 1406 | { 1407 | StackLog(<<"ReConServerProcess::onReload invoked"); 1408 | } 1409 | 1410 | 1411 | /* ==================================================================== 1412 | 1413 | Copyright (c) 2007-2008, Plantronics, Inc. 1414 | All rights reserved. 1415 | 1416 | Redistribution and use in source and binary forms, with or without 1417 | modification, are permitted provided that the following conditions are 1418 | met: 1419 | 1420 | 1. Redistributions of source code must retain the above copyright 1421 | notice, this list of conditions and the following disclaimer. 1422 | 1423 | 2. Redistributions in binary form must reproduce the above copyright 1424 | notice, this list of conditions and the following disclaimer in the 1425 | documentation and/or other materials provided with the distribution. 1426 | 1427 | 3. Neither the name of Plantronics nor the names of its contributors 1428 | may be used to endorse or promote products derived from this 1429 | software without specific prior written permission. 1430 | 1431 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1432 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1433 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1434 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1435 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 1436 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1437 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 1438 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 1439 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1440 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 1441 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1442 | 1443 | ==================================================================== */ 1444 | -------------------------------------------------------------------------------- /reConServer.hxx: -------------------------------------------------------------------------------- 1 | #ifndef RECONSERVER_HXX 2 | #define RECONSERVER_HXX 3 | 4 | #if defined(HAVE_CONFIG_H) 5 | #include "config.h" 6 | #endif 7 | 8 | #include "rutil/Data.hxx" 9 | #include "recon/UserAgent.hxx" 10 | #include "rutil/ServerProcess.hxx" 11 | 12 | #include "MyConversationManager.hxx" 13 | #include "MyUserAgent.hxx" 14 | 15 | namespace reconserver 16 | { 17 | 18 | class ReConServerProcess : public resip::ServerProcess 19 | { 20 | public: 21 | ReConServerProcess(); 22 | virtual ~ReConServerProcess(); 23 | 24 | virtual int main(int argc, char** argv); 25 | virtual void processCommandLine(resip::Data& commandline, MyConversationManager& myConversationManager, MyUserAgent& myUserAgent); 26 | virtual void processKeyboard(char input, MyConversationManager& myConversationManager, MyUserAgent& myUserAgent); 27 | protected: 28 | virtual void doWait(); 29 | virtual void onLoop(); 30 | 31 | virtual void onReload(); 32 | 33 | private: 34 | bool mKeyboardInput; 35 | resip::SharedPtr mUserAgent; 36 | std::auto_ptr mConversationManager; 37 | }; 38 | 39 | } 40 | 41 | #endif 42 | 43 | 44 | /* ==================================================================== 45 | * 46 | * Copyright 2013 Catalin Constantin Usurelu. All rights reserved. 47 | * 48 | * Redistribution and use in source and binary forms, with or without 49 | * modification, are permitted provided that the following conditions 50 | * are met: 51 | * 52 | * 1. Redistributions of source code must retain the above copyright 53 | * notice, this list of conditions and the following disclaimer. 54 | * 55 | * 2. Redistributions in binary form must reproduce the above copyright 56 | * notice, this list of conditions and the following disclaimer in 57 | * the documentation and/or other materials provided with the 58 | * distribution. 59 | * 60 | * 3. Neither the name of the author(s) nor the names of any contributors 61 | * may be used to endorse or promote products derived from this software 62 | * without specific prior written permission. 63 | * 64 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 65 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 66 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 67 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 68 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 69 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 70 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 71 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 72 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 73 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 74 | * SUCH DAMAGE. 75 | * 76 | * ==================================================================== 77 | * 78 | * 79 | */ 80 | 81 | -------------------------------------------------------------------------------- /reConServerConfig.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "os/OsIntTypes.h" 5 | 6 | #include "reConServerConfig.hxx" 7 | 8 | #include 9 | 10 | #define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER 11 | 12 | using namespace std; 13 | using namespace resip; 14 | using namespace recon; 15 | 16 | namespace reconserver { 17 | 18 | ReConServerConfig::ReConServerConfig(): mSecureMediaRequired(false) 19 | { 20 | } 21 | 22 | ReConServerConfig::~ReConServerConfig() 23 | { 24 | } 25 | 26 | 27 | 28 | bool 29 | ReConServerConfig::getConfigValue(const resip::Data& name, ConversationProfile::SecureMediaMode &value) 30 | { 31 | Data lowerName(name); lowerName.lowercase(); 32 | ConfigValuesMap::iterator it = mConfigValues.find(lowerName); 33 | if(it != mConfigValues.end()) 34 | { 35 | if(isEqualNoCase(it->second, "None")) 36 | { 37 | value = ConversationProfile::NoSecureMedia; 38 | } 39 | else if(isEqualNoCase(it->second, "Srtp")) 40 | { 41 | value = ConversationProfile::Srtp; 42 | } 43 | else if(isEqualNoCase(it->second, "SrtpReq")) 44 | { 45 | mSecureMediaRequired = true; 46 | value = ConversationProfile::Srtp; 47 | } 48 | #ifdef USE_SSL 49 | else if(isEqualNoCase(it->second, "SrtpDtls")) 50 | { 51 | value = ConversationProfile::SrtpDtls; 52 | } 53 | else if(isEqualNoCase(it->second, "SrtpDtlsReq")) 54 | { 55 | mSecureMediaRequired = true; 56 | value = ConversationProfile::SrtpDtls; 57 | } 58 | #endif 59 | else 60 | { 61 | cerr << "Invalid Secure Media Mode: " << it->second << endl; 62 | return false; 63 | } 64 | } 65 | // Not found 66 | return false; 67 | } 68 | 69 | ConversationProfile::SecureMediaMode 70 | ReConServerConfig::getConfigSecureMediaMode(const resip::Data& name, const ConversationProfile::SecureMediaMode defaultValue) 71 | { 72 | ConversationProfile::SecureMediaMode ret = defaultValue; 73 | getConfigValue(name, ret); 74 | return ret; 75 | } 76 | 77 | 78 | bool ReConServerConfig::isSecureMediaModeRequired() 79 | { 80 | return mSecureMediaRequired; 81 | } 82 | 83 | 84 | bool 85 | ReConServerConfig::getConfigValue(const resip::Data& name, ConversationProfile::NatTraversalMode &value) 86 | { 87 | Data lowerName(name); lowerName.lowercase(); 88 | ConfigValuesMap::iterator it = mConfigValues.find(lowerName); 89 | if(it != mConfigValues.end()) 90 | { 91 | if(isEqualNoCase(it->second, "None")) 92 | { 93 | value = ConversationProfile::NoNatTraversal; 94 | } 95 | else if(isEqualNoCase(it->second, "Bind")) 96 | { 97 | value = ConversationProfile::StunBindDiscovery; 98 | } 99 | else if(isEqualNoCase(it->second, "UdpAlloc")) 100 | { 101 | value = ConversationProfile::TurnUdpAllocation; 102 | } 103 | else if(isEqualNoCase(it->second, "TcpAlloc")) 104 | { 105 | value = ConversationProfile::TurnTcpAllocation; 106 | } 107 | #ifdef USE_SSL 108 | else if(isEqualNoCase(it->second, "TlsAlloc")) 109 | { 110 | value = ConversationProfile::TurnTlsAllocation; 111 | } 112 | #endif 113 | else 114 | { 115 | cerr << "Invalid NAT Traversal Mode: " << it->second << endl; 116 | exit(-1); 117 | } 118 | } 119 | // Not found 120 | return false; 121 | } 122 | 123 | ConversationProfile::NatTraversalMode 124 | ReConServerConfig::getConfigNatTraversalMode(const resip::Data& name, const ConversationProfile::NatTraversalMode defaultValue) 125 | { 126 | ConversationProfile::NatTraversalMode ret = defaultValue; 127 | getConfigValue(name, ret); 128 | return ret; 129 | } 130 | 131 | bool 132 | ReConServerConfig::getConfigValue(const resip::Data& name, ReConServerConfig::Application& value) 133 | { 134 | Data lowerName(name); 135 | lowerName.lowercase(); 136 | 137 | ConfigValuesMap::iterator it = mConfigValues.find(lowerName); 138 | if(it != mConfigValues.end()) 139 | { 140 | if(isEqualNoCase(it->second, "None")) 141 | { 142 | value = None; 143 | } 144 | else if(isEqualNoCase(it->second, "B2BUA")) 145 | { 146 | value = B2BUA; 147 | } 148 | else 149 | { 150 | cerr << "Invalid Application: " << it->second << endl; 151 | exit(-1); 152 | } 153 | } 154 | // Not found 155 | return false; 156 | } 157 | 158 | ReConServerConfig::Application 159 | ReConServerConfig::getConfigApplication(const resip::Data& name, const ReConServerConfig::Application defaultValue) 160 | { 161 | ReConServerConfig::Application ret = defaultValue; 162 | getConfigValue(name, ret); 163 | return ret; 164 | } 165 | 166 | void 167 | ReConServerConfig::printHelpText(int argc, char **argv) 168 | { 169 | cout << "Command line format is:" << endl; 170 | cout << " " << removePath(argv[0]) << " [] [--=] [--=] ..." << endl; 171 | cout << "Sample Command lines:" << endl; 172 | cout << " " << removePath(argv[0]) << " reConServer.config --IPAddress=192.168.1.100 --SIPUri=sip:1000@myproxy.com --Password=123 --EnableAutoAnswer=true" << endl; 173 | } 174 | 175 | } // namespace 176 | 177 | 178 | /* ==================================================================== 179 | * 180 | * Copyright 2013 Catalin Constantin Usurelu. All rights reserved. 181 | * 182 | * Redistribution and use in source and binary forms, with or without 183 | * modification, are permitted provided that the following conditions 184 | * are met: 185 | * 186 | * 1. Redistributions of source code must retain the above copyright 187 | * notice, this list of conditions and the following disclaimer. 188 | * 189 | * 2. Redistributions in binary form must reproduce the above copyright 190 | * notice, this list of conditions and the following disclaimer in 191 | * the documentation and/or other materials provided with the 192 | * distribution. 193 | * 194 | * 3. Neither the name of the author(s) nor the names of any contributors 195 | * may be used to endorse or promote products derived from this software 196 | * without specific prior written permission. 197 | * 198 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 199 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 200 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 201 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 202 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 203 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 204 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 205 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 206 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 207 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 208 | * SUCH DAMAGE. 209 | * 210 | * ==================================================================== 211 | * 212 | * 213 | */ 214 | 215 | -------------------------------------------------------------------------------- /reConServerConfig.hxx: -------------------------------------------------------------------------------- 1 | #if !defined(RECON_CONFIG_HXX) 2 | #define RECON_CONFIG_HXX 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace reconserver { 17 | 18 | class ReConServerConfig : public resip::SipConfigParse 19 | { 20 | public: 21 | 22 | typedef enum 23 | { 24 | None, 25 | B2BUA 26 | } Application; 27 | 28 | ReConServerConfig(); 29 | virtual ~ReConServerConfig(); 30 | 31 | void printHelpText(int argc, char **argv); 32 | using resip::ConfigParse::getConfigValue; 33 | 34 | bool getConfigValue(const resip::Data& name, recon::ConversationProfile::SecureMediaMode &value); 35 | recon::ConversationProfile::SecureMediaMode getConfigSecureMediaMode(const resip::Data& name, const recon::ConversationProfile::SecureMediaMode defaultValue); 36 | bool isSecureMediaModeRequired(); 37 | 38 | bool getConfigValue(const resip::Data& name, recon::ConversationProfile::NatTraversalMode &value); 39 | recon::ConversationProfile::NatTraversalMode getConfigNatTraversalMode(const resip::Data& name, const recon::ConversationProfile::NatTraversalMode defaultValue); 40 | 41 | bool getConfigValue(const resip::Data& name, Application& defaultValue); 42 | Application getConfigApplication(const resip::Data& name, const Application defaultValue); 43 | 44 | 45 | 46 | private: 47 | 48 | bool mSecureMediaRequired; 49 | 50 | }; 51 | 52 | } // namespace 53 | 54 | #endif 55 | 56 | 57 | /* ==================================================================== 58 | * 59 | * Copyright 2013 Catalin Constantin Usurelu. All rights reserved. 60 | * 61 | * Redistribution and use in source and binary forms, with or without 62 | * modification, are permitted provided that the following conditions 63 | * are met: 64 | * 65 | * 1. Redistributions of source code must retain the above copyright 66 | * notice, this list of conditions and the following disclaimer. 67 | * 68 | * 2. Redistributions in binary form must reproduce the above copyright 69 | * notice, this list of conditions and the following disclaimer in 70 | * the documentation and/or other materials provided with the 71 | * distribution. 72 | * 73 | * 3. Neither the name of the author(s) nor the names of any contributors 74 | * may be used to endorse or promote products derived from this software 75 | * without specific prior written permission. 76 | * 77 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND 78 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 79 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 80 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE 81 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 82 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 83 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 84 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 85 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 86 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 87 | * SUCH DAMAGE. 88 | * 89 | * ==================================================================== 90 | * 91 | * 92 | */ 93 | 94 | -------------------------------------------------------------------------------- /reConServer_readme.txt: -------------------------------------------------------------------------------- 1 | User Agent Tester (reConServer) Instructions 2 | ======================================= 3 | 4 | Author: Scott Godin (s g o d i n a t s i p s p e c t r u m d o t c o m) 5 | 6 | TestUA is a test console for the recon (Conversation Manager) API. 7 | 8 | 9 | Backgound Information on Conversation Manager 10 | --------------------------------------------- 11 | 12 | The Conversation Manager is a VoIP API that allows conversations and participants 13 | to be created. Participants can then be placed into conversations. In general 14 | all participants belonging to the same conversation can be heard and hear each 15 | other. A participants contribution level to a particular conversation can be 16 | modified such that they appear quiter or louder to other participants. 17 | 18 | There are three types of participants: 19 | 1. Local participant - this participant type consists of the default local speaker and 20 | microphone of the computer that is running the reConServer console. Local 21 | participants are always only explicitly destroyed. 22 | 2. Media participant - this participant type consists of an audio file, audio stream, 23 | or tone played out. Media participants are destroyed when the file or tone output is 24 | complete. 25 | 3. Remote Participant - this participant type uses audio from an external source. The 26 | current implementation uses SIP to create/connect to remote participants. Remote 27 | participants are destroyed by the API user, or a "hangup" signal from the 28 | remote party. 29 | 30 | A typical phone conversation consists of 3 components: 1 conversation, 1 local participant 31 | and 1 remote participant. 32 | 33 | 34 | 35 | Startup Command Line Parameters 36 | ------------------------------- 37 | Command line options are: 38 | -?, --?, --help, /? - display command line options 39 | 40 | -aa - enable autoanswer 41 | 42 | Enabling autoanswer will cause reConServer to automatically answer any inbound SIP calls 43 | and place them in the lowest numbered conversation currently created. 44 | 45 | -a - bind SIP transports to this IP address 46 | 47 | In general the IP address to bind to is queried from the host OS. This switch allows 48 | specification of the IP address for OS's that cannot be queried, or for machines that 49 | have mulitple NICs. 50 | 51 | -u - URI of this SIP user 52 | 53 | This option is used to specify the SIP URI for this instance of reConServer. reConServer uses 54 | this setting (-ip is not specified) in order to find the regisration server. If 55 | nothing is specified, then the default of sip:noreg@ will be used. 56 | 57 | -p - SIP password of this this SIP user 58 | 59 | Use this switch in cases where the proxy digest challenges sip messaging. 60 | 61 | -nr - no registration, set this to disable registration with SIP Proxy 62 | 63 | By default, if a SIP uri is specified, reConServer will attempt to register with it. Use 64 | this switch to disable this. 65 | 66 | -d - comma seperated list of DNS servers, overrides OS detected list 67 | 68 | By default, reConServer will query the OS for a list of DNS servers. Use this option in 69 | cases where the OS does not return any, or the correct values. 70 | 71 | -sp - local port number to use for SIP messaging (UDP/TCP) 72 | 73 | By default, reConServer will use port 5062 for SIP messaging, use this switch to specify 74 | something different. 75 | 76 | -mp - local port number to start allocating from for RTP media 77 | 78 | By default, reConServer will use media ports starting at 17384, use this switch to specify 79 | something different. 80 | 81 | -tp - local port number to use for TLS SIP messaging 82 | 83 | By default, reConServer will listen for TLS connections on port 5063, use this switch to 84 | specify something different. Note SIP certificiates must be present in executable 85 | directory for windows hosts and ~/.sipCerts directory on linux hosts. 86 | 87 | -td - domain name to use for TLS server connections 88 | 89 | By default, reConServer will query the OS for a local hostname for TLS, use this switch 90 | to override the OS queried result. 91 | 92 | -nk - no keepalives, set this to disable sending of keepalives 93 | 94 | By default, reConServer will enable UDP CRLF keepalives every 30 seconds and TCP keepalives 95 | every 180 seconds. Use this switch to disable CRLF keepalives. 96 | 97 | -op - URI of a proxy server to use a SIP outbound proxy 98 | 99 | By default reConServer does not use an outbound proxy. Use this switch to route all 100 | outbound, out-of-dialog requests through a fixed proxy despite the destination URI. 101 | 102 | -sm - sets the secure media mode 103 | 104 | By default, no secure media is offered in outbound SIP requests. Use this option to 105 | change that behaviour. Note: Inbound secure media is always accepted. 106 | Srtp - use SRTP with keying outside of media stream (SDES key negotiation) 107 | via SDP. RTP/AVP profile is used, and transport capability of RTP/SAVP is 108 | listed, in order to implement best-effort SRTP. Note: The crypo attribute 109 | is provided outside of the SDP capability, as this is required by SNOM for 110 | optional SRTP offers. 111 | SrtpReq - use SRTP with keying outside of media stream (SDES key negotiation) 112 | via SDP. RTP/SAVP profile is used to indicate that SRTP is mandatory. 113 | SrtpDtls - use SRTP with DTLS key negotiation. RTP/AVP is use as a default, and a 114 | transport capability of UDP/TLS/RTP/SAVP is listed, in order to impelement 115 | best-effort DTLS-SRTP. 116 | SrtpDtlsReq - use SRTP with DTLS key negotiation. UDP/TLS/RTP/SAVP profile is used to 117 | indicate that Dtls-Srtp use is mandatory. 118 | 119 | -nm - sets the NAT traversal mode 120 | 121 | By default, no NAT traversal strategies are used. Use this switch to specify one: 122 | Bind - use Binding discovery on a STUN server, to discover and use "public" address 123 | and port in SDP negotiations 124 | UdpAlloc - Use a TURN server as a media relay. Communicate to the TURN 125 | server over UDP and Allocate a UDP relay address and port to 126 | use in SDP negotiations 127 | TcpAlloc - Use a TURN server as a media relay. Communicate to the TURN 128 | server over TCP and Allocate a UDP relay address and port to 129 | use in SDP negotiations 130 | TlsAlloc - Use a TURN server as a media relay. Communicate to the TURN 131 | server over TLS and Allocate a UDP relay address and port to 132 | use in SDP negotiations 133 | 134 | -ns - set the hostname and port of the NAT STUN/TURN server 135 | 136 | If -nm switch is used then you MUST specify the STUN/TURN server name/address and port. 137 | 138 | -nu - sets the STUN/TURN username to use for NAT server 139 | 140 | Use this option if the STUN/TURN server requires authentication. 141 | 142 | -np - sets the STUN/TURN password to use for NAT server 143 | 144 | Use this option if the STUN/TURN server requires authentication. 145 | 146 | -nl - disables local audio support - removes requirement for local audio hardware. 147 | Note: if local audio support is disabled, then local participants cannot 148 | be created. 149 | 150 | -l - logging level 151 | 152 | By default the logging level is INFO, use this switch to change it. 153 | 154 | 155 | Sample Command line: 156 | reConServer -a 192.168.1.100 -u sip:1000@myproxy.com -p 123 -aa 157 | 158 | 159 | 160 | Console Command Reference 161 | ------------------------- 162 | Once the console is started, reConServer will automatically register with a proxy server, 163 | if required. 164 | 165 | When starting reConServer, one Conversation (Handle=1) and one local participant (Handle=1) is 166 | automatically created for convienience. 167 | 168 | The console then accepts the following commands: 169 | createConversation: <'createconv'|'cc'> 170 | Create a new empty conversation. 171 | 172 | destroyConversation: <'destroyconv'|'dc'> 173 | Destroys conversation and ends all participants that solely belong to this conversation. 174 | 175 | joinConversation: <'joinconv'|'jc'> 176 | Add all participants from source conversation to destination conversation 177 | and destroys source conversation. 178 | 179 | createLocalParticipant: <'createlocal'|'cl'> 180 | Creates a new local participant. 181 | 182 | createRemoteParticipant: <'createremote'|'crp'> [<'manual'>] 183 | (last arg is fork select mode, 'auto' is default) 184 | Creates a new remote participant (outbound SIP call) in conversation specified. 185 | Dest URI must be provided. When ForkSelectMode is set to auto the conversation 186 | manager will automatically dispose of any related conversations that were created, 187 | due to forking. 188 | 189 | createMediaResourceParticipant: <'createmedia'|'cmp'> [] 190 | Creates a new media resource participant in the specified conversation. Media is played 191 | from a source specified by the url and may be a local audio file, audio file fetched via 192 | HTTP or tones. The URL can contain parameters that specify properties of the media 193 | playback, such as number of repeats. 194 | Media Urls are of the following format: 195 | "tone"|"file":[;duration=][;local-only] 196 | [;remote-only][;repeat][;prefetch] 197 | Tones can be any DTMF digit 0-9,*,#,A-D or a special tone: 198 | dialtone, busy, fastbusy, ringback, ring, backspace, callwaiting, holding, or 199 | loudfastbusy 200 | Note: 'repeat' option only makes sense for file and http URLs 201 | Note2: 'prefetch' option only makes sense for http URLs 202 | Note3: audio files may be AU, WAV or RAW formats. Audiofiles should be 16bit mono, 203 | 8khz, PCM to avoid runtime conversion. 204 | 205 | destroyParticipant: <'destroypart'|'dp'> 206 | Ends connections to the participant and removes it from all active conversations. 207 | 208 | addPartcipant: <'addpart'|'ap'> 209 | Adds a participant to an existing conversation. 210 | 211 | removePartcipant: <'removepart'|'rp'> 212 | Removes a participant from a conversation. If the participant no longer exists in 213 | any conversation, then they are destroyed (local participants exempt). For a remote 214 | participant this means the call will be released. 215 | 216 | moveParticipant: <'movepart'|'mp'> 217 | Removes participant from src conversation and adds them to the dst conversation. 218 | 219 | modifyParticipantContribution: <'partcontrib'|'pc'> 220 | (gain in percentage) 221 | Sets a participants input and output gain towards the specified conversation. 222 | 223 | outputBridgeMatrix: <'bridgematrix'|'bm'> 224 | Outputs the sipX mixing bridge matrix for debugging purposes. 225 | 226 | alertPartcipant: <'alert'|'al'> [<'noearly'>] 227 | (last arg is early flag, enabled by default) 228 | Sends a 180 response to the far end. If noearly is enabled then SDP is not sent in the 229 | response. 230 | 231 | answerParticipant: <'answer'|'an'> 232 | Sends a 200 response to the far end. 233 | 234 | rejectParticipant: <'reject'|'rj'> [] 235 | Sends the specied response code to the far end. (default status code is 486) 236 | 237 | redirectPartcipant: <'redirect'|'rd'> 238 | If unanswered - sends a 302 response to the far end with the destURI in the 239 | contact header. Otherwise sends a REFER request to the far end. 240 | 241 | redirectToPartcipant: <'redirectTo'|'rt'> 242 | Sends a REFER request to the far end with a 'replaces' header corresponding to the 243 | partHandle specified. 244 | 245 | setSpeakerVolume: <'volume'|'sv'> 246 | setMicrophoneGain: <'gain'|'sg'> 247 | muteMicrophone: <'mute'|'mm'> <'0'|'1'> (1 to enable/mute) 248 | enableEchoCancel: <'echocancel'|'aec'> <'0'|'1'> (1 to enable) 249 | enableAutoGainControl: <'autogain'|'agc'> <'0'|'1'> (1 to enable) 250 | enableNoiseReduction: <'noisereduction'|'nr'> <'0'|'1'> (1 to enable) 251 | 252 | createSubscription: <'subscribe'|'cs'> 253 | 254 | Creates a SIP subscription to the targetURI of the corresponding eventType. 255 | Expected mimeType and subType must be specified. 256 | 257 | destroySubscription: <'destsub'|'ds'> 258 | Unsubscribes an existing subscription. 259 | 260 | setAutoAnswer <'autoans'|'aa'> <'0'|'1'> (1 to enable (default) 261 | Enable this to have reConServer automatically answer incoming calls and add to the 262 | lowest numbered created conversation. 263 | 264 | setCodecs <'setcodecs'|'sc'> [,]+ (comma separated list) 265 | Changes the default codec list/order. Note a codec plugin must have been 266 | present at startup time, in order to use that codec. Unknown id's are 267 | ignored. Default is: 0,8,96,98,99,108,97,3,109 268 | Acceptable sipX codec Ids are: 269 | 0 - G.711 mu-law 270 | 3 - GSM codec 271 | 8 - G.711 a-law 272 | 96 - Speex NB, 8,000bps 273 | 97 - Speex NB, 5,950bps 274 | 98 - Speex NB, 15,000bps 275 | 99 - Speex NB, 24,600bps 276 | 108 - Internet Low Bit Rate Codec - iLBC (RFC3951) 277 | 109 - AVT/DTMF Tones, RFC 2833 278 | 279 | setSecureMediaMode <'securemedia'|'sm'> <'None'|'Srtp'|'SrtpReq'|'SrtpDtls'|'SrtpDtlsReq'> 280 | Allows changing the secure media mode at runtime. Controls what is present 281 | for secure media in our SDP offers. Note: Inbound secure media is always accepted. 282 | Srtp - use SRTP with keying outside of media stream (SDES key negotiation) 283 | via SDP. RTP/AVP profile is used, and transport capability of RTP/SAVP is 284 | listed, in order to implement best-effort SRTP. Note: The crypo attribute 285 | is provided outside of the SDP capability, as this is required by SNOM for 286 | optional SRTP offers. 287 | SrtpReq - use SRTP with keying outside of media stream (SDES key negotiation) 288 | via SDP. RTP/SAVP profile is used to indicate that SRTP is mandatory. 289 | SrtpDtls - use SRTP with DTLS key negotiation. RTP/AVP is use as a default, and a 290 | transport capability of UDP/TLS/RTP/SAVP is listed, in order to impelement 291 | best-effort DTLS-SRTP. 292 | SrtpDtlsReq - use SRTP with DTLS key negotiation. UDP/TLS/RTP/SAVP profile is used to 293 | indicate that Dtls-Srtp use is mandatory. 294 | 295 | setNATTraversalMode <'natmode'|'nm'> <'None'|'Bind'|'UdpAlloc'|'TcpAlloc'|'TlsAlloc'> 296 | Allows changing the NAT traversal mode at runtime. 297 | Bind - use Binding discovery on a STUN server, to discover and use "public" address 298 | and port in SDP negotiations 299 | UdpAlloc - Use a TURN server as a media relay. Communicate to the TURN 300 | server over UDP and Allocate a UDP relay address and port to 301 | use in SDP negotiations 302 | TcpAlloc - Use a TURN server as a media relay. Communicate to the TURN 303 | server over TCP and Allocate a UDP relay address and port to 304 | use in SDP negotiations 305 | TlsAlloc - Use a TURN server as a media relay. Communicate to the TURN 306 | server over TLS and Allocate a UDP relay address and port to 307 | use in SDP negotiations 308 | 309 | setNATTraversalServer <'natserver'|'ns'> 310 | Allows changing the STUN/TURN server hostname and port at runtime. 311 | 312 | setNATUsername <'natuser'|'nu'> 313 | Allows changing the STUN/TURN username at runtime. 314 | 315 | setNATPassword <'natpwd'|'np'> 316 | Allows chaning the STUN/TURN password at runtime. 317 | 318 | startApplicationTimer: <'starttimer'|'st'> 319 | Test interface for application time API. 320 | 321 | displayInfo: <'info'|'i'> 322 | Display information about all of the currently created conversation handles, and 323 | participant handles. 324 | 325 | exitProgram: <'exit'|'quit'|'q'> 326 | -------------------------------------------------------------------------------- /reconserver.init: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: reConServer 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: reConServer SIP Conferencing 9 | # Description: reConServer SIP conferencing server from the reSIProcate team 10 | ### END INIT INFO 11 | 12 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 13 | NAME=reConServer 14 | DESC="SIP Conferencing" 15 | DAEMON=/usr/sbin/$NAME 16 | USER=recon 17 | GROUP=recon 18 | PIDFILE="/var/run/$NAME/$NAME.pid" 19 | #PIDFILE="/var/run/${NAME}.pid" 20 | PIDFILE_DIR=`dirname $PIDFILE` 21 | LOG_DIR=/var/log/reConServer 22 | 23 | KILL_SPEC=TERM/20/KILL/5 24 | 25 | . /lib/lsb/init-functions 26 | 27 | test -x $DAEMON || exit 1 28 | umask 002 29 | 30 | # Include defaults if available 31 | if [ -f /etc/default/$NAME ] ; then 32 | . /etc/default/$NAME 33 | fi 34 | 35 | DAEMON_OPTS="/etc/reConServer/${NAME}.config --Daemonize=true --PidFile=${PIDFILE}" 36 | 37 | if [ ! -d "$PIDFILE_DIR" ];then 38 | mkdir -p "$PIDFILE_DIR" 39 | chown $USER:$GROUP "$PIDFILE_DIR" 40 | fi 41 | 42 | if [ ! -d "$LOG_DIR" ];then 43 | mkdir -p "$LOG_DIR" 44 | chown $USER:$GROUP "$LOG_DIR" 45 | fi 46 | 47 | set -e 48 | 49 | case "$1" in 50 | start) 51 | echo -n "Starting $DESC: " 52 | start-stop-daemon --start --quiet --user $USER --chuid $USER --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_OPTS 53 | echo "$NAME." 54 | ;; 55 | stop) 56 | echo -n "Stopping $DESC: " 57 | start-stop-daemon --stop --quiet --oknodo --user $USER --pidfile $PIDFILE --retry=${KILL_SPEC} --exec $DAEMON 58 | echo "$NAME." 59 | ;; 60 | status) 61 | echo -n "Status $DESC: " 62 | PID=$(cat $PIDFILE) 63 | kill -0 $PID 64 | rc=$? 65 | # Check exit code 66 | if [ "$rc" -ne 0 ] 67 | then 68 | echo "$NAME is NOT running." 69 | exit 7 70 | else 71 | echo "$NAME is running with PID: $PID" 72 | fi 73 | ;; 74 | restart|force-reload) 75 | echo -n "Restarting $DESC: " 76 | #start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON 77 | $0 stop 78 | #start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_OPTS 79 | $0 start 80 | echo "$NAME." 81 | ;; 82 | *) 83 | N=/etc/init.d/$NAME 84 | echo "Usage: $N {start|stop|status|restart|force-reload}" >&2 85 | exit 1 86 | ;; 87 | esac 88 | 89 | exit 0 90 | -------------------------------------------------------------------------------- /reconserver.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=reConServer 4 | After=network-online.target 5 | After=syslog.target 6 | Wants=network-online.target 7 | 8 | [Service] 9 | ExecStart=/usr/sbin/reConServer /etc/reConServer/reConServer.config --Daemonize=false 10 | ExecReload=/bin/kill -HUP ${MAINPID} 11 | Restart=always 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | 16 | -------------------------------------------------------------------------------- /record_prompt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resiprocate/reConServer/b3a1c47cce7a71c12fffc77e863ed1d673a08a2b/record_prompt.wav --------------------------------------------------------------------------------