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 
126 void WvDBusServer::listen(WvStringParm moniker)
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 
169 void WvDBusServer::register_name(WvStringParm name, WvDBusConn *conn)
170 {
171  name_to_conn[name] = conn;
172 }
173 
174 
175 void WvDBusServer::unregister_name(WvStringParm name, WvDBusConn *conn)
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 }
WvDBusMsg::send
void send(WvDBusConn &conn)
A shortcut for sending this message on the given connection.
Definition: wvdbusmsg.cc:641
WvX509::get_fingerprint
WvString get_fingerprint(const FprintMode mode=FingerSHA1) const
Get the certHash (fingerprint) of the certificate.
Definition: wvx509.cc:1416
WvErrorBase::geterr
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
WvDBusServer::isok
virtual bool isok() const
return true if the stream is actually usable right now
Definition: wvdbusserver.cc:139
WvStringList::popstr
WvString popstr()
get the first string in the list, or an empty string if the list is empty.
Definition: wvstringlist.cc:55
WvDBusConn::add_callback
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
WvDBusServer::unregister_conn
void unregister_conn(WvDBusConn *conn)
Forget all name registrations for a particular connection.
Definition: wvdbusserver.cc:182
WvDBusServer::register_name
void register_name(WvStringParm name, WvDBusConn *conn)
Register a given dbus service name as belonging to a particular connection.
Definition: wvdbusserver.cc:169
WvX509
X509 Class to handle certificates and their related functions.
Definition: wvx509.h:41
WvX509::decode
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
WvDBusConn::send
uint32_t send(WvDBusMsg msg)
Send a message on the bus, not expecting any reply.
Definition: wvdbusconn.cc:194
WvDBusServerAuth
Definition: wvdbusserver.cc:22
WvDBusError
Definition: wvdbusmsg.h:299
WvDBusMsg::append
WvDBusMsg & append(const char *s)
The following methods are designed to allow appending various arguments to the message.
Definition: wvdbusmsg.cc:461
IWvDBusAuth
Definition: wvdbusconn.h:34
WvDBusMsg::reply
WvDBusMsg reply()
Generate a message that will be a reply to this one.
Definition: wvdbusmsg.cc:629
IWvStream
Definition: iwvstream.h:24
WvEncoder::strflushstr
WvString strflushstr(WvStringParm instr, bool finish=false)
Flushes data through the encoder from a string to a string.
Definition: wvencoder.cc:107
IWvListener
Definition: iwvlistener.h:16
WvDBusServer::unregister_name
void unregister_name(WvStringParm name, WvDBusConn *conn)
Undo a register_name().
Definition: wvdbusserver.cc:175
WvDBusServer::listen
void listen(WvStringParm moniker)
Listen using a given WvListener moniker.
Definition: wvdbusserver.cc:126
WvDBusConn::uniquename
WvString uniquename() const
Return this connection's unique name on the bus, assigned by the server at connect time.
Definition: wvdbusconn.cc:176
WvStream::close
virtual void close()
Close the stream if it is open; isok() becomes false from now on.
Definition: wvstream.cc:341
WvDBusMsg
Definition: wvdbusmsg.h:28
WvStream::seterr
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition: wvstream.cc:451
WvDBusServer::~WvDBusServer
virtual ~WvDBusServer()
Shut down this server.
Definition: wvdbusserver.cc:119
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvLog
A WvLog stream accepts log messages from applications and forwards them to all registered WvLogRcv's.
Definition: wvlog.h:56
WvDBusMsg::Iter
Definition: wvdbusmsg.h:176
WvFastString::isnull
bool isnull() const
returns true if this string is null
Definition: wvstring.h:290
wvstrutils.h
WvFastString::cstr
const char * cstr() const
return a (const char *) for this string.
Definition: wvstring.h:267
IObject::release
virtual unsigned int release()=0
Indicate that you are finished using this object.
WvStream::read
virtual size_t read(void *buf, size_t count)
read a data block on the stream.
Definition: wvstream.cc:490
IWvListener::onaccept
virtual IWvListenerCallback onaccept(IWvListenerCallback _cb)=0
Set a user-defined function to be called when a new connection is available.
IWvStream::isok
virtual bool isok() const =0
By default, returns true if geterr() == 0.
WvHexDecoder
A hex decoder.
Definition: wvhex.h:53
WvStream
Unified support for streams, that is, sequences of bytes that may or may not be ready for read/write ...
Definition: wvstream.h:24
WvDBusServer::get_addr
WvString get_addr()
get the full, final address (identification guid and all) of the server if there's more than one list...
Definition: wvdbusserver.cc:158
WvIStreamList::isok
virtual bool isok() const
return true if the stream is actually usable right now
Definition: wvistreamlist.cc:65
WvStream::setclosecallback
IWvStreamCallback setclosecallback(IWvStreamCallback _callback)
Sets a callback to be invoked on close().
Definition: wvstream.cc:1175
WvDBusServer::geterr
virtual int geterr() const
If isok() is false, return the system error number corresponding to the error, -1 for a special error...
Definition: wvdbusserver.cc:152
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
WvStringList::split
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
IObject::addRef
virtual unsigned int addRef()=0
Indicate you are using this object.
WvFastString::num
int num() const
Return a stdc++ string with the contents of this string.
Definition: wvstring.h:286
WvDBusServerAuth::authorize
virtual bool authorize(WvDBusConn &c)
Main action callback.
Definition: wvdbusserver.cc:42
WvDBusConn
Definition: wvdbusconn.h:65