Warning, file /plasma/ksshaskpass/src/main.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * SPDX-FileCopyrightText: 2006 Hans van Leeuwen <hanz@hanz.nl> 0003 * SPDX-FileCopyrightText: 2008-2010 Armin Berres <armin@space-based.de> 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include <memory> 0008 #include <sys/resource.h> 0009 0010 #include <KAboutData> 0011 #include <KLocalizedString> 0012 #include <KMessageBox> 0013 #include <KPasswordDialog> 0014 #include <kwallet.h> 0015 #include <kwidgetsaddons_version.h> 0016 0017 #include <QApplication> 0018 #include <QCommandLineOption> 0019 #include <QCommandLineParser> 0020 #include <QInputDialog> 0021 #include <QLoggingCategory> 0022 #include <QPointer> 0023 #include <QRegularExpression> 0024 #include <QTextStream> 0025 0026 Q_LOGGING_CATEGORY(LOG_KSSHASKPASS, "ksshaskpass") 0027 0028 enum Type { 0029 TypePassword, 0030 TypeClearText, 0031 TypeConfirm, 0032 }; 0033 0034 // Try to understand what we're asked for by parsing the phrase. Unfortunately, sshaskpass interface does not 0035 // include any saner methods to pass the action or the name of the keyfile. Fortunately, openssh and git 0036 // has no i18n, so this should work for all languages as long as the string is unchanged. 0037 static void parsePrompt(const QString &prompt, QString &identifier, bool &ignoreWallet, enum Type &type) 0038 { 0039 QRegularExpressionMatch match; 0040 0041 // openssh sshconnect2.c 0042 // Case: password for authentication on remote ssh server 0043 match = QRegularExpression(QStringLiteral("^(.*@.*)'s password( \\(JPAKE\\))?: $")).match(prompt); 0044 if (match.hasMatch()) { 0045 identifier = match.captured(1); 0046 type = TypePassword; 0047 ignoreWallet = false; 0048 return; 0049 } 0050 0051 // openssh sshconnect2.c 0052 // Case: password change request 0053 match = QRegularExpression(QStringLiteral("^(Enter|Retype) (.*@.*)'s (old|new) password: $")).match(prompt); 0054 if (match.hasMatch()) { 0055 identifier = match.captured(2); 0056 type = TypePassword; 0057 ignoreWallet = true; 0058 return; 0059 } 0060 0061 // openssh sshconnect2.c and sshconnect1.c 0062 // Case: asking for passphrase for a certain keyfile 0063 match = QRegularExpression(QStringLiteral("^Enter passphrase for( RSA)? key '(.*)': $")).match(prompt); 0064 if (match.hasMatch()) { 0065 identifier = match.captured(2); 0066 type = TypePassword; 0067 ignoreWallet = false; 0068 return; 0069 } 0070 0071 // openssh ssh-add.c 0072 // Case: asking for passphrase for a certain keyfile for the first time => we should try a password from the wallet 0073 match = QRegularExpression(QStringLiteral("^Enter passphrase for (.*?)( \\(will confirm each use\\))?: $")).match(prompt); 0074 if (match.hasMatch()) { 0075 identifier = match.captured(1); 0076 type = TypePassword; 0077 ignoreWallet = false; 0078 return; 0079 } 0080 0081 // openssh ssh-add.c 0082 // Case: re-asking for passphrase for a certain keyfile => probably we've tried a password from the wallet, no point 0083 // in trying it again 0084 match = QRegularExpression(QStringLiteral("^Bad passphrase, try again for (.*?)( \\(will confirm each use\\))?: $")).match(prompt); 0085 if (match.hasMatch()) { 0086 identifier = match.captured(1); 0087 type = TypePassword; 0088 ignoreWallet = true; 0089 return; 0090 } 0091 0092 // openssh ssh-pkcs11.c 0093 // Case: asking for PIN for some token label 0094 match = QRegularExpression(QStringLiteral("Enter PIN for '(.*)': $")).match(prompt); 0095 if (match.hasMatch()) { 0096 identifier = match.captured(1); 0097 type = TypePassword; 0098 ignoreWallet = false; 0099 return; 0100 } 0101 0102 // openssh mux.c 0103 match = QRegularExpression(QStringLiteral("^(Allow|Terminate) shared connection to (.*)\\? $")).match(prompt); 0104 if (match.hasMatch()) { 0105 identifier = match.captured(2); 0106 type = TypeConfirm; 0107 ignoreWallet = true; 0108 return; 0109 } 0110 0111 // openssh mux.c 0112 match = QRegularExpression(QStringLiteral("^Open (.* on .*)?$")).match(prompt); 0113 if (match.hasMatch()) { 0114 identifier = match.captured(1); 0115 type = TypeConfirm; 0116 ignoreWallet = true; 0117 return; 0118 } 0119 0120 // openssh mux.c 0121 match = QRegularExpression(QStringLiteral("^Allow forward to (.*:.*)\\? $")).match(prompt); 0122 if (match.hasMatch()) { 0123 identifier = match.captured(1); 0124 type = TypeConfirm; 0125 ignoreWallet = true; 0126 return; 0127 } 0128 0129 // openssh mux.c 0130 match = QRegularExpression(QStringLiteral("^Disable further multiplexing on shared connection to (.*)? $")).match(prompt); 0131 if (match.hasMatch()) { 0132 identifier = match.captured(1); 0133 type = TypeConfirm; 0134 ignoreWallet = true; 0135 return; 0136 } 0137 0138 // openssh ssh-agent.c 0139 match = QRegularExpression(QStringLiteral("^Allow use of key (.*)?\\nKey fingerprint .*\\.$")).match(prompt); 0140 if (match.hasMatch()) { 0141 identifier = match.captured(1); 0142 type = TypeConfirm; 0143 ignoreWallet = true; 0144 return; 0145 } 0146 0147 // openssh sshconnect.c 0148 match = QRegularExpression(QStringLiteral("^Add key (.*) \\(.*\\) to agent\\?$")).match(prompt); 0149 if (match.hasMatch()) { 0150 identifier = match.captured(1); 0151 type = TypeConfirm; 0152 ignoreWallet = true; 0153 return; 0154 } 0155 0156 // git imap-send.c 0157 // Case: asking for password by git imap-send 0158 match = QRegularExpression(QStringLiteral("^Password \\((.*@.*)\\): $")).match(prompt); 0159 if (match.hasMatch()) { 0160 identifier = match.captured(1); 0161 type = TypePassword; 0162 ignoreWallet = false; 0163 return; 0164 } 0165 0166 // git credential.c 0167 // Case: asking for username by git without specifying any other information 0168 match = QRegularExpression(QStringLiteral("^Username: $")).match(prompt); 0169 if (match.hasMatch()) { 0170 identifier = QString(); 0171 type = TypeClearText; 0172 ignoreWallet = true; 0173 return; 0174 } 0175 0176 // git credential.c 0177 // Case: asking for password by git without specifying any other information 0178 match = QRegularExpression(QStringLiteral("^Password: $")).match(prompt); 0179 if (match.hasMatch()) { 0180 identifier = QString(); 0181 type = TypePassword; 0182 ignoreWallet = true; 0183 return; 0184 } 0185 0186 // git credential.c 0187 // Case: asking for username by git for some identifier 0188 match = QRegularExpression(QStringLiteral("^Username for '(.*)': $")).match(prompt); 0189 if (match.hasMatch()) { 0190 identifier = match.captured(1); 0191 type = TypeClearText; 0192 ignoreWallet = false; 0193 return; 0194 } 0195 0196 // git credential.c 0197 // Case: asking for password by git for some identifier 0198 match = QRegularExpression(QStringLiteral("^Password for '(.*)': $")).match(prompt); 0199 if (match.hasMatch()) { 0200 identifier = match.captured(1); 0201 type = TypePassword; 0202 ignoreWallet = false; 0203 return; 0204 } 0205 0206 // Case: username extraction from git-lfs 0207 match = QRegularExpression(QStringLiteral("^Username for \"(.*?)\"$")).match(prompt); 0208 if (match.hasMatch()) { 0209 identifier = match.captured(1); 0210 type = TypeClearText; 0211 ignoreWallet = false; 0212 return; 0213 } 0214 0215 // Case: password extraction from git-lfs 0216 match = QRegularExpression(QStringLiteral("^Password for \"(.*?)\"$")).match(prompt); 0217 if (match.hasMatch()) { 0218 identifier = match.captured(1); 0219 type = TypePassword; 0220 ignoreWallet = false; 0221 return; 0222 } 0223 0224 // Case: password extraction from mercurial, see bug 380085 0225 match = QRegularExpression(QStringLiteral("^(.*?)'s password: $")).match(prompt); 0226 if (match.hasMatch()) { 0227 identifier = match.captured(1); 0228 type = TypePassword; 0229 ignoreWallet = false; 0230 return; 0231 } 0232 0233 // Nothing matched; either it was called by some sort of a script with a custom prompt (i.e. not ssh-add), or 0234 // strings we're looking for were broken. Issue a warning and continue without identifier. 0235 qCWarning(LOG_KSSHASKPASS) << "Unable to parse phrase" << prompt; 0236 } 0237 0238 int main(int argc, char **argv) 0239 { 0240 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0241 QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); 0242 #endif 0243 QApplication app(argc, argv); 0244 KLocalizedString::setApplicationDomain("ksshaskpass"); 0245 0246 // TODO update it. 0247 KAboutData about(QStringLiteral("ksshaskpass"), 0248 i18n("Ksshaskpass"), 0249 QStringLiteral(PROJECT_VERSION), 0250 i18n("KDE version of ssh-askpass"), 0251 KAboutLicense::GPL, 0252 i18n("(c) 2006 Hans van Leeuwen\n(c) 2008-2010 Armin Berres\n(c) 2013 Pali Rohár"), 0253 i18n("Ksshaskpass allows you to interactively prompt users for a passphrase for ssh-add"), 0254 QStringLiteral("https://commits.kde.org/ksshaskpass"), 0255 QStringLiteral("armin@space-based.de")); 0256 0257 about.addAuthor(i18n("Armin Berres"), i18n("Current author"), QStringLiteral("armin@space-based.de")); 0258 about.addAuthor(i18n("Hans van Leeuwen"), i18n("Original author"), QStringLiteral("hanz@hanz.nl")); 0259 about.addAuthor(i18n("Pali Rohár"), i18n("Contributor"), QStringLiteral("pali.rohar@gmail.com")); 0260 about.addAuthor(i18n("Armin Berres"), i18n("Current author"), QStringLiteral("armin@space-based.de"), QString()); 0261 about.addAuthor(i18n("Hans van Leeuwen"), i18n("Original author"), QStringLiteral("hanz@hanz.nl"), QString()); 0262 about.addAuthor(i18n("Pali Rohár"), i18n("Contributor"), QStringLiteral("pali.rohar@gmail.com"), QString()); 0263 KAboutData::setApplicationData(about); 0264 0265 QCommandLineParser parser; 0266 about.setupCommandLine(&parser); 0267 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+[prompt]"), i18nc("Name of a prompt for a password", "Prompt"))); 0268 0269 parser.process(app); 0270 about.processCommandLine(&parser); 0271 0272 const QString walletFolder = app.applicationName(); 0273 QString dialog = i18n("Please enter passphrase"); // Default dialog text. 0274 QString identifier; 0275 QString item; 0276 bool ignoreWallet = false; 0277 enum Type type = TypePassword; 0278 0279 // Parse commandline arguments 0280 if (!parser.positionalArguments().isEmpty()) { 0281 dialog = parser.positionalArguments().at(0); 0282 parsePrompt(dialog, identifier, ignoreWallet, type); 0283 } 0284 0285 // Open KWallet to see if an item was previously stored 0286 std::unique_ptr<KWallet::Wallet> wallet(ignoreWallet ? nullptr : KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 0)); 0287 0288 if ((!ignoreWallet) && (!identifier.isNull()) && wallet.get() && wallet->hasFolder(walletFolder)) { 0289 wallet->setFolder(walletFolder); 0290 0291 wallet->readPassword(identifier, item); 0292 0293 if (item.isEmpty()) { 0294 // There was a bug in previous versions of ksshaskpass that caused it to create keys with single quotes 0295 // around the identifier and even older versions have an extra space appended to the identifier. 0296 // key file name. Try these keys too, and, if there's a match, ensure that it's properly 0297 // replaced with proper one. 0298 for (auto templ : QStringList{QStringLiteral("'%0'"), QStringLiteral("%0 "), QStringLiteral("'%0' ")}) { 0299 const QString keyFile = templ.arg(identifier); 0300 wallet->readPassword(keyFile, item); 0301 if (!item.isEmpty()) { 0302 qCWarning(LOG_KSSHASKPASS) << "Detected legacy key for " << identifier << ", enabling workaround"; 0303 wallet->renameEntry(keyFile, identifier); 0304 break; 0305 } 0306 } 0307 } 0308 } 0309 0310 if (!item.isEmpty()) { 0311 QTextStream(stdout) << item; 0312 return 0; 0313 } 0314 0315 // Item could not be retrieved from wallet. Open dialog 0316 switch (type) { 0317 case TypeConfirm: { 0318 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0319 if (KMessageBox::questionTwoActions(nullptr, 0320 dialog, 0321 i18n("Ksshaskpass"), 0322 KGuiItem(i18nc("@action:button", "Accept"), QStringLiteral("dialog-ok")), 0323 KStandardGuiItem::cancel()) 0324 != KMessageBox::PrimaryAction) { 0325 #else 0326 if (KMessageBox::questionYesNo(nullptr, dialog, i18n("Ksshaskpass")) != KMessageBox::Yes) { 0327 #endif 0328 // dialog has been canceled 0329 return 1; 0330 } 0331 item = QStringLiteral("yes\n"); 0332 break; 0333 } 0334 case TypeClearText: 0335 // Should use a dialog with visible input, but KPasswordDialog doesn't support that and 0336 // other available dialog types don't have a "Keep" checkbox. 0337 /* fallthrough */ 0338 case TypePassword: { 0339 // create the password dialog, but only show "Enable Keep" button, if the wallet is open 0340 KPasswordDialog::KPasswordDialogFlag flag(KPasswordDialog::NoFlags); 0341 if (wallet.get()) { 0342 flag = KPasswordDialog::ShowKeepPassword; 0343 } 0344 QPointer<KPasswordDialog> kpd = new KPasswordDialog(nullptr, flag); 0345 0346 kpd->setPrompt(dialog); 0347 kpd->setWindowTitle(i18n("Ksshaskpass")); 0348 // We don't want to dump core when the password dialog is shown, because it could contain the entered password. 0349 // KPasswordDialog::disableCoreDumps() seems to be gone in KDE 4 -- do it manually 0350 struct rlimit rlim; 0351 rlim.rlim_cur = rlim.rlim_max = 0; 0352 setrlimit(RLIMIT_CORE, &rlim); 0353 0354 if (kpd->exec() == QDialog::Accepted) { 0355 item = kpd->password(); 0356 // If "Enable Keep" is enabled, open/create a folder in KWallet and store the password. 0357 if ((!identifier.isNull()) && wallet.get() && kpd->keepPassword()) { 0358 if (!wallet->hasFolder(walletFolder)) { 0359 wallet->createFolder(walletFolder); 0360 } 0361 wallet->setFolder(walletFolder); 0362 wallet->writePassword(identifier, item); 0363 } 0364 } else { 0365 // dialog has been canceled 0366 return 1; 0367 } 0368 break; 0369 } 0370 } 0371 0372 QTextStream out(stdout); 0373 out << item << "\n"; 0374 return 0; 0375 }