/* $Novell: dyngroup.c,v 1.11 2003/05/12 12:18:46 $ */
/**************************************************************************
*  Novell Software Developer Kit
*
*  Copyright (C) 2002-2003 Novell, Inc. All Rights Reserved.
*
*  THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
*  USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
*  ACCOMPANYING THE SOFTWARE DEVELOPER KIT (SDK) THAT CONTAINS THIS WORK.
*  PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
*  ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
*  PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
*  DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
*  PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
*  CUSTOMERS WITH RESPECT TO THIS CODE.
*
***************************************************************************
   dyngroup.c
***************************************************************************
   Description:

   The dyngroup.c sample demonstrates how to:
   1) create a Dynamic Group object (cn=myDynamicGroup) in a specified 
      container with a specified memberQueryURL
   2) read the "member" attribute of myDynamicGroup and print out values
   3) delete the dynamic group 

   
   "Dynamic groups" are like normal groups, but they let one specify 
   criteria to be used for evaluating memberships to a group; the 
   membership of the group is determined dynamically by the directory 
   servers involved.  This lets the group administrator define the 
   membership in terms of attributes, and let the DSA worry about what is
   an actual member. 

   In other words, a dynamic group is similar to a group object.  But 
   in a dynamic group, a search filter is specified.  Any object passing the 
   search filter is considered a member of the group and will be returned
   when reading the "member" attribute.

   Dynamic groups are supported in Novell's eDirectory version 8.6.1 or later.

   A dynamic group is created with object class = "dynamicGroup" or by adding
   the auxilliary class "dynamicGroupAux" to an existing group object.

   The search filter is specified in "memberQuery" attribute.  The value
   of "memberQuery" is encoded as an "LDAP URL".

   A dynamic group is a subtype of a static group.  Hence members can be
   included statically using "uniqueMember" attribute.

   Similarly members can be excluded statically from a dynamic group using
   "excludedMember" attribute.

   In order to provide consistent results when processing the search criteria,
   the server uses a single authorization entry.  The "dgIdentity" attribute
   holds this authorization entry.  The server decides the entry to be used 
   based on the follwing conditions:
   1) If the dynamic group object contains a public/private key, then the group
      object will be used as the identity.
   2) If no public/private key exists for the group, then the "dgIdentity" 
      attribute will be used as the identity.
   3) If no dgIdentity attribute is present on the group, then the implied
      search is done using the anonymous identity.
   
   The "dgAllowDuplicates" attribute enables/disables duplicate value presence
   in the Dynamic Group entry.  This is a boolean attribute and can be set to
   true or false.  The default is false.  Setting this attribute to true results
   in a faster search, but some values may be duplicates.

   The "dgAllowUnknown" boolean attribute determines the behaviour when the
   Dynamic Group members are not fully expandable for some reason, such as the
   referred server is not accessible. By default it is false.

   The "dgTimeout" attribute determines how long to wait to get results from
   another server during Dynamic Group member search, when the search spans
   across servers.
   
   The search filter is specified in LDAP URL form and is contained in
   the "memberQuery" attribute.  The format of this URL is:
   ldap:///<base dn>??<scope>?<filter>[?[!]x-chain]
 
   The optional extension "x-chain" causes the server to chain to
   other servers if necessary to complete the search.  In its absence,
   the search will be limited to the host server.  The exclamation
   indicates it is a critical extension, and the server will return
   an error if chaining is not supported.
 
***************************************************************************/
#ifdef _WINDOWS
#include <winsock2.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#ifndef _WINDOWS
#include <stdint.h>
#endif
#include <string.h>
#include <ldap.h>
#if defined(N_PLAT_NLM) && defined(LIBC)
#include <screen.h>
#endif

static char usage[] =
"\n Usage:   dyngroup <host name> <port number> <login dn> <password>"
"\n          <container name> <queryURL> \n" 
"\n Example: dyngroup Acme.com 389 cn=admin,o=Acme secret"
"\n          o=Acme ldap:///ou=Sales,o=Acme??sub?(title=*Manager*)\n";

