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