I just wanted to share a use for authpipe that others might find useful.
I wanted to use a virtual shared folder setup in Courier-IMAP so that I
could use IMAP folder ACLs. However, I needed to do username/password
checks against a Windows Active Directory controller. I wanted to set
up Courier-IMAP in such a way that I didn't touch the LDAP schema of the
AD controller, so I decided to use userdb to store the account
information. However, because I wanted to check usernames/passwords
against the AD controller, I couldn't store the passwords in the userdb
files.
I decided that rather than write my own module, I would stack a "filter"
module of sorts on top of the others. This is possible only because of
the way that authdaemond uses the modules -- if for any request a module
returns "FAIL", then authdaemond makes the same request from the next
module in its list. I wrote a shell script, suitable for use with the
authpipe module, and a tiny C program that checks username/password
pairs via PAM (and I used pam_winbind, though you could use any PAM
module). The shell script "authProg" speaks the "authpipe" protocol
outlined in:
http://www.courier-mta.org/authlib/README_authlib.html
The script takes an "AUTH" request, checks the username/password using
the "chkpw_pam" program, then makes a separate connection to authdaemond
to make a "PRE" request to return the account data. Since authProg
simply sends "FAIL" for a "PRE" request, the request falls through to
the authuserdb module.
This simple idea works and required almost no programming, but I'd like
to know if there's a nicer way to do this.
Cheers,
-- Johnny Lam <[EMAIL PROTECTED]>
#!/bin/sh
auth()
{
chkpwprog=/etc/pkg/authlib/chkpw_pam
hoseprog=/usr/pkg/bin/hose
socketfile=/var/authdaemon/socket
[ -x $chkpwprog ] || { fail; return; }
[ -x $hoseprog ] || { fail; return; }
[ -S $socketfile ] || { fail; return; }
# Read the AUTH data from stdin.
read service junk
read authtype junk; [ "$authtype" = "login" ] || { fail; return; }
read username junk
read password
# Authenticate the user/password combination.
CHKPW_PASSWD="$password"; export CHKPW_PASSWD
$chkpwprog $service $username || { fail; return; }
# Make a PRE request to authdaemond so that the next module's
# results will return. Filter out PASSWD* lines since they
# won't apply, given that the correct passwords are stored in
# the service checked by $chkpwprog.
#
echo "PRE . $service $username" |
$hoseprog localhost $socketfile --unix --slave |
while read line; do
case $line in
PASSWD=*|PASSWD2=*) ;;
.) echo "."; break ;;
*) echo "$line" ;;
esac
done
}
# Set the other actions to fail so that authdaemond will fall through and
# try the next module on the authmodulelist.
#
pre() { fail; }
passwd() { fail; }
enumerate() { echo "."; }
fail() { echo "FAIL"; }
# Read the first line of standard input to figure out which action should
# be taken.
#
read stdin
echo " Read: $stdin"
case $stdin in
"AUTH "*) auth ;;
"PRE . "*) pre ;;
"PASSWD "*) passwd ;;
"ENUMERATE") enumerate ;;
*) fail ;;
esac
exit 0
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <security/pam_appl.h>
static int
pam_nullconv(int n,
const struct pam_message **msg,
struct pam_response **resp,
void *data)
{
return PAM_CONV_ERR;
}
static struct pam_conv pamc = { pam_nullconv, NULL };
int
main(int argc, char *argv[])
{
pam_handle_t *pamh = NULL;
int pam_err = PAM_ABORT;
const char *service;
const char *user;
const char *pass;
if (argc != 3)
goto cleanup;
service = argv[1];
user = argv[2];
/*
* Grab the password for the user from the CHKPW_PASSWD environment
* variable.
*/
if ((pass = getenv("CHKPW_PASSWD")) == NULL)
goto cleanup;
if ((pam_err = pam_start(service, user, &pamc, &pamh)) != PAM_SUCCESS)
goto cleanup;
if ((pam_err = pam_set_item(pamh, PAM_AUTHTOK, pass)) != PAM_SUCCESS)
goto cleanup;
if ((pam_err = pam_authenticate(pamh, PAM_SILENT)) != PAM_SUCCESS)
goto cleanup;
cleanup:
if (pamh != NULL) {
pam_end(pamh, pam_err);
}
/* Return 0 for a success, and 1 for everything else. */
return (pam_err == PAM_SUCCESS ? 0 : 1);
}