sfsasl.c revision e9af4bc0b1cc30cea75d6ad4aa2fde97d985e9be
/*
* Copyright (c) 1999-2006, 2008 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*
*/
#include <stdlib.h>
#include <sendmail.h>
#include <errno.h>
/* allow to disable error handling code just in case... */
#ifndef DEAL_WITH_ERROR_SSL
# define DEAL_WITH_ERROR_SSL 1
#endif /* ! DEAL_WITH_ERROR_SSL */
#if SASL
# include "sfsasl.h"
/* Structure used by the "sasl" file type */
struct sasl_obj
{
};
struct sasl_info
{
};
/*
** SASL_GETINFO - returns requested information about a "sasl" file
** descriptor.
**
** Parameters:
** fp -- the file descriptor
** what -- the type of information requested
** valp -- the thang to return the information in
**
** Returns:
** -1 for unknown requests
** >=0 on success with valp filled in (if possible).
*/
static int
int what;
void *valp;
{
switch (what)
{
case SM_IO_WHAT_FD:
return -1;
case SM_IO_IS_READABLE:
return 0;
/* get info from underlying file */
default:
return -1;
}
}
/*
** SASL_OPEN -- creates the sasl specific information for opening a
** file of the sasl type.
**
** Parameters:
** fp -- the file pointer associated with the new open
** info -- contains the sasl connection information pointer and
** the original SM_FILE_T that holds the open
** flags -- ignored
** rpool -- ignored
**
** Returns:
** 0 on success
*/
/* ARGSUSED2 */
static int
const void *info;
int flags;
const void *rpool;
{
{
return -1;
}
/*
** The underlying 'fp' is set to SM_IO_NOW so that the entire
** encoded string is written in one chunk. Otherwise there is
** the possibility that it may appear illegal, bogus or
** mangled to the other side of the connection.
** We will read or write through 'fp' since it is the opaque
** connection for the communications. We need to treat it this
** way in case the encoded string is to be sent down a TLS
** connection rather than, say, sm_io's stdio.
*/
return 0;
}
/*
** SASL_CLOSE -- close the sasl specific parts of the sasl file pointer
**
** Parameters:
** fp -- the file pointer to close
**
** Returns:
** 0 on success
*/
static int
{
return 0;
{
}
return 0;
}
/* how to deallocate a buffer allocated by SASL */
extern void sm_sasl_free __P((void *));
# define SASL_DEALLOC(b) sm_sasl_free(b)
/*
** SASL_READ -- read encrypted information and decrypt it for the caller
**
** Parameters:
** fp -- the file pointer
** buf -- the location to place the decrypted information
** size -- the number of bytes to read after decryption
**
** Results:
** -1 on error
** otherwise the number of bytes read
*/
static ssize_t
char *buf;
{
int result;
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
static unsigned int outlen = 0;
static unsigned int offset = 0;
/*
** sasl_decode() may require more data than a single read() returns.
** Hence we have to put a loop around the decoding.
** This also requires that we may have to split up the returned
** data since it might be larger than the allowed size.
** Therefore we use a static pointer and return portions of it
** if necessary.
** XXX Note: This function is not thread-safe nor can it be used
** on more than one file. A correct implementation would store
** this data in fp->f_cookie.
*/
# if SASL >= 20000
while (outlen == 0)
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
{
if (len <= 0)
return len;
{
if (LogLevel > 7)
"AUTH: sasl_decode error=%d", result);
offset = 0;
outlen = 0;
return -1;
}
}
{
/* be paranoid: outbuf == NULL but outlen != 0 */
syserr("@sasl_read failure: outbuf == NULL but outlen != 0");
/* NOTREACHED */
}
{
/* return another part of the buffer */
}
else
{
/* return the rest of the buffer */
# if SASL < 20000
# endif /* SASL < 20000 */
offset = 0;
outlen = 0;
}
return len;
}
/*
** SASL_WRITE -- write information out after encrypting it
**
** Parameters:
** fp -- the file pointer
** buf -- holds the data to be encrypted and written
** size -- the number of bytes to have encrypted and written
**
** Returns:
** -1 on error
** otherwise number of bytes written
*/
static ssize_t
const char *buf;
{
int result;
# if SASL >= 20000
const char *outbuf;
# else /* SASL >= 20000 */
char *outbuf;
# endif /* SASL >= 20000 */
/*
** Fetch the maximum input buffer size for sasl_encode().
** This can be less than the size set in attemptauth()
** due to a negotiation with the other side, e.g.,
** Cyrus IMAP lmtp program sets maxbuf=4096,
** digestmd5 substracts 25 and hence we'll get 4071
** instead of 8192 (MAXOUTLEN).
** Hack (for now): simply reduce the size, callers are (must be)
** able to deal with that and invoke sasl_write() again with
** the rest of the data.
** Note: it would be better to store this value in the context
** after the negotiation.
*/
(const void **) &maxencode);
{
if (LogLevel > 7)
"AUTH: sasl_encode error=%d", result);
return -1;
}
{
while (outlen > 0)
{
errno = 0;
/* XXX result == 0? */
if (ret <= 0)
return ret;
}
# if SASL < 20000
# endif /* SASL < 20000 */
}
return size;
}
/*
** SFDCSASL -- create sasl file type and open in and out file pointers
** for sendmail to read from and write to.
**
** Parameters:
** fin -- the sm_io file encrypted data to be read from
** fout -- the sm_io file encrypted data to be written to
** conn -- the sasl connection pointer
** tmo -- timeout
**
** Returns:
** -1 on error
** 0 on success
**
** Side effects:
** The arguments "fin" and "fout" are replaced with the new
** SM_FILE_T pointers.
*/
int
int tmo;
{
{
/* no need to do anything */
return 0;
}
return -1;
{
return -1;
}
return 0;
}
#endif /* SASL */
#if STARTTLS
# include "sfsasl.h"
/* Structure used by the "tls" file type */
struct tls_obj
{
};
struct tls_info
{
};
/*
** TLS_GETINFO - returns requested information about a "tls" file
** descriptor.
**
** Parameters:
** fp -- the file descriptor
** what -- the type of information requested
** valp -- the thang to return the information in (unused)
**
** Returns:
** -1 for unknown requests
** >=0 on success with valp filled in (if possible).
*/
/* ARGSUSED2 */
static int
int what;
void *valp;
{
switch (what)
{
case SM_IO_WHAT_FD:
return -1;
case SM_IO_IS_READABLE:
default:
return -1;
}
}
/*
** TLS_OPEN -- creates the tls specific information for opening a
** file of the tls type.
**
** Parameters:
** fp -- the file pointer associated with the new open
** info -- the sm_io file pointer holding the open and the
** TLS encryption connection to be read from or written to
** flags -- ignored
** rpool -- ignored
**
** Returns:
** 0 on success
*/
/* ARGSUSED2 */
static int
const void *info;
int flags;
const void *rpool;
{
{
return -1;
}
/*
** We try to get the "raw" file descriptor that TLS uses to
** over the file descriptor being a blocking or non-blocking type.
** Under the covers TLS handles the change and this allows us
** to do timeouts with sm_io.
*/
return 0;
}
/*
** TLS_CLOSE -- close the tls specific parts of the tls file pointer
**
** Parameters:
** fp -- the file pointer to close
**
** Returns:
** 0 on success
*/
static int
{
return 0;
{
}
return 0;
}
/* maximum number of retries for TLS related I/O due to handshakes */
# define MAX_TLS_IOS 4
/*
** TLS_RETRY -- check whether a failed SSL operation can be retried
**
** Parameters:
** ssl -- TLS structure
** rfd -- read fd
** wfd -- write fd
** tlsstart -- start time of TLS operation
** timeout -- timeout for TLS operation
** err -- SSL error
** where -- description of operation
**
** Results:
** >0 on success
** 0 on timeout
** <0 on error
*/
int
int rfd;
int wfd;
int timeout;
int err;
const char *where;
{
int ret;
ret = -1;
/*
** For SSL_ERROR_WANT_{READ,WRITE}:
** There is not a complete SSL record available yet
** or there is only a partial SSL record removed from
** the network (socket) buffer into the SSL buffer.
** The SSL_connect will only succeed when a full
** SSL record is available (assuming a "real" error
** doesn't happen). To handle when a "real" error
** does happen the select is set for exceptions too.
** The connection may be re-negotiated during this time
** so both read and write "want errors" need to be handled.
** A select() exception loops back so that a proper SSL
** error message can be gotten.
*/
if (left <= 0)
return 0; /* timeout */
if (LogLevel > 14)
{
"STARTTLS=%s, info: fds=%d/%d, err=%d",
}
if (FD_SETSIZE > 0 &&
{
if (LogLevel > 5)
{
"STARTTLS=%s, error: fd %d/%d too large",
if (LogLevel > 8)
}
}
else if (err == SSL_ERROR_WANT_READ)
{
do
{
&tv);
}
else if (err == SSL_ERROR_WANT_WRITE)
{
do
{
&tv);
}
return ret;
}
/* errno to force refill() etc to stop (see IS_IO_ERROR()) */
#ifdef ETIMEDOUT
# define SM_ERR_TIMEOUT ETIMEDOUT
#else /* ETIMEDOUT */
# define SM_ERR_TIMEOUT EIO
#endif /* ETIMEDOUT */
/*
** SET_TLS_RD_TMO -- read secured information for the caller
**
** Parameters:
** rd_tmo -- read timeout
**
** Results:
** none
** This is a hack: there is no way to pass it in
*/
static int tls_rd_tmo = -1;
void
int rd_tmo;
{
tls_rd_tmo = rd_tmo;
}
/*
** TLS_READ -- read secured information for the caller
**
** Parameters:
** fp -- the file pointer
** buf -- the location to place the data
** size -- the number of bytes to read from connection
**
** Results:
** -1 on error
** otherwise the number of bytes read
*/
static ssize_t
char *buf;
{
char *err;
try = 99;
if (r > 0)
return r;
{
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
break;
case SSL_ERROR_WANT_WRITE:
err = "read W BLOCK";
/* FALLTHROUGH */
case SSL_ERROR_WANT_READ:
err = "read R BLOCK";
: tls_rd_tmo,
ssl_err, "read");
if (try > 0)
goto retry;
break;
err = "write X BLOCK";
break;
case SSL_ERROR_SYSCALL:
if (r == 0 && errno == 0) /* out of protocol EOF found */
break;
err = "syscall error";
/*
get_last_socket_error());
*/
break;
case SSL_ERROR_SSL:
if (r == 0 && errno == 0) /* out of protocol EOF found */
break;
#endif /* DEAL_WITH_ERROR_SSL */
err = "generic SSL error";
if (LogLevel > 9)
tlslogerr("read");
/* avoid repeated calls? */
if (r == 0)
r = -1;
#endif /* DEAL_WITH_ERROR_SSL */
break;
}
{
int save_errno;
{
if (LogLevel > 7)
"STARTTLS: read error=timeout");
}
else if (LogLevel > 8)
"STARTTLS: read error=%s (%d), errno=%d, get_error=%s, retry=%d, ssl_err=%d",
ssl_err);
else if (LogLevel > 7)
"STARTTLS: read error=%s (%d), retry=%d, ssl_err=%d",
errno = save_errno;
}
return r;
}
/*
** TLS_WRITE -- write information out through secure connection
**
** Parameters:
** fp -- the file pointer
** buf -- holds the data to be securely written
** size -- the number of bytes to write
**
** Returns:
** -1 on error
** otherwise number of bytes written
*/
static ssize_t
const char *buf;
{
char *err;
try = 99;
if (r > 0)
return r;
{
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
break;
case SSL_ERROR_WANT_WRITE:
err = "read W BLOCK";
/* FALLTHROUGH */
case SSL_ERROR_WANT_READ:
err = "read R BLOCK";
if (try > 0)
goto retry;
break;
err = "write X BLOCK";
break;
case SSL_ERROR_SYSCALL:
if (r == 0 && errno == 0) /* out of protocol EOF found */
break;
err = "syscall error";
/*
get_last_socket_error());
*/
break;
case SSL_ERROR_SSL:
err = "generic SSL error";
/*
ERR_GET_REASON(ERR_peek_error()));
*/
if (LogLevel > 9)
tlslogerr("write");
/* avoid repeated calls? */
if (r == 0)
r = -1;
#endif /* DEAL_WITH_ERROR_SSL */
break;
}
{
int save_errno;
{
if (LogLevel > 7)
"STARTTLS: write error=timeout");
}
else if (LogLevel > 8)
"STARTTLS: write error=%s (%d), errno=%d, get_error=%s, retry=%d, ssl_err=%d",
ssl_err);
else if (LogLevel > 7)
"STARTTLS: write error=%s (%d), errno=%d, retry=%d, ssl_err=%d",
errno = save_errno;
}
return r;
}
/*
** SFDCTLS -- create tls file type and open in and out file pointers
** for sendmail to read from and write to.
**
** Parameters:
** fin -- data input source being replaced
** fout -- data output source being replaced
** con -- the tls connection pointer
**
** Returns:
** -1 on error
** 0 on success
**
** Side effects:
** The arguments "fin" and "fout" are replaced with the new
** SM_FILE_T pointers.
** The original "fin" and "fout" are preserved in the tls file
** type but are not actually used because of the design of TLS.
*/
int
{
NULL);
return -1;
NULL);
{
return -1;
}
return 0;
}
#endif /* STARTTLS */