/* -*- C -*- 
 *
 *
 *  2010 Hewlett-Packard Development Company, L.P.
 *
 * Confidential computer software. Valid license from HP and/or its
 * subsidiaries required for possession, use, or copying.
 *
 * Consistent with FAR 12.211 and 12.212, Commercial Computer Software,
 * Computer Software Documentation, and Technical Data for Commercial Items
 * are licensed to the U.S. Government under vendor's standard commercial
 * license.
 *
 * Neither HP nor any of its subsidiaries shall be liable for technical or
 * editorial errors or omissions contained herein. The information in this
 * document is provided "as is" without warranty of any kind and is subject to
 * change without notice. The warranties for HP products are set forth in the
 * express limited warranty statements accompanying such products. Nothing
 * herein should be construed as constituting an additional warranty.
 *
 * 
 */

/*
 * Compaq revision history
 *
 * Rev    Author         Date         Comments
 * ---    ------         ----         --------
 * 001    Nick Hudson    08-Sep-2000  Initial version
 *
 * 002	  Rupesh S	 19-Mar-2010  Added more configurable parameters that can be passed.
 *					- port
 *					- bind_dn
 *					- bind_password
 *					- port_security
 *					- ca_file
 *					- base_dn
 */

/* 
 * This program demonstrates the use of the OpenVMS LDAP API from a client
 * application.
 *
 * The program may be compiled using either C or C++.
 *
 * The program may be compiled with either 32 or 64 bit pointers (providing
 * that the compiler supports it).
 *
 * To build this program use:
 * $ cc ldap_example
 * $ link ldap_example
 *
 * The program expects to run as a foreign command.  To define the foreign
 * command use the following syntax:
 *
 * $ ldap_example := $disk1:[mydir]ldap_example.exe ! define foreign command
 *
 * The program expects the following arguments:
 *
 *   server         The node which is providing LDAP access to a directory
 *
 *   port    	    The LDAP directory server port to connect to.
 *		    A default port of 389 is used if not provided.
 *
 *   bind_dn	    The bind dn, enclose in double quotes. Use a blank string
 *		    if LDAP directory server supports anonymous bind.
 *
 *   bind_password  The bind password. Use a blank string if LDAP directory
 *		    server supports anonymous bind.
 *
 *   port_security  The port security "SSL" or "TLS". Use a blank string
 *		    if no port security is required.
 *
 *   cafile 	    The location of the ca file. Use a blank string if no
 *		    CA file is required.
 *
 *   base_dn        The base object in the directory for the search operation.
 *		    This is a required field.
 *
 *   filter         The search filter to be used
 *
 *   attributes     An optional list of one or more attributes to be returned
 *                  for each matching record.  If no attributes are specified,
 *                  then all user attributes will be returned.
 *
 * Given the parameters above, the program will attempt to make contact
 * with an LDAP server on node "server", and request a search for all
 * records below the object "o=acme, c=us" that match the filter "sn=s*".
 * For each matching record, the attributes "cn" and "sn" will be displayed.
 */

#include <ldap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int setup_tls (LDAP *ldapContext, int port_security,char *ca_file);
 
/*
 * Generic routine to display an error message and return any 
 * supplementary information from the LDAP handle
 */
void report_error(const char *operation, int result, LDAP *ld)
{
  int    stat;
  char   *errmsgp = NULL;
  
  printf("%s returned error %d (\"%s\")\n",operation,
         result, ldap_err2string(result));
  

  stat = ldap_get_option(ld,LDAP_OPT_ERROR_STRING,&errmsgp);

  if ((stat == -1) || (strlen(errmsgp) == 0)) {
    printf("No supplementary error text available\n");
  }
  else {
    printf("Error text : \"%s\"\n",errmsgp);
  }
  
  if (errmsgp != NULL) {
    ldap_memfree(errmsgp);
    errmsgp = NULL;
  }
  

}

