25#include <QtXml/QDomDocument>
26#include <QDBusConnection>
27#include <QDBusMessage>
28#include <QDBusPendingReply>
46#define SSU_NETWORK_REQUEST_DOMAIN_DATA (static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User + 1))
48static 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 =
118 storeAuthReposKey).toStringList();
120 if (storeAuthRepos.empty())
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()) {
170 SsuCoreConfig *settings = SsuCoreConfig::instance();
171 return settings->
domain(
true);
176 return settings->
brand();
236bool 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.");
281QStringList Ssu::listDomains() {
283 QRegExp domainFilter(
"-domain$");
284 return repoSettings.childGroups().filter(domainFilter).replaceInStrings(domainFilter,
"");
287void 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();
299QVariantMap 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);
320void 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());
437 if (!settings->contains(
"register-url")) {
438 ssuRegisterUrl =
repoUrl(
"register-url");
439 if (ssuRegisterUrl.isEmpty()) {
440 setError(
"URL for ssu registration not set (config key 'register-url')");
444 ssuRegisterUrl = settings->value(
"register-url").toString();
448 if (IMEI.isEmpty()) {
449 setError(
"No valid UID available for your device. For phones: is your modem online?");
453 QSslConfiguration sslConfiguration;
455 sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
458 if (!ssuCaCertificate.isEmpty()) {
459 sslConfiguration.setCaCertificates(QSslCertificate::fromPath(ssuCaCertificate));
462 request.setUrl(QUrl(QString(ssuRegisterUrl)
465 request.setSslConfiguration(sslConfiguration);
466 request.setRawHeader(
"Authorization",
"Basic " +
467 QByteArray(QString(
"%1:%2")
468 .arg(username).arg(password)
469 .toLatin1()).toBase64());
470 request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
476 q.addQueryItem(
"deviceModel", deviceInfo.
deviceModel());
477 if (!
domain().isEmpty()) {
478 q.addQueryItem(
"domain",
domain());
483 ssuLog->
print(LOG_DEBUG, QString(
"Sending request to %1")
484 .arg(request.url().url()));
487 manager->post(request, form.query(QUrl::FullyEncoded).toStdString().c_str());
490 QString homeUrl = settings->value(
"home-url").toString();
491 if (!homeUrl.isEmpty()) {
493 request.setHeader(QNetworkRequest::ContentTypeHeader, 0);
494 request.setUrl(homeUrl.arg(username) +
"/authorized_keys");
495 ssuLog->
print(LOG_DEBUG, QString(
"Trying to get SSH keys from %1").arg(request.url().toString()));
497 manager->get(request);
501bool Ssu::setCredentials(QDomDocument *response)
505 QDomNodeList credentialsList = response->elementsByTagName(
"credentials");
506 QStringList credentialScopes;
507 for (
int i = 0; i < credentialsList.size(); i++) {
508 QDomNode node = credentialsList.at(i);
511 QDomNamedNodeMap attributes = node.attributes();
512 if (attributes.contains(
"scope")) {
513 scope = attributes.namedItem(
"scope").toAttr().value();
515 setError(tr(
"Credentials element does not have scope"));
519 if (node.hasChildNodes()) {
520 QDomElement username = node.firstChildElement(
"username");
521 QDomElement password = node.firstChildElement(
"password");
522 if (username.isNull() || password.isNull()) {
523 setError(tr(
"Username and/or password not set"));
526 settings->beginGroup(
"credentials-" + scope);
527 settings->setValue(
"username", username.text());
528 settings->setValue(
"password", password.text());
529 settings->endGroup();
531 credentialScopes.append(scope);
538 settings->setValue(
"credentialScopes", credentialScopes);
539 settings->setValue(
"lastCredentialsUpdate", QDateTime::currentDateTime());
541 emit credentialsChanged();
546void Ssu::setError(
const QString &errorMessage)
549 errorString = errorMessage;
551 SsuLog *ssuLog = SsuLog::instance();
554 ssuLog->
print(LOG_WARNING, errorMessage);
561void Ssu::storeAuthorizedKeys(
const QByteArray &data)
564 SsuLog *ssuLog = SsuLog::instance();
566 int uid_min = getdef_num(
"UID_MIN", -1);
569 if (getuid() >=
static_cast<uid_t
>(uid_min)) {
570 homePath = dir.homePath();
571 }
else if (getuid() == 0) {
573 struct passwd *pw = getpwuid(uid_min);
575 ssuLog->
print(LOG_DEBUG, QString(
"Unable to find password entry for uid %1")
581 homePath = pw->pw_dir;
586 ssuLog->
print(LOG_DEBUG, QString(
"Dropping to %1/%2 for writing authorized keys")
593 homePath = Sandbox::map(homePath);
595 if (dir.exists(homePath +
"/.ssh/authorized_keys")) {
596 ssuLog->
print(LOG_DEBUG, QString(
".ssh/authorized_keys already exists in %1")
602 if (!dir.exists(homePath +
"/.ssh")) {
603 if (!dir.mkdir(homePath +
"/.ssh")) {
604 ssuLog->
print(LOG_DEBUG, QString(
"Unable to create .ssh in %1")
611 QFile::setPermissions(homePath +
"/.ssh",
612 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
614 QFile authorizedKeys(homePath +
"/.ssh/authorized_keys");
615 authorizedKeys.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
616 authorizedKeys.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
617 QTextStream out(&authorizedKeys);
620 authorizedKeys.close();
631 SsuLog *ssuLog = SsuLog::instance();
634 setError(
"No valid UID available for your device. For phones: is your modem online?");
638 QString ssuCaCertificate, ssuCredentialsUrl;
641 if (!settings->contains(
"credentials-url")) {
642 ssuCredentialsUrl =
repoUrl(
"credentials-url");
643 if (ssuCredentialsUrl.isEmpty()) {
644 setError(
"URL for credentials update not set (config key 'credentials-url')");
648 ssuCredentialsUrl = settings->value(
"credentials-url").toString();
652 setError(
"Device is not registered.");
658 QDateTime now = QDateTime::currentDateTime();
660 if (settings->contains(
"lastCredentialsUpdate")) {
661 QDateTime last = settings->value(
"lastCredentialsUpdate").toDateTime();
662 if (last >= now.addSecs(-1800)) {
663 ssuLog->
print(LOG_DEBUG, QString(
"Skipping credentials update, last update was at %1")
664 .arg(last.toString()));
672 QSslConfiguration sslConfiguration;
674 sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
676 QSslKey privateKey(settings->value(
"privateKey").toByteArray(), QSsl::Rsa);
677 QSslCertificate certificate(settings->value(
"certificate").toByteArray());
679 QList<QSslCertificate> caCertificates;
680 if (ssuCaCertificate.isEmpty()) {
687 caCertificates << sslConfiguration.systemCaCertificates();
689 caCertificates << QSslCertificate::fromPath(ssuCaCertificate);
691 sslConfiguration.setCaCertificates(caCertificates);
693 sslConfiguration.setPrivateKey(privateKey);
694 sslConfiguration.setLocalCertificate(certificate);
696 QNetworkRequest request;
697 request.setUrl(QUrl(ssuCredentialsUrl.arg(deviceInfo.
deviceUid())));
699 ssuLog->
print(LOG_DEBUG, QString(
"Sending credential update request to %1")
700 .arg(request.url().toString()));
701 request.setSslConfiguration(sslConfiguration);
704 manager->get(request);
710 SsuLog *ssuLog = SsuLog::instance();
712 QDBusMessage message = QDBusMessage::createMethodCall(
"com.jolla.jollastore",
714 "com.jolla.jollastore",
717 reply.waitForFinished();
718 if (reply.isError()) {
719 if (settings->value(
"ignore-credential-errors").toBool() ==
true) {
720 ssuLog->
print(LOG_WARNING, QString(
"Warning: ignore-credential-errors is set, passing auth errors down to libzypp"));
721 ssuLog->
print(LOG_WARNING, QString(
"Store credentials not received. %1").arg(reply.error().message()));
723 setError(QString(
"Store credentials not received. %1").arg(reply.error().message()));
727 settings->beginGroup(
"credentials-store");
728 settings->setValue(
"username", reply.argumentAt<0>());
729 settings->setValue(
"password", reply.argumentAt<1>());
730 settings->endGroup();
738 settings->setValue(
"privateKey",
"");
739 settings->setValue(
"certificate",
"");
740 settings->setValue(
"registered",
false);
745bool Ssu::verifyResponse(QDomDocument *response)
747 QString action = response->elementsByTagName(
"action").at(0).toElement().text();
748 QString deviceId = response->elementsByTagName(
"deviceId").at(0).toElement().text();
749 QString protocolVersion = response->elementsByTagName(
"protocolVersion").at(0).toElement().text();
754 tr(
"Response has unsupported protocol version %1, client requires version %2")
755 .arg(protocolVersion)
Q_INVOKABLE QString domain(bool pretty=false)
QString credentialsScope(const QString &repoName, bool rndRepo=false)
Q_INVOKABLE void setFlavour(const QString &flavour)
Q_INVOKABLE bool useSslVerify()
Q_INVOKABLE QDateTime lastCredentialsUpdate()
Q_INVOKABLE Ssu::DeviceModeFlags deviceMode()
Q_INVOKABLE void setDeviceMode(Ssu::DeviceModeFlags mode, enum Ssu::EditMode editMode=Ssu::Replace)
Q_INVOKABLE bool isRegistered()
Q_INVOKABLE void setDomain(const QString &domain)
QString credentialsUrl(const QString &scope)
static QDBusConnection userSessionBus()
Q_INVOKABLE QString release(bool rnd=false)
Q_INVOKABLE QString brand()
Q_INVOKABLE void setRelease(const QString &release, bool rnd=false)
Q_INVOKABLE QString flavour()
QPair< QString, QString > credentials(const QString &scope)
Q_INVOKABLE QString deviceUid()
Q_INVOKABLE QString deviceModel()
void print(int priority, const QString &message)
QString url(const QString &repoName, bool rndRepo=false, QHash< QString, QString > repoParameters=QHash< QString, QString >(), QHash< QString, QString > parametersOverride=QHash< QString, QString >())
static QString caCertificatePath(const QString &domain=QString())
QVariant variable(const QString §ion, const QString &key)
void variableSection(const QString §ion, QHash< QString, QString > *storageHash)
Q_INVOKABLE QDateTime lastCredentialsUpdate()
See SsuCoreConfig::lastCredentialsUpdate.
QString repoUrl(const QString &repoName, bool rndRepo=false, QHash< QString, QString > repoParameters=QHash< QString, QString >(), QHash< QString, QString > parametersOverride=QHash< QString, QString >())
void sendRegistration(const QString &username, const QString &password)
void registrationStatusChanged()
Q_INVOKABLE QString flavour()
See SsuCoreConfig::flavour.
Q_INVOKABLE void setRelease(const QString &release, bool rnd=false)
See SsuCoreConfig::setRelease.
QPair< QString, QString > credentials(const QString &scope)
Q_INVOKABLE QString domain()
See SsuCoreConfig::domain; returns printable version.
QString credentialsUrl(const QString &scope)
void updateStoreCredentials()
Q_INVOKABLE bool useSslVerify()
See SsuCoreConfig::useSslVerify.
Q_INVOKABLE QString release(bool rnd=false)
See SsuCoreConfig::release.
Q_INVOKABLE bool isRegistered()
See SsuCoreConfig::isRegistered.
QString credentialsScope(const QString &repoName, bool rndRepo=false)
Q_INVOKABLE void unregister()
Q_INVOKABLE void setFlavour(const QString &flavour)
See SsuCoreConfig::setFlavour.
void updateCredentials(bool force=false)
Q_INVOKABLE void setDomain(const QString &domain)
See SsuCoreConfig::setDomain.
Q_INVOKABLE void setDeviceMode(DeviceModeFlags mode, enum EditMode editMode=Replace)
See SsuCoreConfig::setDeviceMode.
Q_INVOKABLE QString brand()
See SsuCoreConfig::brand.
Q_INVOKABLE QString lastError()
Q_INVOKABLE DeviceModeFlags deviceMode()
See SsuCoreConfig::deviceMode.
#define SSU_REPO_CONFIGURATION_DIR
Path to config.d for ssu domain configurations.
#define SSU_GROUP_ID
The group ID ssu expects to run as. This is usually the GID of the main phone user.
#define SSU_PROTOCOL_VERSION
The ssu protocol version used by the ssu client libraries.
#define SSU_REPO_CONFIGURATION
Path to the ssu domain configuration file.
#define SSU_CONFIGURATION
Path to the main ssu configuration file.