File indexing completed on 2025-01-05 04:35:36

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de>
0004     SPDX-FileCopyrightText: 2011 Rodrigo Belem <rclbelem@gmail.com>
0005     SPDX-FileCopyrightText: 2015-2020 Harald Sitter <sitter@kde.org>
0006     SPDX-FileCopyrightText: 2019 Nate Graham <nate@kde.org>
0007     SPDX-FileCopyrightText: 2021 Slava Aseev <nullptrnine@basealt.ru>
0008 */
0009 
0010 #include "sambausershareplugin.h"
0011 
0012 #include <QDebug>
0013 #include <QQmlApplicationEngine>
0014 #include <QQuickWidget>
0015 #include <QQuickItem>
0016 #include <QMetaMethod>
0017 #include <QVBoxLayout>
0018 #include <KLocalizedString>
0019 #include <QTimer>
0020 #include <QQmlContext>
0021 #include <QPushButton>
0022 #include <QDBusInterface>
0023 #include <QDBusConnection>
0024 
0025 #include <KLocalizedContext>
0026 #include <KMessageBox>
0027 #include <KPluginFactory>
0028 #include <KService>
0029 #include <KIO/CommandLauncherJob>
0030 
0031 
0032 #include "groupmanager.h"
0033 
0034 #ifdef SAMBA_INSTALL
0035 #include "sambainstaller.h"
0036 #endif
0037 
0038 K_PLUGIN_CLASS_WITH_JSON(SambaUserSharePlugin, "sambausershareplugin.json")
0039 
0040 
0041 SambaUserSharePlugin::SambaUserSharePlugin(QObject *parent)
0042     : KPropertiesDialogPlugin(parent)
0043     , m_url(properties->item().mostLocalUrl().toLocalFile())
0044     , m_userManager(new UserManager(this))
0045 {
0046     if (m_url.isEmpty()) {
0047         return;
0048     }
0049 
0050     const QFileInfo pathInfo(m_url);
0051     if (!pathInfo.permission(QFile::ReadUser | QFile::WriteUser)) {
0052         return;
0053     }
0054 
0055     // TODO: this could be made to load delayed via invokemethod. we technically don't need to fully load
0056     //   the backing data in the ctor, only the qml view with busyindicator
0057     // TODO: relatedly if we make ShareContext and the Model more async vis a vis construction we can init them from
0058     //   QML and stop holding them as members in the plugin
0059     m_context = new ShareContext(properties->item().mostLocalUrl(), this);
0060     // FIXME maybe the manager ought to be owned by the model
0061     qmlRegisterAnonymousType<UserManager>("org.kde.filesharing.samba", 1);
0062     qmlRegisterAnonymousType<User>("org.kde.filesharing.samba", 1);
0063     m_model = new UserPermissionModel(m_context->m_shareData, m_userManager, this);
0064     qmlRegisterAnonymousType<PermissionsHelper>("org.kde.filesharing.samba", 1);
0065     m_permissionsHelper = new PermissionsHelper(m_context->m_shareData.path(), m_userManager, m_model);
0066 
0067 #ifdef SAMBA_INSTALL
0068     qmlRegisterType<SambaInstaller>("org.kde.filesharing.samba", 1, 0, "Installer");
0069 #endif
0070     qmlRegisterType<GroupManager>("org.kde.filesharing.samba", 1, 0, "GroupManager");
0071     // Need access to the column enum, so register this as uncreatable.
0072     qmlRegisterUncreatableType<UserPermissionModel>("org.kde.filesharing.samba", 1, 0, "UserPermissionModel",
0073                                                     QStringLiteral("Access through sambaPlugin.userPermissionModel"));
0074     qmlRegisterAnonymousType<ShareContext>("org.kde.filesharing.samba", 1);
0075     qmlRegisterAnonymousType<SambaUserSharePlugin>("org.kde.filesharing.samba", 1);
0076 
0077     m_page.reset(new QWidget(qobject_cast<KPropertiesDialog *>(parent)));
0078     m_page->setAttribute(Qt::WA_TranslucentBackground);
0079     auto widget = new QQuickWidget(m_page.get());
0080     // Set translation domain before setting the source so strings gets translated.
0081     auto i18nContext = new KLocalizedContext(widget->engine());
0082     i18nContext->setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
0083     widget->engine()->rootContext()->setContextObject(i18nContext);
0084 
0085     widget->setResizeMode(QQuickWidget::SizeRootObjectToView);
0086     widget->setFocusPolicy(Qt::StrongFocus);
0087     widget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
0088     widget->quickWindow()->setColor(Qt::transparent);
0089     auto layout = new QVBoxLayout(m_page.get());
0090     layout->setContentsMargins({});
0091     layout->addWidget(widget);
0092 
0093     widget->rootContext()->setContextProperty(QStringLiteral("sambaPlugin"), this);
0094 
0095     const QUrl url(QStringLiteral("qrc:/org.kde.filesharing.samba/qml/main.qml"));
0096     widget->setSource(url);
0097 
0098     properties->setFileSharingPage(m_page.get());
0099     if (qEnvironmentVariableIsSet("TEST_FOCUS_SHARE")) {
0100         QTimer::singleShot(100, properties, &KPropertiesDialog::showFileSharingPage);
0101     }
0102 
0103     QTimer::singleShot(0, [this] {
0104         connect(m_userManager, &UserManager::loaded, this, [this] {
0105             m_permissionsHelper->reload();
0106             setReady(true);
0107         });
0108         m_userManager->load();
0109     });
0110 }
0111 
0112 bool SambaUserSharePlugin::isSambaInstalled()
0113 {
0114     return QFile::exists(QStringLiteral("/usr/sbin/smbd"))
0115         || QFile::exists(QStringLiteral("/usr/local/sbin/smbd"));
0116 }
0117 
0118 void SambaUserSharePlugin::showSambaStatus()
0119 {
0120     auto job = new KIO::CommandLauncherJob(QStringLiteral("kinfocenter"), {QStringLiteral("kcm_samba")});
0121     job->setDesktopName(QStringLiteral("org.kde.kinfocenter"));
0122     job->start();
0123 }
0124 
0125 void SambaUserSharePlugin::applyChanges()
0126 {
0127     qDebug() << "!!! applying changes !!!" << m_context->enabled() << m_context->name() << m_context->guestEnabled() << m_model->getAcl() << m_context->m_shareData.path();
0128     if (!m_context->enabled()) {
0129         reportRemove(m_context->m_shareData.remove());
0130         return;
0131     }
0132 
0133     // TODO: should run this through reportAdd() as well, ACLs may be invalid and then we shouldn't try to save
0134     m_context->m_shareData.setAcl(m_model->getAcl());
0135     reportAdd(m_context->m_shareData.save());
0136 }
0137 
0138 static QString errorToString(KSambaShareData::UserShareError error)
0139 {
0140     // KSambaShare is a right mess. Every function with an error returns the same enum but can only return a subset of
0141     // possible values. Even so, because it returns the enum we had best mapped all values to semi-suitable string
0142     // representations even when those are utter garbage when they require specific (e.g. an invalid ACL) that
0143     // we do not have here.
0144     switch (error) {
0145     case KSambaShareData::UserShareNameOk: Q_FALLTHROUGH();
0146     case KSambaShareData::UserSharePathOk: Q_FALLTHROUGH();
0147     case KSambaShareData::UserShareAclOk: Q_FALLTHROUGH();
0148     case KSambaShareData::UserShareCommentOk: Q_FALLTHROUGH();
0149     case KSambaShareData::UserShareGuestsOk: Q_FALLTHROUGH();
0150     case KSambaShareData::UserShareOk:
0151         // Technically anything but UserShareOk cannot happen, but best handle everything regardless.
0152         return QString();
0153     case KSambaShareData::UserShareExceedMaxShares:
0154         return i18nc("@info detailed error messsage",
0155                      "You have exhausted the maximum amount of shared directories you may have active at the same time.");
0156     case KSambaShareData::UserShareNameInvalid:
0157         return i18nc("@info detailed error messsage", "The share name is invalid.");
0158     case KSambaShareData::UserShareNameInUse:
0159         return i18nc("@info detailed error messsage", "The share name is already in use for a different directory.");
0160     case KSambaShareData::UserSharePathInvalid:
0161         return i18nc("@info detailed error messsage", "The path is invalid.");
0162     case KSambaShareData::UserSharePathNotExists:
0163         return i18nc("@info detailed error messsage", "The path does not exist.");
0164     case KSambaShareData::UserSharePathNotDirectory:
0165         return i18nc("@info detailed error messsage", "The path is not a directory.");
0166     case KSambaShareData::UserSharePathNotAbsolute:
0167         return i18nc("@info detailed error messsage", "The path is relative.");
0168     case KSambaShareData::UserSharePathNotAllowed:
0169         return i18nc("@info detailed error messsage", "This path may not be shared.");
0170     case KSambaShareData::UserShareAclInvalid:
0171         return i18nc("@info detailed error messsage", "The access rule is invalid.");
0172     case KSambaShareData::UserShareAclUserNotValid:
0173         return i18nc("@info detailed error messsage", "An access rule's user is not valid.");
0174     case KSambaShareData::UserShareGuestsInvalid:
0175         return i18nc("@info detailed error messsage", "The 'Guest' access rule is invalid.");
0176     case KSambaShareData::UserShareGuestsNotAllowed:
0177         return i18nc("@info detailed error messsage", "Enabling guest access is not allowed.");
0178     case KSambaShareData::UserShareSystemError:
0179         return KSambaShare::instance()->lastSystemErrorString().simplified();
0180     }
0181     Q_UNREACHABLE();
0182     return QString();
0183 }
0184 
0185 void SambaUserSharePlugin::reportAdd(KSambaShareData::UserShareError error)
0186 {
0187     if (error == KSambaShareData::UserShareOk) {
0188         return;
0189     }
0190 
0191     QString errorMessage = errorToString(error);
0192     if (error == KSambaShareData::UserShareSystemError) {
0193         // System errors are (untranslated) CLI output. Give them localized context.
0194         errorMessage = xi18nc("@info error in the underlying binaries. %1 is CLI output",
0195                               "<para>An error occurred while trying to share the directory."
0196                               " The share has not been created.</para>"
0197                               "<para>Samba internals report:</para><message>%1</message>",
0198                               errorMessage);
0199     }
0200     KMessageBox::error(qobject_cast<QWidget *>(parent()),
0201                        errorMessage,
0202                        i18nc("@info/title", "Failed to Create Network Share"));
0203 }
0204 
0205 void SambaUserSharePlugin::reportRemove(KSambaShareData::UserShareError error)
0206 {
0207     if (error == KSambaShareData::UserShareOk) {
0208         return;
0209     }
0210 
0211     QString errorMessage = errorToString(error);
0212     if (error == KSambaShareData::UserShareSystemError) {
0213         // System errors are (untranslated) CLI output. Give them localized context.
0214         errorMessage = xi18nc("@info error in the underlying binaries. %1 is CLI output",
0215                               "<para>An error occurred while trying to un-share the directory."
0216                               " The share has not been removed.</para>"
0217                               "<para>Samba internals report:</para><message>%1</message>",
0218                               errorMessage);
0219     }
0220     KMessageBox::error(qobject_cast<QWidget *>(parent()),
0221                        errorMessage,
0222                        i18nc("@info/title", "Failed to Remove Network Share"));
0223 }
0224 
0225 bool SambaUserSharePlugin::isReady() const
0226 {
0227     return m_ready;
0228 }
0229 
0230 void SambaUserSharePlugin::setReady(bool ready)
0231 {
0232     m_ready = ready;
0233     Q_EMIT readyChanged();
0234 }
0235 
0236 void SambaUserSharePlugin::reboot()
0237 {
0238     QDBusInterface kdeShutdown(QStringLiteral("org.kde.Shutdown"), QStringLiteral("/Shutdown"), QStringLiteral("org.kde.Shutdown"));
0239     kdeShutdown.call(QStringLiteral("logoutAndReboot"));
0240 }
0241 
0242 #include "sambausershareplugin.moc"