diff --git a/kioslave/smtp/smtp.cc b/kioslave/smtp/smtp.cc index ac9a9f06b..844fbda57 100644 --- a/kioslave/smtp/smtp.cc +++ b/kioslave/smtp/smtp.cc @@ -1,794 +1,802 @@ /* * Copyright (c) 2000, 2001 Alex Zepeda * Copyright (c) 2001 Michael Häckel * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id$ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smtp.h" //using namespace KIO; extern "C" { int kdemain(int argc, char **argv); } int kdemain(int argc, char **argv) { KInstance instance("kio_smtp"); if (argc != 4) { fprintf(stderr, "Usage: kio_smtp protocol domain-socket1 domain-socket2\n"); exit(-1); } bool useSSL = (strcmp(argv[1], "smtps") == 0) ? true : false; // We might as well allocate it on the heap. Since there's a heap o room there.. SMTPProtocol *slave = new SMTPProtocol(argv[2], argv[3], useSSL); slave->dispatchLoop(); delete slave; return 0; } SMTPProtocol::SMTPProtocol(const QCString & pool, const QCString & app, bool useSSL) : TCPSlaveBase(useSSL ? 465 : 25, useSSL ? "smtps" : "smtp", pool, app, useSSL), m_iOldPort(0), m_opened(false), m_haveTLS(false), m_errorSent(false) { //kdDebug() << "SMTPProtocol::SMTPProtocol" << endl; } SMTPProtocol::~SMTPProtocol() { //kdDebug() << "SMTPProtocol::~SMTPProtocol" << endl; smtp_close(); } void SMTPProtocol::openConnection() { if (smtp_open()) { connected(); } else { closeConnection(); } } void SMTPProtocol::closeConnection() { smtp_close(); } void SMTPProtocol::special(const QByteArray & aData) { QString result; if (m_haveTLS) result = " STARTTLS"; if (!m_sAuthConfig.isEmpty()) result += " " + m_sAuthConfig; infoMessage(result.mid(1)); finished(); } // Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah // If smtphost is the name of a profile, it'll use the information // provided by that profile. If it's not a profile name, it'll use it as // nature intended. // One can also specify in the query: // headers=0 (turns of header generation) // to=emailaddress // cc=emailaddress // bcc=emailaddress // subject=text // profile=text (this will override the "host" setting) // hostname=text (used in the HELO) void SMTPProtocol::put(const KURL & url, int /*permissions */ , bool /*overwrite */ , bool /*resume */ ) { QStringList query = QStringList::split('&', url.query().remove(0, 1)); QString subject = QString::fromLatin1("missing subject"); QString profile; QString from; QString localHostname; QStringList recip, bcc, cc, temp_list; bool headers = true; // sort out our query string for (QStringList::Iterator it = query.begin(); it != query.end(); ++it) { int equalsAt = (*it).find('='); if (equalsAt > 0 && equalsAt < (*it).length()) { QString key = (*it).left(equalsAt); QString value = KURL::decode_string((*it).right((*it).length() - (equalsAt + 1))); if (key == "to") { recip.append(value); } else if (key == "cc") { cc.append(value); } else if (key == "bcc") { bcc.append(value); } else if (key == "headers") { headers = (value == "0"); headers = false; } else if (key == "subject") { subject = value; } else if (key == "from") { from = value; } else if (key == "profile") { profile = value; } else if (key == "hostname") { localHostname = value; } } } KEMailSettings *mset = new KEMailSettings; KURL open_url = url; if (profile == QString::null) { //kdDebug() << "kio_smtp: Profile is null" << endl; QStringList profiles = mset->profiles(); bool hasProfile = false; for (QStringList::Iterator it = profiles.begin(); it != profiles.end(); ++it) { if ((*it) == open_url.host()) { hasProfile = true; break; } } if (hasProfile) { mset->setProfile(open_url.host()); open_url.setHost(mset->getSetting(KEMailSettings::OutServer)); m_sUser = mset->getSetting(KEMailSettings::OutServerLogin); m_sPass = mset->getSetting(KEMailSettings::OutServerPass); if (m_sUser.isEmpty()) m_sUser = QString::null; if (m_sPass.isEmpty()) m_sPass = QString::null; open_url.setUser(m_sUser); open_url.setPass(m_sPass); m_sServer = open_url.host(); m_iPort = open_url.port(); } else { mset->setProfile(mset->defaultProfileName()); } } else { mset->setProfile(profile); } // Check KEMailSettings to see if we've specified an E-Mail address // if that worked, check to see if we've specified a real name // and then format accordingly (either: emailaddress@host.com or // Real Name ) if (from.isEmpty()) { if (mset->getSetting(KEMailSettings::EmailAddress) != QString::null) { from = mset->getSetting(KEMailSettings::EmailAddress); } else { error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); smtp_close(); return; } } from.prepend(QString::fromLatin1("MAIL FROM: <")); from.append(QString::fromLatin1(">")); if (!smtp_open(localHostname)) { error(KIO::ERR_SERVICE_NOT_AVAILABLE, i18n("SMTPProtocol::smtp_open failed (%1)") .arg(open_url.path())); return; } if (!command(from)) { if (!m_errorSent) error(KIO::ERR_NO_CONTENT, i18n("The server didn't accept the " "sender address:\n%1").arg(m_lastError)); smtp_close(); return; } // Loop through our To and CC recipients, and send the proper // SMTP commands, for the benefit of the server. if (!PutRecipients(recip)) return; // To if (!PutRecipients(cc)) return; // Carbon Copy (CC) if (!PutRecipients(bcc)) return; // Blind Carbon Copy (BCC) // Begin sending the actual message contents (most headers+body) if (!command(QString::fromLatin1("DATA"))) { if (!m_errorSent) error(KIO::ERR_NO_CONTENT, i18n("The attempt to start sending the " "message content failed.\nThe server said:\n%1") .arg(m_lastError)); smtp_close(); return; } if (headers) { if (mset->getSetting(KEMailSettings::EmailAddress) != QString::null) { if (mset->getSetting(KEMailSettings::RealName) != QString::null) { from = QString::fromLatin1("From: %1 <%2>\r\n").arg(mset-> getSetting(KEMailSettings:: RealName)) .arg(mset->getSetting(KEMailSettings::EmailAddress)); } else { from = QString::fromLatin1("From: %1\r\n").arg(mset-> getSetting(KEMailSettings:: EmailAddress)); } } else { error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); smtp_close(); // TODO: do we _really_ need to close the connection here/ return; } write(from.latin1(), strlen(from.latin1())); subject = QString::fromLatin1("Subject: %1\r\n").arg(subject); write(subject.latin1(), strlen(subject.latin1())); // Write out the To header for the benefit of the mail clients QString header = QString::fromLatin1("To: %1\r\n"); for (QStringList::Iterator it = recip.begin(); it != recip.end(); ++it) { header = header.arg(*it); write(header.latin1(), strlen(header.latin1())); header = QString::fromLatin1("To: %1\r\n"); } // Write out the CC header for the benefit of the mail clients header = QString::fromLatin1("CC: %1\r\n"); for (QStringList::Iterator it = cc.begin(); it != cc.end(); ++it) { header = header.arg(*it); write(header.latin1(), strlen(header.latin1())); header = QString::fromLatin1("CC: %1\r\n"); } } delete mset; // Loop until we got 0 (end of data) int result; QByteArray buffer; do { dataReq(); // Request for data buffer.resize(0); result = readData(buffer); if (result > 0) { write(buffer.data(), buffer.size()); } else if (result < 0) { error(KIO::ERR_COULD_NOT_WRITE, open_url.path()); } } while (result > 0); write("\r\n.\r\n", 5); if (getResponse() >= SMTP_MIN_NEGATIVE_REPLY) { if (!m_errorSent) error(KIO::ERR_NO_CONTENT, i18n("The server didn't accept the message content:\n%1").arg(m_lastError)); smtp_close(); return; } command(QString::fromLatin1("RSET")); finished(); } bool SMTPProtocol::PutRecipients(QStringList & list) { QString formatted_recip = QString::fromLatin1("RCPT TO: <%1>"); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { if (!command(formatted_recip.arg(*it))) { if (!m_errorSent) { error(KIO::ERR_NO_CONTENT, i18n("The server didn't accept one of the recipients.\nIt said: %1") .arg(m_lastError)); } smtp_close(); return false; } } return true; } void SMTPProtocol::setHost(const QString & host, int port, const QString & user, const QString & pass) { m_sServer = host; m_iPort = port; m_sUser = user; m_sPass = pass; } int SMTPProtocol::getResponse(char *r_buf, unsigned int r_len) { char *buf = 0; ssize_t recv_len = 0, len; int retVal; m_lastError.truncate(0); m_errorSent = false; // Give the buffer the appropiate size // a buffer of less than 5 bytes will *not* work if (r_len) { buf = static_cast < char *>(calloc(r_len, sizeof(char))); len = r_len; } else { buf = static_cast < char *>(calloc(DEFAULT_RESPONSE_BUFFER, sizeof(char))); len = DEFAULT_RESPONSE_BUFFER; } waitForResponse(60); // And grab the data recv_len = readLine(buf, len - 1); if (recv_len < 1) { if (isConnectionValid()) error(KIO::ERR_SERVER_TIMEOUT, m_sServer); else error(KIO::ERR_CONNECTION_BROKEN, m_sServer); m_errorSent = true; free(buf); return 999; } if (recv_len < 4) { error(KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response received.")); m_errorSent = true; free(buf); return 999; } if (buf[3] == '-') { // Multiline response char *origbuf = buf; while ((buf[3] == '-') && (len - recv_len > 3)) { // Three is quite arbitrary buf += recv_len; len -= (recv_len + 1); waitForResponse(60); recv_len = readLine(buf, len - 1); if (recv_len < 1) { if (isConnectionValid()) error(KIO::ERR_SERVER_TIMEOUT, m_sServer); else error(KIO::ERR_CONNECTION_BROKEN, m_sServer); m_errorSent = true; free(buf); return 999; } if (recv_len < 4) { error(KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response received.")); m_errorSent = true; free(buf); return 999; } } buf = origbuf; int bufLen = strlen(buf); if (r_len) { memcpy(r_buf, buf, bufLen); r_buf[r_len - 1] = '\0'; } m_lastError = QCString(buf, bufLen); } else { // single line response if (r_len && recv_len > 4) { memcpy(r_buf, buf + 4, recv_len - 4); } m_lastError = QCString(buf + 4, recv_len - 4); } retVal = GetVal(buf); if (retVal == -1) { if (!isConnectionValid()) error(KIO::ERR_CONNECTION_BROKEN, m_sServer); else error(KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response received: %1").arg(m_lastError)); m_errorSent = true; free(buf); return 999; } free(buf); return retVal; } bool SMTPProtocol::command(const QString & cmd, char *recv_buf, unsigned int len) { QCString write_buf = cmd.latin1(); write_buf += "\r\n"; // Write the command if (write(static_cast < const char *>(write_buf), write_buf.length()) != static_cast < ssize_t > (write_buf.length())) { return false; } return (getResponse(recv_buf, len) < SMTP_MIN_NEGATIVE_REPLY); } bool SMTPProtocol::smtp_open(const QString& fakeHostname) { if (m_opened && m_iOldPort == port(m_iPort) && m_sOldServer == m_sServer && m_sOldUser == m_sUser && (fakeHostname == QString::null || m_hostname == fakeHostname)) { return true; } else { smtp_close(); if (!connectToHost(m_sServer.latin1(), m_iPort)) return false; // connectToHost has already send an error message. m_opened = true; } if (getResponse() >= SMTP_MIN_NEGATIVE_REPLY) { if (!m_errorSent) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("The server didn't accept the connection: %1").arg(m_lastError)); } smtp_close(); return false; } QByteArray ehloByteArray(DEFAULT_EHLO_BUFFER); ehloByteArray.fill(0); if (fakeHostname != QString::null) { m_hostname = fakeHostname; } else - { + { char hostname[256]; - if (gethostname(hostname, 255) == 0) + struct hostent *host; + + host = gethostbyname(hostname); + + if (host && host->h_name) + { + m_hostname = host->h_name; + } + else if (gethostname(hostname, 255) == 0) { - hostname[255] = '\0'; - m_hostname = hostname; + hostname[255] = '\0'; + m_hostname = hostname; } } if(m_hostname == QString::null) { m_hostname = "localhost.invalid"; } if (!command(("EHLO " + m_hostname), ehloByteArray.data(), DEFAULT_EHLO_BUFFER - 1)) { if (m_errorSent) { smtp_close(); return false; } // Let's just check to see if it speaks plain ol' SMTP if (!command(("HELO " + m_hostname))) { if (!m_errorSent) error(KIO::ERR_COULD_NOT_LOGIN, i18n("The server said: %1").arg(m_lastError)); smtp_close(); return false; } } // We parse the ESMTP extensions here... pipelining would be really cool char ehlo_line[DEFAULT_EHLO_BUFFER]; QBuffer ehlobuf(ehloByteArray); if (ehlobuf.open(IO_ReadWrite)) { while (ehlobuf.readLine(ehlo_line, DEFAULT_EHLO_BUFFER - 1) > 0) ParseFeatures(const_cast < const char *>(ehlo_line)); } if ((m_haveTLS && canUseTLS() && metaData("tls") != "off") || metaData("tls") == "on") { // For now we're gonna force it on. if (command(QString::fromLatin1("STARTTLS"))) { int tlsrc = startTLS(); if (tlsrc != 1) { if (tlsrc != -3) { //kdDebug() << "TLS negotiation failed!" << endl; messageBox(Information, i18n("Your SMTP server claims to " "support TLS, but negotiation " "was unsuccessful.\nYou can " "disable TLS in KDE using the " "crypto settings module."), i18n("Connection Failed")); } smtp_close(); return false; } //kdDebug() << "TLS has been enabled!" << endl; if (!command("EHLO " + m_hostname)) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("The server said: %1").arg(m_lastError)); smtp_close(); return false; } } else if (metaData("tls") == "on") { if (!m_errorSent) error(KIO::ERR_COULD_NOT_LOGIN, i18n("Your SMTP server does not support TLS. Disable " "TLS, if you want to connect without encryption.")); smtp_close(); return false; } } // Now we try and login if (!m_sUser.isNull()) { if (m_sPass.isNull()) { KIO::AuthInfo authInfo; authInfo.username = m_sUser; authInfo.password = m_sPass; authInfo.prompt = i18n("Username and password for your SMTP account:"); if (!openPassDlg(authInfo)) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("When prompted, you ran away.")); smtp_close(); return false; } else { m_sUser = authInfo.username; m_sPass = authInfo.password; } } if (!Authenticate()) { smtp_close(); return false; } } m_iOldPort = m_iPort; m_sOldServer = m_sServer; m_sOldUser = m_sUser; m_sOldPass = m_sPass; return true; } bool SMTPProtocol::Authenticate() { KDESasl SASL(m_sUser, m_sPass, (m_bIsSSL) ? "smtps" : "smtp"); QString auth_method; // Choose available method from what the server has given us in its greeting QStringList sl = QStringList::split(' ', m_sAuthConfig); // If the server doesn't support/require authentication, we ignore it if (sl.isEmpty()) return true; QStrIList strList; if (!metaData("sasl").isEmpty()) strList.append(metaData("sasl").latin1()); else for (int i = 0; i < sl.count(); i++) strList.append(sl[i].latin1()); auth_method = SASL.chooseMethod(strList); // If none are available, set it up so we can start over again if (auth_method == QString::null) { //kdDebug() << "kio_smtp: no authentication available" << endl; error(KIO::ERR_COULD_NOT_LOGIN, i18n("No compatible authentication methods found.")); return false; } else { char challenge[2049]; bool ret = false; QByteArray ba; QString cmd = QString::fromLatin1("AUTH ") + auth_method; if (auth_method == "PLAIN") { KCodecs::base64Encode(SASL.getBinaryResponse(ba, false), ba); cmd += ' ' + ba; } if (!command(cmd, challenge, 2049)) { if (!m_errorSent) { error(KIO::ERR_COULD_NOT_LOGIN, i18n("Your SMTP server doesn't support %1.\n" "Choose a different authentication method.") .arg(auth_method)); } return false; } if (auth_method == "PLAIN") { ret = true; } else { ba.duplicate(challenge, strlen(challenge)); cmd = SASL.getResponse(ba); ret = command(cmd, challenge, 2049); if (auth_method == "DIGEST-MD5" || auth_method == "LOGIN") { ba.duplicate(challenge, strlen(challenge)); cmd = SASL.getResponse(ba); ret = command(cmd); } } if (!ret && !m_errorSent) { error(KIO::ERR_COULD_NOT_LOGIN, i18n ("Authentication failed.\nMost likely the password is wrong.\nThe server said: %1"). arg(m_lastError)); } return ret; } return false; } void SMTPProtocol::ParseFeatures(const char* buf) { // We want it to be between 250 and 259 inclusive, and it needs to be "nnn-blah" or "nnn blah" // So sez the SMTP spec.. if ((buf[0] != '2') || (buf[1] != '5') || (!isdigit(buf[2])) || ((buf[3] != '-') && (buf[3] != ' '))) return; // We got an invalid line.. if (strncmp(&buf[4], "AUTH", strlen("AUTH")) == 0) { // Look for auth stuff if (m_sAuthConfig == QString::null) m_sAuthConfig = &buf[5 + strlen("AUTH")]; } else if (strncmp(&buf[4], "STARTTLS", strlen("STARTTLS")) == 0) { m_haveTLS = true; } } void SMTPProtocol::smtp_close() { if (!m_opened) // We're already closed return; command(QString::fromLatin1("QUIT")); closeDescriptor(); m_sOldServer = QString::null; m_sOldUser = QString::null; m_sOldPass = QString::null; m_sAuthConfig = QString::null; m_opened = false; } void SMTPProtocol::stat(const KURL & url) { QString path = url.path(); error(KIO::ERR_DOES_NOT_EXIST, url.path()); } int SMTPProtocol::GetVal(char *buf) { bool ok; QCString st(buf, 4); int val = st.toInt(&ok); return (ok) ? val : -1; }