958 lines
30 KiB
C++
958 lines
30 KiB
C++
|
/**********
|
||
|
This library is free software; you can redistribute it and/or modify it under
|
||
|
the terms of the GNU Lesser General Public License as published by the
|
||
|
Free Software Foundation; either version 2.1 of the License, or (at your
|
||
|
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
|
||
|
|
||
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
||
|
more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public License
|
||
|
along with this library; if not, write to the Free Software Foundation, Inc.,
|
||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
**********/
|
||
|
// "liveMedia"
|
||
|
// Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved.
|
||
|
// A generic SIP client
|
||
|
// Implementation
|
||
|
|
||
|
#include "SIPClient.hh"
|
||
|
#include "GroupsockHelper.hh"
|
||
|
|
||
|
#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
|
||
|
#define _strncasecmp _strnicmp
|
||
|
#else
|
||
|
#define _strncasecmp strncasecmp
|
||
|
#endif
|
||
|
|
||
|
////////// SIPClient //////////
|
||
|
|
||
|
SIPClient* SIPClient
|
||
|
::createNew(UsageEnvironment& env,
|
||
|
unsigned char desiredAudioRTPPayloadFormat,
|
||
|
char const* mimeSubtype,
|
||
|
int verbosityLevel, char const* applicationName) {
|
||
|
return new SIPClient(env, desiredAudioRTPPayloadFormat, mimeSubtype,
|
||
|
verbosityLevel, applicationName);
|
||
|
}
|
||
|
|
||
|
void SIPClient::setUserAgentString(char const* userAgentName) {
|
||
|
if (userAgentName == NULL) return;
|
||
|
|
||
|
// Change the existing user agent header string:
|
||
|
char const* const formatStr = "User-Agent: %s\r\n";
|
||
|
unsigned const headerSize = strlen(formatStr) + strlen(userAgentName);
|
||
|
delete[] fUserAgentHeaderStr;
|
||
|
fUserAgentHeaderStr = new char[headerSize];
|
||
|
sprintf(fUserAgentHeaderStr, formatStr, userAgentName);
|
||
|
fUserAgentHeaderStrLen = strlen(fUserAgentHeaderStr);
|
||
|
}
|
||
|
|
||
|
SIPClient::SIPClient(UsageEnvironment& env,
|
||
|
unsigned char desiredAudioRTPPayloadFormat,
|
||
|
char const* mimeSubtype,
|
||
|
int verbosityLevel, char const* applicationName)
|
||
|
: Medium(env),
|
||
|
fT1(500000 /* 500 ms */),
|
||
|
fDesiredAudioRTPPayloadFormat(desiredAudioRTPPayloadFormat),
|
||
|
fVerbosityLevel(verbosityLevel), fCSeq(0),
|
||
|
fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0),
|
||
|
fURL(NULL), fURLSize(0),
|
||
|
fToTagStr(NULL), fToTagStrSize(0),
|
||
|
fUserName(NULL), fUserNameSize(0),
|
||
|
fInviteSDPDescription(NULL), fInviteSDPDescriptionReturned(NULL),
|
||
|
fInviteCmd(NULL), fInviteCmdSize(0) {
|
||
|
if (mimeSubtype == NULL) mimeSubtype = "";
|
||
|
fMIMESubtype = strDup(mimeSubtype);
|
||
|
fMIMESubtypeSize = strlen(fMIMESubtype);
|
||
|
|
||
|
if (applicationName == NULL) applicationName = "";
|
||
|
fApplicationName = strDup(applicationName);
|
||
|
fApplicationNameSize = strlen(fApplicationName);
|
||
|
|
||
|
struct in_addr ourAddress;
|
||
|
ourAddress.s_addr = ourIPAddress(env); // hack
|
||
|
fOurAddressStr = strDup(AddressString(ourAddress).val());
|
||
|
fOurAddressStrSize = strlen(fOurAddressStr);
|
||
|
|
||
|
fOurSocket = new Groupsock(env, ourAddress, 0, 255);
|
||
|
if (fOurSocket == NULL) {
|
||
|
env << "ERROR: Failed to create socket for addr "
|
||
|
<< fOurAddressStr << ": "
|
||
|
<< env.getResultMsg() << "\n";
|
||
|
}
|
||
|
|
||
|
// Now, find out our source port number. Hack: Do this by first trying to
|
||
|
// send a 0-length packet, so that the "getSourcePort()" call will work.
|
||
|
fOurSocket->output(envir(), (unsigned char*)"", 0);
|
||
|
Port srcPort(0);
|
||
|
getSourcePort(env, fOurSocket->socketNum(), srcPort);
|
||
|
if (srcPort.num() != 0) {
|
||
|
fOurPortNum = ntohs(srcPort.num());
|
||
|
} else {
|
||
|
// No luck. Try again using a default port number:
|
||
|
fOurPortNum = 5060;
|
||
|
delete fOurSocket;
|
||
|
fOurSocket = new Groupsock(env, ourAddress, fOurPortNum, 255);
|
||
|
if (fOurSocket == NULL) {
|
||
|
env << "ERROR: Failed to create socket for addr "
|
||
|
<< fOurAddressStr << ", port "
|
||
|
<< fOurPortNum << ": "
|
||
|
<< env.getResultMsg() << "\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set the "User-Agent:" header to use in each request:
|
||
|
char const* const libName = "LIVE555 Streaming Media v";
|
||
|
char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
|
||
|
char const* libPrefix; char const* libSuffix;
|
||
|
if (applicationName == NULL || applicationName[0] == '\0') {
|
||
|
applicationName = libPrefix = libSuffix = "";
|
||
|
} else {
|
||
|
libPrefix = " (";
|
||
|
libSuffix = ")";
|
||
|
}
|
||
|
unsigned userAgentNameSize
|
||
|
= fApplicationNameSize + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix) + 1;
|
||
|
char* userAgentName = new char[userAgentNameSize];
|
||
|
sprintf(userAgentName, "%s%s%s%s%s",
|
||
|
applicationName, libPrefix, libName, libVersionStr, libSuffix);
|
||
|
setUserAgentString(userAgentName);
|
||
|
delete[] userAgentName;
|
||
|
|
||
|
reset();
|
||
|
}
|
||
|
|
||
|
SIPClient::~SIPClient() {
|
||
|
reset();
|
||
|
|
||
|
delete[] fUserAgentHeaderStr;
|
||
|
delete fOurSocket;
|
||
|
delete[] (char*)fOurAddressStr;
|
||
|
delete[] (char*)fApplicationName;
|
||
|
delete[] (char*)fMIMESubtype;
|
||
|
}
|
||
|
|
||
|
void SIPClient::reset() {
|
||
|
fWorkingAuthenticator = NULL;
|
||
|
delete[] fInviteCmd; fInviteCmd = NULL; fInviteCmdSize = 0;
|
||
|
delete[] fInviteSDPDescription; fInviteSDPDescription = NULL;
|
||
|
|
||
|
delete[] (char*)fUserName; fUserName = strDup(fApplicationName);
|
||
|
fUserNameSize = strlen(fUserName);
|
||
|
|
||
|
fValidAuthenticator.reset();
|
||
|
|
||
|
delete[] (char*)fToTagStr; fToTagStr = NULL; fToTagStrSize = 0;
|
||
|
fServerPortNum = 0;
|
||
|
fServerAddress.s_addr = 0;
|
||
|
delete[] (char*)fURL; fURL = NULL; fURLSize = 0;
|
||
|
}
|
||
|
|
||
|
void SIPClient::setProxyServer(unsigned proxyServerAddress,
|
||
|
portNumBits proxyServerPortNum) {
|
||
|
fServerAddress.s_addr = proxyServerAddress;
|
||
|
fServerPortNum = proxyServerPortNum;
|
||
|
if (fOurSocket != NULL) {
|
||
|
fOurSocket->changeDestinationParameters(fServerAddress,
|
||
|
fServerPortNum, 255);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static char* getLine(char* startOfLine) {
|
||
|
// returns the start of the next line, or NULL if none
|
||
|
for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
|
||
|
if (*ptr == '\r' || *ptr == '\n') {
|
||
|
// We found the end of the line
|
||
|
*ptr++ = '\0';
|
||
|
if (*ptr == '\n') ++ptr;
|
||
|
return ptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
char* SIPClient::invite(char const* url, Authenticator* authenticator) {
|
||
|
// First, check whether "url" contains a username:password to be used:
|
||
|
char* username; char* password;
|
||
|
if (authenticator == NULL
|
||
|
&& parseSIPURLUsernamePassword(url, username, password)) {
|
||
|
char* result = inviteWithPassword(url, username, password);
|
||
|
delete[] username; delete[] password; // they were dynamically allocated
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
if (!processURL(url)) return NULL;
|
||
|
|
||
|
delete[] (char*)fURL; fURL = strDup(url);
|
||
|
fURLSize = strlen(fURL);
|
||
|
|
||
|
fCallId = our_random32();
|
||
|
fFromTag = our_random32();
|
||
|
|
||
|
return invite1(authenticator);
|
||
|
}
|
||
|
|
||
|
char* SIPClient::invite1(Authenticator* authenticator) {
|
||
|
do {
|
||
|
// Send the INVITE command:
|
||
|
|
||
|
// First, construct an authenticator string:
|
||
|
fValidAuthenticator.reset();
|
||
|
fWorkingAuthenticator = authenticator;
|
||
|
char* authenticatorStr
|
||
|
= createAuthenticatorString(fWorkingAuthenticator, "INVITE", fURL);
|
||
|
|
||
|
// Then, construct the SDP description to be sent in the INVITE:
|
||
|
char* rtpmapLine;
|
||
|
unsigned rtpmapLineSize;
|
||
|
if (fMIMESubtypeSize > 0) {
|
||
|
char const* const rtpmapFmt =
|
||
|
"a=rtpmap:%u %s/8000\r\n";
|
||
|
unsigned rtpmapFmtSize = strlen(rtpmapFmt)
|
||
|
+ 3 /* max char len */ + fMIMESubtypeSize;
|
||
|
rtpmapLine = new char[rtpmapFmtSize];
|
||
|
sprintf(rtpmapLine, rtpmapFmt,
|
||
|
fDesiredAudioRTPPayloadFormat, fMIMESubtype);
|
||
|
rtpmapLineSize = strlen(rtpmapLine);
|
||
|
} else {
|
||
|
// Static payload type => no "a=rtpmap:" line
|
||
|
rtpmapLine = strDup("");
|
||
|
rtpmapLineSize = 0;
|
||
|
}
|
||
|
char const* const inviteSDPFmt =
|
||
|
"v=0\r\n"
|
||
|
"o=- %u %u IN IP4 %s\r\n"
|
||
|
"s=%s session\r\n"
|
||
|
"c=IN IP4 %s\r\n"
|
||
|
"t=0 0\r\n"
|
||
|
"m=audio %u RTP/AVP %u\r\n"
|
||
|
"%s";
|
||
|
unsigned inviteSDPFmtSize = strlen(inviteSDPFmt)
|
||
|
+ 20 /* max int len */ + 20 + fOurAddressStrSize
|
||
|
+ fApplicationNameSize
|
||
|
+ fOurAddressStrSize
|
||
|
+ 5 /* max short len */ + 3 /* max char len */
|
||
|
+ rtpmapLineSize;
|
||
|
delete[] fInviteSDPDescription;
|
||
|
fInviteSDPDescription = new char[inviteSDPFmtSize];
|
||
|
sprintf(fInviteSDPDescription, inviteSDPFmt,
|
||
|
fCallId, fCSeq, fOurAddressStr,
|
||
|
fApplicationName,
|
||
|
fOurAddressStr,
|
||
|
fClientStartPortNum, fDesiredAudioRTPPayloadFormat,
|
||
|
rtpmapLine);
|
||
|
unsigned inviteSDPSize = strlen(fInviteSDPDescription);
|
||
|
delete[] rtpmapLine;
|
||
|
|
||
|
char const* const cmdFmt =
|
||
|
"INVITE %s SIP/2.0\r\n"
|
||
|
"From: %s <sip:%s@%s>;tag=%u\r\n"
|
||
|
"Via: SIP/2.0/UDP %s:%u\r\n"
|
||
|
"Max-Forwards: 70\r\n"
|
||
|
"To: %s\r\n"
|
||
|
"Contact: sip:%s@%s:%u\r\n"
|
||
|
"Call-ID: %u@%s\r\n"
|
||
|
"CSeq: %d INVITE\r\n"
|
||
|
"Content-Type: application/sdp\r\n"
|
||
|
"%s" /* Proxy-Authorization: line (if any) */
|
||
|
"%s" /* User-Agent: line */
|
||
|
"Content-Length: %d\r\n\r\n"
|
||
|
"%s";
|
||
|
unsigned inviteCmdSize = strlen(cmdFmt)
|
||
|
+ fURLSize
|
||
|
+ 2*fUserNameSize + fOurAddressStrSize + 20 /* max int len */
|
||
|
+ fOurAddressStrSize + 5 /* max port len */
|
||
|
+ fURLSize
|
||
|
+ fUserNameSize + fOurAddressStrSize + 5
|
||
|
+ 20 + fOurAddressStrSize
|
||
|
+ 20
|
||
|
+ strlen(authenticatorStr)
|
||
|
+ fUserAgentHeaderStrLen
|
||
|
+ 20
|
||
|
+ inviteSDPSize;
|
||
|
delete[] fInviteCmd; fInviteCmd = new char[inviteCmdSize];
|
||
|
sprintf(fInviteCmd, cmdFmt,
|
||
|
fURL,
|
||
|
fUserName, fUserName, fOurAddressStr, fFromTag,
|
||
|
fOurAddressStr, fOurPortNum,
|
||
|
fURL,
|
||
|
fUserName, fOurAddressStr, fOurPortNum,
|
||
|
fCallId, fOurAddressStr,
|
||
|
++fCSeq,
|
||
|
authenticatorStr,
|
||
|
fUserAgentHeaderStr,
|
||
|
inviteSDPSize,
|
||
|
fInviteSDPDescription);
|
||
|
fInviteCmdSize = strlen(fInviteCmd);
|
||
|
delete[] authenticatorStr;
|
||
|
|
||
|
// Before sending the "INVITE", arrange to handle any response packets,
|
||
|
// and set up timers:
|
||
|
fInviteClientState = Calling;
|
||
|
fEventLoopStopFlag = 0;
|
||
|
TaskScheduler& sched = envir().taskScheduler(); // abbrev.
|
||
|
sched.turnOnBackgroundReadHandling(fOurSocket->socketNum(),
|
||
|
&inviteResponseHandler, this);
|
||
|
fTimerALen = 1*fT1; // initially
|
||
|
fTimerACount = 0; // initially
|
||
|
fTimerA = sched.scheduleDelayedTask(fTimerALen, timerAHandler, this);
|
||
|
fTimerB = sched.scheduleDelayedTask(64*fT1, timerBHandler, this);
|
||
|
fTimerD = NULL; // for now
|
||
|
|
||
|
if (!sendINVITE()) break;
|
||
|
|
||
|
// Enter the event loop, to handle response packets, and timeouts:
|
||
|
envir().taskScheduler().doEventLoop(&fEventLoopStopFlag);
|
||
|
|
||
|
// We're finished with this "INVITE".
|
||
|
// Turn off response handling and timers:
|
||
|
sched.turnOffBackgroundReadHandling(fOurSocket->socketNum());
|
||
|
sched.unscheduleDelayedTask(fTimerA);
|
||
|
sched.unscheduleDelayedTask(fTimerB);
|
||
|
sched.unscheduleDelayedTask(fTimerD);
|
||
|
|
||
|
// NOTE: We return the SDP description that we used in the "INVITE",
|
||
|
// not the one that we got from the server.
|
||
|
// ##### Later: match the codecs in the response (offer, answer) #####
|
||
|
if (fInviteSDPDescription != NULL) {
|
||
|
return strDup(fInviteSDPDescription);
|
||
|
}
|
||
|
} while (0);
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void SIPClient::inviteResponseHandler(void* clientData, int /*mask*/) {
|
||
|
SIPClient* client = (SIPClient*)clientData;
|
||
|
unsigned responseCode = client->getResponseCode();
|
||
|
client->doInviteStateMachine(responseCode);
|
||
|
}
|
||
|
|
||
|
// Special 'response codes' that represent timers expiring:
|
||
|
unsigned const timerAFires = 0xAAAAAAAA;
|
||
|
unsigned const timerBFires = 0xBBBBBBBB;
|
||
|
unsigned const timerDFires = 0xDDDDDDDD;
|
||
|
|
||
|
void SIPClient::timerAHandler(void* clientData) {
|
||
|
SIPClient* client = (SIPClient*)clientData;
|
||
|
if (client->fVerbosityLevel >= 1) {
|
||
|
client->envir() << "RETRANSMISSION " << ++client->fTimerACount
|
||
|
<< ", after " << client->fTimerALen/1000000.0
|
||
|
<< " additional seconds\n";
|
||
|
}
|
||
|
client->doInviteStateMachine(timerAFires);
|
||
|
}
|
||
|
|
||
|
void SIPClient::timerBHandler(void* clientData) {
|
||
|
SIPClient* client = (SIPClient*)clientData;
|
||
|
if (client->fVerbosityLevel >= 1) {
|
||
|
client->envir() << "RETRANSMISSION TIMEOUT, after "
|
||
|
<< 64*client->fT1/1000000.0 << " seconds\n";
|
||
|
fflush(stderr);
|
||
|
}
|
||
|
client->doInviteStateMachine(timerBFires);
|
||
|
}
|
||
|
|
||
|
void SIPClient::timerDHandler(void* clientData) {
|
||
|
SIPClient* client = (SIPClient*)clientData;
|
||
|
if (client->fVerbosityLevel >= 1) {
|
||
|
client->envir() << "TIMER D EXPIRED\n";
|
||
|
}
|
||
|
client->doInviteStateMachine(timerDFires);
|
||
|
}
|
||
|
|
||
|
void SIPClient::doInviteStateMachine(unsigned responseCode) {
|
||
|
// Implement the state transition diagram (RFC 3261, Figure 5)
|
||
|
TaskScheduler& sched = envir().taskScheduler(); // abbrev.
|
||
|
switch (fInviteClientState) {
|
||
|
case Calling: {
|
||
|
if (responseCode == timerAFires) {
|
||
|
// Restart timer A (with double the timeout interval):
|
||
|
fTimerALen *= 2;
|
||
|
fTimerA
|
||
|
= sched.scheduleDelayedTask(fTimerALen, timerAHandler, this);
|
||
|
|
||
|
fInviteClientState = Calling;
|
||
|
if (!sendINVITE()) doInviteStateTerminated(0);
|
||
|
} else {
|
||
|
// Turn off timers A & B before moving to a new state:
|
||
|
sched.unscheduleDelayedTask(fTimerA);
|
||
|
sched.unscheduleDelayedTask(fTimerB);
|
||
|
|
||
|
if (responseCode == timerBFires) {
|
||
|
envir().setResultMsg("No response from server");
|
||
|
doInviteStateTerminated(0);
|
||
|
} else if (responseCode >= 100 && responseCode <= 199) {
|
||
|
fInviteClientState = Proceeding;
|
||
|
} else if (responseCode >= 200 && responseCode <= 299) {
|
||
|
doInviteStateTerminated(responseCode);
|
||
|
} else if (responseCode >= 400 && responseCode <= 499) {
|
||
|
doInviteStateTerminated(responseCode);
|
||
|
// this isn't what the spec says, but it seems right...
|
||
|
} else if (responseCode >= 300 && responseCode <= 699) {
|
||
|
fInviteClientState = Completed;
|
||
|
fTimerD
|
||
|
= sched.scheduleDelayedTask(32000000, timerDHandler, this);
|
||
|
if (!sendACK()) doInviteStateTerminated(0);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case Proceeding: {
|
||
|
if (responseCode >= 100 && responseCode <= 199) {
|
||
|
fInviteClientState = Proceeding;
|
||
|
} else if (responseCode >= 200 && responseCode <= 299) {
|
||
|
doInviteStateTerminated(responseCode);
|
||
|
} else if (responseCode >= 400 && responseCode <= 499) {
|
||
|
doInviteStateTerminated(responseCode);
|
||
|
// this isn't what the spec says, but it seems right...
|
||
|
} else if (responseCode >= 300 && responseCode <= 699) {
|
||
|
fInviteClientState = Completed;
|
||
|
fTimerD = sched.scheduleDelayedTask(32000000, timerDHandler, this);
|
||
|
if (!sendACK()) doInviteStateTerminated(0);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case Completed: {
|
||
|
if (responseCode == timerDFires) {
|
||
|
envir().setResultMsg("Transaction terminated");
|
||
|
doInviteStateTerminated(0);
|
||
|
} else if (responseCode >= 300 && responseCode <= 699) {
|
||
|
fInviteClientState = Completed;
|
||
|
if (!sendACK()) doInviteStateTerminated(0);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case Terminated: {
|
||
|
doInviteStateTerminated(responseCode);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SIPClient::doInviteStateTerminated(unsigned responseCode) {
|
||
|
fInviteClientState = Terminated; // FWIW...
|
||
|
if (responseCode < 200 || responseCode > 299) {
|
||
|
// We failed, so return NULL;
|
||
|
delete[] fInviteSDPDescription; fInviteSDPDescription = NULL;
|
||
|
delete[] fInviteSDPDescriptionReturned; fInviteSDPDescriptionReturned = NULL;
|
||
|
}
|
||
|
|
||
|
// Unblock the event loop:
|
||
|
fEventLoopStopFlag = ~0;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::sendINVITE() {
|
||
|
if (!sendRequest(fInviteCmd, fInviteCmdSize)) {
|
||
|
envir().setResultErrMsg("INVITE send() failed: ");
|
||
|
return False;
|
||
|
}
|
||
|
return True;
|
||
|
}
|
||
|
|
||
|
unsigned SIPClient::getResponseCode() {
|
||
|
unsigned responseCode = 0;
|
||
|
do {
|
||
|
// Get the response from the server:
|
||
|
unsigned const readBufSize = 10000;
|
||
|
char readBuffer[readBufSize+1]; char* readBuf = readBuffer;
|
||
|
|
||
|
char* firstLine = NULL;
|
||
|
char* nextLineStart = NULL;
|
||
|
unsigned bytesRead = getResponse(readBuf, readBufSize);
|
||
|
if (bytesRead == 0) break;
|
||
|
if (fVerbosityLevel >= 1) {
|
||
|
envir() << "Received INVITE response: " << readBuf << "\n";
|
||
|
}
|
||
|
|
||
|
// Inspect the first line to get the response code:
|
||
|
firstLine = readBuf;
|
||
|
nextLineStart = getLine(firstLine);
|
||
|
if (!parseResponseCode(firstLine, responseCode)) break;
|
||
|
|
||
|
if (responseCode != 200) {
|
||
|
if (responseCode >= 400 && responseCode <= 499
|
||
|
&& fWorkingAuthenticator != NULL) {
|
||
|
// We have an authentication failure, so fill in
|
||
|
// "*fWorkingAuthenticator" using the contents of a following
|
||
|
// "Proxy-Authenticate:" line. (Once we compute a 'response' for
|
||
|
// "fWorkingAuthenticator", it can be used in a subsequent request
|
||
|
// - that will hopefully succeed.)
|
||
|
char* lineStart;
|
||
|
while (1) {
|
||
|
lineStart = nextLineStart;
|
||
|
if (lineStart == NULL) break;
|
||
|
|
||
|
nextLineStart = getLine(lineStart);
|
||
|
if (lineStart[0] == '\0') break; // this is a blank line
|
||
|
|
||
|
char* realm = strDupSize(lineStart);
|
||
|
char* nonce = strDupSize(lineStart);
|
||
|
// ##### Check for the format of "Proxy-Authenticate:" lines from
|
||
|
// ##### known server types.
|
||
|
// ##### This is a crock! We should make the parsing more general
|
||
|
Boolean foundAuthenticateHeader = False;
|
||
|
if (
|
||
|
// Asterisk #####
|
||
|
sscanf(lineStart, "Proxy-Authenticate: Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"",
|
||
|
realm, nonce) == 2 ||
|
||
|
// Cisco ATA #####
|
||
|
sscanf(lineStart, "Proxy-Authenticate: Digest algorithm=MD5,domain=\"%*[^\"]\",nonce=\"%[^\"]\", realm=\"%[^\"]\"",
|
||
|
nonce, realm) == 2) {
|
||
|
fWorkingAuthenticator->setRealmAndNonce(realm, nonce);
|
||
|
foundAuthenticateHeader = True;
|
||
|
}
|
||
|
delete[] realm; delete[] nonce;
|
||
|
if (foundAuthenticateHeader) break;
|
||
|
}
|
||
|
}
|
||
|
envir().setResultMsg("cannot handle INVITE response: ", firstLine);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Skip every subsequent header line, until we see a blank line.
|
||
|
// While doing so, check for "To:" and "Content-Length:" lines.
|
||
|
// The remaining data is assumed to be the SDP descriptor that we want.
|
||
|
// We should really do some more checking on the headers here - e.g., to
|
||
|
// check for "Content-type: application/sdp", "CSeq", etc. #####
|
||
|
int contentLength = -1;
|
||
|
char* lineStart;
|
||
|
while (1) {
|
||
|
lineStart = nextLineStart;
|
||
|
if (lineStart == NULL) break;
|
||
|
|
||
|
nextLineStart = getLine(lineStart);
|
||
|
if (lineStart[0] == '\0') break; // this is a blank line
|
||
|
|
||
|
char* toTagStr = strDupSize(lineStart);
|
||
|
if (sscanf(lineStart, "To:%*[^;]; tag=%s", toTagStr) == 1) {
|
||
|
delete[] (char*)fToTagStr; fToTagStr = strDup(toTagStr);
|
||
|
fToTagStrSize = strlen(fToTagStr);
|
||
|
}
|
||
|
delete[] toTagStr;
|
||
|
|
||
|
if (sscanf(lineStart, "Content-Length: %d", &contentLength) == 1
|
||
|
|| sscanf(lineStart, "Content-length: %d", &contentLength) == 1) {
|
||
|
if (contentLength < 0) {
|
||
|
envir().setResultMsg("Bad \"Content-Length:\" header: \"",
|
||
|
lineStart, "\"");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We're now at the end of the response header lines
|
||
|
if (lineStart == NULL) {
|
||
|
envir().setResultMsg("no content following header lines: ", readBuf);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Use the remaining data as the SDP descr, but first, check
|
||
|
// the "Content-Length:" header (if any) that we saw. We may need to
|
||
|
// read more data, or we may have extraneous data in the buffer.
|
||
|
char* bodyStart = nextLineStart;
|
||
|
if (bodyStart != NULL && contentLength >= 0) {
|
||
|
// We saw a "Content-Length:" header
|
||
|
unsigned numBodyBytes = &readBuf[bytesRead] - bodyStart;
|
||
|
if (contentLength > (int)numBodyBytes) {
|
||
|
// We need to read more data. First, make sure we have enough
|
||
|
// space for it:
|
||
|
unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
|
||
|
#ifdef USING_TCP
|
||
|
// THIS CODE WORKS ONLY FOR TCP: #####
|
||
|
unsigned remainingBufferSize
|
||
|
= readBufSize - (bytesRead + (readBuf - readBuffer));
|
||
|
if (numExtraBytesNeeded > remainingBufferSize) {
|
||
|
char tmpBuf[200];
|
||
|
sprintf(tmpBuf, "Read buffer size (%d) is too small for \"Content-Length:\" %d (need a buffer size of >= %d bytes\n",
|
||
|
readBufSize, contentLength,
|
||
|
readBufSize + numExtraBytesNeeded - remainingBufferSize);
|
||
|
envir().setResultMsg(tmpBuf);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Keep reading more data until we have enough:
|
||
|
if (fVerbosityLevel >= 1) {
|
||
|
envir() << "Need to read " << numExtraBytesNeeded
|
||
|
<< " extra bytes\n";
|
||
|
}
|
||
|
while (numExtraBytesNeeded > 0) {
|
||
|
char* ptr = &readBuf[bytesRead];
|
||
|
unsigned bytesRead2;
|
||
|
struct sockaddr_in fromAddr;
|
||
|
Boolean readSuccess
|
||
|
= fOurSocket->handleRead((unsigned char*)ptr,
|
||
|
numExtraBytesNeeded,
|
||
|
bytesRead2, fromAddr);
|
||
|
if (!readSuccess) break;
|
||
|
ptr[bytesRead2] = '\0';
|
||
|
if (fVerbosityLevel >= 1) {
|
||
|
envir() << "Read " << bytesRead2
|
||
|
<< " extra bytes: " << ptr << "\n";
|
||
|
}
|
||
|
|
||
|
bytesRead += bytesRead2;
|
||
|
numExtraBytesNeeded -= bytesRead2;
|
||
|
}
|
||
|
#endif
|
||
|
if (numExtraBytesNeeded > 0) break; // one of the reads failed
|
||
|
}
|
||
|
|
||
|
bodyStart[contentLength] = '\0'; // trims any extra data
|
||
|
delete[] fInviteSDPDescriptionReturned; fInviteSDPDescriptionReturned = strDup(bodyStart);
|
||
|
}
|
||
|
} while (0);
|
||
|
|
||
|
return responseCode;
|
||
|
}
|
||
|
|
||
|
char* SIPClient::inviteWithPassword(char const* url, char const* username,
|
||
|
char const* password) {
|
||
|
delete[] (char*)fUserName; fUserName = strDup(username);
|
||
|
fUserNameSize = strlen(fUserName);
|
||
|
|
||
|
Authenticator authenticator(username, password);
|
||
|
char* inviteResult = invite(url, &authenticator);
|
||
|
if (inviteResult != NULL) {
|
||
|
// We are already authorized
|
||
|
return inviteResult;
|
||
|
}
|
||
|
|
||
|
// The "realm" and "nonce" fields should have been filled in:
|
||
|
if (authenticator.realm() == NULL || authenticator.nonce() == NULL) {
|
||
|
// We haven't been given enough information to try again, so fail:
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// Try again (but with the same CallId):
|
||
|
inviteResult = invite1(&authenticator);
|
||
|
if (inviteResult != NULL) {
|
||
|
// The authenticator worked, so use it in future requests:
|
||
|
fValidAuthenticator = authenticator;
|
||
|
}
|
||
|
|
||
|
return inviteResult;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::sendACK() {
|
||
|
char* cmd = NULL;
|
||
|
do {
|
||
|
char const* const cmdFmt =
|
||
|
"ACK %s SIP/2.0\r\n"
|
||
|
"From: %s <sip:%s@%s>;tag=%u\r\n"
|
||
|
"Via: SIP/2.0/UDP %s:%u\r\n"
|
||
|
"Max-Forwards: 70\r\n"
|
||
|
"To: %s;tag=%s\r\n"
|
||
|
"Call-ID: %u@%s\r\n"
|
||
|
"CSeq: %d ACK\r\n"
|
||
|
"Content-Length: 0\r\n\r\n";
|
||
|
unsigned cmdSize = strlen(cmdFmt)
|
||
|
+ fURLSize
|
||
|
+ 2*fUserNameSize + fOurAddressStrSize + 20 /* max int len */
|
||
|
+ fOurAddressStrSize + 5 /* max port len */
|
||
|
+ fURLSize + fToTagStrSize
|
||
|
+ 20 + fOurAddressStrSize
|
||
|
+ 20;
|
||
|
cmd = new char[cmdSize];
|
||
|
sprintf(cmd, cmdFmt,
|
||
|
fURL,
|
||
|
fUserName, fUserName, fOurAddressStr, fFromTag,
|
||
|
fOurAddressStr, fOurPortNum,
|
||
|
fURL, fToTagStr,
|
||
|
fCallId, fOurAddressStr,
|
||
|
fCSeq /* note: it's the same as before; not incremented */);
|
||
|
|
||
|
if (!sendRequest(cmd, strlen(cmd))) {
|
||
|
envir().setResultErrMsg("ACK send() failed: ");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
delete[] cmd;
|
||
|
return True;
|
||
|
} while (0);
|
||
|
|
||
|
delete[] cmd;
|
||
|
return False;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::sendBYE() {
|
||
|
// NOTE: This should really be retransmitted, for reliability #####
|
||
|
char* cmd = NULL;
|
||
|
do {
|
||
|
char const* const cmdFmt =
|
||
|
"BYE %s SIP/2.0\r\n"
|
||
|
"From: %s <sip:%s@%s>;tag=%u\r\n"
|
||
|
"Via: SIP/2.0/UDP %s:%u\r\n"
|
||
|
"Max-Forwards: 70\r\n"
|
||
|
"To: %s;tag=%s\r\n"
|
||
|
"Call-ID: %u@%s\r\n"
|
||
|
"CSeq: %d BYE\r\n"
|
||
|
"Content-Length: 0\r\n\r\n";
|
||
|
unsigned cmdSize = strlen(cmdFmt)
|
||
|
+ fURLSize
|
||
|
+ 2*fUserNameSize + fOurAddressStrSize + 20 /* max int len */
|
||
|
+ fOurAddressStrSize + 5 /* max port len */
|
||
|
+ fURLSize + fToTagStrSize
|
||
|
+ 20 + fOurAddressStrSize
|
||
|
+ 20;
|
||
|
cmd = new char[cmdSize];
|
||
|
sprintf(cmd, cmdFmt,
|
||
|
fURL,
|
||
|
fUserName, fUserName, fOurAddressStr, fFromTag,
|
||
|
fOurAddressStr, fOurPortNum,
|
||
|
fURL, fToTagStr,
|
||
|
fCallId, fOurAddressStr,
|
||
|
++fCSeq);
|
||
|
|
||
|
if (!sendRequest(cmd, strlen(cmd))) {
|
||
|
envir().setResultErrMsg("BYE send() failed: ");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
delete[] cmd;
|
||
|
return True;
|
||
|
} while (0);
|
||
|
|
||
|
delete[] cmd;
|
||
|
return False;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::processURL(char const* url) {
|
||
|
do {
|
||
|
// If we don't already have a server address/port, then
|
||
|
// get these by parsing the URL:
|
||
|
if (fServerAddress.s_addr == 0) {
|
||
|
NetAddress destAddress;
|
||
|
if (!parseSIPURL(envir(), url, destAddress, fServerPortNum)) break;
|
||
|
fServerAddress.s_addr = *(unsigned*)(destAddress.data());
|
||
|
|
||
|
if (fOurSocket != NULL) {
|
||
|
fOurSocket->changeDestinationParameters(fServerAddress,
|
||
|
fServerPortNum, 255);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return True;
|
||
|
} while (0);
|
||
|
|
||
|
return False;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::parseSIPURL(UsageEnvironment& env, char const* url,
|
||
|
NetAddress& address,
|
||
|
portNumBits& portNum) {
|
||
|
do {
|
||
|
// Parse the URL as "sip:<username>@<address>:<port>/<etc>"
|
||
|
// (with ":<port>" and "/<etc>" optional)
|
||
|
// Also, skip over any "<username>[:<password>]@" preceding <address>
|
||
|
char const* prefix = "sip:";
|
||
|
unsigned const prefixLength = 4;
|
||
|
if (_strncasecmp(url, prefix, prefixLength) != 0) {
|
||
|
env.setResultMsg("URL is not of the form \"", prefix, "\"");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
unsigned const parseBufferSize = 100;
|
||
|
char parseBuffer[parseBufferSize];
|
||
|
unsigned addressStartIndex = prefixLength;
|
||
|
while (url[addressStartIndex] != '\0'
|
||
|
&& url[addressStartIndex++] != '@') {}
|
||
|
char const* from = &url[addressStartIndex];
|
||
|
|
||
|
// Skip over any "<username>[:<password>]@"
|
||
|
char const* from1 = from;
|
||
|
while (*from1 != '\0' && *from1 != '/') {
|
||
|
if (*from1 == '@') {
|
||
|
from = ++from1;
|
||
|
break;
|
||
|
}
|
||
|
++from1;
|
||
|
}
|
||
|
|
||
|
char* to = &parseBuffer[0];
|
||
|
unsigned i;
|
||
|
for (i = 0; i < parseBufferSize; ++i) {
|
||
|
if (*from == '\0' || *from == ':' || *from == '/') {
|
||
|
// We've completed parsing the address
|
||
|
*to = '\0';
|
||
|
break;
|
||
|
}
|
||
|
*to++ = *from++;
|
||
|
}
|
||
|
if (i == parseBufferSize) {
|
||
|
env.setResultMsg("URL is too long");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
NetAddressList addresses(parseBuffer);
|
||
|
if (addresses.numAddresses() == 0) {
|
||
|
env.setResultMsg("Failed to find network address for \"",
|
||
|
parseBuffer, "\"");
|
||
|
break;
|
||
|
}
|
||
|
address = *(addresses.firstAddress());
|
||
|
|
||
|
portNum = 5060; // default value
|
||
|
char nextChar = *from;
|
||
|
if (nextChar == ':') {
|
||
|
int portNumInt;
|
||
|
if (sscanf(++from, "%d", &portNumInt) != 1) {
|
||
|
env.setResultMsg("No port number follows ':'");
|
||
|
break;
|
||
|
}
|
||
|
if (portNumInt < 1 || portNumInt > 65535) {
|
||
|
env.setResultMsg("Bad port number");
|
||
|
break;
|
||
|
}
|
||
|
portNum = (portNumBits)portNumInt;
|
||
|
}
|
||
|
|
||
|
return True;
|
||
|
} while (0);
|
||
|
|
||
|
return False;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::parseSIPURLUsernamePassword(char const* url,
|
||
|
char*& username,
|
||
|
char*& password) {
|
||
|
username = password = NULL; // by default
|
||
|
do {
|
||
|
// Parse the URL as "sip:<username>[:<password>]@<whatever>"
|
||
|
char const* prefix = "sip:";
|
||
|
unsigned const prefixLength = 4;
|
||
|
if (_strncasecmp(url, prefix, prefixLength) != 0) break;
|
||
|
|
||
|
// Look for the ':' and '@':
|
||
|
unsigned usernameIndex = prefixLength;
|
||
|
unsigned colonIndex = 0, atIndex = 0;
|
||
|
for (unsigned i = usernameIndex; url[i] != '\0' && url[i] != '/'; ++i) {
|
||
|
if (url[i] == ':' && colonIndex == 0) {
|
||
|
colonIndex = i;
|
||
|
} else if (url[i] == '@') {
|
||
|
atIndex = i;
|
||
|
break; // we're done
|
||
|
}
|
||
|
}
|
||
|
if (atIndex == 0) break; // no '@' found
|
||
|
|
||
|
char* urlCopy = strDup(url);
|
||
|
urlCopy[atIndex] = '\0';
|
||
|
if (colonIndex > 0) {
|
||
|
urlCopy[colonIndex] = '\0';
|
||
|
password = strDup(&urlCopy[colonIndex+1]);
|
||
|
} else {
|
||
|
password = strDup("");
|
||
|
}
|
||
|
username = strDup(&urlCopy[usernameIndex]);
|
||
|
delete[] urlCopy;
|
||
|
|
||
|
return True;
|
||
|
} while (0);
|
||
|
|
||
|
return False;
|
||
|
}
|
||
|
|
||
|
char*
|
||
|
SIPClient::createAuthenticatorString(Authenticator const* authenticator,
|
||
|
char const* cmd, char const* url) {
|
||
|
if (authenticator != NULL && authenticator->realm() != NULL
|
||
|
&& authenticator->nonce() != NULL && authenticator->username() != NULL
|
||
|
&& authenticator->password() != NULL) {
|
||
|
// We've been provided a filled-in authenticator, so use it:
|
||
|
char const* const authFmt
|
||
|
= "Proxy-Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", response=\"%s\", uri=\"%s\"\r\n";
|
||
|
char const* response = authenticator->computeDigestResponse(cmd, url);
|
||
|
unsigned authBufSize = strlen(authFmt)
|
||
|
+ strlen(authenticator->username()) + strlen(authenticator->realm())
|
||
|
+ strlen(authenticator->nonce()) + strlen(url) + strlen(response);
|
||
|
char* authenticatorStr = new char[authBufSize];
|
||
|
sprintf(authenticatorStr, authFmt,
|
||
|
authenticator->username(), authenticator->realm(),
|
||
|
authenticator->nonce(), response, url);
|
||
|
authenticator->reclaimDigestResponse(response);
|
||
|
|
||
|
return authenticatorStr;
|
||
|
}
|
||
|
|
||
|
return strDup("");
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::sendRequest(char const* requestString,
|
||
|
unsigned requestLength) {
|
||
|
if (fVerbosityLevel >= 1) {
|
||
|
envir() << "Sending request: " << requestString << "\n";
|
||
|
}
|
||
|
// NOTE: We should really check that "requestLength" is not #####
|
||
|
// too large for UDP (see RFC 3261, section 18.1.1) #####
|
||
|
return fOurSocket->output(envir(), (unsigned char*)requestString, requestLength);
|
||
|
}
|
||
|
|
||
|
unsigned SIPClient::getResponse(char*& responseBuffer,
|
||
|
unsigned responseBufferSize) {
|
||
|
if (responseBufferSize == 0) return 0; // just in case...
|
||
|
responseBuffer[0] = '\0'; // ditto
|
||
|
|
||
|
// Keep reading data from the socket until we see "\r\n\r\n" (except
|
||
|
// at the start), or until we fill up our buffer.
|
||
|
// Don't read any more than this.
|
||
|
char* p = responseBuffer;
|
||
|
Boolean haveSeenNonCRLF = False;
|
||
|
int bytesRead = 0;
|
||
|
while (bytesRead < (int)responseBufferSize) {
|
||
|
unsigned bytesReadNow;
|
||
|
struct sockaddr_in fromAddr;
|
||
|
unsigned char* toPosn = (unsigned char*)(responseBuffer+bytesRead);
|
||
|
Boolean readSuccess
|
||
|
= fOurSocket->handleRead(toPosn, responseBufferSize-bytesRead,
|
||
|
bytesReadNow, fromAddr);
|
||
|
if (!readSuccess || bytesReadNow == 0) {
|
||
|
envir().setResultMsg("SIP response was truncated");
|
||
|
break;
|
||
|
}
|
||
|
bytesRead += bytesReadNow;
|
||
|
|
||
|
// Check whether we have "\r\n\r\n":
|
||
|
char* lastToCheck = responseBuffer+bytesRead-4;
|
||
|
if (lastToCheck < responseBuffer) continue;
|
||
|
for (; p <= lastToCheck; ++p) {
|
||
|
if (haveSeenNonCRLF) {
|
||
|
if (*p == '\r' && *(p+1) == '\n' &&
|
||
|
*(p+2) == '\r' && *(p+3) == '\n') {
|
||
|
responseBuffer[bytesRead] = '\0';
|
||
|
|
||
|
// Before returning, trim any \r or \n from the start:
|
||
|
while (*responseBuffer == '\r' || *responseBuffer == '\n') {
|
||
|
++responseBuffer;
|
||
|
--bytesRead;
|
||
|
}
|
||
|
return bytesRead;
|
||
|
}
|
||
|
} else {
|
||
|
if (*p != '\r' && *p != '\n') {
|
||
|
haveSeenNonCRLF = True;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
Boolean SIPClient::parseResponseCode(char const* line,
|
||
|
unsigned& responseCode) {
|
||
|
if (sscanf(line, "%*s%u", &responseCode) != 1) {
|
||
|
envir().setResultMsg("no response code in line: \"", line, "\"");
|
||
|
return False;
|
||
|
}
|
||
|
|
||
|
return True;
|
||
|
}
|