WvStreams
|
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 }