Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Linux: Run external process with requesting password



  • Hi,
    I'm working on a Qt application on Linux OS. What I need is to exec some system commands and track the progress between each one. I know QProcess class is designed for these purposes, but I need some commands to be execute with root permissions (e.g. sudo apt-get update/install). I've found pkexec tool, but I wanto to avoid to ask permissions for each command.
    Below an example of what I need:

    QProgressDialog *dialog= new QProgressDialog();
    QProcess *proc = QProcess();
    [...]
    
    proc .execute("sudo apt-get update"); 
    dialog.setValue(20);
    
    proc .execute("sudo apt-get install MY_PACKAGE");
    dialog.setValue(50);
    
    proc .execute("sudo RUN_MY_SCRIPT");
    dialog.setValue(100);
    

    I've tried also another solution. I've created a simple script with all the commands, running this with pkexec tool. This works fine, but I cannot track progress from between each command.

    QProcess::execute("pkexec MY_SCRIPT_WITH_ALL_COMMANDS"); 
    

  • Moderators

    You can print progress inside your script echo 20, echo 50 etc. And then use QProcess::readyReadStandardOutput to react to it in your GUI.



  • @sierdzio
    How did I not think about it? Thank you!!!!



  • @robcont_
    @sierdzio's suggestion of putting echos into script may work, but it's also possible that depending on line/pipe buffering your calling program won't get to see those lines of output until termination of script, which is not what you want. You'll have to test and see?

    BTW, you might be advised to put a set -e at the head of your script, to deal with any errors which might happen. If you wish to report to your user what the script is doing, you might also add set -x.

    Otherwise, are you happy to go down the pkexec route with all commands put into one script and executed at the same time, or did you actively want to run each command separately, or what?



  • @JonB
    Yes I've tested @sierdzio 's suggestion and it works. I marked the topic as solved for this reason.

    However, the best would be to run each command separately, as is I put them one by one in a shell.
    The problem I do not know how to get around is that each command must be run with sudo.
    In a shell the password is asked only for the first command, with the others "inheriting" these permissions.
    I would like to have the same behavior in my app. A dialog asking for password is prompted only for the the first command. The others will be executed as well as I already gave authorization.



  • @robcont_
    Then perhaps you should not yet mark your question as solved!

    You have several possibilities.

    The first is I don't know why you have gone down the pkexec route at all? Are you planning to configure it for your script with its permissions-XML file or what? If not I don't think I understand what it is adding to the situation.

    The next is to utilise the fact that sudo is prepared to "cache" the password, once initially supplied, for a period of time. Issuing further sudo commands during that period should not re-prompt for password.

    Security policies may support credential caching to allow the user to run
    sudo again for a period of time without requiring authentication. The
    sudoers policy caches credentials for 15 minutes, unless overridden in
    sudoers(5). By running sudo with the -v option, a user can update the
    cached credentials without running a command.

    So I don't know whether you tested that out on your target system for your multiple sudo commands, or just assumed they would each re-prompt.

    The next is that if you're going to put all your commands into one script file for pkexec I don't know why you introduced that instead of just doing same script file (without the sudos) and executing that script itself via sudo to achieve the same principle.

    I think you're saying you do actively wish to prompt for password first time, rather than just granting automatic privilege (via pkexec), which is indeed a good idea. Have you tried out sudo -A, --askpass for that?

    So there are a few things to look at!



  • @JonB said in Linux: Run external process with requesting password:

    The first is I don't know why you have gone down the pkexec route at all? Are you planning to configure it for your script with its permissions-XML file or what? If not I don't think I understand what it is adding to the situation.

    I'm using pkexec show a dialog asking for password. I followed this this post. I have a clean installation of Kubuntu without gksudo, kdesudo, kdesu and I need to run my app without these tools installed.

    I think you're saying you do actively wish to prompt for password first time, rather than just granting automatic privilege (via pkexec), which is indeed a good idea. Have you tried out sudo -A, --askpass for that?

    This is exactely what I want! Sorry I'm new on Linux and I didn't knew the sudo -A, --askpass option. How can I configure it properly? Following the same link above I'm not succeeding.



  • @robcont_
    Well, that link shows several possible prompters, the first of which is plain sudo and only the last of which is pkexec, and as it notes that may require configuring. You proably/perhaps do not want to have to do any system configuring to support your solution.

    Unfortunately I cannot actually test anything on my Ubuntu because I'm already configured (via /etc/sudoers) not to require a password, so my sudo won't prompt !

    But from reading try:

    env SUDO_ASKPASS=/usr/bin/ssh-askpass sudo ls -l
    env SUDO_ASKPASS="/usr/bin/zenity --password" sudo ls -l
    

    However, it does look like ssh-askpass needs installing. On my Ubuntu 18.04 zenity --password ... does work out-of-the-box.

    Now that I realise pkexec works by putting up the standard desktop password dialog for you, perhaps that is a perfectly good reason for using that after all. It does for me.



  • @JonB
    Thanks for your help. After some readings I've found a working solution.

    QProcess askPermission;
    askPermission.start("kdialog --password \"Please enter your password to continue\"");
    askPermission.waitForReadyRead(-1);
    QByteArray sudoPwd = askPermission.readAll().simplified();
    askPermission.close();
    
    system("echo " + sudoPwd + " | sudo -S apt-get update");
    system("echo " + sudoPwd + " | sudo -S apt-get install gawk");
    


  • @robcont_
    I'm happy for you if you're happy with this.

    I assume you know it's a nasty security risk, and will go completely wrong depending on what characters are in the password, but you're happy with that? If you do at least want it to work regardless of what letters might be in the password, just let us know.



  • @JonB said in Linux: Run external process with requesting password:

    @robcont_
    I'm happy for you if you're happy with this.

    I assume you know it's a nasty security risk, and will go completely wrong depending on what characters are in the password, but you're happy with that? If you do at least want it to work regardless of what letters might be in the password, just let us know.

    Yeah, I know there could be security risks. Now I verify first the correctness of the password with a "dummy" ls command, and only if it is successful I continue with the others. Otherwise, I request the password again.

    bool askForPermissions()
    {
        QProcess shell;
        shell.start("kdialog --title \"Superuser\" --password \"Please enter your password to continue\"");
        shell.waitForReadyRead(-1);
        QByteArray sudoPwd = shell.readAllStandardOutput().simplified();
        shell.close();
    
        int ret = system("echo " + sudoPwd + " | sudo -S ls");
        if (ret != 0) {
            QMessageBox msgBox;
            msgBox.setWindowTitle(tr("Error"));
            msgBox.setIcon(QMessageBox::Critical);
            msgBox.setText(tr("\nWrong password"));
            msgBox.setStandardButtons(QMessageBox::Ok);
            msgBox.setButtonText(QMessageBox::Ok, "Retry");
            msgBox.exec();
        }
        return ret == 0;
    }
    
    
    void myFunct()
    {
        while (!askForPermissions()) {}
    
        // Permissions granted, do work ...
    }
    


  • @robcont_
    As I said, if you are happy with what you are doing that's OK. But if you don't mind my saying, there are so many faults/potential problems with your approach, I wouldn't do things your way. If it's not too much trouble, if I were you I would reconsider.

    The whole idea of getting the password from the user yourself and then passing it to sudo (or whatever) is dodgy. If it's possible to adapt to something like

    env SUDO_ASKPASS="kdialog --password" sudo ls
    

    where that means that sudo itself is getting the password but using your dialog it would be better. However, I agree this might cause re-prompt issues, I don't know.

    Assuming you wish to stick with getting the password yourself and passing to sudo, I still think you should consider changing your code:

    • system(...): Just don't do this, don't use system() here. You've already started using QProcess, at least use that instead.

    • echo: Just don't do this. You don't know how echo works, and you don't know what it does with a whole bunch of characters, which might be in the password and will just make it go wrong (if you're lucky, or do something nasty if I pick a nasty password).

    • |: Don't do this either :) Using the pipe is going to force your command to be executed via a shell, and you don't want that.

    • sudo -S ls: Not great. At least try to find an option which doesn't try to run some command, maybe sudo -S -l or sudo -S true will do what you want better/safer, you'll have to try.

    You don't need echo, pipe or shell at all. All you want to do here is run sudo -S as the one & only process and directly send the password [followed by newline, sudo -S requires that] from your Qt program to its stdin. You've already started using QProcess, it will do this for you neatly. I can't remember the exact calls, but look at the doc page and it tells you (I think there's even an example) how you can write from your calling app to the sub-process's stdin.

    The above will take you a bit of reading & programming compared to the quick & dirty way you have now. I realise it's up to you whether that effort is worth it or not --- it is if you might work with QProcess again in the future, or you just want to learn --- it's just that using echo ... | to do this is so offensive I can't help but protest! :)


Log in to reply