void main(int argc, char *argv[])
{
  int         stat;
  int         i;
  int         num_entries;
  char        **attrs_array = NULL;
  int         attribute_count;
  int 	      port_num, ssl_sec=0;
  /*
   * For servers which don't support version 3 of the protocol, edit
   * the line below to use LDAP_VERSION2.  Both of these constants
   * are defined inside LDAP.H
   */
  int         protocol = LDAP_VERSION3;
  
  /*
   * The following pointers may be allocated by the LDAP API, and 
   * should be free'd using an appropriate mechanism
   */
  LDAP        *ld = NULL;
  LDAPMessage *result = NULL;
  LDAPMessage *one_entry = NULL;
  char        *dn = NULL;
  char        *attr_name = NULL;
  char        **values = NULL;
  BerElement  *ber = NULL;
  
  if (argc < 9) {
    printf("Usage:ldap_example server port bind_dn bind_password port_security cafile base_dn filter [attributes]\n\n");
    printf("Mandatory arguments : For specifying NULL values use \"\"\n");
    printf("server        --> The node which is providing LDAP access to a directory\n");
    printf("port          --> The port through which to search\n");
    printf("bind_dn       --> The bind dn, enclose in double quotes. Specify a \"\" if\n");
    printf("                  anonymous bind is supported by LDAP directory server.\n");
    printf("bind_password --> The bind password. Specify a \"\" if anonymous bind\n");
    printf("                  is supported by LDAP directory server.\n");
    printf("port_security --> The port security \"SSL\" or \"TLS\". Specify a \"\" if\n");
    printf("                  you are not using any port security.\n");
    printf("cafile        --> The location of the ca file. Specify a \"\" if ca file is\n");
    printf("                   not present.\n");
    printf("base_dn       --> The base object in the directory for the search operation.\n");
    printf("                  This is a required argument.\n");
    printf("filter        --> The search filter to be used. Specify a \"\" if the LDAP\n");
    printf("                  search needs to be done without filters.\n");
    printf("\nOptional arguments : \n");
    printf("attributes    --> An optional list of one or more attributes to be returned\n");
    printf("                  for each matching record.  If no attributes are specified,\n");
    printf("                  then all user attributes will be returned.\n");
    printf("Example : \n");
    printf ("\n $ ldap_example server1 389 \"\" \"\" \"\" \"\" \"ou=vms,o=testcom\" \"\"");
    printf ("\n $ ldap_example server1 389 \"cn=admin,ou=vms,o=testcom\" \"WELCOME123\" \"\" \"\" -");
    printf ("\n \"ou=vms,o=testcom\" \"\"");
    printf ("\n $ ldap_example server1 389 \"cn=admin,ou=vms,o=testcom\" \"WELCOME123\" \"\" \"\" -");
    printf ("\n \"ou=vms,o=testcom\" \"\" \"DN\"");
    printf ("\n $ ldap_example server1 389 \"cn=admin,ou=vms,o=testcom\" \"WELCOME123\" \"\" \"\" -");
    printf ("\n \"ou=vms,o=testcom\" \"\" \"DN\" \"SN\"");
    printf ("\n $ ldap_example server2 389 -");
    printf ("\n \"CN=query_account,CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"welcome@123\" \"\" \"\" \"CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"\" \"samaccountname\"");
    printf ("\n $ ldap_example server2 636 -");
    printf ("\n \"CN=query_account,CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"welcome@123\" \"SSL\" \"\" \"CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"\" \"samaccountname\"");
    printf ("\n $ ldap_example server2 389 \"CN=query_account,CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"welcome@123\" \"starttls\" \"\" \"CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"\" \"samaccountname\"");
    printf ("\n $ ldap_example server2 636 \"CN=query_account,CN=Users,DC=testdomain,DC=testcom,DC=com\" -");
    printf ("\n \"welcome@123\" \"SSL\" \"SYS$SYSROOT:[SYSMGR]server2.CER\" -");
    printf ("\n \"CN=Users,DC=testdomain,DC=testcom,DC=com\" \"\" \"samaccountname\"\n");
    goto finished;
  }
  
  /*
   * If the user requested any particular attributes, then build up
   * an array to pass to the search routine.  If no attributes are
   * specified, then "attrs_array" will be NULL, and the server should 
   * return all user attributes for each matching entry
   */
  attribute_count = (argc - 9);
  if (attribute_count != 0) {
    i = 0;

    /*
     * Allocate enough room for a pointer to each attribute, plus
     * a NULL terminating pointer. 
     */
    attrs_array = (char **)calloc((attribute_count + 1),sizeof(char *));
    while (i < attribute_count) {
      attrs_array[i] = strdup(argv[i+9]);
      i++;
    }
  }
  
    
  /*
   * Establish a context with the library specifying an LDAP server name
   * and using the port provided , if no port is provided use default LDAP port number.
   * No connection is made with the server at this stage, so a failure here
   * would indicate a problem with the library, rather than a network or
   * server related issue.
   */
  if ((argv[2]== NULL) || (strcmp(argv[2], "") == 0))
      port_num=LDAP_PORT;
  else
      port_num=atoi(argv[2]);
  ld = ldap_init(argv[1],port_num );
  if (ld == NULL) {
    printf("ldap_init failed\n");
    goto finished;
  }
  

  /*
   * Having been provided with a LDAP context, it is possible now to
   * specify options for this session
   */
  stat = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol);
  if (stat != LDAP_SUCCESS) {
    report_error("ldap_set_option",stat,ld);
    goto finished;
  }
  /*
   * Secure the communication channel unless the user has explicitly disabled security.
   */
  if ((argv[5] != NULL) && (strcmp(argv[5], "") != 0))
  {
    /*
     * Setup port security to SSL or TLS
     */
    if ( (strcmp("SSL",argv[5])== 0) || (strcmp("ssl",argv[5])== 0) )
      ssl_sec = 1;
    stat = setup_tls( ld,ssl_sec,argv[6]);
    if (stat != LDAP_SUCCESS) 
    {
       report_error("setup_tls",stat,ld);
       goto finished;
    }
  }
  /*
   * Execute a simple bind, with the base dn and password.
   */
  stat = ldap_simple_bind_s(ld,argv[3],argv[4]);
  if (stat != LDAP_SUCCESS) {
    report_error("simple_bind",stat,ld);
    goto finished;
  }
  

  /*
   * Issue a synchronous search request; this call will not return 
   * until the server has responded.
   */
  stat = ldap_search_s(ld,                  /* ld */
                       argv[7],             /* base */
                       LDAP_SCOPE_SUBTREE,  /* scope */
                       argv[8],             /* filter */
                       attrs_array,         /* attrs */
                       0,                   /* attrsonly */
                       &result);            /* res */
  

  if (stat != LDAP_SUCCESS) {
    report_error("ldap_search_s",stat,ld);
    goto finished;
  }

  num_entries = ldap_count_entries(ld,result);

  if (num_entries == 0) {
    printf("No matching entries found\n");
    goto finished;
  }

  printf("Number of entries returned : %d\n",num_entries);
  
  /*
   * Now look at the results that came back
   */
  one_entry = ldap_first_entry(ld,result);

  while (one_entry != NULL) {

    /*
     * The distinguished name of this entry
     */
    dn = ldap_get_dn(ld,one_entry);

    if (dn != NULL) {
      printf("\ndn = %s\n",ldap_get_dn(ld,one_entry));
      ldap_memfree(dn);
      dn = NULL;

      /*
       * Step through each attribute
       */
      attr_name = ldap_first_attribute(ld,one_entry,&ber);
      if (attr_name == NULL) {
        printf("<No attributes returned>\n");
      }

      /*
       * Extract the values for each attribute returned.  This program
       * assumes that such values will be "printable" value strings.  For
       * non-printable (binary) data, ldap_get_values_len() should
       * be used.
       */
      while (attr_name != NULL) {
        values = ldap_get_values(ld,one_entry,attr_name);
        if (values == NULL) {
          printf("<no values returned>\n");
        }
        else {    
          i = 0;
          while (values[i] != NULL) {
            printf("%s : %s\n",attr_name,values[i]);
            i++;
          }
        }
        
        ldap_memfree(attr_name);
        attr_name = NULL;
        
        ldap_value_free(values);
        values = NULL;
        
          
        attr_name = ldap_next_attribute(ld,one_entry,ber);
      }

      /*
       * The BerElement pointer is no longer needed and so can be
       * released.  Since this pointer was just being used as an
       * iterator and doesn't point to any memory that the program
       * has allocated itself, use zero as the second parameter to 
       * ber_free().
       */
      if (ber != NULL) {
        ber_free(ber, 0);
        ber = NULL;
      }
        
    }
    else {
      report_error("ldap_get_dn",0,ld);
    }
    
    one_entry = ldap_next_entry(ld,one_entry);
  }

  /*
   * All exit paths should come through here, where free up any data
   * structures that the library has allocated using the appropriate
   * functions.
   * It is a good habit to set pointers to NULL after releasing them,
   * although in the cases below it isn't strictly necessary.
   */
