/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 *
 ***************************************************************************/

/* OS/400 additional support. */

#include <curl/curl.h>
#include "config-os400.h"  /* Not curl_setup.h: we only need some defines. */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <pthread.h>
#include <netdb.h>
#include <qadrt.h>
#include <errno.h>

#ifdef HAVE_LIBZ
#include <zlib.h>
#endif

#ifdef USE_GSKIT
#include <gskssl.h>
#include <qsoasync.h>
#endif

#ifdef HAVE_GSSAPI
#include <gssapi.h>
#endif

#ifndef CURL_DISABLE_LDAP
#include <ldap.h>
#endif

#include <netinet/in.h>
#include <arpa/inet.h>

#include "os400sys.h"

/**
*** QADRT OS/400 ASCII runtime defines only the most used procedures, but a
*** lot of them are not supported. This module implements ASCII wrappers for
*** those that are used by libcurl, but not defined by QADRT.
**/

#pragma convert(0)                              /* Restore EBCDIC. */

#define MIN_BYTE_GAIN   1024    /* Minimum gain when shortening a buffer. */

struct buffer_t {
  unsigned long size;            /* Buffer size. */
  char *buf;                     /* Buffer address. */
};


static char *buffer_undef(localkey_t key, long size);
static char *buffer_threaded(localkey_t key, long size);
static char *buffer_unthreaded(localkey_t key, long size);

static pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_key_t    thdkey;
static struct buffer_t *locbufs;

char *(*Curl_thread_buffer)(localkey_t key, long size) = buffer_undef;

static void thdbufdestroy(void *private)
{
  if(private) {
    struct buffer_t *p = (struct buffer_t *) private;
    localkey_t i;

    for(i = (localkey_t) 0; i < LK_LAST; i++) {
      free(p->buf);
      p++;
    }

    free(private);
  }
}


static void
terminate(void)
{
  if(Curl_thread_buffer == buffer_threaded) {
    locbufs = pthread_getspecific(thdkey);
    pthread_setspecific(thdkey, (void *) NULL);
    pthread_key_delete(thdkey);
  }

  if(Curl_thread_buffer != buffer_undef) {
    thdbufdestroy((void *) locbufs);
    locbufs = (struct buffer_t *) NULL;
  }

  Curl_thread_buffer = buffer_undef;
}


static char *
get_buffer(struct buffer_t *buf, long size)
{
  char *cp;

  /* If `size' >= 0, make sure buffer at `buf' is at least `size'-byte long.
     Return the buffer address. */

  if(size < 0)
    return buf->buf;

  if(!buf->buf) {
    buf->buf = malloc(size);
    if(buf->buf)
      buf->size = size;

    return buf->buf;
  }

  if((unsigned long) size <= buf->size) {
    /* Shorten the buffer only if it frees a significant byte count. This
       avoids some realloc() overhead. */

    if(buf->size - size < MIN_BYTE_GAIN)
      return buf->buf;
  }

  /* Resize the buffer. */

  cp = realloc(buf->buf, size);
  if(cp) {
    buf->buf = cp;
    buf->size = size;
  }
  else if(size <= buf->size)
    cp = buf->buf;

  return cp;
}


static char *
buffer_unthreaded(localkey_t key, long size)
{
  return get_buffer(locbufs + key, size);
}


static char *
buffer_threaded(localkey_t key, long size)
{
  struct buffer_t *bufs;

  /* Get the buffer for the given local key in the current thread, and
     make sure it is at least `size'-byte long. Set `size' to < 0 to get
     its address only. */

  bufs = (struct buffer_t *) pthread_getspecific(thdkey);

  if(!bufs) {
    if(size < 0)
      return (char *) NULL;             /* No buffer yet. */

    /* Allocate buffer descriptors for the current thread. */

    bufs = calloc((size_t) LK_LAST, sizeof(*bufs));
    if(!bufs)
      return (char *) NULL;

    if(pthread_setspecific(thdkey, (void *) bufs)) {
      free(bufs);
      return (char *) NULL;
    }
  }

  return get_buffer(bufs + key, size);
}


static char *
buffer_undef(localkey_t key, long size)
{
  /* Define the buffer system, get the buffer for the given local key in
     the current thread, and make sure it is at least `size'-byte long.
     Set `size' to < 0 to get its address only. */

  pthread_mutex_lock(&mutex);

  /* Determine if we can use pthread-specific data. */

  if(Curl_thread_buffer == buffer_undef) {      /* If unchanged during lock. */
    if(!pthread_key_create(&thdkey, thdbufdestroy))
      Curl_thread_buffer = buffer_threaded;
    else {
      locbufs = calloc((size_t) LK_LAST, sizeof(*locbufs));
      if(!locbufs) {
        pthread_mutex_unlock(&mutex);
        return (char *) NULL;
      }
      else
        Curl_thread_buffer = buffer_unthreaded;
    }

    atexit(terminate);
  }

  pthread_mutex_unlock(&mutex);
  return Curl_thread_buffer(key, size);
}


