1 /*
2  * sippres.cxx
3  *
4  * SIP Presence classes for Opal
5  *
6  * Open Phone Abstraction Library (OPAL)
7  *
8  * Copyright (c) 2009 Post Increment
9  *
10  * The contents of this file are subject to the Mozilla Public License
11  * Version 1.0 (the "License"); you may not use this file except in
12  * compliance with the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS"
16  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17  * the License for the specific language governing rights and limitations
18  * under the License.
19  *
20  * The Original Code is Open Phone Abstraction Library.
21  *
22  * The Initial Developer of the Original Code is Post Increment
23  *
24  * Contributor(s): ______________________________________.
25  *
26  * $Revision: 22858 $
27  * $Author: csoutheren $
28  * $Date: 2009-06-12 22:50:19 +1000 (Fri, 12 Jun 2009) $
29  */
30 
31 /*
32  * This code implements all or part of the following RFCs
33  *
34  * RFC 3856 "A Presence Event Package for the Session Initiation Protocol (SIP)"
35  * RFC 3857 "A Watcher Information Event Template-Package for the Session Initiation Protocol (SIP)"
36  * RFC 3858 "An Extensible Markup Language (XML) Based Format for Watcher Information"
37  * RFC 3863 "Presence Information Data Format (PIDF)"
38  * RFC 4825 "The Extensible Markup Language (XML) Configuration Access Protocol (XCAP)"
39  * RFC 4827 "An Extensible Markup Language (XML) Configuration Access Protocol (XCAP) Usage for Manipulating Presence Document Contents"
40  * RFC 5025 "Presence Authorization Rules"
41  *
42  * This code does not implement the following RFCs
43  *
44  * RFC 5263 "Session Initiation Protocol (SIP) Extension for Partial Notification of Presence Information"
45  *
46  */
47 
48 
49 #include <ptlib.h>
50 #include <opal/buildopts.h>
51 
52 #if P_EXPAT && OPAL_SIP
53 
54 #include <sip/sippres.h>
55 #include <ptclib/pdns.h>
56 #include <ptclib/pxml.h>
57 #include <ptclib/random.h>
58 
59 
60 PFACTORY_CREATE(PFactory<OpalPresentity>, SIP_Presentity, "sip", false);
61 static bool Synonym_for_pres_URL = PFactory<OpalPresentity>::RegisterAs("pres", "sip");
62 
PIDFEntityKey()63 const PCaselessString & SIP_Presentity::PIDFEntityKey()    { static const PConstCaselessString s("PIDF-Entity");    return s; }
SubProtocolKey()64 const PCaselessString & SIP_Presentity::SubProtocolKey()   { static const PConstCaselessString s("Sub-Protocol");   return s; }
PresenceAgentKey()65 const PCaselessString & SIP_Presentity::PresenceAgentKey() { static const PConstCaselessString s("Presence Agent"); return s; }
TransportKey()66 const PCaselessString & SIP_Presentity::TransportKey()     { static const PConstCaselessString s("Transport");      return s; }
XcapRootKey()67 const PCaselessString & SIP_Presentity::XcapRootKey()      { static const PConstCaselessString s("XCAP Root");      return s; }
XcapAuthIdKey()68 const PCaselessString & SIP_Presentity::XcapAuthIdKey()    { static const PConstCaselessString s("XCAP Auth ID");   return s; }
XcapPasswordKey()69 const PCaselessString & SIP_Presentity::XcapPasswordKey()  { static const PConstCaselessString s("XCAP Password");  return s; }
XcapAuthAuidKey()70 const PCaselessString & SIP_Presentity::XcapAuthAuidKey()  { static const PConstCaselessString s("XCAP AUID");      return s; }
XcapAuthFileKey()71 const PCaselessString & SIP_Presentity::XcapAuthFileKey()  { static const PConstCaselessString s("XCAP AuthFile");  return s; }
XcapBuddyListKey()72 const PCaselessString & SIP_Presentity::XcapBuddyListKey() { static const PConstCaselessString s("XCAP BuddyList"); return s; }
73 
74 static struct SIPAttributeInfo {
75   const char * m_name;
76   const char * m_type;
77 } const AttributeInfo[] = {
78   /*  0 */ { SIP_Presentity::PIDFEntityKey(),    "URL" },
79   /*  1 */ { SIP_Presentity::SubProtocolKey(),   "Enum\nPeerToPeer,Agent,XCAP,OMA\nAgent" },
80   /*  2 */ { SIP_Presentity::PresenceAgentKey(), "String" },
81   /*  3 */ { SIP_Presentity::TransportKey(),     "Enum\nUDP,TCP,TLS\nTCP" },
82   /*  4 */ { OpalPresentity::AuthNameKey(),      "String" },
83   /*  5 */ { OpalPresentity::AuthPasswordKey(),  "Password" },
84   /*  6 */ { SIP_Presentity::XcapRootKey(),      "URL" },
85   /*  7 */ { SIP_Presentity::XcapAuthIdKey(),    "String" },
86   /*  8 */ { SIP_Presentity::XcapPasswordKey(),  "Password" },
87   /*  9 */ { SIP_Presentity::XcapAuthAuidKey(),  "String" },
88   /* 10 */ { SIP_Presentity::XcapAuthFileKey(),  "String" },
89   /* 11 */ { SIP_Presentity::XcapBuddyListKey(), "String" },
90   /* 12 */ { OpalPresentity::TimeToLiveKey(),    "Integer\n1,999999999\n300" }
91 };
92 
93 OPAL_DEFINE_COMMAND(OpalSetLocalPresenceCommand,     SIP_Presentity, Internal_SendLocalPresence);
94 OPAL_DEFINE_COMMAND(OpalSubscribeToPresenceCommand,  SIP_Presentity, Internal_SubscribeToPresence);
95 OPAL_DEFINE_COMMAND(SIPWatcherInfoCommand,           SIP_Presentity, Internal_SubscribeToWatcherInfo);
96 OPAL_DEFINE_COMMAND(OpalAuthorisationRequestCommand, SIP_Presentity, Internal_AuthorisationRequest);
97 
98 static const char * const AuthNames[OpalPresentity::NumAuthorisations] = { "allow", "block", "polite-block", "confirm", "remove" };
99 
100 
101 //////////////////////////////////////////////////////////////////////////////////////
102 
SIP_Presentity()103 SIP_Presentity::SIP_Presentity()
104   : m_endpoint(NULL)
105   , m_subProtocol(e_OMA)
106   , m_watcherInfoVersion(-1)
107 {
108   m_attributes.Set(SubProtocolKey, "OMA");
109 }
110 
111 
SIP_Presentity(const SIP_Presentity & other)112 SIP_Presentity::SIP_Presentity(const SIP_Presentity & other)
113   : OpalPresentityWithCommandThread(other)
114   , m_endpoint(NULL)
115   , m_watcherInfoVersion(-1)
116 {
117 }
118 
119 
~SIP_Presentity()120 SIP_Presentity::~SIP_Presentity()
121 {
122   Close();
123 }
124 
125 
GetAttributeNames() const126 PStringArray SIP_Presentity::GetAttributeNames() const
127 {
128   PStringArray names;
129   for (PINDEX i = 0; i < PARRAYSIZE(AttributeInfo); ++i)
130     names += AttributeInfo[i].m_name;
131   return names;
132 }
133 
134 
GetAttributeTypes() const135 PStringArray SIP_Presentity::GetAttributeTypes() const
136 {
137   PStringArray types;
138   for (PINDEX i = 0; i < PARRAYSIZE(AttributeInfo); ++i)
139     types += AttributeInfo[i].m_type;
140   return types;
141 }
142 
143 
Open()144 bool SIP_Presentity::Open()
145 {
146   if (!OpalPresentityWithCommandThread::Open())
147     return false;
148 
149   // find the endpoint
150   m_endpoint = dynamic_cast<SIPEndPoint *>(m_manager->FindEndPoint("sip"));
151   if (m_endpoint == NULL) {
152     PTRACE(1, "SIPPres\tCannot open SIP_Presentity without sip endpoint");
153     return false;
154   }
155 
156   PCaselessString subProto = m_attributes.Get(SubProtocolKey);
157   if (subProto == "PeerToPeer")
158     m_subProtocol = e_PeerToPeer;
159   else if (subProto == "Agent")
160     m_subProtocol = e_WithAgent;
161   else if (subProto == "XCAP")
162     m_subProtocol = e_XCAP;
163   else if (subProto == "OMA")
164     m_subProtocol = e_OMA;
165   else {
166     PTRACE(1, "SIPPres\tUnknown sub-protocol \"" << subProto << '"');
167     return false;
168   }
169 
170   m_presenceAgent.MakeEmpty(); // Make invalid
171 
172   if (m_subProtocol == e_PeerToPeer) {
173     PTRACE(3, "SIPPres\tUsing peer to peer mode for " << m_aor);
174   }
175   else {
176     // find presence server for Presentity as per RFC 3861
177     // if not found, look for default presence server setting
178     // if none, use hostname portion of domain name
179     m_presenceAgent = m_attributes.Get(PresenceAgentKey);
180     if (m_presenceAgent.IsEmpty()) {
181       m_presenceAgent = m_aor.AsString(PURL::HostPortOnly);
182 
183 #if P_DNS
184       if (m_aor.GetScheme() == "pres") {
185         PStringList hosts;
186         bool found = PDNS::LookupSRV(m_aor.GetHostName(), "_pres._sip", hosts) && !hosts.IsEmpty();
187         PTRACE(2, "SIPPres\tSRV lookup for '_pres._sip." << m_aor.GetHostName()
188                << "' " << (found ? "succeeded" : "failed"));
189         if (found)
190           m_presenceAgent = hosts.front();
191       }
192 #endif // P_DNS
193     }
194 
195     PTRACE(3, "SIPPres\tUsing " << m_presenceAgent << " as presence server for " << m_aor);
196   }
197 
198   m_watcherSubscriptionAOR.MakeEmpty();
199   m_watcherInfoVersion = -1;
200 
201   StartThread();
202 
203   // subscribe to presence watcher infoformation
204   SendCommand(CreateCommand<SIPWatcherInfoCommand>());
205 
206   return true;
207 }
208 
209 
Close()210 bool SIP_Presentity::Close()
211 {
212   if (!OpalPresentityWithCommandThread::Close())
213     return false;
214 
215   StopThread();
216 
217   if (!m_publishedTupleId.IsEmpty()) {
218     SIP_Presentity_OpalSetLocalPresenceCommand cmd;
219     cmd.m_state = OpalPresenceInfo::NoPresence;
220     Internal_SendLocalPresence(cmd);
221   }
222 
223   m_notificationMutex.Wait();
224 
225   PString watcherSubscriptionAOR = m_watcherSubscriptionAOR;
226   m_watcherSubscriptionAOR.MakeEmpty();
227 
228   StringMap presenceIdByAor = m_presenceIdByAor;
229   m_watcherAorById.clear();
230   m_presenceIdByAor.clear();
231   m_presenceAorById.clear();
232   m_authorisationIdByAor.clear();
233 
234   m_notificationMutex.Signal();
235 
236   if (!watcherSubscriptionAOR.IsEmpty()) {
237     PTRACE(3, "SIPPres\t'" << m_aor << "' sending final unsubscribe for own presence watcher");
238     m_endpoint->Unsubscribe(SIPSubscribe::Presence | SIPSubscribe::Watcher, watcherSubscriptionAOR, true);
239   }
240 
241   for (StringMap::iterator subs = presenceIdByAor.begin(); subs != presenceIdByAor.end(); ++subs) {
242     PTRACE(3, "SIPPres\t'" << m_aor << "' sending final unsubscribe to " << subs->first);
243     m_endpoint->Unsubscribe(SIPSubscribe::Presence, subs->second, true);
244   }
245 
246   if (!m_publishedTupleId.IsEmpty() && m_subProtocol != e_PeerToPeer)
247     m_endpoint->Publish(m_aor.AsString(), PString::Empty(), 0);
248 
249   PTRACE(4, "SIPPres\t'" << m_aor << "' awaiting unsubscriptions to complete.");
250   while (m_endpoint->IsSubscribed(SIPSubscribe::Presence | SIPSubscribe::Watcher, watcherSubscriptionAOR, true))
251     PThread::Sleep(100);
252   for (StringMap::iterator subs = presenceIdByAor.begin(); subs != presenceIdByAor.end(); ++subs) {
253     while (m_endpoint->IsSubscribed(SIPSubscribe::Presence, subs->second, true))
254       PThread::Sleep(100);
255   }
256 
257   m_endpoint = NULL;
258   PTRACE(3, "SIPPres\t'" << m_aor << "' closed.");
259   return true;
260 }
261 
262 
Internal_SubscribeToPresence(const OpalSubscribeToPresenceCommand & cmd)263 void SIP_Presentity::Internal_SubscribeToPresence(const OpalSubscribeToPresenceCommand & cmd)
264 {
265   if (cmd.m_subscribe) {
266     if (m_presenceIdByAor.find(cmd.m_presentity) != m_presenceIdByAor.end()) {
267       PTRACE(3, "SIPPres\t'" << m_aor << "' already subscribed to presence of '" << cmd.m_presentity << '\'');
268       return;
269     }
270 
271     PTRACE(3, "SIPPres\t'" << m_aor << "' subscribing to presence of '" << cmd.m_presentity << '\'');
272 
273     // subscribe to the presence event on the presence server
274     SIPSubscribe::Params param(SIPSubscribe::Presence);
275 
276     param.m_localAddress    = m_aor.AsString();
277     param.m_addressOfRecord = cmd.m_presentity;
278     if (m_subProtocol >= e_XCAP)
279       param.m_remoteAddress = m_presenceAgent + ";transport=" + m_attributes.Get(TransportKey, "tcp").ToLower();
280     param.m_authID          = m_attributes.Get(OpalPresentity::AuthNameKey, m_aor.GetUserName());
281     param.m_password        = m_attributes.Get(OpalPresentity::AuthPasswordKey);
282     param.m_expire          = GetExpiryTime();
283     param.m_contentType     = "application/pidf+xml";
284     param.m_eventList       = true;
285 
286     param.m_onSubcribeStatus = PCREATE_NOTIFIER2(OnPresenceSubscriptionStatus, const SIPSubscribe::SubscriptionStatus &);
287     param.m_onNotify         = PCREATE_NOTIFIER2(OnPresenceNotify, SIPSubscribe::NotifyCallbackInfo &);
288 
289     PString id;
290     if (m_endpoint->Subscribe(param, id, false)) {
291       m_presenceIdByAor[cmd.m_presentity] = id;
292       m_presenceAorById[id] = cmd.m_presentity;
293     }
294   }
295   else {
296     StringMap::iterator id = m_presenceIdByAor.find(cmd.m_presentity);
297     if (id == m_presenceIdByAor.end()) {
298       PTRACE(3, "SIPPres\t'" << m_aor << "' already unsubscribed to presence of '" << cmd.m_presentity << '\'');
299       return;
300     }
301 
302     PTRACE(3, "SIPPres\t'" << m_aor << "' unsubscribing to presence of '" << cmd.m_presentity << '\'');
303     m_endpoint->Unsubscribe(SIPSubscribe::Presence, id->second);
304   }
305 }
306 
307 
OnPresenceSubscriptionStatus(SIPSubscribeHandler &,const SIPSubscribe::SubscriptionStatus & status)308 void SIP_Presentity::OnPresenceSubscriptionStatus(SIPSubscribeHandler &, const SIPSubscribe::SubscriptionStatus & status)
309 {
310   if (status.m_reason == SIP_PDU::Information_Trying)
311     return;
312 
313   m_notificationMutex.Wait();
314   if (!status.m_wasSubscribing || status.m_reason >= 400) {
315     PString id = status.m_handler->GetCallID();
316     StringMap::iterator aor = m_presenceAorById.find(id);
317     if (aor != m_presenceAorById.end()) {
318       PTRACE(status.m_reason >= 400 ? 2 : 3, "SIPPres\t'" << m_aor << "' "
319              << (status.m_wasSubscribing ? "error " : "un")
320              << "subscribing to presence of '" << aor->second << '\'');
321       m_endpoint->Unsubscribe(SIPSubscribe::Presence, status.m_addressofRecord, true);
322       m_presenceIdByAor.erase(aor->second);
323       m_presenceAorById.erase(aor);
324     }
325   }
326   m_notificationMutex.Signal();
327 }
328 
329 
OnPresenceNotify(SIPSubscribeHandler & handler,SIPSubscribe::NotifyCallbackInfo & status)330 void SIP_Presentity::OnPresenceNotify(SIPSubscribeHandler & handler, SIPSubscribe::NotifyCallbackInfo & status)
331 {
332   list<SIPPresenceInfo> infoList;
333   PString error;
334   PString body = status.m_notify.GetEntityBody();
335   if (handler.GetProductInfo().name.Find("Asterisk") != P_MAX_INDEX) {
336     PString to = status.m_notify.GetMIME().GetTo().AsString();
337     PString from = status.m_notify.GetMIME().GetFrom().AsString();
338     PTRACE(4, "SIP\tCompensating for " << handler.GetProductInfo().name << ","
339               " replacing " << to << " with " << from);
340     body.Replace(to, from);
341   }
342   if (!SIPPresenceInfo::ParseXML(body, infoList, error)) {
343     status.m_response.SetEntityBody(error);
344     return;
345   }
346 
347   // send 200 OK now, and flag caller NOT to send the response
348   status.SendResponse(SIP_PDU::Successful_OK);
349 
350   m_notificationMutex.Wait();
351   for (list<SIPPresenceInfo>::iterator it = infoList.begin(); it != infoList.end(); ++it) {
352     SetPIDFEntity(it->m_target);
353     PTRACE(3, "SIPPres\t'" << m_aor << "' request for presence of '" << it->m_entity << "' is " << it->m_state);
354     OnPresenceChange(*it);
355   }
356   m_notificationMutex.Signal();
357 }
358 
359 
GetExpiryTime() const360 unsigned SIP_Presentity::GetExpiryTime() const
361 {
362   int ttl = m_attributes.Get(OpalPresentity::TimeToLiveKey, "300").AsInteger();
363   return ttl > 0 ? ttl : 300;
364 }
365 
366 
Internal_SubscribeToWatcherInfo(const SIPWatcherInfoCommand & cmd)367 void SIP_Presentity::Internal_SubscribeToWatcherInfo(const SIPWatcherInfoCommand & cmd)
368 {
369   if (m_subProtocol == e_PeerToPeer) {
370     PTRACE(3, "SIPPres\tRequires agent to do watcher, aor=" << m_aor);
371     return;
372   }
373 
374   if (cmd.m_unsubscribe) {
375     if (m_watcherSubscriptionAOR.IsEmpty()) {
376       PTRACE(3, "SIPPres\tAlredy unsubscribed presence watcher for " << m_aor);
377       return;
378     }
379 
380     PTRACE(3, "SIPPres\t'" << m_aor << "' sending unsubscribe for own presence watcher");
381     m_endpoint->Unsubscribe(SIPSubscribe::Presence | SIPSubscribe::Watcher, m_watcherSubscriptionAOR);
382     return;
383   }
384 
385   PString aorStr = m_aor.AsString();
386   PTRACE(3, "SIPPres\t'" << aorStr << "' sending subscribe for own presence.watcherinfo");
387 
388   // subscribe to the presence.winfo event on the presence server
389   SIPSubscribe::Params param(SIPSubscribe::Presence | SIPSubscribe::Watcher);
390   param.m_contentType      = "application/watcherinfo+xml";
391   param.m_localAddress     = aorStr;
392   param.m_addressOfRecord  = aorStr;
393   param.m_remoteAddress    = m_presenceAgent + ";transport=" + m_attributes.Get(TransportKey, "tcp").ToLower();
394   param.m_authID           = m_attributes.Get(OpalPresentity::AuthNameKey, m_aor.GetUserName());
395   param.m_password         = m_attributes.Get(OpalPresentity::AuthPasswordKey);
396   param.m_expire           = GetExpiryTime();
397   param.m_onSubcribeStatus = PCREATE_NOTIFIER2(OnWatcherInfoSubscriptionStatus, const SIPSubscribe::SubscriptionStatus &);
398   param.m_onNotify         = PCREATE_NOTIFIER2(OnWatcherInfoNotify, SIPSubscribe::NotifyCallbackInfo &);
399 
400   m_endpoint->Subscribe(param, m_watcherSubscriptionAOR);
401 }
402 
403 
SetPIDFEntity(PURL & entity)404 void SIP_Presentity::SetPIDFEntity(PURL & entity)
405 {
406   if (entity.Parse(m_attributes.Get(SIP_Presentity::PIDFEntityKey), "pres")) {
407     PTRACE(4, "SIPPres\tPIDF entity set via attribute to " << entity);
408     return;
409   }
410 
411   if (m_aor.GetScheme() == "pres") {
412     entity = m_aor;
413     PTRACE(4, "SIPPres\tPIDF entity set via AOR to " << entity);
414   }
415 
416   if (entity.Parse(m_aor.GetUserName() + '@' + m_aor.GetHostName(), "pres")) {
417     PTRACE(4, "SIPPres\tPIDF entity derived from AOR as " << entity);
418     return;
419   }
420 
421   entity = m_aor;
422   PTRACE(4, "SIPPres\tPIDF entity set via failsafe AOR of " << entity);
423 }
424 
425 
OnWatcherInfoSubscriptionStatus(SIPSubscribeHandler &,const SIPSubscribe::SubscriptionStatus & status)426 void SIP_Presentity::OnWatcherInfoSubscriptionStatus(SIPSubscribeHandler &, const SIPSubscribe::SubscriptionStatus & status)
427 {
428    if (status.m_reason == SIP_PDU::Information_Trying)
429     return;
430 
431   OpalPresenceInfo info(status.m_wasSubscribing ? OpalPresenceInfo::Unchanged : OpalPresenceInfo::NoPresence);
432   SetPIDFEntity(info.m_entity);
433   info.m_target = info.m_entity;
434 
435   m_notificationMutex.Wait();
436 
437   if (status.m_reason/100 == 4)
438     info.m_state = OpalPresenceInfo::Forbidden;
439   else if (status.m_reason/100 != 2)
440     info.m_state = OpalPresenceInfo::InternalError;
441 
442   OnPresenceChange(info);
443 
444   if (!status.m_wasSubscribing) {
445     m_endpoint->Unsubscribe(SIPSubscribe::Presence | SIPSubscribe::Watcher, status.m_addressofRecord, true);
446     m_watcherSubscriptionAOR.MakeEmpty();
447   }
448 
449   m_notificationMutex.Signal();
450 }
451 
452 
OnWatcherInfoNotify(SIPSubscribeHandler &,SIPSubscribe::NotifyCallbackInfo & status)453 void SIP_Presentity::OnWatcherInfoNotify(SIPSubscribeHandler &, SIPSubscribe::NotifyCallbackInfo & status)
454 {
455   // Check for empty body, if so then is OK, just a ping ...
456   if (status.m_notify.GetEntityBody().IsEmpty()) {
457     PTRACE(4, "SIPPres\tEmpty body on presence watcher NOTIFY, ignoring");
458     status.m_response.SetStatusCode(SIP_PDU::Successful_OK);
459     return;
460   }
461 
462   static PXML::ValidationInfo const WatcherValidation[] = {
463     { PXML::RequiredNonEmptyAttribute,  "id"  },
464     { PXML::RequiredAttributeWithValue, "status",  { "pending\nactive\nwaiting\nterminated" } },
465     { PXML::RequiredAttributeWithValue, "event",   { "subscribe\napproved\ndeactivated\nprobation\nrejected\ntimeout\ngiveup" } },
466     { PXML::EndOfValidationList }
467   };
468 
469   static PXML::ValidationInfo const WatcherListValidation[] = {
470     { PXML::RequiredNonEmptyAttribute,  "resource" },
471     { PXML::RequiredAttributeWithValue, "package", { "presence" } },
472 
473     { PXML::Subtree,                    "watcher", { WatcherValidation } , 0 },
474     { PXML::EndOfValidationList }
475   };
476 
477   static PXML::ValidationInfo const WatcherInfoValidation[] = {
478     { PXML::SetDefaultNamespace,        "urn:ietf:params:xml:ns:watcherinfo" },
479     { PXML::ElementName,                "watcherinfo", },
480     { PXML::RequiredNonEmptyAttribute,  "version"},
481     { PXML::RequiredAttributeWithValue, "state",   { "full\npartial" } },
482 
483     { PXML::Subtree,                    "watcher-list", { WatcherListValidation }, 0 },
484     { PXML::EndOfValidationList }
485   };
486 
487   PXML xml;
488   PString error;
489   if (!xml.LoadAndValidate(status.m_notify.GetEntityBody(), WatcherInfoValidation, error, PXML::WithNS)) {
490     status.m_response.SetEntityBody(error);
491     PTRACE(2, "SIPPres\tError parsing XML in presence watcher NOTIFY: " << error);
492     return;
493   }
494 
495   // send 200 OK now, and flag caller NOT to send the response
496   status.SendResponse(SIP_PDU::Successful_OK);
497 
498   PXMLElement * rootElement = xml.GetRootElement();
499 
500   int version = rootElement->GetAttribute("version").AsUnsigned();
501 
502   PWaitAndSignal mutex(m_notificationMutex);
503 
504   // check version number
505   if (m_watcherInfoVersion >= 0 && version <= m_watcherInfoVersion) {
506     PTRACE(3, "SIPPres\t'" << m_aor << "' received repeated NOTIFY for own presence.watcherinfo, already processed");
507     return;
508   }
509 
510   // if this is a full list of watcher info, we can empty out our pending lists
511   if (rootElement->GetAttribute("state") *= "full") {
512     PTRACE(3, "SIPPres\t'" << m_aor << "' received full watcher list for own presence.watcherinfo");
513     m_watcherAorById.clear();
514   }
515   else if (m_watcherInfoVersion < 0) {
516     PTRACE(2, "SIPPres\t'" << m_aor << "' received partial watcher list for own presence.watcherinfo, but awaiting full list");
517     return;
518   }
519   else if (version != m_watcherInfoVersion+1) {
520     PTRACE(2, "SIPPres\t'" << m_aor << "' received partial watcher list for own presence.watcherinfo, but have missing sequence number, resubscribing");
521     m_watcherInfoVersion = -1;
522     SendCommand(CreateCommand<SIPWatcherInfoCommand>());
523     return;
524   }
525   else {
526     PTRACE(3, "SIPPres\t'" << m_aor << "' received partial watcher list for own presence.watcherinfo");
527   }
528 
529   m_watcherInfoVersion = version;
530 
531   // go through list of watcher information
532   PINDEX watcherListIndex = 0;
533   PXMLElement * watcherList;
534   while ((watcherList = rootElement->GetElement("watcher-list", watcherListIndex++)) != NULL) {
535     PINDEX watcherIndex = 0;
536     PXMLElement * watcher;
537     while ((watcher = watcherList->GetElement("watcher", watcherIndex++)) != NULL)
538       OnReceivedWatcherStatus(watcher);
539   }
540 }
541 
542 
OnReceivedWatcherStatus(PXMLElement * watcher)543 void SIP_Presentity::OnReceivedWatcherStatus(PXMLElement * watcher)
544 {
545   PString id     = watcher->GetAttribute("id");
546   PString status = watcher->GetAttribute("status");
547 
548   AuthorisationRequest authreq;
549   authreq.m_presentity = watcher->GetData().Trim();
550 
551   StringMap::iterator existingAOR = m_watcherAorById.find(id);
552 
553   // save pending subscription status from this user
554   if (status == "pending") {
555     if (existingAOR != m_watcherAorById.end()) {
556       PTRACE(3, "SIPPres\t'" << m_aor << "' received followup to request from '" << authreq.m_presentity << "' for access to presence information");
557     }
558     else {
559       m_watcherAorById[id] = authreq.m_presentity;
560       PTRACE(3, "SIPPres\t'" << authreq.m_presentity << "' has requested access to presence information of '" << m_aor << '\'');
561       OnAuthorisationRequest(authreq);
562     }
563   }
564   else {
565     PTRACE(3, "SIPPres\t'" << m_aor << "' has received event '" << watcher->GetAttribute("event")
566            << "', status '" << status << "', for '" << authreq.m_presentity << '\'');
567   }
568 }
569 
570 
Internal_SendLocalPresence(const OpalSetLocalPresenceCommand & cmd)571 void SIP_Presentity::Internal_SendLocalPresence(const OpalSetLocalPresenceCommand & cmd)
572 {
573   PTRACE(3, "SIPPres\t'" << m_aor << "' sending own presence " << cmd.m_state << "/" << cmd.m_note);
574 
575   SIPPresenceInfo sipPresence;
576   static PAtomicInteger::IntegerType g_idNumber;
577   sipPresence.m_personId = PString(++g_idNumber);
578   SetPIDFEntity(sipPresence.m_entity);
579   sipPresence.m_contact =  m_aor;  // As required by OMA-TS-Presence_SIMPLE-V2_0-20090917-C
580   if (m_subProtocol != e_PeerToPeer)
581     sipPresence.m_presenceAgent = m_presenceAgent;
582   sipPresence.m_state = cmd.m_state;
583   sipPresence.m_note = cmd.m_note;
584 
585   if (m_publishedTupleId.IsEmpty())
586     m_publishedTupleId = sipPresence.m_tupleId;
587   else
588     sipPresence.m_tupleId = m_publishedTupleId;
589 
590   if (m_subProtocol != e_PeerToPeer)
591     m_endpoint->PublishPresence(sipPresence, GetExpiryTime());
592   else
593     m_endpoint->Notify(m_aor, SIPSubscribe::Presence, sipPresence.AsXML());
594 }
595 
596 
ChangeAuthNode(XCAPClient & xcap,const OpalAuthorisationRequestCommand & cmd)597 bool SIP_Presentity::ChangeAuthNode(XCAPClient & xcap, const OpalAuthorisationRequestCommand & cmd)
598 {
599   PString ruleId = m_authorisationIdByAor[cmd.m_presentity];
600 
601   XCAPClient::NodeSelector node;
602   node.SetNamespace("urn:ietf:params:xml:ns:pres-rules", "pr");
603   node.SetNamespace("urn:ietf:params:xml:ns:common-policy", "cr");
604   node.AddElement("cr:ruleset");
605   node.AddElement("cr:rule", "id", ruleId);
606   xcap.SetNode(node);
607 
608   if (cmd.m_authorisation == AuthorisationRemove) {
609     if (xcap.DeleteXml()) {
610       PTRACE(3, "SIPPres\tRule id=" << ruleId << " removed for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
611     }
612     else {
613       PTRACE(3, "SIPPres\tCould not remove rule id=" << ruleId
614              << " for '" << cmd.m_presentity << "' at '" << m_aor << "\'\n"
615              << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
616     }
617     return true;
618   }
619 
620   PXML xml;
621   if (!xcap.GetXml(xml)) {
622     PTRACE(3, "SIPPres\tCould not locate existing rule id=" << ruleId
623            << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
624     return false;
625   }
626 
627   PXMLElement * root, * actions, * subHandling = NULL;
628   if ((root = xml.GetRootElement()) == NULL ||
629       (actions = root->GetElement("cr:actions")) == NULL ||
630       (subHandling = actions->GetElement("pr:sub-handling")) == NULL) {
631     PTRACE(2, "SIPPres\tInvalid XML in existing rule id=" << ruleId
632            << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
633     return false;
634   }
635 
636   if (subHandling->GetData() *= AuthNames[cmd.m_authorisation]) {
637     PTRACE(3, "SIPPres\tRule id=" << ruleId << " already set to "
638            << AuthNames[cmd.m_authorisation] << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
639     return true;
640   }
641 
642   // Adjust the authorisation
643   subHandling->SetData(AuthNames[cmd.m_authorisation]);
644 
645   if (xcap.PutXml(xml)) {
646     PTRACE(3, "SIPPres\tRule id=" << ruleId << " changed to"
647            << AuthNames[cmd.m_authorisation] << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
648     return true;
649   }
650 
651   PTRACE(3, "SIPPres\tCould not change existing rule id=" << ruleId
652          << " for '" << cmd.m_presentity << "' at '" << m_aor << "\'\n"
653          << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
654   return false;
655 }
656 
657 
Internal_AuthorisationRequest(const OpalAuthorisationRequestCommand & cmd)658 void SIP_Presentity::Internal_AuthorisationRequest(const OpalAuthorisationRequestCommand & cmd)
659 {
660   if (m_subProtocol < e_XCAP) {
661     PTRACE(4, "SIPPres\tRequires XCAP to do authorisation, aor=" << m_aor);
662     return;
663   }
664 
665   XCAPClient xcap;
666   InitRootXcap(xcap);
667   xcap.SetApplicationUniqueID(m_attributes.Get(XcapAuthAuidKey,
668                 m_subProtocol == e_OMA ? "org.openmobilealliance.pres-rules" : "pres-rules")); // As per RFC5025/9.1
669   xcap.SetContentType("application/auth-policy+xml");   // As per RFC5025/9.4
670   xcap.SetUserIdentifier(m_aor.AsString());             // As per RFC5025/9.7
671   xcap.SetAuthenticationInfo(m_attributes.Get(XcapAuthIdKey, m_attributes.Get(AuthNameKey, xcap.GetUserIdentifier())),
672                              m_attributes.Get(XcapPasswordKey, m_attributes.Get(AuthPasswordKey)));
673   xcap.SetFilename(m_attributes.Get(XcapAuthFileKey, m_subProtocol == e_OMA ? "pres-rules" : "index"));
674 
675   // See if we can use teh quick method
676   if (m_authorisationIdByAor.find(cmd.m_presentity) != m_authorisationIdByAor.end()) {
677     if (ChangeAuthNode(xcap, cmd))
678       return;
679   }
680 
681   PXML xml;
682   PXMLElement * element;
683 
684   // Could not find rule element quickly, get the whole document
685   if (!xcap.GetXml(xml)) {
686     if (xcap.GetLastResponseCode() != PHTTP::NotFound) {
687       PTRACE(2, "SIPPres\tUnexpected error getting rule file for '"
688              << cmd.m_presentity << "' at '" << m_aor << "\'\n"
689              << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
690       return;
691     }
692 
693     // No whole document, create a fresh one
694     xml.SetOptions(PXML::NoOptions);
695     element = xml.SetRootElement("cr:ruleset");
696     element->SetAttribute("xmlns:pr", "urn:ietf:params:xml:ns:pres-rules");
697     element->SetAttribute("xmlns:cr", "urn:ietf:params:xml:ns:common-policy");
698 
699     element = element->AddElement("rule", "id", "presence_allow_own");
700 
701     element->AddElement("cr:conditions")->AddElement("cr:identity")->AddElement("cr:one")->SetAttribute("id", m_aor.AsString());
702     element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationPermitted]);
703     element = element->AddElement("cr:transformations");
704     element->AddElement("pr:provide-services")->AddElement("pr:all-services");
705     element->AddElement("pr:provide-persons")->AddElement("pr:all-persons");
706     element->AddElement("pr:provide-all-attributes");
707     if (m_subProtocol == e_OMA) {
708       PXMLElement * root = xml.GetRootElement();
709       root->SetAttribute("xmlns:ocp", "urn:oma:xml:xdm:common-policy");
710 
711       element = root->AddElement("cr:rule", "id", "wp_prs_block_anonymous");
712       element->AddElement("cr:conditions")->AddElement("ocp:anonymous-request");
713       element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationDenied]);
714 
715       element = root->AddElement("cr:rule", "id", "wp_prs_unlisted");
716       element->AddElement("cr:conditions")->AddElement("ocp:other-identity");
717       element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationConfirming]);
718 
719       // Use an xcap object to calculate horrible URL
720       XCAPClient xcap;
721       InitBuddyXcap(xcap, PString::Empty(), "oma_grantedcontacts");
722 
723       element = root->AddElement("cr:rule", "id", "wp_prs_grantedcontacts");
724       element->AddElement("cr:conditions")->AddElement("ocp:external-list")->AddElement("ocp:entry", "anc", xcap.BuildURL().AsString());
725       element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationPermitted]);
726       element = element->AddElement("cr:transformations");
727       element->AddElement("pr:provide-services")->AddElement("pr:all-services");
728       element->AddElement("pr:provide-persons")->AddElement("pr:all-persons");
729       element->AddElement("pr:provide-all-attributes");
730 
731       InitBuddyXcap(xcap, PString::Empty(), "oma_blockedcontacts");
732 
733       element = root->AddElement("cr:rule", "id", "wp_prs_blockedcontacts");
734       element->AddElement("cr:conditions")->AddElement("ocp:external-list")->AddElement("ocp:entry", "anc", xcap.BuildURL().AsString());
735       element->AddElement("cr:actions")->AddElement("pr:sub-handling", AuthNames[AuthorisationDenied]);
736     }
737 
738     if (!xcap.PutXml(xml)) {
739       PTRACE(2, "SIPPres\tCould not add new rule file for '" << m_aor << "\'\n"
740              << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
741       return;
742     }
743 
744     PTRACE(3, "SIPPres\tNew rule file created for '" << m_aor << '\'');
745   }
746 
747   // Extract all the rules from it
748   m_authorisationIdByAor.clear();
749 
750   bool existingRuleForWatcher = false;
751 
752   PINDEX ruleIndex = 0;
753   while ((element = xml.GetElement("cr:rule", ruleIndex++)) != NULL) {
754     PString ruleId = element->GetAttribute("id");
755     if ((element = element->GetElement("cr:conditions")) != NULL &&
756         (element = element->GetElement("cr:identity")) != NULL &&
757         (element = element->GetElement("cr:one")) != NULL) {
758       PString watcher = element->GetAttribute("id");
759 
760       m_authorisationIdByAor[watcher] = ruleId;
761       PTRACE(4, "SIPPres\tGetting rule id=" << ruleId << " for '" << watcher << "' at '" << m_aor << '\'');
762 
763       if (watcher == cmd.m_presentity)
764         existingRuleForWatcher = true;
765     }
766   }
767 
768   if (existingRuleForWatcher) {
769     ChangeAuthNode(xcap, cmd);
770     return;
771   }
772 
773   // Create new rule with id as per http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName
774   static PAtomicInteger NextRuleId(PRandom::Number());
775   PString newRuleId(PString::Printf, "wp_prs%s_one_%lu",
776                     cmd.m_authorisation == AuthorisationPermitted ? "_allow" : "",
777                     ++NextRuleId);
778 
779   xml.SetOptions(PXML::FragmentOnly);
780   element = xml.SetRootElement("cr:rule");
781   element->SetAttribute("xmlns:pr", "urn:ietf:params:xml:ns:pres-rules");
782   element->SetAttribute("xmlns:cr", "urn:ietf:params:xml:ns:common-policy");
783   element->SetAttribute("id", newRuleId);
784 
785   element->AddElement("cr:conditions")->AddElement("cr:identity")->AddElement("cr:one")->SetAttribute("id", cmd.m_presentity);
786   element->AddElement("cr:actions")->AddElement("pr:sub-handling")->SetData(AuthNames[cmd.m_authorisation]);
787   element = element->AddElement("cr:transformations");
788   element->AddElement("pr:provide-services")->AddElement("pr:all-services");
789   element->AddElement("pr:provide-persons")->AddElement("pr:all-persons");
790   element->AddElement("pr:provide-all-attributes");
791 
792   XCAPClient::NodeSelector node;
793   node.SetNamespace("urn:ietf:params:xml:ns:pres-rules", "pr");
794   node.SetNamespace("urn:ietf:params:xml:ns:common-policy", "cr");
795   node.AddElement("cr:ruleset");
796   node.AddElement("cr:rule", "id", newRuleId);
797   xcap.SetNode(node);
798 
799   if (xcap.PutXml(xml)) {
800     PTRACE(3, "SIPPres\tNew rule set to" << AuthNames[cmd.m_authorisation] << " for '" << cmd.m_presentity << "' at '" << m_aor << '\'');
801     m_authorisationIdByAor[cmd.m_presentity] = newRuleId;
802     return;
803   }
804 
805   PTRACE(2, "SIPPres\tCould not add new rule for '" << cmd.m_presentity << "' at '" << m_aor << "\'\n"
806          << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
807 }
808 
809 
InitRootXcap(XCAPClient & xcap)810 void SIP_Presentity::InitRootXcap(XCAPClient & xcap)
811 {
812   PString root = m_attributes.Get(XcapRootKey);
813   if (root.IsEmpty())
814     root = "http:" + m_presenceAgent + '/';
815   xcap.SetRoot(root);
816 }
817 
818 
InitBuddyXcap(XCAPClient & xcap,const PString & entryName,const PString & listName)819 void SIP_Presentity::InitBuddyXcap(XCAPClient & xcap, const PString & entryName, const PString & listName)
820 {
821   InitRootXcap(xcap);
822   xcap.SetApplicationUniqueID("resource-lists");            // As per RFC5025/9.1
823   xcap.SetContentType("application/resource-lists+xml");    // As per RFC5025/9.4
824   xcap.SetUserIdentifier(m_aor.AsString());                 // As per RFC5025/9.7
825   xcap.SetAuthenticationInfo(m_attributes.Get(XcapAuthIdKey, m_attributes.Get(OpalPresentity::AuthNameKey, xcap.GetUserIdentifier())),
826                              m_attributes.Get(XcapPasswordKey, m_attributes.Get(AuthPasswordKey)));
827   xcap.SetFilename("index");
828 
829   XCAPClient::NodeSelector node;
830   node.SetNamespace("urn:ietf:params:xml:ns:resource-lists");
831   node.AddElement("resource-lists");
832   node.AddElement("list", "name", listName.IsEmpty() ? m_attributes.Get(XcapBuddyListKey,
833                                         m_subProtocol == e_OMA ? "oma_buddylist" : "buddylist") : listName);
834 
835   if (!entryName.IsEmpty())
836     node.AddElement("entry", "uri", entryName);
837 
838   xcap.SetNode(node);
839 }
840 
841 
BuddyInfoToXML(const OpalPresentity::BuddyInfo & buddy,PXMLElement * parent)842 static PXMLElement * BuddyInfoToXML(const OpalPresentity::BuddyInfo & buddy, PXMLElement * parent)
843 {
844   PXMLElement * element = new PXMLElement(parent, "entry");
845   element->SetAttribute("uri", buddy.m_presentity);
846 
847   if (!buddy.m_displayName.IsEmpty())
848     element->AddElement("display-name", buddy.m_displayName);
849 
850   return element;
851 }
852 
853 
XMLToBuddyInfo(const PXMLElement * element,OpalPresentity::BuddyInfo & buddy)854 static bool XMLToBuddyInfo(const PXMLElement * element, OpalPresentity::BuddyInfo & buddy)
855 {
856   if (element == NULL || element->GetName() != "entry")
857     return false;
858 
859   buddy.m_presentity = element->GetAttribute("uri");
860 
861   PXMLElement * itemElement;
862   if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:display-name")) != NULL)
863     buddy.m_displayName = itemElement->GetData();
864 
865   if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:card")) != NULL) {
866     PURL url;
867     if (url.Parse(itemElement->GetData())) {
868       PString str;
869       if (url.LoadResource(str))
870         buddy.m_vCard.Parse(str);
871     }
872   }
873 
874   if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:icon")) != NULL)
875     buddy.m_icon = itemElement->GetData();
876 
877   if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:map")) != NULL)
878     buddy.m_map = itemElement->GetData();
879 
880   if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:sound")) != NULL)
881     buddy.m_sound = itemElement->GetData();
882 
883   if ((itemElement= element->GetElement("urn:ietf:params:xml:ns:pidf:cipid:homepage")) != NULL)
884     buddy.m_homePage = itemElement->GetData();
885 
886   buddy.m_contentType = "application/resource-lists+xml";
887   buddy.m_rawXML = element->AsString();
888   return true;
889 }
890 
891 
RecursiveGetBuddyList(OpalPresentity::BuddyList & buddies,XCAPClient & xcap,const PURL & url)892 static bool RecursiveGetBuddyList(OpalPresentity::BuddyList & buddies, XCAPClient & xcap, const PURL & url)
893 {
894   if (url.IsEmpty())
895     return false;
896 
897   PXML xml;
898   if (!xcap.GetXml(url, xml))
899     return false;
900 
901   PXMLElement * element;
902   PINDEX idx = 0;
903   while ((element = xml.GetElement("entry", idx++)) != NULL) {
904     OpalPresentity::BuddyInfo buddy;
905     if (XMLToBuddyInfo(element, buddy))
906       buddies.push_back(buddy);
907   }
908 
909   idx = 0;
910   while ((element = xml.GetElement("external", idx++)) != NULL)
911     RecursiveGetBuddyList(buddies, xcap, element->GetAttribute("anchor"));
912 
913   idx = 0;
914   while ((element = xml.GetElement("entry-ref", idx++)) != NULL) {
915     PURL url(xcap.GetRoot());
916     url.SetPathStr(url.GetPathStr() + element->GetAttribute("ref"));
917     RecursiveGetBuddyList(buddies, xcap, url);
918   }
919 
920   return true;
921 }
922 
923 
GetBuddyListEx(BuddyList & buddies)924 OpalPresentity::BuddyStatus SIP_Presentity::GetBuddyListEx(BuddyList & buddies)
925 {
926   if (m_subProtocol < e_XCAP) {
927     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
928     return BuddyStatus_ListFeatureNotImplemented;
929   }
930 
931   XCAPClient xcap;
932   InitBuddyXcap(xcap);
933   if (RecursiveGetBuddyList(buddies, xcap, xcap.BuildURL()) ||
934       !buddies.empty() ||
935       xcap.GetLastResponseCode() == PHTTP::NotFound)
936     return BuddyStatus_OK;
937 
938   return BuddyStatus_GenericFailure;
939 }
940 
941 
SetBuddyListEx(const BuddyList & buddies)942 OpalPresentity::BuddyStatus SIP_Presentity::SetBuddyListEx(const BuddyList & buddies)
943 {
944   if (m_subProtocol < e_XCAP) {
945     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
946     return BuddyStatus_ListFeatureNotImplemented;
947   }
948 
949   PXML xml(PXML::FragmentOnly);
950 
951   PString buddyListKey = m_subProtocol == e_OMA ? "oma_buddylist" : "buddylist";
952   PXMLElement * root = xml.SetRootElement("list");
953   root->SetAttribute("xmlns", "urn:ietf:params:xml:ns:resource-lists");
954   root->SetAttribute("name", m_attributes.Get(XcapBuddyListKey, buddyListKey));
955 
956   for (BuddyList::const_iterator it = buddies.begin(); it != buddies.end(); ++it)
957     root->AddChild(BuddyInfoToXML(*it, root));
958 
959   XCAPClient xcap;
960   InitBuddyXcap(xcap);
961 
962   if (xcap.PutXml(xml))
963     return BuddyStatus_OK;
964 
965   if (xcap.GetLastResponseCode() == PHTTP::Conflict && xcap.GetLastResponseInfo().Find("Parent") != P_MAX_INDEX) {
966     // Got "Parent does not exist" error, so need to add whole file
967     xml.SetOptions(PXML::NoOptions);
968     root = xml.SetRootElement("resource-lists");
969     root->SetAttribute("xmlns", "urn:ietf:params:xml:ns:resource-lists");
970 
971     PXMLElement * listElement = root->AddElement("list", "name", m_attributes.Get(XcapBuddyListKey, buddyListKey));
972 
973     for (BuddyList::const_iterator it = buddies.begin(); it != buddies.end(); ++it)
974       listElement->AddChild(BuddyInfoToXML(*it, listElement));
975 
976     xcap.ClearNode();
977     if (xcap.PutXml(xml))
978       return BuddyStatus_OK;
979   }
980 
981   PTRACE(2, "SIPPres\tError setting buddy list of '" << m_aor << "\'\n"
982          << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
983 
984   return BuddyStatus_GenericFailure;
985 }
986 
987 
DeleteBuddyListEx()988 OpalPresentity::BuddyStatus SIP_Presentity::DeleteBuddyListEx()
989 {
990   if (m_subProtocol < e_XCAP) {
991     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
992     return BuddyStatus_ListFeatureNotImplemented;
993   }
994 
995   XCAPClient xcap;
996   InitBuddyXcap(xcap);
997 
998   if (xcap.DeleteXml())
999     return BuddyStatus_OK;
1000 
1001   PTRACE(2, "SIPPres\tError deleting buddy list of '" << m_aor << "\'\n"
1002          << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
1003   return BuddyStatus_GenericFailure;
1004 }
1005 
1006 
GetBuddyEx(BuddyInfo & buddy)1007 OpalPresentity::BuddyStatus SIP_Presentity::GetBuddyEx(BuddyInfo & buddy)
1008 {
1009   if (m_subProtocol < e_XCAP) {
1010     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
1011     return BuddyStatus_ListFeatureNotImplemented;
1012   }
1013 
1014   XCAPClient xcap;
1015   InitBuddyXcap(xcap, buddy.m_presentity);
1016 
1017   PXML xml;
1018   if (xcap.GetXml(xml) && XMLToBuddyInfo(xml.GetRootElement(), buddy))
1019     return BuddyStatus_OK;
1020   else
1021     return BuddyStatus_GenericFailure;
1022 }
1023 
1024 
SetBuddyEx(const BuddyInfo & buddy)1025 OpalPresentity::BuddyStatus SIP_Presentity::SetBuddyEx(const BuddyInfo & buddy)
1026 {
1027   if (m_subProtocol < e_XCAP) {
1028     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
1029     return BuddyStatus_ListFeatureNotImplemented;
1030   }
1031 
1032   if (buddy.m_presentity.IsEmpty())
1033     return BuddyStatus_GenericFailure;
1034 
1035   XCAPClient xcap;
1036   InitBuddyXcap(xcap, buddy.m_presentity);
1037 
1038   PXML xml(PXML::FragmentOnly);
1039   xml.SetRootElement(BuddyInfoToXML(buddy, NULL));
1040 
1041   if (xcap.PutXml(xml))
1042     return BuddyStatus_OK;
1043 
1044   if (xcap.GetLastResponseCode() != PHTTP::Conflict || xcap.GetLastResponseInfo().Find("Parent") == P_MAX_INDEX) {
1045     PTRACE(2, "SIPPres\tError setting buddy '" << buddy.m_presentity << "' of '" << m_aor << "\'\n"
1046            << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
1047     return BuddyStatus_GenericFailure;
1048   }
1049 
1050   // Got "Parent does not exist" error, so need to add whole list
1051   BuddyList buddies;
1052   buddies.push_back(buddy);
1053   return SetBuddyListEx(buddies);
1054 }
1055 
1056 
DeleteBuddyEx(const PURL & presentity)1057 OpalPresentity::BuddyStatus SIP_Presentity::DeleteBuddyEx(const PURL & presentity)
1058 {
1059   if (m_subProtocol < e_XCAP) {
1060     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
1061     return BuddyStatus_ListFeatureNotImplemented;
1062   }
1063 
1064   XCAPClient xcap;
1065   InitBuddyXcap(xcap, presentity);
1066 
1067   if (xcap.DeleteXml())
1068     return BuddyStatus_OK;
1069 
1070   PTRACE(2, "SIPPres\tError deleting buddy '" << presentity << "' of '" << m_aor << "\'\n"
1071          << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
1072   return BuddyStatus_GenericFailure;
1073 }
1074 
1075 
SubscribeBuddyListEx(PINDEX & numSuccessful,bool subscribe)1076 OpalPresentity::BuddyStatus SIP_Presentity::SubscribeBuddyListEx(PINDEX & numSuccessful, bool subscribe)
1077 {
1078   if (m_subProtocol < e_XCAP) {
1079     PTRACE(4, "SIPPres\tRequires XCAP to have buddies, aor=" << m_aor);
1080     return BuddyStatus_ListFeatureNotImplemented;
1081   }
1082 
1083   PXML xml;
1084   XCAPClient xcap;
1085   InitRootXcap(xcap);
1086   xcap.SetApplicationUniqueID("rls-services");
1087   xcap.SetContentType("application/rls-services+xml");
1088   xcap.SetUserIdentifier(m_aor.AsString());
1089   xcap.SetAuthenticationInfo(m_attributes.Get(XcapAuthIdKey, m_attributes.Get(AuthNameKey, xcap.GetUserIdentifier())),
1090                              m_attributes.Get(XcapPasswordKey, m_attributes.Get(AuthPasswordKey)));
1091   xcap.SetFilename("index");
1092 
1093   PString serviceURI = xcap.GetUserIdentifier() + ";pres-list=oma_buddylist";
1094 
1095   if (!xcap.GetXml(xml)) {
1096     if (xcap.GetLastResponseCode() != PHTTP::NotFound) {
1097       PTRACE(2, "SIPPres\tUnexpected error getting rls-services file for at '" << m_aor << "\'\n"
1098              << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
1099       return OpalPresentity::SubscribeBuddyListEx(numSuccessful, subscribe); // Do individual subscribes
1100     }
1101 
1102     // No file at all, add the root element.
1103     xml.SetRootElement("rls-services")->SetAttribute("xmlns", "urn:ietf:params:xml:ns:rls-services");
1104   }
1105   else {
1106     // Have file, see if have specific service
1107     PXMLElement * element = xml.GetElement("service", "uri", serviceURI);
1108     if (element != NULL) {
1109       PTRACE(4, "SIPPres\tConfirmed rls-services entry for '" << serviceURI << "\' is\n" << xml);
1110       numSuccessful = P_MAX_INDEX;
1111       return SubscribeToPresence(serviceURI, subscribe) ? BuddyStatus_OK : BuddyStatus_GenericFailure;
1112     }
1113 
1114     // Nope, so add it
1115   }
1116 
1117   // Added service to existing XML or newly rooted one
1118   PXMLElement * element = xml.GetRootElement()->AddElement("service");
1119   element->SetAttribute("uri", serviceURI);
1120 
1121   // Calculate the horrible URL using a temporary XCAP client
1122   XCAPClient xcapTemp;
1123   InitBuddyXcap(xcapTemp);
1124   element->AddElement("resource-list")->SetData(xcapTemp.BuildURL().AsString());
1125 
1126   element->AddElement("packages")->AddElement("package")->SetData("presence");
1127 
1128   if (xcap.PutXml(xml)) {
1129     numSuccessful = P_MAX_INDEX;
1130     return SubscribeToPresence(serviceURI, subscribe) ? BuddyStatus_OK : BuddyStatus_GenericFailure;
1131   }
1132 
1133   PTRACE(2, "SIPPres\tCould not add new rls-services entry for '" << m_aor << "\'\n"
1134          << xcap.GetLastResponseCode() << ' '  << xcap.GetLastResponseInfo());
1135 
1136   return OpalPresentity::SubscribeBuddyListEx(numSuccessful, subscribe); // Do individual subscribes
1137 }
1138 
1139 
1140 //////////////////////////////////////////////////////////////
1141 
XCAPClient()1142 XCAPClient::XCAPClient()
1143   : m_global(false)
1144   , m_filename("index")
1145 {
1146 }
1147 
1148 
BuildURL()1149 PURL XCAPClient::BuildURL()
1150 {
1151   PURL uri(m_root);                              // XCAP root
1152 
1153   uri.AppendPath(m_auid);                        // Application Unique ID
1154 
1155   uri.AppendPath(m_global ? "global" : "users"); // RFC4825/6.2, The path segment after the AUID MUST either be "global" or "users".
1156 
1157   if (!m_global)
1158     uri.AppendPath(m_xui);                       // XCAP User Identifier
1159 
1160   if (!m_filename.IsEmpty()) {
1161     uri.AppendPath(m_filename);                        // Final resource name
1162     m_node.AddToURL(uri);
1163   }
1164 
1165   return uri;
1166 }
1167 
1168 
HasNode(const PURL & url)1169 static bool HasNode(const PURL & url)
1170 {
1171   const PStringArray & path = url.GetPath();
1172   for (PINDEX i = 0; i < path.GetSize(); ++i) {
1173     if (path[i] == "~~")
1174       return true;
1175   }
1176 
1177   return false;
1178 }
1179 
1180 
GetXml(const PURL & url,PXML & xml)1181 bool XCAPClient::GetXml(const PURL & url, PXML & xml)
1182 {
1183   bool hasNode = HasNode(url);
1184 
1185   PString body;
1186   if (!GetTextDocument(url, body, hasNode ? "application/xcap-el+xml" : m_contentType)) {
1187     PTRACE(3, "SIPPres\tError getting buddy list at '" << url << "\'\n"
1188            << GetLastResponseCode() << ' '  << GetLastResponseInfo());
1189     return false;
1190   }
1191 
1192   if (xml.Load(body, hasNode ? PXML::FragmentOnly : PXML::NoOptions))
1193     return true;
1194 
1195   PTRACE(2, "XCAP\tError parsing XML for '" << url << "\'\n"
1196             "Line " << xml.GetErrorLine() << ", Column " << xml.GetErrorColumn() << ": " << xml.GetErrorString());
1197   return false;
1198 }
1199 
1200 
PutXml(const PURL & url,const PXML & xml)1201 bool XCAPClient::PutXml(const PURL & url, const PXML & xml)
1202 {
1203   PStringStream strm;
1204   strm << xml;
1205   return PutTextDocument(url, strm, HasNode(url) ? "application/xcap-el+xml" : m_contentType);
1206 }
1207 
1208 
AsString() const1209 PString XCAPClient::ElementSelector::AsString() const
1210 {
1211   PStringStream strm;
1212 
1213   strm << m_name;
1214 
1215   if (!m_position.IsEmpty())
1216     strm << '[' << m_position << ']';
1217 
1218   if (!m_attribute.IsEmpty())
1219     strm << "[@" << m_attribute << "=\"" << m_value << "\"]";
1220 
1221   return strm;
1222 }
1223 
1224 
AddToURL(PURL & uri) const1225 void XCAPClient::NodeSelector::AddToURL(PURL & uri) const
1226 {
1227   if (empty())
1228     return;
1229 
1230   uri.AppendPath("~~");                      // Node selector
1231 
1232   for (const_iterator it = begin(); it != end(); ++it)
1233     uri.AppendPath(it->AsString());
1234 
1235   if (m_namespaces.empty())
1236     return;
1237 
1238   PStringStream query;
1239   for (std::map<PString, PString>::const_iterator it = m_namespaces.begin(); it != m_namespaces.end(); ++it) {
1240     query << "xmlns(";
1241     if (!it->first.IsEmpty())
1242       query << it->first << '=';
1243     query << it->second << ')';
1244   }
1245 
1246   // Non-standard format query parameter, we fake that by having one
1247   // dictionary element at key value empty string
1248   uri.SetQueryVar(PString::Empty(), query);
1249 }
1250 
1251 
1252 //////////////////////////////////////////////////////////////
1253 
1254 
1255 
1256 #endif // P_EXPAT && OPAL_SIP
1257