1 /*
2  * This file is part of Licq, an instant messaging client for UNIX.
3  * Copyright (C) 2013 Licq developers <licq-dev@googlegroups.com>
4  *
5  * Please refer to the COPYRIGHT file distributed with this source
6  * distribution for the names of the individual contributors.
7  *
8  * Licq is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Licq is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Licq; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 #include "plugin.h"
24 
25 #include <boost/foreach.hpp>
26 #include <cstring>
27 #include <list>
28 #include <poll.h>
29 
30 #include <licq/contactlist/owner.h>
31 #include <licq/contactlist/user.h>
32 #include <licq/contactlist/usermanager.h>
33 #include <licq/daemon.h>
34 #include <licq/inifile.h>
35 #include <licq/logging/log.h>
36 #include <licq/plugin/pluginmanager.h>
37 #include <licq/pluginsignal.h>
38 #include <licq/protocolmanager.h>
39 #include <licq/userevents.h>
40 
41 #include "dbusinterface.h"
42 
43 using namespace LicqDbus;
44 
Plugin()45 Plugin::Plugin()
46   : myConn(NULL),
47     myDbusName("org.licq"),
48     myReadOnly(false)
49 {
50   // Empty
51 }
52 
~Plugin()53 Plugin::~Plugin()
54 {
55   // Empty
56 }
57 
run()58 int Plugin::run()
59 {
60   setSignalMask(Licq::PluginSignal::SignalUser);
61 
62   bool useSystemBus = false;
63   Licq::IniFile conf("dbus.conf");
64   if (conf.loadFile())
65   {
66     conf.setSection("DBus");
67     conf.get("BusName", myDbusName, myDbusName);
68     conf.get("ReadOnly", myReadOnly, myReadOnly);
69 
70     std::string bus;
71     conf.get("Bus", bus);
72     if (bus == "system")
73       useSystemBus = true;
74   }
75 
76   myMainLoop.addRawFile(getReadPipe(), this);
77 
78   myConn = new DbusInterface(myMainLoop, this, useSystemBus);
79   myConn->connect();
80 
81   // Timer to try reconnecting if connection is lost
82   myMainLoop.addTimeout(60*1000, this, 0, false);
83 
84   myMainLoop.run();
85   Licq::gLog.info("D-Bus plugin shutting down");
86 
87   myMainLoop.removeCallback(this);
88 
89   // Send shutdown signal to clients before disconnecting from D-Bus
90   myConn->sendSignal("/org/licq/Core", "org.licq.Core", "Shutdown", NULL);
91   myConn->flush();
92 
93   myConn->disconnect();
94   delete myConn;
95   myConn = NULL;
96 
97   return 0;
98 }
99 
isEnabled() const100 bool Plugin::isEnabled() const
101 {
102   return true;
103 }
104 
timeoutEvent(int)105 void Plugin::timeoutEvent(int /*id*/)
106 {
107   // Try to reconnect to message bus if not connected
108   myConn->connect();
109 }
110 
rawFileEvent(int,int fd,int revents)111 void Plugin::rawFileEvent(int /*id*/, int fd, int revents)
112 {
113   if (revents & (POLLERR|POLLHUP|POLLNVAL))
114     myMainLoop.quit();
115 
116   if ((revents & POLLIN) == 0)
117     return;
118 
119   char ch;
120   ::read(fd, &ch, sizeof(char));
121 
122   switch (ch)
123   {
124     case PipeSignal:
125       processSignal(popSignal().get());
126       break;
127 
128     case PipeEvent:
129       popEvent();
130       break;
131 
132     case PipeShutdown:
133       myMainLoop.quit();
134       break;
135 
136     case PipeEnable:
137     case PipeDisable:
138       break;
139 
140     default:
141       Licq::gLog.error("Unknown notification from Licq core: %c", ch);
142   }
143 }
144 
protocolIdToString(unsigned long protocolId)145 std::string Plugin::protocolIdToString(unsigned long protocolId)
146 {
147   switch (protocolId)
148   {
149     case ICQ_PPID: return "ICQ";
150     case MSN_PPID: return "MSN";
151     case JABBER_PPID: return "Jabber";
152     default:
153       return Licq::protocolId_toString(protocolId);
154   }
155 }
156 
userIdToObjectPath(const Licq::UserId & userId)157 std::string Plugin::userIdToObjectPath(const Licq::UserId& userId)
158 {
159   std::string s("/org/licq/ContactList/");
160   s += protocolIdToString(userId.protocolId());
161   if (!userId.isOwner())
162   {
163     s += '/';
164     s += DbusInterface::encodeObjectPathPart(userId.ownerId().accountId());
165   }
166   s += '/';
167   s += DbusInterface::encodeObjectPathPart(userId.accountId());
168   return s;
169 }
170 
objectPathToUserId(const std::string & object)171 Licq::UserId Plugin::objectPathToUserId(const std::string& object)
172 {
173   if (object.compare(0, 22, "/org/licq/ContactList/") != 0)
174     return Licq::UserId();
175 
176   size_t p1 = object.find('/', 23);
177   if (p1 == std::string::npos)
178     // Missing two parameters
179     return Licq::UserId();
180 
181   unsigned long protocolId = Licq::protocolId_fromString(object.substr(22, p1-22));
182   if (protocolId == 0)
183     // Unknown protocol
184     return Licq::UserId();
185 
186   size_t p2 = object.find('/', p1+1);
187   if (p2 == std::string::npos)
188   {
189     // Owner
190     std::string accountId(DbusInterface::decodeObjectPathPart(object.substr(p1+1)));
191     return Licq::UserId(protocolId, accountId);
192   }
193 
194   if (object.find('/', p2+1) != std::string::npos)
195     // Too many levels
196     return Licq::UserId();
197 
198   std::string ownerAccountId(DbusInterface::decodeObjectPathPart(object.substr(p1+1, p2-p1-1)));
199   std::string accountId(DbusInterface::decodeObjectPathPart(object.substr(p2+1)));
200   if (accountId == ownerAccountId)
201     // Owners shouldn't have the last level
202     return Licq::UserId();
203 
204   return Licq::UserId(Licq::UserId(protocolId, ownerAccountId), accountId);
205 }
206 
processSignal(const Licq::PluginSignal * sig)207 void Plugin::processSignal(const Licq::PluginSignal* sig)
208 {
209   assert(sig != NULL);
210 
211   switch (sig->signal())
212   {
213     case Licq::PluginSignal::SignalUser:
214     {
215       Licq::UserReadGuard user(sig->userId());
216       if (!user.isLocked())
217         break;
218 
219       std::string object(userIdToObjectPath(sig->userId()));
220       std::string iface(sig->userId().isOwner() ? "org.licq.Account" : "org.licq.Contact");
221 
222       switch (sig->subSignal())
223       {
224         case Licq::PluginSignal::UserStatus:
225           // Note: The statusString() returns a translated string for displaying
226           //       The numeric status should be used for any client logic
227           myConn->sendSignal(object, iface, "Status", "us", user->status(), user->statusString().c_str());
228           break;
229 
230         case Licq::PluginSignal::UserEvents:
231         {
232           if (sig->argument() == 0)
233             // Auto response check
234             break;
235 
236           // Get a display name to include in signals
237           std::string name = user->getAlias();
238           if (name.empty())
239             name = user->getFullName();
240           if (name.empty())
241             name = user->accountId();
242 
243           // Signal number of unread messages for this contact
244           myConn->sendSignal(object, iface, "NumUnread", "us", user->NewMessages(), name.c_str());
245 
246           // Signal total number of unread messages for all contacts
247           myConn->sendSignal("/org/licq/ContactList", "org.licq.ContactList",
248               "NumUnread", "u", Licq::User::getNumUserEvents());
249 
250           const Licq::UserEvent* ue = user->EventPeekId(sig->argument());
251           if (ue != NULL)
252           {
253             // New message received
254             myConn->sendSignal(object, iface, "ReceivedEvent", "ss", ue->text().c_str(), name.c_str());
255           }
256           break;
257         }
258       }
259       break;
260     }
261 
262     default:
263       break;
264   }
265 }
266 
dbusConnected()267 void Plugin::dbusConnected()
268 {
269   if (!myConn->requestName(myDbusName))
270     Licq::gLog.warning("Failed to claim name on message bus");
271 
272   // Signal clients that we are up and running
273   myConn->sendSignal("/org/licq/Core", "org.licq.Core", "Started", NULL);
274 }
275 
dbusMethod(const char * path,const char * iface,const char * member,DBusMessage * msgref,DBusMessageIter * argref,const char * fmt)276 int Plugin::dbusMethod(const char* path, const char* iface, const char* member,
277       DBusMessage* msgref, DBusMessageIter* argref, const char* fmt)
278 {
279   if (strcmp(iface, "org.licq.Core") == 0)
280   {
281     if (strcmp(path, "/org/licq/Core") != 0)
282       return DbusInterface::ErrorUnknownObject;
283 
284     if (strcmp(member, "GetVersion") == 0)
285     {
286       myConn->sendReply(msgref, "s", Licq::gDaemon.Version());
287       return DbusInterface::MethodReplied;
288     }
289 
290     if (!myReadOnly && strcmp(member, "Shutdown") == 0)
291     {
292       myConn->sendReply(msgref, NULL);
293       myConn->flush();
294       Licq::gDaemon.Shutdown();
295       return DbusInterface::MethodReplied;
296     }
297 
298     return DbusInterface::ErrorUnknownMethod;
299   }
300 
301   if (strcmp(iface, "org.licq.ContactList") == 0)
302   {
303     if (strcmp(path, "/org/licq/ContactList") != 0)
304       return DbusInterface::ErrorUnknownObject;
305 
306     if (strcmp(member, "GetAccounts") == 0)
307     {
308       std::list<std::string> owners;
309       {
310         Licq::OwnerListGuard ownerList;
311         BOOST_FOREACH(const Licq::Owner* o, **ownerList)
312           owners.push_back(userIdToObjectPath(o->id()));
313       }
314       myConn->sendReply(msgref, "ao", &owners);
315       return DbusInterface::MethodReplied;
316     }
317 
318     return DbusInterface::ErrorUnknownMethod;
319   }
320 
321   if (strcmp(iface, "org.licq.Account") == 0 || strcmp(iface, "org.licq.Contact") == 0)
322   {
323     Licq::UserId userId(objectPathToUserId(path));
324     if (!userId.isValid())
325       return DbusInterface::ErrorUnknownObject;
326 
327     // If object path is owner iface must also be and vice versa
328     if ((strcmp(iface, "org.licq.Account") == 0) != userId.isOwner())
329       return DbusInterface::ErrorUnknownObject;
330 
331     if (strcmp(member, "GetContacts") == 0 && userId.isOwner())
332     {
333       std::list<std::string> contacts;
334       {
335         Licq::UserListGuard userList(userId);
336         BOOST_FOREACH(const Licq::User* u, **userList)
337           contacts.push_back(userIdToObjectPath(u->id()));
338       }
339       myConn->sendReply(msgref, "ao", &contacts);
340       return DbusInterface::MethodReplied;
341     }
342 
343     if (!myReadOnly && strcmp(member, "SetStatus") == 0 && userId.isOwner())
344     {
345       // Make sure owner exists
346       if (!Licq::gUserManager.userExists(userId))
347         return DbusInterface::ErrorUnknownObject;
348 
349       unsigned status;
350       if (strcmp(fmt, "s") == 0)
351       {
352         const char* strStatus;
353         myConn->getNextMessageParamValue(argref, &strStatus);
354         if (!Licq::User::stringToStatus(strStatus, status))
355           return DbusInterface::ErrorInvalidArgs;
356       }
357       else if (strcmp(fmt, "u") == 0)
358         myConn->getNextMessageParamValue(argref, &status);
359       else
360         return DbusInterface::ErrorInvalidSignature;
361 
362       Licq::gProtocolManager.setStatus(userId, status);
363 
364       myConn->sendReply(msgref, NULL);
365       return DbusInterface::MethodReplied;
366     }
367 
368     // Only read access functions below
369     Licq::UserReadGuard u(userId);
370     if (!u.isLocked())
371       return DbusInterface::ErrorUnknownObject;
372 
373     if (strcmp(member, "GetName") == 0)
374     {
375       myConn->sendReply(msgref, "ssss", u->getAlias().c_str(), u->getFirstName().c_str(),
376           u->getLastName().c_str(), u->getEmail().c_str());
377       return DbusInterface::MethodReplied;
378     }
379 
380     if (strcmp(member, "GetStatus") == 0)
381     {
382       myConn->sendReply(msgref, "us", u->status(), u->statusString().c_str());
383       return DbusInterface::MethodReplied;
384     }
385 
386     return DbusInterface::ErrorUnknownMethod;
387   }
388 
389   return DbusInterface::ErrorUnknownInterface;
390 }
391 
dbusIntrospect(const char * path)392 std::string Plugin::dbusIntrospect(const char* path)
393 {
394   const char* p = path;
395 
396   if (strcmp(path, "/") == 0)
397     return "<node name=\"org\">";
398   if (strcmp(path, "/org") == 0)
399     return "<node name=\"licq\">";
400 
401   if (strncmp(path, "/org/licq", 9) != 0)
402     return "";
403   p = path + 9;
404   if (p[0] == '\0')
405     return "<node name=\"Core\"/><node name=\"ContactList\"/>";
406 
407   if (strcmp(p, "/Core") == 0)
408   {
409     std::string s = "<interface name=\"org.licq.Core\">";
410     if (!myReadOnly)
411       s += "<method name=\"Shutdown\"/>";
412     s += "<method name=\"GetVersion\"><arg type=\"s\" direction=\"out\"/></method>"
413         "<signal name=\"Started\"/>"
414         "<signal name=\"Shutdown\"/>"
415         "</interface>";
416     return s;
417   }
418 
419   // Everything below is for ContactList tree
420   if (strncmp(p, "/ContactList", 12) != 0)
421     return "";
422   p += 12;
423 
424   if (p[0] == '\0')
425   {
426     std::string s("<interface name=\"org.licq.ContactList\">"
427           "<method name=\"GetAccounts\">"
428             "<arg type=\"ao\" direction=\"out\"/>"
429           "</method>"
430         "</interface>");
431     Licq::OwnerListGuard ownerList;
432     BOOST_FOREACH(const Licq::Owner* o, **ownerList)
433       s += "<node name=\"" + protocolIdToString(o->protocolId()) + "\"/>";
434     return s;
435   }
436 
437   if (*(p++) != '/')
438     return "";
439   unsigned long protocolId = Licq::protocolId_fromString(p);
440   if (protocolId != 0)
441   {
442     std::string s;
443     Licq::OwnerListGuard ownerList(protocolId);
444     BOOST_FOREACH(const Licq::Owner* o, **ownerList)
445       s += "<node name=\"" + DbusInterface::encodeObjectPathPart(o->accountId()) + "\"/>";
446     return s;
447   }
448 
449   Licq::UserId userId(objectPathToUserId(path));
450   if (!userId.isValid())
451     return "";
452 
453   std::string s;
454 
455   if (userId.isOwner())
456   {
457     s = "<interface name=\"org.licq.Account\">"
458           "<method name=\"GetContacts\">"
459             "<arg name=\"Contacts\" type=\"ao\" direction=\"out\"/>"
460           "</method>";
461     if (!myReadOnly)
462       s += "<method name=\"SetStatus\">"
463             "<arg name=\"StatusBits\" type=\"u\" direction=\"in\"/>"
464           "</method>";
465   }
466   else
467     s = "<interface name=\"org.licq.Contact\">";
468 
469   s.append("<method name=\"GetName\">"
470           "<arg name=\"Alias\" type=\"s\" direction=\"out\"/>"
471           "<arg name=\"FirstName\" type=\"s\" direction=\"out\"/>"
472           "<arg name=\"LastName\" type=\"s\" direction=\"out\"/>"
473           "<arg name=\"Email\" type=\"s\" direction=\"out\"/>"
474         "</method>"
475         "<method name=\"GetStatus\">"
476           "<arg name=\"StatusBits\" type=\"u\" direction=\"out\"/>"
477           "<arg name=\"StatusText\" type=\"s\" direction=\"out\"/>"
478         "</method>"
479         "<signal name=\"Status\">"
480           "<arg name=\"StatusBits\" type=\"u\"/>"
481           "<arg name=\"StatusText\" type=\"s\"/>"
482         "</signal>"
483         "<signal name=\"NumUnread\">"
484           "<arg name=\"NumUnread\" type=\"u\"/>"
485           "<arg name=\"DisplayName\" type=\"s\"/>"
486         "</signal>"
487         "<signal name=\"ReceivedEvent\">"
488           "<arg name=\"MessageText\" type=\"s\"/>"
489           "<arg name=\"DisplayName\" type=\"s\"/>"
490         "</signal>"
491       "</interface>");
492 
493   if (userId.isOwner())
494   {
495     Licq::UserListGuard userList(userId);
496     BOOST_FOREACH(const Licq::User* u, **userList)
497       s += "<node name=\"" + DbusInterface::encodeObjectPathPart(u->accountId()) + "\"/>";
498   }
499 
500   return s;
501 }
502