mirror of
https://github.com/openssh/libopenssh
synced 2026-04-16 17:56:33 +00:00
498 lines
12 KiB
C
498 lines
12 KiB
C
/* $OpenBSD: $ */
|
|
/*
|
|
* Copyright (c) 2012 Markus Friedl. All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <errno.h>
|
|
#include <event.h>
|
|
#include <fcntl.h>
|
|
#include <netdb.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include "ssh_api.h"
|
|
#include "log.h"
|
|
#include "misc.h"
|
|
#include "myproposal.h"
|
|
#include "readconf.h"
|
|
#include "authfile.h"
|
|
#include "err.h"
|
|
#include "sshbuf.h"
|
|
|
|
struct side {
|
|
int fd;
|
|
struct event input, output;
|
|
struct ssh *ssh;
|
|
};
|
|
#define SESSION_CONNECTED 0x01
|
|
#define SESSION_NEEDS_FLUSH 0x02
|
|
struct session {
|
|
struct side client, server;
|
|
TAILQ_ENTRY(session) next;
|
|
int flags;
|
|
};
|
|
Forward fwd;
|
|
|
|
void accept_cb(int, short, void *);
|
|
void connect_cb(int, short, void *);
|
|
void input_cb(int, short, void *);
|
|
void output_cb(int, short, void *);
|
|
|
|
int do_connect(const char *, int);
|
|
int do_listen(const char *, int);
|
|
void session_close(struct session *);
|
|
int ssh_packet_fwd(struct side *, struct side *);
|
|
int ssh_prepare_output(struct side *);
|
|
void usage(void);
|
|
|
|
uid_t original_real_uid; /* XXX */
|
|
TAILQ_HEAD(, session) sessions;
|
|
struct kex_params kex_params;
|
|
int foreground;
|
|
int dump_packets;
|
|
|
|
#define BUFSZ 16*1024
|
|
struct sshkey *hostkey, *known_hostkey;
|
|
|
|
int
|
|
do_listen(const char *addr, int port)
|
|
{
|
|
int sock, on = 1;
|
|
struct addrinfo hints, *ai, *aitop;
|
|
char strport[NI_MAXSERV];
|
|
snprintf(strport, sizeof strport, "%d", port);
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
if (getaddrinfo(addr, strport, &hints, &aitop) != 0) {
|
|
error("getaddrinfo: fatal error");
|
|
return -1;
|
|
}
|
|
sock = -1;
|
|
for (ai = aitop; ai; ai = ai->ai_next) {
|
|
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
|
|
continue;
|
|
if ((sock = socket(ai->ai_family, SOCK_STREAM, 0)) < 0)
|
|
continue;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on,
|
|
sizeof(on)) == -1) {
|
|
error("setsockopt: %s", strerror(errno));
|
|
close(sock);
|
|
sock = -1;
|
|
continue;
|
|
}
|
|
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
|
|
error("bind: %s", strerror(errno));
|
|
close(sock);
|
|
sock = -1;
|
|
continue;
|
|
}
|
|
if (listen(sock, 5) < 0) {
|
|
error("listen: %s", strerror(errno));
|
|
close(sock);
|
|
sock = -1;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
freeaddrinfo(aitop);
|
|
return sock;
|
|
}
|
|
|
|
int
|
|
do_connect(const char *addr, int port)
|
|
{
|
|
struct addrinfo hints, *ai, *aitop;
|
|
int gaierr;
|
|
int sock = -1;
|
|
char strport[NI_MAXSERV];
|
|
snprintf(strport, sizeof strport, "%d", port);
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0) {
|
|
error("getaddrinfo %s: %s", addr, gai_strerror(gaierr));
|
|
return -1;
|
|
}
|
|
for (ai = aitop; ai; ai = ai->ai_next) {
|
|
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
|
|
continue;
|
|
if ((sock = socket(ai->ai_family, SOCK_STREAM, 0)) < 0) {
|
|
error("socket: %s", strerror(errno));
|
|
continue;
|
|
}
|
|
if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
|
|
error("fcntl connect F_SETFL: %s", strerror(errno));
|
|
close(sock);
|
|
sock = -1;
|
|
continue;
|
|
}
|
|
if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 &&
|
|
errno != EINPROGRESS) {
|
|
error("connect(%s, %d): %s", addr, port, strerror(errno));
|
|
close(sock);
|
|
sock = -1;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
freeaddrinfo(aitop);
|
|
if (!ai)
|
|
return -1;
|
|
return sock;
|
|
}
|
|
|
|
void
|
|
session_close(struct session *s)
|
|
{
|
|
if (s->client.fd != -1)
|
|
close(s->client.fd);
|
|
if (s->server.fd != -1)
|
|
close(s->server.fd);
|
|
if (s->flags & SESSION_CONNECTED) {
|
|
event_del(&s->client.input);
|
|
event_del(&s->client.output);
|
|
event_del(&s->server.input);
|
|
event_del(&s->server.output);
|
|
if (s->client.ssh) {
|
|
ssh_free(s->client.ssh);
|
|
s->client.ssh = NULL;
|
|
}
|
|
if (s->server.ssh) {
|
|
ssh_free(s->server.ssh);
|
|
s->server.ssh = NULL;
|
|
}
|
|
TAILQ_REMOVE(&sessions, s, next);
|
|
}
|
|
debug2("closing session %p", s);
|
|
free(s);
|
|
}
|
|
|
|
void
|
|
accept_cb(int fd, short type, void *arg)
|
|
{
|
|
struct event *ev = arg;
|
|
struct session *s = NULL;
|
|
socklen_t addrlen;
|
|
struct sockaddr addr;
|
|
int acceptfd = -1;
|
|
|
|
if ((acceptfd = accept(fd, &addr, &addrlen)) < 0) {
|
|
fatal("accept: %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
if (fcntl(acceptfd, F_SETFL, O_NONBLOCK) < 0) {
|
|
error("fcntl accepted F_SETFL: %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
if ((s = calloc(1, sizeof(struct session))) == NULL) {
|
|
error("calloc: %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
s->server.fd = -1;
|
|
s->client.fd = acceptfd;
|
|
event_add(ev, NULL);
|
|
addrlen = sizeof(addr);
|
|
if ((s->server.fd = do_connect(fwd.connect_host, fwd.connect_port)) < 0) {
|
|
error("do_connect() failed");
|
|
goto fail;
|
|
}
|
|
event_set(&s->server.output, s->server.fd, EV_WRITE, connect_cb, s);
|
|
event_add(&s->server.output, NULL);
|
|
debug2("new session %p", s);
|
|
return;
|
|
fail:
|
|
if (acceptfd != -1)
|
|
close(acceptfd);
|
|
if (s)
|
|
free(s);
|
|
}
|
|
|
|
void
|
|
connect_cb(int fd, short type, void *arg)
|
|
{
|
|
struct session *s = arg;
|
|
int soerr;
|
|
int r;
|
|
socklen_t sz = sizeof(soerr);
|
|
|
|
event_del(&s->server.output);
|
|
if (getsockopt(s->server.fd, SOL_SOCKET, SO_ERROR, &soerr, &sz) < 0) {
|
|
soerr = errno;
|
|
error("connect_cb: getsockopt: %s", strerror(errno));
|
|
}
|
|
if (soerr != 0)
|
|
goto fail;
|
|
memcpy(kex_params.proposal, myproposal, sizeof(kex_params.proposal));
|
|
if ((r = ssh_init(&s->client.ssh, 1, &kex_params)) != 0) {
|
|
error("could init client context: %s", ssh_err(r));
|
|
goto fail;
|
|
}
|
|
if ((r = ssh_init(&s->server.ssh, 0, &kex_params)) != 0) {
|
|
error("could init server context: %s", ssh_err(r));
|
|
goto fail;
|
|
}
|
|
if ((r = ssh_add_hostkey(s->client.ssh, hostkey)) != 0) {
|
|
error("could not load server hostkey: %s", ssh_err(r));
|
|
goto fail;
|
|
}
|
|
if ((r = ssh_add_hostkey(s->server.ssh, known_hostkey)) != 0) {
|
|
error("could not load client known hostkey: %s", ssh_err(r));
|
|
goto fail;
|
|
}
|
|
event_set(&s->client.input, s->client.fd, EV_READ, input_cb, s);
|
|
event_set(&s->client.output, s->client.fd, EV_WRITE, output_cb, s);
|
|
event_set(&s->server.input, s->server.fd, EV_READ, input_cb, s);
|
|
event_set(&s->server.output, s->server.fd, EV_WRITE, output_cb, s);
|
|
event_add(&s->server.input, NULL);
|
|
event_add(&s->client.input, NULL);
|
|
s->flags = SESSION_CONNECTED;
|
|
TAILQ_INSERT_TAIL(&sessions, s, next);
|
|
return;
|
|
fail:
|
|
close(s->server.fd);
|
|
s->server.fd = -1;
|
|
session_close(s);
|
|
return;
|
|
}
|
|
|
|
/* schedule output event and return 1 if there is any output pending */
|
|
int
|
|
ssh_prepare_output(struct side *side)
|
|
{
|
|
size_t len;
|
|
|
|
ssh_output_ptr(side->ssh, &len);
|
|
if (len) {
|
|
debug3("output %zd for %d", len, side->fd);
|
|
event_add(&side->output, NULL);
|
|
}
|
|
return len > 0;
|
|
}
|
|
|
|
int
|
|
ssh_packet_fwd(struct side *from, struct side *to)
|
|
{
|
|
struct sshbuf *b;
|
|
const u_char *data;
|
|
u_char type;
|
|
size_t len;
|
|
int ret;
|
|
|
|
if (!from->ssh || !to->ssh)
|
|
return 0;
|
|
for (;;) {
|
|
if ((ret = ssh_packet_next(from->ssh, &type)) != 0)
|
|
return ret;
|
|
if (!type) {
|
|
debug3("no packet on %d", from->fd);
|
|
return 0;
|
|
}
|
|
data = ssh_packet_payload(from->ssh, &len);
|
|
debug("ssh_packet_fwd %d->%d type %d len %zd",
|
|
from->fd, to->fd, type, len);
|
|
if ((dump_packets && type != 50) ||
|
|
dump_packets > 1) {
|
|
if ((b = sshbuf_new()) != NULL) {
|
|
if (sshbuf_put(b, data, len) == 0)
|
|
sshbuf_dump(b, stderr);
|
|
sshbuf_free(b);
|
|
}
|
|
}
|
|
if ((ret = ssh_packet_put(to->ssh, type, data, len)) != 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
void
|
|
input_cb(int fd, short type, void *arg)
|
|
{
|
|
u_char buf[BUFSZ];
|
|
struct session *s = arg;
|
|
struct side *r, *w;
|
|
ssize_t len;
|
|
int pending, r1, r2;
|
|
const char *tag;
|
|
|
|
if (fd == s->client.fd) {
|
|
tag = "client";
|
|
r = &s->client;
|
|
w = &s->server;
|
|
} else {
|
|
tag = "server";
|
|
r = &s->server;
|
|
w = &s->client;
|
|
}
|
|
debug2("input_cb %s fd %d", tag, fd);
|
|
len = read(fd, buf, sizeof(buf));
|
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) {
|
|
event_add(&r->input, NULL);
|
|
} else if (len <= 0) {
|
|
debug("read %s failed fd %d len %zd", tag, fd, len);
|
|
session_close(s);
|
|
return;
|
|
} else {
|
|
debug2("read %s fd %d len %zd", tag, fd, len);
|
|
event_add(&r->input, NULL);
|
|
ssh_input_append(r->ssh, buf, len);
|
|
}
|
|
r1 = ssh_packet_fwd(r, w);
|
|
r2 = ssh_packet_fwd(w, r);
|
|
pending = ssh_prepare_output(r) + ssh_prepare_output(w);
|
|
if (r1 || r1) {
|
|
error("ssh_packet_fwd: %s/%s", ssh_err(r1), ssh_err(r2));
|
|
if (pending)
|
|
s->flags |= SESSION_NEEDS_FLUSH;
|
|
else
|
|
session_close(s);
|
|
}
|
|
}
|
|
|
|
void
|
|
output_cb(int fd, short type, void *arg)
|
|
{
|
|
struct session *s = arg;
|
|
struct side *r, *w;
|
|
ssize_t len, olen;
|
|
int pending;
|
|
const char *tag;
|
|
const u_char *obuf;
|
|
|
|
if (fd == s->client.fd) {
|
|
tag = "client";
|
|
w = &s->client;
|
|
r = &s->server;
|
|
} else {
|
|
tag = "server";
|
|
w = &s->server;
|
|
r = &s->client;
|
|
}
|
|
debug2("output_cb %s fd %d", tag, fd);
|
|
obuf = ssh_output_ptr(w->ssh, &olen);
|
|
if (olen > 0) {
|
|
len = write(fd, obuf, olen);
|
|
if (len < 0 && (errno == EINTR || errno == EAGAIN)) {
|
|
event_add(&w->output, NULL);
|
|
} else if (len <= 0) {
|
|
debug("write %s failed fd %d len %zd", tag, fd, len);
|
|
session_close(s);
|
|
return;
|
|
} else if (len < olen) {
|
|
debug("write %s partial fd %d len %zd olen %zd",
|
|
tag, fd, len, olen);
|
|
ssh_output_consume(w->ssh, len);
|
|
} else {
|
|
debug2("write %s done fd %d", tag, fd);
|
|
ssh_output_consume(w->ssh, len);
|
|
}
|
|
}
|
|
if (!(s->flags & SESSION_NEEDS_FLUSH)) {
|
|
ssh_packet_fwd(r, w);
|
|
ssh_packet_fwd(w, r);
|
|
}
|
|
pending = ssh_prepare_output(r) + ssh_prepare_output(w);
|
|
if ((s->flags & SESSION_NEEDS_FLUSH) && !pending) {
|
|
debug("delayed close %p", s);
|
|
s->flags &= ~SESSION_NEEDS_FLUSH;
|
|
session_close(s);
|
|
}
|
|
}
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
extern char *__progname;
|
|
|
|
fprintf(stderr,
|
|
"usage: %s [-dfh] [-L [laddr:]lport:saddr:sport]"
|
|
" [-C knownkey] [-S serverkey]\n",
|
|
__progname);
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int ch, log_stderr = 1, fd, r;
|
|
struct event ev;
|
|
char *hostkey_file = NULL, *known_hostkey_file = NULL;
|
|
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
|
|
LogLevel log_level = SYSLOG_LEVEL_VERBOSE;
|
|
extern char *__progname;
|
|
|
|
TAILQ_INIT(&sessions);
|
|
|
|
while ((ch = getopt(argc, argv, "dfC:DL:S:")) != -1) {
|
|
switch (ch) {
|
|
case 'd':
|
|
if (log_level == SYSLOG_LEVEL_VERBOSE)
|
|
log_level = SYSLOG_LEVEL_DEBUG1;
|
|
else if (log_level == SYSLOG_LEVEL_DEBUG1)
|
|
log_level = SYSLOG_LEVEL_DEBUG2;
|
|
else if (log_level == SYSLOG_LEVEL_DEBUG2)
|
|
log_level = SYSLOG_LEVEL_DEBUG3;
|
|
break;
|
|
case 'f':
|
|
foreground = 1;
|
|
break;
|
|
case 'C':
|
|
known_hostkey_file = optarg;
|
|
break;
|
|
case 'D':
|
|
foreground = 1;
|
|
dump_packets++;
|
|
break;
|
|
case 'L':
|
|
if (parse_forward(&fwd, optarg, 0, 0) == 0)
|
|
fatal("cannot parse: %s", optarg);
|
|
if (fwd.listen_host == NULL)
|
|
fwd.listen_host = "0.0.0.0";
|
|
break;
|
|
case 'S':
|
|
hostkey_file = optarg;
|
|
break;
|
|
default:
|
|
usage();
|
|
break;
|
|
}
|
|
}
|
|
log_init(__progname, log_level, log_facility, log_stderr);
|
|
if (hostkey_file &&
|
|
(r = sshkey_load_private(hostkey_file, "", &hostkey, NULL)) != 0)
|
|
fatal("sshkey_load_private: %s: %s", hostkey_file, ssh_err(r));
|
|
if (known_hostkey_file &&
|
|
(r = sshkey_load_public(known_hostkey_file, &known_hostkey,
|
|
NULL)) != 0)
|
|
fatal("sshkey_load_public: %s: %s", known_hostkey_file,
|
|
ssh_err(r));
|
|
if (!foreground)
|
|
daemon(0, 0);
|
|
event_init();
|
|
if ((fd = do_listen(fwd.listen_host, fwd.listen_port)) < 0)
|
|
fatal(" do_listen failed");
|
|
event_set(&ev, fd, EV_READ, accept_cb, &ev);
|
|
event_add(&ev, NULL);
|
|
event_dispatch();
|
|
exit(1);
|
|
}
|