static char *
set_thread_string(localkey_t key, const char *s)
{
  int i;
  char *cp;

  if(!s)
    return (char *) NULL;

  i = strlen(s) + 1;
  cp = Curl_thread_buffer(key, MAX_CONV_EXPANSION * i + 1);

  if(cp) {
    i = QadrtConvertE2A(cp, s, MAX_CONV_EXPANSION * i, i);
    cp[i] = '\0';
  }

  return cp;
}


int
Curl_getnameinfo_a(const struct sockaddr *sa, curl_socklen_t salen,
                   char *nodename, curl_socklen_t nodenamelen,
                   char *servname, curl_socklen_t servnamelen,
                   int flags)
{
  char *enodename = NULL;
  char *eservname = NULL;
  int status;

  if(nodename && nodenamelen) {
    enodename = malloc(nodenamelen);
    if(!enodename)
      return EAI_MEMORY;
  }

  if(servname && servnamelen) {
    eservname = malloc(servnamelen);
    if(!eservname) {
      free(enodename);
      return EAI_MEMORY;
    }
  }

  status = getnameinfo(sa, salen, enodename, nodenamelen,
                       eservname, servnamelen, flags);

  if(!status) {
    int i;
    if(enodename) {
      i = QadrtConvertE2A(nodename, enodename,
                          nodenamelen - 1, strlen(enodename));
      nodename[i] = '\0';
    }

    if(eservname) {
      i = QadrtConvertE2A(servname, eservname,
                          servnamelen - 1, strlen(eservname));
      servname[i] = '\0';
    }
  }

  free(enodename);
  free(eservname);
  return status;
}

int
Curl_getaddrinfo_a(const char *nodename, const char *servname,
                   const struct addrinfo *hints,
                   struct addrinfo **res)
{
  char *enodename;
  char *eservname;
  int status;
  int i;

  enodename = (char *) NULL;
  eservname = (char *) NULL;

  if(nodename) {
    i = strlen(nodename);

    enodename = malloc(i + 1);
    if(!enodename)
      return EAI_MEMORY;

    i = QadrtConvertA2E(enodename, nodename, i, i);
    enodename[i] = '\0';
  }

  if(servname) {
    i = strlen(servname);

    eservname = malloc(i + 1);
    if(!eservname) {
      free(enodename);
      return EAI_MEMORY;
    }

    QadrtConvertA2E(eservname, servname, i, i);
    eservname[i] = '\0';
  }

  status = getaddrinfo(enodename, eservname, hints, res);
  free(enodename);
  free(eservname);
  return status;
}

#ifdef USE_GSKIT

/* ASCII wrappers for the GSKit procedures. */

/*
 * EBCDIC --> ASCII string mapping table.
 * Some strings returned by GSKit are dynamically allocated and automatically
 * released when closing the handle.
 * To provide the same functionality, we use a "private" handle that
 * holds the GSKit handle and a list of string mappings. This will allow
 * avoid conversion of already converted strings and releasing them upon
 * close time.
 */

struct gskstrlist {
  struct gskstrlist *next;
  const char *ebcdicstr;
  const char *asciistr;
};

struct Curl_gsk_descriptor {
  gsk_handle h;
  struct gskstrlist *strlist;
};

int Curl_gsk_environment_open(gsk_handle *my_env_handle)
{
  struct Curl_gsk_descriptor *p;
  int rc;

  if(!my_env_handle)
    return GSK_OS400_ERROR_INVALID_POINTER;
  p = (struct Curl_gsk_descriptor *) malloc(sizeof(*p));
  if(!p)
    return GSK_INSUFFICIENT_STORAGE;
  p->strlist = (struct gskstrlist *) NULL;
  rc = gsk_environment_open(&p->h);
  if(rc != GSK_OK)
    free(p);
  else
    *my_env_handle = (gsk_handle) p;
  return rc;
}