int main( int argc, char **argv)
{
    int         version, ldapPort, i, rc; 
    char        *ldapHost, *loginDN, *password, *containerName, *queryURL;
    char        *vclass[2], *vURL[2], *vdgIdentity[2], *requiredAttributes[2];
    char        *attribute, **values, *dn=NULL;
    BerElement  *ber;
    LDAP        *ld;
    LDAPMod     modClass, modMemberQuery, modDgIdentity; 
    LDAPMod     *mods[4];
    LDAPMessage *searchResult=NULL, *entry;
    
    /* Since reading members of a dynamic group could potentially involve
     * a significant directory search, we use a timeout.
     */
    struct timeval timeOut = {10,0};   /* 10 second connection/search timeout */

    /* Name of the new dynamic group object to create. (RDN)
     * Includes a trailing comma.
     */
    char        *newGroupRDN = "cn=myDynamicGroup,";

    #if defined(N_PLAT_NLM) && defined(LIBC)
    setscreenmode(SCR_NO_MODE);              /* Don't clear screen on exit */
    #endif
    
    if (argc != 7)
    {
        printf("%s", usage);
        return(1);
    }

    ldapHost        =   argv[1];
    ldapPort        =   atoi(argv[2]);
    loginDN         =   argv[3];
    password        =   argv[4];
    containerName   =   argv[5];
    queryURL        =   argv[6];

    /* Set LDAP version to 3 and set connection timeout. */
    version = LDAP_VERSION3;
    ldap_set_option( NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
    ldap_set_option( NULL, LDAP_OPT_NETWORK_TIMEOUT, &timeOut);

    /* Initialize the LDAP session */
    if (( ld = (LDAP *)(intptr_t)ldap_init( ldapHost, ldapPort )) == NULL)
    {
        printf("\n   LDAP session initialization failed\n");
        return( 1 );
    }
    printf ( "\n   LDAP session initialized\n");

    /* Bind to the server */
    rc = ldap_simple_bind_s( ld, loginDN, password );
    if (rc != LDAP_SUCCESS )
    {
        printf("ldap_simple_bind_s: %s\n", ldap_err2string( rc ));
        ldap_unbind_s ( ld );
        return( 1 );
    }
    printf("\n   Bind successful\n");

    /*
     * To add an entry to the directory:
     *   1. Create the array of LDAPMod structures representing the
     *      attributes of the new entry 
     *   2. Specify the DN of the entry to create
     *   3. Call ldap_add_ext_s to add the entry to the directory
     */      

    /* The objectclass "dynamicGroup" is used to create dynamic group objects */
    modClass.mod_op               =   LDAP_MOD_ADD;
    modClass.mod_type             =   "objectClass";
    vclass[0]                     =   "dynamicGroup";
    vclass[1]                     =   NULL;
    modClass.mod_values           =   vclass;
    
    /* The memberQuery attribute describes the membership of the list using
     * an LDAPURL, which is defined in RFC2255. 
     */
    modMemberQuery.mod_op      =   LDAP_MOD_ADD;
    modMemberQuery.mod_type    =   "memberQuery";
    vURL[0]                       =   queryURL;
    vURL[1]                       =   NULL;
    modMemberQuery.mod_values  =   vURL;


    /* Set the identity to use for the implied search.  
     * loginDN is used as the dgIdentity in this sample.
     */
    modDgIdentity.mod_op          =   LDAP_MOD_ADD;
    modDgIdentity.mod_type        =   "dgIdentity";
    vdgIdentity[0]                =   loginDN;
    vdgIdentity[1]                =   NULL;
    modDgIdentity.mod_values      =   vdgIdentity;

    mods[0] = &modClass;
    mods[1] = &modMemberQuery;
    mods[2] = &modDgIdentity;
    mods[3] = NULL;

    /*
     * Construct the object's dn using the container name from the
     * command line
     */
    dn=(char*)malloc(strlen(newGroupRDN) + strlen(containerName) + 1);
    strcpy( dn, newGroupRDN );
    strcat( dn, containerName);

    /*
     * Now add the object
     */
    rc = ldap_add_ext_s( ld,     /* LDAP session handle */ 
                         dn,     /* dn of the object to be created */
                         mods,   /* array of add options and values */
                         NULL,   /* server control */
                         NULL ); /* client control */

    if ( rc != LDAP_SUCCESS )
    {
        printf("\n   ldap_add_ext_s: %s\n", ldap_err2string( rc ));
        if (rc == LDAP_UNDEFINED_TYPE)
            printf("      Dynamic Groups require eDirectory 8.6.1 or later.\n");
        free( dn );
        ldap_unbind_s ( ld );
        return(1);
    }
    
    printf("\n   Dynamic Group entry successfully added: %s\n", dn);

    printf("\n   Reading the member attribute of dynamic group object:\n");

   /* The "member" attribute is used to interrogate both the static and dynamic
    * member values of a dynamic group object. 
    * 
    * Querying for "member" attribute.
    */
    requiredAttributes[0]="member";
    requiredAttributes[1]=NULL;

   /* Search the directory */
    rc = ldap_search_ext_s(
                    ld,                    /* LDAP session handle */
                    dn,                    /* entry to read */
                    LDAP_SCOPE_BASE,       /* search scope */
                    "(objectclass=*)",     /* search filter */
                    requiredAttributes,    /* attributes to return */
                    0,                     /* return attributes and values */
                    NULL,                  /* server controls */
                    NULL,                  /* client controls */
                    &timeOut,              /* search timeout */
                    LDAP_NO_LIMIT,         /* no size limit */
                    &searchResult );       /* returned results */

    if ( rc != LDAP_SUCCESS )
    {
        printf("ldap_search_ext_s: %s\n", ldap_err2string( rc ));
        free( dn );
        ldap_msgfree( searchResult );
        ldap_unbind_s( ld );
        return ( 1 );
    }

    /* Read and print search results.  We expect only one entry and attr. */
    if ( (entry = ldap_first_entry( ld, searchResult )) != NULL)
    {
        if ( (attribute = ldap_first_attribute( ld, entry, &ber )) != NULL)
        {
            /* Get values and print. */
            values = (char **)(intptr_t)ldap_get_values( ld, entry, attribute);
            if (values != NULL)                
            {
                for ( i = 0; values[i] != NULL; i++ )
                    printf("        %s: %s\n", attribute, values[i] );
                ldap_value_free( values );
            }
            ldap_memfree( attribute );
        }
		else
		{
		    printf("     No objects passed the memberQuery filter.\n"
                   "     Hence, there are no members in this group.\n");
		}
        ber_free(ber, 0);
    }
    ldap_msgfree( searchResult );

    /* Delete the dynamic group */
    rc = ldap_delete_ext_s( ld,         /* LDAP session handle */
                            dn,         /* dn of the object to delete */
                            NULL,       /* server controls */
                            NULL );     /* client controls */

    if ( rc != LDAP_SUCCESS )
        printf("\n   ldap_delete_ext_s: %s\n", ldap_err2string( rc ));
    else
        printf("\n   Dynamic Group entry successfully deleted: %s\n\n", dn);

    free ( dn );
    ldap_unbind_s( ld );

    return( rc );
}

