gsasl.c 7.52 KB
/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 2003 Free Software Foundation, Inc.

   GNU Mailutils is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   GNU Mailutils is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Mailutils; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef WITH_GSASL

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <mailutils/argp.h>
#include <mailutils/error.h>
#include <mailutils/mu_auth.h>
#include <mailutils/nls.h>
#include <mailutils/stream.h>

#include <gsasl.h>

char *gsasl_cram_md5_pwd = SITE_CRAM_MD5_PWD;

#define ARG_CRAM_PASSWD 1

static struct argp_option _gsasl_argp_options[] = {
  {NULL, 0, NULL, 0, N_("GSASL options"), 0},
  {"cram-passwd", ARG_CRAM_PASSWD, N_("FILE"), 0,
   N_("Specify password file for CRAM-MD5 authentication"), 0},
  { NULL,      0, NULL, 0, NULL, 0 }
};

static error_t
_gsasl_argp_parser (int key, char *arg, struct argp_state *state)
{
  switch (key)
    {
    case ARG_CRAM_PASSWD:
      gsasl_cram_md5_pwd = arg;
      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

static struct argp _gsasl_argp = {
  _gsasl_argp_options,
  _gsasl_argp_parser
};

static struct argp_child _gsasl_argp_child = {
  &_gsasl_argp,
  0,
  NULL,
  0
};

void
mu_gsasl_init_argp ()
{
  if (mu_register_capa ("gsasl", &_gsasl_argp_child))
    {
      mu_error (_("INTERNAL ERROR: cannot register argp capability gsasl"));
      abort ();
    }
}


struct _gsasl_stream {
  Gsasl_session_ctx *sess_ctx; /* Context */
  int last_err;        /* Last Gsasl error code */
  
  int fd;              /* File descriptor */

  char *buffer;        /* Line buffer */
  size_t size;         /* Allocated size */
  size_t level;        /* Current filling level */
};

static void
_gsasl_destroy (stream_t stream)
{
  struct _gsasl_stream *s = stream_get_owner (stream);
  free (s->buffer);
  s->buffer = NULL;
}

#define buffer_drop(s) s->level = 0

int
buffer_grow (struct _gsasl_stream *s, char *ptr, size_t size)
{
  if (!s->buffer)
    {
      s->buffer = malloc (size);
      s->size = size;
      s->level = 0;
    }
  else if (s->size - s->level < size)
    {
      size_t newsize = s->size + size;
      s->buffer = realloc (s->buffer, newsize);
      if (s->buffer)
	s->size = newsize;
    }

  if (!s->buffer)
    return ENOMEM;
  
  memcpy (s->buffer + s->level, ptr, size);
  s->level += size;
  return 0;
}

static int
_gsasl_readline (stream_t stream, char *optr, size_t osize,
		 off_t offset, size_t *nbytes)
{
  struct _gsasl_stream *s = stream_get_owner (stream);
  int rc;
  size_t len, sz;
  char *bufp;
  
  if (s->level)
    {
      len = s->level > osize ? osize : s->level;
      memcpy (optr, s->buffer, len);
      if (s->level > len)
	{
	  memmove (s->buffer, s->buffer + len, s->level - len);
	  s->level -= len;
	}
      if (nbytes)
	*nbytes = len;
      return 0;
    }
  
  do
    {
      char buf[80];
      size_t sz;

      sz = read (s->fd, buf, sizeof (buf));
      if (sz == (size_t) -1)
	{
	  if (errno == EINTR)
	    continue;
	  return errno;
	}

      rc = buffer_grow (s, buf, sz);
      if (rc)
	return rc;

      len = UINT_MAX; /* override the bug in libgsasl */
      rc = gsasl_decode (s->sess_ctx, s->buffer, s->level, NULL, &len);
    }
  while (rc == GSASL_NEEDS_MORE);

  if (rc != GSASL_OK)
    {
      s->last_err = rc;
      return EIO;
    }
      
  bufp = malloc (len + 1);
  if (!bufp)
    return ENOMEM;
  rc = gsasl_decode (s->sess_ctx, s->buffer, s->level, bufp, &len);
  if (rc != GSASL_OK)
    {
      s->last_err = rc;
      return EIO;
    }
  bufp[len++] = '\0';

  sz = len > osize ? osize : len;
  
  if (len > osize)
    {
      memcpy (optr, bufp, osize);
      buffer_drop (s);
      buffer_grow (s, bufp + osize, len - osize);
      len = osize;
    }
  else
    {
      buffer_drop (s);
      memcpy (optr, bufp, len);
    }
  
  if (nbytes)
    *nbytes = len;
  
  free (bufp);

  return 0;
}

int
write_chunk (struct _gsasl_stream *s, char *start, char *end)
{
  size_t chunk_size = end - start + 1;
  size_t len;
  size_t wrsize;
  char *buf = NULL;
      
  len = UINT_MAX; /* override the bug in libgsasl */
  gsasl_encode (s->sess_ctx, start, chunk_size, NULL, &len);
  buf = malloc (len);
  if (!buf)
    return ENOMEM;

  gsasl_encode (s->sess_ctx, start, chunk_size, buf, &len);

  wrsize = 0;
  do
    {
      size_t sz = write (s->fd, buf + wrsize, len - wrsize);
      if (sz == (size_t)-1)
	{
	  if (errno == EINTR)
	    continue;
	  free (buf);
	  return errno;
	}
      wrsize += sz;
    }
  while (wrsize < len);
  
  free (buf);

  return 0;
}


static int
_gsasl_write (stream_t stream, const char *iptr, size_t isize,
	      off_t offset, size_t *nbytes)
{
  int rc;
  size_t total = 0;
  struct _gsasl_stream *s = stream_get_owner (stream);
  
  rc = buffer_grow (s, iptr, isize);
  if (rc)
    return rc;

  if (s->level > 2)
    {
      char *start, *end;
      
      for (start = s->buffer, end = strchr (start, '\n');
	   end && end < s->buffer + s->level;
	   start = end + 1, end = strchr (start, '\n'))
	if (end[-1] == '\r')
	  {
	    int rc = write_chunk (s, start, end);
	    if (rc)
	      return rc;
	  }

      if (start > s->buffer)
	{
	  if (start < s->buffer + s->level)
	    {
	      int rest = s->buffer + s->level - start + 1;
	      memmove (s->buffer, start, rest);
	      s->level = rest;
	    }
	  else 
	    s->level = 0;
	}
    }
  
  if (nbytes)
    *nbytes = isize;
      
  return 0;
}

static int
_gsasl_flush (stream_t stream)
{
  return 0;
}

static int
_gsasl_close (stream_t stream)
{
  int flags;
  struct _gsasl_stream *s = stream_get_owner (stream);

  stream_get_flags (stream, &flags);
  if (!(flags & MU_STREAM_NO_CLOSE))
    close (s->fd);
  if (s->sess_ctx)
    gsasl_server_finish (s->sess_ctx);
  return 0;
}

static int
_gsasl_open (stream_t stream)
{
  struct _gsasl_stream *s = stream_get_owner (stream);
  return 0;
}

int
_gsasl_strerror (stream_t stream, const char **pstr)
{
  struct _gsasl_stream *s = stream_get_owner (stream);
  *pstr = gsasl_strerror (s->last_err);
  return 0;
}


int
gsasl_stream_create (stream_t *stream, int fd,
		     Gsasl_session_ctx *ctx, int flags)
{
  struct _gsasl_stream *s;
  int rc;
    
  if (stream == NULL)
    return EINVAL;

  if ((flags & ~(MU_STREAM_READ|MU_STREAM_WRITE))
      || (flags & (MU_STREAM_READ|MU_STREAM_WRITE)) ==
          (MU_STREAM_READ|MU_STREAM_WRITE))
    return EINVAL;
  
  s = calloc (1, sizeof (*s));
  if (s == NULL)
    return ENOMEM;

  s->fd = fd;
  s->sess_ctx = ctx;

  rc = stream_create (stream, flags|MU_STREAM_NO_CHECK, s);
  if (rc)
    {
      free (s);
      return rc;
    }

  stream_set_open (*stream, _gsasl_open, s);
  stream_set_close (*stream, _gsasl_close, s);
  stream_set_flush (*stream, _gsasl_flush, s);
  stream_set_destroy (*stream, _gsasl_destroy, s);
  stream_set_strerror (*stream, _gsasl_strerror, s);

  if (flags & MU_STREAM_READ)
    stream_set_readline (*stream, _gsasl_readline, s);
  else
    stream_set_write (*stream, _gsasl_write, s);

  return 0;
}
  
#endif