blob: cab241614354e992b58004e8fe61340a70a99ecf [file] [log] [blame]
/**********
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 3 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-2020 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;
client->fTimerA = NULL;
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;
client->fTimerB = NULL;
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;
client->fTimerD = NULL;
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:" or "WWW-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 ||
sscanf(lineStart, "WWW-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
= "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;
}