2 * Worldvisions Weaver Software:
3 * Copyright (C) 2005-2006 Net Integration Technologies, Inc.
6 * Copyright (C) 2007, Carillon Information Security Inc.
8 * This library is licensed under the LGPL, please read LICENSE for details.
11 #include "wvdbusserver.h"
12 #include "wvdbusconn.h"
13 #include "wvstrutils.h"
15 #include "wvtcplistener.h"
16 #include "wvdelayedcallback.h"
17 #undef interface // windows
18 #include <dbus/dbus.h>
22 class WvDBusServerAuth
: public IWvDBusAuth
24 enum State
{ NullWait
, AuthWait
, BeginWait
};
29 virtual bool authorize(WvDBusConn
&c
);
31 virtual wvuid_t
get_uid() { return client_uid
; }
35 WvDBusServerAuth::WvDBusServerAuth()
38 client_uid
= WVUID_INVALID
;
42 bool WvDBusServerAuth::authorize(WvDBusConn
&c
)
44 c
.log("State=%s\n", state
);
45 if (state
== NullWait
)
48 size_t len
= c
.read(buf
, 1);
49 if (len
== 1 && buf
[0] == '\0')
55 c
.seterr("Client didn't start with NUL byte");
57 return false; // no data yet, come back later
60 const char *line
= c
.in();
62 return false; // not done yet
66 WvString
cmd(words
.popstr());
68 if (state
== AuthWait
)
70 if (!strcasecmp(cmd
, "AUTH"))
72 // FIXME actually check authentication information!
73 WvString
typ(words
.popstr());
74 if (!strcasecmp(typ
, "EXTERNAL"))
77 WvHexDecoder().strflushstr(words
.popstr());
80 // FIXME: Check that client is on the same machine!
81 client_uid
= uid
.num();
89 // Some clients insist that we reject something because
90 // their state machine can't handle us accepting just the
92 c
.out("REJECTED EXTERNAL\r\n");
97 c
.seterr("AUTH command expected: '%s'", line
);
99 else if (state
== BeginWait
)
101 if (!strcasecmp(cmd
, "BEGIN"))
104 c
.seterr("BEGIN command expected: '%s'", line
);
111 WvDBusServer::WvDBusServer()
112 : log("DBus Server", WvLog::Debug
)
114 // user must now call listen() at least once.
115 add(&listeners
, false, "listeners");
119 WvDBusServer::~WvDBusServer()
126 void WvDBusServer::listen(WvStringParm moniker
)
128 IWvListener
*listener
= IWvListener::create(moniker
);
129 log(WvLog::Info
, "Listening on '%s'\n", *listener
->src());
130 if (!listener
->isok())
131 log(WvLog::Info
, "Can't listen: %s\n",
133 listener
->onaccept(wv::bind(&WvDBusServer::new_connection_cb
,
135 listeners
.add(listener
, true, "listener");
139 bool WvDBusServer::isok() const
144 WvIStreamList::Iter
i(listeners
);
145 for (i
.rewind(); i
.next(); )
148 return WvIStreamList::isok();
152 int WvDBusServer::geterr() const
154 return WvIStreamList::geterr();
158 WvString
WvDBusServer::get_addr()
161 WvIStreamList::Iter
i(listeners
);
162 for (i
.rewind(); i
.next(); )
164 return WvString("tcp:%s", *i
->src());
169 void WvDBusServer::register_name(WvStringParm name
, WvDBusConn
*conn
)
171 name_to_conn
[name
] = conn
;
175 void WvDBusServer::unregister_name(WvStringParm name
, WvDBusConn
*conn
)
177 assert(name_to_conn
[name
] == conn
);
178 name_to_conn
.erase(name
);
182 void WvDBusServer::unregister_conn(WvDBusConn
*conn
)
185 std::map
<WvString
,WvDBusConn
*>::iterator i
;
186 for (i
= name_to_conn
.begin(); i
!= name_to_conn
.end(); )
188 if (i
->second
== conn
)
190 name_to_conn
.erase(i
->first
);
191 i
= name_to_conn
.begin();
199 std::map
<uint32_t,WvDBusConn
*>::iterator i
;
200 for (i
= serial_to_conn
.begin(); i
!= serial_to_conn
.end(); )
202 if (i
->second
== conn
)
204 serial_to_conn
.erase(i
->first
);
205 i
= serial_to_conn
.begin();
212 all_conns
.unlink(conn
);
216 bool WvDBusServer::do_server_msg(WvDBusConn
&conn
, WvDBusMsg
&msg
)
218 WvString
method(msg
.get_member());
220 if (msg
.get_path() == "/org/freedesktop/DBus/Local")
222 if (method
== "Disconnected")
223 return true; // nothing to do until their *stream* disconnects
226 if (msg
.get_dest() != "org.freedesktop.DBus") return false;
228 // dbus-daemon seems to ignore the path as long as the service is right
229 //if (msg.get_path() != "/org/freedesktop/DBus") return false;
231 // I guess it's for us!
233 if (method
== "Hello")
236 msg
.reply().append(conn
.uniquename()).send(conn
);
239 else if (method
== "RequestName")
241 WvDBusMsg::Iter
args(msg
);
242 WvString _name
= args
.getnext();
243 // uint32_t flags = args.getnext(); // supplied, but ignored
245 log("request_name_cb(%s)\n", _name
);
246 register_name(_name
, &conn
);
248 msg
.reply().append((uint32_t)DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER
)
252 else if (method
== "ReleaseName")
254 WvDBusMsg::Iter
args(msg
);
255 WvString _name
= args
.getnext();
257 log("release_name_cb(%s)\n", _name
);
258 unregister_name(_name
, &conn
);
260 msg
.reply().append((uint32_t)DBUS_RELEASE_NAME_REPLY_RELEASED
)
264 else if (method
== "GetNameOwner")
266 WvDBusMsg::Iter
args(msg
);
267 WvString known_name
= args
.getnext();
268 WvDBusConn
*serv
= name_to_conn
[known_name
];
270 msg
.reply().append(serv
->uniquename()).send(conn
);
272 WvDBusError(msg
, "org.freedesktop.DBus.Error.NameHasNoOwner",
273 "No match for name '%s'", known_name
).send(conn
);
276 else if (method
== "AddMatch")
278 // we just proxy every signal to everyone for now
279 msg
.reply().send(conn
);
282 else if (method
== "GetConnectionUnixUser" ||
283 method
== "GetConnectionUnixUserName")
285 WvDBusMsg::Iter
args(msg
);
286 WvString _name
= args
.getnext();
287 WvDBusConn
*target
= name_to_conn
[_name
];
291 WvDBusError(msg
, "org.freedesktop.DBus.Error.Failed",
292 "No connection found for name '%s'.", _name
).send(conn
);
296 wvuid_t client_uid
= target
->get_uid();
298 if (client_uid
== WVUID_INVALID
)
300 WvDBusError(msg
, "org.freedesktop.DBus.Error.Failed",
301 "No user associated with connection '%s'.",
302 target
->uniquename()).send(conn
);
306 log("Found unix user for '%s', uid is %s.\n", _name
, client_uid
);
308 if (method
== "GetConnectionUnixUser")
310 WvString
s(client_uid
);
311 msg
.reply().append((uint32_t)atoll(s
)).send(conn
);
314 else if (method
== "GetConnectionUnixUserName")
316 WvString username
= wv_username_from_uid(client_uid
);
319 WvDBusError(msg
, "org.freedesktop.DBus.Error.Failed",
320 "No username for uid='%s'", client_uid
)
325 msg
.reply().append(username
).send(conn
);
329 assert(false); // should never happen
333 else if (method
== "GetConnectionCert" ||
334 method
== "GetConnectionCertFingerprint")
336 WvDBusMsg::Iter
args(msg
);
337 WvString connid
= args
.getnext();
339 WvDBusConn
*c
= name_to_conn
[connid
];
341 WvString ret
= c
? c
->getattr("peercert") : WvString::null
;
343 WvDBusError(msg
, "org.freedesktop.DBus.Error.Failed",
344 "Connection %s did not present a certificate",
348 if (method
== "GetConnectionCertFingerprint")
351 // We can assume it's valid because our SSL conn authenticated
352 tempcert
.decode(WvX509::CertPEM
, ret
);
353 ret
= tempcert
.get_fingerprint();
355 msg
.reply().append(ret
).send(conn
);
362 WvDBusError(msg
, "org.freedesktop.DBus.Error.UnknownMethod",
363 "Unknown dbus method '%s'", method
).send(conn
);
364 return true; // but we've handled it, since it belongs to us
369 bool WvDBusServer::do_bridge_msg(WvDBusConn
&conn
, WvDBusMsg
&msg
)
371 // if we get here, nobody handled the message internally, so we can try
375 uint32_t rserial
= msg
.get_replyserial();
376 std::map
<uint32_t,WvDBusConn
*>::iterator i
377 = serial_to_conn
.find(rserial
);
378 if (i
!= serial_to_conn
.end())
380 WvDBusConn
*dconn
= i
->second
;
381 log("Proxy reply: target is %s\n", dconn
->uniquename());
382 dbus_message_set_sender(msg
, conn
.uniquename().cstr());
384 serial_to_conn
.erase(rserial
);
389 log("Proxy reply: unknown serial #%s!\n", rserial
);
390 // fall through and let someone else look at it
393 else if (!!msg
.get_dest()) // don't handle blank (broadcast) paths here
395 std::map
<WvString
,WvDBusConn
*>::iterator i
396 = name_to_conn
.find(msg
.get_dest());
397 WvDBusConn
*dconn
= (i
== name_to_conn
.end()) ? NULL
: i
->second
;
398 log("Proxying #%s -> %s\n",
400 dconn
? dconn
->uniquename() : WvString("(UNKNOWN)"));
401 dbus_message_set_sender(msg
, conn
.uniquename().cstr());
404 uint32_t serial
= dconn
->send(msg
);
405 serial_to_conn
[serial
] = &conn
;
406 log("Proxy: now expecting reply #%s to %s\n",
407 serial
, conn
.uniquename());
412 "Proxy: no connection for '%s'\n", msg
.get_dest());
422 bool WvDBusServer::do_broadcast_msg(WvDBusConn
&conn
, WvDBusMsg
&msg
)
426 log("Broadcasting #%s\n", msg
.get_serial());
428 // note: we broadcast messages even back to the connection where
429 // they originated. I'm not sure this is necessarily ideal, but if
430 // you don't do that then an app can't signal objects that might be
432 WvDBusConnList::Iter
i(all_conns
);
433 for (i
.rewind(); i
.next(); )
441 bool WvDBusServer::do_gaveup_msg(WvDBusConn
&conn
, WvDBusMsg
&msg
)
443 WvDBusError(msg
, "org.freedesktop.DBus.Error.NameHasNoOwner",
444 "No running service named '%s'", msg
.get_dest()).send(conn
);
449 void WvDBusServer::conn_closed(WvStream
&s
)
451 WvDBusConn
*c
= (WvDBusConn
*)&s
;
457 void WvDBusServer::new_connection_cb(IWvStream
*s
)
459 WvDBusConn
*c
= new WvDBusConn(s
, new WvDBusServerAuth
, false);
462 all_conns
.append(c
, true);
463 register_name(c
->uniquename(), c
);
465 /* The delayed callback here should be explained. The
466 * 'do_broadcast_msg' function sends out data along all connections.
467 * Unfortunately, this is a prime time to figure out a connection died.
468 * A dying connection is removed from the all_conns list... but we are
469 * still in do_broadcast_msg, and using an iterator to go over this list.
470 * The consequences of this were not pleasant, at best. Wrapping cb in a
471 * delayedcallback will always safely remove a connection.
473 IWvStreamCallback mycb
= wv::bind(&WvDBusServer::conn_closed
, this,
475 c
->setclosecallback(wv::delayed(mycb
));
477 c
->add_callback(WvDBusConn::PriSystem
,
478 wv::bind(&WvDBusServer::do_server_msg
, this,
480 c
->add_callback(WvDBusConn::PriBridge
,
481 wv::bind(&WvDBusServer::do_bridge_msg
, this,
483 c
->add_callback(WvDBusConn::PriBroadcast
,
484 wv::bind(&WvDBusServer::do_broadcast_msg
, this,
486 c
->add_callback(WvDBusConn::PriGaveUp
,
487 wv::bind(&WvDBusServer::do_gaveup_msg
, this,
490 append(c
, true, "wvdbus servconn");