From 8fd0d184a7fd2025b4701fee5ceef995e8f598c1 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Mon, 20 Apr 2009 02:42:40 +0000 Subject: [PATCH] Flesh out config options, implement authorization checks --- contrib/slapd-modules/nssov/README | 2 +- contrib/slapd-modules/nssov/nssov.c | 91 ++++++++++++- contrib/slapd-modules/nssov/nssov.h | 25 +++- contrib/slapd-modules/nssov/pam.c | 190 ++++++++++++++++++++++++--- contrib/slapd-modules/nssov/passwd.c | 4 +- 5 files changed, 288 insertions(+), 24 deletions(-) diff --git a/contrib/slapd-modules/nssov/README b/contrib/slapd-modules/nssov/README index 82c60493c4..ba76fd1ea3 100644 --- a/contrib/slapd-modules/nssov/README +++ b/contrib/slapd-modules/nssov/README @@ -109,7 +109,7 @@ and checked to determine access. Also a check may be performed to see if the user is a member of a particular group. This method is pretty inflexible and doesn't scale well to large networks of users, hosts, and services. - The second uses slapd's ACL engine to check if the user has "auth" + The second uses slapd's ACL engine to check if the user has "compare" privilege on an ipHost object whose name matches the current hostname, and whose authorizedService attribute matches the current service name. This method is preferred, since it allows authorization to be centralized in diff --git a/contrib/slapd-modules/nssov/nssov.c b/contrib/slapd-modules/nssov/nssov.c index 10f6178e83..b3e7d014be 100644 --- a/contrib/slapd-modules/nssov/nssov.c +++ b/contrib/slapd-modules/nssov/nssov.c @@ -407,9 +407,21 @@ static slap_verbmasks nss_svcs[] = { { BER_BVNULL, 0 } }; +static slap_verbmasks pam_opts[] = { + { BER_BVC("userhost"), NI_PAM_USERHOST }, + { BER_BVC("userservice"), NI_PAM_USERSVC }, + { BER_BVC("usergroup"), NI_PAM_USERGRP }, + { BER_BVC("hostservice"), NI_PAM_HOSTSVC }, + { BER_BVC("sasl2dn"), NI_PAM_SASL2DN }, + { BER_BVC("uid2dn"), NI_PAM_UID2DN }, + { BER_BVNULL, 0 } +}; + enum { NSS_SSD=1, - NSS_MAP + NSS_MAP, + NSS_PAM, + NSS_PAMGROUP, }; static ConfigDriver nss_cf_gen; @@ -425,6 +437,52 @@ static ConfigTable nsscfg[] = { "DESC 'Map lookups of attr to attr' " "EQUALITY caseIgnoreMatch " "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "nssov-pam", "options", 2, 0, 0, ARG_MAGIC|NSS_PAM, + nss_cf_gen, "(OLcfgCtAt:3.3 NAME 'olcNssPam' " + "DESC 'PAM authentication and authorization options' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "nssov-pam-defhost", "hostname", 2, 2, 0, ARG_OFFSET|ARG_BERVAL, + (void *)offsetof(struct nssov_info, ni_pam_defhost), + "(OLcfgCtAt:3.4 NAME 'olcNssPamDefHost' " + "DESC 'Default hostname for service checks' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "nssov-pam-group-dn", "DN", 2, 2, 0, ARG_MAGIC|ARG_DN|NSS_PAMGROUP, + nss_cf_gen, "(OLcfgCtAt:3.5 NAME 'olcNssPamGroupDN' " + "DESC 'DN of group in which membership is required' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "nssov-pam-group-ad", "options", 2, 2, 0, ARG_OFFSET|ARG_ATDESC, + (void *)offsetof(struct nssov_info, ni_pam_group_ad), + "(OLcfgCtAt:3.6 NAME 'olcNssPamGroupAD' " + "DESC 'Member attribute to use for group check' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "nssov-pam-min-uid", "uid", 2, 2, 0, ARG_OFFSET|ARG_INT, + (void *)offsetof(struct nssov_info, ni_pam_min_uid), + "(OLcfgCtAt:3.7 NAME 'olcNssPamMinUid' " + "DESC 'Minimum UID allowed to login' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "nssov-pam-max-uid", "uid", 2, 2, 0, ARG_OFFSET|ARG_INT, + (void *)offsetof(struct nssov_info, ni_pam_max_uid), + "(OLcfgCtAt:3.8 NAME 'olcNssPamMaxUid' " + "DESC 'Maximum UID allowed to login' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "nssov-pam-template-ad", "attr", 2, 2, 0, ARG_OFFSET|ARG_ATDESC, + (void *)offsetof(struct nssov_info, ni_pam_template_ad), + "(OLcfgCtAt:3.9 NAME 'olcNssPamTemplateAD' " + "DESC 'Attribute to use for template login name' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "nssov-pam-template", "name", 2, 2, 0, ARG_OFFSET|ARG_BERVAL, + (void *)offsetof(struct nssov_info, ni_pam_template), + "(OLcfgCtAt:3.10 NAME 'olcNssPamTemplate' " + "DESC 'Default template login name' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, { NULL, NULL, 0,0,0, ARG_IGNORED } }; @@ -433,7 +491,10 @@ static ConfigOCs nssocs[] = { "NAME 'olcNssOvConfig' " "DESC 'NSS lookup configuration' " "SUP olcOverlayConfig " - "MAY ( olcNssSsd $ olcNssMap ) )", + "MAY ( olcNssSsd $ olcNssMap $ olcNssPam $ olcNssPamDefHost $ " + "olcNssPamGroupDN $ olcNssPamGroupAD $ " + "olcNssPamMinUid $ olcNssPamMaxUid $ " + "olcNssPamTemplateAD $ olcNssPamTemplate ) )", Cft_Overlay, nsscfg }, { NULL, 0, NULL } }; @@ -445,6 +506,7 @@ nss_cf_gen(ConfigArgs *c) nssov_info *ni = on->on_bi.bi_private; nssov_mapinfo *mi; int i, j, rc = 0; + slap_mask_t m; if ( c->op == SLAP_CONFIG_EMIT ) { switch(c->type) { @@ -500,9 +562,21 @@ nss_cf_gen(ConfigArgs *c) } } break; + case NSS_PAM: + rc = mask_to_verbs( pam_opts, ni->ni_pam_opts, &c->rvalue_vals ); + break; + case NSS_PAMGROUP: + if (!BER_BVISEMPTY( &ni->ni_pam_group_dn )) { + value_add_one( &c->rvalue_vals, &ni->ni_pam_group_dn ); + value_add_one( &c->rvalue_nvals, &ni->ni_pam_group_dn ); + } else { + rc = 1; + } + break; } return rc; } else if ( c->op == LDAP_MOD_DELETE ) { + /* FIXME */ return 1; } switch( c->type ) { @@ -563,6 +637,19 @@ nss_cf_gen(ConfigArgs *c) } } break; + case NSS_PAM: + m = ni->ni_pam_opts; + i = verbs_to_mask(c->argc, c->argv, pam_opts, &m); + if (i == 0) { + ni->ni_pam_opts = m; + } else { + rc = 1; + } + break; + case NSS_PAMGROUP: + ni->ni_pam_group_dn = c->value_ndn; + ch_free( c->value_dn.bv_val ); + break; } return rc; } diff --git a/contrib/slapd-modules/nssov/nssov.h b/contrib/slapd-modules/nssov/nssov.h index d8c6f6832d..2f41b10bab 100644 --- a/contrib/slapd-modules/nssov/nssov.h +++ b/contrib/slapd-modules/nssov/nssov.h @@ -64,8 +64,30 @@ typedef struct nssov_info int ni_socket; Connection *ni_conn; BackendDB *ni_db; + + /* PAM authz support... */ + slap_mask_t ni_pam_opts; + struct berval ni_pam_group_dn; + AttributeDescription *ni_pam_group_ad; + int ni_pam_min_uid; + int ni_pam_max_uid; + AttributeDescription *ni_pam_template_ad; + struct berval ni_pam_template; + struct berval ni_pam_defhost; + AttributeDescription *ni_pam_host_ad; + AttributeDescription *ni_pam_svc_ad; } nssov_info; +#define NI_PAM_USERHOST 1 /* old style host checking */ +#define NI_PAM_USERSVC 2 /* old style service checking */ +#define NI_PAM_USERGRP 4 /* old style group checking */ +#define NI_PAM_HOSTSVC 8 /* new style authz checking */ +#define NI_PAM_SASL2DN 0x10 /* use sasl2dn */ +#define NI_PAM_UID2DN 0x20 /* use uid2dn */ + +#define NI_PAM_OLD (NI_PAM_USERHOST|NI_PAM_USERSVC|NI_PAM_USERGRP) +#define NI_PAM_NEW NI_PAM_HOSTSVC + /* Read the default configuration file. */ void nssov_cfg_init(nssov_info *ni,const char *fname); @@ -139,11 +161,12 @@ int read_address(TFILE *fp,char *addr,int *addrlen,int *af); /* checks to see if the specified string is a valid username */ int isvalidusername(struct berval *name); -/* transforms the DN info a uid doing an LDAP lookup if needed */ +/* transforms the DN into a uid doing an LDAP lookup if needed */ int nssov_dn2uid(Operation *op,nssov_info *ni,struct berval *dn,struct berval *uid); /* transforms the uid into a DN by doing an LDAP lookup */ int nssov_uid2dn(Operation *op,nssov_info *ni,struct berval *uid,struct berval *dn); +int nssov_name2dn_cb(Operation *op, SlapReply *rs); /* Escapes characters in a string for use in a search filter. */ int nssov_escape(struct berval *src,struct berval *dst); diff --git a/contrib/slapd-modules/nssov/pam.c b/contrib/slapd-modules/nssov/pam.c index 1ee873b0e4..c2f950f579 100644 --- a/contrib/slapd-modules/nssov/pam.c +++ b/contrib/slapd-modules/nssov/pam.c @@ -104,7 +104,6 @@ int pam_authc(nssov_info *ni,TFILE *fp,Operation *op) char svcc[256]; char pwdc[256]; struct berval uid, svc, pwd, sdn, dn; - int hlen; struct bindinfo bi; bi.authz = PAM_SUCCESS; @@ -129,28 +128,34 @@ int pam_authc(nssov_info *ni,TFILE *fp,Operation *op) goto finish; } - /* Why didn't we make this a berval? */ - hlen = strlen(global_host); - - /* First try this form, to allow service-dependent mappings */ - /* cn=+uid=,cn=,cn=pam,cn=auth */ - sdn.bv_len = uid.bv_len + svc.bv_len + hlen + STRLENOF( "cn=+uid=,cn=,cn=pam,cn=auth" ); - sdn.bv_val = op->o_tmpalloc( sdn.bv_len + 1, op->o_tmpmemctx ); - sprintf(sdn.bv_val, "cn=%s+uid=%s,cn=%s,cn=pam,cn=auth", svcc, uidc, global_host); BER_BVZERO(&dn); - slap_sasl2dn(op, &sdn, &dn, 0); - op->o_tmpfree( sdn.bv_val, op->o_tmpmemctx ); + + if (ni->ni_pam_opts & NI_PAM_SASL2DN) { + int hlen = global_host_bv.bv_len; + + /* cn=+uid=,cn=,cn=pam,cn=auth */ + sdn.bv_len = uid.bv_len + svc.bv_len + hlen + + STRLENOF( "cn=+uid=,cn=,cn=pam,cn=auth" ); + sdn.bv_val = op->o_tmpalloc( sdn.bv_len + 1, op->o_tmpmemctx ); + sprintf(sdn.bv_val, "cn=%s+uid=%s,cn=%s,cn=pam,cn=auth", + svcc, uidc, global_host_bv.bv_val); + slap_sasl2dn(op, &sdn, &dn, 0); + op->o_tmpfree( sdn.bv_val, op->o_tmpmemctx ); + } /* If no luck, do a basic uid search */ - if (BER_BVISEMPTY(&dn)) { - if (!nssov_uid2dn(op, ni, &uid, &dn)) { - rc = PAM_USER_UNKNOWN; - goto finish; + if (BER_BVISEMPTY(&dn) && (ni->ni_pam_opts & NI_PAM_UID2DN)) { + nssov_uid2dn(op, ni, &uid, &dn); + if (!BER_BVISEMPTY(&dn)) { + sdn = dn; + dnNormalize( 0, NULL, NULL, &sdn, &dn, op->o_tmpmemctx ); } - sdn = dn; - dnNormalize( 0, NULL, NULL, &sdn, &dn, op->o_tmpmemctx ); } BER_BVZERO(&sdn); + if (BER_BVISEMPTY(&dn)) { + rc = PAM_USER_UNKNOWN; + goto finish; + } /* Should only need to do this once at open time, but there's always * the possibility that ppolicy will get loaded later. @@ -201,6 +206,18 @@ finish: return 0; } +static int pam_nullcb(Operation *op, SlapReply *rs) +{ + return LDAP_SUCCESS; +} + +static struct berval grpmsg = + BER_BVC("Access denied by group check"); +static struct berval hostmsg = + BER_BVC("Access denied for this host"); +static struct berval svcmsg = + BER_BVC("Access denied for this service"); + int pam_authz(nssov_info *ni,TFILE *fp,Operation *op) { struct berval dn, svc; @@ -209,6 +226,11 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op) int32_t tmpint32; char dnc[1024]; char svcc[256]; + int rc = PAM_SUCCESS; + Entry *e = NULL; + Attribute *a; + SlapReply rs = {REP_RESULT}; + slap_callback cb = {0}; READ_STRING_BUF2(fp,dnc,sizeof(dnc)); dn.bv_val = dnc; @@ -219,12 +241,144 @@ int pam_authz(nssov_info *ni,TFILE *fp,Operation *op) Debug(LDAP_DEBUG_TRACE,"nssov_pam_authz(%s)\n",dn.bv_val,0,0); + /* We don't do authorization if they weren't authenticated by us */ + if (BER_BVISEMPTY(&dn)) { + rc = PAM_USER_UNKNOWN; + goto finish; + } + + /* See if they have access to the host and service */ + if (ni->ni_pam_opts & NI_PAM_HOSTSVC) { + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + struct berval hostdn = BER_BVNULL; + { + nssov_mapinfo *mi = &ni->ni_maps[NM_host]; + char fbuf[1024]; + struct berval filter = {sizeof(fbuf),fbuf}; + SlapReply rs2 = {REP_RESULT}; + + /* Lookup the host entry */ + nssov_filter_byname(mi,0,&global_host_bv,&filter); + cb.sc_private = &hostdn; + cb.sc_response = nssov_name2dn_cb; + op->o_callback = &cb; + op->o_req_dn = mi->mi_base; + op->o_req_ndn = mi->mi_base; + op->ors_scope = mi->mi_scope; + op->ors_filterstr = filter; + op->ors_filter = str2filter_x(op, filter.bv_val); + op->ors_attrs = slap_anlist_no_attrs; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = 2; + rc = op->o_bd->be_search(op, &rs2); + filter_free_x(op, op->ors_filter, 1); + + if (BER_BVISEMPTY(&hostdn) && + !BER_BVISEMPTY(&ni->ni_pam_defhost)) { + filter.bv_len = sizeof(fbuf); + filter.bv_val = fbuf; + memset(&rs2, 0, sizeof(rs2)); + rs2.sr_type = REP_RESULT; + nssov_filter_byname(mi,0,&ni->ni_pam_defhost,&filter); + op->ors_filterstr = filter; + op->ors_filter = str2filter_x(op, filter.bv_val); + rc = op->o_bd->be_search(op, &rs2); + filter_free_x(op, op->ors_filter, 1); + } + + /* no host entry, no default host -> deny */ + if (BER_BVISEMPTY(&hostdn)) { + rc = PAM_PERM_DENIED; + authzmsg = hostmsg; + goto finish; + } + } + + cb.sc_response = pam_nullcb; + cb.sc_private = NULL; + op->o_tag = LDAP_REQ_COMPARE; + op->o_req_dn = hostdn; + op->o_req_ndn = hostdn; + ava.aa_desc = ni->ni_pam_svc_ad; + ava.aa_value = svc; + op->orc_ava = &ava; + rc = op->o_bd->be_compare( op, &rs ); + if ( rs.sr_err != LDAP_COMPARE_TRUE ) { + authzmsg = svcmsg; + rc = PAM_PERM_DENIED; + goto finish; + } + } + + /* See if they're a member of the group */ + if ((ni->ni_pam_opts & NI_PAM_USERGRP) && + !BER_BVISEMPTY(&ni->ni_pam_group_dn) && + ni->ni_pam_group_ad) { + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + op->o_callback = &cb; + cb.sc_response = pam_nullcb; + op->o_tag = LDAP_REQ_COMPARE; + op->o_req_dn = ni->ni_pam_group_dn; + op->o_req_ndn = ni->ni_pam_group_dn; + ava.aa_desc = ni->ni_pam_group_ad; + ava.aa_value = dn; + op->orc_ava = &ava; + rc = op->o_bd->be_compare( op, &rs ); + if ( rs.sr_err != LDAP_COMPARE_TRUE ) { + authzmsg = grpmsg; + rc = PAM_PERM_DENIED; + goto finish; + } + } + + /* We need to check the user's entry for these bits */ + if ((ni->ni_pam_opts & (NI_PAM_USERHOST|NI_PAM_USERSVC)) || + ni->ni_pam_template_ad ) { + rc = be_entry_get_rw( op, &dn, NULL, NULL, 0, &e ); + if (rc != LDAP_SUCCESS) { + rc = PAM_USER_UNKNOWN; + goto finish; + } + } + if (ni->ni_pam_opts & NI_PAM_USERHOST) { + a = attr_find(e->e_attrs, ni->ni_pam_host_ad); + if (!a || value_find_ex( ni->ni_pam_host_ad, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + a->a_vals, &global_host_bv, op->o_tmpmemctx )) { + rc = PAM_PERM_DENIED; + authzmsg = hostmsg; + goto finish; + } + } + if (ni->ni_pam_opts & NI_PAM_USERSVC) { + a = attr_find(e->e_attrs, ni->ni_pam_svc_ad); + if (!a || value_find_ex( ni->ni_pam_svc_ad, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + a->a_vals, &svc, op->o_tmpmemctx )) { + rc = PAM_PERM_DENIED; + authzmsg = svcmsg; + goto finish; + } + } + + if (ni->ni_pam_template_ad) { + a = attr_find(e->e_attrs, ni->ni_pam_template_ad); + if (a) + tmpluser = a->a_vals[0]; + else if (!BER_BVISEMPTY(&ni->ni_pam_template)) + tmpluser = ni->ni_pam_template; + } + +finish: WRITE_INT32(fp,NSLCD_VERSION); WRITE_INT32(fp,NSLCD_ACTION_PAM_AUTHZ); WRITE_INT32(fp,NSLCD_RESULT_SUCCESS); - WRITE_INT32(fp,PAM_SUCCESS); + WRITE_INT32(fp,rc); WRITE_BERVAL(fp,&authzmsg); WRITE_BERVAL(fp,&tmpluser); + if (e) { + be_entry_release_r(op, e); + } return 0; } diff --git a/contrib/slapd-modules/nssov/passwd.c b/contrib/slapd-modules/nssov/passwd.c index 14f28a7106..20ef4d67fb 100644 --- a/contrib/slapd-modules/nssov/passwd.c +++ b/contrib/slapd-modules/nssov/passwd.c @@ -143,7 +143,7 @@ int nssov_dn2uid(Operation *op,nssov_info *ni,struct berval *dn,struct berval *u return 0; } -static int uid2dn_cb(Operation *op,SlapReply *rs) +int nssov_name2dn_cb(Operation *op,SlapReply *rs) { if ( rs->sr_type == REP_SEARCH ) { @@ -175,7 +175,7 @@ int nssov_uid2dn(Operation *op,nssov_info *ni,struct berval *uid,struct berval * nssov_filter_byid(mi,UID_KEY,uid,&filter); BER_BVZERO(dn); cb.sc_private = dn; - cb.sc_response = uid2dn_cb; + cb.sc_response = nssov_name2dn_cb; op2 = *op; op2.o_callback = &cb; op2.o_req_dn = mi->mi_base; -- 2.39.5