25 #include <QtXml/QDomDocument>
26 #include <QDBusConnection>
27 #include <QDBusMessage>
28 #include <QDBusPendingReply>
33 #include <sys/types.h>
44 #include "../constants.h"
46 #define SSU_NETWORK_REQUEST_DOMAIN_DATA (static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User + 1))
48 static void restoreUid()
68 !settingsInfo.permission(QFile::WriteGroup)) {
70 proc.start(
"/usr/bin/ssuconfperm");
71 proc.waitForFinished();
78 if (!settings->contains(
"arch"))
79 settings->setValue(
"arch", TARGET_ARCH);
82 #warning "TARGET_ARCH not defined"
88 manager =
new QNetworkAccessManager(
this);
89 connect(manager, SIGNAL(finished(QNetworkReply *)),
90 SLOT(requestFinished(QNetworkReply *)));
107 if (repoName ==
"store" || repoName.startsWith(
"store-c-"))
113 QString storeAuthReposKey = QString(
"store-auth-repos-%1")
114 .arg(rndRepo ?
"rnd" :
"release");
115 QStringList storeAuthRepos =
117 domain() +
"-domain",
118 storeAuthReposKey).toStringList();
120 if (storeAuthRepos.empty())
123 domain() +
"-domain",
124 "store-auth-repos").toStringList();
126 if (storeAuthRepos.contains(repoName))
131 QHash<QString, QString> secureDomainAuth;
133 QHashIterator<QString, QString> i(secureDomainAuth);
134 while (i.hasNext()) {
136 if (
repoUrl(repoName, rndRepo).contains(i.key()) && !i.value().isEmpty()) {
162 Ssu::DeviceModeFlags Ssu::deviceMode()
168 QString Ssu::domain()
171 return settings->
domain(
true);
176 return settings->
brand();
236 bool Ssu::registerDevice(QDomDocument *response)
238 QString certificateString = response->elementsByTagName(
"certificate").at(0).toElement().text();
239 QSslCertificate certificate(certificateString.toLatin1());
240 SsuLog *ssuLog = SsuLog::instance();
243 if (certificate.isNull()) {
245 settings->setValue(
"registered",
false);
246 setError(
"Certificate is invalid");
249 settings->setValue(
"certificate", certificate.toPem());
252 QString privateKeyString = response->elementsByTagName(
"privateKey").at(0).toElement().text();
253 QSslKey privateKey(privateKeyString.toLatin1(), QSsl::Rsa);
255 if (privateKey.isNull()) {
256 settings->setValue(
"registered",
false);
257 setError(
"Private key is invalid");
260 settings->setValue(
"privateKey", privateKey.toPem());
265 QString oldUser = response->elementsByTagName(
"user").at(0).toElement().text();
266 ssuLog->
print(LOG_DEBUG, QString(
"Old user for your device was: %1").arg(oldUser));
269 settings->setValue(
"registered",
true);
272 if (!settings->isWritable()) {
273 setError(
"Configuration is not writable, device registration failed.");
281 QStringList Ssu::listDomains() {
283 QRegExp domainFilter(
"-domain$");
284 return repoSettings.childGroups().filter(domainFilter).replaceInStrings(domainFilter,
"");
287 void Ssu::setDomainConfig(
const QString &domain, QVariantMap config) {
289 repoSettings.beginGroup(domain +
"-domain");
290 repoSettings.remove(
"");
292 for (QVariantMap::iterator i = config.begin(); i != config.end(); i++) {
293 repoSettings.setValue(i.key(), i.value());
295 repoSettings.endGroup();
299 QVariantMap Ssu::getDomainConfig(
const QString &domain) {
302 repoSettings.beginGroup(domain +
"-domain");
303 foreach(QString key, repoSettings.allKeys()) {
304 config.insert(key, repoSettings.value(key).toString());
306 repoSettings.endGroup();
313 QHash<QString, QString> repoParameters,
314 QHash<QString, QString> parametersOverride)
317 return manager.
url(repoName, rndRepo, repoParameters, parametersOverride);
320 void Ssu::requestFinished(QNetworkReply *reply)
322 QSslConfiguration sslConfiguration = reply->sslConfiguration();
323 SsuLog *ssuLog = SsuLog::instance();
325 QNetworkRequest request = reply->request();
326 QVariant originalDomainVariant = request.attribute(SSU_NETWORK_REQUEST_DOMAIN_DATA);
328 ssuLog->
print(LOG_DEBUG, QString(
"Certificate used was issued for '%1' by '%2'. Complete chain:")
329 .arg(sslConfiguration.peerCertificate().subjectInfo(QSslCertificate::CommonName).join(
""))
330 .arg(sslConfiguration.peerCertificate().issuerInfo(QSslCertificate::CommonName).join(
"")));
332 foreach (
const QSslCertificate cert, sslConfiguration.peerCertificateChain()) {
333 ssuLog->
print(LOG_DEBUG, QString(
"-> %1").arg(cert.subjectInfo(QSslCertificate::CommonName).join(
"")));
344 if (settings->contains(
"home-url")) {
345 QString homeUrl = settings->value(
"home-url").toString().arg(QString());
346 homeUrl.remove(QRegExp(
"//+$"));
348 if (request.url().toString().startsWith(homeUrl, Qt::CaseInsensitive)) {
350 if (reply->error() == 0) {
351 QByteArray data = reply->readAll();
352 storeAuthorizedKeys(data);
359 if (reply->error() > 0) {
360 setError(reply->errorString());
364 data = reply->readAll();
365 ssuLog->
print(LOG_DEBUG, QString(
"RequestOutput %1")
368 if (!doc.setContent(data, &xmlError)) {
369 setError(tr(
"Unable to parse server response (%1)").arg(xmlError));
373 action = doc.elementsByTagName(
"action").at(0).toElement().text();
375 if (!verifyResponse(&doc)) {
379 ssuLog->
print(LOG_DEBUG, QString(
"Handling request of type %1")
381 if (action ==
"register") {
382 if (registerDevice(&doc)) {
385 }
else if (action ==
"credentials") {
386 if (setCredentials(&doc)) {
390 setError(tr(
"Response to unknown action encountered: %1").arg(action));
395 if (!originalDomainVariant.isNull()) {
396 QString originalDomain = originalDomainVariant.toString();
397 ssuLog->
print(LOG_DEBUG, QString(
"Restoring domain on error: '%1'").arg(originalDomain));
403 ssuLog->
print(LOG_DEBUG, QString(
"Request finished, pending requests: %1").arg(pendingRequests));
404 if (pendingRequests == 0) {
413 QString ssuRegisterUrl;
414 QString username, domainName;
416 SsuLog *ssuLog = SsuLog::instance();
420 QNetworkRequest request;
421 request.setAttribute(SSU_NETWORK_REQUEST_DOMAIN_DATA, domain());
422 ssuLog->
print(LOG_DEBUG, QString(
"Saving current domain before request: '%1'").arg(domain()));
425 if (usernameDomain.contains(
'@')) {
427 username = usernameDomain.section(
'@', 0, 0);
428 domainName = usernameDomain.section(
'@', 1, 1);
432 username = usernameDomain;
433 if (settings->contains(
"default-rnd-domain"))
434 setDomain(settings->value(
"default-rnd-domain").toString());
438 if (ssuCaCertificate.isEmpty()) {
439 setError(
"CA certificate for ssu not set ('_ca-certificate in domain')");
443 if (!settings->contains(
"register-url")) {
444 ssuRegisterUrl =
repoUrl(
"register-url");
445 if (ssuRegisterUrl.isEmpty()) {
446 setError(
"URL for ssu registration not set (config key 'register-url')");
450 ssuRegisterUrl = settings->value(
"register-url").toString();
454 if (IMEI.isEmpty()) {
455 setError(
"No valid UID available for your device. For phones: is your modem online?");
459 QSslConfiguration sslConfiguration;
461 sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
463 sslConfiguration.setCaCertificates(QSslCertificate::fromPath(ssuCaCertificate));
465 request.setUrl(QUrl(QString(ssuRegisterUrl)
468 request.setSslConfiguration(sslConfiguration);
469 request.setRawHeader(
"Authorization",
"Basic " +
470 QByteArray(QString(
"%1:%2")
471 .arg(username).arg(password)
472 .toLatin1()).toBase64());
473 request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
479 q.addQueryItem(
"deviceModel", deviceInfo.
deviceModel());
480 if (!domain().isEmpty()) {
481 q.addQueryItem(
"domain", domain());
486 ssuLog->
print(LOG_DEBUG, QString(
"Sending request to %1")
487 .arg(request.url().url()));
490 manager->post(request, form.query(QUrl::FullyEncoded).toStdString().c_str());
493 QString homeUrl = settings->value(
"home-url").toString().arg(username);
494 if (!homeUrl.isEmpty()) {
496 request.setHeader(QNetworkRequest::ContentTypeHeader, 0);
497 request.setUrl(homeUrl +
"/authorized_keys");
498 ssuLog->
print(LOG_DEBUG, QString(
"Trying to get SSH keys from %1").arg(request.url().toString()));
500 manager->get(request);
504 bool Ssu::setCredentials(QDomDocument *response)
508 QDomNodeList credentialsList = response->elementsByTagName(
"credentials");
509 QStringList credentialScopes;
510 for (
int i = 0; i < credentialsList.size(); i++) {
511 QDomNode node = credentialsList.at(i);
514 QDomNamedNodeMap attributes = node.attributes();
515 if (attributes.contains(
"scope")) {
516 scope = attributes.namedItem(
"scope").toAttr().value();
518 setError(tr(
"Credentials element does not have scope"));
522 if (node.hasChildNodes()) {
523 QDomElement username = node.firstChildElement(
"username");
524 QDomElement password = node.firstChildElement(
"password");
525 if (username.isNull() || password.isNull()) {
526 setError(tr(
"Username and/or password not set"));
529 settings->beginGroup(
"credentials-" + scope);
530 settings->setValue(
"username", username.text());
531 settings->setValue(
"password", password.text());
532 settings->endGroup();
534 credentialScopes.append(scope);
541 settings->setValue(
"credentialScopes", credentialScopes);
542 settings->setValue(
"lastCredentialsUpdate", QDateTime::currentDateTime());
544 emit credentialsChanged();
549 void Ssu::setError(
const QString &errorMessage)
552 errorString = errorMessage;
554 SsuLog *ssuLog = SsuLog::instance();
557 ssuLog->
print(LOG_WARNING, errorMessage);
564 void Ssu::storeAuthorizedKeys(
const QByteArray &data)
567 SsuLog *ssuLog = SsuLog::instance();
569 int uid_min = getdef_num(
"UID_MIN", -1);
572 if (getuid() >=
static_cast<uid_t
>(uid_min)) {
573 homePath = dir.homePath();
574 }
else if (getuid() == 0) {
576 struct passwd *pw = getpwuid(uid_min);
578 ssuLog->
print(LOG_DEBUG, QString(
"Unable to find password entry for uid %1")
584 homePath = pw->pw_dir;
589 ssuLog->
print(LOG_DEBUG, QString(
"Dropping to %1/%2 for writing authorized keys")
596 homePath = Sandbox::map(homePath);
598 if (dir.exists(homePath +
"/.ssh/authorized_keys")) {
599 ssuLog->
print(LOG_DEBUG, QString(
".ssh/authorized_keys already exists in %1")
605 if (!dir.exists(homePath +
"/.ssh")) {
606 if (!dir.mkdir(homePath +
"/.ssh")) {
607 ssuLog->
print(LOG_DEBUG, QString(
"Unable to create .ssh in %1")
614 QFile::setPermissions(homePath +
"/.ssh",
615 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
617 QFile authorizedKeys(homePath +
"/.ssh/authorized_keys");
618 authorizedKeys.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
619 authorizedKeys.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
620 QTextStream out(&authorizedKeys);
623 authorizedKeys.close();
634 SsuLog *ssuLog = SsuLog::instance();
637 setError(
"No valid UID available for your device. For phones: is your modem online?");
641 QString ssuCaCertificate, ssuCredentialsUrl;
643 if (ssuCaCertificate.isEmpty()) {
644 setError(
"CA certificate for ssu not set ('_ca-certificate in domain')");
648 if (!settings->contains(
"credentials-url")) {
649 ssuCredentialsUrl =
repoUrl(
"credentials-url");
650 if (ssuCredentialsUrl.isEmpty()) {
651 setError(
"URL for credentials update not set (config key 'credentials-url')");
655 ssuCredentialsUrl = settings->value(
"credentials-url").toString();
659 setError(
"Device is not registered.");
665 QDateTime now = QDateTime::currentDateTime();
667 if (settings->contains(
"lastCredentialsUpdate")) {
668 QDateTime last = settings->value(
"lastCredentialsUpdate").toDateTime();
669 if (last >= now.addSecs(-1800)) {
670 ssuLog->
print(LOG_DEBUG, QString(
"Skipping credentials update, last update was at %1")
671 .arg(last.toString()));
679 QSslConfiguration sslConfiguration;
681 sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
683 QSslKey privateKey(settings->value(
"privateKey").toByteArray(), QSsl::Rsa);
684 QSslCertificate certificate(settings->value(
"certificate").toByteArray());
686 QList<QSslCertificate> caCertificates;
687 caCertificates << QSslCertificate::fromPath(ssuCaCertificate);
688 sslConfiguration.setCaCertificates(caCertificates);
690 sslConfiguration.setPrivateKey(privateKey);
691 sslConfiguration.setLocalCertificate(certificate);
693 QNetworkRequest request;
694 request.setUrl(QUrl(ssuCredentialsUrl.arg(deviceInfo.
deviceUid())));
696 ssuLog->
print(LOG_DEBUG, QString(
"Sending credential update request to %1")
697 .arg(request.url().toString()));
698 request.setSslConfiguration(sslConfiguration);
701 manager->get(request);
707 SsuLog *ssuLog = SsuLog::instance();
709 QDBusMessage message = QDBusMessage::createMethodCall(
"com.jolla.jollastore",
711 "com.jolla.jollastore",
714 reply.waitForFinished();
715 if (reply.isError()) {
716 if (settings->value(
"ignore-credential-errors").toBool() ==
true) {
717 ssuLog->
print(LOG_WARNING, QString(
"Warning: ignore-credential-errors is set, passing auth errors down to libzypp"));
718 ssuLog->
print(LOG_WARNING, QString(
"Store credentials not received. %1").arg(reply.error().message()));
720 setError(QString(
"Store credentials not received. %1").arg(reply.error().message()));
724 settings->beginGroup(
"credentials-store");
725 settings->setValue(
"username", reply.argumentAt<0>());
726 settings->setValue(
"password", reply.argumentAt<1>());
727 settings->endGroup();
735 settings->setValue(
"privateKey",
"");
736 settings->setValue(
"certificate",
"");
737 settings->setValue(
"registered",
false);
742 bool Ssu::verifyResponse(QDomDocument *response)
744 QString action = response->elementsByTagName(
"action").at(0).toElement().text();
745 QString deviceId = response->elementsByTagName(
"deviceId").at(0).toElement().text();
746 QString protocolVersion = response->elementsByTagName(
"protocolVersion").at(0).toElement().text();
751 tr(
"Response has unsupported protocol version %1, client requires version %2")
752 .arg(protocolVersion)