finished:

  /*
   * "dn" may have been allocated by ldap_get_dn()
   */
  if (dn != NULL) {
    ldap_memfree(dn);
    dn = NULL;
  }

  /*
   * "attr_name" may have been allocated by ldap_first_attribute() or
   * ldap_next_attribute()
   */
  if (attr_name != NULL) {
    ldap_memfree(attr_name);
    attr_name = NULL;
  }
  
  /*
   * "values" may have been allocated by ldap_get_values()
   */
  if (values != NULL) {
    ldap_value_free(values);
    values = NULL;
  }
  
  /*
   * "ber" may have been allocated by ldap_first_attribute() or
   * ldap_next_attribute()
   * Since this pointer was just being used as an iterator and
   * doesn't point to any memory that the program has allocated
   * itself, use zero as the second parameter to ber_free()
   */
  if (ber != NULL) {
    ber_free(ber, 0);
    ber = NULL;
  }
      
  /*
   * "one_entry" may have been allocated by ldap_first_entry() or
   * ldap_next_entry()
   */
  if (one_entry != NULL) {
    ldap_msgfree(one_entry);
    one_entry = NULL;
  }  

  /*
   * "result" may have been allocated by ldap_search_s()
   */
  if (result != NULL) {
    ldap_msgfree(result);
    result = NULL;
  }  

  /*
   * "ld" was returned from ldap_init()
   */
  if (ld != NULL) {
    ldap_unbind(ld);
    ld = NULL;
  }
  
  /*
   * "attrs_array" was allocated by this program
   */
  if (attrs_array != NULL) {
    i = 0;
    while (attrs_array[i] != NULL) {
      free(attrs_array[i]);
      attrs_array[i] = NULL;
      i++;
    }
    free(attrs_array);
    attrs_array = NULL;
  }

  printf("\nProgram terminating\n");

}
/*
** 
*/
int setup_tls(LDAP *ldapContext,  int port_security, char *ca_file)
{
	int	status;
	void    *ldapTLSOption_on = LDAP_OPT_ON;
	int	StartTLS;

 		/*
		 * Determine StartTLS method: SSL socket (0) or StartTLS negotiation over
		 * standard socket (1).
		 */
		if (port_security == 1)
			StartTLS = 0;
		else
			StartTLS = 1;

		/*
		 * If the user specified a CA certificate file, then use it to
		 * verify the server's certificate. We need to do this every time we connect
		 * because the server could change beneath us at any time -- so be paranoid.
		 */
		if (strcmp(ca_file,"")!=0)
		{
			if (ldap_set_option(ldapContext, LDAP_OPT_TLS_VERIFY_REQUIRED, &ldapTLSOption_on) != LDAP_SUCCESS)
				return -1;
			if (ldap_set_option(ldapContext, LDAP_OPT_TLS_CA_FILE, ca_file) != LDAP_SUCCESS)
				return -1;
		}
 
		status = ldap_tls_start(ldapContext, StartTLS);
		if (status != LDAP_SUCCESS)
			return -1;
 
	return LDAP_SUCCESS;
}
