File indexing completed on 2024-04-21 05:51:27

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "SSHProcessInfo.h"
0009 
0010 // Unix
0011 #ifndef Q_OS_WIN
0012 #include <arpa/inet.h>
0013 #include <cerrno>
0014 #include <netinet/in.h>
0015 #include <pwd.h>
0016 #include <sys/param.h>
0017 #include <sys/socket.h>
0018 #include <unistd.h>
0019 #endif // Q_OS_WIN
0020 
0021 // Qt
0022 #include <QDebug>
0023 
0024 using namespace Konsole;
0025 
0026 SSHProcessInfo::SSHProcessInfo(const ProcessInfo &process)
0027     : _process(process)
0028     , _user(QString())
0029     , _host(QString())
0030     , _port(QString())
0031     , _command(QString())
0032 {
0033     bool ok = false;
0034 
0035     // check that this is a SSH process
0036     const QString &name = _process.name(&ok);
0037 
0038     if (!ok || name != QLatin1String("ssh")) {
0039         if (!ok) {
0040             qWarning() << "Could not read process info";
0041         } else {
0042             qWarning() << "Process is not a SSH process";
0043         }
0044 
0045         return;
0046     }
0047 
0048     // read arguments
0049     const QVector<QString> &args = _process.arguments(&ok);
0050 
0051     // SSH options
0052     // these are taken from the SSH manual ( accessed via 'man ssh' )
0053 
0054     // options which take no arguments
0055     static const QString noArgumentOptions(QStringLiteral("1246AaCfgKkMNnqsTtVvXxYy"));
0056     // options which take one argument
0057     static const QString singleArgumentOptions(QStringLiteral("bcDeFIiJLlmOopRSWw"));
0058 
0059     if (ok) {
0060         // find the username, host and command arguments
0061         //
0062         // the username/host is assumed to be the first argument
0063         // which is not an option
0064         // ( ie. does not start with a dash '-' character )
0065         // or an argument to a previous option.
0066         //
0067         // the command, if specified, is assumed to be the argument following
0068         // the username and host
0069         //
0070         // note that we skip the argument at index 0 because that is the
0071         // program name ( expected to be 'ssh' in this case )
0072         for (int i = 1; i < args.count(); i++) {
0073             // If this one is an option ...
0074             // Most options together with its argument will be skipped.
0075             if (args[i].startsWith(QLatin1Char('-'))) {
0076                 const QChar optionChar = (args[i].length() > 1) ? args[i][1] : QLatin1Char('\0');
0077                 // for example: -p2222 or -p 2222 ?
0078                 const bool optionArgumentCombined = args[i].length() > 2;
0079 
0080                 if (noArgumentOptions.contains(optionChar)) {
0081                     continue;
0082                 } else if (singleArgumentOptions.contains(optionChar)) {
0083                     QString argument;
0084                     if (optionArgumentCombined) {
0085                         argument = args[i].mid(2);
0086                     } else {
0087                         // Verify correct # arguments are given
0088                         if ((i + 1) < args.count()) {
0089                             argument = args[i + 1];
0090                         }
0091                         i++;
0092                     }
0093 
0094                     // support using `-l user` to specify username.
0095                     if (optionChar == QLatin1Char('l')) {
0096                         _user = argument;
0097                     }
0098                     // support using `-p port` to specify port.
0099                     else if (optionChar == QLatin1Char('p')) {
0100                         _port = argument;
0101                     }
0102 
0103                     continue;
0104                 }
0105             }
0106 
0107             // check whether the host has been found yet
0108             // if not, this must be the username/host argument
0109             if (_host.isEmpty()) {
0110                 // check to see if only a hostname is specified, or whether
0111                 // both a username and host are specified ( in which case they
0112                 // are separated by an '@' character:  username@host )
0113 
0114                 const int separatorPosition = args[i].indexOf(QLatin1Char('@'));
0115                 if (separatorPosition != -1) {
0116                     // username and host specified
0117                     _user = args[i].left(separatorPosition);
0118                     _host = args[i].mid(separatorPosition + 1);
0119                 } else {
0120                     // just the host specified
0121                     _host = args[i];
0122                 }
0123             } else {
0124                 // host has already been found, this must be part of the
0125                 // command arguments.
0126                 // Note this is not 100% correct.  If any of the above
0127                 // noArgumentOptions or singleArgumentOptions are found, this
0128                 // will not be correct (example ssh server top -i 50)
0129                 // Suggest putting ssh command in quotes
0130                 if (_command.isEmpty()) {
0131                     _command = args[i];
0132                 } else {
0133                     _command = _command + QLatin1Char(' ') + args[i];
0134                 }
0135             }
0136         }
0137     } else {
0138         qWarning() << "Could not read arguments";
0139 
0140         return;
0141     }
0142 }
0143 
0144 QString SSHProcessInfo::userName() const
0145 {
0146     return _user;
0147 }
0148 
0149 QString SSHProcessInfo::host() const
0150 {
0151     return _host;
0152 }
0153 
0154 QString SSHProcessInfo::port() const
0155 {
0156     return _port;
0157 }
0158 
0159 QString SSHProcessInfo::command() const
0160 {
0161     return _command;
0162 }
0163 
0164 QString SSHProcessInfo::format(const QString &input) const
0165 {
0166     QString output(input);
0167 
0168     // search for and replace known markers
0169     output.replace(QLatin1String("%u"), _user);
0170 
0171     // provide 'user@' if user is defined -- this makes nicer
0172     // remote tabs possible: "%U%h %c" => User@Host Command
0173     //                                 => Host Command
0174     // Depending on whether -l was passed to ssh (which is mostly not the
0175     // case due to ~/.ssh/config).
0176     if (_user.isEmpty()) {
0177         output.remove(QLatin1String("%U"));
0178     } else {
0179         output.replace(QLatin1String("%U"), _user + QLatin1Char('@'));
0180     }
0181 
0182 #ifdef Q_OS_WIN
0183     // TODO
0184 #else
0185     // test whether host is an ip address
0186     // in which case 'short host' and 'full host'
0187     // markers in the input string are replaced with
0188     // the full address
0189     struct in_addr address;
0190     const bool isIpAddress = inet_aton(_host.toLocal8Bit().constData(), &address) != 0;
0191     if (isIpAddress) {
0192         output.replace(QLatin1String("%h"), _host);
0193     } else {
0194         output.replace(QLatin1String("%h"), _host.left(_host.indexOf(QLatin1Char('.'))));
0195     }
0196 
0197     QString fullHost = _host;
0198     if (!_port.isEmpty() && _port != QLatin1String("22")) {
0199         fullHost.append(QLatin1Char(':'));
0200         fullHost.append(_port);
0201     }
0202     output.replace(QLatin1String("%H"), fullHost);
0203     output.replace(QLatin1String("%c"), _command);
0204 #endif
0205     return output;
0206 }