WvStreams
wvdbusserver.cc
00001 /* -*- Mode: C++ -*-
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 2005-2006 Net Integration Technologies, Inc.
00004  * 
00005  * Pathfinder Software:
00006  *   Copyright (C) 2007, Carillon Information Security Inc.
00007  *
00008  * This library is licensed under the LGPL, please read LICENSE for details.
00009  *
00010  */ 
00011 #include "wvdbusserver.h"
00012 #include "wvdbusconn.h"
00013 #include "wvstrutils.h"
00014 #include "wvuid.h"
00015 #include "wvtcplistener.h"
00016 #include "wvdelayedcallback.h"
00017 #undef interface // windows
00018 #include <dbus/dbus.h>
00019 #include "wvx509.h"
00020 
00021 
00022 class WvDBusServerAuth : public IWvDBusAuth
00023 {
00024     enum State { NullWait, AuthWait, BeginWait };
00025     State state;
00026     wvuid_t client_uid;
00027 public:
00028     WvDBusServerAuth();
00029     virtual bool authorize(WvDBusConn &c);
00030 
00031     virtual wvuid_t get_uid() { return client_uid; }
00032 };
00033 
00034 
00035 WvDBusServerAuth::WvDBusServerAuth()
00036 {
00037     state = NullWait;
00038     client_uid = WVUID_INVALID;
00039 }
00040 
00041 
00042 bool WvDBusServerAuth::authorize(WvDBusConn &c)
00043 {
00044     c.log("State=%s\n", state);
00045     if (state == NullWait)
00046     {
00047         char buf[1];
00048         size_t len = c.read(buf, 1);
00049         if (len == 1 && buf[0] == '\0')
00050         {
00051             state = AuthWait;
00052             // fall through
00053         }
00054         else if (len > 0)
00055             c.seterr("Client didn't start with NUL byte");
00056         else
00057             return false; // no data yet, come back later
00058     }
00059     
00060     const char *line = c.in();
00061     if (!line)
00062         return false; // not done yet
00063     
00064     WvStringList words;
00065     words.split(line);
00066     WvString cmd(words.popstr());
00067     
00068     if (state == AuthWait)
00069     {
00070         if (!strcasecmp(cmd, "AUTH"))
00071         {
00072             // FIXME actually check authentication information!
00073             WvString typ(words.popstr());
00074             if (!strcasecmp(typ, "EXTERNAL"))
00075             {
00076                 WvString uid = 
00077                     WvHexDecoder().strflushstr(words.popstr());
00078                 if (!!uid)
00079                 {
00080                     // FIXME: Check that client is on the same machine!
00081                     client_uid = uid.num();
00082                 }
00083                 
00084                 state = BeginWait;
00085                 c.out("OK f00f\r\n");
00086             }
00087             else
00088             {
00089                 // Some clients insist that we reject something because
00090                 // their state machine can't handle us accepting just the
00091                 // "AUTH " command.
00092                 c.out("REJECTED EXTERNAL\r\n");
00093                 // no change in state
00094             }
00095         }
00096         else
00097             c.seterr("AUTH command expected: '%s'", line);
00098     }
00099     else if (state == BeginWait)
00100     {
00101         if (!strcasecmp(cmd, "BEGIN"))
00102             return true; // done
00103         else
00104             c.seterr("BEGIN command expected: '%s'", line);
00105     }
00106 
00107     return false;
00108 }
00109 
00110 
00111 WvDBusServer::WvDBusServer()
00112     : log("DBus Server", WvLog::Debug)
00113 {
00114     // user must now call listen() at least once.
00115     add(&listeners, false, "listeners");
00116 }
00117 
00118 
00119 WvDBusServer::~WvDBusServer()
00120 {
00121     close();
00122     zap();
00123 }
00124 
00125 
00126 void WvDBusServer::listen(WvStringParm moniker)
00127 {
00128     IWvListener *listener = IWvListener::create(moniker);
00129     log(WvLog::Info, "Listening on '%s'\n", *listener->src());
00130     if (!listener->isok())
00131         log(WvLog::Info, "Can't listen: %s\n",
00132             listener->errstr());
00133     listener->onaccept(wv::bind(&WvDBusServer::new_connection_cb,
00134                                 this, _1));
00135     listeners.add(listener, true, "listener");
00136 }
00137 
00138 
00139 bool WvDBusServer::isok() const
00140 {
00141     if (geterr())
00142         return false;
00143     
00144     WvIStreamList::Iter i(listeners);
00145     for (i.rewind(); i.next(); )
00146         if (!i->isok())
00147             return false;
00148     return WvIStreamList::isok();
00149 }
00150 
00151 
00152 int WvDBusServer::geterr() const
00153 {
00154     return WvIStreamList::geterr();
00155 }
00156 
00157 
00158 WvString WvDBusServer::get_addr()
00159 {
00160     // FIXME assumes tcp
00161     WvIStreamList::Iter i(listeners);
00162     for (i.rewind(); i.next(); )
00163         if (i->isok())
00164             return WvString("tcp:%s", *i->src());
00165     return WvString();
00166 }
00167 
00168 
00169 void WvDBusServer::register_name(WvStringParm name, WvDBusConn *conn)
00170 {
00171     name_to_conn[name] = conn;
00172 }
00173 
00174 
00175 void WvDBusServer::unregister_name(WvStringParm name, WvDBusConn *conn)
00176 {
00177     assert(name_to_conn[name] == conn);
00178     name_to_conn.erase(name);
00179 }
00180 
00181 
00182 void WvDBusServer::unregister_conn(WvDBusConn *conn)
00183 {
00184     {
00185         std::map<WvString,WvDBusConn*>::iterator i;
00186         for (i = name_to_conn.begin(); i != name_to_conn.end(); )
00187         {
00188             if (i->second == conn)
00189             {
00190                 name_to_conn.erase(i->first);
00191                 i = name_to_conn.begin();
00192             }
00193             else
00194                 ++i;
00195         }
00196     }
00197     
00198     all_conns.unlink(conn);
00199 }
00200 
00201 
00202 bool WvDBusServer::do_server_msg(WvDBusConn &conn, WvDBusMsg &msg)
00203 {
00204     WvString method(msg.get_member());
00205     
00206     if (msg.get_path() == "/org/freedesktop/DBus/Local")
00207     {
00208         if (method == "Disconnected")
00209             return true; // nothing to do until their *stream* disconnects
00210     }
00211     
00212     if (msg.get_dest() != "org.freedesktop.DBus") return false;
00213 
00214     // dbus-daemon seems to ignore the path as long as the service is right
00215     //if (msg.get_path() != "/org/freedesktop/DBus") return false;
00216     
00217     // I guess it's for us!
00218     
00219     if (method == "Hello")
00220     {
00221         log("hello_cb\n");
00222         msg.reply().append(conn.uniquename()).send(conn);
00223         return true;
00224     }
00225     else if (method == "RequestName")
00226     {
00227         WvDBusMsg::Iter args(msg);
00228         WvString _name = args.getnext();
00229         // uint32_t flags = args.getnext(); // supplied, but ignored
00230         
00231         log("request_name_cb(%s)\n", _name);
00232         register_name(_name, &conn);
00233         
00234         msg.reply().append((uint32_t)DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
00235             .send(conn);
00236         return true;
00237     }
00238     else if (method == "ReleaseName")
00239     {
00240         WvDBusMsg::Iter args(msg);
00241         WvString _name = args.getnext();
00242         
00243         log("release_name_cb(%s)\n", _name);
00244         unregister_name(_name, &conn);
00245         
00246         msg.reply().append((uint32_t)DBUS_RELEASE_NAME_REPLY_RELEASED)
00247             .send(conn);
00248         return true;
00249     }
00250     else if (method == "NameHasOwner")
00251     {
00252         WvDBusMsg::Iter args(msg);
00253         WvString known_name = args.getnext();
00254         WvDBusConn *serv = name_to_conn[known_name];
00255         msg.reply().append(!!serv).send(conn);
00256         return true;
00257     }
00258     else if (method == "GetNameOwner")
00259     {
00260         WvDBusMsg::Iter args(msg);
00261         WvString known_name = args.getnext();
00262         WvDBusConn *serv = name_to_conn[known_name];
00263         if (serv)
00264             msg.reply().append(serv->uniquename()).send(conn);
00265         else
00266             WvDBusError(msg, "org.freedesktop.DBus.Error.NameHasNoOwner", 
00267                         "No match for name '%s'", known_name).send(conn);
00268         return true;
00269     }
00270     else if (method == "AddMatch")
00271     {
00272         // we just proxy every signal to everyone for now
00273         msg.reply().send(conn);
00274         return true;
00275     }
00276     else if (method == "StartServiceByName")
00277     {
00278         // we don't actually support this, but returning an error message
00279         // confuses perl's Net::DBus library, at least.
00280         msg.reply().send(conn);
00281         return true;
00282     }
00283     else if (method == "GetConnectionUnixUser" || 
00284              method == "GetConnectionUnixUserName")
00285     {
00286         WvDBusMsg::Iter args(msg);
00287         WvString _name = args.getnext();
00288         WvDBusConn *target = name_to_conn[_name];
00289         
00290         if (!target)
00291         {
00292             WvDBusError(msg, "org.freedesktop.DBus.Error.Failed", 
00293                 "No connection found for name '%s'.", _name).send(conn);
00294             return true;
00295         }
00296         
00297         wvuid_t client_uid = target->get_uid();
00298         
00299         if (client_uid == WVUID_INVALID)
00300         {
00301             WvDBusError(msg, "org.freedesktop.DBus.Error.Failed", 
00302                         "No user associated with connection '%s'.",
00303                         target->uniquename()).send(conn);
00304             return true;
00305         }
00306         
00307         log("Found unix user for '%s', uid is %s.\n", _name, client_uid);
00308         
00309         if (method == "GetConnectionUnixUser")
00310         {
00311             WvString s(client_uid);
00312             msg.reply().append((uint32_t)atoll(s)).send(conn);
00313             return true;
00314         }
00315         else if (method == "GetConnectionUnixUserName")
00316         {
00317             WvString username = wv_username_from_uid(client_uid);
00318             if (!username)
00319             {
00320                 WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
00321                             "No username for uid='%s'", client_uid)
00322                     .send(conn);
00323                 return true;
00324             }
00325             
00326             msg.reply().append(username).send(conn);
00327             return true;
00328         }
00329         else
00330             assert(false); // should never happen
00331             
00332         assert(false);
00333     }
00334     else if (method == "GetConnectionCert" ||
00335             method == "GetConnectionCertFingerprint")
00336     {
00337         WvDBusMsg::Iter args(msg);
00338         WvString connid = args.getnext();
00339 
00340         WvDBusConn *c = name_to_conn[connid];
00341 
00342         WvString ret = c ? c->getattr("peercert") : WvString::null;
00343         if (ret.isnull())
00344             WvDBusError(msg, "org.freedesktop.DBus.Error.Failed",
00345                             "Connection %s did not present a certificate",
00346                             connid).send(conn);
00347         else
00348         {
00349             if (method == "GetConnectionCertFingerprint") 
00350             {
00351                 WvX509 tempcert;
00352                 // We can assume it's valid because our SSL conn authenticated
00353                 tempcert.decode(WvX509::CertPEM, ret);
00354                 ret = tempcert.get_fingerprint();
00355             }
00356             msg.reply().append(ret).send(conn);
00357         }
00358 
00359         return true;
00360     }
00361     else
00362     {
00363         WvDBusError(msg, "org.freedesktop.DBus.Error.UnknownMethod", 
00364                     "Unknown dbus method '%s'", method).send(conn);
00365         return true; // but we've handled it, since it belongs to us
00366     }
00367 }
00368 
00369 
00370 bool WvDBusServer::do_bridge_msg(WvDBusConn &conn, WvDBusMsg &msg)
00371 {
00372     // if we get here, nobody handled the message internally, so we can try
00373     // to proxy it.
00374     if (!!msg.get_dest()) // don't handle blank (broadcast) paths here
00375     {
00376         std::map<WvString,WvDBusConn*>::iterator i 
00377             = name_to_conn.find(msg.get_dest());
00378         WvDBusConn *dconn = (i == name_to_conn.end()) ? NULL : i->second;
00379         log("Proxying #%s -> %s\n",
00380             msg.get_serial(),
00381             dconn ? dconn->uniquename() : WvString("(UNKNOWN)"));
00382         dbus_message_set_sender(msg, conn.uniquename().cstr());
00383         if (dconn)
00384             dconn->send(msg);
00385         else
00386         {
00387             log(WvLog::Warning,
00388                 "Proxy: no connection for '%s'\n", msg.get_dest());
00389             return false;
00390         }
00391         return true;
00392     }
00393     
00394     return false;
00395 }
00396 
00397 
00398 bool WvDBusServer::do_broadcast_msg(WvDBusConn &conn, WvDBusMsg &msg)
00399 {
00400     if (!msg.get_dest())
00401     {
00402         log("Broadcasting #%s\n", msg.get_serial());
00403         
00404         // note: we broadcast messages even back to the connection where
00405         // they originated.  I'm not sure this is necessarily ideal, but if
00406         // you don't do that then an app can't signal objects that might be
00407         // inside itself.
00408         WvDBusConnList::Iter i(all_conns);
00409         for (i.rewind(); i.next(); )
00410             i->send(msg);
00411         return true;
00412     }
00413     return false;
00414 }
00415 
00416 
00417 bool WvDBusServer::do_gaveup_msg(WvDBusConn &conn, WvDBusMsg &msg)
00418 {
00419     WvDBusError(msg, "org.freedesktop.DBus.Error.NameHasNoOwner", 
00420                 "No running service named '%s'", msg.get_dest()).send(conn);
00421     return true;
00422 }
00423 
00424 
00425 void WvDBusServer::conn_closed(WvStream &s)
00426 {
00427     WvDBusConn *c = (WvDBusConn *)&s;
00428     unregister_conn(c);
00429     this->release();
00430 }
00431 
00432 
00433 void WvDBusServer::new_connection_cb(IWvStream *s)
00434 {
00435     WvDBusConn *c = new WvDBusConn(s, new WvDBusServerAuth, false);
00436     c->addRef();
00437     this->addRef();
00438     all_conns.append(c, true);
00439     register_name(c->uniquename(), c);
00440 
00441     /* The delayed callback here should be explained.  The
00442      * 'do_broadcast_msg' function sends out data along all connections.
00443      * Unfortunately, this is a prime time to figure out a connection died.
00444      * A dying connection is removed from the all_conns list... but we are
00445      * still in do_broadcast_msg, and using an iterator to go over this list.
00446      * The consequences of this were not pleasant, at best.  Wrapping cb in a
00447      * delayedcallback will always safely remove a connection.
00448      */
00449     IWvStreamCallback mycb = wv::bind(&WvDBusServer::conn_closed, this,
00450                                  wv::ref(*c));
00451     c->setclosecallback(wv::delayed(mycb));
00452 
00453     c->add_callback(WvDBusConn::PriSystem,
00454                     wv::bind(&WvDBusServer::do_server_msg, this,
00455                              wv::ref(*c), _1));
00456     c->add_callback(WvDBusConn::PriBridge, 
00457                     wv::bind(&WvDBusServer::do_bridge_msg, this,
00458                              wv::ref(*c), _1));
00459     c->add_callback(WvDBusConn::PriBroadcast,
00460                     wv::bind(&WvDBusServer::do_broadcast_msg, this,
00461                              wv::ref(*c), _1));
00462     c->add_callback(WvDBusConn::PriGaveUp,
00463                     wv::bind(&WvDBusServer::do_gaveup_msg, this,
00464                              wv::ref(*c), _1));
00465     
00466     append(c, true, "wvdbus servconn");
00467 }