2 * Worldvisions Weaver Software:
3 * Copyright (C) 2004-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 "wvdbusconn.h"
12 #include "wvmoniker.h"
13 #include "wvstrutils.h"
14 #undef interface // windows
15 #include <dbus/dbus.h>
18 static WvString
translate(WvStringParm dbus_moniker
)
21 WvStringList::Iter
i(l
);
23 if (!strncasecmp(dbus_moniker
, "unix:", 5))
25 WvString path
, tmpdir
;
26 l
.split(dbus_moniker
+5, ",");
27 for (i
.rewind(); i
.next(); )
29 if (!strncasecmp(*i
, "path=", 5))
31 else if (!strncasecmp(*i
, "abstract=", 9))
32 path
= WvString("@%s", *i
+ 9);
33 else if (!strncasecmp(*i
, "tmpdir=", 7))
37 return WvString("unix:%s", path
);
39 return WvString("unix:%s/dbus.sock", tmpdir
);
41 else if (!strncasecmp(dbus_moniker
, "tcp:", 4))
43 WvString host
, port
, family
;
44 l
.split(dbus_moniker
+4, ",");
45 for (i
.rewind(); i
.next(); )
47 if (!strncasecmp(*i
, "family=", 7))
49 else if (!strncasecmp(*i
, "host=", 5))
51 else if (!strncasecmp(*i
, "port=", 5))
55 return WvString("tcp:%s:%s", host
, port
);
57 return WvString("tcp:%s", host
);
59 return WvString("tcp:0.0.0.0:%s", port
); // localhost
62 return dbus_moniker
; // unrecognized
66 static IWvStream
*stream_creator(WvStringParm _s
, IObject
*)
70 if (!strcasecmp(s
, "starter"))
72 WvString
startbus(getenv("DBUS_STARTER_ADDRESS"));
74 return IWvStream::create(translate(startbus
));
77 WvString
starttype(getenv("DBUS_STARTER_BUS_TYPE"));
78 if (!!starttype
&& !strcasecmp(starttype
, "system"))
80 else if (!!starttype
&& !strcasecmp(starttype
, "session"))
85 if (!strcasecmp(s
, "system"))
87 // NOTE: the environment variable for the address of the system
88 // bus is very often not set-- in that case, look in your dbus
89 // system bus config file (e.g. /etc/dbus-1/system.conf) for the
90 // raw address and either set this environment variable to that, or
91 // pass in the address directly
92 WvString
bus(getenv("DBUS_SYSTEM_BUS_ADDRESS"));
94 return IWvStream::create(translate(bus
));
97 if (!strcasecmp(s
, "session"))
99 WvString
bus(getenv("DBUS_SESSION_BUS_ADDRESS"));
101 return IWvStream::create(translate(bus
));
104 return IWvStream::create(translate(s
));
107 static WvMoniker
<IWvStream
> reg("dbus", stream_creator
);
110 static int conncount
;
112 WvDBusConn::WvDBusConn(IWvStream
*_cloned
, IWvDBusAuth
*_auth
, bool _client
)
113 : WvStreamClone(_cloned
),
114 log(WvString("DBus %s%s",
116 ++conncount
), WvLog::Debug5
),
119 init(_auth
, _client
);
123 WvDBusConn::WvDBusConn(WvStringParm moniker
, IWvDBusAuth
*_auth
, bool _client
)
124 : WvStreamClone(IWvStream::create(moniker
)),
125 log(WvString("DBus %s%s",
127 ++conncount
), WvLog::Debug5
),
130 log("Connecting to '%s'\n", moniker
);
131 init(_auth
, _client
);
135 void WvDBusConn::init(IWvDBusAuth
*_auth
, bool _client
)
137 log("Initializing.\n");
139 auth
= _auth
? _auth
: new WvDBusClientAuth
;
140 authorized
= in_post_select
= false;
141 if (!client
) set_uniquename(WvString(":%s.0", conncount
));
147 // this will get enqueued until later, but we want to make sure it
148 // comes before anything the user tries to send - including anything
149 // goofy they enqueue in the authorization part.
156 WvDBusConn::~WvDBusConn()
158 log("Shutting down.\n");
160 log("Error was: %s\n", errstr());
168 void WvDBusConn::close()
172 WvStreamClone::close();
176 WvString
WvDBusConn::uniquename() const
182 void WvDBusConn::request_name(WvStringParm name
, const WvDBusCallback
&onreply
,
185 uint32_t flags
= (DBUS_NAME_FLAG_ALLOW_REPLACEMENT
|
186 DBUS_NAME_FLAG_REPLACE_EXISTING
);
187 WvDBusMsg
msg("org.freedesktop.DBus", "/org/freedesktop/DBus",
188 "org.freedesktop.DBus", "RequestName");
189 msg
.append(name
).append(flags
);
190 send(msg
, onreply
, msec_timeout
);
194 uint32_t WvDBusConn::send(WvDBusMsg msg
)
196 msg
.marshal(out_queue
);
199 log(" >> %s\n", msg
);
203 log(" .> %s\n", msg
);
204 return msg
.get_serial();
208 void WvDBusConn::send(WvDBusMsg msg
, const WvDBusCallback
&onreply
,
213 add_pending(msg
, onreply
, msec_timeout
);
226 bool reply_wait(WvDBusMsg
&msg
)
227 { reply
= new WvDBusMsg(msg
); return true; }
231 WvDBusMsg
WvDBusConn::send_and_wait(WvDBusMsg msg
, time_t msec_timeout
,
232 wv::function
<void(uint32_t)> serial_cb
)
236 send(msg
, wv::bind(&xxReplyWaiter::reply_wait
, &rw
, _1
),
239 serial_cb(msg
.get_serial());
240 while (!rw
.reply
&& isok())
243 return WvDBusError(msg
, DBUS_ERROR_FAILED
,
244 WvString("Connection closed (%s) "
245 "while waiting for reply.",
252 void WvDBusConn::out(WvStringParm s
)
259 const char *WvDBusConn::in()
261 const char *s
= trim_string(getline(0));
268 void WvDBusConn::send_hello()
270 WvDBusMsg
msg("org.freedesktop.DBus", "/org/freedesktop/DBus",
271 "org.freedesktop.DBus", "Hello");
272 send(msg
, wv::bind(&WvDBusConn::_registered
, this, _1
));
273 WvDBusMsg
msg2("org.freedesktop.DBus", "/org/freedesktop/DBus",
274 "org.freedesktop.DBus", "AddMatch");
275 msg2
.append("type='signal'");
276 send(msg2
); // don't need to monitor this for completion
280 void WvDBusConn::set_uniquename(WvStringParm s
)
282 // we want to print the message before switching log.app, so that we
283 // can trace which log.app turned into which
284 log("Assigned name '%s'\n", s
);
286 log
.app
= WvString("DBus %s%s", client
? "" : "s", uniquename());
290 void WvDBusConn::try_auth()
292 bool done
= auth
->authorize(*this);
295 // ready to send messages!
296 if (out_queue
.used())
298 log(" >> (sending enqueued messages)\n");
307 void WvDBusConn::add_callback(CallbackPri pri
, WvDBusCallback cb
, void *cookie
)
309 callbacks
.append(new CallbackInfo(pri
, cb
, cookie
), true);
313 void WvDBusConn::del_callback(void *cookie
)
315 // remember, there might be more than one callback with the same cookie.
316 CallbackInfoList::Iter
i(callbacks
);
317 for (i
.rewind(); i
.next(); )
318 if (i
->cookie
== cookie
)
323 int WvDBusConn::priority_order(const CallbackInfo
*a
, const CallbackInfo
*b
)
325 return a
->pri
- b
->pri
;
328 bool WvDBusConn::filter_func(WvDBusMsg
&msg
)
333 uint32_t rserial
= msg
.get_replyserial();
336 Pending
*p
= pending
[rserial
];
341 return true; // handled it
345 // handle all the generic filters
346 CallbackInfoList::Sorter
i(callbacks
, priority_order
);
347 for (i
.rewind(); i
.next(); )
349 bool handled
= i
->cb(msg
);
350 if (handled
) return true;
353 return false; // couldn't handle the message, sorry
357 WvDBusClientAuth::WvDBusClientAuth()
359 sent_request
= false;
363 wvuid_t
WvDBusClientAuth::get_uid()
369 bool WvDBusClientAuth::authorize(WvDBusConn
&c
)
374 WvString uid
= get_uid();
375 c
.out("AUTH EXTERNAL %s\r\n\0", WvHexEncoder().strflushstr(uid
));
380 const char *line
= c
.in();
383 if (!strncasecmp(line
, "OK ", 3))
388 else if (!strncasecmp(line
, "ERROR ", 6))
389 c
.seterr("Auth failed: %s", line
);
391 c
.seterr("Unknown AUTH response: '%s'", line
);
399 time_t WvDBusConn::mintimeout_msec()
402 PendingDict::Iter
i(pending
);
403 for (i
.rewind(); i
.next(); )
405 if (!when
|| when
> i
->valid_until
)
406 when
= i
->valid_until
;
410 else if (when
<= wvstime())
413 return msecdiff(when
, wvstime());
417 bool WvDBusConn::post_select(SelectInfo
&si
)
419 bool ready
= WvStreamClone::post_select(si
);
420 if (si
.inherit_request
) return ready
;
422 if (in_post_select
) return false;
423 in_post_select
= true;
425 if (!authorized
&& ready
)
428 if (!alarm_remaining())
430 WvTime now
= wvstime();
431 PendingDict::Iter
i(pending
);
432 for (i
.rewind(); i
.next(); )
434 if (now
> i
->valid_until
)
436 log("Expiring %s\n", i
->msg
);
437 expire_pending(i
.ptr());
443 if (authorized
&& ready
)
445 // put this in a loop so that wvdbusd can forward packets rapidly.
446 // Otherwise TCP_NODELAY kicks in, because we do a select() loop
447 // between packets, which causes delay_output() to flush.
452 size_t needed
= WvDBusMsg::demarshal_bytes_needed(in_queue
);
453 size_t amt
= needed
- in_queue
.used();
458 while ((m
= WvDBusMsg::demarshal(in_queue
)) != NULL
)
467 alarm(mintimeout_msec());
468 in_post_select
= false;
473 bool WvDBusConn::isidle()
475 return !out_queue
.used() && pending
.isempty();
479 void WvDBusConn::expire_pending(Pending
*p
)
483 WvDBusCallback
xcb(p
->cb
);
484 pending
.remove(p
); // prevent accidental recursion
485 WvDBusError
e(p
->msg
, DBUS_ERROR_FAILED
,
486 "Timed out while waiting for reply");
492 void WvDBusConn::cancel_pending(uint32_t serial
)
494 Pending
*p
= pending
[serial
];
497 WvDBusCallback
xcb(p
->cb
);
498 WvDBusMsg
msg(p
->msg
);
499 pending
.remove(p
); // prevent accidental recursion
500 WvDBusError
e(msg
, DBUS_ERROR_FAILED
,
501 "Canceled while waiting for reply");
507 void WvDBusConn::add_pending(WvDBusMsg
&msg
, WvDBusCallback cb
,
510 uint32_t serial
= msg
.get_serial();
513 cancel_pending(serial
);
514 pending
.add(new Pending(msg
, cb
, msec_timeout
), true);
515 alarm(mintimeout_msec());
519 bool WvDBusConn::_registered(WvDBusMsg
&msg
)
521 WvDBusMsg::Iter
i(msg
);
522 _uniquename
= i
.getnext().get_str();
523 set_uniquename(_uniquename
);