Link to home
Start Free TrialLog in
Avatar of rhedrick
rhedrick

asked on

Attempting to run su using pipes, dup2, execl from a c++ app

OK here is what my app is actually doing.  I have a c++ application that basically receives input from a php page.  That part is coded perfectly fine,  I can send info from the page, and receive it from the app, as well as the app send info to the page.

Here is my problem.  I want the user to be able to type in their name and password, this name and password also is the same as the username and password they have on the linux box (RH9).  Originally I was going to try to compare the encrypted password in the shadow file to what the user types in, but I don't see that as possible after doing some research.

Is there an easy way to do this?  Check to see if a password matches that for a username?

My other solution was the following:
NOTE: the application will not be running as root

I could have a c++ app run "su username_entered_from_web_page_here"
then have the c++ app type in the corresponding password I received from the page.
The function below will return true if the login is executed correctly, and false otherwise.
If the user logs in correctly I would also have the ability to execute some more commands.

Assuming I have the username in a string username, and the password in a string password, how can I accomplish this?

Exact code would be GREAT, here is the code I have so far:

bool verifyUser(string user, string pass){
      cout << "Starting verification process\n";

      int filedes1[2], filedes2[2];
      int pid;
 
      /* Make our first pipe. */

      if (pipe(filedes1) == -1){
            perror ("pipe");
            return false;
      }

      /* Make our second pipe. */

      if (pipe(filedes2) == -1){
            perror ("pipe");
            return false;
      }

      /* Fork a child */

      if ((pid = fork()) == 0){
            dup2(filedes1[0], fileno(stdin)); /* Copy the reading end of the pipe. */
            dup2(filedes2[1], fileno(stdout)); /* Copy the writing end of the pipe */

      /* Uncomment this if you want stderr sent too.

      dup2(filedes2[1], fileno(stderr));

      */

      /* If execl() returns at all, there was an error. */

            if (execl("/bin/su","su",user.c_str(),NULL)){
                  perror("execl");
                  return false;
            }
      }
      else if (pid == -1){
            perror("fork");
            return false;
      }
      else{
            int output;
            char c;

            write(filedes1[1], pass.c_str(), strlen(pass.c_str())); /* Write the string */

            int outputLength;
            char buffer[2048];
            outputLength = read(filedes2[0], buffer, 2048);
            string s(buffer,outputLength);

            //cout << s << "\n";
            //cout << "Done reading\n";
            return false;
      }
}

and of course this does not work, please help
Pretty much anyway I've tried it I tend to get this error:
"standard in must be a tty"
Avatar of Mysidia
Mysidia
Flag of United States of America image

Su doesn't read the password from stdin, after it's launched it opens up /dev/tty and grabs the input directly from
the terminal device.  You could try and automate a remote login to the local system (then what su sees as /dev/tty
will be login program's stdin),

but no amount of re-mapping fds 0, 1, or 2  will convince su to allow the password to just be piped in....

In general, the program needs to be running as root to check the password for a local user.
(Programs running with ordinary user privs don't have access to the encrypted passwords)

I'll try to think of some other method of getting SU to do what you want and check back soon
Avatar of rhedrick
rhedrick

ASKER

well i can login as root, that wouldn't be a problem

so you're saying that if I log in as root, I can verify the users password?  I thought I could only change it.

Josh
ASKER CERTIFIED SOLUTION
Avatar of grg99
grg99

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
yes well i don't want to run a pop2, pop3, imap or ftp server.  I need to be able to drop this c++ app on any of the RH9 servers and it will run no matter what.  Some of them have FTP, but that is it.

Josh
You either want to authenticate them using PAM; or if you want to make assumptions
about the authentication method, use the shadow functions

getspnam(user)  to read their entry from the shadow file
then compare it using crypt.

For details about using pam

See the Linux-Pam Application Developer's Guide, section 2
 http://www.krasline.ru/Library/pam-doc/pam_appl-2.html

And the example code: http://www.krasline.ru/Library/pam-doc/pam_appl-6.html

for an example PAM app; it may be sufficient to replace  misc_conv with a conversation
function you would write according to that documentation

You might find these snippets in addition to the example code in the docs
useful for experimenting:

// gcc -o progname progname.c -lpam
// to compile,  requires pam library headers.. may be in a separate
// libpam-devel or libpam0-devel  .. not sure about RH9

// This is code is a conversation function that could be used with the example code in the
// Linux Pam Application Developer's Guide... this isn't standalone code, only a conversation
// function

#include <stdlib.h>
                                                                               
int get_line(char *buf, int size)
{
        if (!fgets(buf, size, stdin))
                return 0;
                                                                               
        if (buf[0] == '\n' || buf[0] == '\0')
                return 0;
        buf[strlen(buf) - 1] = '\0';
        return 1;
}

int my_convo(int num_msg, const struct pam_message**msg,
             struct pam_response** resp, void *appdata_ptr)
{
        struct pam_response *myresp = calloc(num_msg, sizeof(struct pam_response));
        char input[256];
        int i;
                                                                               
        for(i = 0; i < num_msg; i++) {
                if (msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
                        printf("%s", msg[i]->msg);
                        if (get_line(input, 256)) {
                                myresp[i].resp = strdup(input);
                        }
                } else if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
                        /* Warning: this basically ignores the echo off prompting and leaves echo on */
                        printf("%s", msg[i]->msg);
                        if (get_line(input, 256)) {
                                myresp[i].resp = strdup(input);
                        }
                } else if (msg[i]->msg_style == PAM_ERROR_MSG) {
                        printf("%s\n", msg[i]->msg);
                } else if (msg[i]->msg_style == PAM_TEXT_INFO) {
                        printf("%s\n", msg[i]->msg);
                }
        }
        *resp = myresp;
        return PAM_SUCCESS;
} // Warning.. this leaves myresp[] allocated and various myresp[ items ]->resp allocated
OK I tried the example on the second link you posted, and I think this is exactly what I want... but I'm getting this error when I'm compiling:

[root@localhost root]# g++ test2.cpp -o test2
In file included from /usr/include/security/pam_misc.h:7,
                 from test2.cpp:12:
/usr/include/security/pam_client.h:55: non-local function `int
   pamc_converse(pamc_handle_s*, <anonymous struct>**)' uses anonymous type


Sorry i'm not 100% new to compiling on linux, but I'm not an expert, what is an anonymous type?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I am still trying to get this to work, I do attend on awarding the points when I get it working correctly =P

Actual work seems to get in the way when I start working on this