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
{ 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;
"DESC 'Map <service> lookups of <orig> attr to <new> 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 }
};
"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 }
};
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) {
}
}
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 ) {
}
}
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;
}
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);
/* 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);
char svcc[256];
char pwdc[256];
struct berval uid, svc, pwd, sdn, dn;
- int hlen;
struct bindinfo bi;
bi.authz = PAM_SUCCESS;
goto finish;
}
- /* Why didn't we make this a berval? */
- hlen = strlen(global_host);
-
- /* First try this form, to allow service-dependent mappings */
- /* cn=<service>+uid=<user>,cn=<host>,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=<service>+uid=<user>,cn=<host>,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.
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;
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;
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;
}
return 0;
}
-static int uid2dn_cb(Operation *op,SlapReply *rs)
+int nssov_name2dn_cb(Operation *op,SlapReply *rs)
{
if ( rs->sr_type == REP_SEARCH )
{
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;