//============================================================================= // // File : libkviident.cpp // Creation date : Tue Oct 2 18:22:04 2001 GMT by Szymon Stefanek // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2001-2010 Szymon Stefanek (pragma at kvirc dot net) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #include "libkviident.h" #include "KviModule.h" #include "kvi_socket.h" #include "KviApplication.h" #include "kvi_out.h" #include "KviNetUtils.h" #include "KviLocale.h" #include "KviWindow.h" #include "KviOptions.h" #include "kvi_defaults.h" #define KVI_IDENT_THREAD_EVENT_EXITING KVI_THREAD_USER_EVENT_BASE + 111 #define KVI_IDENT_THREAD_EVENT_EXITING_ON_REQUEST KVI_THREAD_USER_EVENT_BASE + 112 // FIXME: Should have a timeout on the requests!!! static KviIdentDaemon * g_pIdentDaemon = nullptr; static KviIdentSentinel * g_pIdentSentinel = nullptr; extern KVIRC_API int g_iIdentDaemonRunningUsers; void startIdentService() { if(!g_pIdentDaemon) g_pIdentDaemon = new KviIdentDaemon(); if(!g_pIdentDaemon->isRunning()) g_pIdentDaemon->start(); while(g_pIdentDaemon->isStartingUp()) { #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) Sleep(10); #else usleep(100); #endif } } void stopIdentService() { if(g_pIdentDaemon) delete g_pIdentDaemon; g_pIdentDaemon = nullptr; } KviIdentSentinel::KviIdentSentinel() : QObject(nullptr) { } KviIdentSentinel::~KviIdentSentinel() { KviThreadManager::killPendingEvents(this); } bool KviIdentSentinel::event(QEvent * e) { if(KVI_OPTION_UINT(KviOption_uintIdentdOutputMode) == KviIdentdOutputMode::Quiet || !g_pActiveWindow) return QObject::event(e); KviWindow * pTarget = KVI_OPTION_UINT(KviOption_uintIdentdOutputMode) == KviIdentdOutputMode::ToActiveWindow ? (KviWindow *)g_pActiveWindow : (KviWindow *)g_pApp->activeConsole(); if(e->type() == KVI_THREAD_EVENT) { if(((KviThreadEvent *)e)->id() == KVI_THREAD_EVENT_DATA) { KviIdentMessageData * d = ((KviThreadDataEvent *)e)->getData(); if(pTarget) { if(d->szHost.hasData()) { if(d->szAux.hasData()) { if(_OUTPUT_PARANOIC) pTarget->output(KVI_OUT_IDENT, __tr("%s (%s) (%s:%u)"), d->szMessage.ptr(), d->szAux.ptr(), d->szHost.ptr(), d->uPort); else pTarget->output(KVI_OUT_IDENT, __tr("%s (%s)"), d->szMessage.ptr(), d->szAux.ptr()); } else { if(_OUTPUT_PARANOIC) pTarget->output(KVI_OUT_IDENT, __tr("%s (%s:%u)"), d->szMessage.ptr(), d->szHost.ptr(), d->uPort); else pTarget->output(KVI_OUT_IDENT, __tr("%s"), d->szMessage.ptr()); } } else { pTarget->output(KVI_OUT_IDENT, __tr("[IDENT]: %s"), d->szMessage.ptr()); } } delete d; } else if(((KviThreadEvent *)e)->id() == KVI_IDENT_THREAD_EVENT_EXITING) { if(_OUTPUT_VERBOSE) if(pTarget) pTarget->outputNoFmt(KVI_OUT_IDENT, __tr("Shutting down Ident service (spontaneous action)")); stopIdentService(); } else if(((KviThreadEvent *)e)->id() == KVI_IDENT_THREAD_EVENT_EXITING_ON_REQUEST) { if(_OUTPUT_VERBOSE) if(pTarget) pTarget->outputNoFmt(KVI_OUT_IDENT, __tr("Shutting down Ident service (requested action)")); } return true; } return QObject::event(e); } KviIdentRequest::KviIdentRequest(kvi_socket_t sock, const char * host, kvi_u32_t uPort) { m_sock = sock; m_szHost = host; m_uPort = uPort; m_tStart = time(nullptr); } KviIdentRequest::~KviIdentRequest() { kvi_socket_close(m_sock); } KviIdentDaemon::KviIdentDaemon() : KviSensitiveThread() { m_szUser = KVI_OPTION_STRING(KviOption_stringIdentdUser); if(m_szUser.isEmpty()) m_szUser = "kvirc"; m_uPort = KVI_OPTION_UINT(KviOption_uintIdentdPort); #ifdef COMPILE_IPV6_SUPPORT m_bEnableIPv6 = KVI_OPTION_BOOL(KviOption_boolIdentdEnableIPv6); #else m_bEnableIPv6 = false; #endif m_bIPv6ContainsIPv4 = KVI_OPTION_BOOL(KviOption_boolIdentdIPv6ContainsIPv4); } KviIdentDaemon::~KviIdentDaemon() { terminate(); g_iIdentDaemonRunningUsers = 0; g_pIdentDaemon = nullptr; } void KviIdentDaemon::postMessage(const char * message, KviIdentRequest * r, const char * szAux) { KviThreadDataEvent * e = new KviThreadDataEvent(KVI_THREAD_EVENT_DATA); KviIdentMessageData * d = new KviIdentMessageData; d->szMessage = message; if(szAux) d->szAux = szAux; if(r) { d->szHost = r->m_szHost; d->uPort = r->m_uPort; } e->setData(d); postEvent(g_pIdentSentinel, e); } void KviIdentDaemon::run() { m_sock = KVI_INVALID_SOCKET; m_sock6 = KVI_INVALID_SOCKET; bool bEventPosted = false; m_pRequestList = new KviPointerList; m_pRequestList->setAutoDelete(true); KviPointerList dying; dying.setAutoDelete(false); #ifdef COMPILE_IPV6_SUPPORT // If we have enabled ipv6 and we have to use a single socket: this one is IPV6 // otherwise this one is IPV4 KviSockaddr sa(m_uPort, m_bEnableIPv6 && m_bIPv6ContainsIPv4); #else KviSockaddr sa(m_uPort, false); #endif KviIdentRequest * r; #ifdef COMPILE_IPV6_SUPPORT m_sock = kvi_socket_create((m_bEnableIPv6 && m_bIPv6ContainsIPv4) ? KVI_SOCKET_PF_INET6 : KVI_SOCKET_PF_INET, KVI_SOCKET_TYPE_STREAM, KVI_SOCKET_PROTO_TCP); #else m_sock = kvi_socket_create(KVI_SOCKET_PF_INET, KVI_SOCKET_TYPE_STREAM, KVI_SOCKET_PROTO_TCP); #endif if(m_sock == KVI_INVALID_SOCKET) { postMessage(__tr("Can't start the Ident service : socket() failed"), nullptr); goto exit_thread; } if(!kvi_socket_setNonBlocking(m_sock)) { postMessage(__tr("Can't start the Ident service : async setting failed"), nullptr); goto exit_thread; } if(!sa.socketAddress()) { postMessage(__tr("Can't enable the Ident service : can't setup the listen address"), nullptr); goto exit_thread; } if(!kvi_socket_bind(m_sock, sa.socketAddress(), ((int)(sa.addressLength())))) { postMessage(__tr("Can't start the Ident service : bind() failed"), nullptr); goto exit_thread; } if(!kvi_socket_listen(m_sock, 128)) { postMessage(__tr("Can't start the Ident service : listen() failed"), nullptr); goto exit_thread; } #ifdef COMPILE_IPV6_SUPPORT if(m_bEnableIPv6 && (!m_bIPv6ContainsIPv4)) { // Need to start the IPV6 socket too KviSockaddr sa6(m_uPort, true); m_sock6 = kvi_socket_create(KVI_SOCKET_PF_INET6, KVI_SOCKET_TYPE_STREAM, KVI_SOCKET_PROTO_TCP); if(m_sock6 == KVI_INVALID_SOCKET) { postMessage(__tr("Can't start the Ident service on IPv6 : socket() failed"), nullptr); goto ipv6_failure; } if(!kvi_socket_setNonBlocking(m_sock6)) { postMessage(__tr("Can't start the Ident service on IPv6 : async setting failed"), nullptr); kvi_socket_close(m_sock6); m_sock6 = KVI_INVALID_SOCKET; goto ipv6_failure; } #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) /* * under windows, ipv6 hosts behind an ipv4 NAT can use Teredo to be reachable from the outer internet using their * ipv6 address; the default protection level doesn't permit this, so we need to set this socket option * see bug ticket #393 */ int iPlu = KVI_PROTECTION_LEVEL_UNRESTRICTED; if(!kvi_socket_setsockopt(m_sock6, KVI_IPPROTO_IPV6, KVI_IPV6_PROTECTION_LEVEL, (const void *)&iPlu, sizeof(iPlu))) { //this is not a fatal error postMessage(__tr("Can't set the IPv6 Ident service protection level to unrestricted: the Ident service won't be exposed to the internet"), 0); } #endif if(!sa6.socketAddress()) { postMessage(__tr("Can't enable the Ident service on IPv6 : can't setup the listen address"), nullptr); kvi_socket_close(m_sock6); m_sock6 = KVI_INVALID_SOCKET; goto ipv6_failure; } if(!kvi_socket_bind(m_sock6, sa6.socketAddress(), ((int)(sa6.addressLength())))) { postMessage(__tr("Can't start the Ident service on IPv6 : bind() failed"), nullptr); kvi_socket_close(m_sock6); m_sock6 = KVI_INVALID_SOCKET; goto ipv6_failure; } if(!kvi_socket_listen(m_sock6, 128)) { postMessage(__tr("Can't start the Ident service on IPv6 : listen() failed"), nullptr); kvi_socket_close(m_sock6); m_sock6 = KVI_INVALID_SOCKET; goto ipv6_failure; } } #endif ipv6_failure: #ifdef COMPILE_IPV6_SUPPORT if(m_bEnableIPv6) { if(m_sock6 != KVI_INVALID_SOCKET) { if(_OUTPUT_PARANOIC) postMessage(__tr("Starting Ident service (IPv4/v6 on separate namespaces)"), nullptr); } else { if(_OUTPUT_PARANOIC) postMessage(__tr("Starting Ident service (IPv4/v6 in IPv6 namespace)"), nullptr); } } else { if(_OUTPUT_PARANOIC) postMessage(__tr("Starting Ident service (IPv4)"), nullptr); } #else //!COMPILE_IPV6_SUPPORT if(_OUTPUT_PARANOIC) postMessage(__tr("Service startup (IPv4)"), 0); #endif //!COMPILE_IPV6_SUPPORT for(;;) { if(KviThreadEvent * e = dequeueEvent()) { // This can be ONLY a terminate event delete e; goto exit_on_request; } struct timeval t; t.tv_sec = 0; t.tv_usec = 10000; int nmax = 0; fd_set rs; FD_ZERO(&rs); if(m_sock != KVI_INVALID_SOCKET) { FD_SET(m_sock, &rs); if(((unsigned int)m_sock) > ((unsigned int)nmax)) nmax = m_sock; } #ifdef COMPILE_IPV6_SUPPORT if(m_sock6 != KVI_INVALID_SOCKET) { FD_SET(m_sock6, &rs); if(((unsigned int)m_sock6) > ((unsigned int)nmax)) nmax = m_sock6; } #endif for(r = m_pRequestList->first(); r; r = m_pRequestList->next()) { FD_SET(r->m_sock, &rs); if(((unsigned int)r->m_sock) > ((unsigned int)nmax)) nmax = r->m_sock; } // FIXME: SO_REUSEADDR ? int ret = kvi_socket_select(nmax + 1, &rs, nullptr, nullptr, &t); if(ret == 0) msleep(100); else { if(m_sock != KVI_INVALID_SOCKET) { if(FD_ISSET(m_sock, &rs)) { #ifdef COMPILE_IPV6_SUPPORT KviSockaddr satmp(0, m_bEnableIPv6 && m_bIPv6ContainsIPv4); #else KviSockaddr satmp(0, false); #endif int salen = (int)satmp.addressLength(); kvi_socket_t t = kvi_socket_accept(m_sock, satmp.socketAddress(), &salen); if(t != KVI_INVALID_SOCKET) { QString szHost; if(!satmp.getStringAddress(szHost)) szHost = "unknown"; KviIdentRequest * r = new KviIdentRequest(t, szHost.toUtf8().data(), satmp.port()); m_pRequestList->append(r); postMessage(__tr("Ident service accepting connection"), r); } } } #ifdef COMPILE_IPV6_SUPPORT if(m_sock6 != KVI_INVALID_SOCKET) { if(FD_ISSET(m_sock6, &rs)) { KviSockaddr satmp(0, true); int salen = (int)satmp.addressLength(); kvi_socket_t t = kvi_socket_accept(m_sock6, satmp.socketAddress(), &salen); if(t != KVI_INVALID_SOCKET) { QString szHost; if(!satmp.getStringAddress(szHost)) szHost = "unknown"; KviIdentRequest * r = new KviIdentRequest(t, szHost.toUtf8().data(), satmp.port()); m_pRequestList->append(r); postMessage(__tr("Ident service accepting connection"), r); } } } #endif for(r = m_pRequestList->first(); r; r = m_pRequestList->next()) { if(FD_ISSET(r->m_sock, &rs)) { char buffer[1025]; int readed = kvi_socket_recv(r->m_sock, buffer, 1024); if(readed > 0) { buffer[readed] = '\0'; r->m_szData.append(buffer); } else { // error ? if(readed < 0) { int err = kvi_socket_error(); if(!kvi_socket_recoverableConnectError(err)) { postMessage(__tr("Ident service socket error : dropping connection"), r); dying.append(r); } } else { // connection closed postMessage(__tr("Ident service connection closed by remote host"), r); dying.append(r); } } } } for(r = m_pRequestList->first(); r; r = m_pRequestList->next()) { int idx = r->m_szData.findFirstIdx('\n'); if(idx != -1) { // Ok...parse the request KviCString szReq = r->m_szData.left(idx); r->m_szData.cutLeft(idx + 1); szReq.trim(); if(szReq.hasData()) { postMessage(__tr("Ident service processing request"), r, szReq.ptr()); if(kvi_strEqualCI("VERSION", szReq.ptr())) { KviCString reply("Quad-Echelon 7.12-r-244"); kvi_socket_write(r->m_sock, reply.ptr(), reply.len()); } else { KviCString reply(KviCString::Format, "%s : USERID : UNIX : %s\r\n", szReq.ptr(), m_szUser.ptr()); kvi_socket_write(r->m_sock, reply.ptr(), reply.len()); } dying.append(r); } else { postMessage(__tr("Empty request (EOT ?)"), r, szReq.ptr()); dying.append(r); } } else { if(r->m_szData.len() > 1024) { // request too long...kill it dying.append(r); postMessage(__tr("Dropping connection (request too long)"), r); } } } } time_t curTime = time(nullptr); for(r = m_pRequestList->first(); r; r = m_pRequestList->next()) { if((unsigned int)(curTime - r->m_tStart) > 30) { postMessage(__tr("Timed out while waiting for the request : dropping connection"), r); dying.append(r); } } for(KviIdentRequest * ir = dying.first(); ir; ir = dying.next()) m_pRequestList->removeRef(ir); dying.clear(); } exit_on_request: postEvent(g_pIdentSentinel, new KviThreadEvent(KVI_IDENT_THREAD_EVENT_EXITING_ON_REQUEST)); bEventPosted = true; exit_thread: if(!bEventPosted) postEvent(g_pIdentSentinel, new KviThreadEvent(KVI_IDENT_THREAD_EVENT_EXITING)); if(m_sock != KVI_INVALID_SOCKET) kvi_socket_close(m_sock); if(m_sock6 != KVI_INVALID_SOCKET) kvi_socket_close(m_sock6); delete m_pRequestList; m_pRequestList = nullptr; } /* @doc: ident.start @type: command @title: ident.start @short: Starts the builtin Ident service @syntax: ident.start @description: Starts the builtin Ident service.[br] WARNING: the KVIrc Ident service is just a partial implementation of the RFC specifications. You should use is ONLY if you can't get any other Ident daemon running on your machine.[br] */ static bool ident_kvs_cmd_start(KviKvsModuleCommandCall *) { if(!g_iIdentDaemonRunningUsers) startIdentService(); g_iIdentDaemonRunningUsers++; return true; } /* @doc: ident.stop @type: command @title: ident.stop @short: Stops the Ident service @syntax: ident.stop @description: Stops the Ident service @seealso: [cmd]ident.start[/cmd] */ static bool ident_kvs_cmd_stop(KviKvsModuleCommandCall *) { if(g_iIdentDaemonRunningUsers) g_iIdentDaemonRunningUsers--; if(!g_iIdentDaemonRunningUsers) stopIdentService(); return true; } static bool ident_module_init(KviModule * m) { g_pIdentSentinel = new KviIdentSentinel(); KVSM_REGISTER_SIMPLE_COMMAND(m, "start", ident_kvs_cmd_start); KVSM_REGISTER_SIMPLE_COMMAND(m, "stop", ident_kvs_cmd_stop); return true; } static bool ident_module_cleanup(KviModule *) { stopIdentService(); delete g_pIdentSentinel; g_pIdentSentinel = nullptr; return true; } static bool ident_module_can_unload(KviModule *) { return !g_pIdentDaemon; } KVIRC_MODULE( "Ident", // module name "4.0.0", // module version "Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net)", // author & (C) "Ident service", ident_module_init, ident_module_can_unload, 0, ident_module_cleanup, 0)