int Curl_gsk_secure_soc_open(gsk_handle my_env_handle,
                             gsk_handle *my_session_handle)
{
  struct Curl_gsk_descriptor *p;
  gsk_handle h;
  int rc;

  if(!my_env_handle)
    return GSK_INVALID_HANDLE;
  if(!my_session_handle)
    return GSK_OS400_ERROR_INVALID_POINTER;
  h = ((struct Curl_gsk_descriptor *) my_env_handle)->h;
  p = (struct Curl_gsk_descriptor *) malloc(sizeof(*p));
  if(!p)
    return GSK_INSUFFICIENT_STORAGE;
  p->strlist = (struct gskstrlist *) NULL;
  rc = gsk_secure_soc_open(h, &p->h);
  if(rc != GSK_OK)
    free(p);
  else
    *my_session_handle = (gsk_handle) p;
  return rc;
}

static void gsk_free_handle(struct Curl_gsk_descriptor *p)
{
  struct gskstrlist *q;

  while((q = p->strlist)) {
    p->strlist = q;
    free((void *) q->asciistr);
    free(q);
  }
  free(p);
}

int Curl_gsk_environment_close(gsk_handle *my_env_handle)
{
  struct Curl_gsk_descriptor *p;
  int rc;

  if(!my_env_handle)
    return GSK_OS400_ERROR_INVALID_POINTER;
  if(!*my_env_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) *my_env_handle;
  rc = gsk_environment_close(&p->h);
  if(rc == GSK_OK) {
    gsk_free_handle(p);
    *my_env_handle = (gsk_handle) NULL;
  }
  return rc;
}


int Curl_gsk_secure_soc_close(gsk_handle *my_session_handle)
{
  struct Curl_gsk_descriptor *p;
  int rc;

  if(!my_session_handle)
    return GSK_OS400_ERROR_INVALID_POINTER;
  if(!*my_session_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) *my_session_handle;
  rc = gsk_secure_soc_close(&p->h);
  if(rc == GSK_OK) {
    gsk_free_handle(p);
    *my_session_handle = (gsk_handle) NULL;
  }
  return rc;
}

