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.
659 lines
17 KiB
659 lines
17 KiB
|
|
/***************************************************************************
|
|
kinetd.cpp
|
|
--------------
|
|
begin : Mon Feb 11 2002
|
|
copyright : (C) 2002 by Tim Jansen
|
|
email : tim@tjansen.de
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "kinetd.h"
|
|
#include "kinetd.moc"
|
|
#include "kinetinterface.h"
|
|
#include "kuser.h"
|
|
#include "uuid.h"
|
|
#include <tqregexp.h>
|
|
#include <kservicetype.h>
|
|
#include <kdebug.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kconfig.h>
|
|
#include <knotifyclient.h>
|
|
#include <ksockaddr.h>
|
|
#include <kextsock.h>
|
|
#include <klocale.h>
|
|
#include <kglobal.h>
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
PortListener::PortListener(KService::Ptr s,
|
|
TDEConfig *config,
|
|
KServiceRegistry *srvreg) :
|
|
m_port(-1),
|
|
m_serviceRegistered(false),
|
|
m_socket(0),
|
|
m_config(config),
|
|
m_srvreg(srvreg),
|
|
m_dnssdreg(0)
|
|
{
|
|
m_dnssdRegistered = false;
|
|
|
|
m_uuid = createUUID();
|
|
loadConfig(s);
|
|
|
|
if (m_valid && m_enabled)
|
|
acquirePort();
|
|
}
|
|
|
|
bool PortListener::acquirePort() {
|
|
if (m_socket) {
|
|
if ((m_port >= m_portBase) &&
|
|
(m_port < (m_portBase + m_autoPortRange)))
|
|
return true;
|
|
else
|
|
delete m_socket;
|
|
}
|
|
m_port = m_portBase;
|
|
m_socket = new TDEServerSocket(m_port, false);
|
|
while (!m_socket->bindAndListen()) {
|
|
m_port++;
|
|
if (m_port >= (m_portBase+m_autoPortRange)) {
|
|
kdDebug() << "Kinetd cannot load service "<<m_serviceName
|
|
<<": unable to get port" << endl;
|
|
m_port = -1;
|
|
delete m_socket;
|
|
m_socket = 0;
|
|
return false;
|
|
}
|
|
delete m_socket;
|
|
m_socket = new TDEServerSocket(m_port, false);
|
|
}
|
|
connect(m_socket, TQT_SIGNAL(accepted(TDESocket*)),
|
|
TQT_SLOT(accepted(TDESocket*)));
|
|
|
|
bool s = m_registerService;
|
|
bool sd =m_dnssdRegister;
|
|
setServiceRegistrationEnabledInternal(false);
|
|
dnssdRegister(false);
|
|
setServiceRegistrationEnabledInternal(s);
|
|
dnssdRegister(sd);
|
|
return true;
|
|
}
|
|
|
|
void PortListener::freePort() {
|
|
m_port = -1;
|
|
if (m_socket)
|
|
delete m_socket;
|
|
m_socket = 0;
|
|
setServiceRegistrationEnabledInternal(m_registerService);
|
|
dnssdRegister(false);
|
|
}
|
|
|
|
void PortListener::loadConfig(KService::Ptr s) {
|
|
m_valid = true;
|
|
m_autoPortRange = 0;
|
|
m_enabled = true;
|
|
m_argument = TQString();
|
|
m_multiInstance = false;
|
|
|
|
TQVariant vid, vport, vautoport, venabled, vargument, vmultiInstance, vurl,
|
|
vsattributes, vslifetime, vdname, vdtype, vddata;
|
|
|
|
m_execPath = s->exec().utf8();
|
|
vid = s->property("X-TDE-KINETD-id");
|
|
vport = s->property("X-TDE-KINETD-port");
|
|
vautoport = s->property("X-TDE-KINETD-autoPortRange");
|
|
venabled = s->property("X-TDE-KINETD-enabled");
|
|
vargument = s->property("X-TDE-KINETD-argument");
|
|
vmultiInstance = s->property("X-TDE-KINETD-multiInstance");
|
|
vurl = s->property("X-TDE-KINETD-serviceURL");
|
|
vsattributes = s->property("X-TDE-KINETD-serviceAttributes");
|
|
vslifetime = s->property("X-TDE-KINETD-serviceLifetime");
|
|
vdname = s->property("X-TDE-KINETD-DNSSD-Name");
|
|
vdtype = s->property("X-TDE-KINETD-DNSSD-Type");
|
|
vddata = s->property("X-TDE-KINETD-DNSSD-Properties");
|
|
|
|
if (!vid.isValid()) {
|
|
kdDebug() << "Kinetd cannot load service "<<m_serviceName
|
|
<<": no id set" << endl;
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
|
|
if (!vport.isValid()) {
|
|
kdDebug() << "Kinetd cannot load service "<<m_serviceName
|
|
<<": invalid port" << endl;
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
|
|
m_serviceName = vid.toString();
|
|
m_serviceLifetime = vslifetime.toInt();
|
|
if (m_serviceLifetime < 120) // never less than 120 s
|
|
m_serviceLifetime = 120;
|
|
m_portBase = vport.toInt();
|
|
if (vautoport.isValid())
|
|
m_autoPortRange = vautoport.toInt();
|
|
if (venabled.isValid())
|
|
m_enabled = venabled.toBool();
|
|
if (vargument.isValid())
|
|
m_argument = vargument.toString();
|
|
if (vmultiInstance.isValid())
|
|
m_multiInstance = vmultiInstance.toBool();
|
|
if (vurl.isValid()) {
|
|
m_serviceURL = vurl.toString();
|
|
m_registerService = true;
|
|
}
|
|
else {
|
|
m_serviceURL = TQString();
|
|
m_registerService = false;
|
|
}
|
|
if (vsattributes.isValid()) {
|
|
m_serviceAttributes = vsattributes.toString();
|
|
}
|
|
else
|
|
m_serviceAttributes = "";
|
|
if (vddata.isValid()) {
|
|
TQStringList attrs = vddata.toStringList();
|
|
for (TQStringList::iterator it=attrs.begin();
|
|
it!=attrs.end();it++) {
|
|
TQString key = (*it).section('=',0,0);
|
|
TQString value = processServiceTemplate((*it).section('=',1))[0];
|
|
if (!key.isEmpty()) m_dnssdData[key]=value;
|
|
}
|
|
}
|
|
if (vdname.isValid() && vdtype.isValid()) {
|
|
m_dnssdName = processServiceTemplate(vdname.toString())[0];
|
|
m_dnssdType = vdtype.toString();
|
|
m_dnssdRegister = true;
|
|
kdDebug() << "DNS-SD register is enabled\n";
|
|
}
|
|
else
|
|
m_dnssdRegister = false;
|
|
|
|
|
|
m_slpLifetimeEnd = TQDateTime::currentDateTime().addSecs(m_serviceLifetime);
|
|
m_defaultPortBase = m_portBase;
|
|
m_defaultAutoPortRange = m_autoPortRange;
|
|
|
|
m_config->setGroup("ListenerConfig");
|
|
m_enabled = m_config->readBoolEntry("enabled_" + m_serviceName,
|
|
m_enabled);
|
|
m_portBase = m_config->readNumEntry("port_base_" + m_serviceName,
|
|
m_portBase);
|
|
m_autoPortRange = m_config->readNumEntry("auto_port_range_" + m_serviceName,
|
|
m_autoPortRange);
|
|
TQDateTime nullTime;
|
|
m_expirationTime = m_config->readDateTimeEntry("enabled_expiration_"+m_serviceName,
|
|
&nullTime);
|
|
if ((!m_expirationTime.isNull()) && (m_expirationTime < TQDateTime::currentDateTime()))
|
|
m_enabled = false;
|
|
m_registerService = m_config->readBoolEntry("enabled_srvreg_"+m_serviceName,
|
|
m_registerService);
|
|
}
|
|
|
|
void PortListener::accepted(TDESocket *sock) {
|
|
TQString host, port;
|
|
TDESocketAddress *ksa = KExtendedSocket::peerAddress(sock->socket());
|
|
if ((!ksa) || !ksa->address()) {
|
|
delete sock;
|
|
return;
|
|
}
|
|
KExtendedSocket::resolve(ksa, host, port);
|
|
KNotifyClient::event("IncomingConnection",
|
|
i18n("Connection from %1").arg(host));
|
|
delete ksa;
|
|
|
|
if ((!m_enabled) ||
|
|
((!m_multiInstance) && m_process.isRunning())) {
|
|
delete sock;
|
|
return;
|
|
}
|
|
|
|
// disable CLOEXEC flag, fixes #77412
|
|
fcntl(sock->socket(), F_SETFD, fcntl(sock->socket(), F_GETFD) & ~FD_CLOEXEC);
|
|
|
|
m_process.clearArguments();
|
|
m_process << m_execPath << m_argument << TQString::number(sock->socket());
|
|
if (!m_process.start(TDEProcess::DontCare)) {
|
|
KNotifyClient::event("ProcessFailed",
|
|
i18n("Call \"%1 %2 %3\" failed").arg(m_execPath)
|
|
.arg(m_argument)
|
|
.arg(sock->socket()));
|
|
}
|
|
|
|
delete sock;
|
|
}
|
|
|
|
bool PortListener::isValid() {
|
|
return m_valid;
|
|
}
|
|
|
|
bool PortListener::isEnabled() {
|
|
return m_enabled && m_valid;
|
|
}
|
|
|
|
int PortListener::port() {
|
|
return m_port;
|
|
}
|
|
|
|
TQStringList PortListener::processServiceTemplate(const TQString &a) {
|
|
TQStringList l;
|
|
TQValueVector<KInetInterface> v = KInetInterface::getAllInterfaces(false);
|
|
TQValueVector<KInetInterface>::Iterator it = v.begin();
|
|
while (it != v.end()) {
|
|
KInetSocketAddress *address = (*(it++)).address();
|
|
if (!address)
|
|
continue;
|
|
TQString hostName = address->nodeName();
|
|
KUser u;
|
|
TQString x = a; // replace does not work in const TQString. Why??
|
|
l.append(x.replace(TQRegExp("%h"), KServiceRegistry::encodeAttributeValue(hostName))
|
|
.replace(TQRegExp("%p"), TQString::number(m_port))
|
|
.replace(TQRegExp("%u"), KServiceRegistry::encodeAttributeValue(u.loginName()))
|
|
.replace(TQRegExp("%i"), KServiceRegistry::encodeAttributeValue(m_uuid))
|
|
.replace(TQRegExp("%f"), KServiceRegistry::encodeAttributeValue(u.fullName())));
|
|
}
|
|
return l;
|
|
}
|
|
|
|
bool PortListener::setPort(int port, int autoPortRange) {
|
|
if ((port == m_portBase) && (autoPortRange == m_autoPortRange))
|
|
return (m_port != -1);
|
|
|
|
m_config->setGroup("ListenerConfig");
|
|
if (port > 0) {
|
|
m_portBase = port;
|
|
m_autoPortRange = autoPortRange;
|
|
|
|
m_config->writeEntry("port_base_" + m_serviceName, m_portBase);
|
|
m_config->writeEntry("auto_port_range_"+m_serviceName, m_autoPortRange);
|
|
}
|
|
else {
|
|
m_portBase = m_defaultPortBase;
|
|
m_autoPortRange = m_defaultAutoPortRange;
|
|
|
|
m_config->deleteEntry("port_base_" + m_serviceName);
|
|
m_config->deleteEntry("auto_port_range_"+m_serviceName);
|
|
}
|
|
|
|
m_config->sync();
|
|
if (m_enabled)
|
|
return acquirePort();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void PortListener::setEnabled(bool e) {
|
|
setEnabledInternal(e, TQDateTime());
|
|
}
|
|
|
|
void PortListener::setEnabledInternal(bool e, const TQDateTime &ex) {
|
|
m_config->setGroup("ListenerConfig");
|
|
m_config->writeEntry("enabled_" + m_serviceName, e);
|
|
m_config->writeEntry("enabled_expiration_"+m_serviceName, ex);
|
|
m_config->sync();
|
|
|
|
m_expirationTime = ex;
|
|
|
|
if (e) {
|
|
if (m_port < 0)
|
|
acquirePort();
|
|
m_enabled = m_port >= 0;
|
|
|
|
}
|
|
else {
|
|
freePort();
|
|
m_enabled = false;
|
|
}
|
|
}
|
|
|
|
void PortListener::setEnabled(const TQDateTime &ex) {
|
|
setEnabledInternal(true, ex);
|
|
}
|
|
|
|
bool PortListener::isServiceRegistrationEnabled() {
|
|
return m_registerService;
|
|
}
|
|
|
|
void PortListener::setServiceRegistrationEnabled(bool e) {
|
|
setServiceRegistrationEnabledInternal(e);
|
|
dnssdRegister(e && m_enabled);
|
|
m_config->setGroup("ListenerConfig");
|
|
m_config->writeEntry("enable_srvreg_" + m_serviceName, e);
|
|
m_config->sync();
|
|
}
|
|
|
|
void PortListener::setServiceRegistrationEnabledInternal(bool e) {
|
|
m_registerService = e;
|
|
|
|
if ((!m_srvreg) || m_serviceURL.isNull())
|
|
return;
|
|
if (m_serviceRegistered == (m_enabled && e))
|
|
return;
|
|
|
|
if (m_enabled && e) {
|
|
m_registeredServiceURLs = processServiceTemplate(m_serviceURL);
|
|
TQStringList attributes = processServiceTemplate(m_serviceAttributes);
|
|
TQStringList::Iterator it = m_registeredServiceURLs.begin();
|
|
TQStringList::Iterator it2 = attributes.begin();
|
|
while ((it != m_registeredServiceURLs.end()) &&
|
|
(it2 != attributes.end())) {
|
|
if (!m_srvreg->registerService(
|
|
*(it++),
|
|
*(it2++),
|
|
m_serviceLifetime))
|
|
kdDebug(7021) << "Failure registering SLP service (no slpd running?)"<< endl;
|
|
}
|
|
m_serviceRegistered = true;
|
|
// make lifetime 30s shorter, because the timeout is not precise
|
|
m_slpLifetimeEnd = TQDateTime::currentDateTime().addSecs(m_serviceLifetime-30);
|
|
} else {
|
|
TQStringList::Iterator it = m_registeredServiceURLs.begin();
|
|
while (it != m_registeredServiceURLs.end())
|
|
m_srvreg->unregisterService(*(it++));
|
|
m_serviceRegistered = false;
|
|
}
|
|
}
|
|
|
|
void PortListener::dnssdRegister(bool e) {
|
|
if (m_dnssdName.isNull() || m_dnssdType.isNull())
|
|
return;
|
|
if (m_dnssdRegistered == e)
|
|
return;
|
|
if (e) {
|
|
m_dnssdRegistered=true;
|
|
m_dnssdreg = new DNSSD::PublicService(m_dnssdName,m_dnssdType,m_port);
|
|
m_dnssdreg->setTextData(m_dnssdData);
|
|
m_dnssdreg->publishAsync();
|
|
} else {
|
|
m_dnssdRegistered=false;
|
|
delete m_dnssdreg;
|
|
m_dnssdreg=0;
|
|
}
|
|
}
|
|
|
|
void PortListener::refreshRegistration() {
|
|
if (m_serviceRegistered && (m_slpLifetimeEnd.addSecs(-90) < TQDateTime::currentDateTime())) {
|
|
setServiceRegistrationEnabledInternal(false);
|
|
setServiceRegistrationEnabledInternal(true);
|
|
}
|
|
}
|
|
|
|
TQDateTime PortListener::expiration() {
|
|
return m_expirationTime;
|
|
}
|
|
|
|
TQDateTime PortListener::serviceLifetimeEnd() {
|
|
if (m_serviceRegistered)
|
|
return m_slpLifetimeEnd;
|
|
else
|
|
return TQDateTime();
|
|
}
|
|
|
|
TQString PortListener::name() {
|
|
return m_serviceName;
|
|
}
|
|
|
|
PortListener::~PortListener() {
|
|
setServiceRegistrationEnabledInternal(false);
|
|
delete m_socket;
|
|
}
|
|
|
|
|
|
KInetD::KInetD(TQCString &n) :
|
|
KDEDModule(n)
|
|
{
|
|
m_config = new TDEConfig("kinetdrc");
|
|
m_srvreg = new KServiceRegistry();
|
|
if (!m_srvreg->available()) {
|
|
kdDebug(7021) << "SLP not available"<< endl;
|
|
delete m_srvreg;
|
|
m_srvreg = 0;
|
|
}
|
|
m_portListeners.setAutoDelete(true);
|
|
connect(&m_expirationTimer, TQT_SIGNAL(timeout()), TQT_SLOT(setExpirationTimer()));
|
|
connect(&m_portRetryTimer, TQT_SIGNAL(timeout()), TQT_SLOT(portRetryTimer()));
|
|
connect(&m_reregistrationTimer, TQT_SIGNAL(timeout()), TQT_SLOT(reregistrationTimer()));
|
|
loadServiceList();
|
|
}
|
|
|
|
void KInetD::loadServiceList()
|
|
{
|
|
m_portListeners.clear();
|
|
|
|
|
|
KService::List kinetdModules =
|
|
KServiceType::offers("KInetDModule");
|
|
for(KService::List::ConstIterator it = kinetdModules.begin();
|
|
it != kinetdModules.end();
|
|
it++) {
|
|
KService::Ptr s = *it;
|
|
PortListener *pl = new PortListener(s, m_config, m_srvreg);
|
|
if (pl->isValid())
|
|
m_portListeners.append(pl);
|
|
else
|
|
delete pl;
|
|
}
|
|
|
|
setExpirationTimer();
|
|
setPortRetryTimer(true);
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
void KInetD::expirationTimer() {
|
|
setExpirationTimer();
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
void KInetD::setExpirationTimer() {
|
|
TQDateTime nextEx = getNextExpirationTime(); // disables expired portlistener!
|
|
if (!nextEx.isNull())
|
|
m_expirationTimer.start(TQDateTime::currentDateTime().secsTo(nextEx)*1000 + 30000,
|
|
false);
|
|
else
|
|
m_expirationTimer.stop();
|
|
}
|
|
|
|
void KInetD::portRetryTimer() {
|
|
setPortRetryTimer(true);
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
void KInetD::setReregistrationTimer() {
|
|
TQDateTime d;
|
|
PortListener *pl = m_portListeners.first();
|
|
while (pl) {
|
|
TQDateTime d2 = pl->serviceLifetimeEnd();
|
|
if (!d2.isNull()) {
|
|
if (d2 < TQDateTime::currentDateTime()) {
|
|
m_reregistrationTimer.start(0, true);
|
|
return;
|
|
}
|
|
else if (d.isNull() || (d2 < d))
|
|
d = d2;
|
|
}
|
|
pl = m_portListeners.next();
|
|
}
|
|
|
|
if (!d.isNull()) {
|
|
int s = TQDateTime::currentDateTime().secsTo(d);
|
|
if (s < 30)
|
|
s = 30; // max frequency 30s
|
|
m_reregistrationTimer.start(s*1000, true);
|
|
}
|
|
else
|
|
m_reregistrationTimer.stop();
|
|
}
|
|
|
|
void KInetD::reregistrationTimer() {
|
|
PortListener *pl = m_portListeners.first();
|
|
while (pl) {
|
|
pl->refreshRegistration();
|
|
pl = m_portListeners.next();
|
|
}
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
void KInetD::setPortRetryTimer(bool retry) {
|
|
int unmappedPorts = 0;
|
|
|
|
PortListener *pl = m_portListeners.first();
|
|
while (pl) {
|
|
if (pl->isEnabled() && (pl->port() < 0))
|
|
if (retry) {
|
|
if (!pl->acquirePort())
|
|
unmappedPorts++;
|
|
}
|
|
else if (pl->port() < 0)
|
|
unmappedPorts++;
|
|
pl = m_portListeners.next();
|
|
}
|
|
|
|
if (unmappedPorts > 0)
|
|
m_portRetryTimer.start(30000, false);
|
|
else
|
|
m_portRetryTimer.stop();
|
|
}
|
|
|
|
PortListener *KInetD::getListenerByName(TQString name)
|
|
{
|
|
PortListener *pl = m_portListeners.first();
|
|
while (pl) {
|
|
if (pl->name() == name)
|
|
return pl;
|
|
pl = m_portListeners.next();
|
|
}
|
|
return pl;
|
|
}
|
|
|
|
// gets next expiration timer, SIDEEFFECT: disables expired portlisteners while doing this
|
|
TQDateTime KInetD::getNextExpirationTime()
|
|
{
|
|
PortListener *pl = m_portListeners.first();
|
|
TQDateTime d;
|
|
while (pl) {
|
|
TQDateTime d2 = pl->expiration();
|
|
if (!d2.isNull()) {
|
|
if (d2 < TQDateTime::currentDateTime())
|
|
pl->setEnabled(false);
|
|
else if (d.isNull() || (d2 < d))
|
|
d = d2;
|
|
}
|
|
pl = m_portListeners.next();
|
|
}
|
|
return d;
|
|
}
|
|
|
|
TQStringList KInetD::services()
|
|
{
|
|
TQStringList list;
|
|
PortListener *pl = m_portListeners.first();
|
|
while (pl) {
|
|
list.append(pl->name());
|
|
pl = m_portListeners.next();
|
|
}
|
|
return list;
|
|
}
|
|
|
|
bool KInetD::isEnabled(TQString service)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return false;
|
|
|
|
return pl->isEnabled();
|
|
}
|
|
|
|
int KInetD::port(TQString service)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return -1;
|
|
|
|
return pl->port();
|
|
}
|
|
|
|
bool KInetD::setPort(TQString service, int port, int autoPortRange)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return false;
|
|
|
|
bool s = pl->setPort(port, autoPortRange);
|
|
setPortRetryTimer(false);
|
|
setReregistrationTimer();
|
|
return s;
|
|
}
|
|
|
|
bool KInetD::isInstalled(TQString service)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
return (pl != 0);
|
|
}
|
|
|
|
void KInetD::setEnabled(TQString service, bool enable)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return;
|
|
|
|
pl->setEnabled(enable);
|
|
setExpirationTimer();
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
void KInetD::setEnabled(TQString service, TQDateTime expiration)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return;
|
|
|
|
pl->setEnabled(expiration);
|
|
setExpirationTimer();
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
void KInetD::setServiceRegistrationEnabled(TQString service, bool enable)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return;
|
|
|
|
pl->setServiceRegistrationEnabled(enable);
|
|
setReregistrationTimer();
|
|
}
|
|
|
|
bool KInetD::isServiceRegistrationEnabled(TQString service)
|
|
{
|
|
PortListener *pl = getListenerByName(service);
|
|
if (!pl)
|
|
return false;
|
|
|
|
return pl->isServiceRegistrationEnabled();
|
|
}
|
|
|
|
KInetD::~KInetD() {
|
|
m_portListeners.clear();
|
|
delete m_config;
|
|
if (m_srvreg)
|
|
delete m_srvreg;
|
|
}
|
|
|
|
extern "C" {
|
|
KDE_EXPORT KDEDModule *create_kinetd(TQCString &name)
|
|
{
|
|
TDEGlobal::locale()->insertCatalogue("kinetd");
|
|
return new KInetD(name);
|
|
}
|
|
}
|