#include #include "bconsole.h" #include #include "xmpp.h" #include #define ROOTCERT_PATH "/usr/local/share/psi/certs/rootcert.xml" QCA::Cert readCertXml(const QDomElement &e) { QCA::Cert cert; // there should be one child data tag QDomElement data = e.elementsByTagName("data").item(0).toElement(); if(!data.isNull()) cert.fromDER(Base64::stringToArray(data.text())); return cert; } QPtrList getRootCerts(const QString &store) { QPtrList list; // open the Psi rootcerts file QFile f(store); if(!f.open(IO_ReadOnly)) { printf("unable to open %s\n", f.name().latin1()); return list; } QDomDocument doc; doc.setContent(&f); f.close(); QDomElement base = doc.documentElement(); if(base.tagName() != "store") { printf("wrong format of %s\n", f.name().latin1()); return list; } QDomNodeList cl = base.elementsByTagName("certificate"); if(cl.count() == 0) { printf("no certs found in %s\n", f.name().latin1()); return list; } int num = 0; for(int n = 0; n < (int)cl.count(); ++n) { QCA::Cert *cert = new QCA::Cert(readCertXml(cl.item(n).toElement())); if(cert->isNull()) { printf("error reading cert\n"); delete cert; continue; } ++num; list.append(cert); } printf("imported %d root certs\n", num); return list; } static QString prompt(const QString &s) { printf("* %s ", s.latin1()); fflush(stdout); char line[256]; fgets(line, 255, stdin); QString result = line; if(result[result.length()-1] == '\n') result.truncate(result.length()-1); return result; } static void showCertInfo(const QCA::Cert &cert) { fprintf(stderr, "-- Cert --\n"); fprintf(stderr, " CN: %s\n", cert.subject()["CN"].latin1()); fprintf(stderr, " Valid from: %s, until %s\n", cert.notBefore().toString().latin1(), cert.notAfter().toString().latin1()); fprintf(stderr, " PEM:\n%s\n", cert.toPEM().latin1()); } static QString resultToString(int result) { QString s; switch(result) { case QCA::TLS::NoCert: s = QObject::tr("No certificate presented."); break; case QCA::TLS::Valid: break; case QCA::TLS::HostMismatch: s = QObject::tr("Hostname mismatch."); break; case QCA::TLS::Rejected: s = QObject::tr("Root CA rejects the specified purpose."); break; case QCA::TLS::Untrusted: s = QObject::tr("Not trusted for the specified purpose."); break; case QCA::TLS::SignatureFailed: s = QObject::tr("Invalid signature."); break; case QCA::TLS::InvalidCA: s = QObject::tr("Invalid CA certificate."); break; case QCA::TLS::InvalidPurpose: s = QObject::tr("Invalid certificate purpose."); break; case QCA::TLS::SelfSigned: s = QObject::tr("Certificate is self-signed."); break; case QCA::TLS::Revoked: s = QObject::tr("Certificate has been revoked."); break; case QCA::TLS::PathLengthExceeded: s = QObject::tr("Maximum cert chain length exceeded."); break; case QCA::TLS::Expired: s = QObject::tr("Certificate has expired."); break; case QCA::TLS::Unknown: default: s = QObject::tr("General validation error."); break; } return s; } class App : public QObject { Q_OBJECT public: XMPP::AdvancedConnector *conn; QCA::TLS *tls; XMPP::QCATLSHandler *tlsHandler; XMPP::ClientStream *stream; BConsole *c; XMPP::Jid jid; QPtrList rootCerts; App(const XMPP::Jid &_jid, const XMPP::AdvancedConnector::Proxy &proxy, const QString &host, int port, bool opt_ssl, bool opt_probe) :QObject(0) { c = 0; jid = _jid; // Connector conn = new XMPP::AdvancedConnector; conn->setProxy(proxy); if(!host.isEmpty()) conn->setOptHostPort(host, port); conn->setOptProbe(opt_probe); conn->setOptSSL(opt_ssl); // TLSHandler tls = 0; tlsHandler = 0; rootCerts.setAutoDelete(true); if(QCA::isSupported(QCA::CAP_TLS)) { rootCerts = getRootCerts(ROOTCERT_PATH); tls = new QCA::TLS; tls->setCertificateStore(rootCerts); tlsHandler = new XMPP::QCATLSHandler(tls); connect(tlsHandler, SIGNAL(tlsHandshaken()), SLOT(tls_handshaken())); } // Stream stream = new XMPP::ClientStream(conn, tlsHandler); connect(stream, SIGNAL(connected()), SLOT(cs_connected())); connect(stream, SIGNAL(securityLayerActivated(int)), SLOT(cs_securityLayerActivated())); connect(stream, SIGNAL(needAuthParams(bool, bool, bool)), SLOT(cs_needAuthParams(bool, bool, bool))); connect(stream, SIGNAL(authenticated()), SLOT(cs_authenticated())); connect(stream, SIGNAL(connectionClosed()), SLOT(cs_connectionClosed())); connect(stream, SIGNAL(readyRead()), SLOT(cs_readyRead())); connect(stream, SIGNAL(stanzaWritten()), SLOT(cs_stanzaWritten())); connect(stream, SIGNAL(warning(int)), SLOT(cs_warning(int))); connect(stream, SIGNAL(error(int)), SLOT(cs_error(int))); fprintf(stderr, "conntest: Connecting ...\n"); stream->setSSFRange(0, 256); stream->connectToServer(jid); } ~App() { delete stream; delete tls; // this destroys the TLSHandler also delete conn; delete c; } signals: void quit(); private slots: void tls_handshaken() { QCA::Cert cert = tls->peerCertificate(); int vr = tls->certificateValidityResult(); fprintf(stderr, "conntest: Successful TLS handshake.\n"); if(!cert.isNull()) showCertInfo(cert); if(vr == QCA::TLS::Valid) fprintf(stderr, "conntest: Valid certificate.\n"); else fprintf(stderr, "conntest: Invalid certificate: %s\n", resultToString(vr).latin1()); tlsHandler->continueAfterHandshake(); } void cs_connected() { fprintf(stderr, "conntest: Connected\n"); } void cs_securityLayerActivated() { fprintf(stderr, "conntest: Security layer activated (%s)\n", tls->isHandshaken() ? "TLS": "SASL"); } void cs_needAuthParams(bool user, bool pass, bool realm) { fprintf(stderr, "conntest: need auth params -"); if(user) fprintf(stderr, " (user)"); if(pass) fprintf(stderr, " (pass)"); if(realm) fprintf(stderr, " (realm)"); fprintf(stderr, "\n"); if(user) stream->setUsername(jid.node()); if(pass) stream->setPassword(prompt("Password (not hidden!) :")); stream->continueAfterParams(); } void cs_authenticated() { fprintf(stderr, "conntest: <<< Authenticated >>>\n"); // console c = new BConsole; connect(c, SIGNAL(connectionClosed()), SLOT(con_connectionClosed())); connect(c, SIGNAL(readyRead()), SLOT(con_readyRead())); } void cs_connectionClosed() { fprintf(stderr, "conntest: Disconnected by peer\n"); quit(); } void cs_readyRead() { for(XMPP::Stanza s; !(s = stream->read()).isNull();) { QString str = s.toString(); printf("%s\n", str.local8Bit().data()); } } void cs_stanzaWritten() { fprintf(stderr, "conntest: Stanza written\n"); } void cs_warning(int warn) { if(warn == XMPP::ClientStream::WarnOldVersion) { fprintf(stderr, "conntest: Warning: pre-1.0 protocol server\n"); } else if(warn == XMPP::ClientStream::WarnNoTLS) { fprintf(stderr, "conntest: Warning: TLS not available!\n"); } stream->continueAfterWarning(); } void cs_error(int err) { if(err == XMPP::ClientStream::ErrParse) { fprintf(stderr, "conntest: XML parsing error\n"); } else if(err == XMPP::ClientStream::ErrProtocol) { fprintf(stderr, "conntest: XMPP protocol error\n"); } else if(err == XMPP::ClientStream::ErrStream) { int x = stream->errorCondition(); QString s; if(x == XMPP::Stream::GenericStreamError) s = "generic stream error"; else if(x == XMPP::ClientStream::Conflict) s = "conflict (remote login replacing this one)"; else if(x == XMPP::ClientStream::ConnectionTimeout) s = "timed out from inactivity"; else if(x == XMPP::ClientStream::InternalServerError) s = "internal server error"; else if(x == XMPP::ClientStream::InvalidXml) s = "invalid XML"; else if(x == XMPP::ClientStream::PolicyViolation) s = "policy violation. go to jail!"; else if(x == XMPP::ClientStream::ResourceConstraint) s = "server out of resources"; else if(x == XMPP::ClientStream::SystemShutdown) s = "system is shutting down NOW"; fprintf(stderr, "conntest: XMPP stream error: %s\n", s.latin1()); } else if(err == XMPP::ClientStream::ErrConnection) { int x = conn->errorCode(); QString s; if(x == XMPP::AdvancedConnector::ErrConnectionRefused) s = "unable to connect to server"; else if(x == XMPP::AdvancedConnector::ErrHostNotFound) s = "host not found"; else if(x == XMPP::AdvancedConnector::ErrProxyConnect) s = "proxy connect"; else if(x == XMPP::AdvancedConnector::ErrProxyNeg) s = "proxy negotiating"; else if(x == XMPP::AdvancedConnector::ErrProxyAuth) s = "proxy authorization"; else if(x == XMPP::AdvancedConnector::ErrStream) s = "stream error"; fprintf(stderr, "conntest: connection error: %s\n", s.latin1()); } else if(err == XMPP::ClientStream::ErrNeg) { int x = stream->errorCondition(); QString s; if(x == XMPP::ClientStream::HostGone) s = "host no longer hosted"; else if(x == XMPP::ClientStream::HostUnknown) s = "host unknown"; else if(x == XMPP::ClientStream::RemoteConnectionFailed) s = "a required remote connection failed"; else if(x == XMPP::ClientStream::SeeOtherHost) s = QString("see other host: [%1]").arg(stream->errorText()); else if(x == XMPP::ClientStream::UnsupportedVersion) s = "server does not support proper xmpp version"; fprintf(stderr, "conntest: stream negotiation error: %s\n", s.latin1()); } else if(err == XMPP::ClientStream::ErrTLS) { int x = stream->errorCondition(); QString s; if(x == XMPP::ClientStream::TLSStart) s = "server rejected STARTTLS"; else if(x == XMPP::ClientStream::TLSFail) { int t = tlsHandler->tlsError(); if(t == QCA::TLS::ErrHandshake) s = "TLS handshake error"; else s = "broken security layer (TLS)"; } fprintf(stderr, "conntest: %s\n", s.latin1()); } else if(err == XMPP::ClientStream::ErrAuth) { int x = stream->errorCondition(); QString s; if(x == XMPP::ClientStream::GenericAuthError) s = "unable to login"; else if(x == XMPP::ClientStream::NoMech) s = "no appropriate auth mechanism available for given security settings"; else if(x == XMPP::ClientStream::BadProto) s = "bad server response"; else if(x == XMPP::ClientStream::BadServ) s = "server failed mutual authentication"; else if(x == XMPP::ClientStream::EncryptionRequired) s = "encryption required for chosen SASL mechanism"; else if(x == XMPP::ClientStream::InvalidAuthzid) s = "invalid authzid"; else if(x == XMPP::ClientStream::InvalidMech) s = "invalid SASL mechanism"; else if(x == XMPP::ClientStream::InvalidRealm) s = "invalid realm"; else if(x == XMPP::ClientStream::MechTooWeak) s = "SASL mechanism too weak for authzid"; else if(x == XMPP::ClientStream::NotAuthorized) s = "not authorized"; else if(x == XMPP::ClientStream::TemporaryAuthFailure) s = "temporary auth failure"; fprintf(stderr, "conntest: auth error: %s\n", s.latin1()); } else if(err == XMPP::ClientStream::ErrSecurityLayer) fprintf(stderr, "conntest: broken security layer (SASL)\n"); quit(); } void con_connectionClosed() { fprintf(stderr, "conntest: Closing.\n"); stream->close(); quit(); } void con_readyRead() { QByteArray a = c->read(); QCString cs; cs.resize(a.size()+1); memcpy(cs.data(), a.data(), a.size()); QString s = QString::fromLocal8Bit(cs); stream->writeDirect(s); } }; #include "conntest.moc" int main(int argc, char **argv) { QApplication app(argc, argv, false); if(argc < 2) { printf("usage: conntest [options] [jid]\n"); printf(" Options:\n"); printf(" --host=host:port\n"); printf(" --sslhost=host:port\n"); printf(" --probe\n"); printf(" --proxy=[https|poll|socks],host:port,url\n"); printf(" --proxy-auth=user,pass\n"); printf("\n"); return 0; } bool have_tls = QCA::isSupported(QCA::CAP_TLS); XMPP::Jid jid; XMPP::AdvancedConnector::Proxy proxy; QString host; int port = 0; bool opt_ssl = false; bool opt_probe = false; for(int at = 1; at < argc; ++at) { QString s = argv[at]; // is it an option? if(s.left(2) == "--") { QString name; QStringList args; int n = s.find('=', 2); if(n != -1) { name = s.mid(2, n-2); ++n; args = QStringList::split(',', s.mid(n), true); } else { name = s.mid(2); args.clear(); } // eat the arg --argc; for(int x = at; x < argc; ++x) argv[x] = argv[x+1]; --at; // don't advance // process option if(name == "proxy") { QString proxy_host; int proxy_port = 0; QString type = args[0]; QString s = args[1]; int n = s.find(':'); if(n == -1) { if(type != "poll") { printf("Invalid host:port for proxy\n"); return 0; } } else { proxy_host = s.mid(0, n); ++n; proxy_port = s.mid(n).toInt(); } if(type == "https") { proxy.setHttpConnect(proxy_host, proxy_port); } else if(type == "poll") { if(args.count() < 3) { printf("poll needs more args\n"); return 0; } QString proxy_url = args[2]; proxy.setHttpPoll(proxy_host, proxy_port, proxy_url); } else if(type == "socks") { proxy.setSocks(proxy_host, proxy_port); } else { printf("No such proxy type '%s'\n", type.latin1()); return 0; } } else if(name == "proxy-auth") { proxy.setUserPass(args[0], args[1]); } else if(name == "host") { QString s = args[0]; int n = s.find(':'); if(n == -1) { printf("Invalid host:port for host\n"); return 0; } host = s.mid(0, n); ++n; port = s.mid(n).toInt(); } else if(name == "sslhost") { QString s = args[0]; int n = s.find(':'); if(n == -1) { printf("Invalid host:port for host\n"); return 0; } host = s.mid(0, n); ++n; port = s.mid(n).toInt(); opt_ssl = true; } else if(name == "probe") { opt_probe = true; } else { printf("Unknown option '%s'\n", name.latin1()); return 0; } } } if(argc < 2) { printf("No host specified!\n"); return 0; } jid = argv[1]; if((opt_ssl || opt_probe) && !have_tls) { printf("TLS not supported, so the sslhost and probe options are not allowed.\n"); return 0; } printf("JID: %s\n", jid.full().latin1()); if(proxy.type() != XMPP::AdvancedConnector::Proxy::None) { printf("Proxy: "); if(proxy.type() == XMPP::AdvancedConnector::Proxy::HttpConnect) printf("HttpConnect (%s:%d)", proxy.host().latin1(), proxy.port()); else if(proxy.type() == XMPP::AdvancedConnector::Proxy::HttpPoll) { printf("HttpPoll {%s}", proxy.url().latin1()); if(!proxy.host().isEmpty()) { printf(" (%s:%d)", proxy.host().latin1(), proxy.port()); } } else if(proxy.type() == XMPP::AdvancedConnector::Proxy::Socks) printf("Socks (%s:%d)", proxy.host().latin1(), proxy.port()); printf("\n"); } if(proxy.type() != XMPP::AdvancedConnector::Proxy::HttpPoll) { if(!host.isEmpty()) { printf("Host: %s:%d", host.latin1(), port); if(opt_ssl) printf(" (ssl)"); printf("\n"); } else { if(opt_probe) printf("Probe active\n"); } } printf("----------\n"); App *a = new App(jid, proxy, host, port, opt_ssl, opt_probe); QObject::connect(a, SIGNAL(quit()), &app, SLOT(quit())); app.exec(); delete a; return 0; }