00001 /* 00002 !concept {:name => "QXmpp and Lua based remote control mechanism"} 00003 */ 00004 00005 #include "rk_remokon.h" 00006 #include "rk_remokon_qt.hpp" 00007 00008 #include "ac_app_context.h" 00009 #include "cf_query.h" // get_ConfigDb_int 00010 #include "er_errors.h" 00011 00012 #include "QXmppLogger.h" 00013 00014 #include <stdlib.h> 00015 00016 // -------------------------------------------------- 00017 // utilities 00018 // -------------------------------------------------- 00019 00020 #define TRAPFATAL(_stm) { \ 00021 try { \ 00022 _stm ; \ 00023 } catch (const std::exception &ex) { \ 00024 er_log_none(er_FATAL, "remokon: %s", ex.what()); \ 00025 } \ 00026 } 00027 00028 #define GTRAP(_ret, _stm) { \ 00029 try { \ 00030 _stm ; \ 00031 } catch (const std::exception &ex) { \ 00032 if (error) \ 00033 *error = gx_error_new(domain_qt, -1, \ 00034 "remokon: %s", ex.what()); \ 00035 return _ret; \ 00036 } \ 00037 } 00038 00039 // -------------------------------------------------- 00040 // _rk_Remokon 00041 // -------------------------------------------------- 00042 00043 static lua_State* new_my_lua() 00044 { 00045 lua_State* L = cl_lua_new_libs(); 00046 if (!L) { 00047 throw std::bad_alloc(); 00048 } 00049 return L; 00050 } 00051 00052 _rk_Remokon::_rk_Remokon() : 00053 iIsActive(false), L(new_my_lua()) 00054 { 00055 this->params.server = ac_STATIC_GET(remokon_host); 00056 this->params.port = ac_STATIC_GET(remokon_port); 00057 this->params.password = ac_STATIC_GET(remokon_password); 00058 this->params.jid = ac_STATIC_GET(jid); 00059 #if defined(__SYMBIAN32__) 00060 this->params.iap_id = get_config_iap_id(); 00061 #endif /* __SYMBIAN32__ */ 00062 00063 this->iHaveConfig = ((this->params.server != NULL) && 00064 (this->params.password != NULL) && 00065 (this->params.jid != NULL)); 00066 00067 this->iAutostartEnabled = (force_get_ConfigDb_bool("remokon.autostart", FALSE)); 00068 00069 if (this->iHaveConfig) { 00070 logg("Jabber config: server %s:%d, jid '%s', auto %d", 00071 this->params.server, this->params.port, 00072 this->params.jid, 00073 this->iAutostartEnabled); 00074 00075 iXmppConfiguration.setHost(params.server); 00076 iXmppConfiguration.setPort(params.port); 00077 iXmppConfiguration.setJid(params.jid); 00078 iXmppConfiguration.setPassword(params.password); 00079 } 00080 00081 iXmppPresence.setType(QXmppPresence::Available); 00082 QXmppPresence::Status& stat = iXmppPresence.status(); 00083 stat.setType(QXmppPresence::Status::Chat); 00084 #if defined(__SYMBIAN32__) 00085 stat.setStatusText(QString("Logging away on Symbian.")); 00086 #else 00087 stat.setStatusText(QString("Logging away on a PC.")); 00088 #endif /* __SYMBIAN32__ */ 00089 00090 // Desired security policy. We are strict on Symbian, since there we 00091 // assume the required certificate must have been globally 00092 // installed. For testing on the desktop we do not require that, nor 00093 // do we know if QXmpp allows for custom CA configuration. 00094 #if defined(__SYMBIAN32__) 00095 iXmppConfiguration.setIgnoreSslErrors(false); 00096 iXmppConfiguration.setUseSASLAuthentication(true); 00097 iXmppConfiguration.setStreamSecurityMode(QXmppConfiguration::TLSRequired); 00098 iXmppConfiguration.setSASLAuthMechanism(QXmppConfiguration::SASLDigestMD5); 00099 #endif /* __SYMBIAN32__ */ 00100 00101 // Note that "connect" produces a boolean return value if you want 00102 // to check. 00103 connect(&iSession, SIGNAL(error(QXmppClient::Error)), 00104 this, SLOT(gotJabberError(QXmppClient::Error))); 00105 connect(&iSession, SIGNAL(messageReceived(const QXmppMessage&)), 00106 this, SLOT(gotJabberMessage(const QXmppMessage&))); 00107 00108 iRunTimer.setSingleShot(true); 00109 connect(&iRunTimer, SIGNAL(timeout()), 00110 this, SLOT(runTimeout())); 00111 } 00112 00113 _rk_Remokon::~_rk_Remokon() 00114 { 00115 stop(); 00116 if (L) 00117 lua_close(L); 00118 } 00119 00120 void _rk_Remokon::start() 00121 { 00122 logh(); 00123 if (!iIsActive) { 00124 iSession.connectToServer(iXmppConfiguration, iXmppPresence); 00125 iIsActive = true; 00126 } 00127 } 00128 00129 void _rk_Remokon::stop() 00130 { 00131 if (iIsActive) { 00132 iSession.disconnectFromServer(); 00133 iIsActive = false; 00134 } 00135 } 00136 00137 static int SecsToMsecs(int secs) 00138 { 00139 long long ms64 = (long long)(secs) * 1000LL; 00140 if (ms64 > 0x7fffffffLL) ms64 = 0x7fffffffLL; 00141 return (int)ms64; 00142 } 00143 00144 void _rk_Remokon::resetRunTimer() 00145 { 00146 // It is okay to call "start" even when "isActive". 00147 iRunTimer.start(SecsToMsecs(iRunForSecs)); 00148 } 00149 00150 // If already running (in timed mode or otherwise), will be put into 00151 // timed mode, with the specified time period. This also resets any 00152 // existing countdown timer. 00153 void _rk_Remokon::startTimed(int secs) 00154 { 00155 logh(); 00156 if (secs <= 0) 00157 return; 00158 iRunForSecs = secs; 00159 resetRunTimer(); 00160 if (!iIsActive) 00161 start(); 00162 } 00163 00164 void _rk_Remokon::runTimeout() 00165 { 00166 log_db_log_status(ac_global_LogDb, NULL, 00167 "remokon: stopping after %d secs", iRunForSecs); 00168 TRAPFATAL(stop()); 00169 } 00170 00171 void _rk_Remokon::send(const QString& toJid, const QString& msgText) 00172 { 00173 iSession.sendMessage(toJid, msgText); 00174 } 00175 00176 // Not sure yet if we need to handle any of these errors. The client 00177 // object is supposed to itself do some retrying. For now we just log. 00178 void _rk_Remokon::gotJabberError(QXmppClient::Error anError) 00179 { 00180 switch (anError) 00181 { 00182 case QXmppClient::SocketError: 00183 { 00184 // Unfortunately we cannot use errorString() on the socket 00185 // since the API does not give us a handle to the socket. 00186 QAbstractSocket::SocketError error = iSession.socketError(); 00187 er_log_none(0, "remokon: %d (%s) %d", 00188 anError, "QXmppClient::SocketError", error); 00189 break; 00190 } 00191 case QXmppClient::KeepAliveError: 00192 { 00193 er_log_none(0, "remokon: %d (%s)", 00194 anError, "QXmppClient::KeepAliveError"); 00195 break; 00196 } 00197 case QXmppClient::XmppStreamError: 00198 { 00199 QXmppStanza::Error::Condition error = iSession.xmppStreamError(); 00200 er_log_none(0, "remokon: %d (%s) %d", 00201 anError, "QXmppClient::XmppStreamError", error); 00202 break; 00203 } 00204 default: 00205 { 00206 er_log_none(0, "remokon: %d (unknown QXmpp error)", anError); 00207 break; 00208 } 00209 } 00210 } 00211 00212 class LuaStack 00213 { 00214 private: 00215 lua_State* L; 00216 public: 00217 int pop; 00218 public: 00219 LuaStack(lua_State* aL) : L(aL), pop(0) {} 00220 ~LuaStack() { 00221 if (pop) { 00222 lua_pop(L, pop); 00223 //logg("remokon Lua stack: popped %d", pop); 00224 } 00225 } 00226 }; 00227 00228 #define TOCSTR(exp) ((exp).toUtf8().data()) 00229 00230 void _rk_Remokon::gotJabberMessage(const QXmppMessage& msg) 00231 { 00232 if (msg.body().isEmpty()) 00233 return; 00234 00235 resetRunTimer(); 00236 00237 QByteArray luaStrBa = msg.body().toUtf8(); 00238 const char* luaStr = luaStrBa.data(); 00239 logg("remote message from '%s': '%s'", 00240 TOCSTR(msg.from()), 00241 luaStr); 00242 00243 // If set, points to a literal or something within Lua state. 00244 const gchar* replyText = NULL; 00245 00246 LuaStack stack(L); 00247 int level = lua_gettop(L); 00248 int& pop = stack.pop; 00249 00250 int res = (luaL_loadstring(L, luaStr) || lua_pcall(L, 0, LUA_MULTRET, 0)); 00251 if (res != 0) { 00252 pop = 1; // expecting error message only 00253 assert(pop == (lua_gettop(L) - level)); 00254 assert(lua_isstring(L, -1)); 00255 replyText = lua_tostring(L, -1); 00256 if (!replyText) replyText = "Error: out of memory"; 00257 goto reply; 00258 } 00259 00260 // Now we may have any number of values, and not all of them 00261 // necessarily strings. 00262 // 00263 // Might be nice to get any values printed to string output and 00264 // concatenated, upto certain max length. Lua does have a "print" 00265 // function, but seems to use printf directly, so probably would 00266 // require a bit of work to have it use some safe sprintf instead. 00267 // Some macro magic in print.c could work. 00268 pop = lua_gettop(L) - level; 00269 logg("nresults is %d", pop); 00270 if (pop > 0) { 00271 if (pop > 1) { 00272 replyText = "<multiple results>"; 00273 } else { 00274 // Stored within Lua state at least until the corresponding value is popped. 00275 replyText = lua_tostring(L, -1); 00276 if (!replyText) replyText = "<unconvertible expression>"; 00277 } 00278 goto reply; 00279 } 00280 00281 // Evaluated to nothing. 00282 replyText = "OK"; 00283 00284 reply: 00285 assert(replyText); 00286 send(msg.from(), QString(replyText)); 00287 } 00288 00289 // -------------------------------------------------- 00290 // public API 00291 // -------------------------------------------------- 00292 00293 extern "C" 00294 rk_Remokon* rk_Remokon_new(GError** error) 00295 { 00296 #if !defined(__SYMBIAN32__) && !defined(NDEBUG) 00297 QXmppLogger::getLogger()->setLoggingType(QXmppLogger::StdoutLogging); 00298 #endif 00299 00300 rk_Remokon* self = NULL; 00301 GTRAP(NULL, self = q_check_ptr(new rk_Remokon)); 00302 return self; 00303 } 00304 00305 // Supports only partially initialized objects. 00306 extern "C" 00307 void rk_Remokon_destroy(rk_Remokon* self) 00308 { 00309 delete self; 00310 } 00311 00312 extern "C" 00313 gboolean rk_Remokon_is_autostart_enabled(rk_Remokon* self) 00314 { 00315 // Former value is constant, the latter may vary. 00316 return (self->iHaveConfig && self->iAutostartEnabled); 00317 } 00318 00319 #define no_config_error \ 00320 gx_error_new(domain_cl2app, code_no_configuration, \ 00321 "some Jabber config missing") 00322 00323 // Does nothing if already started. 00324 extern "C" 00325 gboolean rk_Remokon_start(rk_Remokon* self, GError** error) 00326 { 00327 if (!self->iHaveConfig) { 00328 // No point in trying to start without proper configuration. 00329 if (error) 00330 *error = no_config_error; 00331 return FALSE; 00332 } 00333 00334 if (!rk_Remokon_is_started(self)) { 00335 GTRAP(FALSE, self->start()); 00336 } 00337 return TRUE; 00338 } 00339 00340 // Supports only partially initialized objects. 00341 // Does nothing if already stopped. 00342 extern "C" 00343 void rk_Remokon_stop(rk_Remokon* self) 00344 { 00345 TRAPFATAL(self->stop()); 00346 } 00347 00348 extern "C" 00349 gboolean rk_Remokon_start_timed(rk_Remokon* self, 00350 int secs, 00351 GError** error) 00352 { 00353 if (!self->iHaveConfig) { 00354 // No point in trying to start without proper configuration. 00355 if (error) 00356 *error = no_config_error; 00357 return FALSE; 00358 } 00359 00360 GTRAP(FALSE, self->startTimed(secs)); 00361 return TRUE; 00362 } 00363 00364 extern "C" 00365 gboolean rk_Remokon_reconfigure(rk_Remokon* self, 00366 const gchar* key, 00367 const gchar* value, 00368 GError** error) 00369 { 00370 if (strcmp(key, "remokon.autostart")) { 00371 self->iAutostartEnabled = force_lua_eval_bool(value, FALSE); 00372 } 00373 00374 #if defined(__SYMBIAN32__) 00375 else if (strcmp(key, "iap") == 0) { 00376 // It should be safe to just set this value. A change won't 00377 // take effect before a reconnect, but this is okay, a Remokon 00378 // stop followed by start will allow for that via the API. 00379 self->params.iap_id = force_lua_eval_int(value, -1); 00380 } 00381 #endif /* __SYMBIAN32__ */ 00382 00383 return TRUE; 00384 } 00385 00386 extern "C" 00387 gboolean rk_Remokon_is_started(rk_Remokon* self) 00388 { 00389 return self && self->iIsActive; 00390 } 00391 00392 extern "C" 00393 gboolean rk_Remokon_is_connected(rk_Remokon* self) 00394 { 00395 return self && self->iSession.isConnected(); 00396 } 00397 00398 extern "C" 00399 gboolean rk_Remokon_send(rk_Remokon* self, 00400 const char* toJid, 00401 const char* msgText, 00402 GError** error) 00403 { 00404 if (!rk_Remokon_is_connected(self)) { 00405 if (error) 00406 *error = gx_error_new(domain_cl2app, code_not_connected, 00407 "no Jabber connection"); 00408 return FALSE; 00409 } 00410 00411 GTRAP(FALSE, self->send(QString(toJid), QString(msgText))); 00412 00413 return TRUE; 00414 } 00415 00416 /** 00417 00418 Copyright 2009-2011 Helsinki Institute for Information Technology 00419 (HIIT) and the authors. All rights reserved. 00420 00421 Authors: Tero Hasu <tero.hasu@hut.fi> 00422 00423 Permission is hereby granted, free of charge, to any person 00424 obtaining a copy of this software and associated documentation files 00425 (the "Software"), to deal in the Software without restriction, 00426 including without limitation the rights to use, copy, modify, merge, 00427 publish, distribute, sublicense, and/or sell copies of the Software, 00428 and to permit persons to whom the Software is furnished to do so, 00429 subject to the following conditions: 00430 00431 The above copyright notice and this permission notice shall be 00432 included in all copies or substantial portions of the Software. 00433 00434 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 00435 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 00436 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 00437 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 00438 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 00439 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 00440 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 00441 SOFTWARE. 00442 00443 **/
ContextLogger2—ContextLogger2 Logger Daemon Internals—Generated on Mon May 2 13:49:56 2011 by Doxygen 1.6.1