/* -*- c++ -*- * * mmserver.cpp * * Copyright (C) 2003 Petter E. Stokke * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "mmserver.h" #include "mmconnection.h" #include "version.h" #include "hostmanager.h" #include "serverinfo.h" #include "clientinfo.h" #include #include #include #include #include #include #include #include #include #include ConsoleStatusCallback::ConsoleStatusCallback(QObject* parent) : QObject(parent) { } void ConsoleStatusCallback::callback(const QString&, const QString& res) { int ul = -1, dl = -1; QString nick; QRegExp nickRe("client_name += +(.+)$"); QRegExp ulRe("max_hard_upload_rate += +([0-9]+)"); QRegExp dlRe("max_hard_download_rate += +([0-9]+)"); QStringList lines = QStringList::split("\n", res); QStringList::Iterator it; for (it = lines.begin(); it != lines.end(); ++it) { if (ulRe.search(*it) != -1) ul = ulRe.cap(1).toInt(); else if (dlRe.search(*it) != -1) dl = dlRe.cap(1).toInt(); else if (nickRe.search(*it) != -1) nick = nickRe.cap(1); } emit updatedInfo(nick, ul, dl); deleteLater(); } MMServer::MMServer(const QString& listenAddress, int listenPort, const QString& host, const QString& password) : KExtendedSocket(listenAddress, listenPort, KExtendedSocket::passiveSocket | KExtendedSocket::inetSocket) , m_donkeyHost(host) , m_useFakeContentType(false) , m_sessionID(0) , m_dwBlocked(0) , m_cPWFailed(0) , m_currentServer(0) { m_password = password; hostManager = new HostManager(this); donkey = new DonkeyProtocol(true, this); QObject::connect(donkey, SIGNAL(signalDisconnected(int)), this, SLOT(donkeyDisconnected(int))); QObject::connect(donkey, SIGNAL(signalConnected()), this, SLOT(donkeyConnected())); QObject::connect(donkey, SIGNAL(clientStats(int64, int64, int64, int, int, int, int, int, int, int, QMap*)), this, SLOT(clientStats(int64, int64, int64, int, int, int, int, int, int, int, QMap*))); QObject::connect(donkey, SIGNAL(updatedConnectedServers()), this, SLOT(updatedConnectedServers())); QObject::connect(hostManager, SIGNAL(hostListUpdated()), this, SLOT(hostListUpdated())); hostListUpdated(); kdDebug(7020) << "MMServer::MMServer(\"" << listenAddress << "\", " << listenPort << ");" << endl; setAddressReusable(true); QObject::connect(this, SIGNAL(readyAccept()), this, SLOT(incomingConnection())); if (listen()) { kdDebug(7020) << "Failed to bind socket." << endl; return; } kdDebug(7020) << "Socket is listening." << endl; } void MMServer::hostListUpdated() { if (!m_donkeyHost.isNull() && hostManager->validHostName(m_donkeyHost)) donkey->setHost( hostManager->hostProperties(m_donkeyHost) ); else donkey->setHost( hostManager->defaultHost() ); donkey->connectToCore(); } void MMServer::donkeyConnected() { m_donkeyConnected = true; } void MMServer::donkeyDisconnected(int) { m_donkeyConnected = false; } void MMServer::clientStats(int64 ul, int64 dl, int64 sh, int nsh, int tul, int tdl, int uul, int udl, int ndl, int ncp, QMap* nws) { m_uploaded = ul; m_downloaded = dl; m_shared = sh; m_sharedFiles = nsh; m_tcpUpload = tul; m_tcpDownload = tdl; m_udpUpload = uul; m_udpDownload = udl; m_downloadingFiles = ndl; m_completedFiles = ncp; m_networks = *nws; /* kdDebug(7020) << "ul " << (int)ul << " dl " << (int)dl << " sh " << (int)sh << " nsh " << nsh << " tul " << tul << " tdl " << tdl << " uul " << uul << " udl " << udl << " ndl " << ndl << " ncp " << ncp << endl; */ donkey->updateConnectedServers(); donkey->updateDownloadFiles(); donkey->updateDownloadedFiles(); ConsoleStatusCallback* cb = new ConsoleStatusCallback(this); QObject::connect(cb, SIGNAL(updatedInfo(const QString&,int,int)), SLOT(updatedOptionInfo(const QString&,int,int))); donkey->sendConsoleMessage("vo", cb); } void MMServer::updatedOptionInfo(const QString& nick, int ul, int dl) { m_nick = nick; m_maxUpload = ul; m_maxDownload = dl; } void MMServer::updatedConnectedServers() { const ServerInfo* si = 0; int score = 0; const QIntDict& s = donkey->connectedServers(); QIntDictIterator it(s); for (; it.current(); ++it) { // kdDebug(7020) << "Server " << it.currentKey() << ": " << it.current()->serverScore() << " " << it.current()->serverName() << endl; if (!si || it.current()->serverScore() > score) si = it.current(); } m_currentServer = si; } void MMServer::incomingConnection() { kdDebug(7020) << "Inbound connection." << endl; KExtendedSocket* foo; if (accept(foo)) { kdDebug(7020) << "Accept failed." << endl; return; } kdDebug(7020) << "Connection accepted." << endl; if (m_donkeyConnected) QObject::connect(new MMConnection(foo, this), SIGNAL(processMessage(MMConnection*, MMPacket*)), SLOT(processMessage(MMConnection*, MMPacket*))); else { QString out; out = "HTTP/1.1 404 Not Found\r\n"; out += QString("Server: KMLDonkeyMobileMule/%1\r\n").arg(KMLDONKEY_VERSION); out += "Connection: close\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"; out += "\r\n"; out += "404 Not Found\r\n"; out += "

404 Not Found

MobileMule is currently disconnected from the MLDonkey core.

\r\n"; QCString data = out.utf8(); foo->writeBlock((const char*)data, data.length()); foo->flush(); foo->closeNow(); foo->deleteLater(); } } const char* MMServer::getContentType() { return m_useFakeContentType ? "image/vnd.wap.wbmp" : "application/octet-stream"; } void MMServer::processMessage(MMConnection* sender, MMPacket* p) { int16 sid = p->readShort(); if ( (m_sessionID && sid != m_sessionID) && p->opcode() != MMP_HELLO) { sender->sendPacket(MMPacket(MMP_INVALIDID)); m_sessionID = 0; return; } kdDebug(7020) << "Received message, opcode " << (int)p->opcode() << " sid " << sid << endl; switch (p->opcode()) { case MMP_HELLO: processHelloPacket(p, sender); break; case MMP_FILECOMMANDREQ: processFileCommand(p, sender); break; case MMP_FILEDETAILREQ: processDetailRequest(p, sender); break; case MMP_COMMANDREQ: processCommandRequest(p, sender); break; case MMP_SEARCHREQ: processSearchRequest(p, sender); break; case MMP_DOWNLOADREQ: processDownloadRequest(p, sender); break; case MMP_PREVIEWREQ: processPreviewRequest(p, sender); break; case MMP_CHANGELIMIT: processChangeLimitRequest(p, sender); break; case MMP_STATUSREQ: processStatusRequest(sender); break; case MMP_FILELISTREQ: processFileListRequest(sender); break; case MMP_FINISHEDREQ: processFinishedListRequest(sender); break; default: sender->sendPacket(MMPacket(MMP_GENERALERROR)); break; } } void MMServer::processHelloPacket(MMPacket* p, MMConnection* sender) { kdDebug(7020) << "processHelloPacket()" << endl; MMPacket* packet = new MMPacket(MMP_HELLOANS); if (p->readByte() != MM_VERSION) { packet->writeByte(MMT_WRONGVERSION); sender->sendPacket(packet); return; } if (m_dwBlocked && m_dwBlocked > time(0)) { packet->writeByte(MMT_WRONGPASSWORD); sender->sendPacket(packet); return; } QString password = p->readString(); if (password != m_password) { m_dwBlocked = 0; packet->writeByte(MMT_WRONGPASSWORD); sender->sendPacket(packet); m_cPWFailed++; if (m_cPWFailed == 3) { kdDebug(7020) << "3 failed logins for MobileMule logged - any further attempt is blocked for 10 min!" << endl; m_cPWFailed = 0; m_dwBlocked = time(0) + MMS_BLOCKTIME; } return; } m_useFakeContentType = (p->readByte() != 0); packet->writeByte(MMT_OK); m_sessionID = (int16)KApplication::random(); kdDebug(7020) << "Logged in successfully, sid set to " << m_sessionID << endl; packet->writeShort(m_sessionID); packet->writeString(m_nick); packet->writeShort(m_maxUpload); packet->writeShort(m_maxDownload); processStatusRequest(sender, packet); } void MMServer::processStatusRequest(MMConnection* sender, MMPacket* packet) { if (!packet) packet = new MMPacket(MMP_STATUSANSWER); else packet->writeByte(MMP_STATUSANSWER); packet->writeShort((int16)((m_tcpUpload + m_udpUpload) / 100)); packet->writeShort((int16)((m_maxUpload * 1024) / 100)); packet->writeShort((int16)((m_tcpDownload + m_udpDownload) / 100)); packet->writeShort((int16)((m_maxDownload * 1024) / 100)); QIntDictIterator it(donkey->downloadFiles()); int8 d = 0, p = 0; for (; it.current(); ++it) { if (it.current()->fileState() == FileInfo::Paused) p++; else d++; } packet->writeByte(d); packet->writeByte(p); packet->writeInt((int32)(m_downloaded / 1048576)); packet->writeShort((int16)((m_tcpDownload + m_udpDownload) / 100)); // should be average dl rate, not current if (m_currentServer) { packet->writeByte(2); // 1 if LowID or 2 otherwise, but there's no way to tell packet->writeInt(m_currentServer->serverNUsers()); } else { packet->writeByte(0); packet->writeInt(0); } sender->sendPacket(packet); } void MMServer::processFileListRequest(MMConnection* sender, MMPacket* packet) { if (!packet) packet = new MMPacket(MMP_FILELISTANS); else packet->writeByte(MMP_FILELISTANS); packet->writeByte(1); // Number of categories, followed by this number of strings containing category titles packet->writeString(i18n("the generic file category name", "All")); const QIntDict& l = donkey->downloadFiles(); packet->writeByte(l.count()); QIntDictIterator lit(l); m_sentFileList.clear(); for (; lit.current(); ++lit) { FileInfo* fi = lit.current(); if (fi->fileState() == FileInfo::Paused) packet->writeByte(MMT_PAUSED); else { if (fi->fileSpeed()) packet->writeByte(MMT_DOWNLOADING); else packet->writeByte(MMT_WAITING); } packet->writeString(fi->fileName()); packet->writeByte(0); // The file's category, 0 means no category (I hope) m_sentFileList.append(*fi); } sender->sendPacket(packet); } void MMServer::processFinishedListRequest(MMConnection* sender) { MMPacket* packet = new MMPacket(MMP_FINISHEDANS); packet->writeByte(1); // Number of categories, followed by this number of strings containing category titles packet->writeString(i18n("the generic file category name", "All")); const QIntDict& l = donkey->downloadedFiles(); packet->writeByte(l.count()); QIntDictIterator lit(l); m_sentFinishedList.clear(); for (; lit.current(); ++lit) { FileInfo* fi = lit.current(); packet->writeByte(0xFF); // I guess this means the file's status is finished packet->writeString(fi->fileName()); packet->writeByte(0); // Category m_sentFinishedList.append(*fi); } sender->sendPacket(packet); } void MMServer::processFileCommand(MMPacket* data, MMConnection* sender) { int8 byCommand = data->readByte(); int8 byFileIndex = data->readByte(); if (byFileIndex >= m_sentFileList.size()) { sender->sendPacket(MMPacket(MMP_GENERALERROR)); return; } const FileInfo& fi = m_sentFileList[byFileIndex]; switch (byCommand) { case MMT_PAUSE: donkey->pauseFile(fi.fileNo(), true); break; case MMT_RESUME: donkey->pauseFile(fi.fileNo(), false); break; case MMT_CANCEL: donkey->cancelFile(fi.fileNo()); break; default: sender->sendPacket(MMPacket(MMP_GENERALERROR)); return; } MMPacket* packet = new MMPacket(MMP_FILECOMMANDANS); processFileListRequest(sender, packet); } void MMServer::processDetailRequest(MMPacket* data, MMConnection* sender) { int8 byFileIndex = data->readByte(); if (byFileIndex >= m_sentFileList.size()) { sender->sendPacket(MMPacket(MMP_GENERALERROR)); return; } FileInfo* fi = donkey->findDownloadFileNo(m_sentFileList[byFileIndex].fileNo()); MMPacket* packet = new MMPacket(MMP_FILEDETAILANS); packet->writeInt(fi->fileSize()); packet->writeInt(fi->fileDownloaded()); packet->writeInt(fi->fileDownloaded()); packet->writeShort((int)fi->fileSpeed() / 100); packet->writeShort(fi->fileSources().size()); QValueList clients = fi->fileSources().keys(); QValueListIterator clit; int dlct = 0; for (clit = clients.begin(); clit != clients.end(); ++clit) { ClientInfo* ci = donkey->findClientNo(*clit); if (!ci) continue; if (ci->clientState() == ClientInfo::Downloading) dlct++; } packet->writeShort(dlct); packet->writeByte(fi->filePriority() < 0 ? 1 : (fi->filePriority() > 0 ? 3 : 2)); packet->writeByte(fi->fileChunks().size()); packet->writeByteArray(fi->fileChunks()); sender->sendPacket(packet); } void MMServer::processCommandRequest(MMPacket* data, MMConnection* sender) { int8 byCommand = data->readByte(); switch (byCommand) { case MMT_SDEMULE: donkey->killCore(); break; case MMT_SDPC: // Shutdown PC. I don't think I want to implement this at all. break; case MMT_SERVERCONNECT: donkey->connectMoreServers(); break; default: sender->sendPacket(MMPacket(MMP_GENERALERROR)); return; } sender->sendPacket(MMPacket(MMP_COMMANDANS)); } void MMServer::processSearchRequest(MMPacket* data, MMConnection* sender) { QString strSearch(data->readString()); int8 byType = data->readByte(); QString strLocalSearchType; switch (byType) { case 0: strLocalSearchType = ""; break; case 1: strLocalSearchType = "Program"; // FIXME: Should be archive break; case 2: strLocalSearchType = "Audio"; break; case 3: strLocalSearchType = "Image"; break; case 4: strLocalSearchType = "Program"; break; case 5: strLocalSearchType = "Video"; break; default: strLocalSearchType = ""; break; } if (!m_currentServer) { MMPacket packet(MMP_SEARCHANS); packet.writeByte(MMT_NOTCONNECTED); sender->sendPacket(packet); return; } // FIXME: I'm too lazy to implement searching right now. sender->sendPacket(MMPacket(MMP_GENERALERROR)); } void MMServer::processChangeLimitRequest(MMPacket* data, MMConnection* sender) { int16 nNewUpload = data->readShort(); int16 nNewDownload = data->readShort(); donkey->setOption("max_hard_upload_rate", QString::number(nNewUpload)); donkey->setOption("max_hard_download_rate", QString::number(nNewDownload)); MMPacket packet(MMP_CHANGELIMITANS); packet.writeShort(nNewUpload); // Report the new rates; assume they were successfully changed packet.writeShort(nNewDownload); sender->sendPacket(packet); } void MMServer::processDownloadRequest(MMPacket*, MMConnection* sender) { // FIXME: Implement searching. sender->sendPacket(MMPacket(MMP_GENERALERROR)); } void MMServer::processPreviewRequest(MMPacket*, MMConnection* sender) { // FIXME: Implement previewing. sender->sendPacket(MMPacket(MMP_GENERALERROR)); } #include "mmserver.moc"