/* pam.c - pam module functions Copyright (C) 2009 Howard Chu Copyright (C) 2009 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* WARNING: this code is under development and the details of the protocol may change between releases. */ #include "config.h" #include #include #include #include #include "common.h" #include "compat/attrs.h" /* these are defined (before including pam_modules.h) for staticly linking */ #define PAM_SM_AUTH #define PAM_SM_ACCOUNT #define PAM_SM_SESSION #define PAM_SM_PASSWORD #ifndef HAVE_PAM_PAM_MODULES_H #include #else #include #endif #define IGNORE_UNKNOWN 1 #define IGNORE_UNAVAIL 2 #define USE_FIRST 1 #define TRY_FIRST 2 #define USE_TOKEN 4 #define PLD_CTX "PAM_LDAPD_CTX" /* this struct represents that context that the PAM module keeps between calls */ typedef struct pld_ctx { char *user; char *dn; char *tmpluser; char *authzmsg; char *oldpw; int authok; int authz; int sessid; char buf[1024]; } pld_ctx; /* map a NSLCD PAM status code to a PAM status code */ static int nslcd2pam_rc(int rc) { #define map(i) case NSLCD_##i: return i; switch(rc) { map(PAM_SUCCESS); map(PAM_PERM_DENIED); map(PAM_AUTH_ERR); map(PAM_CRED_INSUFFICIENT); map(PAM_AUTHINFO_UNAVAIL); map(PAM_USER_UNKNOWN); map(PAM_MAXTRIES); map(PAM_NEW_AUTHTOK_REQD); map(PAM_ACCT_EXPIRED); map(PAM_SESSION_ERR); map(PAM_AUTHTOK_DISABLE_AGING); map(PAM_IGNORE); map(PAM_ABORT); default: return PAM_ABORT; } } /* clear the context to all empty values */ static void ctx_clear(pld_ctx *ctx) { if (ctx->user) { free(ctx->user); ctx->user=NULL; } if (ctx->oldpw) { memset(ctx->oldpw,0,strlen(ctx->oldpw)); free(ctx->oldpw); ctx->oldpw=NULL; } ctx->dn=NULL; ctx->tmpluser=NULL; ctx->authzmsg=NULL; ctx->authok=0; ctx->authz=0; } /* free the context (this is installed as handler into PAM) */ static void ctx_free(pam_handle_t *UNUSED(pamh),void *data,int UNUSED(err)) { pld_ctx *ctx=data; ctx_clear(ctx); free(ctx); } /* try to get the module's context, returns a PAM status code */ static int ctx_get(pam_handle_t *pamh,const char *username,pld_ctx **pctx) { pld_ctx *ctx=NULL; int rc; /* try to get the context from PAM */ rc=pam_get_data(pamh, PLD_CTX,(const void **)&ctx); if ((rc==PAM_SUCCESS)&&(ctx!=NULL)) { /* if the user is different clear the context */ if ((ctx->user!=NULL)&&(strcmp(ctx->user,username)!=0)) ctx_clear(ctx); } else { /* allocate a new context */ ctx=calloc(1,sizeof(*ctx)); ctx_clear(ctx); if (!ctx) return PAM_BUF_ERR; /* store the new context with the handler to free it */ rc=pam_set_data(pamh,PLD_CTX,ctx,ctx_free); if (rc!=PAM_SUCCESS) ctx_free(pamh,ctx,0); } if (rc==PAM_SUCCESS) *pctx=ctx; return rc; } /* ask the user for an authentication token (password) */ static int pam_get_authtok(pam_handle_t *pamh,int flags,char *prompt1,char *prompt2,char **pwd) { int rc; char *p; struct pam_message msg[1], *pmsg[1]; struct pam_response *resp; struct pam_conv *conv; *pwd=NULL; rc=pam_get_item(pamh,PAM_CONV,(const void **)&conv); if (rc==PAM_SUCCESS) { pmsg[0]=&msg[0]; msg[0].msg_style=PAM_PROMPT_ECHO_OFF; msg[0].msg=prompt1; resp=NULL; rc=conv->conv(1, (const struct pam_message **)pmsg, &resp,conv->appdata_ptr); } else { return rc; } if (resp!=NULL) { if ((flags & PAM_DISALLOW_NULL_AUTHTOK) && resp[0].resp==NULL) { free(resp); return PAM_AUTH_ERR; } p=resp[0].resp; resp[0].resp=NULL; free(resp); } else { return PAM_CONV_ERR; } if (prompt2) { msg[0].msg=prompt2; resp=NULL; rc=conv->conv(1, (const struct pam_message **) pmsg, &resp, conv->appdata_ptr); if (resp && resp[0].resp && !strcmp(resp[0].resp, p)) rc=PAM_SUCCESS; else rc=PAM_AUTHTOK_RECOVERY_ERR; if (resp) { if (resp[0].resp) { (void) memset(resp[0].resp, 0, strlen(resp[0].resp)); free(resp[0].resp); } free(resp); } } if (rc==PAM_SUCCESS) *pwd=p; else if (p) { memset(p, 0, strlen(p)); free(p); } return rc; } /* perform an authentication call over nslcd */ static int nslcd_request_authc(pld_ctx *ctx,const char *username,const char *svc,const char *passwd) { PAM_REQUEST(NSLCD_ACTION_PAM_AUTHC, /* write the request parameters */ WRITE_STRING(fp,username); WRITE_STRING(fp,ctx->dn); WRITE_STRING(fp,svc); WRITE_STRING(fp,passwd), /* read the result entry */ READ_BUF_STRING(fp,ctx->tmpluser); READ_BUF_STRING(fp,ctx->dn); READ_PAM_CODE(fp,ctx->authok) READ_PAM_CODE(fp,ctx->authz) READ_BUF_STRING(fp,ctx->authzmsg);) } int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc,const char **argv) { int rc; const char *username,*svc; char *passwd=NULL; int first_pass=0,ignore_flags=0; int i; pld_ctx *ctx; /* go over arguments */ for (i=0;iauthok; if ((rc==PAM_AUTHINFO_UNAVAIL)&&(ignore_flags&IGNORE_UNAVAIL)) rc=PAM_IGNORE; else if ((rc==PAM_USER_UNKNOWN)&&(ignore_flags&IGNORE_UNKNOWN)) rc=PAM_IGNORE; } if ((rc==PAM_SUCCESS)||(first_pass&USE_FIRST)) break; first_pass=0; } /* save username */ if (rc==PAM_SUCCESS) { ctx->user=strdup(username); /* if password change is required, save old password in context */ if (ctx->authz==PAM_NEW_AUTHTOK_REQD) ctx->oldpw=strdup(passwd); } /* update caller's idea of the user name */ if ( (rc==PAM_SUCCESS) && ctx->tmpluser && ctx->tmpluser[0] && (strcmp(ctx->tmpluser,username)!=0) ) { rc=pam_set_item(pamh,PAM_USER,ctx->tmpluser); } return rc; } /* called to update the authentication credentials */ int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,const char **argv) { /* we don't need to do anything here */ return PAM_SUCCESS; } static int pam_warn( struct pam_conv *aconv, const char *message, int style, int no_warn) { struct pam_message msg, *pmsg; struct pam_response *resp; if (no_warn) return PAM_SUCCESS; pmsg=&msg; msg.msg_style=style; msg.msg=(char *) message; resp=NULL; return aconv->conv(1, (const struct pam_message **) &pmsg, &resp, aconv->appdata_ptr); } /* perform an authorisation call over nslcd */ static int nslcd_request_authz(pld_ctx *ctx,const char *username, const char *svc,const char *ruser,const char *rhost,const char *tty) { PAM_REQUEST(NSLCD_ACTION_PAM_AUTHZ, /* write the request parameters */ WRITE_STRING(fp,username); WRITE_STRING(fp,ctx->dn); WRITE_STRING(fp,svc); WRITE_STRING(fp,ruser); WRITE_STRING(fp,rhost); WRITE_STRING(fp,tty), /* read the result entry */ READ_BUF_STRING(fp,ctx->tmpluser); READ_BUF_STRING(fp,ctx->dn); READ_PAM_CODE(fp,ctx->authz); READ_BUF_STRING(fp,ctx->authzmsg);) } int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc,const char **argv) { int rc; const char *username,*svc,*ruser,*rhost,*tty; int no_warn=0, ignore_flags=0; int i; struct pam_conv *appconv; pld_ctx *ctx=NULL, ctx2; for (i=0;idn; ctx2.user=ctx->user; rc=nslcd_request_authz(&ctx2,username,svc,ruser,rhost,tty); if ((rc==PAM_AUTHINFO_UNAVAIL)&&(ignore_flags&IGNORE_UNAVAIL)) rc=PAM_IGNORE; else if ((rc==PAM_USER_UNKNOWN)&&(ignore_flags&IGNORE_UNKNOWN)) rc=PAM_IGNORE; if (rc!=PAM_SUCCESS) { if (rc!=PAM_IGNORE) pam_warn(appconv,"LDAP authorization failed",PAM_ERROR_MSG,no_warn); } else { if (ctx2.authzmsg && ctx2.authzmsg[0]) pam_warn(appconv,ctx2.authzmsg,PAM_TEXT_INFO,no_warn); if (ctx2.authz==PAM_SUCCESS) { rc=ctx->authz; if (ctx->authzmsg && ctx->authzmsg[0]) pam_warn(appconv,ctx->authzmsg,PAM_TEXT_INFO,no_warn); } } /* update caller's idea of the user name */ if ( (rc==PAM_SUCCESS) && ctx->tmpluser && ctx->tmpluser[0] && (strcmp(ctx->tmpluser,username)!=0) ) { rc=pam_set_item(pamh,PAM_USER,ctx->tmpluser); } return rc; } /* do a session nslcd request (open or close) */ static int nslcd_request_sess(pam_handle_t *pamh,pld_ctx *ctx,int action) { const char *svc=NULL,*tty=NULL,*rhost=NULL,*ruser=NULL; PAM_REQUEST(action, /* get information for request (ignore errors) */ pam_get_item(pamh,PAM_SERVICE,(const void **)&svc); pam_get_item(pamh,PAM_TTY,(const void **)&tty); pam_get_item(pamh,PAM_RHOST,(const void **)&rhost); pam_get_item(pamh,PAM_RUSER,(const void **)&ruser); /* write the request parameters */ WRITE_STRING(fp,ctx->user); WRITE_STRING(fp,ctx->dn); WRITE_STRING(fp,svc); WRITE_STRING(fp,tty); WRITE_STRING(fp,rhost); WRITE_STRING(fp,ruser); WRITE_INT32(fp,ctx->sessid), /* read the result entry */ READ_INT32(fp,ctx->sessid)) } static int pam_sm_session( pam_handle_t *pamh, int flags, int argc, const char **argv, int action, int *no_warn) { int rc, err; const char *username; int ignore_flags=0; int i, success=PAM_SUCCESS; pld_ctx *ctx=NULL; for (i=0;idn); WRITE_STRING(fp,svc); WRITE_STRING(fp,oldpw); WRITE_STRING(fp,newpw), /* read the result entry */ READ_BUF_STRING(fp,ctx->tmpluser); READ_BUF_STRING(fp,ctx->dn); READ_PAM_CODE(fp,ctx->authz); READ_BUF_STRING(fp,ctx->authzmsg);) } int pam_sm_chauthtok( pam_handle_t *pamh, int flags, int argc, const char **argv) { int rc; const char *username, *p=NULL, *q=NULL, *svc; int first_pass=0, no_warn=0, ignore_flags=0; int i, success=PAM_SUCCESS; struct pam_conv *appconv; pld_ctx *ctx=NULL; for (i=0;idn) { rc=nslcd_request_pwmod(ctx,username,svc,p,NULL); if ((rc==PAM_AUTHINFO_UNAVAIL)&&(ignore_flags&IGNORE_UNAVAIL)) rc=PAM_IGNORE; else if ((rc==PAM_USER_UNKNOWN)&&(ignore_flags&IGNORE_UNKNOWN)) rc=PAM_IGNORE; } return rc; } rc=pam_get_item(pamh,PAM_OLDAUTHTOK,&p); if (rc) return rc; if (!p) p=ctx->oldpw; if (first_pass) { rc=pam_get_item(pamh,PAM_AUTHTOK,&q); if ((rc!=PAM_SUCCESS || !q) && (first_pass & (USE_FIRST|USE_TOKEN))) { if (rc==PAM_SUCCESS) rc=PAM_AUTHTOK_RECOVERY_ERR; return rc; } } if (!q) { rc=pam_get_authtok(pamh, flags, "Enter new LDAP Password: ", "Retype new LDAP Password: ", &q); if (rc==PAM_SUCCESS) { pam_set_item(pamh,PAM_AUTHTOK,q); memset(q,0,strlen(q)); free(q); rc=pam_get_item(pamh,PAM_AUTHTOK,&q); } if (rc!=PAM_SUCCESS) return rc; } rc=nslcd_request_pwmod(ctx,username,svc,p,q); if ((rc==PAM_AUTHINFO_UNAVAIL)&&(ignore_flags&IGNORE_UNAVAIL)) rc=PAM_IGNORE; else if ((rc==PAM_USER_UNKNOWN)&&(ignore_flags&IGNORE_UNKNOWN)) rc=PAM_IGNORE; p=NULL; q=NULL; if (rc==PAM_SUCCESS) { rc=ctx->authz; if (rc!=PAM_SUCCESS) pam_warn(appconv, ctx->authzmsg, PAM_ERROR_MSG, no_warn); } else if (rc!=PAM_IGNORE) pam_warn(appconv, "LDAP pwmod failed", PAM_ERROR_MSG, no_warn); return rc; } #ifdef PAM_STATIC struct pam_module _pam_ldap_modstruct={ "pam_ldap", pam_sm_authenticate, pam_sm_setcred, pam_sm_acct_mgmt, pam_sm_open_session, pam_sm_close_session, pam_sm_chauthtok }; #endif /* PAM_STATIC */