Source: http://ejabberd.jabber.ru/files/contributions/ejabberd_auth_pam.diff Index: Makefile.in =================================================================== --- Makefile.in (revisión: 705) +++ Makefile.in (copia de trabajo) @@ -27,7 +27,7 @@ prefix = @prefix@ -SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@ +SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@ @pam@ ERLSHLIBS = expat_erl.so SOURCES = $(wildcard *.erl) BEAMS = $(SOURCES:.erl=.beam) Index: ejabberd.cfg.example =================================================================== --- ejabberd.cfg.example (revisión: 705) +++ ejabberd.cfg.example (copia de trabajo) @@ -95,7 +95,14 @@ %{auth_method, odbc}. %{odbc_server, "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"}. +% If you want to allow both internal and PAM authentication: +{auth_method, [internal, pam]}. +% Default PAM service "login" can be changed with: +%{pam_service, "login"}. +% Default PAM prompt "Password:" can be changed with: +%{pam_prompt_pwd, "Password:"}. + % Host name: {hosts, ["localhost"]}. Index: configure.ac =================================================================== --- configure.ac (revisión: 705) +++ configure.ac (copia de trabajo) @@ -16,6 +16,10 @@ AM_WITH_EXPAT #locating zlib AM_WITH_ZLIB +#locating zlib +AM_WITH_ZLIB +#locating Linux-PAM +AM_WITH_PAM # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -27,6 +31,7 @@ AC_FUNC_MALLOC AC_HEADER_STDC +AC_MOD_ENABLE(pam, yes) AC_MOD_ENABLE(mod_pubsub, yes) AC_MOD_ENABLE(mod_irc, yes) AC_MOD_ENABLE(mod_muc, yes) @@ -55,6 +60,7 @@ AC_SUBST(db_type) AC_CONFIG_FILES([Makefile + $make_pam $make_mod_irc $make_mod_muc $make_mod_pubsub Index: pam/Makefile.in =================================================================== --- pam/Makefile.in (revisión: 0) +++ pam/Makefile.in (revisión: 0) @@ -0,0 +1,35 @@ +# $Id$ + +CC = @CC@ +CFLAGS = @CFLAGS@ @PAM_CFLAGS@ @ERLANG_CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ @PAM_LIBS@ @ERLANG_LIBS@ + +SUBDIRS = + +ERLSHLIBS = ../ejabberd_auth_pam.so + +OUTDIR = .. +EFLAGS = -I .. -pz .. +OBJS = \ + $(OUTDIR)/ejabberd_auth_pam.beam + +all: $(OBJS) $(ERLSHLIBS) + +$(OUTDIR)/%.beam: %.erl + @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $< + +$(ERLSHLIBS): ../%.so: %.c + $(CC) -Wall $(CFLAGS) $(LDFLAGS) \ + $(subst ../,,$(subst .so,.c,$@)) $(LIBS) \ + -o $@ -fpic -shared + +clean: + rm -f $(OBJS) $(ERLSHLIBS) + +distclean: clean + rm -f Makefile + +TAGS: + etags *.erl Index: pam/ejabberd_auth_pam.c =================================================================== --- pam/ejabberd_auth_pam.c (revisión: 0) +++ pam/ejabberd_auth_pam.c (revisión: 0) @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include + +#ifdef LINUX_PAM /* XXX: and OpenPAM? */ +# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member) +#else +# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member) +#endif + +// +#ifndef LINKEDLIBPAM +#include + +#ifndef LIBPAMPATH +#define LIBPAMPATH "/usr/lib/libpam.so" +#endif + +static void *libpam_h = NULL; + +void init_libpam() { + if ( libpam_h == NULL ) { + libpam_h = dlopen( LIBPAMPATH, RTLD_GLOBAL | RTLD_NOW ); + printf( "Open: %s\r\n", LIBPAMPATH ); + }; +}; + +void fin_libpam() { + if ( libpam_h != NULL ) { + dlclose( libpam_h ); + libpam_h = NULL; + printf( "Close: %s\r\n", LIBPAMPATH ); + }; +}; +#endif +// + +typedef struct _PromptMatch { + struct _PromptMatch* next; + char* prompt; + char* response; +} *PromptMatch; + +typedef struct { + ErlDrvPort port; +} ejabberd_pam_data; + +static PromptMatch _newMatch( PromptMatch list, char* prompt, char* response ) { + PromptMatch result = ( PromptMatch)malloc( sizeof( struct _PromptMatch )); + bzero( result, sizeof( struct _PromptMatch )); + result->prompt = prompt; + result->response = response; + result->next = list; + return result; +}; + +static char* _findMatch( PromptMatch list, const char* prompt ) { + PromptMatch cur = list; + while ( cur != NULL ) { + if ( strstr( prompt, cur->prompt ) != 0 ) + return cur->response; + cur = cur->next; + }; + return NULL; +}; + +static void _cleanupMatch( PromptMatch list ) { + PromptMatch cur = list; + PromptMatch next; + while ( cur != NULL ) { + next = cur->next; + free( cur->prompt ); + free( cur->response ); + free( cur ); + cur = next; + }; +}; + +static int pam_conversation( int msgcnt, const struct pam_message** msgs, struct pam_response** res, void* arg ) { + int iResult = PAM_CONV_ERR; + if (( msgcnt > 0 ) || ( msgcnt <= PAM_MAX_NUM_MSG )) { + // Allocate a response for each message + int iSizeResponse = sizeof( struct pam_response ) * msgcnt; + struct pam_response *reply = (struct pam_response *)malloc( iSizeResponse ); + if ( reply != NULL ) { + bzero( reply, iSizeResponse ); + // Walk each message from PAM and lookup the response corresponding to each prompt + int ii; + char* response; + for ( ii = 0; ii < msgcnt; ii++ ) { + printf( "prompt(index %d)(style %d): %s\r\n", ii, PAM_MSG_MEMBER( msgs, ii, msg_style ), PAM_MSG_MEMBER( msgs, ii, msg )); + switch ( PAM_MSG_MEMBER( msgs, ii, msg_style )) { + case PAM_PROMPT_ECHO_ON : + case PAM_PROMPT_ECHO_OFF : + // Find the response for the current prompt; if none is found, warn + response = _findMatch( (PromptMatch)arg, PAM_MSG_MEMBER( msgs, ii, msg )); + if ( response != NULL ) { + // Copy password into response + reply[ ii ].resp = strdup( response ); + reply[ ii ].resp_retcode = PAM_SUCCESS; + } else { + printf("Warning: No prompt found for: %s\r\n", PAM_MSG_MEMBER( msgs, ii, msg )); + reply[ ii ].resp = strdup( "Unexpected prompt" ); + reply[ ii ].resp_retcode = PAM_SUCCESS; + }; + break; + default : + reply[ ii ].resp = strdup(""); + reply[ ii ].resp_retcode = PAM_SUCCESS; + }; + }; + *res = reply; + iResult = PAM_SUCCESS; + }; + }; + return iResult; +}; + +#define DECODE_STRING(buffer, index, result) \ + { \ + int tmp = 0; int size = 0; \ + ei_get_type(buffer, index, &tmp, &size); \ + result = malloc(size + 1); \ + ei_decode_string(buffer, index, result); \ + } + +#define RETURN_INT(value) \ + { \ + ErlDrvBinary* b = driver_alloc_binary(1); \ + rlen = 1; \ + b->orig_bytes[0] = value; \ + *rbuf = (char*)b; \ + return rlen; \ + } + + +static ErlDrvData start( ErlDrvPort port, char* command ) { + printf( "PAM-AUTH driver start\r\n" ); + #ifndef LINKEDLIBPAM + init_libpam(); + #endif + ejabberd_pam_data *d = (ejabberd_pam_data *)driver_alloc( sizeof( ejabberd_pam_data )); + d->port = port; + set_port_control_flags( port, PORT_CONTROL_FLAG_BINARY ); + return (ErlDrvData)d; +}; + +static void stop( ErlDrvData handle ) { + driver_free( (char *)handle ); + #ifndef LINKEDLIBPAM + fin_libpam(); + #endif + printf( "PAM-AUTH driver stop\r\n" ); +}; + +static int auth_params( char* buf, PromptMatch* head ) { + int iSize = 0; + int iIndex = 0; + int iResult = 0; + char* prompt = NULL; + char* response = NULL; + // Parse the version and tuple header + ei_decode_version( buf, &iIndex, &iSize ); + ei_decode_tuple_header( buf, &iIndex, &iSize ); + if ( iSize == 2 ) { + // The first item in the tuple should be the username + DECODE_STRING( buf, &iIndex, response ); + *head = _newMatch( *head, strdup( "login" ), response ); + + // The second item in the tuple should be a list of tuples + // Each tuple in the list consists of the expected prompt string + // and the value to return at that prompt + ei_decode_list_header( buf, &iIndex, &iSize ); + + // Loop over the list and pull out each prompt/response tuple and + // store it into a PromptItem list that will get processed in + // the PAM conversation + int ii = 0; + for ( ii = 0; ii < iSize; ii++ ) { + int iTupleSize = 0; + ei_decode_tuple_header( buf, &iIndex, &iTupleSize ); + if ( iTupleSize != 2 ) { + printf( "Incorrect number of arguments in prompt/response tuple(index %d): %d\r\n", ii, iTupleSize ); + } else { + // Decode the prompt from the tuple + DECODE_STRING( buf, &iIndex, prompt ); + DECODE_STRING( buf, &iIndex, response ); + // Add a new prompt item to track this prompt/response pair + *head = _newMatch( *head, prompt, response ); + }; + }; + iResult = 1; + }; + return iResult; +}; + +static int auth( PromptMatch head ) { + // Setup PAM conversation structure; pass in the prompt match list + // so we can process the prompts from PAM + int flags = 0; //PAM_SILENT + int iResult = 0; + pam_handle_t* pam = NULL; + + char* service = _findMatch( head, "service" ); + char* username = _findMatch( head, "login" ); + + struct pam_conv conv = { pam_conversation, head }; + + // Spin up PAM + int rc = pam_start( service, username, &conv, &pam ); + if ( rc == PAM_SUCCESS ) { + // Attempt to authenticate the user + rc = pam_authenticate( pam, flags ); + if ( rc != PAM_SUCCESS ) + printf("Authentication failed for: %s. Error: %s\r\n", username, pam_strerror( NULL, rc )); + else + iResult = 1; + pam_end( pam, rc ); + } else + printf("Unable to spin up PAM: %s\r\n", pam_strerror( NULL, rc )); + return iResult; +}; + +static int control( ErlDrvData drv_data, unsigned int command, char *buf, + int len, char** rbuf, int rlen ) { + int iResult; + PromptMatch head = NULL; + if (( iResult = auth_params( buf, &head )) != 0 ) { + iResult = auth( head ); + _cleanupMatch( head ); + }; + RETURN_INT( iResult ); +}; + +ErlDrvEntry entry = { + NULL, /* F_PTR init, N/A */ + start, /* L_PTR start, called when port is opened */ + stop, /* F_PTR stop, called when port is closed */ + NULL, /* F_PTR output, called when erlang has sent */ + NULL, /* F_PTR ready_input, called when input descriptor ready */ + NULL, /* F_PTR ready_output, called when output descriptor ready */ + "ejabberd_auth_pam", /* char *driver_name, the argument to open_port */ + NULL, /* F_PTR finish, called when unloaded */ + NULL, /* handle */ + control, /* F_PTR control, port_command callback */ + NULL, /* F_PTR timeout, reserved */ + NULL /* F_PTR outputv, reserved */ +}; + +DRIVER_INIT(ejabberd_auth_pam) /* must match name in driver_entry */ +{ + return &entry; +}; Index: pam/ejabberd_auth_pam.erl =================================================================== --- pam/ejabberd_auth_pam.erl (revisión: 0) +++ pam/ejabberd_auth_pam.erl (revisión: 0) @@ -0,0 +1,90 @@ +-module(ejabberd_auth_pam). +-author(''). + +-export([ + start/1, + plain_password_required/0, + set_password/3, + try_register/3, + remove_user/2, + remove_user/3, + get_password/2, + get_password_s/2, + is_user_exists/2, + get_vh_registered_users/1, + check_password/3, + check_password/5 + ]). + +start( _Host ) -> + case erl_ddll:load_driver( ejabberd:get_so_path(), ejabberd_auth_pam) of + ok -> ok; + {error, already_loaded} -> ok; + Error -> exit({error, could_not_load_driver, Error}) + end. + +plain_password_required() -> + true. + +check_password( User, Server, Password ) -> + PAMService = + case ejabberd_config:get_local_option( { pam_service, Server } ) of + undefined -> + "login"; + Service -> + Service + end, + PAMPrompt = + case ejabberd_config:get_local_option( { pam_prompt_pwd, Server } ) of + undefined -> + "Password:"; + Prompt -> + Prompt + end, + Port = open_port( { spawn, ejabberd_auth_pam }, [ binary ] ), + Bin = term_to_binary( { User, [ { PAMPrompt, Password }, { "service", PAMService } ] }), + Res = port_control( Port, 1, Bin ), + case Res of + <<0>> -> false; + <<1>> -> true; + _ -> undefined + end. + +check_password( User, Server, Password, _StreamID, _Digest ) -> + check_password( User, Server, Password ). + +set_password( _User, _Server, _Password ) -> + { error, not_allowed }. + +try_register( _User, _Server, _Password ) -> + { error, not_allowed }. + +remove_user( _User, _Server ) -> + { error, not_allowed }. + +remove_user( _User, _Server, _Password ) -> + { error, not_allowed }. + +get_vh_registered_users( _Server ) -> + []. + +is_user_exists( _User, _Server ) -> + {ok,FileBin}=file:read_file("/etc/passwd"), + Tmp=binary_to_list(FileBin), + FileStr=string:concat("\n", Tmp), + + Tmp2=string:concat("\n", _User), + RegExp=string:concat(Tmp2, ":"), + + case regexp:match(FileStr, RegExp) of + {match, _, _} -> + true; + _ -> + false + end. + +get_password( _User, _Server ) -> + false. + +get_password_s( _User, _Server ) -> + "". Index: aclocal.m4 =================================================================== --- aclocal.m4 (revisión: 705) +++ aclocal.m4 (copia de trabajo) @@ -292,3 +292,32 @@ fi ]) dnl + +AC_DEFUN(AM_WITH_PAM, +[ AC_ARG_WITH(pam, [ --with-pam=PREFIX prefix where PAM is installed ]) +unset PAM_LIBS; +unset PAM_CFLAGS; + +if test x"$with_pam" != x; then + PAM_CFLAGS="-I$with_pam/include" + PAM_LIBS="-L$with_pam/lib" +fi + +AC_CHECK_LIB(pam, pam_start, + [ PAM_LIBS="$PAM_LIBS -lpam" pam_found=yes ], + [ pam_found=no ], + "$PAM_LIBS") + +if test $pam_found = yes; then + pam_save_CFLAGS="$CFLAGS" + CFLAGS="$PAM_CFLAGS $CFLAGS" + AC_CHECK_HEADERS(security/pam_appl.h, ,$pam_found=no) + if test $pam_found = no; then + AC_MSG_ERROR([Could not find security/pam_appl.h]) + fi + CFLAGS=$pam_save_CFLAGS + + AC_SUBST(PAM_CFLAGS) + AC_SUBST(PAM_LIBS) +fi +])