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