/* * Copyright 2013 Range Networks, Inc. * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribuion. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. This program 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. */ // Written by Pat Thompson. #define LOG_GROUP LogGroup::SIP // Can set Log.Level.SIP for debugging #include #include #include #include #include #include #include "SIPParse.h" #include "SIPMessage.h" #include "SIPBase.h" #include "SIPDialog.h" namespace SIP { using namespace std; using namespace Control; struct SipParseError : public std::exception { SipParseError() { LOG(DEBUG) << "SipParseError"; } virtual const char *what() const throw() { return "SipParseError"; } }; // endpos is the index of one char past the end of the string, eg, of the trailing nul. // Move endpos backward until we find a char that is not one of the trimchars, and leave // endpos at the index one past that char, which is the appropriate length to substr everything but that char. // Example: string result = input.substr(input.c_str(),input.size()); size_t trimrightn(const char *startp, size_t endpos, const char *trimchars /*=" \t\r\n"*/) { while (endpos > 0 && strchr(trimchars,startp[endpos-1])) { endpos--; } return endpos; } // Return the index of the first char not in trimchars, or if none, of the trailing nul in the string. // Note that if you trimleft and trimright a string of spaces, the two indicies would cross, so be careful. See trimboth. size_t trimleftn(const char *startp, size_t startpos/*=0*/, const char *trimchars /*=" \t\r\n"*/) { while (startp[startpos] && strchr(trimchars,startp[startpos])) { startpos++; } return startpos; } // Trim both ends of a string. string trimboth(string input, const char *trimchars /*=" \t\r\n"*/) { size_t end = trimrightn(input.c_str(),input.size()); size_t start = (end==0) ? 0 : trimleftn(input.c_str(),0,trimchars); return input.substr(start,end); //const char *bp = str.c_str(); //const char *endp = str.c_str() + str.size(), *ep = endp; // points to the trailing nul. //while (*bp && strchr(trimchars,*bp)) { bp++; } //while (ep > bp && strchr(trimchars,ep[-1])) { ep--; } //return (bp == str.c_str() && bp == endp) ? str : string(bp,bp-ep); } void commaListPushBack(string *cl, string val) { val = trimboth(val," ,"); // Make sure there is no errant garbage around the new value. if (! cl->empty()) { cl->append(","); } cl->append(val); } void commaListPushFront(string *cl, string val) { val = trimboth(val," ,"); // Make sure there is no errant garbage around the new value. if (cl->empty()) { *cl = val; } else { *cl = val + "," + *cl; } } string commaListFront(string cl) { size_t cn = cl.find_first_of(','); if (cn == string::npos) { cn = cl.size(); } return cl.substr(0,trimrightn(cl.c_str(),cn)); } // Case insenstive string comparison // Yes there is a way to do this in C++ by changing the character traits, but who cares. bool strcaseeql(const char *a,const char *b) { return 0==strcasecmp((a),(b)); } bool strncaseeql(const char *a,const char *b, unsigned n) { return 0==strncasecmp((a),(b),(n)); } bool strceql(const string a, const string b) { return strcaseeql(a.c_str(),b.c_str()); } //static const char *reserved = ";/?:@&=+$,"; //static const char *mark = "-_.!~*'()"; //static const char *param_unreserved = "[]/:&+$"; static const char *token = "-.!%*_+`'~"; // or alphanum class SipChar { typedef unsigned char uchar; static char charClassData[256]; enum { ccIsToken = 1 }; public: SipChar() { memset(charClassData,0,256); for (uchar ch = 'a'; ch <= 'z'; ch++) { charClassData[ch] = ccIsToken; } for (uchar ch = 'A'; ch <= 'Z'; ch++) { charClassData[ch] = ccIsToken; } for (uchar ch = '0'; ch <= '9'; ch++) { charClassData[ch] = ccIsToken; } for (uchar *cp = (uchar*)token; *cp; cp++) { charClassData[*cp] |= ccIsToken; } } static bool isToken(const uchar ch) { return charClassData[ch] & ccIsToken; } // unreserved is alphanum + mark. } gSipChar; // Without the global variable the class is never initialized. char SipChar::charClassData[256]; // This is pretty easy to parse. The major demarcation chars are reserved: @ ; ? & // [sip: | sips: ] user [: password] @ host [: port] [;param=value]* [? hname=havalue [& hname=hvalue]* ] // absoluteURI ::= scheme: (hierpart | opaquepart) // scheme ::= alphanum|[+-.] // hierpart ::= // blah blah | / blah blah // This can not distinguish between a missing param and one with an empty value. string SipParamList::paramFind(const char*name) { for (SipParamList::iterator it = this->begin(); it != this->end(); it++) { if (strceql(it->mName.c_str(),name)) { return it->mValue; } } return string(""); } struct SipParseLine { const char *currentLine; // Start of current line being parsed, used only for error messages. const char *pp; // Pointer into the parse buffer. It is const* for this class, but not for SipParseMessage void spLineInit(const char *buffer) { currentLine = pp = buffer; } SipParseLine(const char* buffer) { spLineInit(buffer); } SipParseLine() {} virtual void spError(const char *msg) { LOG(ERR) << "SIP Parse error "< \t\f",params); } while (*pp == '?' || *pp == '&') { pp++; scanUriParam("&> \t\f",headers); } return address; } }; // We are not currently using this because we dont care about them. // The uri-parameters appear within the and are: transport,user,moethod,ttl,lr. // The headers appear after '&' with the // In To: and From: there are generic-parameters after the URI outside the <>, including tag. string parseURI(const char *buffer, SipParamList &uriparams, SipParamList &headers) { try { SipParseLine parser(buffer); return parser.parseLineURI(uriparams,headers); } catch (...) { LOG(DEBUG) << "Caught SIP Parse error"; return ""; } } // Extract a SIP parameter from a string containing a list of parameters. They look like ;param1=value;param2=value ... // The input string need not start exactly at the beginning of the list. // The paramid is specified with the semi-colon and =, example: ";tag=" static string extractParam(const char *pp, const char *paramid) { const int taghdrlen = strlen(paramid); // strlen(";tag="); if (const char *tp = strstr(pp,paramid)) { pp += taghdrlen; const char *ep = strchr(pp,';'); return ep ? string(pp,ep-tp) : string(pp); } return string(""); // Not found. } void SipPreposition::rebuild() { if (mTag.empty()) { mFullHeader = format("%s <%s>",mDisplayName,mUri.uriValue()); } else { mFullHeader = format("%s <%s>;tag=%s",mDisplayName,mUri.uriValue(),mTag); } LOG(DEBUG) <tail // The pointers can be NULL. // Return false if it is invalid. bool crackUri(string header, string *before, string *uri, string *tail) { //string junk; //int n = myscanf("%s <%[^>]>%s",before?before:&junk,uri?uri:&junk,tail?tail:&junk); size_t nUriBegin = header.find_first_of('<'); if (nUriBegin == string::npos) { // Old format allows the URI without <> but it is not possible to specify the tag parameter that way. // It should begin with "sip:" or "sips:" but we are not checking. if (uri) *uri = header; return true; } size_t nUriEnd = header.find_first_of('>',nUriBegin); if (nUriBegin == string::npos) { return false; } // Warning: The display name may be a quoted string or multiple unquoted tokens. if (before) *before = header.substr(0,trimrightn(header.c_str(),nUriBegin)); if (uri) *uri = header.substr(nUriBegin+1,nUriEnd-nUriBegin-1); if (tail) *tail = header.substr(nUriEnd+1); return true; // happiness } // Parse immediately, only we're not going to do a full parse on this either. All we care about is the tag. // Note that the tag param is outside the void SipPreposition::prepSet(const string header) { mFullHeader = header; mDisplayName.clear(); mTag.clear(); mUri.clear(); if (header.empty()) { return; } try { if (1) { // We dont need the parser for this. It is trivial. string uri, tail; if (!crackUri(header,&mDisplayName,&uri,&tail)) { LOG(ERR) << "Bad SIP Contact field:"<'); if (!ep) { LOG(ERR) << "Bad SIP Contact field:"< off the ends of the URI. parser.pp = ep; mTag = extractParam(parser.pp,";tag="); } else { mUri.uriSet(thing); } } } catch (...) { LOG(DEBUG) << "Caught SIP Parse error"; } } void parseToParams(string stuff, SipParamList ¶ms) { SipParseLine parser(stuff.c_str()); try { do { SipParam param; if (! parser.scanGenericParam(param)) break; params.push_back(param); } while (parser.scanChar(',')); } catch(SipParseError) { // error was already logged. LOG(DEBUG) << "Caught SIP Parse error"; return; } } // This is only for www-authenticate, not for Authentication-Info void parseAuthenticate(string stuff, SipParamList ¶ms) { SipParseLine parser(stuff.c_str()); try { string challengeType = parser.scanToken(); if (! strceql(challengeType,"Digest")) { LOG(ERR) << format("unrecognized challenge type:%s",challengeType.c_str()); return; } do { SipParam param; if (! parser.scanGenericParam(param)) break; params.push_back(param); } while (parser.scanChar(',')); } catch(SipParseError) { // error was already logged. LOG(DEBUG) << "Caught SIP Parse error"; return; } } // You can pass in the comma-separated list of vias and it will parse just the first. void SipVia::viaParse(string vialine) { LOG(DEBUG) <assign(vialine); // Spaces are allowed anywhere in the via spec even though most people dont insert htem. // Example: "SIP / 2.0 / UDP host : port ; branch = branchstring" // The port may be empty. There may be options after the port. try { SipParseLine parser(vialine.c_str()); parser.scanToken(); // protocol-name parser.scanChar('/'); parser.scanToken(); // protocol-version parser.scanChar('/'); parser.scanToken(); // transport mSentBy = parser.scanToken(); // host if (parser.scanChar(':')) { mSentBy.append(":"); mSentBy.append(parser.scanToken()); // port } // Now the list of via-params //SipParam param; //while (parser.scanGenericParam(param)) { // if (strcaseeql(param.mName.c_str(),"branch")) { // not sure if this is case insensitive, but be safe. // mViaBranch = param.mValue; // break; // We can break; we dont care about any other parameters. // } //} while (parser.scanChar(';')) { SipParam param; while (parser.scanGenericParam(param)) { if (strcaseeql(param.mName.c_str(),"branch")) { // not sure if this is case insensitive, but be safe. mViaBranch = param.mValue; break; // We can break; we dont care about any other parameters. } } } } catch(...) { LOG(ERR) << "Error parsing via:"<msmCode = scanInt(); skipSpace(); sipmsg->msmReason = string(pp); // Rest of the line is the reason. } else { // This is a request. sipmsg->msmReqMethod = scanNonSpace(); sipmsg->msmReqUri = scanNonSpace(); skipSpace(); scanSipVersion(); } skipSpace(); //LOG(DEBUG); // Get the rest of the header lines. while (nextLine()) { // We have a header line. The headers names themselves are case insensitive. LOG(DEBUG) << "nextLine="<msmTo.prepSet(string(pp)); } else if (strceql(name,"from") || strceql(name,"f")) { sipmsg->msmFrom.prepSet(string(pp)); } else if (strceql(name,"contact") || strceql(name,"m")) { sipmsg->msmContactValue = string(pp); } else if (strceql(name,"CSeq")) { sipmsg->msmCSeqNum = scanInt(); sipmsg->msmCSeqMethod = scanToken(); } else if (strceql(name,"call-id") || strceql(name,"i")) { // The call-id string is defined as word[@word], but unless we are really interested // in validating incoming SIP messages, we can simply scan for non-space. sipmsg->msmCallId = scanNonSpace(); } else if (strceql(name,"via") || strceql(name,"v")) { // Multiple vias can appear on separate lines or be comma separated in one line, // so for simplicity we will just keep them all in a comma separated list. commaListPushBack(&sipmsg->msmVias,pp); } else if (strceql(name,"record-route")) { commaListPushBack(&sipmsg->msmRecordRoutes,pp); } else if (strceql(name,"route")) { commaListPushBack(&sipmsg->msmRoutes,pp); // No need to treat this specially here, even though authenticate info does not follow normal SIP parsing rules. //} else if (strceql(name,"www-authenticate")) { // // authenticate info does not follow normal SIP parsing rules. // sipmsg->msmAuthenticateValue = string(pp); } else if (strceql(name,"content-type")) { sipmsg->msmContentType = string(pp); } else { SipParam param(name,string(pp)); sipmsg->msmHeaders.push_back(param); } } LOG(DEBUG) << "end"; if (pp[0] == '\r' && pp[1] == '\n') { pp += 2; sipmsg->msmBody = string(pp); } else { // Dont abort for this. Just log an error. logError("Missing message body"); } return sipmsg; } SipParseMessage(char *buffer) { spInit(buffer); } }; SipMessage *sipParseBuffer(const char *buffer) { LOG(DEBUG) << "DEBUG"; // The SIP Parser modifies buffer, but puts it back the way it found it. // This is OK because no thread contention for the buffer is possible because all callers pass a unique string for parsing. SipParseMessage parser(Unconst(buffer)); SipMessage *result; try { result = parser.sipParse(); } catch (SipParseError) { // error was already logged. LOG(DEBUG) << "SipParseError caught"; return NULL; } return result; } void codecsToSdp(CodecSet codecs, string *codeclist, string *attrs) { attrs->clear(); attrs->reserve(80); codeclist->reserve(20); // We are using the same code for offers and answers, so for now only included the one we want: /** if (codecs.isSet(PCMULAW)) { attrs->append("a=rtpmap:0 PCMU/8000\r\n"); if (!codeclist->empty()) { codeclist->append(" "); } codeclist->append("0"); } ***/ if (codecs.isSet(GSM_FR) || codecs.isSet(GSM_HR) || codecs.isEmpty()) { attrs->append("a=rtpmap:3 GSM/8000\r\n"); if (!codeclist->empty()) { codeclist->append(" "); } codeclist->append("3"); } // TODO: Does the half-rate codec need a special RTP format is conversion performed lower down? // RFC5993 7.1 says it is "a=rtpmap:append("a=rtpmap:96 AMR/8000\r\n"); if (!codeclist->empty()) { codeclist->append(" "); } codeclist->append("96"); } **/ } // We dont fully parse it; just pull out the o,m,c,a lines. void SdpInfo::sdpParse(const char *buffer) { const char *bp, *eol; for (bp = buffer; bp && *bp; bp = eol ? eol+1 : NULL) { eol = strchr(bp,'\n'); switch (*bp) { case 'o': if (myscanf(bp,"o=%s %s %s IN IP4 %s",&sdpUsername,&sdpSessionId, &sdpVersionId, &sdpHost) < 4) { LOG(ERR) << "SDP unrecognized o= line, sdp:"<sipLocalUsername(); sdpRtpPort = dialog->mRTPPort; sdpHost = dialog->localIP(); codecsToSdp(dialog->mCodec,&sdpCodecList,&sdpAttrs); static const string zero("0"); sdpSessionId = sdpVersionId = zero; } void SdpInfo::sdpInitRefer(const SipBase *dialog, int remotePort) { sdpInitOffer(dialog); // Same as above, except: sdpRtpPort = remotePort; sdpVersionId = format("%lu",time(NULL)); } // Note that sdp is not completely order independent. string SdpInfo::sdpValue() { string result; result.reserve(100); char buf[302]; result.append("v=0\r\n"); // SDP protocol version // originator, session id, ip address. snprintf(buf,300,"o=%s %s %s IN IP4 %s\r\n",sdpUsername.c_str(),sdpSessionId.c_str(),sdpVersionId.c_str(),sdpHost.c_str()); result.append(buf); // RFC3264 5: And I quote: // "The SDP 's=' line conveys the subject of the session, which is reasonably defined for multicast, // but ill defined for unicast. For unicast sessions, it is RECOMMENDED that it consist of a single space // character (0x20) or a dash (-)." // I dont know why we are setting it to 'Talk Time'. result.append("s=Talk Time\r\n"); result.append("t=0 0\r\n"); // time session is active; 0 means unbounded. snprintf(buf,300,"m=audio %u RTP/AVP %s\r\n",sdpRtpPort,sdpCodecList.c_str()); // media name and transport address. result.append(buf); // media name and transport address. // Optional connection information. Redundant because we included in 'o='. snprintf(buf,300,"c=IN IP4 %s\r\n",sdpHost.c_str()); result.append(buf); result.append(sdpAttrs); return result; } }; // namespace SIP