 INSTALL                |    6 ++
 configure.in           |   33 +++++++++++
 doc/PAM.txt            |   40 +++++++++++++
 src/ngircd/Makefile.am |    6 +-
 src/ngircd/client.c    |   44 ++++++++++++++
 src/ngircd/client.h    |    6 ++
 src/ngircd/conf.c      |    8 ++
 src/ngircd/irc-login.c |   15 ++++-
 src/ngircd/ngircd.c    |    5 +
 src/ngircd/pam.c       |  145 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/ngircd/pam.h       |   25 ++++++++
 11 files changed, 327 insertions(+), 6 deletions(-)

Index: INSTALL
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/INSTALL,v
retrieving revision 1.26
diff -u -p -r1.26 INSTALL
--- INSTALL	8 Apr 2007 11:39:08 -0000	1.26
+++ INSTALL	9 Apr 2008 08:50:22 -0000
@@ -184,6 +184,12 @@ standard locations.
   to the daemon, for example by using "/etc/hosts.{allow|deny}".
   The "libwrap" is required for this option.
 
+* PAM:
+  --with-pam[=<path>]
+
+  Enable support for PAM, the Pluggable Authentication Modules library.
+  See doc/PAM.txt for details.
+
 
 IV. Useful make-targets
 ~~~~~~~~~~~~~~~~~~~~~~~
