root/usr/src/cmd/cmd-inet/usr.bin/ftp/secure.c
/*
 * 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.
 */

/*
 * 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);
}