How do I write a test for system login?

23,912

Solution 1

Using PAM is the best solution. You can write small C code, or install python-pam package and use a python script which comes with the python-pam package. See /usr/share/doc/python-pam/examples/pamtest.py

Solution 2

The right approach to testing whether a user can log in is to actually log in as that user.

So what I recommend is to make the CGI script use expect to run su, pass a password and run the command that must be executed. Here's a draft of an expect script that does just this (warning: absolutely untested, and I'm not fluent in expect). Substitute in the user name, password and command (where I wrote bob, swordfish and somecommand); be sure to quote correctly.

spawn "/bin/su" "bob"
expect "Password:"
send "swordfish\r"
expect "^\\$"
send "somecommand"
expect -re "^\\$"
send "exit\r"
expect eof

If you really don't want to execute the command through a layer of su (for example because what you do has to be performed by the CGI process itself), then use expect to run the command true and check that the return status is 0.

Another approach would be to use PAM directly in your application, through Python's PAM binding.

Solution 3

There is a 'C', 'Python' PAM solution quoted here, let me put the perl one too :-)

Source: http://search.cpan.org/~nikip/Authen-PAM-0.16/PAM/FAQ.pod#1._Can_I_authenticate_a_user_non_interactively?

#!/usr/bin/perl

  use Authen::PAM;
  use POSIX qw(ttyname);

  $service = "login";
  $username = "foo";
  $password = "bar";
  $tty_name = ttyname(fileno(STDIN));

  sub my_conv_func {
    my @res;
    while ( @_ ) {
        my $code = shift;
        my $msg = shift;
        my $ans = "";

        $ans = $username if ($code == PAM_PROMPT_ECHO_ON() );
        $ans = $password if ($code == PAM_PROMPT_ECHO_OFF() );

        push @res, (PAM_SUCCESS(),$ans);
    }
    push @res, PAM_SUCCESS();
    return @res;
  }

  ref($pamh = new Authen::PAM($service, $username, \&my_conv_func)) ||
         die "Error code $pamh during PAM init!";

  $res = $pamh->pam_set_item(PAM_TTY(), $tty_name);
  $res = $pamh->pam_authenticate;
  print $pamh->pam_strerror($res),"\n" unless $res == PAM_SUCCESS();

Solution 4

To more specifically answer: "Is it possible to create a bash script that will test a given username and password combination against the registred user on the host?"

Yes.

#!/bin/bash
uid=`id -u`

if [ $uid -ne 0 ]; then 
    echo "You must be root to run this"
    exit 1
fi