Index: configure.in
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/configure.in,v
retrieving revision 1.126
diff -u -p -r1.126 configure.in
--- configure.in	26 Feb 2008 22:04:15 -0000	1.126
+++ configure.in	9 Apr 2008 08:50:22 -0000
@@ -33,6 +33,7 @@ AH_TEMPLATE([IRCPLUS], [Define if IRC+ p
 AH_TEMPLATE([WANT_IPV6], [Define if IPV6 protocol should be enabled])
 AH_TEMPLATE([ZEROCONF], [Define if support for Zeroconf should be included])
 AH_TEMPLATE([IDENTAUTH], [Define if the server should do IDENT requests])
+AH_TEMPLATE([PAM], [Define if PAM should be used])
 
 AH_TEMPLATE([TARGET_OS], [Target operating system name])
 AH_TEMPLATE([TARGET_VENDOR], [Target system vendor])
@@ -416,6 +417,33 @@ if test "$x_identauth_on" = "yes"; then
 	AC_CHECK_HEADERS(ident.h,,AC_MSG_ERROR([required C header missing!]))
 fi
 
+# compile in PAM support?
+
+x_pam_on=no
+AC_ARG_WITH(pam,
+	[  --with-pam              enable user authentication using PAM],
+	[	if test "$withval" != "no"; then
+			if test "$withval" != "yes"; then
+				CFLAGS="-I$withval/include $CFLAGS"
+				CPPFLAGS="-I$withval/include $CPPFLAGS"
+				LDFLAGS="-L$withval/lib $LDFLAGS"
+			fi
+			AC_CHECK_LIB(pam, pam_authenticate)
+			AC_CHECK_FUNCS(pam_authenticate, x_pam_on=yes,
+				AC_MSG_ERROR([Can't enable PAM support!])
+			)
+		fi
+	]
+)
+if test "$x_pam_on" = "yes"; then
+	AC_DEFINE(PAM, 1)
+	AC_CHECK_HEADERS(security/pam_appl.h,pam_ok=yes)
+	if test "$pam_ok" != "yes"; then
+		AC_CHECK_HEADERS(pam/pam_appl.h,pam_ok=yes,
+			AC_MSG_ERROR([required C header missing!]))
+	fi
+fi
+
 # compile in IRC+ protocol support?
 
 x_ircplus_on=yes
@@ -587,8 +615,13 @@ test "$x_identauth_on" = "yes" \
 echo $ECHO_N "        I/O backend: $ECHO_C"
 	echo "\"$x_io_backend\""
 
+echo $ECHO_N "        PAM support: $ECHO_C"
+test "$x_pam_on" = "yes" \
+	&& echo $ECHO_N "yes   $ECHO_C" \
+	|| echo $ECHO_N "no    $ECHO_C"
 echo $ECHO_N "      IPv6 protocol: $ECHO_C"
 echo "$x_ipv6_on"
+
 echo
 
 # -eof-
Index: doc/PAM.txt
===================================================================
RCS file: doc/PAM.txt
diff -N doc/PAM.txt
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ doc/PAM.txt	9 Apr 2008 08:50:22 -0000
@@ -0,0 +1,40 @@
+
+                     ngIRCd - Next Generation IRC Server
+
+                        (c)2001-2006 Alexander Barton,
+                    alex@barton.de, http://www.barton.de/
+
+               ngIRCd is free software and published under the
+                   terms of the GNU General Public License.
+
+                                 -- PAM.txt --
+
+
+ngIRCd can optionally be compiled to use PAM, the Pluggable Authentication
+Modules library passing the command line parameter "--with-pam" to the
+"configure" script. When compiled with PAM support, the ngIRCd will
+authenticate all connecting users using the configured PAM modules.
+
+Please see the PAM documentaton ("man 7 pam"!?) for details and information
+about configuring PAM and its individual modules.
+
+A very simple -- and quite useless ;-) -- example would be:
+
+	/etc/pam.d/ngircd:
+	  auth  required  pam_debug.so
+
+Here the "pam_debug" module will be called each time a client connects to
+the ngIRCd and has sent its PASS, NICK, and USER commands.
+
+
+Please note ONE VERY IMPORTANT THING:
+
+All the PAM modules are executed with the privileges of the user ngIRCd is
+running as. Therefore a lot of PAM modules wouldn't work as expected, because
+they would need root privileges ("pam_unix", for example)!
+Only PAM modules not(!) requiring root privileges (such as "pam_pgsql",
+"pam_mysql", ...) can be used in conjuction with ngIRCd.
+
+
+-- 
+$Id$
Index: src/ngircd/Makefile.am
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/src/ngircd/Makefile.am,v
retrieving revision 1.51
diff -u -p -r1.51 Makefile.am
--- src/ngircd/Makefile.am	26 Feb 2008 22:04:17 -0000	1.51
+++ src/ngircd/Makefile.am	9 Apr 2008 08:50:22 -0000
@@ -1,6 +1,6 @@
 #
 # ngIRCd -- The Next Generation IRC Daemon
-# Copyright (c)2001-2003 by Alexander Barton (alex@barton.de)
+# Copyright (c)2001-2006 Alexander Barton (alex@barton.de)
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -23,7 +23,7 @@ sbin_PROGRAMS = ngircd
 ngircd_SOURCES = ngircd.c array.c channel.c client.c conf.c conn.c conn-func.c \
 	conn-zip.c hash.c io.c irc.c irc-channel.c irc-info.c irc-login.c \
 	irc-mode.c irc-op.c irc-oper.c irc-server.c irc-write.c lists.c log.c \
-	match.c numeric.c parse.c rendezvous.c resolve.c
+	match.c numeric.c pam.c parse.c rendezvous.c resolve.c
 
 ngircd_LDFLAGS = -L../portab -L../tool -L../ipaddr
 
@@ -32,7 +32,7 @@ ngircd_LDADD = -lngportab -lngtool -lngi
 noinst_HEADERS = ngircd.h array.h channel.h client.h conf.h conn.h conn-func.h \
 	conn-zip.h hash.h io.h irc.h irc-channel.h irc-info.h irc-login.h \
 	irc-mode.h irc-op.h irc-oper.h irc-server.h irc-write.h lists.h log.h \
-	match.h numeric.h parse.h rendezvous.h resolve.h \
+	match.h numeric.h pam.h parse.h rendezvous.h resolve.h \
 	defines.h messages.h
 
 clean-local:
Index: src/ngircd/client.c
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/src/ngircd/client.c,v
retrieving revision 1.98
diff -u -p -r1.98 client.c
--- src/ngircd/client.c	4 Apr 2008 19:30:01 -0000	1.98
+++ src/ngircd/client.c	9 Apr 2008 08:50:22 -0000
@@ -375,6 +375,25 @@ Client_SetUser( CLIENT *Client, char *Us
 } /* Client_SetUser */
 
 
+/**
+ * Set "original" user name of a client.
+ * This function saves the "original" user name, the user name specified by
+ * the peer using the USER command, into the CLIENT structure. This user
+ * name may be used for authentication, for example.
+ * @param Client The client.
+ * @param User User name to set.
+ */
+GLOBAL void
+Client_SetOrigUser(CLIENT *Client, char *User) {
+	assert(Client != NULL);
+	assert(User != NULL);
+
+#ifdef PAM & IDENTAUTH
+	strlcpy(Client->orig_user, User, sizeof(Client->orig_user));
+#endif
+} /* Client_SetOrigUser */
+
+
 GLOBAL void
 Client_SetInfo( CLIENT *Client, char *Info )
 {
@@ -632,6 +651,31 @@ Client_User( CLIENT *Client )
 } /* Client_User */
 
 
+#ifdef PAM
+
+/**
+ * Get the "original" user name as supplied by the USER command.
+ * The user name as given by the client is used for authentication instead
+ * of the one detected using IDENT requests.
+ * @param Client The client.
+ * @return Original user name.
+ */
+GLOBAL char *
+Client_OrigUser(CLIENT *Client) {
+#ifndef IDENTAUTH
+	char *user = Client->user;
+	
+	if (user[0] == '~')
+		user++;
+	return user;
+#else
+	return Client->orig_user;
+#endif
+} /* Client_OrigUser */
+
+#endif
+
+
 GLOBAL char *
 Client_Hostname( CLIENT *Client )
 {
Index: src/ngircd/client.h
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/src/ngircd/client.h,v
retrieving revision 1.46
diff -u -p -r1.46 client.h
--- src/ngircd/client.h	23 Jan 2007 16:07:19 -0000	1.46
+++ src/ngircd/client.h	9 Apr 2008 08:50:22 -0000
@@ -46,6 +46,9 @@ typedef struct _CLIENT
 	char pwd[CLIENT_PASS_LEN];	/* password received of the client */
 	char host[CLIENT_HOST_LEN];	/* hostname of the client */
 	char user[CLIENT_USER_LEN];	/* user name ("login") */
+#ifdef PAM & IDENTAUTH
+	char orig_user[CLIENT_USER_LEN];/* user name supplied to USER command */
+#endif
 	char info[CLIENT_INFO_LEN];	/* long user name (user) / info text (server) */
 	char modes[CLIENT_MODE_LEN];	/* client modes */
 	int hops, token, mytoken;	/* "hops" and "Token" (see SERVER command) */
@@ -95,6 +98,9 @@ GLOBAL char *Client_ID PARAMS(( CLIENT *
 GLOBAL char *Client_Mask PARAMS(( CLIENT *Client ));
 GLOBAL char *Client_Info PARAMS(( CLIENT *Client ));
 GLOBAL char *Client_User PARAMS(( CLIENT *Client ));
+#ifdef PAM
+GLOBAL char *Client_OrigUser PARAMS(( CLIENT *Client ));
+#endif
 GLOBAL char *Client_Hostname PARAMS(( CLIENT *Client ));
 GLOBAL char *Client_Password PARAMS(( CLIENT *Client ));
 GLOBAL char *Client_Modes PARAMS(( CLIENT *Client ));
Index: src/ngircd/conf.c
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/src/ngircd/conf.c,v
retrieving revision 1.105
diff -u -p -r1.105 conf.c
--- src/ngircd/conf.c	18 Mar 2008 20:12:47 -0000	1.105
+++ src/ngircd/conf.c	9 Apr 2008 08:50:22 -0000
@@ -179,7 +179,9 @@ Conf_Test( void )
 	puts( "[GLOBAL]" );
 	printf( "  Name = %s\n", Conf_ServerName );
 	printf( "  Info = %s\n", Conf_ServerInfo );
+#ifndef PAM
 	printf( "  Password = %s\n", Conf_ServerPwd );
+#endif
 	printf( "  AdminInfo1 = %s\n", Conf_ServerAdmin1 );
 	printf( "  AdminInfo2 = %s\n", Conf_ServerAdmin2 );
 	printf( "  AdminEMail = %s\n", Conf_ServerAdminMail );
@@ -1138,6 +1140,12 @@ Validate_Config(bool Configtest, bool Re
 			     "No administrative information configured but required by RFC!");
 	}
 
+#ifdef PAM
+	if (Conf_ServerPwd[0])
+		Config_Error(LOG_ERR,
+		         "This server uses PAM, \"Password\" will be ignored!");
+#endif
+
 #ifdef DEBUG
 	servers = servers_once = 0;
 	for (i = 0; i < MAX_SERVERS; i++) {
Index: src/ngircd/irc-login.c
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/src/ngircd/irc-login.c,v
retrieving revision 1.55
diff -u -p -r1.55 irc-login.c
--- src/ngircd/irc-login.c	5 Feb 2008 11:46:55 -0000	1.55
+++ src/ngircd/irc-login.c	9 Apr 2008 08:50:22 -0000
@@ -31,6 +31,7 @@ static char UNUSED id[] = "$Id: irc-logi
 #include "channel.h"
 #include "log.h"
 #include "messages.h"
+#include "pam.h"
 #include "parse.h"
 #include "irc.h"
 #include "irc-info.h"
@@ -377,6 +378,7 @@ IRC_USER( CLIENT *Client, REQUEST *Req )
 #else
 		Client_SetUser( Client, Req->argv[0], false );
 #endif
+		Client_SetOrigUser(Client, Req->argv[0]);
 
 		/* "Real name" or user info text: Don't set it to the empty string, the original ircd
 		 * can't deal with such "real names" (e. g. "USER user * * :") ... */
@@ -582,11 +584,18 @@ Hello_User( CLIENT *Client )
 	assert( Client != NULL );
 
 	/* Check password ... */
-	if( strcmp( Client_Password( Client ), Conf_ServerPwd ) != 0 )
+#ifdef PAM
+	if (! PAM_Authenticate(Client))
+#else
+	if (strcmp(Client_Password(Client), Conf_ServerPwd) != 0)
+#endif
 	{
 		/* Bad password! */
-		Log( LOG_ERR, "User \"%s\" rejected (connection %d): Bad password!", Client_Mask( Client ), Client_Conn( Client ));
-		Conn_Close( Client_Conn( Client ), NULL, "Bad password", true);
+		Log(LOG_ERR,
+		    "User \"%s\" rejected (connection %d): Access denied!",
+		    Client_Mask(Client), Client_Conn(Client));
+		Conn_Close(Client_Conn(Client), NULL,
+		    "Access denied! Bad password?", true);
 		return DISCONNECTED;
 	}
 
Index: src/ngircd/ngircd.c
===================================================================
RCS file: /srv/cvs/ngircd/ngircd/src/ngircd/ngircd.c,v
retrieving revision 1.119
diff -u -p -r1.119 ngircd.c
--- src/ngircd/ngircd.c	18 Mar 2008 20:12:47 -0000	1.119
+++ src/ngircd/ngircd.c	9 Apr 2008 08:50:22 -0000
@@ -373,6 +373,11 @@ Fill_Version( void )
 
 	strlcat( NGIRCd_VersionAddition, "IDENT", sizeof NGIRCd_VersionAddition );
 #endif
+#ifdef PAM
+	if (NGIRCd_VersionAddition[0])
+		strlcat(NGIRCd_VersionAddition, "+", sizeof NGIRCd_VersionAddition);
+	strlcat(NGIRCd_VersionAddition, "PAM", sizeof NGIRCd_VersionAddition);
+#endif
 #ifdef DEBUG
 	if( NGIRCd_VersionAddition[0] )
 		strlcat( NGIRCd_VersionAddition, "+", sizeof NGIRCd_VersionAddition );
Index: src/ngircd/pam.c
===================================================================
RCS file: src/ngircd/pam.c
diff -N src/ngircd/pam.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/ngircd/pam.c	9 Apr 2008 08:50:22 -0000
@@ -0,0 +1,145 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2006 Alexander Barton (alex@barton.de)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ *
+ * PAM User Authentification
+ */
+
+#include "portab.h"
+
+#ifdef PAM
+
+static char UNUSED id[] = "$Id$";
+
+#include "imp.h"
+#include <assert.h>
+
+#include "defines.h"
+#include "log.h"
+#include "conn.h"
+#include "client.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_SECURITY_PAM_APPL_H
+#include <security/pam_appl.h>
+#endif
+
+#ifdef HAVE_PAM_PAM_APPL_H
+#include <pam/pam_appl.h>
+#endif
+
+#include "exp.h"
+#include "pam.h"
+
+
+static char *password;
+
+
+/**
+ * PAM "conversation function".
+ * This is a callback function used by the PAM library to get the password.
+ * Please see the PAM documentation for details :-)
+ */
+static int
+password_conversation(int num_msg, const struct pam_message **msg,
+ struct pam_response **resp, void *appdata_ptr) {
+	LogDebug("PAM: conv(%d, %d, '%s', '%s')",
+	    num_msg, msg[0]->msg_style, msg[0]->msg, appdata_ptr);
+
+	/* Can we deal with this request? */
+	if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) {
+		Log(LOG_ERR, "PAM: Unexpected PAM conversation '%d:%s'!",
+		    msg[0]->msg_style, msg[0]->msg);
+		return PAM_CONV_ERR;
+	}
+
+	if (! appdata_ptr) {
+		/* Sometimes appdata_ptr gets lost!? */
+		appdata_ptr = password;
+	}
+
+	/* Duplicate password ("application data") for the PAM library */
+	*resp = calloc(num_msg, sizeof(struct pam_response));
+	if (! *resp) {
+		Log(LOG_ERR, "PAM: Out of memory!");
+		return PAM_CONV_ERR;
+	}
+
+	(*resp)[0].resp = strdup((char *)appdata_ptr);
+	(*resp)[0].resp_retcode = 0;
+
+	return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR);
+}
+
+
+/**
+ * PAM "conversation" structure.
+ */
+static struct pam_conv conv = {
+	&password_conversation,
+	NULL
+};
+
+
+/**
+ * Authenticate a connectiong client using PAM.
+ * @param Client The client to authenticate.
+ * @return true when authentication succeeded, false otherwise.
+ */
+GLOBAL bool
+PAM_Authenticate(CLIENT *Client) {
+	pam_handle_t *pam;
+	int retval = PAM_SUCCESS;
+
+	LogDebug("PAM: Authenticate \"%s\" (%s) ...",
+	    Client_OrigUser(Client), Client_Mask(Client));
+
+	/* Set supplied client password */
+	if (password)
+		free(password);
+	password = strdup(Client_Password(Client));
+	conv.appdata_ptr = password;
+
+	/* Initialize PAM */
+	retval = pam_start("ngircd", Client_OrigUser(Client), &conv, &pam);
+	if (retval != PAM_SUCCESS) {
+		Log(LOG_ERR, "PAM: Failed to create authenticator!");
+		return false;
+	}
+
+	/* Do not delay failed login attempts, this would block the daemon */
+	pam_fail_delay(pam, 0);
+	pam_set_item(pam, PAM_RUSER, Client_User(Client));
+	pam_set_item(pam, PAM_RHOST, Client_Hostname(Client));
+
+	/* PAM authentication ... */
+	retval = pam_authenticate(pam, 0);
+
+	/* Success? */
+	if (retval == PAM_SUCCESS)
+		Log(LOG_INFO, "PAM: Authenticated \"%s\" (%s).",
+		    Client_OrigUser(Client), Client_Mask(Client));
+	else	
+		Log(LOG_ERR, "PAM: Error on \"%s\" (%s): %s",
+		    Client_OrigUser(Client), Client_Mask(Client),
+		    pam_strerror(pam, retval));
+
+	/* Free PAM structures */
+	if (pam_end(pam, retval) != PAM_SUCCESS)
+		Log(LOG_ERR, "PAM: Failed to release authenticator!");
+
+	return (retval == PAM_SUCCESS);
+} /* PAM_Authenticate */
+
+
+#endif /* PAM */
+
+/* -eof- */
Index: src/ngircd/pam.h
===================================================================
RCS file: src/ngircd/pam.h
diff -N src/ngircd/pam.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/ngircd/pam.h	9 Apr 2008 08:50:22 -0000
@@ -0,0 +1,25 @@
+/*
+ * ngIRCd -- The Next Generation IRC Daemon
+ * Copyright (c)2001-2006 Alexander Barton (alex@barton.de)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * Please read the file COPYING, README and AUTHORS for more information.
+ *
+ * PAM User Authentification
+ */
+
+#ifdef PAM
+
+#ifndef __pam_h__
+#define __pam_h__
+
+GLOBAL bool PAM_Authenticate PARAMS((CLIENT *Client));
+
+#endif	/* __pam_h__ */
+
+#endif	/* PAM */
+
+/* -eof- */
