You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
616 lines
15 KiB
616 lines
15 KiB
//
|
|
// 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 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 opinion) 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 "kvi_module.h"
|
|
|
|
#include "libkviident.h"
|
|
|
|
#include "kvi_socket.h"
|
|
#include "kvi_app.h"
|
|
#include "kvi_out.h"
|
|
#include "kvi_netutils.h"
|
|
#include "kvi_locale.h"
|
|
#include "kvi_window.h"
|
|
|
|
#include "kvi_options.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 = 0;
|
|
static KviIdentSentinel * g_pIdentSentinel = 0;
|
|
|
|
extern KVIRC_API int g_iIdentDaemonRunningUsers;
|
|
|
|
void startIdentService()
|
|
{
|
|
// tqDebug("Stargin");
|
|
if(!g_pIdentDaemon)g_pIdentDaemon = new KviIdentDaemon();
|
|
if(!g_pIdentDaemon->isRunning())g_pIdentDaemon->start();
|
|
while(g_pIdentDaemon->isStartingUp())
|
|
{
|
|
#ifdef COMPILE_ON_WINDOWS
|
|
Sleep(10);
|
|
#else
|
|
usleep(100);
|
|
#endif
|
|
}
|
|
// tqDebug("Service started");
|
|
}
|
|
|
|
void stopIdentService()
|
|
{
|
|
// tqDebug("Stopping");
|
|
if(g_pIdentDaemon)delete g_pIdentDaemon;
|
|
g_pIdentDaemon = 0;
|
|
// tqDebug("Stopped");
|
|
}
|
|
|
|
KviIdentSentinel::KviIdentSentinel()
|
|
: TQObject(0)
|
|
{
|
|
}
|
|
|
|
KviIdentSentinel::~KviIdentSentinel()
|
|
{
|
|
KviThreadManager::killPendingEvents(this);
|
|
}
|
|
|
|
bool KviIdentSentinel::event(TQEvent *e)
|
|
{
|
|
if(KVI_OPTION_UINT(KviOption_uintIdentdOutputMode)==KviIdentdOutputMode::Quiet || !g_pActiveWindow)
|
|
return TQObject::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<KviIdentMessageData> *)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 identd 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 identd service (requested action)"));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return TQObject::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(0);
|
|
}
|
|
|
|
KviIdentRequest::~KviIdentRequest()
|
|
{
|
|
kvi_socket_close(m_sock);
|
|
}
|
|
|
|
|
|
|
|
KviIdentDaemon::KviIdentDaemon()
|
|
: KviSensitiveThread()
|
|
{
|
|
// tqDebug("Thread constructor");
|
|
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);
|
|
// tqDebug("Thread constructor done");
|
|
}
|
|
|
|
KviIdentDaemon::~KviIdentDaemon()
|
|
{
|
|
// tqDebug("Thread destructor");
|
|
terminate();
|
|
g_iIdentDaemonRunningUsers = 0;
|
|
|
|
g_pIdentDaemon = 0;
|
|
// tqDebug("Destructor gone");
|
|
}
|
|
|
|
void KviIdentDaemon::postMessage(const char * message,KviIdentRequest * r,const char * szAux)
|
|
{
|
|
KviThreadDataEvent<KviIdentMessageData> * e = new KviThreadDataEvent<KviIdentMessageData>(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()
|
|
{
|
|
// tqDebug("RUN STARTED");
|
|
m_sock = KVI_INVALID_SOCKET;
|
|
m_sock6 = KVI_INVALID_SOCKET;
|
|
bool bEventPosted = false;
|
|
|
|
m_pRequestList = new KviPointerList<KviIdentRequest>;
|
|
m_pRequestList->setAutoDelete(true);
|
|
|
|
KviPointerList<KviIdentRequest> 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"),0);
|
|
goto exit_thread;
|
|
}
|
|
|
|
if(!kvi_socket_setNonBlocking(m_sock))
|
|
{
|
|
postMessage(__tr("Can't start the ident service : async setting failed"),0);
|
|
goto exit_thread;
|
|
}
|
|
|
|
if(!sa.socketAddress())
|
|
{
|
|
postMessage(__tr("Can't enable the ident service : can't setup the listen address"),0);
|
|
goto exit_thread;
|
|
}
|
|
|
|
if(!kvi_socket_bind(m_sock,sa.socketAddress(),((int)(sa.addressLength()))))
|
|
{
|
|
postMessage(__tr("Can't start the ident service : bind() failed"),0);
|
|
goto exit_thread;
|
|
}
|
|
|
|
if(!kvi_socket_listen(m_sock,128))
|
|
{
|
|
postMessage(__tr("Can't start the ident service : listen() failed"),0);
|
|
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"),0);
|
|
goto ipv6_failure;
|
|
}
|
|
|
|
if(!kvi_socket_setNonBlocking(m_sock6))
|
|
{
|
|
postMessage(__tr("Can't start the ident service on IpV6 : async setting failed"),0);
|
|
kvi_socket_close(m_sock6);
|
|
m_sock6 = KVI_INVALID_SOCKET;
|
|
goto ipv6_failure;
|
|
}
|
|
|
|
if(!sa6.socketAddress())
|
|
{
|
|
postMessage(__tr("Can't enable the ident service on IpV6 : can't setup the listen address"),0);
|
|
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"),0);
|
|
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"),0);
|
|
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 identd service (IpV4/V6 on separate namespaces)"),0);
|
|
} else {
|
|
if(_OUTPUT_PARANOIC)
|
|
postMessage(__tr("Starting identd service (IpV4/V6 in IpV6 namespace)"),0);
|
|
}
|
|
|
|
} else {
|
|
if(_OUTPUT_PARANOIC)
|
|
postMessage(__tr("Starting identd service (IpV4)"),0);
|
|
}
|
|
#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,0,0,&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)
|
|
{
|
|
TQString szHost;
|
|
if(!satmp.getStringAddress(szHost))szHost = "unknown";
|
|
KviIdentRequest * r = new KviIdentRequest(t,szHost.utf8().data(),satmp.port());
|
|
m_pRequestList->append(r);
|
|
postMessage(__tr("Identd 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)
|
|
{
|
|
TQString szHost;
|
|
if(!satmp.getStringAddress(szHost))szHost = "unknown";
|
|
KviIdentRequest * r = new KviIdentRequest(t,szHost.utf8().data(),satmp.port());
|
|
m_pRequestList->append(r);
|
|
postMessage(__tr("Identd 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("Identd socket error : dropping connection"),r);
|
|
dying.append(r);
|
|
}
|
|
} else {
|
|
// connection closed
|
|
postMessage(__tr("Identd 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
|
|
KviStr szReq = r->m_szData.left(idx);
|
|
r->m_szData.cutLeft(idx + 1);
|
|
szReq.stripWhiteSpace();
|
|
|
|
if(szReq.hasData())
|
|
{
|
|
postMessage(__tr("Identd processing request"),r,szReq.ptr());
|
|
|
|
if(kvi_strEqualCI("VERSION",szReq.ptr()))
|
|
{
|
|
KviStr reply("Quad-Echelon 7.12-r-244");
|
|
kvi_socket_write(r->m_sock,reply.ptr(),reply.len());
|
|
} else {
|
|
KviStr reply(KviStr::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 {
|
|
// tqDebug("Data is : (%s)",r->m_szData.ptr());
|
|
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(0);
|
|
|
|
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 = 0;
|
|
|
|
// tqDebug("RUN EXITING");
|
|
}
|
|
|
|
|
|
/*
|
|
@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 * c)
|
|
{
|
|
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 * c)
|
|
{
|
|
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 *m)
|
|
{
|
|
stopIdentService();
|
|
delete g_pIdentSentinel;
|
|
g_pIdentSentinel = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ident_module_can_unload(KviModule *m)
|
|
{
|
|
return !g_pIdentDaemon;
|
|
}
|
|
|
|
KVIRC_MODULE(
|
|
"Ident", // module name
|
|
"1.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
|
|
)
|
|
|
|
#include "libkviident.moc"
|