if [ $# -lt 1 ]; then
    echo "You must supply a username to check - ($# supplied)"
    exit 1
fi

username=$1
salt=`grep $username /etc/shadow | awk -F: ' {print substr($2,4,8)}'`

if [ "$salt" != "" ]; then

        newpass=`openssl passwd -1 -salt $salt`
        grep $username  /etc/shadow | grep -q  $newpass && echo "Success" || echo "Failure"

fi

Solution 5

If you have root access, and are using md5 passwords, and you just need to compare passwords, then you can use the perl Crypt::PasswdMD5 module. Take the MD5 Hash from /etc/shadow, strip the $1$, and then split on the remaining $. Field 1 = Salt, Field 2 = crypted text. Then hash the text input into your CGI, compare that to the crypted text, and Bob's your uncle.

#!/usr/bin/env perl

use Crypt::PasswdMD5;

my $user                = $ARGV[0];
my $plain               = $ARGV[1];
my $check               = qx{ grep $user /etc/shadow | cut -d: -f2 };
chomp($check);
my($salt,$md5txt)       = $check =~ m/\$1\$([^\$].+)\$(.*)$/;
my $pass                = unix_md5_crypt($plain, $salt);

if ( "$check" eq "$pass" ) {
        print "OK","\n";
} else {
        print "ERR","\n";
}
Share:
23,912

Related videos on Youtube

Tim
Author by

Tim

My name is Jakub T. Jankiewicz, I'm coding mostly in JavaScript. I love Lisp Macros, jQuery library, ReactJS, CSS3, HTML5, SVG, GNU/Linux, GNU Emacs and Inkscape. Working with JavaScript and R for Roche/Genentech via Astek Poland. my english blog - In Code We Trust my polish blog - Głównie JavaScript (ang. Mostly JavaScript) Usefull Links Other links Yet another links Few of my JavaScript Open Source projects: jQuery Terminal: JavaScript library for Web based Terminal Emulator LIPS - Powerful Scheme based lisp interpreter written in JavaScript sysend.js: Library for sending messages between Windows and Tabs Gaiman Programming Language and Text based Game engine GIT Web Terminal Posts: EchoJS News, EchoJS News (2), HackerNews

Updated on September 18, 2022

Comments

  • Tim
    Tim almost 2 years

    I've written a Python CGI script that invokes bash commands, and it needs to test for a successful login on the host.

    How do I write a test for that?

    For example, could I create a bash script that tests a given username and password combination against the registered user on the host?

    • Admin
      Admin over 12 years
      Perhaps you could look at the code behind the login program.
    • Admin
      Admin over 12 years
      Not related to the question, but I hope you are encrypting the traffic to your web server so that user logins can't be sniffed off the wire.
  • Nikhil Mulley
    Nikhil Mulley over 12 years
    Did this work for you? I am seeing that you are checking an existing shadow password against the shadow password but where is the hashing involved here?
  • Nikhil Mulley
    Nikhil Mulley over 12 years
    plain and simple :-). This will work as long as /etc/passwd uses md5 hashing.Incase the authentication (nsswitch) is different for the system, then pam modules are best to use.
  • Tim
    Tim over 12 years
    I tested and it don't work
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 12 years
    While this is true, it does not appear to be relevant to the question, which is about an application accessed through a web browser.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 12 years
    Bad idea! You're assuming that your program is running as root, or at least as the shadow group, which is very strongly not recommended for a CGI: you'd need another layer of privilege escalation. And you're assuming a particular password hashing algorithm (one supported by openssl) and password storage location (/etc/shadow as opposed to e.g. NIS or LDAP) which may or may not be the one actually used for that particular user.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 12 years
    Bad idea! You're assuming that your program is running as root, or at least as the shadow group, which is very strongly not recommended for a CGI: you'd need another layer of privilege escalation. And you're assuming a particular password hashing algorithm (MD5 and not bcrypt or other recommended algorithm) and password storage location (/etc/shadow as opposed to e.g. NIS or LDAP) which may or may not be the one actually used for that particular user.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 12 years
    Yeah, ok, but the question was about a CGI script written in Python.
  • Tim
    Tim over 12 years
    Yes I know, but thought that this is not posible with without root. All other solutions use root as well, exept yours.
  • Tim
    Tim over 12 years
    It is awesome, The only solution that without root access.
  • Tim
    Tim over 12 years
    this works su -c true bob && echo success it shame that su don't accept password as argument
  • Tim
    Tim over 12 years
    This CGI script is JSON-RPC service called via Ajax and I need method login that return a token, token should be returned if login succeed. So basicaly every user need to be able to execute that script.
  • Tim
    Tim over 12 years
    I tested su from CGI script and it need a terminal for it to work.
  • Tim
    Tim over 12 years
    I try PAM but it didn't work. But I try again this example and it work.
  • Haddad
    Haddad over 10 years
    In OpenSUSE 12.3 (python-pam 0.5.0-84.1.1) and 13.1 (0.5.0-87.1.2), the full path to pamtest.py is /usr/share/doc/packages/python-pam/examples/pamtest.py The script pamtest.py can be used to test credentials on systems using PAM for authentication, is included in the python-pam package (which requires python), and in some distributions the full path is /usr/share/doc/python-pam/examples/pamtest.py.
  • Volker Siegel
    Volker Siegel almost 10 years
    Could you add some context information to make it "feel" more like an answer?
  • ceving
    ceving over 9 years
    @jcubic It is a really stupid idea to put a password in a command line argument, because command line arguments are public on a Unix system. Removing the password would provide the same level of security as putting it in the command line. And it would be much easier to check: /bin/true would be enough.