freebsd-dev/crypto/heimdal/kcm/connect.c
2010-01-03 20:45:49 +00:00

689 lines
15 KiB
C

/*
* Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "kcm_locl.h"
RCSID("$Id: connect.c 16314 2005-11-29 19:03:50Z lha $");
struct descr {
int s;
int type;
char *path;
unsigned char *buf;
size_t size;
size_t len;
time_t timeout;
struct sockaddr_storage __ss;
struct sockaddr *sa;
socklen_t sock_len;
kcm_client peercred;
};
static void
init_descr(struct descr *d)
{
memset(d, 0, sizeof(*d));
d->sa = (struct sockaddr *)&d->__ss;
d->s = -1;
}
/*
* re-initialize all `n' ->sa in `d'.
*/
static void
reinit_descrs (struct descr *d, int n)
{
int i;
for (i = 0; i < n; ++i)
d[i].sa = (struct sockaddr *)&d[i].__ss;
}
/*
* Update peer credentials from socket.
*
* SCM_CREDS can only be updated the first time there is read data to
* read from the filedescriptor, so if we read do it before this
* point, the cred data might not be is not there yet.
*/
static int
update_client_creds(int s, kcm_client *peer)
{
#ifdef GETPEERUCRED
/* Solaris 10 */
{
ucred_t *peercred;
if (getpeerucred(s, &peercred) != 0) {
peer->uid = ucred_geteuid(peercred);
peer->gid = ucred_getegid(peercred);
peer->pid = 0;
ucred_free(peercred);
return 0;
}
}
#endif
#ifdef GETPEEREID
/* FreeBSD, OpenBSD */
{
uid_t uid;
gid_t gid;
if (getpeereid(s, &uid, &gid) == 0) {
peer->uid = uid;
peer->gid = gid;
peer->pid = 0;
return 0;
}
}
#endif
#ifdef SO_PEERCRED
/* Linux */
{
struct ucred pc;
socklen_t pclen = sizeof(pc);
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, (void *)&pc, &pclen) == 0) {
peer->uid = pc.uid;
peer->gid = pc.gid;
peer->pid = pc.pid;
return 0;
}
}
#endif
#if defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
{
struct xucred peercred;
socklen_t peercredlen = sizeof(peercred);
if (getsockopt(s, LOCAL_PEERCRED, 1,
(void *)&peercred, &peercredlen) == 0
&& peercred.cr_version == XUCRED_VERSION)
{
peer->uid = peercred.cr_uid;
peer->gid = peercred.cr_gid;
peer->pid = 0;
return 0;
}
}
#endif
#if defined(SOCKCREDSIZE) && defined(SCM_CREDS)
/* NetBSD */
if (peer->uid == -1) {
struct msghdr msg;
socklen_t crmsgsize;
void *crmsg;
struct cmsghdr *cmp;
struct sockcred *sc;
memset(&msg, 0, sizeof(msg));
crmsgsize = CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX));
if (crmsgsize == 0)
return 1 ;
crmsg = malloc(crmsgsize);
if (crmsg == NULL)
goto failed_scm_creds;
memset(crmsg, 0, crmsgsize);
msg.msg_control = crmsg;
msg.msg_controllen = crmsgsize;
if (recvmsg(s, &msg, 0) < 0) {
free(crmsg);
goto failed_scm_creds;
}
if (msg.msg_controllen == 0 || (msg.msg_flags & MSG_CTRUNC) != 0) {
free(crmsg);
goto failed_scm_creds;
}
cmp = CMSG_FIRSTHDR(&msg);
if (cmp->cmsg_level != SOL_SOCKET || cmp->cmsg_type != SCM_CREDS) {
free(crmsg);
goto failed_scm_creds;
}
sc = (struct sockcred *)(void *)CMSG_DATA(cmp);
peer->uid = sc->sc_euid;
peer->gid = sc->sc_egid;
peer->pid = 0;
free(crmsg);
return 0;
} else {
/* we already got the cred, just return it */
return 0;
}
failed_scm_creds:
#endif
krb5_warn(kcm_context, errno, "failed to determine peer identity");
return 1;
}
/*
* Create the socket (family, type, port) in `d'
*/
static void
init_socket(struct descr *d)
{
struct sockaddr_un un;
struct sockaddr *sa = (struct sockaddr *)&un;
krb5_socklen_t sa_size = sizeof(un);
init_descr (d);
un.sun_family = AF_UNIX;
if (socket_path != NULL)
d->path = socket_path;
else
d->path = _PATH_KCM_SOCKET;
strlcpy(un.sun_path, d->path, sizeof(un.sun_path));
d->s = socket(AF_UNIX, SOCK_STREAM, 0);
if (d->s < 0){
krb5_warn(kcm_context, errno, "socket(%d, %d, 0)", AF_UNIX, SOCK_STREAM);
d->s = -1;
return;
}
#if defined(HAVE_SETSOCKOPT) && defined(SOL_SOCKET) && defined(SO_REUSEADDR)
{
int one = 1;
setsockopt(d->s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
}
#endif
#ifdef LOCAL_CREDS
{
int one = 1;
setsockopt(d->s, 0, LOCAL_CREDS, (void *)&one, sizeof(one));
}
#endif
d->type = SOCK_STREAM;
unlink(d->path);
if (bind(d->s, sa, sa_size) < 0) {
krb5_warn(kcm_context, errno, "bind %s", un.sun_path);
close(d->s);
d->s = -1;
return;
}
if (listen(d->s, SOMAXCONN) < 0) {
krb5_warn(kcm_context, errno, "listen %s", un.sun_path);
close(d->s);
d->s = -1;
return;
}
chmod(d->path, 0777);
return;
}
/*
* Allocate descriptors for all the sockets that we should listen on
* and return the number of them.
*/
static int
init_sockets(struct descr **desc)
{
struct descr *d;
size_t num = 0;
d = (struct descr *)malloc(sizeof(*d));
if (d == NULL) {
krb5_errx(kcm_context, 1, "malloc failed");
}
init_socket(d);
if (d->s != -1) {
kcm_log(5, "listening on domain socket %s", d->path);
num++;
}
reinit_descrs (d, num);
*desc = d;
return num;
}
/*
* handle the request in `buf, len', from `addr' (or `from' as a string),
* sending a reply in `reply'.
*/
static int
process_request(unsigned char *buf,
size_t len,
krb5_data *reply,
kcm_client *client)
{
krb5_data request;
if (len < 4) {
kcm_log(1, "malformed request from process %d (too short)",
client->pid);
return -1;
}
if (buf[0] != KCM_PROTOCOL_VERSION_MAJOR ||
buf[1] != KCM_PROTOCOL_VERSION_MINOR) {
kcm_log(1, "incorrect protocol version %d.%d from process %d",
buf[0], buf[1], client->pid);
return -1;
}
buf += 2;
len -= 2;
/* buf is now pointing at opcode */
request.data = buf;
request.length = len;
return kcm_dispatch(kcm_context, client, &request, reply);
}
/*
* Handle the request in `buf, len' to socket `d'
*/
static void
do_request(void *buf, size_t len, struct descr *d)
{
krb5_error_code ret;
krb5_data reply;
reply.length = 0;
ret = process_request(buf, len, &reply, &d->peercred);
if (reply.length != 0) {
unsigned char len[4];
struct msghdr msghdr;
struct iovec iov[2];
kcm_log(5, "sending %lu bytes to process %d",
(unsigned long)reply.length,
(int)d->peercred.pid);
memset (&msghdr, 0, sizeof(msghdr));
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = iov;
msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
#if 0
msghdr.msg_control = NULL;
msghdr.msg_controllen = 0;
#endif
len[0] = (reply.length >> 24) & 0xff;
len[1] = (reply.length >> 16) & 0xff;
len[2] = (reply.length >> 8) & 0xff;
len[3] = reply.length & 0xff;
iov[0].iov_base = (void*)len;
iov[0].iov_len = 4;
iov[1].iov_base = reply.data;
iov[1].iov_len = reply.length;
if (sendmsg (d->s, &msghdr, 0) < 0) {
kcm_log (0, "sendmsg(%d): %d %s", (int)d->peercred.pid,
errno, strerror(errno));
krb5_data_free(&reply);
return;
}
krb5_data_free(&reply);
}
if (ret) {
kcm_log(0, "Failed processing %lu byte request from process %d",
(unsigned long)len, d->peercred.pid);
}
}
static void
clear_descr(struct descr *d)
{
if(d->buf)
memset(d->buf, 0, d->size);
d->len = 0;
if(d->s != -1)
close(d->s);
d->s = -1;
}
#define STREAM_TIMEOUT 4
/*
* accept a new stream connection on `d[parent]' and store it in `d[child]'
*/
static void
add_new_stream (struct descr *d, int parent, int child)
{
int s;
if (child == -1)
return;
d[child].peercred.pid = -1;
d[child].peercred.uid = -1;
d[child].peercred.gid = -1;
d[child].sock_len = sizeof(d[child].__ss);
s = accept(d[parent].s, d[child].sa, &d[child].sock_len);
if(s < 0) {
krb5_warn(kcm_context, errno, "accept");
return;
}
if (s >= FD_SETSIZE) {
krb5_warnx(kcm_context, "socket FD too large");
close (s);
return;
}
d[child].s = s;
d[child].timeout = time(NULL) + STREAM_TIMEOUT;
d[child].type = SOCK_STREAM;
}
/*
* Grow `d' to handle at least `n'.
* Return != 0 if fails
*/
static int
grow_descr (struct descr *d, size_t n)
{
if (d->size - d->len < n) {
unsigned char *tmp;
size_t grow;
grow = max(1024, d->len + n);
if (d->size + grow > max_request) {
kcm_log(0, "Request exceeds max request size (%lu bytes).",
(unsigned long)d->size + grow);
clear_descr(d);
return -1;
}
tmp = realloc (d->buf, d->size + grow);
if (tmp == NULL) {
kcm_log(0, "Failed to re-allocate %lu bytes.",
(unsigned long)d->size + grow);
clear_descr(d);
return -1;
}
d->size += grow;
d->buf = tmp;
}
return 0;
}
/*
* Handle incoming data to the stream socket in `d[index]'
*/
static void
handle_stream(struct descr *d, int index, int min_free)
{
unsigned char buf[1024];
int n;
int ret = 0;
if (d[index].timeout == 0) {
add_new_stream (d, index, min_free);
return;
}
if (update_client_creds(d[index].s, &d[index].peercred)) {
krb5_warnx(kcm_context, "failed to update peer identity");
clear_descr(d + index);
return;
}
if (d[index].peercred.uid == -1) {
krb5_warnx(kcm_context, "failed to determine peer identity");
clear_descr (d + index);
return;
}
n = recvfrom(d[index].s, buf, sizeof(buf), 0, NULL, NULL);
if (n < 0) {
krb5_warn(kcm_context, errno, "recvfrom");
return;
} else if (n == 0) {
krb5_warnx(kcm_context, "connection closed before end of data "
"after %lu bytes from process %ld",
(unsigned long) d[index].len, (long) d[index].peercred.pid);
clear_descr (d + index);
return;
}
if (grow_descr (&d[index], n))
return;
memcpy(d[index].buf + d[index].len, buf, n);
d[index].len += n;
if (d[index].len > 4) {
krb5_storage *sp;
int32_t len;
sp = krb5_storage_from_mem(d[index].buf, d[index].len);
if (sp == NULL) {
kcm_log (0, "krb5_storage_from_mem failed");
ret = -1;
} else {
krb5_ret_int32(sp, &len);
krb5_storage_free(sp);
if (d[index].len - 4 >= len) {
memmove(d[index].buf, d[index].buf + 4, d[index].len - 4);
ret = 1;
} else
ret = 0;
}
}
if (ret < 0)
return;
else if (ret == 1) {
do_request(d[index].buf, d[index].len, &d[index]);
clear_descr(d + index);
}
}
#ifdef HAVE_DOOR_CREATE
static void
kcm_door_server(void *cookie, char *argp, size_t arg_size,
door_desc_t *dp, uint_t n_desc)
{
kcm_client peercred;
door_cred_t cred;
krb5_error_code ret;
krb5_data reply;
size_t length;
char *p;
reply.length = 0;
p = NULL;
length = 0;
if (door_cred(&cred) != 0) {
kcm_log(0, "door_cred failed with %s", strerror(errno));
goto out;
}
peercred.uid = cred.dc_euid;
peercred.gid = cred.dc_egid;
peercred.pid = cred.dc_pid;
ret = process_request((unsigned char*)argp, arg_size, &reply, &peercred);
if (reply.length != 0) {
p = alloca(reply.length); /* XXX don't use alloca */
if (p) {
memcpy(p, reply.data, reply.length);
length = reply.length;
}
krb5_data_free(&reply);
}
out:
door_return(p, length, NULL, 0);
}
static void
kcm_setup_door(void)
{
int fd, ret;
char *path;
fd = door_create(kcm_door_server, NULL, 0);
if (fd < 0)
krb5_err(kcm_context, 1, errno, "Failed to create door");
if (door_path != NULL)
path = door_path;
else
path = _PATH_KCM_DOOR;
unlink(path);
ret = open(path, O_RDWR | O_CREAT, 0666);
if (ret < 0)
krb5_err(kcm_context, 1, errno, "Failed to create/open door");
close(ret);
ret = fattach(fd, path);
if (ret < 0)
krb5_err(kcm_context, 1, errno, "Failed to attach door");
}
#endif /* HAVE_DOOR_CREATE */
void
kcm_loop(void)
{
struct descr *d;
int ndescr;
#ifdef HAVE_DOOR_CREATE
kcm_setup_door();
#endif
ndescr = init_sockets(&d);
if (ndescr <= 0) {
krb5_warnx(kcm_context, "No sockets!");
#ifndef HAVE_DOOR_CREATE
exit(1);
#endif
}
while (exit_flag == 0){
struct timeval tmout;
fd_set fds;
int min_free = -1;
int max_fd = 0;
int i;
FD_ZERO(&fds);
for(i = 0; i < ndescr; i++) {
if (d[i].s >= 0){
if(d[i].type == SOCK_STREAM &&
d[i].timeout && d[i].timeout < time(NULL)) {
kcm_log(1, "Stream connection from %d expired after %lu bytes",
d[i].peercred.pid, (unsigned long)d[i].len);
clear_descr(&d[i]);
continue;
}
if (max_fd < d[i].s)
max_fd = d[i].s;
if (max_fd >= FD_SETSIZE)
krb5_errx(kcm_context, 1, "fd too large");
FD_SET(d[i].s, &fds);
} else if (min_free < 0 || i < min_free)
min_free = i;
}
if (min_free == -1) {
struct descr *tmp;
tmp = realloc(d, (ndescr + 4) * sizeof(*d));
if(tmp == NULL)
krb5_warnx(kcm_context, "No memory");
else {
d = tmp;
reinit_descrs (d, ndescr);
memset(d + ndescr, 0, 4 * sizeof(*d));
for(i = ndescr; i < ndescr + 4; i++)
init_descr (&d[i]);
min_free = ndescr;
ndescr += 4;
}
}
tmout.tv_sec = STREAM_TIMEOUT;
tmout.tv_usec = 0;
switch (select(max_fd + 1, &fds, 0, 0, &tmout)){
case 0:
kcm_run_events(kcm_context, time(NULL));
break;
case -1:
if (errno != EINTR)
krb5_warn(kcm_context, errno, "select");
break;
default:
for(i = 0; i < ndescr; i++) {
if(d[i].s >= 0 && FD_ISSET(d[i].s, &fds)) {
if (d[i].type == SOCK_STREAM)
handle_stream(d, i, min_free);
}
}
kcm_run_events(kcm_context, time(NULL));
break;
}
}
if (d->path != NULL)
unlink(d->path);
free(d);
}