File indexing completed on 2025-01-05 04:35:36
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0003 SPDX-FileCopyrightText: 2020-2021 Harald Sitter <sitter@kde.org> 0004 */ 0005 0006 #include "authhelper.h" 0007 #include <KLocalizedString> 0008 #include <KUser> 0009 #include <QProcess> 0010 #include <QRegularExpression> 0011 #include <kauth/helpersupport.h> 0012 0013 namespace 0014 { 0015 bool isValidUserName(const QString &name) 0016 { 0017 // https://systemd.io/USER_NAMES/ 0018 static QRegularExpression expr(QStringLiteral("^[a-z_][a-z0-9_-]*$")); 0019 return expr.match(name).hasMatch(); 0020 } 0021 0022 std::optional<QString> callerUidToUserName() 0023 { 0024 const auto uid = KAuth::HelperSupport::callerUid(); 0025 if (uid < 0) { 0026 return {}; 0027 } 0028 0029 const KUser user(static_cast<K_UID>(uid)); 0030 if (!user.isValid()) { 0031 return {}; 0032 } 0033 0034 auto name = user.loginName(); 0035 if (name.isEmpty()) { 0036 return {}; 0037 } 0038 return name; 0039 } 0040 } // namespace 0041 0042 ActionReply AuthHelper::isuserknown(const QVariantMap &args) 0043 { 0044 const auto username = args.value(QStringLiteral("username")).toString(); 0045 if (!isValidUserName(username)) { 0046 auto reply = ActionReply::HelperErrorReply(); 0047 reply.setErrorDescription(xi18nc("@info", "User name <resource>%1</resource> is not valid as the name of a Samba user; cannot check for its existence.", username)); 0048 return reply; 0049 } 0050 0051 QProcess p; 0052 const auto program = QStringLiteral("pdbedit"); 0053 const auto arguments = QStringList({QStringLiteral("--debuglevel=0"), QStringLiteral("--user"), username }); 0054 p.setProgram(program); 0055 p.setArguments(arguments); 0056 p.start(); 0057 // Should be fairly quick: short timeout. 0058 const int pdbeditTimeout = 4000; // milliseconds 0059 p.waitForFinished(pdbeditTimeout); 0060 0061 if (p.exitStatus() != QProcess::NormalExit) { 0062 // QByteArray can't do direct conversion to QString 0063 const QString errorText = QString::fromUtf8(p.readAllStandardOutput()); 0064 auto reply = ActionReply::HelperErrorReply(); 0065 reply.setErrorDescription(xi18nc("@info '%1 %2' together make up a terminal command; %3 is the command's output", 0066 "Command <command>%1 %2</command> failed:<nl/><nl/>%3", program, arguments.join(QLatin1Char(' ')), errorText)); 0067 return reply; 0068 } 0069 0070 ActionReply reply; 0071 reply.addData(QStringLiteral("exists"), p.exitCode() == 0); 0072 return reply; 0073 } 0074 0075 ActionReply AuthHelper::createuser(const QVariantMap &args) 0076 { 0077 const auto username = callerUidToUserName(); 0078 if (!username.has_value()) { 0079 auto reply = ActionReply::HelperErrorReply(); 0080 reply.setErrorDescription(xi18nc("@info error while looking up uid from dbus", "Could not resolve calling user.")); 0081 return reply; 0082 } 0083 const auto password = args.value(QStringLiteral("password")).toString(); 0084 if (password.isEmpty()) { 0085 auto reply = ActionReply::HelperErrorReply(); 0086 reply.setErrorDescription(i18nc("@info", "For security reasons, creating Samba users with empty passwords is not allowed.")); 0087 return reply; 0088 } 0089 0090 QProcess p; 0091 p.setProgram(QStringLiteral("smbpasswd")); 0092 p.setArguments({ 0093 QStringLiteral("-L"), /* local mode */ 0094 QStringLiteral("-s"), /* read from stdin */ 0095 QStringLiteral("-D"), QStringLiteral("0"), /* force-disable debug */ 0096 QStringLiteral("-a"), /* add user */ 0097 username.value() }); 0098 p.start(); 0099 // despite being in silent mode we still need to write the password twice! 0100 p.write((password + QStringLiteral("\n")).toUtf8()); 0101 p.write((password + QStringLiteral("\n")).toUtf8()); 0102 p.waitForBytesWritten(); 0103 p.closeWriteChannel(); 0104 p.waitForFinished(); 0105 0106 if (p.exitStatus() != QProcess::NormalExit) { 0107 auto reply = ActionReply::HelperErrorReply(); 0108 reply.setErrorDescription(QString::fromUtf8(p.readAllStandardError())); 0109 return reply; 0110 } 0111 0112 ActionReply reply; 0113 reply.addData(QStringLiteral("created"), p.exitCode() == 0); 0114 // stderr will generally contain info on what went wrong so forward it 0115 // so the UI may display it 0116 reply.addData(QStringLiteral("stderr"), p.readAllStandardError()); 0117 return reply; 0118 } 0119 0120 ActionReply AuthHelper::addtogroup(const QVariantMap &args) 0121 { 0122 const auto group = args.value(QStringLiteral("group")).toString(); 0123 const auto user = callerUidToUserName(); 0124 if (!user.has_value()) { 0125 auto reply = ActionReply::HelperErrorReply(); 0126 reply.setErrorDescription(xi18nc("@info error while looking up uid from dbus", "Could not resolve calling user.")); 0127 return reply; 0128 } 0129 if (!isValidUserName(group)) { 0130 auto reply = ActionReply::HelperErrorReply(); 0131 reply.setErrorDescription(xi18nc("@info", "<resource>%1</resource> is not a valid group name; cannot make user <resource>%2</resource> a member of it.", group, user.value())); 0132 return reply; 0133 } 0134 // Harden against some input abuse. 0135 // Keep this condition in sync with the one in groupmanager.cpp 0136 if (group.contains(QLatin1String("admin")) || 0137 group.contains(QLatin1String("root"))) { 0138 auto reply = ActionReply::HelperErrorReply(); 0139 reply.setErrorDescription(xi18nc("@info", "For security reasons, cannot make user <resource>%1</resource> a member of group <resource>%2</resource>. \ 0140 The group name is insecure; valid group names do not \ 0141 include the text <resource>admin</resource> or <resource>root</resource>.")); 0142 return reply; 0143 } 0144 0145 QProcess p; 0146 #if defined(Q_OS_FREEBSD) 0147 p.setProgram(QStringLiteral("pw")); 0148 p.setArguments({ 0149 QStringLiteral("group"), 0150 QStringLiteral("mod"), 0151 QStringLiteral("{%1}").arg(group), 0152 QStringLiteral("-m"), 0153 QStringLiteral("{%1}").arg(user.value()) }); 0154 #elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) 0155 p.setProgram(QStringLiteral("/usr/sbin/usermod")); 0156 p.setArguments({ 0157 QStringLiteral("--append"), 0158 QStringLiteral("--groups"), 0159 group, 0160 user.value() }); 0161 #else 0162 # error "Platform lacks group management support. Please add support." 0163 #endif 0164 0165 p.start(); 0166 p.waitForFinished(1000); 0167 0168 if (p.exitCode() != 0 || p.exitStatus() != QProcess::NormalExit) { 0169 auto reply = ActionReply::HelperErrorReply(); 0170 reply.setErrorDescription(QString::fromUtf8(p.readAll())); 0171 return reply; 0172 } 0173 0174 return ActionReply::SuccessReply(); 0175 } 0176 0177 KAUTH_HELPER_MAIN("org.kde.filesharing.samba", AuthHelper)