/*
 * Copyright (c) 2003-2016
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/* ldif.c - routines for dealing with LDIF files */

/*
 * Originally borrowed from OpenLDAP and adopted for use in DACS because
 * 1) local_ldap_auth.c needed ldif_put(), which wasn't publicly exported
 * by OpenLDAP, and 2) to avoid potentially linking a pile of OpenLDAP code
 * that wasn't needed by DACS.  Because these reasons aren't as valid now,
 * let's just deprecate this code and use OpenLDAP.
 *
 * Copyright 2009 Dembach Goo Informatik
 *                Bernd Zeimetz <bze@dg-i.net>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
*/

/* $OpenLDAP: pkg/ldap/libraries/liblutil/ldif.c,v 1.15.2.7 2009/01/22 00:00:58 kurt Exp $ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 1998-2009 The OpenLDAP Foundation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* Portions Copyright (c) 1992-1996 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.  This
 * software is provided ``as is'' without express or implied warranty.
 */
/* This work was originally developed by the University of Michigan
 * and distributed as part of U-MICH LDAP.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2016\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: dacs_ldif.c 2927 2016-11-16 21:42:10Z brachman $";
#endif

#include "log.h"

#include <stdio.h>
#include <assert.h>

#include "dacs_ldif.h"

static const char *log_module_name = "ldif";

#define LDAP_DEBUG_ANY		-1

#ifdef CSRIMALLOC
#define ber_memalloc malloc
#define ber_memcalloc calloc
#define ber_memrealloc realloc
#define ber_strdup strdup
#endif

static const char nib2b64[0x40] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/*
 * name and OID of attributeTypes that must be base64 encoded in any case
 */
typedef struct must_b64_encode_s {
  struct berval	name;
  struct berval	oid;
} must_b64_encode_s;

#define ber_bvstrcasecmp(v1,v2)		 \
  ((v1)->bv_len < (v2)->bv_len						\
   ? -1 : ((v1)->bv_len > (v2)->bv_len									\
		   ? 1 : strncasecmp((v1)->bv_val, (v2)->bv_val, (v1)->bv_len) ))

#define STRLENOF(s)		(sizeof(s)-1)
#define BER_BVISNULL(bv)        ((bv)->bv_val == NULL)
#define BER_BVNULL              { 0L, NULL }
#define BER_BVC(s)              { STRLENOF(s), (char *)(s) }
static must_b64_encode_s	default_must_b64_encode[] = {
  { BER_BVC( "userPassword" ), BER_BVC( "2.5.4.35" ) },
  { BER_BVNULL, BER_BVNULL }
};

static must_b64_encode_s	*must_b64_encode = default_must_b64_encode;


/*
 * returns 1 iff the string corresponds to the name or the OID of any 
 * of the attributeTypes listed in must_b64_encode
 */
static int
ldif_must_b64_encode( LDAP_CONST char *s )
{
  int		i;
  struct berval	bv;

  assert( must_b64_encode != NULL );
  assert( s != NULL );

  ber_str2bv( s, 0, 0, &bv );

  for ( i = 0; !BER_BVISNULL( &must_b64_encode[i].name ); i++ ) {
	if ( ber_bvstrcasecmp( &must_b64_encode[i].name, &bv ) == 0
		 || ber_bvcmp( &must_b64_encode[i].oid, &bv ) == 0 )
	  {
		return 1;
	  }
  }

  return 0;
}

/* compatibility with U-Mich off by one bug */
#define LDIF_KLUDGE 1

