/* -*- c++ -*- * * kio_mldonkey.cpp * * Copyright (C) 2003, 2004 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. * */ // This is the protocol version that the local code implements. // Note that this is distinct from what DonkeyProtocol implements. #define IMPLEMENTED_PROTOCOL_VERSION 26 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "donkeyprotocol.h" #include "donkeyhost.h" #include "kio_mldonkey.h" using namespace KIO; MLDonkeyURL::MLDonkeyURL(const KURL& url) : original(url) { m_isValid = m_isRoot = m_isHost = m_isPath = m_isFile = false; if (url.hasHost() || url.hasUser() || url.hasPass() || url.hasRef() || url.hasSubURL() || !url.queryItems().isEmpty() || !url.hasPath()) return; QString path = url.path(); if (path.isEmpty() || path == "/") { m_isValid = m_isRoot = true; return; } QRegExp hre("/([^/]+)/?"); if (hre.exactMatch(path)) { m_isValid = m_isHost = true; m_host = hre.cap(1); return; } QRegExp pre("/([^/]+)/([^/]+)/?"); if (pre.exactMatch(path)) { m_isValid = m_isPath = true; m_host = pre.cap(1); m_path = pre.cap(2); return; } QRegExp fre("/([^/]+)/([^/]+)/(.+)"); if (fre.exactMatch(path)) { m_isValid = m_isFile = true; m_host = fre.cap(1); m_path = fre.cap(2); m_fileName = fre.cap(3); return; } } UDSEntry constructUDSEntry(const QString& name, mode_t type, off_t size, time_t ctime = 0, time_t mtime = 0, time_t atime = 0) { UDSEntry entry; UDSAtom a_name; a_name.m_uds = KIO::UDS_NAME; a_name.m_str = name; entry.append(a_name); UDSAtom a_type; a_type.m_uds = KIO::UDS_FILE_TYPE; a_type.m_long = type; entry.append(a_type); UDSAtom a_size; a_size.m_uds = KIO::UDS_SIZE; a_size.m_long = size; entry.append(a_size); UDSAtom a_date; a_date.m_uds = KIO::UDS_CREATION_TIME; a_date.m_long = ctime; entry.append(a_date); a_date.m_uds = KIO::UDS_MODIFICATION_TIME; a_date.m_long = mtime; entry.append(a_date); a_date.m_uds = KIO::UDS_ACCESS_TIME; a_date.m_long = atime; entry.append(a_date); return entry; } UDSEntry constructUDSEntry(const FileInfo& fi) { return constructUDSEntry(fi.fileName(), S_IFREG, fi.fileSize(), (time_t)fi.fileAge(), (time_t)fi.fileAge(), time(0) - (time_t)fi.fileLastSeen() ); } DonkeyMessage* MLDonkeyProtocol::readMessage() { unsigned char szbuf[4]; if (KSocks::self()->read(sock->fd(), szbuf, 4) != 4) { error(ERR_CONNECTION_BROKEN, QString::null); return 0; } int sz = (int)szbuf[0]; sz |= ((int)szbuf[1]) << 8; sz |= ((int)szbuf[2]) << 16; sz |= ((int)szbuf[3]) << 24; char* buf = (char*)malloc(sz); if (!buf) { kdDebug(7166) << "Oops, out of memory!" << endl; error(ERR_OUT_OF_MEMORY, QString::null); return 0; } char* p = buf; int r, c = 0; while (c < sz) { r = KSocks::self()->read(sock->fd(), p, sz - c); if (r <= 0) { kdDebug(7166) << "Read error." << endl; error(ERR_CONNECTION_BROKEN, QString::null); free(buf); return 0; } p += r; c += r; } DonkeyMessage* msg = new DonkeyMessage(buf, sz); free(buf); // kdDebug(7166) << "Received message:" << endl << msg->dumpArray() << endl; return msg; } bool MLDonkeyProtocol::sendMessage(DonkeyMessage* msg) { unsigned char op[4]; int sz = msg->size() + 2; op[0] = (unsigned char)(sz & 0xff); op[1] = (unsigned char)((sz >> 8) & 0xff); op[2] = (unsigned char)((sz >> 16) & 0xff); op[3] = (unsigned char)((sz >> 24) & 0xff); if (KSocks::self()->write(sock->fd(), op, 4) != 4) { error(ERR_CONNECTION_BROKEN, QString::null); return false; } op[0] = (unsigned char)(msg->opcode() & 0xff); op[1] = (unsigned char)((msg->opcode() >> 8) & 0xff); if (KSocks::self()->write(sock->fd(), op, 2) != 2) { error(ERR_CONNECTION_BROKEN, QString::null); return false; } if (KSocks::self()->write(sock->fd(), msg->data(), msg->size()) != (signed long int)msg->size()) { error(ERR_CONNECTION_BROKEN, QString::null); return false; } return true; } bool MLDonkeyProtocol::connectSock(DonkeyHost* host) { kdDebug(7166) << "MLDonkeyProtocol::connectSock(\"" << host->name() << "\")" << endl; int on = 1; currentHost = QString::null; kdDebug(7166) << "Constructing socket..." << endl; sock = new KExtendedSocket(host->address(), host->port(), KExtendedSocket::inetSocket); if (!sock) { error(ERR_OUT_OF_MEMORY, QString::null); return false; } sock->setTimeout(connectTimeout()); kdDebug(7166) << "Connecting to " << host->address() << ":" << host->port() << "..." << endl; if (sock->connect() < 0) { if (sock->status() == IO_LookupError) { error(ERR_UNKNOWN_HOST, host->address()); } else { error(ERR_COULD_NOT_CONNECT, host->address()); } kdDebug(7166) << "Connection failed." << endl; delete sock; sock = NULL; return false; } int sno = sock->fd(); if (setsockopt(sno, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on)) == -1) { kdDebug() << "Failed to set socket options." << endl; delete sock; sock = NULL; error(ERR_COULD_NOT_CREATE_SOCKET, host->address()); return false; } proto = 0; bool auth = false; kdDebug() << "Socked connected, establishing protocol..." << endl; while (!auth) { DonkeyMessage *out, *msg = readMessage(); if (!msg) { disconnectSock(); return false; } switch (msg->opcode()) { case DonkeyProtocol::CoreProtocol: proto = msg->readInt32(); kdDebug(7166) << "CoreProtocol message, version " << proto << endl; if (proto < MIN_PROTOCOL_VERSION) { kdDebug(7166) << "Obsolete protocol version." << endl; error(ERR_UPGRADE_REQUIRED, "This MLDonkey is too old!"); delete msg; disconnectSock(); return false; } out = new DonkeyMessage(DonkeyProtocol::GuiProtocol); out->writeInt32(IMPLEMENTED_PROTOCOL_VERSION); if (proto > IMPLEMENTED_PROTOCOL_VERSION) proto = IMPLEMENTED_PROTOCOL_VERSION; if (!sendMessage(out)) { delete out; delete msg; disconnectSock(); return false; } delete out; out = new DonkeyMessage(DonkeyProtocol::GuiExtensions); out->writeInt16(1); out->writeInt32(1); out->writeInt8(1); if (!sendMessage(out)) { delete out; delete msg; disconnectSock(); return false; } delete out; out = new DonkeyMessage(DonkeyProtocol::Password); out->writeString(host->password()); out->writeString(host->username()); if (!sendMessage(out)) { delete out; delete msg; disconnectSock(); return false; } delete out; break; case DonkeyProtocol::BadPassword: kdDebug(7166) << "Authentication failure." << endl; delete msg; disconnectSock(); error(ERR_ACCESS_DENIED, host->address()); return false; case DonkeyProtocol::Console: auth = true; kdDebug(7166) << "Authenticated successfully." << endl; break; default: break; } delete msg; } kdDebug(7166) << "Successfully connected and authenticated." << endl; currentHost = host->name(); return true; } void MLDonkeyProtocol::disconnectSock() { if (sock) { kdDebug(7166) << "MLDonkeyProtocol::disconnectSock() -> socket closed." << endl; delete sock; sock = NULL; currentHost = QString::null; } } bool MLDonkeyProtocol::connectDonkey(const QString& hostName) { if (!hostManager->validHostName(hostName)) { kdDebug(7166) << "Bad host name \"" << hostName << "\"" << endl; error(ERR_DOES_NOT_EXIST, hostName); return false; } if (currentHost == hostName && sock->socketStatus() == KExtendedSocket::connected) { kdDebug(7166) << "Reusing connected socket for \"" << currentHost << "\"" << endl; return true; } disconnectSock(); return connectSock( (DonkeyHost*)hostManager->hostProperties(hostName) ); } bool MLDonkeyProtocol::readDownloads(const QString& hostName) { kdDebug(7166) << "MLDonkeyProtocol::readDownloads(\"" << hostName << "\")" << endl; if (!connectDonkey(hostName)) return false; kdDebug(7166) << "readDownloads: connected." << endl; bool gotdl = false; DonkeyMessage out(DonkeyProtocol::GetDownloadFiles); if (!sendMessage(&out)) { kdDebug(7166) << "Failed to send GetDownloadFiles message." << endl; disconnectSock(); return false; } kdDebug(7166) << "readDownloads: waiting for file info." << endl; DonkeyMessage* msg; while (!gotdl) { if (!(msg = readMessage())) { disconnectSock(); return false; } int i, j; switch (msg->opcode()) { case DonkeyProtocol::DownloadFiles_v4: case DonkeyProtocol::DownloadFiles: j = msg->readInt16(); for (i=0; iopcode()) { case DonkeyProtocol::DownloadedFiles_v2: case DonkeyProtocol::DownloadedFiles: j = msg->readInt16(); for (i=0; iopcode()) { case DonkeyProtocol::DownloadFiles_v4: case DonkeyProtocol::DownloadFiles: j = msg->readInt16(); for (i=0; iopcode()) { case DonkeyProtocol::DownloadedFiles_v2: case DonkeyProtocol::DownloadedFiles: j = msg->readInt16(); for (i=0; ihostProperties(mu.host()); const FileInfo* fi = 0; if (mu.path() == "downloading") fi = statDownload(mu); else if (mu.path() == "complete") fi = statDownloaded(mu); if (!fi) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } KURL path; path.setProtocol("http"); path.setHost("localhost"); path.setPort(37435); path.setPath("/"); path.addPath(host->name()); path.addPath(host->username()); path.addPath(host->password()); path.addPath(QString::number(fi->fileNo())); kdDebug(7166) << "Redirected path = \"" << path.url() << "\"" << endl; redirection(path); finished(); } void MLDonkeyProtocol::stat(const KURL& url ) { kdDebug(7166) << "kio_mldonkey::stat(const KURL& url = \"" << url.url() << "\")" << endl ; kdDebug(7166) << "Path is \"" << url.path() << "\"" << endl; if (url.hasHost()) { error(KIO::ERR_UNKNOWN_HOST, url.host()); return; } MLDonkeyURL mu(url); if (!mu.isValid()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } kdDebug(7166) << "Decoded path is \"" << mu.path() << "\"" << endl; if (mu.isRoot()) { statEntry(constructUDSEntry(QString::null, S_IFDIR, 0)); finished(); return; } if (mu.isHost()) { if (!hostManager->validHostName(mu.host())) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } statEntry(constructUDSEntry(mu.host(), S_IFDIR, 0)); finished(); return; } if (mu.isPath()) { if (!hostManager->validHostName(mu.host())) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (mu.path() == "downloading" || mu.path() == "complete") { statEntry(constructUDSEntry(mu.path(), S_IFDIR, 0)); finished(); return; } else { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } } if (mu.isFile()) { if (!hostManager->validHostName(mu.host())) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (mu.path() == "downloading") { const FileInfo* fi = statDownload(mu); if (!fi) return; statEntry(constructUDSEntry(*fi)); finished(); return; } else if (mu.path() == "complete") { const FileInfo* fi = statDownloaded(mu); if (!fi) return; statEntry(constructUDSEntry(*fi)); finished(); return; } else { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } } error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } void MLDonkeyProtocol::listDir(const KURL& url) { kdDebug(7166) << "kio_mldonkey::listDir(const KURL& url = \"" << url.url() << "\")" << endl; kdDebug(7166) << "Path is \"" << url.path() << "\"" << endl; if (url.hasHost()) { error(KIO::ERR_UNKNOWN_HOST, url.host()); return; } MLDonkeyURL mu(url); if (!mu.isValid()) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (mu.isFile()) { error(KIO::ERR_IS_FILE, url.path()); return; } if (mu.isRoot()) { QStringList hostNames(hostManager->hostList()); totalSize(hostNames.count()); QStringList::Iterator it; for (it = hostNames.begin(); it != hostNames.end(); ++it) if (hostManager->validHostName(*it)) listEntry(constructUDSEntry(*it, S_IFDIR, 0), false); listEntry(UDSEntry(), true); finished(); return; } if (mu.isHost()) { if (!hostManager->validHostName(mu.host())) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } listEntry(constructUDSEntry("downloading", S_IFDIR, 0), false); listEntry(constructUDSEntry("complete", S_IFDIR, 0), false); listEntry(UDSEntry(), true); finished(); return; } if (mu.isPath()) { if (!hostManager->validHostName(mu.host())) { kdDebug(7166) << "Bad host name \"" << mu.host() << "\"" << endl; error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (mu.path() == "downloading") { kdDebug(7166) << "Reading path \"" << mu.path() << "\"..." << endl; if (!readDownloads(mu.host())) return; finished(); return; } if (mu.path() == "complete") { kdDebug(7166) << "Reading path \"" << mu.path() << "\"..." << endl; if (!readComplete(mu.host())) return; finished(); return; } else { kdDebug(7166) << "Unknown path \"" << mu.host() << "\" : \"" << mu.path() << "\"" << endl; error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } } error(KIO::ERR_DOES_NOT_EXIST, url.path()); } extern "C" { int kdemain(int argc, char **argv) { KInstance instance( "kio_mldonkey" ); kdDebug(7166) << "*** Starting kio_mldonkey " << endl; if (argc != 4) { kdDebug(7166) << "Usage: kio_mldonkey protocol domain-socket1 domain-socket2" << endl; exit(-1); } MLDonkeyProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); kdDebug(7166) << "*** kio_mldonkey Done" << endl; return 0; } }