/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Shared routines for client and server for
* secure read(), write(), getc(), and putc().
* Only one security context, thus only work on one fd at a time!
*/
#include "ftp_var.h"
#include <gssapi/gssapi.h>
#include <arpa/ftp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
extern struct sockaddr_in hisaddr;
extern struct sockaddr_in myaddr;
extern int dlevel;
extern int auth_type;
extern uint_t maxbuf; /* maximum output buffer size */
extern uchar_t *ucbuf; /* cleartext buffer */
static uint_t nout; /* number of chars in ucbuf */
static uint_t smaxbuf; /* Internal saved value of maxbuf */
static uint_t smaxqueue; /* Maximum allowed to queue before flush */
extern gss_ctx_id_t gcontext;
static int secure_putbuf(int, uchar_t *, uint_t);
static int
looping_write(int fd, const char *buf, int len)
{
int cc, len2 = 0;
if (len == 0)
return (0);
do {
cc = write(fd, buf, len);
if (cc < 0) {
if (errno == EINTR)
continue;
return (cc);
} else if (cc == 0) {
return (len2);
} else {
buf += cc;
len2 += cc;
len -= cc;
}
} while (len > 0);
return (len2);
}
static int
looping_read(int fd, char *buf, int len)
{
int cc, len2 = 0;
do {
cc = read(fd, buf, len);
if (cc < 0) {
if (errno == EINTR)
continue;
return (cc); /* errno is already set */
} else if (cc == 0) {
return (len2);
} else {
buf += cc;
len2 += cc;
len -= cc;
}
} while (len > 0);
return (len2);
}
#define ERR -2
static void
secure_error(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
}
/*
* Given maxbuf as a buffer size, determine how much can we
* really transfer given the overhead of different algorithms
*
* Sets smaxbuf and smaxqueue
*/
static int
secure_determine_constants(void)
{
smaxbuf = maxbuf;
smaxqueue = maxbuf;
if (auth_type == AUTHTYPE_GSSAPI) {
OM_uint32 maj_stat, min_stat, mlen;
OM_uint32 msize = maxbuf;
maj_stat = gss_wrap_size_limit(&min_stat, gcontext,
(dlevel == PROT_P),
GSS_C_QOP_DEFAULT,
msize, &mlen);
if (maj_stat != GSS_S_COMPLETE) {
user_gss_error(maj_stat, min_stat,
"GSSAPI fudge determination");
/* Return error how? */
return (ERR);
}
smaxqueue = mlen;
}
return (0);
}
static uchar_t
secure_putbyte(int fd, uchar_t c)
{
int ret;
if ((smaxbuf == 0) || (smaxqueue == 0) || (smaxbuf != maxbuf)) {
ret = secure_determine_constants();
if (ret)
return (ret);
}
ucbuf[nout++] = c;
if (nout == smaxqueue) {
nout = 0;
ret = secure_putbuf(fd, ucbuf, smaxqueue);
return (ret ? ret :c);
}
return (c);
}
/*
* returns:
* 0 on success
* -1 on error (errno set)
* -2 on security error
*/
int
secure_flush(int fd)
{
int ret;
if (dlevel == PROT_C)
return (0);
if (nout)
if (ret = secure_putbuf(fd, ucbuf, nout))
return (ret);
return (secure_putbuf(fd, (uchar_t *)"", nout = 0));
}
/*
* returns:
* >= 0 on success
* -1 on error
* -2 on security error
*/
int
secure_putc(int c, FILE *stream)
{
if (dlevel == PROT_C)
return (putc(c, stream));
return (secure_putbyte(fileno(stream), (uchar_t)c));
}
/*
* returns:
* nbyte on success
* -1 on error (errno set)
* -2 on security error
*/
ssize_t
secure_write(int fd, const void *inbuf, size_t nbyte)
{
uint_t i;
int c;
uchar_t *buf = (uchar_t *)inbuf;
if (dlevel == PROT_C)
return (write(fd, buf, nbyte));
for (i = 0; nbyte > 0; nbyte--)
if ((c = secure_putbyte(fd, buf[i++])) < 0)
return (c);
return (i);
}
/*
* returns:
* 0 on success
* -1 on error, errno set
* -2 on security error
*/
static int secure_putbuf(int fd, uchar_t *buf, uint_t nbyte)
{
static char *outbuf; /* output ciphertext */
static uint_t bufsize; /* size of outbuf */
int length;
uint_t net_len;
/* Other auth types go here ... */
if (auth_type == AUTHTYPE_GSSAPI) {
gss_buffer_desc in_buf, out_buf;
OM_uint32 maj_stat, min_stat;
int conf_state;
in_buf.value = buf;
in_buf.length = nbyte;
maj_stat = gss_seal(&min_stat, gcontext,
(dlevel == PROT_P), /* confidential */
GSS_C_QOP_DEFAULT,
&in_buf, &conf_state,
&out_buf);
if (maj_stat != GSS_S_COMPLETE) {
/*
* generally need to deal
* ie. should loop, but for now just fail
*/
user_gss_error(maj_stat, min_stat, dlevel == PROT_P?
"GSSAPI seal failed" : "GSSAPI sign failed");
return (ERR);
}
if (bufsize < out_buf.length) {
outbuf = outbuf ?
realloc(outbuf, (size_t)out_buf.length) :
malloc((size_t)out_buf.length);
if (outbuf)
bufsize = out_buf.length;
else {
bufsize = 0;
secure_error("%s (in malloc of PROT buffer)",
strerror(errno));
return (ERR);
}
}
memcpy(outbuf, out_buf.value, length = out_buf.length);
gss_release_buffer(&min_stat, &out_buf);
}
net_len = htonl((uint32_t)length);
if (looping_write(fd, (char *)&net_len, 4) == -1)
return (-1);
if (looping_write(fd, outbuf, length) != length)
return (-1);
return (0);
}
static int
secure_getbyte(int fd)
{
/* number of chars in ucbuf, pointer into ucbuf */
static uint_t nin, bufp;
int kerror;
uint_t length;
if (nin == 0) {
if ((kerror =
looping_read(fd, (char *)&length, sizeof (length)))
!= sizeof (length)) {
secure_error("Couldn't read PROT buffer length: %d/%s",
kerror, (kerror == -1) ? strerror(errno) :
"premature EOF");
return (ERR);
}
if ((length = ntohl((uint32_t)length)) > maxbuf) {
secure_error("Length (%d) of PROT buffer > PBSZ=%u",
length, maxbuf);
return (ERR);
}
if ((kerror = looping_read(fd, (char *)ucbuf, length))
!= length) {
secure_error("Couldn't read %u byte PROT buffer: %s",
length, kerror == -1 ?
strerror(errno) : "premature EOF");
return (ERR);
}
/* Other auth types go here ... */
if (auth_type == AUTHTYPE_GSSAPI) {
gss_buffer_desc xmit_buf, msg_buf;
OM_uint32 maj_stat, min_stat;
int conf_state;
xmit_buf.value = ucbuf;
xmit_buf.length = length;
conf_state = (dlevel == PROT_P);
/* decrypt/verify the message */
maj_stat = gss_unseal(&min_stat, gcontext, &xmit_buf,
&msg_buf, &conf_state, NULL);
if (maj_stat != GSS_S_COMPLETE) {
user_gss_error(maj_stat, min_stat,
(dlevel == PROT_P)?
"failed unsealing ENC message":
"failed unsealing MIC message");
return (ERR);
}
memcpy(ucbuf, msg_buf.value,
nin = bufp = msg_buf.length);
gss_release_buffer(&min_stat, &msg_buf);
}
/* Other auth types go here ... */
}
return ((nin == 0) ? EOF : ucbuf[bufp - nin--]);
}
/*
* returns:
* 0 on success
* -1 on EOF
* -2 on security error
*/
int
secure_getc(FILE *stream)
{
if (dlevel == PROT_C)
return (getc(stream));
return (secure_getbyte(fileno(stream)));
}
/*
* returns:
* > 0 on success (n == # of bytes read)
* 0 on EOF
* -1 on error, errno set, only for PROT_C
* -2 on security error (ERR = -2)
*/
ssize_t
secure_read(int fd, void *inbuf, size_t nbyte)
{
int c, i;
char *buf = (char *)inbuf;
if (dlevel == PROT_C)
return (read(fd, buf, nbyte));
if (goteof)
return (goteof = 0);
for (i = 0; nbyte > 0; nbyte--)
switch (c = secure_getbyte(fd)) {
case ERR:
return (c);
case EOF:
goteof = i ? 1 : 0;
return (i);
default:
buf[i++] = c;
}
return (i);
}