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.
Definition: wvdbusconn.cc:194
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...
Definition: wvdbusconn.cc:307
WvString uniquename() const
Return this connection's unique name on the bus, assigned by the server at connect time.
Definition: wvdbusconn.cc:176
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.
Definition: wvdbusserver.cc:42
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:1174
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.
Definition: wvstringlist.h:28
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.
Definition: wvstringlist.cc:19
WvString popstr()
get the first string in the list, or an empty string if the list is empty.
Definition: wvstringlist.cc:55
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.