void
ldif_sput(
		  char **out,
		  int type,
		  LDAP_CONST char *name,
		  LDAP_CONST char *val,
		  ber_len_t vlen )
{
  const unsigned char *byte, *stop;
  unsigned char	buf[3];
  unsigned long	bits;
  char		*save;
  int		pad;
  int		namelen = 0;

  ber_len_t savelen;
  ber_len_t len=0;
  ber_len_t i;

  /* prefix */
  switch( type ) {
  case LDIF_PUT_COMMENT:
	*(*out)++ = '#';
	len++;

	if( vlen ) {
	  *(*out)++ = ' ';
	  len++;
	}

	break;

  case LDIF_PUT_SEP:
	*(*out)++ = '\n';
	return;
  }

  /* name (attribute type) */
  if( name != NULL ) {
	/* put the name + ":" */
	namelen = strlen(name);
	strcpy(*out, name);
	*out += namelen;
	len += namelen;

	if( type != LDIF_PUT_COMMENT ) {
	  *(*out)++ = ':';
	  len++;
	}

  }
#ifdef LDAP_DEBUG
  else {
	assert( type == LDIF_PUT_COMMENT );
  }
#endif

  if( vlen == 0 ) {
	*(*out)++ = '\n';
	return;
  }

  switch( type ) {
  case LDIF_PUT_NOVALUE:
	*(*out)++ = '\n';
	return;

  case LDIF_PUT_URL: /* url value */
	*(*out)++ = '<';
	len++;
	break;

  case LDIF_PUT_B64: /* base64 value */
	*(*out)++ = ':';
	len++;
	break;
  }

  switch( type ) {
  case LDIF_PUT_TEXT:
  case LDIF_PUT_URL:
  case LDIF_PUT_B64:
	*(*out)++ = ' ';
	len++;
	/* fall-thru */

  case LDIF_PUT_COMMENT:
	/* pre-encoded names */
	for ( i=0; i < vlen; i++ ) {
	  if ( len > LDIF_LINE_WIDTH ) {
		*(*out)++ = '\n';
		*(*out)++ = ' ';
		len = 1;
	  }

	  *(*out)++ = val[i];
	  len++;
	}
	*(*out)++ = '\n';
	return;
  }

  save = *out;
  savelen = len;

  *(*out)++ = ' ';
  len++;

  stop = (const unsigned char *) (val + vlen);

  if ( type == LDIF_PUT_VALUE
	   && isgraph( (unsigned char) val[0] ) && val[0] != ':' && val[0] != '<'
	   && isgraph( (unsigned char) val[vlen-1] )
#ifndef LDAP_BINARY_DEBUG
	   && strstr( name, ";binary" ) == NULL
#endif
#ifndef LDAP_PASSWD_DEBUG
	   && !ldif_must_b64_encode( name )
#endif
	   ) {
	int b64 = 0;

	for ( byte = (const unsigned char *) val; byte < stop;
		  byte++, len++ )
	  {
		if ( !isascii( *byte ) || !isprint( *byte ) ) {
		  b64 = 1;
		  break;
		}
		if ( len > LDIF_LINE_WIDTH+LDIF_KLUDGE ) {
		  *(*out)++ = '\n';
		  *(*out)++ = ' ';
		  len = 1;
		}
		*(*out)++ = *byte;
	  }

	if( !b64 ) {
	  *(*out)++ = '\n';
	  return;
	}
  }

  *out = save;
  *(*out)++ = ':';
  *(*out)++ = ' ';
  len = savelen + 2;

  /* convert to base 64 (3 bytes => 4 base 64 digits) */
  for ( byte = (const unsigned char *) val;
		byte < stop - 2;
	    byte += 3 )
	{
	  bits = (byte[0] & 0xff) << 16;
	  bits |= (byte[1] & 0xff) << 8;
	  bits |= (byte[2] & 0xff);

	  for ( i = 0; i < 4; i++, len++, bits <<= 6 ) {
		if ( len > LDIF_LINE_WIDTH+LDIF_KLUDGE ) {
		  *(*out)++ = '\n';
		  *(*out)++ = ' ';
		  len = 1;
		}

		/* get b64 digit from high order 6 bits */
		*(*out)++ = nib2b64[ (bits & 0xfc0000L) >> 18 ];
	  }
	}

  /* add padding if necessary */
  if ( byte < stop ) {
	for ( i = 0; byte + i < stop; i++ ) {
	  buf[i] = byte[i];
	}
	for ( pad = 0; i < 3; i++, pad++ ) {
	  buf[i] = '\0';
	}
	byte = buf;
	bits = (byte[0] & 0xff) << 16;
	bits |= (byte[1] & 0xff) << 8;
	bits |= (byte[2] & 0xff);

	for ( i = 0; i < 4; i++, len++, bits <<= 6 ) {
	  if ( len > LDIF_LINE_WIDTH+LDIF_KLUDGE ) {
		*(*out)++ = '\n';
		*(*out)++ = ' ';
		len = 1;
	  }

	  if( i + pad < 4 ) {
		/* get b64 digit from low order 6 bits */
		*(*out)++ = nib2b64[ (bits & 0xfc0000L) >> 18 ];
	  } else {
		*(*out)++ = '=';
	  }
	}
  }
  *(*out)++ = '\n';
}


/*
 * ldif_type_and_value return BER malloc'd, zero-terminated LDIF line
 */
char *
ldif_put(
		 int type,
		 LDAP_CONST char *name,
		 LDAP_CONST char *val,
		 ber_len_t vlen )
{
  char	*buf, *p;
  ber_len_t nlen;

  nlen = ( name != NULL ) ? strlen( name ) : 0;

  buf = (char *) ber_memalloc( LDIF_SIZE_NEEDED( nlen, vlen ) + 1 );

  if ( buf == NULL ) {
	log_msg((LOG_ERROR_LEVEL,
			 "ldif_put: ber_memalloc failed!"));
	return NULL;
  }

  p = buf;
  ldif_sput( &p, type, name, val, vlen );
  *p = '\0';

  return( buf );
}

