altprivsep.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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
* 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"
#include "includes.h"
#include "atomicio.h"
#include "auth.h"
#include "bufaux.h"
#include "buffer.h"
#include "cipher.h"
#include "compat.h"
#include "dispatch.h"
#include "getput.h"
#include "kex.h"
#include "log.h"
#include "mac.h"
#include "packet.h"
#include "uidswap.h"
#include "ssh2.h"
#include "sshlogin.h"
#include "xmalloc.h"
#include "altprivsep.h"
#include <fcntl.h>
static Buffer to_monitor;
static Buffer from_monitor;
/*
* Sun's Alternative Privilege Separation basics:
*
* Abstract
* --------
*
* sshd(1M) fork()s and drops privs in the child while retaining privs
* in the parent (a.k.a., the monitor). The unprivileged sshd and the
* monitor talk over a pipe using a simple protocol.
*
* The monitor protocol is all about having the monitor carry out the
* only operations that require privileges OR access to privileged
* SSHv2 re-keying.
*
* Re-Keying
* ---------
*
* Re-keying is the only protocol version specific aspect of sshd in
* which the monitor gets involved.
*
* The monitor processes all SSHv2 re-key protocol packets, but the
* unprivileged sshd process does the transport layer crypto for those
* packets.
*
* The monitor and its unprivileged sshd child process treat
* SSH_MSG_NEWKEYS SSH2 messages specially: a) the monitor does not call
* set_newkeys(), but b) the child asks the monitor for the set of
* negotiated algorithms, key, IV and what not for the relevant
* transport direction and then calls set_newkeys().
*
* Monitor Protocol
* ----------------
*
* Monitor IPC message formats are similar to SSHv2 messages, minus
* compression, encryption, padding and MACs:
*
* - 4 octet message length
* - message data
* - 1 octet message type
* - message data
*
* In broad strokes:
*
* - IPC: pipe, exit(2)/wait4(2)
*
* - threads: the monitor and child are single-threaded
*
* - monitor main loop: a variant of server_loop2(), for re-keying only
* - unpriv child main loop: server_loop2(), as usual
*
* - protocol:
* - key exchange packets are always forwarded as is to the monitor
* - newkeys, record_login(), record_logout() are special packets
* using the packet type range reserved for local extensions
*
* - the child drops privs and runs like a normal sshd, except that it
* sets dispatch handlers for key exchange packets that forward the
* packets to the monitor
*
* Event loops:
*
* - all monitor protocols are synchronous: because the SSHv2 rekey
* protocols are synchronous and because the other monitor operations
* are synchronous (or have no replies),
*
* - server_loop2() is modified to check the monitor pipe for rekey
* packets to forward to the client
*
* - and dispatch handlers are set, upon receipt of KEXINIT (and reset
* when NEWKEYS is sent out) to forward incoming rekey packets to the
* monitor.
*
* - the monitor runs an event loop not unlike server_loop2() and runs
* key exchanges almost exactly as a pre-altprivsep sshd would
*
* - unpriv sshd exit -> monitor cleanup (including audit logout) and exit
*
* child (so that the audit event for the logout better reflects
* reality -- i.e., logged out means logged out, but for bg jobs)
*
* Message formats:
*
*
* - all other monitor requests are sent as SSH2_PRIV_MSG_ALTPRIVSEP and have a
* sub-type identifier (one octet)
* - private request sub-types include:
* - get new shared secret from last re-key
* pid, ttyname, program name
*
* Reply sub-types include:
*
* - NOP (for record_login/logout)
* - new shared secret from last re-key
*/
static int aps_started = 0;
static int is_monitor = 0;
static int pipe_fds[2];
static int pipe_fd = -1;
static Authctxt *xxx_authctxt;
/* Monitor functions */
static void aps_record_login(void);
static void aps_record_logout(void);
/* Altprivsep packet utilities for communication with the monitor */
static void altprivsep_packet_start(u_char);
static int altprivsep_packet_send(void);
static int altprivsep_packet_read(void);
static void altprivsep_packet_read_expect(int type);
static void altprivsep_packet_put_char(int ch);
static void altprivsep_packet_put_cstring(const char *str);
static u_int altprivsep_packet_get_char(void);
/*
* Start monitor from privileged sshd process.
*
* Return values are like fork(2); the parent is the monitor. The caller should
* fatal() on error.
*
* Privileges are dropped, on the unprivileged side, upon success.
*/
{
int junk;
fatal("Monitor startup failed: missing state");
error("Monitor startup failure: could not create pipes: %s",
return (-1);
}
monitor_pid = getpid();
/* parent */
debug2("Monitor pid %ld, unprivileged child pid %ld",
/* signal readyness of monitor */
aps_started = 1;
is_monitor = 1;
debug2("Monitor started");
set_log_txt_prefix("monitor ");
return (pid);
}
if (pid < 0) {
debug2("Monitor startup failure: could not fork unprivileged"
return (pid);
}
/* caller should drop privs */
/* wait for monitor to be ready */
debug2("Waiting for monitor");
debug2("Monitor signalled readiness");
if (compat20) {
debug3("Setting handler to forward re-key packets to monitor");
}
/* AltPrivSep interfaces are set up */
aps_started = 1;
return (pid);
}
int
altprivsep_get_pipe_fd(void)
{
return (pipe_fd);
}
void
{
fatal("Missing key exchange context in unprivileged process");
if (type != SSH2_MSG_NEWKEYS)
if (!altprivsep_fwd_packet(type))
fatal("Monitor not responding");
/* tell server_loop2() that we're re-keying */
/* NEWKEYS is special: get the new keys for client->server direction */
if (type == SSH2_MSG_NEWKEYS) {
debug2("Getting new inbound keystate from monitor");
}
}
void
{
void *data;
int type;
if (pipe_fd == -1)
return;
return;
fatal("Monitor not responding");
if (!compat20)
return; /* shouldn't happen! but be safe */
if (type == 0)
return; /* EOF -- nothing to do here */
if (type >= SSH2_MSG_MAX)
fatal("Received garbage from monitor");
if (type == SSH2_PRIV_MSG_ALTPRIVSEP)
return; /* shouldn't happen! */
/* NEWKEYS is special: get the new keys for server->client direction */
if (type == SSH2_MSG_NEWKEYS) {
debug2("Getting new outbound keystate from monitor");
packet_send();
return;
}
packet_send();
}
void
{
}
int
altprivsep_started(void)
{
return (aps_started);
}
int
altprivsep_is_monitor(void)
{
return (is_monitor);
}
/*
* A fatal cleanup function to forcibly shutdown the connection socket
*/
void
altprivsep_shutdown_sock(void *arg)
{
int sock;
return;
}
/* Calls _to_ monitor from unprivileged process */
static
int
{
void *data;
/* packet_send()s any replies from the monitor to the client */
return (altprivsep_packet_send());
}
/* To be called from packet.c:set_newkeys() before referencing current_keys */
void
{
if (!altprivsep_started())
return;
if (altprivsep_is_monitor())
return; /* shouldn't happen */
/* request new keys */
fatal("Received garbage from monitor during re-keying");
/* Cipher name, key, IV */
fatal("Monitor negotiated an unknown cipher during re-key");
/* MAC name */
fatal("Monitor negotiated an unknown MAC algorithm "
"during re-key");
/* Compression algorithm name */
fatal("Monitor negotiated an unknown compression "
"algorithm during re-key");
/*
* Now install new keys
*
* the many internal interfaces are parametrized, made reentrant
* and thread-safe, made more consistent, and when necessary-but-
* currently-missing interfaces are added then this bit of
* ugliness can be revisited.
*
* The ugliness is in the set_newkeys(), its name and the lack
* of a (Newkeys *) parameter, which forces us to pass the
* newkeys through current_keys[mode]. But this saves us some
* lines of code for now, though not comments.
*
* Also, we've abused, in the code above, knowledge of what
* set_newkeys() expects the current_keys[mode] to contain.
*/
}
void
{
}
void
{
}
static void aps_send_newkeys(void);
/* Monitor side dispatch handler for SSH2_PRIV_MSG_ALTPRIVSEP */
/* ARGSUSED */
void
{
req_type = packet_get_char();
switch (req_type) {
case APS_MSG_NEWKEYS_REQ:
break;
case APS_MSG_RECORD_LOGIN:
break;
case APS_MSG_RECORD_LOGOUT:
break;
default:
break;
}
}
/* Monitor-side handlers for APS_MSG_* */
static
void
aps_send_newkeys(void)
{
/* get direction for which newkeys are wanted */
/* get those newkeys */
/*
* Negotiated algorithms, client->server and server->client, for
* cipher, mac and compression.
*/
packet_send();
}
struct _aps_login_rec {
char *lr_tty;
struct _aps_login_rec *next;
};
typedef struct _aps_login_rec aps_login_rec;
static
void
aps_record_login(void)
{
char *proc_path;
debug2("Spurious record_login request from unprivileged sshd");
return;
}
/* Insert new record on list */
xxx_authctxt->user);
packet_send();
}
static
void
aps_record_logout(void)
{
aps_login_rec **p, *q;
pid = packet_get_int();
q = *p;
xxx_authctxt->user);
/* dequeue */
*p = q->next;
xfree(q);
break;
}
}
packet_send();
}
/* Utilities for communication with the monitor */
static
void
{
}
static
void
{
}
static
void
{
}
static
void
altprivsep_packet_put_cstring(const char *str)
{
}
static
void
{
}
/*
* Send a monitor packet to the monitor. This function is blocking.
*
* Returns -1 if the monitor pipe has been closed earlier, fatal()s if
* there's any other problems.
*/
static
int
altprivsep_packet_send(void)
{
if (pipe_fd == -1)
return (-1);
return (0);
/*
* We talk the SSHv2 binary packet protocol to the monitor,
* using the none cipher, mac and compression algorithms.
*
* But, interestingly, the none cipher has a block size of 8
* bytes, thus we must pad the packet.
*
* Also, encryption includes the packet length, so the padding
* must account for that field. I.e., (sizeof (packet length) +
* sizeof (padding length) + packet length + padding length) %
* block_size must == 0.
*
* Also, there must be at least four (4) bytes of padding.
*/
if (padlen < 4)
padlen += 8;
/* packet length counts padding and padding length field */
continue;
else
goto pipe_gone;
}
/* packet length field */
goto pipe_gone;
/* padding length field */
goto pipe_gone;
goto pipe_gone;
return (1);
pipe_fd = -1;
fatal("Monitor not responding");
/* NOTREACHED */
return (0);
}
/*
* Read a monitor packet from the monitor. This function is blocking.
*/
static
int
altprivsep_packet_read(void)
{
if (pipe_fd == -1)
return (-1);
continue;
else
goto pipe_gone;
}
/* packet length field */
goto pipe_gone;
/* padding length field */
goto pipe_gone;
/* packet data + padding */
goto pipe_gone;
/* remove padding */
if (padlen > 0)
/* packet type */
return (buffer_get_char(&from_monitor));
pipe_fd = -1;
if (len < 0)
fatal("Monitor not responding");
debug2("Monitor pipe closed by monitor");
return (0);
}
static
void
{
int type;
if (type <= 0)
fatal("Monitor not responding");
fatal("Protocol error in privilege separation; expected "
}
static
{
return (buffer_get_char(&from_monitor));
}
void
{
if (length_ptr != NULL)
return (buffer_ptr(&from_monitor));
}
void
{
}