/********** 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 .) 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 ;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 ;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 = "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; }