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;
27 public:
29  virtual bool authorize(WvDBusConn &c);
30 
31  virtual wvuid_t get_uid() { return client_uid; }
32 };
33 
34 
35 WvDBusServerAuth::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 =
77  WvHexDecoder().strflushstr(words.popstr());
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 
111 WvDBusServer::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 
139 bool WvDBusServer::isok() const
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 
202 bool 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 
370 bool 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 
398 bool 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 
417 bool 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 
425 void WvDBusServer::conn_closed(WvStream &s)
426 {
427  WvDBusConn *c = (WvDBusConn *)&s;
428  unregister_conn(c);
429  this->release();
430 }
431 
432 
433 void 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 }
IWvStreamCallback setclosecallback(IWvStreamCallback _callback)
Sets a callback to be invoked on close().
Definition: wvstream.cc:1174
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Definition: wvstring.h:93
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
void split(WvStringParm s, const char *splitchars=" \\, int limit=0)
split s and form a list ignoring splitchars (except at beginning and end) ie.
Definition: wvstringlist.cc:19
int num() const
Return a stdc++ string with the contents of this string.
Definition: wvstring.h:286
WvString get_addr()
get the full, final address (identification guid and all) of the server if there&#39;s more than one list...
virtual bool isok() const
return true if the stream is actually usable right now
WvString popstr()
get the first string in the list, or an empty string if the list is empty.
Definition: wvstringlist.cc:55
bool isnull() const
returns true if this string is null
Definition: wvstring.h:290
WvString get_fingerprint(const FprintMode mode=FingerSHA1) const
Get the certHash (fingerprint) of the certificate.
Definition: wvx509.cc:1416
const char * cstr() const
return a (const char *) for this string.
Definition: wvstring.h:267
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
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
void listen(WvStringParm moniker)
Listen using a given WvListener moniker.
X509 Class to handle certificates and their related functions.
Definition: wvx509.h:41
uint32_t send(WvDBusMsg msg)
Send a message on the bus, not expecting any reply.
Definition: wvdbusconn.cc:194
Unified support for streams, that is, sequences of bytes that may or may not be ready for read/write ...
Definition: wvstream.h:24
virtual IWvListenerCallback onaccept(IWvListenerCallback _cb)=0
Set a user-defined function to be called when a new connection is available.
virtual ~WvDBusServer()
Shut down this server.
WvDBusMsg & append(const char *s)
The following methods are designed to allow appending various arguments to the message.
Definition: wvdbusmsg.cc:461
Iter & getnext()
Same as next(), but returns *this instead so you can convert the new item to the right value type...
Definition: wvdbusmsg.h:222
void send(WvDBusConn &conn)
A shortcut for sending this message on the given connection.
Definition: wvdbusmsg.cc:641
virtual bool isok() const
return true if the stream is actually usable right now
virtual bool authorize(WvDBusConn &c)
Main action callback.
Definition: wvdbusserver.cc:42
WvString strflushstr(WvStringParm instr, bool finish=false)
Flushes data through the encoder from a string to a string.
Definition: wvencoder.cc:107
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
void unregister_conn(WvDBusConn *conn)
Forget all name registrations for a particular connection.
A hex decoder.
Definition: wvhex.h:53
void register_name(WvStringParm name, WvDBusConn *conn)
Register a given dbus service name as belonging to a particular connection.
WvString uniquename() const
Return this connection&#39;s unique name on the bus, assigned by the server at connect time...
Definition: wvdbusconn.cc:176
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition: wvstream.cc:451
virtual bool isok() const =0
By default, returns true if geterr() == 0.
Various little string functions.
WvDBusMsg reply()
Generate a message that will be a reply to this one.
Definition: wvdbusmsg.cc:629
void unregister_name(WvStringParm name, WvDBusConn *conn)
Undo a register_name().
virtual unsigned int addRef()=0
Indicate you are using this object.
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
virtual int geterr() const
If isok() is false, return the system error number corresponding to the error, -1 for a special error...
virtual size_t read(void *buf, size_t count)
read a data block on the stream.
Definition: wvstream.cc:490