WvStreams
wvdbusserver.cc
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.
7 *
8 * This library is licensed under the LGPL, please read LICENSE for details.
9 *
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"
20
21
23{
24 enum State { NullWait, AuthWait, BeginWait };
25 State state;
26 wvuid_t client_uid;
27public:
29 virtual bool authorize(WvDBusConn &c);
30
31 virtual wvuid_t get_uid() { return client_uid; }
32};
33
34
35WvDBusServerAuth::WvDBusServerAuth()
36{
37 state = NullWait;
38 client_uid = WVUID_INVALID;
39}
40
41
43{
44 c.log("State=%s\n", state);
45 if (state == NullWait)
46 {
47 char buf[1];
48 size_t len = c.read(buf, 1);
49 if (len == 1 && buf[0] == '\0')
50 {
51 state = AuthWait;
52 // fall through
53 }
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
58 }
59
60 const char *line = c.in();
61 if (!line)
62 return false; // not done yet
63
64 WvStringList words;
65 words.split(line);
66 WvString cmd(words.popstr());
67
68 if (state == AuthWait)
69 {
70 if (!strcasecmp(cmd, "AUTH"))
71 {
72 // FIXME actually check authentication information!
73 WvString typ(words.popstr());
74 if (!strcasecmp(typ, "EXTERNAL"))
75 {
76 WvString uid =
78 if (!!uid)
79 {
80 // FIXME: Check that client is on the same machine!
81 client_uid = uid.num();
82 }
83
84 state = BeginWait;
85 c.out("OK f00f\r\n");
86 }
87 else
88 {
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
94 }
95 }
96 else
97 c.seterr("AUTH command expected: '%s'", line);
98 }
99 else if (state == BeginWait)
100 {
101 if (!strcasecmp(cmd, "BEGIN"))
102 return true; // done
103 else
104 c.seterr("BEGIN command expected: '%s'", line);
105 }
106
107 return false;
108}
109
110
111WvDBusServer::WvDBusServer()
112 : log("DBus Server", WvLog::Debug)
113{
114 // user must now call listen() at least once.
115 add(&listeners, false, "listeners");
116}
117
118
120{
121 close();
122 zap();
123}
124
125
127{
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");
136}
137
138
140{
141 if (geterr())
142 return false;
143
144 WvIStreamList::Iter i(listeners);
145 for (i.rewind(); i.next(); )
146 if (!i->isok())
147 return false;
148 return WvIStreamList::isok();
149}
150
151
153{
154 return WvIStreamList::geterr();
155}
156
157
159{
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();
166}
167
168
170{
171 name_to_conn[name] = conn;
172}
173
174
176{
177 assert(name_to_conn[name] == conn);
178 name_to_conn.erase(name);
179}
180
181
183{
184 {
185 std::map<WvString,WvDBusConn*>::iterator i;
186 for (i = name_to_conn.begin(); i != name_to_conn.end(); )
187 {
188 if (i->second == conn)
189 {
190 name_to_conn.erase(i->first);
191 i = name_to_conn.begin();
192 }
193 else
194 ++i;
195 }
196 }
197
198 all_conns.unlink(conn);
199}
200
201
202bool WvDBusServer::do_server_msg(WvDBusConn &conn, WvDBusMsg &msg)
203{
204 WvString method(msg.get_member());
205
206 if (msg.get_path() == "/org/freedesktop/DBus/Local")
207 {
208 if (method == "Disconnected")
209 return true; // nothing to do until their *stream* disconnects
210 }
211
212 if (msg.get_dest() != "org.freedesktop.DBus") return false;
213
214 // dbus-daemon seems to ignore the path as long as the service is right
215 //if (msg.get_path() != "/org/freedesktop/DBus") return false;
216
217 // I guess it's for us!
218
219 if (method == "Hello")
220 {
221 log("hello_cb\n");
222 msg.reply().append(conn.uniquename()).send(conn);
223 return true;
224 }
225 else if (method == "RequestName")
226 {
227 WvDBusMsg::Iter args(msg);
228 WvString _name = args.getnext();
229 // uint32_t flags = args.getnext(); // supplied, but ignored
230
231 log("request_name_cb(%s)\n", _name);
232 register_name(_name, &conn);
233
234 msg.reply().append((uint32_t)DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
235 .send(conn);
236 return true;
237 }
238 else if (method == "ReleaseName")
239 {
240 WvDBusMsg::Iter args(msg);
241 WvString _name = args.getnext();
242
243 log("release_name_cb(%s)\n", _name);
244 unregister_name(_name, &conn);
245
246 msg.reply().append((uint32_t)DBUS_RELEASE_NAME_REPLY_RELEASED)
247 .send(conn);
248 return true;
249 }
250 else if (method == "NameHasOwner")
251 {
252 WvDBusMsg::Iter args(msg);
253 WvString known_name = args.getnext();
254 WvDBusConn *serv = name_to_conn[known_name];
255 msg.reply().append(!!serv).send(conn);
256 return true;
257 }
258 else if (method == "GetNameOwner")
259 {
260 WvDBusMsg::Iter args(msg);
261 WvString known_name = args.getnext();
262 WvDBusConn *serv = name_to_conn[known_name];
263 if (serv)
264 msg.reply().append(serv->uniquename()).send(conn);
265 else
266 WvDBusError(msg, "org.freedesktop.DBus.Error.NameHasNoOwner",
267 "No match for name '%s'", known_name).send(conn);
268 return true;
269 }
270 else if (method == "AddMatch")
271 {
272 // we just proxy every signal to everyone for now
273 msg.reply().send(conn);
274 return true;
275 }
276 else if (method == "StartServiceByName")
277 {
278 // we don't actually support this, but returning an error message
279 // confuses perl's Net::DBus library, at least.
280 msg.reply().send(conn);
281 return true;
282 }
283 else if (method == "GetConnectionUnixUser" ||
284 method == "GetConnectionUnixUserName")
285 {
286 WvDBusMsg::Iter args(msg);
287 WvString _name = args.getnext();
288 WvDBusConn *target = name_to_conn[_name];
289
290 if (!target)
291 {
292 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
293 "No connection found for name '%s'.", _name).send(conn);
294 return true;
295 }
296
297 wvuid_t client_uid = target->get_uid();
298
299 if (client_uid == WVUID_INVALID)
300 {
301 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
302 "No user associated with connection '%s'.",
303 target->uniquename()).send(conn);
304 return true;
305 }
306
307 log("Found unix user for '%s', uid is %s.\n", _name, client_uid);
308
309 if (method == "GetConnectionUnixUser")
310 {
311 WvString s(client_uid);
312 msg.reply().append((uint32_t)atoll(s)).send(conn);
313 return true;
314 }
315 else if (method == "GetConnectionUnixUserName")
316 {
317 WvString username = wv_username_from_uid(client_uid);
318 if (!username)
319 {
320 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
321 "No username for uid='%s'", client_uid)
322 .send(conn);
323 return true;
324 }
325
326 msg.reply().append(username).send(conn);
327 return true;
328 }
329 else
330 assert(false); // should never happen
331
332 assert(false);
333 }
334 else if (method == "GetConnectionCert" ||
335 method == "GetConnectionCertFingerprint")
336 {
337 WvDBusMsg::Iter args(msg);
338 WvString connid = args.getnext();
339
340 WvDBusConn *c = name_to_conn[connid];
341
342 WvString ret = c ? c->getattr("peercert") : WvString::null;
343 if (ret.isnull())
344 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
345 "Connection %s did not present a certificate",
346 connid).send(conn);
347 else
348 {
349 if (method == "GetConnectionCertFingerprint")
350 {
351 WvX509 tempcert;
352 // We can assume it's valid because our SSL conn authenticated
353 tempcert.decode(WvX509::CertPEM, ret);
354 ret = tempcert.get_fingerprint();
355 }
356 msg.reply().append(ret).send(conn);
357 }
358
359 return true;
360 }
361 else
362 {
363 WvDBusError(msg, "org.freedesktop.DBus.Error.UnknownMethod",
364 "Unknown dbus method '%s'", method).send(conn);
365 return true; // but we've handled it, since it belongs to us
366 }
367}
368
369
370bool WvDBusServer::do_bridge_msg(WvDBusConn &conn, WvDBusMsg &msg)
371{
372 // if we get here, nobody handled the message internally, so we can try
373 // to proxy it.
374 if (!!msg.get_dest()) // don't handle blank (broadcast) paths here
375 {
376 std::map<WvString,WvDBusConn*>::iterator i
377 = name_to_conn.find(msg.get_dest());
378 WvDBusConn *dconn = (i == name_to_conn.end()) ? NULL : i->second;
379 log("Proxying #%s -> %s\n",
380 msg.get_serial(),
381 dconn ? dconn->uniquename() : WvString("(UNKNOWN)"));
382 dbus_message_set_sender(msg, conn.uniquename().cstr());
383 if (dconn)
384 dconn->send(msg);
385 else
386 {
387 log(WvLog::Warning,
388 "Proxy: no connection for '%s'\n", msg.get_dest());
389 return false;
390 }
391 return true;
392 }
393
394 return false;
395}
396
397
398bool WvDBusServer::do_broadcast_msg(WvDBusConn &conn, WvDBusMsg &msg)
399{
400 if (!msg.get_dest())
401 {
402 log("Broadcasting #%s\n", msg.get_serial());
403
404 // note: we broadcast messages even back to the connection where
405 // they originated. I'm not sure this is necessarily ideal, but if
406 // you don't do that then an app can't signal objects that might be
407 // inside itself.
408 WvDBusConnList::Iter i(all_conns);
409 for (i.rewind(); i.next(); )
410 i->send(msg);
411 return true;
412 }
413 return false;
414}
415
416
417bool WvDBusServer::do_gaveup_msg(WvDBusConn &conn, WvDBusMsg &msg)
418{
419 WvDBusError(msg, "org.freedesktop.DBus.Error.NameHasNoOwner",
420 "No running service named '%s'", msg.get_dest()).send(conn);
421 return true;
422}
423
424
425void WvDBusServer::conn_closed(WvStream &s)
426{
427 WvDBusConn *c = (WvDBusConn *)&s;
429 this->release();
430}
431
432
433void WvDBusServer::new_connection_cb(IWvStream *s)
434{
435 WvDBusConn *c = new WvDBusConn(s, new WvDBusServerAuth, false);
436 c->addRef();
437 this->addRef();
438 all_conns.append(c, true);
439 register_name(c->uniquename(), c);
440
441 /* The delayed callback here should be explained. The
442 * 'do_broadcast_msg' function sends out data along all connections.
443 * Unfortunately, this is a prime time to figure out a connection died.
444 * A dying connection is removed from the all_conns list... but we are
445 * still in do_broadcast_msg, and using an iterator to go over this list.
446 * The consequences of this were not pleasant, at best. Wrapping cb in a
447 * delayedcallback will always safely remove a connection.
448 */
449 IWvStreamCallback mycb = wv::bind(&WvDBusServer::conn_closed, this,
450 wv::ref(*c));
451 c->setclosecallback(wv::delayed(mycb));
452
453 c->add_callback(WvDBusConn::PriSystem,
454 wv::bind(&WvDBusServer::do_server_msg, this,
455 wv::ref(*c), _1));
456 c->add_callback(WvDBusConn::PriBridge,
457 wv::bind(&WvDBusServer::do_bridge_msg, this,
458 wv::ref(*c), _1));
459 c->add_callback(WvDBusConn::PriBroadcast,
460 wv::bind(&WvDBusServer::do_broadcast_msg, this,
461 wv::ref(*c), _1));
462 c->add_callback(WvDBusConn::PriGaveUp,
463 wv::bind(&WvDBusServer::do_gaveup_msg, this,
464 wv::ref(*c), _1));
465
466 append(c, true, "wvdbus servconn");
467}
virtual unsigned int addRef()=0
Indicate you are using this object.
virtual unsigned int release()=0
Indicate that you are finished using this object.
virtual IWvListenerCallback onaccept(IWvListenerCallback _cb)=0
Set a user-defined function to be called when a new connection is available.
virtual bool isok() const =0
By default, returns true if geterr() == 0.
uint32_t send(WvDBusMsg msg)
Send a message on the bus, not expecting any reply.
void add_callback(CallbackPri pri, WvDBusCallback cb, void *cookie=NULL)
Adds a callback to the connection: all received messages will be sent to all callbacks to look at and...
WvString uniquename() const
Return this connection's unique name on the bus, assigned by the server at connect time.
WvDBusMsg reply()
Generate a message that will be a reply to this one.
Definition wvdbusmsg.cc:629
void send(WvDBusConn &conn)
A shortcut for sending this message on the given connection.
Definition wvdbusmsg.cc:641
WvDBusMsg & append(const char *s)
The following methods are designed to allow appending various arguments to the message.
Definition wvdbusmsg.cc:461
virtual bool authorize(WvDBusConn &c)
Main action callback.
void listen(WvStringParm moniker)
Listen using a given WvListener moniker.
virtual int geterr() const
If isok() is false, return the system error number corresponding to the error, -1 for a special error...
void unregister_name(WvStringParm name, WvDBusConn *conn)
Undo a register_name().
void unregister_conn(WvDBusConn *conn)
Forget all name registrations for a particular connection.
WvString get_addr()
get the full, final address (identification guid and all) of the server if there's more than one list...
virtual ~WvDBusServer()
Shut down this server.
void register_name(WvStringParm name, WvDBusConn *conn)
Register a given dbus service name as belonging to a particular connection.
virtual bool isok() const
return true if the stream is actually usable right now
WvString strflushstr(WvStringParm instr, bool finish=false)
Flushes data through the encoder from a string to a string.
Definition wvencoder.cc:107
virtual int geterr() const
If isok() is false, return the system error number corresponding to the error, -1 for a special error...
Definition wverror.h:48
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Definition wvstring.h:94
int num() const
Return a stdc++ string with the contents of this string.
Definition wvstring.h:286
bool isnull() const
returns true if this string is null
Definition wvstring.h:290
const char * cstr() const
return a (const char *) for this string.
Definition wvstring.h:267
A hex decoder.
Definition wvhex.h:54
virtual bool isok() const
return true if the stream is actually usable right now
A WvLog stream accepts log messages from applications and forwards them to all registered WvLogRcv's.
Definition wvlog.h:57
Unified support for streams, that is, sequences of bytes that may or may not be ready for read/write ...
Definition wvstream.h:25
IWvStreamCallback setclosecallback(IWvStreamCallback _callback)
Sets a callback to be invoked on close().
Definition wvstream.cc:1175
virtual size_t read(void *buf, size_t count)
read a data block on the stream.
Definition wvstream.cc:490
virtual void close()
Close the stream if it is open; isok() becomes false from now on.
Definition wvstream.cc:341
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition wvstream.cc:451
This is a WvList of WvStrings, and is a really handy way to parse strings.
void split(WvStringParm s, const char *splitchars=" \t\r\n", int limit=0)
split s and form a list ignoring splitchars (except at beginning and end) ie.
WvString popstr()
get the first string in the list, or an empty string if the list is empty.
WvString is an implementation of a simple and efficient printable-string class.
Definition wvstring.h:330
X509 Class to handle certificates and their related functions.
Definition wvx509.h:42
WvString get_fingerprint(const FprintMode mode=FingerSHA1) const
Get the certHash (fingerprint) of the certificate.
Definition wvx509.cc:1416
virtual void decode(const DumpMode mode, WvStringParm str)
Load the information from the format requested by mode into the class - this overwrites the certifica...
Definition wvx509.cc:499
Various little string functions.