hIJEFi
hIJEFi

Reputation: 11

Entering sudo password through c

I asked this question before but no-one gave a straight answer. I wanted to know how I can enter the sudo password through c code. I'm trying to write a script to be able to execute sudo bash and enter the password required. Also I know the risks of hardcoding passwords but I don't mind in this instance.

Upvotes: 0

Views: 1717

Answers (1)

Nominal Animal
Nominal Animal

Reputation: 39406

No. Doing it that way is an antipattern.

There are several alternatives to choose from, depending on the situation:

  • Use gksudo (if DISPLAY environment variable is set) for a graphical prompt.
     

  • Install the scripts to be executed in /usr/share/yourapp/scripts/ or /usr/lib/yourapp/scripts/, and the proper sudo configuration that allows running them with sudo without supplying a password in /etc/sudoers.d/yourapp (or /etc/sudoers in systems without /etc/sudoers.d/)
     

  • At the beginning of your program, check if geteuid() == 0. If not, re-execute self using gksudo/sudo, to obtain root privileges.

    For normal operations, your program should use only the privileges of the real user who executed the program. To be able to raise the privileges later, the root privileges are "saved". So, initially, your program will drop the privileges using e.g.

        uid_t  uid = getuid();
        gid_t  gid = getgid();
        if (setresgid(gid, gid, 0) == -1 ||
            setresuid(uid, uid, 0) == -1) {
            /* Failed: no root privileges! */
        }
    

    To re-elevate privileges, you use

        if (setresgid(gid, 0, 0) == -1 ||
            setresuid(uid, 0, 0) == -1) {
            /* Failed: no root privileges! */ 
        }
    

    which changes only the effective identity to root (as setuid binaries do), or

        if (setresgid(0, 0, 0) == -1 ||
            setresuid(0, 0, 0) == -1) {
            /* Failed: no root privileges! */ 
        }
    

    which changes both real and effective identity to root.

    Often, the privileges are elevated for only forking a privileged child slave, after which the main program drops the privileges completely using

        if (setresgid(gid, gid, gid) == -1 ||
            setresuid(uid, uid, uid) == -1) {
            /* Failed. */
        }
    

    keeping just a socket pair or pipes between the parent and the child; the child can then fork and execute new processes. (If an Unix domain socket is used, the parent can even send new descriptors to be used for the new processes' standard streams via ancillary messages.)
     

  • Use filesystem capabilities to give your program the capabilities it needs, without elevating all its privileges.

    For example, sudo setcap CAP_NET_BIND_SERVICE=pe /usr/bin/yourapp gives /usr/bin/yourapp the CAP_NET_BIND_SERVICE capability (permitted and effective; not inherited), which allows your program to bind to any unused TCP/IP and UDP/IP ports, including 1-1024. See man 7 capabilities for detailed descriptions of the capabilities.
     

  • Use a trusted helper binary (program) to act as sudo for you. If you install this at e.g. /usr/lib/yourapp/execute, you can add the sudo configuration necessary to allow executing it without supplying a password. Alternatively, you can make it setuid root, or give it the necessary capabilities via filesystem capabilities.

    To avoid other programs from exploiting this helper, you must ensure it is only executed by your program. One way to ensure that is to have your program create an Unix domain socket pair, leaving one end open in your program, and the other end for the helper in e.g. descriptor 3. Before doing anything, the helper checks that there is nothing to receive yet (to avoid "pipe stuffing" attacks), and writes a single byte to the parent. The parent responds with a single byte, but with its credentials in an ancillary message. The credentials contain the process ID of the parent. (Do not simply use getppid(), because that allows certain attacks; this socket approach verifies the parent is still alive when we do the check.) Finally, use readlink() to read the /proc/PID/exe pseudo-symlink, where PID is the parent process ID from the credentials ancillary message. At this point, the helper should send a byte, and receive a byte with the credentials again as an ancillary message, to ensure the parent process is still the same.

    The verification process is complex, but necessary, to avoid making it easy to exploit root privileges by misusing the helper. For another approach to do exactly this, look into Apache suEXEC, the helper used by Apache to execute CGI programs with specific user privileges.
     


Let's say you are totally uninterested in doing things in a sensible way, and insist on using passwords. Fine; all I ask is that you don't publish such code, or at least warn your users that it is completely unsafe.

This is not just a crude hack: it is a suicidal one, similar to putting the password to your web site in your e-mail signature, because you only mail to friends who should have admin access to your site in the first place. So, footgun, with a hair trigger, no safety, and armed with buckshot coated in tetrodotoxin. With a nice label with big, child-readable letters saying "Please play with me! I'm safe!", stored in the kids bedroom.

The simplest thing to do is to execute sudo with the -S flag, which causes it to read the password from the standard input. For example, example.c:

#define  _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

int main(void)
{
    FILE  *cmd;
    int    status;

    cmd = popen("/usr/bin/sudo -S id -un 2>/dev/null", "w");
    if (!cmd) {
        fprintf(stderr, "Cannot run sudo: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    fprintf(cmd, "Password\n");
    fflush(cmd);

    status = pclose(cmd);
    if (WIFEXITED(status)) {
        if (WEXITSTATUS(status) == EXIT_SUCCESS)
            fprintf(stderr, "No errors.\n");
        else
            fprintf(stderr, "Command failed with exit status %d.\n", WEXITSTATUS(status));
    } else
    if (WIFSIGNALED(status))
        fprintf(stderr, "Command killed by signal %d.\n", WTERMSIG(status));
    else
        fprintf(stderr, "Command failed.\n");

    return EXIT_SUCCESS;
}

The command pipe is opened in write mode, so that we can write the password to it. The 2>/dev/null redirection hides the password prompt.

If the password is correct, the above will output what id -un outputs when run as root (i.e.: root) to standard output, and No errors. to standard error.

If the password is incorrect, sudo will retry a couple of times (so nothing will happen for a few seconds), then the program will report Command failed with exit status 1. to standard error, because that's what sudo does when the password is incorrect.

Upvotes: 2

Related Questions