int Curl_gsk_environment_init(gsk_handle my_env_handle)
{
  struct Curl_gsk_descriptor *p;

  if(!my_env_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_env_handle;
  return gsk_environment_init(p->h);
}


int Curl_gsk_secure_soc_init(gsk_handle my_session_handle)
{
  struct Curl_gsk_descriptor *p;

  if(!my_session_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_session_handle;
  return gsk_secure_soc_init(p->h);
}


int
Curl_gsk_attribute_set_buffer_a(gsk_handle my_gsk_handle, GSK_BUF_ID bufID,
                                const char *buffer, int bufSize)
{
  struct Curl_gsk_descriptor *p;
  char *ebcdicbuf;
  int rc;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  if(!buffer)
    return GSK_OS400_ERROR_INVALID_POINTER;
  if(bufSize < 0)
    return GSK_ATTRIBUTE_INVALID_LENGTH;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  if(!bufSize)
    bufSize = strlen(buffer);
  ebcdicbuf = malloc(bufSize + 1);
  if(!ebcdicbuf)
    return GSK_INSUFFICIENT_STORAGE;
  QadrtConvertA2E(ebcdicbuf, buffer, bufSize, bufSize);
  ebcdicbuf[bufSize] = '\0';
  rc = gsk_attribute_set_buffer(p->h, bufID, ebcdicbuf, bufSize);
  free(ebcdicbuf);
  return rc;
}


int
Curl_gsk_attribute_set_enum(gsk_handle my_gsk_handle, GSK_ENUM_ID enumID,
                            GSK_ENUM_VALUE enumValue)
{
  struct Curl_gsk_descriptor *p;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  return gsk_attribute_set_enum(p->h, enumID, enumValue);
}


int
Curl_gsk_attribute_set_numeric_value(gsk_handle my_gsk_handle,
                                     GSK_NUM_ID numID, int numValue)
{
  struct Curl_gsk_descriptor *p;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  return gsk_attribute_set_numeric_value(p->h, numID, numValue);
}


int
Curl_gsk_attribute_set_callback(gsk_handle my_gsk_handle,
                                GSK_CALLBACK_ID callBackID,
                                void *callBackAreaPtr)
{
  struct Curl_gsk_descriptor *p;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  return gsk_attribute_set_callback(p->h, callBackID, callBackAreaPtr);
}


static int
cachestring(struct Curl_gsk_descriptor *p,
            const char *ebcdicbuf, int bufsize, const char **buffer)
{
  int rc;
  char *asciibuf;
  struct gskstrlist *sp;

  for(sp = p->strlist; sp; sp = sp->next)
    if(sp->ebcdicstr == ebcdicbuf)
      break;
  if(!sp) {
    sp = (struct gskstrlist *) malloc(sizeof(*sp));
    if(!sp)
      return GSK_INSUFFICIENT_STORAGE;
    asciibuf = malloc(bufsize + 1);
    if(!asciibuf) {
      free(sp);
      return GSK_INSUFFICIENT_STORAGE;
    }
    QadrtConvertE2A(asciibuf, ebcdicbuf, bufsize, bufsize);
    asciibuf[bufsize] = '\0';
    sp->ebcdicstr = ebcdicbuf;
    sp->asciistr = asciibuf;
    sp->next = p->strlist;
    p->strlist = sp;
  }
  *buffer = sp->asciistr;
  return GSK_OK;
}


int
Curl_gsk_attribute_get_buffer_a(gsk_handle my_gsk_handle, GSK_BUF_ID bufID,
                                const char **buffer, int *bufSize)
{
  struct Curl_gsk_descriptor *p;
  int rc;
  const char *mybuf;
  int mylen;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  if(!buffer || !bufSize)
    return GSK_OS400_ERROR_INVALID_POINTER;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  rc = gsk_attribute_get_buffer(p->h, bufID, &mybuf, &mylen);
  if(rc != GSK_OK)
    return rc;
  rc = cachestring(p, mybuf, mylen, buffer);
  if(rc == GSK_OK)
    *bufSize = mylen;
  return rc;
}


int
Curl_gsk_attribute_get_enum(gsk_handle my_gsk_handle, GSK_ENUM_ID enumID,
                            GSK_ENUM_VALUE *enumValue)
{
  struct Curl_gsk_descriptor *p;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  return gsk_attribute_get_enum(p->h, enumID, enumValue);
}


int
Curl_gsk_attribute_get_numeric_value(gsk_handle my_gsk_handle,
                                     GSK_NUM_ID numID, int *numValue)
{
  struct Curl_gsk_descriptor *p;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  return gsk_attribute_get_numeric_value(p->h, numID, numValue);
}


int
Curl_gsk_attribute_get_cert_info(gsk_handle my_gsk_handle,
                                 GSK_CERT_ID certID,
                                 const gsk_cert_data_elem **certDataElem,
                                 int *certDataElementCount)
{
  struct Curl_gsk_descriptor *p;

  if(!my_gsk_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_gsk_handle;
  /* No need to convert code: text results are already in ASCII. */
  return gsk_attribute_get_cert_info(p->h, certID,
                                     certDataElem, certDataElementCount);
}


int
Curl_gsk_secure_soc_misc(gsk_handle my_session_handle, GSK_MISC_ID miscID)
{
  struct Curl_gsk_descriptor *p;

  if(!my_session_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_session_handle;
  return gsk_secure_soc_misc(p->h, miscID);
}


int
Curl_gsk_secure_soc_read(gsk_handle my_session_handle, char *readBuffer,
                         int readBufSize, int *amtRead)
{
  struct Curl_gsk_descriptor *p;

  if(!my_session_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_session_handle;
  return gsk_secure_soc_read(p->h, readBuffer, readBufSize, amtRead);
}


int
Curl_gsk_secure_soc_write(gsk_handle my_session_handle, char *writeBuffer,
                          int writeBufSize, int *amtWritten)
{
  struct Curl_gsk_descriptor *p;

  if(!my_session_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_session_handle;
  return gsk_secure_soc_write(p->h, writeBuffer, writeBufSize, amtWritten);
}


const char *
Curl_gsk_strerror_a(int gsk_return_value)
{
  return set_thread_string(LK_GSK_ERROR, gsk_strerror(gsk_return_value));
}

int
Curl_gsk_secure_soc_startInit(gsk_handle my_session_handle,
                              int IOCompletionPort,
                              Qso_OverlappedIO_t *communicationsArea)
{
  struct Curl_gsk_descriptor *p;

  if(!my_session_handle)
    return GSK_INVALID_HANDLE;
  p = (struct Curl_gsk_descriptor *) my_session_handle;
  return gsk_secure_soc_startInit(p->h, IOCompletionPort, communicationsArea);
}

#endif /* USE_GSKIT */

#ifdef HAVE_GSSAPI

/* ASCII wrappers for the GSSAPI procedures. */

static int
Curl_gss_convert_in_place(OM_uint32 *minor_status, gss_buffer_t buf)
{
  unsigned int i = buf->length;

  /* Convert `buf' in place, from EBCDIC to ASCII.
     If error, release the buffer and return -1. Else return 0. */

  if(i) {
    char *t = malloc(i);
    if(!t) {
      gss_release_buffer(minor_status, buf);

      if(minor_status)
        *minor_status = ENOMEM;

      return -1;
    }

    QadrtConvertE2A(t, buf->value, i, i);
    memcpy(buf->value, t, i);
    free(t);
  }

  return 0;
}


OM_uint32
Curl_gss_import_name_a(OM_uint32 *minor_status, gss_buffer_t in_name,
                       gss_OID in_name_type, gss_name_t *out_name)
{
  int rc;
  unsigned int i;
  gss_buffer_desc in;

  if(!in_name || !in_name->value || !in_name->length)
    return gss_import_name(minor_status, in_name, in_name_type, out_name);

  memcpy((char *) &in, (char *) in_name, sizeof(in));
  i = in.length;

  in.value = malloc(i + 1);
  if(!in.value) {
    if(minor_status)
      *minor_status = ENOMEM;

    return GSS_S_FAILURE;
  }

  QadrtConvertA2E(in.value, in_name->value, i, i);
  ((char *) in.value)[i] = '\0';
  rc = gss_import_name(minor_status, &in, in_name_type, out_name);
  free(in.value);
  return rc;
}

OM_uint32
Curl_gss_display_status_a(OM_uint32 *minor_status, OM_uint32 status_value,
                          int status_type, gss_OID mech_type,
                          gss_msg_ctx_t *message_context,
                          gss_buffer_t status_string)
{
  int rc;

  rc = gss_display_status(minor_status, status_value, status_type,
                          mech_type, message_context, status_string);

  if(rc != GSS_S_COMPLETE || !status_string ||
     !status_string->length || !status_string->value)
    return rc;

  /* No way to allocate a buffer here, because it will be released by
     gss_release_buffer(). The solution is to overwrite the EBCDIC buffer
     with ASCII to return it. */

  if(Curl_gss_convert_in_place(minor_status, status_string))
    return GSS_S_FAILURE;

  return rc;
}

OM_uint32
Curl_gss_init_sec_context_a(OM_uint32 *minor_status,
                            gss_cred_id_t cred_handle,
                            gss_ctx_id_t *context_handle,
                            gss_name_t target_name, gss_OID mech_type,
                            gss_flags_t req_flags, OM_uint32 time_req,
                            gss_channel_bindings_t input_chan_bindings,
                            gss_buffer_t input_token,
                            gss_OID *actual_mech_type,
                            gss_buffer_t output_token, gss_flags_t *ret_flags,
                            OM_uint32 *time_rec)
{
  int rc;
  gss_buffer_desc in;
  gss_buffer_t inp;

  in.value = NULL;
  inp = input_token;

  if(inp) {
    if(inp->length && inp->value) {
      unsigned int i = inp->length;

      in.value = malloc(i + 1);
      if(!in.value) {
        if(minor_status)
          *minor_status = ENOMEM;

        return GSS_S_FAILURE;
      }

      QadrtConvertA2E(in.value, input_token->value, i, i);
      ((char *) in.value)[i] = '\0';
      in.length = i;
      inp = &in;
    }
  }

  rc = gss_init_sec_context(minor_status, cred_handle, context_handle,
                            target_name, mech_type, req_flags, time_req,
                            input_chan_bindings, inp, actual_mech_type,
                            output_token, ret_flags, time_rec);
  free(in.value);

  if(rc != GSS_S_COMPLETE || !output_token ||
     !output_token->length || !output_token->value)
    return rc;

  /* No way to allocate a buffer here, because it will be released by
     gss_release_buffer(). The solution is to overwrite the EBCDIC buffer
     with ASCII to return it. */

  if(Curl_gss_convert_in_place(minor_status, output_token))
    return GSS_S_FAILURE;

  return rc;
}


OM_uint32
Curl_gss_delete_sec_context_a(OM_uint32 *minor_status,
                              gss_ctx_id_t *context_handle,
                              gss_buffer_t output_token)
{
  int rc;

  rc = gss_delete_sec_context(minor_status, context_handle, output_token);

  if(rc != GSS_S_COMPLETE || !output_token ||
     !output_token->length || !output_token->value)
    return rc;

  /* No way to allocate a buffer here, because it will be released by
     gss_release_buffer(). The solution is to overwrite the EBCDIC buffer
     with ASCII to return it. */

  if(Curl_gss_convert_in_place(minor_status, output_token))
    return GSS_S_FAILURE;

  return rc;
}

#endif /* HAVE_GSSAPI */

#ifndef CURL_DISABLE_LDAP

/* ASCII wrappers for the LDAP procedures. */

void *
Curl_ldap_init_a(char *host, int port)
{
  unsigned int i;
  char *ehost;
  void *result;

  if(!host)
    return (void *) ldap_init(host, port);

  i = strlen(host);

  ehost = malloc(i + 1);
  if(!ehost)
    return (void *) NULL;

  QadrtConvertA2E(ehost, host, i, i);
  ehost[i] = '\0';
  result = (void *) ldap_init(ehost, port);
  free(ehost);
  return result;
}

int
Curl_ldap_simple_bind_s_a(void *ld, char *dn, char *passwd)
{
  int i;
  char *edn;
  char *epasswd;

  edn = (char *) NULL;
  epasswd = (char *) NULL;

  if(dn) {
    i = strlen(dn);

    edn = malloc(i + 1);
    if(!edn)
      return LDAP_NO_MEMORY;

    QadrtConvertA2E(edn, dn, i, i);
    edn[i] = '\0';
  }

  if(passwd) {
    i = strlen(passwd);

    epasswd = malloc(i + 1);
    if(!epasswd) {
      free(edn);
      return LDAP_NO_MEMORY;
    }

    QadrtConvertA2E(epasswd, passwd, i, i);
    epasswd[i] = '\0';
  }

  i = ldap_simple_bind_s(ld, edn, epasswd);
  free(epasswd);
  free(edn);
  return i;
}

int
Curl_ldap_search_s_a(void *ld, char *base, int scope, char *filter,
                     char **attrs, int attrsonly, LDAPMessage **res)
{
  int i;
  int j;
  char *ebase;
  char *efilter;
  char **eattrs;
  int status;

  ebase = (char *) NULL;
  efilter = (char *) NULL;
  eattrs = (char **) NULL;
  status = LDAP_SUCCESS;

  if(base) {
    i = strlen(base);

    ebase = malloc(i + 1);
    if(!ebase)
      status = LDAP_NO_MEMORY;
    else {
      QadrtConvertA2E(ebase, base, i, i);
      ebase[i] = '\0';
    }
  }

  if(filter && status == LDAP_SUCCESS) {
    i = strlen(filter);

    efilter = malloc(i + 1);
    if(!efilter)
      status = LDAP_NO_MEMORY;
    else {
      QadrtConvertA2E(efilter, filter, i, i);
      efilter[i] = '\0';
    }
  }

  if(attrs && status == LDAP_SUCCESS) {
    for(i = 0; attrs[i++];)
      ;

    eattrs = calloc(i, sizeof(*eattrs));
    if(!eattrs)
      status = LDAP_NO_MEMORY;
    else {
      for(j = 0; attrs[j]; j++) {
        i = strlen(attrs[j]);

        eattrs[j] = malloc(i + 1);
        if(!eattrs[j]) {
          status = LDAP_NO_MEMORY;
          break;
        }

        QadrtConvertA2E(eattrs[j], attrs[j], i, i);
        eattrs[j][i] = '\0';
      }
    }
  }

  if(status == LDAP_SUCCESS)
    status = ldap_search_s(ld, ebase? ebase: "", scope,
                           efilter? efilter: "(objectclass=*)",
                           eattrs, attrsonly, res);

  if(eattrs) {
    for(j = 0; eattrs[j]; j++)
      free(eattrs[j]);

    free(eattrs);
  }

  free(efilter);
  free(ebase);
  return status;
}


struct berval **
Curl_ldap_get_values_len_a(void *ld, LDAPMessage *entry, const char *attr)
{
  char *cp;
  struct berval **result;

  cp = (char *) NULL;

  if(attr) {
    int i = strlen(attr);

    cp = malloc(i + 1);
    if(!cp) {
      ldap_set_lderrno(ld, LDAP_NO_MEMORY, NULL,
                       ldap_err2string(LDAP_NO_MEMORY));
      return (struct berval **) NULL;
    }

    QadrtConvertA2E(cp, attr, i, i);
    cp[i] = '\0';
  }

  result = ldap_get_values_len(ld, entry, cp);
  free(cp);

  /* Result data are binary in nature, so they haven't been
     converted to EBCDIC. Therefore do not convert. */

  return result;
}

char *
Curl_ldap_err2string_a(int error)
{
  return set_thread_string(LK_LDAP_ERROR, ldap_err2string(error));
}

char *
Curl_ldap_get_dn_a(void *ld, LDAPMessage *entry)
{
  int i;
  char *cp;
  char *cp2;

  cp = ldap_get_dn(ld, entry);

  if(!cp)
    return cp;

  i = strlen(cp);

  cp2 = malloc(i + 1);
  if(!cp2)
    return cp2;

  QadrtConvertE2A(cp2, cp, i, i);
  cp2[i] = '\0';

  /* No way to allocate a buffer here, because it will be released by
     ldap_memfree() and ldap_memalloc() does not exist. The solution is to
     overwrite the EBCDIC buffer with ASCII to return it. */

  strcpy(cp, cp2);
  free(cp2);
  return cp;
}

char *
Curl_ldap_first_attribute_a(void *ld,
                            LDAPMessage *entry, BerElement **berptr)
{
  int i;
  char *cp;
  char *cp2;

  cp = ldap_first_attribute(ld, entry, berptr);

  if(!cp)
    return cp;

  i = strlen(cp);

  cp2 = malloc(i + 1);
  if(!cp2)
    return cp2;

  QadrtConvertE2A(cp2, cp, i, i);
  cp2[i] = '\0';

  /* No way to allocate a buffer here, because it will be released by
     ldap_memfree() and ldap_memalloc() does not exist. The solution is to
     overwrite the EBCDIC buffer with ASCII to return it. */

  strcpy(cp, cp2);
  free(cp2);
  return cp;
}

char *
Curl_ldap_next_attribute_a(void *ld,
                           LDAPMessage *entry, BerElement *berptr)
{
  int i;
  char *cp;
  char *cp2;

  cp = ldap_next_attribute(ld, entry, berptr);

  if(!cp)
    return cp;

  i = strlen(cp);

  cp2 = malloc(i + 1);
  if(!cp2)
    return cp2;

  QadrtConvertE2A(cp2, cp, i, i);
  cp2[i] = '\0';

  /* No way to allocate a buffer here, because it will be released by
     ldap_memfree() and ldap_memalloc() does not exist. The solution is to
     overwrite the EBCDIC buffer with ASCII to return it. */

  strcpy(cp, cp2);
  free(cp2);
  return cp;
}

#endif /* CURL_DISABLE_LDAP */

static int
sockaddr2ebcdic(struct sockaddr_storage *dstaddr,
                const struct sockaddr *srcaddr, int srclen)
{
  const struct sockaddr_un *srcu;
  struct sockaddr_un *dstu;
  unsigned int i;
  unsigned int dstsize;

  /* Convert a socket address to job CCSID, if needed. */

  if(!srcaddr || srclen < offsetof(struct sockaddr, sa_family) +
     sizeof(srcaddr->sa_family) || srclen > sizeof(*dstaddr)) {
    errno = EINVAL;
    return -1;
  }

  memcpy((char *) dstaddr, (char *) srcaddr, srclen);

  switch(srcaddr->sa_family) {

  case AF_UNIX:
    srcu = (const struct sockaddr_un *) srcaddr;
    dstu = (struct sockaddr_un *) dstaddr;
    dstsize = sizeof(*dstaddr) - offsetof(struct sockaddr_un, sun_path);
    srclen -= offsetof(struct sockaddr_un, sun_path);
    i = QadrtConvertA2E(dstu->sun_path, srcu->sun_path, dstsize - 1, srclen);
    dstu->sun_path[i] = '\0';
    srclen = i + offsetof(struct sockaddr_un, sun_path);
  }

  return srclen;
}


static int
sockaddr2ascii(struct sockaddr *dstaddr, int dstlen,
               const struct sockaddr_storage *srcaddr, int srclen)
{
  const struct sockaddr_un *srcu;
  struct sockaddr_un *dstu;
  unsigned int dstsize;

  /* Convert a socket address to ASCII, if needed. */

  if(!srclen)
    return 0;
  if(srclen > dstlen)
    srclen = dstlen;
  if(!srcaddr || srclen < 0) {
    errno = EINVAL;
    return -1;
  }

  memcpy((char *) dstaddr, (char *) srcaddr, srclen);

  if(srclen >= offsetof(struct sockaddr_storage, ss_family) +
     sizeof(srcaddr->ss_family)) {
    switch(srcaddr->ss_family) {

    case AF_UNIX:
      srcu = (const struct sockaddr_un *) srcaddr;
      dstu = (struct sockaddr_un *) dstaddr;
      dstsize = dstlen - offsetof(struct sockaddr_un, sun_path);
      srclen -= offsetof(struct sockaddr_un, sun_path);
      if(dstsize > 0 && srclen > 0) {
        srclen = QadrtConvertE2A(dstu->sun_path, srcu->sun_path,
                                 dstsize - 1, srclen);
        dstu->sun_path[srclen] = '\0';
      }
      srclen += offsetof(struct sockaddr_un, sun_path);
    }
  }

  return srclen;
}

int
Curl_os400_connect(int sd, struct sockaddr *destaddr, int addrlen)
{
  int i;
  struct sockaddr_storage laddr;

  i = sockaddr2ebcdic(&laddr, destaddr, addrlen);

  if(i < 0)
    return -1;

  return connect(sd, (struct sockaddr *) &laddr, i);
}

int
Curl_os400_bind(int sd, struct sockaddr *localaddr, int addrlen)
{
  int i;
  struct sockaddr_storage laddr;

  i = sockaddr2ebcdic(&laddr, localaddr, addrlen);

  if(i < 0)
    return -1;

  return bind(sd, (struct sockaddr *) &laddr, i);
}

int
Curl_os400_sendto(int sd, char *buffer, int buflen, int flags,
                  struct sockaddr *dstaddr, int addrlen)
{
  int i;
  struct sockaddr_storage laddr;

  i = sockaddr2ebcdic(&laddr, dstaddr, addrlen);

  if(i < 0)
    return -1;

  return sendto(sd, buffer, buflen, flags, (struct sockaddr *) &laddr, i);
}

int
Curl_os400_recvfrom(int sd, char *buffer, int buflen, int flags,
                    struct sockaddr *fromaddr, int *addrlen)
{
  int rcvlen;
  struct sockaddr_storage laddr;
  int laddrlen = sizeof(laddr);

  if(!fromaddr || !addrlen || *addrlen <= 0)
    return recvfrom(sd, buffer, buflen, flags, fromaddr, addrlen);

  laddr.ss_family = AF_UNSPEC;          /* To detect if unused. */
  rcvlen = recvfrom(sd, buffer, buflen, flags,
                    (struct sockaddr *) &laddr, &laddrlen);

  if(rcvlen < 0)
    return rcvlen;

  if(laddr.ss_family == AF_UNSPEC)
    laddrlen = 0;
  else {
    laddrlen = sockaddr2ascii(fromaddr, *addrlen, &laddr, laddrlen);
    if(laddrlen < 0)
      return laddrlen;
  }
  *addrlen = laddrlen;
  return rcvlen;
}

int
Curl_os400_getpeername(int sd, struct sockaddr *addr, int *addrlen)
{
  struct sockaddr_storage laddr;
  int laddrlen = sizeof(laddr);
  int retcode = getpeername(sd, (struct sockaddr *) &laddr, &laddrlen);

  if(!retcode) {
    laddrlen = sockaddr2ascii(addr, *addrlen, &laddr, laddrlen);
    if(laddrlen < 0)
      return laddrlen;
    *addrlen = laddrlen;
  }

  return retcode;
}

int
Curl_os400_getsockname(int sd, struct sockaddr *addr, int *addrlen)
{
  struct sockaddr_storage laddr;
  int laddrlen = sizeof(laddr);
  int retcode = getsockname(sd, (struct sockaddr *) &laddr, &laddrlen);

  if(!retcode) {
    laddrlen = sockaddr2ascii(addr, *addrlen, &laddr, laddrlen);
    if(laddrlen < 0)
      return laddrlen;
    *addrlen = laddrlen;
  }

  return retcode;
}


#ifdef HAVE_LIBZ
const char *
Curl_os400_zlibVersion(void)
{
  return set_thread_string(LK_ZLIB_VERSION, zlibVersion());
}


int
Curl_os400_inflateInit_(z_streamp strm, const char *version, int stream_size)
{
  z_const char *msgb4 = strm->msg;
  int ret;

  ret = inflateInit(strm);

  if(strm->msg != msgb4)
    strm->msg = set_thread_string(LK_ZLIB_MSG, strm->msg);

  return ret;
}

int
Curl_os400_inflateInit2_(z_streamp strm, int windowBits,
                         const char *version, int stream_size)
{
  z_const char *msgb4 = strm->msg;
  int ret;

  ret = inflateInit2(strm, windowBits);

  if(strm->msg != msgb4)
    strm->msg = set_thread_string(LK_ZLIB_MSG, strm->msg);

  return ret;
}

int
Curl_os400_inflate(z_streamp strm, int flush)
{
  z_const char *msgb4 = strm->msg;
  int ret;

  ret = inflate(strm, flush);

  if(strm->msg != msgb4)
    strm->msg = set_thread_string(LK_ZLIB_MSG, strm->msg);

  return ret;
}

int
Curl_os400_inflateEnd(z_streamp strm)
{
  z_const char *msgb4 = strm->msg;
  int ret;

  ret = inflateEnd(strm);

  if(strm->msg != msgb4)
    strm->msg = set_thread_string(LK_ZLIB_MSG, strm->msg);

  return ret;
}

#endif
