Fix some unrelated unit tests when !HAVE_OPENSSL_POLICY_MAPPING.
[wvstreams.git] / dbus / wvdbusserver.cc
blob3404a9c8c577076650b2dd6b2d0f23023172b473
1 /* -*- Mode: C++ -*-
2 * Worldvisions Weaver Software:
3 * Copyright (C) 2005-2006 Net Integration Technologies, Inc.
4 *
5 * Pathfinder Software:
6 * Copyright (C) 2007, Carillon Information Security Inc.
8 * This library is licensed under the LGPL, please read LICENSE for details.
10 */
11 #include "wvdbusserver.h"
12 #include "wvdbusconn.h"
13 #include "wvstrutils.h"
14 #include "wvuid.h"
15 #include "wvtcplistener.h"
16 #include "wvdelayedcallback.h"
17 #undef interface // windows
18 #include <dbus/dbus.h>
19 #include "wvx509.h"
22 class WvDBusServerAuth : public IWvDBusAuth
24 enum State { NullWait, AuthWait, BeginWait };
25 State state;
26 wvuid_t client_uid;
27 public:
28 WvDBusServerAuth();
29 virtual bool authorize(WvDBusConn &c);
31 virtual wvuid_t get_uid() { return client_uid; }
35 WvDBusServerAuth::WvDBusServerAuth()
37 state = NullWait;
38 client_uid = WVUID_INVALID;
42 bool WvDBusServerAuth::authorize(WvDBusConn &c)
44 c.log("State=%s\n", state);
45 if (state == NullWait)
47 char buf[1];
48 size_t len = c.read(buf, 1);
49 if (len == 1 && buf[0] == '\0')
51 state = AuthWait;
52 // fall through
54 else if (len > 0)
55 c.seterr("Client didn't start with NUL byte");
56 else
57 return false; // no data yet, come back later
60 const char *line = c.in();
61 if (!line)
62 return false; // not done yet
64 WvStringList words;
65 words.split(line);
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"))
76 WvString uid =
77 WvHexDecoder().strflushstr(words.popstr());
78 if (!!uid)
80 // FIXME: Check that client is on the same machine!
81 client_uid = uid.num();
84 state = BeginWait;
85 c.out("OK f00f\r\n");
87 else
89 // Some clients insist that we reject something because
90 // their state machine can't handle us accepting just the
91 // "AUTH " command.
92 c.out("REJECTED EXTERNAL\r\n");
93 // no change in state
96 else
97 c.seterr("AUTH command expected: '%s'", line);
99 else if (state == BeginWait)
101 if (!strcasecmp(cmd, "BEGIN"))
102 return true; // done
103 else
104 c.seterr("BEGIN command expected: '%s'", line);
107 return false;
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()
121 close();
122 zap();
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",
132 listener->errstr());
133 listener->onaccept(wv::bind(&WvDBusServer::new_connection_cb,
134 this, _1));
135 listeners.add(listener, true, "listener");
139 bool WvDBusServer::isok() const
141 if (geterr())
142 return false;
144 WvIStreamList::Iter i(listeners);
145 for (i.rewind(); i.next(); )
146 if (!i->isok())
147 return false;
148 return WvIStreamList::isok();
152 int WvDBusServer::geterr() const
154 return WvIStreamList::geterr();
158 WvString WvDBusServer::get_addr()
160 // FIXME assumes tcp
161 WvIStreamList::Iter i(listeners);
162 for (i.rewind(); i.next(); )
163 if (i->isok())
164 return WvString("tcp:%s", *i->src());
165 return WvString();
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();
193 else
194 ++i;
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();
207 else
208 ++i;
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")
235 log("hello_cb\n");
236 msg.reply().append(conn.uniquename()).send(conn);
237 return true;
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)
249 .send(conn);
250 return true;
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)
261 .send(conn);
262 return true;
264 else if (method == "GetNameOwner")
266 WvDBusMsg::Iter args(msg);
267 WvString known_name = args.getnext();
268 WvDBusConn *serv = name_to_conn[known_name];
269 if (serv)
270 msg.reply().append(serv->uniquename()).send(conn);
271 else
272 WvDBusError(msg, "org.freedesktop.DBus.Error.NameHasNoOwner",
273 "No match for name '%s'", known_name).send(conn);
274 return true;
276 else if (method == "AddMatch")
278 // we just proxy every signal to everyone for now
279 msg.reply().send(conn);
280 return true;
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];
289 if (!target)
291 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
292 "No connection found for name '%s'.", _name).send(conn);
293 return true;
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);
303 return true;
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);
312 return true;
314 else if (method == "GetConnectionUnixUserName")
316 WvString username = wv_username_from_uid(client_uid);
317 if (!username)
319 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
320 "No username for uid='%s'", client_uid)
321 .send(conn);
322 return true;
325 msg.reply().append(username).send(conn);
326 return true;
328 else
329 assert(false); // should never happen
331 assert(false);
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;
342 if (ret.isnull())
343 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
344 "Connection %s did not present a certificate",
345 connid).send(conn);
346 else
348 if (method == "GetConnectionCertFingerprint")
350 WvX509 tempcert;
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);
358 return true;
360 else
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
372 // to proxy it.
373 if (msg.is_reply())
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());
383 dconn->send(msg);
384 serial_to_conn.erase(rserial);
385 return true;
387 else
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",
399 msg.get_serial(),
400 dconn ? dconn->uniquename() : WvString("(UNKNOWN)"));
401 dbus_message_set_sender(msg, conn.uniquename().cstr());
402 if (dconn)
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());
409 else
411 log(WvLog::Warning,
412 "Proxy: no connection for '%s'\n", msg.get_dest());
413 return false;
415 return true;
418 return false;
422 bool WvDBusServer::do_broadcast_msg(WvDBusConn &conn, WvDBusMsg &msg)
424 if (!msg.get_dest())
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
431 // inside itself.
432 WvDBusConnList::Iter i(all_conns);
433 for (i.rewind(); i.next(); )
434 i->send(msg);
435 return true;
437 return false;
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);
445 return true;
449 void WvDBusServer::conn_closed(WvStream &s)
451 WvDBusConn *c = (WvDBusConn *)&s;
452 unregister_conn(c);
453 this->release();
457 void WvDBusServer::new_connection_cb(IWvStream *s)
459 WvDBusConn *c = new WvDBusConn(s, new WvDBusServerAuth, false);
460 c->addRef();
461 this->addRef();
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,
474 wv::ref(*c));
475 c->setclosecallback(wv::delayed(mycb));
477 c->add_callback(WvDBusConn::PriSystem,
478 wv::bind(&WvDBusServer::do_server_msg, this,
479 wv::ref(*c), _1));
480 c->add_callback(WvDBusConn::PriBridge,
481 wv::bind(&WvDBusServer::do_bridge_msg, this,
482 wv::ref(*c), _1));
483 c->add_callback(WvDBusConn::PriBroadcast,
484 wv::bind(&WvDBusServer::do_broadcast_msg, this,
485 wv::ref(*c), _1));
486 c->add_callback(WvDBusConn::PriGaveUp,
487 wv::bind(&WvDBusServer::do_gaveup_msg, this,
488 wv::ref(*c), _1));
490 append(c, true, "wvdbus servconn");