/********** 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 .) 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 ;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 ;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 ;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:@
:/" // (with ":" and "/" optional) // Also, skip over any "[:]@" preceding
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 "[:]@" 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:[:]@" 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; }