]> git.sur5r.net Git - openldap/commitdiff
Flesh out config options, implement authorization checks
authorHoward Chu <hyc@openldap.org>
Mon, 20 Apr 2009 02:42:40 +0000 (02:42 +0000)
committerHoward Chu <hyc@openldap.org>
Mon, 20 Apr 2009 02:42:40 +0000 (02:42 +0000)
contrib/slapd-modules/nssov/README
contrib/slapd-modules/nssov/nssov.c
contrib/slapd-modules/nssov/nssov.h
contrib/slapd-modules/nssov/pam.c
contrib/slapd-modules/nssov/passwd.c

index 82c60493c41cbe5e78216d794ac95c61f84e9f5c..ba76fd1ea3adbc0a4d761559fd6772f8b8138cfb 100644 (file)
@@ -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
index 10f6178e83a777dcd3e08e3e9a82cb93467709d3..b3e7d014be16c0fcfe3662a43ee311ef0750b8e4 100644 (file)
@@ -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 <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 }
 };
 
@@ -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;
 }
index d8c6f6832d54f69144704d89d8d126d46c453c19..2f41b10bab1359424db1bd163e4ec2bcc8673aa8 100644 (file)
@@ -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);
index 1ee873b0e47c6504b7c9b291ed23a7e9902b7958..c2f950f579a0cabf3f9e55a460457d8138d153d1 100644 (file)
@@ -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=<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.
@@ -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;
 }
 
index 14f28a7106b2c6fc107ac11601376b436348bbd9..20ef4d67fbf3ee51e2b9dfeced31c3ffb321a555 100644 (file)
@@ -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;