From 48e671d5303c70deea9cde5c1a785882c0501f6e Mon Sep 17 00:00:00 2001 From: damien Date: Fri, 14 Jan 2000 04:45:46 +0000 Subject: [PATCH] - Merged OpenBSD IPv6 patch: - [sshd.c sshd.8 sshconnect.c ssh.h ssh.c servconf.h servconf.c scp.1] [scp.c packet.h packet.c login.c log.c canohost.c channels.c] [hostfile.c sshd_config] ipv6 support: mostly gethostbyname->getaddrinfo/getnameinfo, new features: sshd allows multiple ListenAddress and Port options. note that libwrap is not IPv6-ready. (based on patches from fujiwara@rcac.tdi.co.jp) - [ssh.c canohost.c] more hints (hints.ai_socktype=SOCK_STREAM) for getaddrinfo, from itojun@ - [channels.c] listen on _all_ interfaces for X11-Fwd (hints.ai_flags = AI_PASSIVE) - [packet.h] allow auth-kerberos for IPv4 only - [scp.1 sshd.8 servconf.h scp.c] document -4, -6, and 'ssh -L 2022/::1/22' - [ssh.c] 'ssh @host' is illegal (null user name), from karsten@gedankenpolizei.de - [sshconnect.c] better error message - [sshd.c] allow auth-kerberos for IPv4 only - Big IPv6 merge: - Cleanup overrun in sockaddr copying on RHL 6.1 - Replacements for getaddrinfo, getnameinfo, etc based on versions from patch from KIKUCHI Takahiro - Replacement for missing structures on systems that lack IPv6 - record_login needed to know about AF_INET6 addresses - Borrowed more code from OpenBSD: rresvport_af and requisites --- ChangeLog | 33 ++++ Makefile.in | 62 +++----- TODO | 2 + acconfig.h | 28 ++-- bsd-bindresvport.c | 116 ++++++++++++++ bsd-bindresvport.h | 10 ++ bsd-rresvport.c | 107 +++++++++++++ bsd-rresvport.h | 10 ++ canohost.c | 182 +++++++++++---------- channels.c | 386 ++++++++++++++++++++++++++------------------- configure.in | 88 ++++++++++- defines.h | 4 + fake-gai-errnos.h | 12 ++ fake-getaddrinfo.c | 119 ++++++++++++++ fake-getaddrinfo.h | 44 ++++++ fake-getnameinfo.c | 61 +++++++ fake-getnameinfo.h | 17 ++ fake-socket.h | 49 ++++++ fixpaths | 2 - hostfile.c | 2 +- includes.h | 9 ++ log.c | 2 +- login.c | 40 ++++- nchan.c | 28 ++-- nchan.ms | 9 +- packet.c | 63 ++++++-- packet.h | 4 + scp.1.in | 10 +- scp.c | 52 +++++- servconf.c | 86 +++++++--- servconf.h | 10 +- ssh.1.in | 20 ++- ssh.c | 52 ++++-- ssh.h | 15 +- sshconnect.c | 205 +++++++++++------------- sshd.8.in | 15 +- sshd.c | 247 +++++++++++++++++++---------- sshd_config.in | 1 + 38 files changed, 1616 insertions(+), 586 deletions(-) create mode 100644 bsd-bindresvport.c create mode 100644 bsd-bindresvport.h create mode 100644 bsd-rresvport.c create mode 100644 bsd-rresvport.h create mode 100644 fake-gai-errnos.h create mode 100644 fake-getaddrinfo.c create mode 100644 fake-getaddrinfo.h create mode 100644 fake-getnameinfo.c create mode 100644 fake-getnameinfo.h create mode 100644 fake-socket.h diff --git a/ChangeLog b/ChangeLog index e96dfb80..701bf403 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,36 @@ +20000114 + - Merged OpenBSD IPv6 patch: + - [sshd.c sshd.8 sshconnect.c ssh.h ssh.c servconf.h servconf.c scp.1] + [scp.c packet.h packet.c login.c log.c canohost.c channels.c] + [hostfile.c sshd_config] + ipv6 support: mostly gethostbyname->getaddrinfo/getnameinfo, new + features: sshd allows multiple ListenAddress and Port options. note + that libwrap is not IPv6-ready. (based on patches from + fujiwara@rcac.tdi.co.jp) + - [ssh.c canohost.c] + more hints (hints.ai_socktype=SOCK_STREAM) for getaddrinfo, + from itojun@ + - [channels.c] + listen on _all_ interfaces for X11-Fwd (hints.ai_flags = AI_PASSIVE) + - [packet.h] + allow auth-kerberos for IPv4 only + - [scp.1 sshd.8 servconf.h scp.c] + document -4, -6, and 'ssh -L 2022/::1/22' + - [ssh.c] + 'ssh @host' is illegal (null user name), from + karsten@gedankenpolizei.de + - [sshconnect.c] + better error message + - [sshd.c] + allow auth-kerberos for IPv4 only + - Big IPv6 merge: + - Cleanup overrun in sockaddr copying on RHL 6.1 + - Replacements for getaddrinfo, getnameinfo, etc based on versions + from patch from KIKUCHI Takahiro + - Replacement for missing structures on systems that lack IPv6 + - record_login needed to know about AF_INET6 addresses + - Borrowed more code from OpenBSD: rresvport_af and requisites + 20000110 - Fixes to auth-skey to enable it to use the standard OpenSSL libraries diff --git a/Makefile.in b/Makefile.in index 23f00582..786cd436 100644 --- a/Makefile.in +++ b/Makefile.in @@ -19,7 +19,6 @@ CC=@CC@ PATHS=-DETCDIR=\"$(sysconfdir)\" -DSSH_PROGRAM=\"$(SSH_PROGRAM)\" -DSSH_ASKPASS_DEFAULT=\"$(ASKPASS_PROGRAM)\" CFLAGS=@CFLAGS@ $(PATHS) @DEFS@ EXTRA_TARGETS=@GNOME_ASKPASS@ -TARGETS=libssh.a ssh sshd ssh-add ssh-keygen ssh-agent scp $(EXTRA_TARGETS) LIBS=@LIBS@ AR=@AR@ RANLIB=@RANLIB@ @@ -30,34 +29,19 @@ LDFLAGS=-L. @LDFLAGS@ GNOME_CFLAGS=`gnome-config --cflags gnome gnomeui` GNOME_LIBS=`gnome-config --libs gnome gnomeui` -OBJS= atomicio.o authfd.o authfile.o auth-krb4.o auth-passwd.o auth-pam.o \ - auth-rhosts.o auth-rh-rsa.o auth-rsa.o auth-skey.o bsd-daemon.o \ - bsd-login.o bsd-misc.o bsd-mktemp.o bsd-snprintf.o bsd-strlcat.o \ - bsd-strlcpy.o bufaux.o buffer.o canohost.o channels.o cipher.o \ - clientloop.o compress.o crc32.o deattack.o hostfile.o \ - log-client.o login.o log-server.o match.o md5crypt.o mpaux.o \ - packet.o pty.o radix.o readconf.o readpass.o rsa.o servconf.o \ - serverloop.o sshconnect.o tildexpand.o ttymodes.o uidswap.o \ - xmalloc.o - -LIBOBJS= atomicio.o authfd.o authfile.o bsd-daemon.o bsd-misc.o \ - bsd-mktemp.o bsd-snprintf.o bsd-strlcat.o bsd-strlcpy.o bufaux.o \ - buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o \ - deattack.o fingerprint.o hostfile.o log.o match.o mpaux.o nchan.o \ - packet.o radix.o readpass.o rsa.o tildexpand.o ttymodes.o uidswap.o \ - xmalloc.o +TARGETS=ssh sshd ssh-add ssh-keygen ssh-agent scp $(EXTRA_TARGETS) + +LIBOBJS= atomicio.o authfd.o authfile.o bsd-bindresvport.o bsd-daemon.o bsd-misc.o bsd-mktemp.o bsd-rresvport.o bsd-snprintf.o bsd-strlcat.o bsd-strlcpy.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o fake-getaddrinfo.o fake-getnameinfo.o fingerprint.o hostfile.o log.o match.o mpaux.o nchan.o packet.o radix.o readpass.o rsa.o tildexpand.o ttymodes.o uidswap.o xmalloc.o SSHOBJS= ssh.o sshconnect.o log-client.o readconf.o clientloop.o -SSHDOBJS= sshd.o auth-rhosts.o auth-krb4.o auth-pam.o auth-passwd.o \ - auth-rsa.o auth-rh-rsa.o pty.o log-server.o login.o servconf.o \ - serverloop.o bsd-login.o md5crypt.o +SSHDOBJS= sshd.o auth-rhosts.o auth-krb4.o auth-pam.o auth-passwd.o auth-rsa.o auth-rh-rsa.o pty.o log-server.o login.o servconf.o serverloop.o bsd-login.o md5crypt.o MANPAGES=scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh.1 sshd.8 CONFIGFILES=sshd_config ssh_config -all: $(OBJS) $(TARGETS) $(MANPAGES) $(CONFIGFILES) +all: $(TARGETS) $(MANPAGES) $(CONFIGFILES) $(OBJS): config.h @@ -67,31 +51,27 @@ libssh.a: $(LIBOBJS) $(AR) rv $@ $(LIBOBJS) $(RANLIB) $@ -ssh: $(SSHOBJS) libssh.a +ssh: libssh.a $(SSHOBJS) $(CC) -o $@ $(SSHOBJS) $(LDFLAGS) -lssh $(LIBS) -sshd: $(SSHDOBJS) libssh.a +sshd: libssh.a $(SSHDOBJS) $(CC) -o $@ $(SSHDOBJS) $(LDFLAGS) -lssh $(LIBS) -scp: scp.o libssh.a +scp: libssh.a scp.o $(CC) -o $@ scp.o $(LDFLAGS) -lssh $(LIBS) -ssh-add: ssh-add.o log-client.o libssh.a +ssh-add: libssh.a ssh-add.o log-client.o $(CC) -o $@ ssh-add.o log-client.o $(LDFLAGS) -lssh $(LIBS) -ssh-agent: ssh-agent.o log-client.o libssh.a +ssh-agent: libssh.a ssh-agent.o log-client.o $(CC) -o $@ ssh-agent.o log-client.o $(LDFLAGS) -lssh $(LIBS) -ssh-keygen: ssh-keygen.o log-client.o libssh.a +ssh-keygen: libssh.a ssh-keygen.o log-client.o $(CC) -o $@ ssh-keygen.o log-client.o $(LDFLAGS) -lssh $(LIBS) gnome-ssh-askpass: gnome-ssh-askpass.c $(CC) $(CFLAGS) $(GNOME_CFLAGS) -o $@ gnome-ssh-askpass.c $(GNOME_LIBS) -clean: - rm -f *.o $(TARGETS) config.status config.cache config.log core \ - *.1 *.8 sshd_config ssh_config - scp.1: scp.1.in $(PERL) $(FIXPATHS) -Dsysconfdir=${sysconfdir} $(srcdir)/scp.1.in @@ -116,6 +96,18 @@ sshd_config: sshd_config.in ssh_config: ssh_config.in $(PERL) $(FIXPATHS) -Dsysconfdir=${sysconfdir} ssh_config.in +clean: + rm -f *.o *.a $(TARGETS) config.status config.cache config.log + rm -f core *.1 *.8 sshd_config ssh_config + +distclean: clean + rm -f Makefile config.h core *~ + +mrproper: distclean + +veryclean: distclean + rm -f configure config.h.in + install: $(TARGETS) $(INSTALL) -d $(bindir) $(INSTALL) -d $(sbindir) @@ -180,11 +172,3 @@ uninstall: -rm -f ${ASKPASS_PROGRAM} -rmdir $(libexecdir)/ssh ; -distclean: clean - rm -f Makefile config.h core *~ - -mrproper: distclean - -veryclean: distclean - rm -f configure config.h.in - diff --git a/TODO b/TODO index e3249fd1..0eccc9be 100644 --- a/TODO +++ b/TODO @@ -7,6 +7,8 @@ - Replace the horror in acconfig.h which tries to comphensate for the lack of u_intXX_t types. There must be a better way. +- Move all compatability cruft (bsd-*, fake-*) into subordinate library + - Hanging on logout: localhost$ ssh remotehost diff --git a/acconfig.h b/acconfig.h index e61bfd33..52aef76a 100644 --- a/acconfig.h +++ b/acconfig.h @@ -40,31 +40,17 @@ * message at run-time. */ #undef RSAREF -/* Define is utmp.h has a ut_host field */ +/* struct utmp and struct utmpx fields */ #undef HAVE_HOST_IN_UTMP - -/* Define is utmpx.h has a ut_host field */ #undef HAVE_HOST_IN_UTMPX - -/* Define is utmp.h has a ut_addr field */ #undef HAVE_ADDR_IN_UTMP - -/* Define is utmpx.h has a ut_addr field */ #undef HAVE_ADDR_IN_UTMPX - -/* Define is utmpx.h has a syslen field */ +#undef HAVE_ADDR_V6_IN_UTMP +#undef HAVE_ADDR_V6_IN_UTMPX #undef HAVE_SYSLEN_IN_UTMPX - -/* Define is utmp.h has a ut_pid field */ #undef HAVE_PID_IN_UTMP - -/* Define is utmp.h has a ut_type field */ #undef HAVE_TYPE_IN_UTMP - -/* Define is utmp.h has a ut_tv field */ #undef HAVE_TV_IN_UTMP - -/* Define is utmp.h has a ut_id field */ #undef HAVE_ID_IN_UTMP /* Define if you want to use utmpx */ @@ -125,6 +111,14 @@ #undef HAVE_UINTXX_T #undef HAVE_SOCKLEN_T #undef HAVE_SIZE_T +#undef HAVE_STRUCT_SOCKADDR_STORAGE +#undef HAVE_STRUCT_ADDRINFO +#undef HAVE_STRUCT_IN6_ADDR +#undef HAVE_STRUCT_SOCKADDR_IN6 + +/* Fields in struct sockaddr_storage */ +#undef HAVE_SS_FAMILY_IN_SS +#undef HAVE___SS_FAMILY_IN_SS /* Define if you have /dev/ptmx */ #undef HAVE_DEV_PTMX diff --git a/bsd-bindresvport.c b/bsd-bindresvport.c new file mode 100644 index 00000000..3ea37ee5 --- /dev/null +++ b/bsd-bindresvport.c @@ -0,0 +1,116 @@ +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +#include "config.h" + +#ifndef HAVE_BINRESVPORT_AF + +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: bindresvport.c,v 1.11 1999/12/17 19:22:08 deraadt Exp $"; +#endif /* LIBC_SCCS and not lint */ + +/* + * Copyright (c) 1987 by Sun Microsystems, Inc. + * + * Portions Copyright(C) 1996, Jason Downs. All rights reserved. + */ + +#include "includes.h" + +#define STARTPORT 600 +#define ENDPORT (IPPORT_RESERVED - 1) +#define NPORTS (ENDPORT - STARTPORT + 1) + +#if 0 +/* + * Bind a socket to a privileged IP port + */ +int +bindresvport(sd, sin) + int sd; + struct sockaddr_in *sin; +{ + return bindresvport_af(sd, (struct sockaddr *)sin, AF_INET); +} +#endif + +/* + * Bind a socket to a privileged IP port + */ +int +bindresvport_af(sd, sa, af) + int sd; + struct sockaddr *sa; + int af; +{ + int error; + struct sockaddr_storage myaddr; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + u_int16_t *portp; + int salen; + int i; + + if (sa == NULL) { + memset(&myaddr, 0, sizeof(myaddr)); + sa = (struct sockaddr *)&myaddr; + } + + if (af == AF_INET) { + sin = (struct sockaddr_in *)sa; + salen = sizeof(struct sockaddr_in); + portp = &sin->sin_port; + } else if (af == AF_INET6) { + sin6 = (struct sockaddr_in6 *)sa; + salen = sizeof(struct sockaddr_in6); + portp = &sin6->sin6_port; + } else { + errno = EPFNOSUPPORT; + return (-1); + } + sa->sa_family = af; + + if (*portp == 0) + *portp = (getpid() % NPORTS) + STARTPORT; + + for(i = 0; i < NPORTS; i++) { + error = bind(sd, sa, salen); + + if ((error == 0) || ((error < 0) && (errno != EADDRINUSE))) + break; + + (*portp)++; + if (*portp < ENDPORT) + *portp = STARTPORT; + } + + return (error); +} + +#endif /* HAVE_BINRESVPORT_AF */ diff --git a/bsd-bindresvport.h b/bsd-bindresvport.h new file mode 100644 index 00000000..df084e3d --- /dev/null +++ b/bsd-bindresvport.h @@ -0,0 +1,10 @@ +#ifndef _BSD_BINRESVPORT_H +#define _BSD_BINRESVPORT_H + +#include "config.h" + +#ifndef HAVE_BINRESVPORT_AF +int bindresvport_af(int sd, struct sockaddr *sa, int af); +#endif /* !HAVE_BINRESVPORT_AF */ + +#endif /* _BSD_BINRESVPORT_H */ diff --git a/bsd-rresvport.c b/bsd-rresvport.c new file mode 100644 index 00000000..c29165fa --- /dev/null +++ b/bsd-rresvport.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 1995, 1996, 1998 Theo de Raadt. All rights reserved. + * Copyright (c) 1983, 1993, 1994 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * This product includes software developed by Theo de Raadt. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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 "config.h" + +#ifndef HAVE_RRESVPORT_AF + +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: rresvport.c,v 1.4 1999/12/17 20:48:03 deraadt Exp $"; +#endif /* LIBC_SCCS and not lint */ + +#include "includes.h" + +#if 0 +int +rresvport(alport) + int *alport; +{ + return rresvport_af(alport, AF_INET); +} +#endif + +int +rresvport_af(alport, af) + int *alport; + int af; +{ + struct sockaddr_storage ss; + struct sockaddr *sa; + u_int16_t *portp; + int s; + int sa_len; + + bzero(&ss, sizeof ss); + sa = (struct sockaddr *)&ss; + + switch (af) { + case AF_INET: + sa_len = sizeof(struct sockaddr_in); + portp = &((struct sockaddr_in *)sa)->sin_port; + break; + case AF_INET6: + sa_len = sizeof(struct sockaddr_in6); + portp = &((struct sockaddr_in6 *)sa)->sin6_port; + break; + default: + errno = EPFNOSUPPORT; + return (-1); + } + sa->sa_family = af; + + s = socket(af, SOCK_STREAM, 0); + if (s < 0) + return (-1); + + *portp = htons(*alport); + if (*alport < IPPORT_RESERVED - 1) { + if (bind(s, sa, sa_len) >= 0) + return (s); + if (errno != EADDRINUSE) { + (void)close(s); + return (-1); + } + } + + *portp = 0; + if (bindresvport_af(s, sa, af) == -1) { + (void)close(s); + return (-1); + } + *alport = ntohs(*portp); + return (s); +} + +#endif /* HAVE_RRESVPORT_AF */ diff --git a/bsd-rresvport.h b/bsd-rresvport.h new file mode 100644 index 00000000..69d6bbd9 --- /dev/null +++ b/bsd-rresvport.h @@ -0,0 +1,10 @@ +#ifndef _BSD_RRESVPORT_H +#define _BSD_RRESVPORT_H + +#include "config.h" + +#ifndef HAVE_RRESVPORT_AF +int rresvport_af(int *alport, int af); +#endif /* !HAVE_RRESVPORT_AF */ + +#endif /* _BSD_RRESVPORT_H */ diff --git a/canohost.c b/canohost.c index 76b5ed2f..4e9c6a92 100644 --- a/canohost.c +++ b/canohost.c @@ -28,10 +28,12 @@ RCSID("$Id$"); char * get_remote_hostname(int socket) { - struct sockaddr_in from; - int fromlen, i; - struct hostent *hp; + struct sockaddr_storage from; + int i; + socklen_t fromlen; + struct addrinfo hints, *ai, *aitop; char name[MAXHOSTNAMELEN]; + char ntop[NI_MAXHOST], ntop2[NI_MAXHOST]; /* Get IP address of client. */ fromlen = sizeof(from); @@ -40,20 +42,15 @@ get_remote_hostname(int socket) debug("getpeername failed: %.100s", strerror(errno)); fatal_cleanup(); } - /* Map the IP address to a host name. */ - hp = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr), - from.sin_family); - if (hp) { - /* Got host name, find canonic host name. */ - if (strchr(hp->h_name, '.') != 0) - strlcpy(name, hp->h_name, sizeof(name)); - else if (hp->h_aliases != 0 - && hp->h_aliases[0] != 0 - && strchr(hp->h_aliases[0], '.') != 0) - strlcpy(name, hp->h_aliases[0], sizeof(name)); - else - strlcpy(name, hp->h_name, sizeof(name)); + if (getnameinfo((struct sockaddr *)&from, fromlen, ntop, sizeof(ntop), + NULL, 0, NI_NUMERICHOST) != 0) + fatal("get_remote_hostname: getnameinfo NI_NUMERICHOST failed"); + /* Map the IP address to a host name. */ + if (getnameinfo((struct sockaddr *)&from, fromlen, name, sizeof(name), + NULL, 0, NI_NAMEREQD) == 0) { + /* Got host name. */ + name[sizeof(name) - 1] = '\0'; /* * Convert it to all lowercase (which is expected by the rest * of this software). @@ -71,32 +68,34 @@ get_remote_hostname(int socket) * fooled if the intruder has access to the name server of * the domain). */ - hp = gethostbyname(name); - if (!hp) { - log("reverse mapping checking gethostbyname for %.700s failed - POSSIBLE BREAKIN ATTEMPT!", name); - strlcpy(name, inet_ntoa(from.sin_addr), sizeof name); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = from.ss_family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(name, NULL, &hints, &aitop) != 0) { + log("reverse mapping checking getaddrinfo for %.700s failed - POSSIBLE BREAKIN ATTEMPT!", name); + strlcpy(name, ntop, sizeof name); goto check_ip_options; } /* Look for the address from the list of addresses. */ - for (i = 0; hp->h_addr_list[i]; i++) - if (memcmp(hp->h_addr_list[i], &from.sin_addr, sizeof(from.sin_addr)) - == 0) - break; - /* - * If we reached the end of the list, the address was not - * there. - */ - if (!hp->h_addr_list[i]) { + for (ai = aitop; ai; ai = ai->ai_next) { + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop2, + sizeof(ntop2), NULL, 0, NI_NUMERICHOST) == 0 && + (strcmp(ntop, ntop2) == 0)) + break; + } + freeaddrinfo(aitop); + /* If we reached the end of the list, the address was not there. */ + if (!ai) { /* Address not found for the host name. */ log("Address %.100s maps to %.600s, but this does not map back to the address - POSSIBLE BREAKIN ATTEMPT!", - inet_ntoa(from.sin_addr), name); - strlcpy(name, inet_ntoa(from.sin_addr), sizeof name); + ntop, name); + strlcpy(name, ntop, sizeof name); goto check_ip_options; } /* Address was found for the host name. We accept the host name. */ } else { /* Host name not found. Use ascii representation of the address. */ - strlcpy(name, inet_ntoa(from.sin_addr), sizeof name); + strlcpy(name, ntop, sizeof name); log("Could not reverse map address %.100s.", name); } @@ -113,10 +112,12 @@ check_ip_options: * rest of the interaction and could still bypass security. So we * exit here if we detect any IP options. */ - { + /* IP options -- IPv4 only */ + if (from.ss_family == AF_INET) { unsigned char options[200], *ucp; char text[1024], *cp; - int option_size, ipproto; + socklen_t option_size; + int ipproto; struct protoent *ip; if ((ip = getprotobyname("ip")) != NULL) @@ -125,47 +126,21 @@ check_ip_options: ipproto = IPPROTO_IP; option_size = sizeof(options); if (getsockopt(0, ipproto, IP_OPTIONS, (char *) options, - &option_size) >= 0 && option_size != 0) { + &option_size) >= 0 && option_size != 0) { cp = text; /* Note: "text" buffer must be at least 3x as big as options. */ for (ucp = options; option_size > 0; ucp++, option_size--, cp += 3) sprintf(cp, " %2.2x", *ucp); log("Connection from %.100s with IP options:%.800s", - inet_ntoa(from.sin_addr), text); + ntop, text); packet_disconnect("Connection from %.100s with IP options:%.800s", - inet_ntoa(from.sin_addr), text); + ntop, text); } } return xstrdup(name); } -static char *canonical_host_name = NULL; -static char *canonical_host_ip = NULL; - -/* Returns 1 if remote host is connected via socket, 0 if not. */ - -int -peer_connection_is_on_socket() -{ - struct sockaddr_in from; - int fromlen; - int in = packet_get_connection_in(); - int out = packet_get_connection_out(); - - /* filedescriptors in and out are the same, so it's a socket */ - if (in == out) - return 1; - fromlen = sizeof(from); - memset(&from, 0, sizeof(from)); - if (getpeername(in, (struct sockaddr *) & from, &fromlen) < 0) - return 0; - if (from.sin_family != AF_INET) - return 0; - - return 1; -} - /* * Return the canonical name of the host in the other side of the current * connection. The host name is cached, so it is efficient to call this @@ -175,12 +150,14 @@ peer_connection_is_on_socket() const char * get_canonical_hostname() { + static char *canonical_host_name = NULL; + /* Check if we have previously retrieved this same name. */ if (canonical_host_name != NULL) return canonical_host_name; /* Get the real hostname if socket; otherwise return UNKNOWN. */ - if (peer_connection_is_on_socket()) + if (packet_connection_is_on_socket()) canonical_host_name = get_remote_hostname(packet_get_connection_in()); else canonical_host_name = xstrdup("UNKNOWN"); @@ -190,21 +167,24 @@ get_canonical_hostname() /* * Returns the IP-address of the remote host as a string. The returned - * string need not be freed. + * string must not be freed. */ const char * get_remote_ipaddr() { - struct sockaddr_in from; - int fromlen, socket; + static char *canonical_host_ip = NULL; + struct sockaddr_storage from; + socklen_t fromlen; + int socket; + char ntop[NI_MAXHOST]; /* Check whether we have chached the name. */ if (canonical_host_ip != NULL) return canonical_host_ip; /* If not a socket, return UNKNOWN. */ - if (!peer_connection_is_on_socket()) { + if (!packet_connection_is_on_socket()) { canonical_host_ip = xstrdup("UNKNOWN"); return canonical_host_ip; } @@ -219,48 +199,76 @@ get_remote_ipaddr() fatal_cleanup(); } /* Get the IP address in ascii. */ - canonical_host_ip = xstrdup(inet_ntoa(from.sin_addr)); + if (getnameinfo((struct sockaddr *)&from, fromlen, ntop, sizeof(ntop), + NULL, 0, NI_NUMERICHOST) != 0) + fatal("get_remote_hostname: getnameinfo NI_NUMERICHOST failed"); + + canonical_host_ip = xstrdup(ntop); /* Return ip address string. */ return canonical_host_ip; } -/* Returns the port of the peer of the socket. */ +/* Returns the local/remote port for the socket. */ -int -get_peer_port(int sock) +int +get_sock_port(int sock, int local) { - struct sockaddr_in from; - int fromlen; + struct sockaddr_storage from; + socklen_t fromlen; + char strport[NI_MAXSERV]; /* Get IP address of client. */ fromlen = sizeof(from); memset(&from, 0, sizeof(from)); - if (getpeername(sock, (struct sockaddr *) & from, &fromlen) < 0) { - debug("getpeername failed: %.100s", strerror(errno)); - fatal_cleanup(); + if (local) { + if (getsockname(sock, (struct sockaddr *)&from, &fromlen) < 0) { + error("getsockname failed: %.100s", strerror(errno)); + return 0; + } + } else { + if (getpeername(sock, (struct sockaddr *) & from, &fromlen) < 0) { + debug("getpeername failed: %.100s", strerror(errno)); + fatal_cleanup(); + } } /* Return port number. */ - return ntohs(from.sin_port); + if (getnameinfo((struct sockaddr *)&from, fromlen, NULL, 0, + strport, sizeof(strport), NI_NUMERICSERV) != 0) + fatal("get_sock_port: getnameinfo NI_NUMERICSERV failed"); + return atoi(strport); } -/* Returns the port number of the remote host. */ +/* Returns remote/local port number for the current connection. */ int -get_remote_port() +get_port(int local) { - int socket; - /* * If the connection is not a socket, return 65535. This is * intentionally chosen to be an unprivileged port number. */ - if (!peer_connection_is_on_socket()) + if (!packet_connection_is_on_socket()) return 65535; - /* Get client socket. */ - socket = packet_get_connection_in(); + /* Get socket and return the port number. */ + return get_sock_port(packet_get_connection_in(), local); +} + +int +get_peer_port(int sock) +{ + return get_sock_port(sock, 0); +} - /* Get and return the peer port number. */ - return get_peer_port(socket); +int +get_remote_port() +{ + return get_port(0); +} + +int +get_local_port() +{ + return get_port(1); } diff --git a/channels.c b/channels.c index e96d6705..30d3b108 100644 --- a/channels.c +++ b/channels.c @@ -374,7 +374,8 @@ void channel_after_select(fd_set * readset, fd_set * writeset) { struct sockaddr addr; - int addrlen, newsock, i, newch, len; + int newsock, i, newch, len; + socklen_t addrlen; Channel *ch; char buf[16384], *remote_hostname; @@ -412,7 +413,7 @@ channel_after_select(fd_set * readset, fd_set * writeset) * forwarded TCP/IP port. */ if (FD_ISSET(ch->sock, readset)) { - debug("Connection to port %d forwarding to %.100s:%d requested.", + debug("Connection to port %d forwarding to %.100s port %d requested.", ch->listening_port, ch->path, ch->host_port); addrlen = sizeof(addr); newsock = accept(ch->sock, &addr, &addrlen); @@ -421,7 +422,7 @@ channel_after_select(fd_set * readset, fd_set * writeset) break; } remote_hostname = get_remote_hostname(newsock); - snprintf(buf, sizeof buf, "listen port %d:%.100s:%d, connect from %.200s:%d", + snprintf(buf, sizeof buf, "listen port %d for %.100s port %d, connect from %.200s port %d", ch->listening_port, ch->path, ch->host_port, remote_hostname, get_peer_port(newsock)); xfree(remote_hostname); @@ -532,10 +533,19 @@ channel_output_poll() for (i = 0; i < channels_alloc; i++) { ch = &channels[i]; + /* We are only interested in channels that can have buffered incoming data. */ - if (ch->type != SSH_CHANNEL_OPEN && - ch->type != SSH_CHANNEL_INPUT_DRAINING) - continue; + if (compat13) { + if (ch->type != SSH_CHANNEL_OPEN && + ch->type != SSH_CHANNEL_INPUT_DRAINING) + continue; + } else { + if (ch->type != SSH_CHANNEL_OPEN) + continue; + if (ch->istate != CHAN_INPUT_OPEN && + ch->istate != CHAN_INPUT_WAIT_DRAIN) + continue; + } /* Get the amount of buffered data for this channel. */ len = buffer_len(&ch->input); @@ -575,25 +585,33 @@ channel_output_poll() void channel_input_data(int payload_len) { - int channel; + int id; char *data; unsigned int data_len; + Channel *ch; /* Get the channel number and verify it. */ - channel = packet_get_int(); - if (channel < 0 || channel >= channels_alloc || - channels[channel].type == SSH_CHANNEL_FREE) - packet_disconnect("Received data for nonexistent channel %d.", channel); + id = packet_get_int(); + if (id < 0 || id >= channels_alloc) + packet_disconnect("Received data for nonexistent channel %d.", id); + ch = &channels[id]; + + if (ch->type == SSH_CHANNEL_FREE) + packet_disconnect("Received data for free channel %d.", ch->self); /* Ignore any data for non-open channels (might happen on close) */ - if (channels[channel].type != SSH_CHANNEL_OPEN && - channels[channel].type != SSH_CHANNEL_X11_OPEN) + if (ch->type != SSH_CHANNEL_OPEN && + ch->type != SSH_CHANNEL_X11_OPEN) + return; + + /* same for protocol 1.5 if output end is no longer open */ + if (!compat13 && ch->ostate != CHAN_OUTPUT_OPEN) return; /* Get the data. */ data = packet_get_string(&data_len); packet_integrity_check(payload_len, 4 + 4 + data_len, SSH_MSG_CHANNEL_DATA); - buffer_append(&channels[channel].output, data, data_len); + buffer_append(&ch->output, data, data_len); xfree(data); } @@ -610,23 +628,11 @@ channel_not_very_much_buffered_data() for (i = 0; i < channels_alloc; i++) { ch = &channels[i]; - switch (ch->type) { - case SSH_CHANNEL_X11_LISTENER: - case SSH_CHANNEL_PORT_LISTENER: - case SSH_CHANNEL_AUTH_SOCKET: - continue; - case SSH_CHANNEL_OPEN: + if (ch->type == SSH_CHANNEL_OPEN) { if (buffer_len(&ch->input) > packet_get_maxsize()) return 0; if (buffer_len(&ch->output) > packet_get_maxsize()) return 0; - continue; - case SSH_CHANNEL_INPUT_DRAINING: - case SSH_CHANNEL_OUTPUT_DRAINING: - case SSH_CHANNEL_X11_OPEN: - case SSH_CHANNEL_FREE: - default: - continue; } } return 1; @@ -853,9 +859,11 @@ channel_open_message() case SSH_CHANNEL_X11_OPEN: case SSH_CHANNEL_INPUT_DRAINING: case SSH_CHANNEL_OUTPUT_DRAINING: - snprintf(buf, sizeof buf, " #%d %.300s (t%d r%d i%d o%d)\r\n", - c->self, c->remote_name, - c->type, c->remote_id, c->istate, c->ostate); + snprintf(buf, sizeof buf, " #%d %.300s (t%d r%d i%d/%d o%d/%d)\r\n", + c->self, c->remote_name, + c->type, c->remote_id, + c->istate, buffer_len(&c->input), + c->ostate, buffer_len(&c->output)); buffer_append(&buffer, buf, strlen(buf)); continue; default: @@ -878,50 +886,76 @@ void channel_request_local_forwarding(u_short port, const char *host, u_short host_port, int gateway_ports) { - int ch, sock, on = 1; - struct sockaddr_in sin; + int success, ch, sock, on = 1; + struct addrinfo hints, *ai, *aitop; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; struct linger linger; if (strlen(host) > sizeof(channels[0].path) - 1) packet_disconnect("Forward host name too long."); - /* Create a port to listen for the host. */ - sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) - packet_disconnect("socket: %.100s", strerror(errno)); - - /* Initialize socket address. */ - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - if (gateway_ports == 1) - sin.sin_addr.s_addr = htonl(INADDR_ANY); - else - sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sin.sin_port = htons(port); - /* - * Set socket options. We would like the socket to disappear as soon - * as it has been closed for whatever reason. + * getaddrinfo returns a loopback address if the hostname is + * set to NULL and hints.ai_flags is not AI_PASSIVE */ - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); - linger.l_onoff = 1; - linger.l_linger = 5; - setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); - - /* Bind the socket to the address. */ - if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) - packet_disconnect("bind: %.100s", strerror(errno)); - - /* Start listening for connections on the socket. */ - if (listen(sock, 5) < 0) - packet_disconnect("listen: %.100s", strerror(errno)); - - /* Allocate a channel number for the socket. */ - ch = channel_allocate(SSH_CHANNEL_PORT_LISTENER, sock, - xstrdup("port listener")); - strlcpy(channels[ch].path, host, sizeof(channels[ch].path)); - channels[ch].host_port = host_port; - channels[ch].listening_port = port; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_flags = gateway_ports ? AI_PASSIVE : 0; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", port); + if (getaddrinfo(NULL, strport, &hints, &aitop) != 0) + packet_disconnect("getaddrinfo: fatal error"); + + success = 0; + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), + strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("channel_request_local_forwarding: getnameinfo failed"); + continue; + } + /* Create a port to listen for the host. */ + sock = socket(ai->ai_family, SOCK_STREAM, 0); + if (sock < 0) { + /* this is no error since kernel may not support ipv6 */ + verbose("socket: %.100s", strerror(errno)); + continue; + } + /* + * Set socket options. We would like the socket to disappear + * as soon as it has been closed for whatever reason. + */ + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); + linger.l_onoff = 1; + linger.l_linger = 5; + setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); + debug("Local forwarding listening on %s port %s.", ntop, strport); + + /* Bind the socket to the address. */ + if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + /* address can be in use ipv6 address is already bound */ + verbose("bind: %.100s", strerror(errno)); + close(sock); + continue; + } + /* Start listening for connections on the socket. */ + if (listen(sock, 5) < 0) { + error("listen: %.100s", strerror(errno)); + close(sock); + continue; + } + /* Allocate a channel number for the socket. */ + ch = channel_allocate(SSH_CHANNEL_PORT_LISTENER, sock, + xstrdup("port listener")); + strlcpy(channels[ch].path, host, sizeof(channels[ch].path)); + channels[ch].host_port = host_port; + channels[ch].listening_port = port; + success = 1; + } + if (success == 0) + packet_disconnect("cannot listen port: %d", port); + freeaddrinfo(aitop); } /* @@ -1000,12 +1034,13 @@ channel_input_port_forward_request(int is_root) void channel_input_port_open(int payload_len) { - int remote_channel, sock, newch, i; + int remote_channel, sock = 0, newch, i; u_short host_port; - struct sockaddr_in sin; char *host, *originator_string; - struct hostent *hp; int host_len, originator_len; + struct addrinfo hints, *ai, *aitop; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int gaierr; /* Get remote channel number. */ remote_channel = packet_get_int(); @@ -1047,41 +1082,47 @@ channel_input_port_open(int payload_len) packet_send(); } } - memset(&sin, 0, sizeof(sin)); - sin.sin_addr.s_addr = inet_addr(host); - if ((sin.sin_addr.s_addr & 0xffffffff) != 0xffffffff) { - /* It was a valid numeric host address. */ - sin.sin_family = AF_INET; - } else { - /* Look up the host address from the name servers. */ - hp = gethostbyname(host); - if (!hp) { - error("%.100s: unknown host.", host); - goto fail; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", host_port); + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) { + error("%.100s: unknown host (%s)", host, gai_strerror(gaierr)); + goto fail; + } + + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), + strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("channel_input_port_open: getnameinfo failed"); + continue; } - if (!hp->h_addr_list[0]) { - error("%.100s: host has no IP address.", host); - goto fail; + /* Create the socket. */ + sock = socket(ai->ai_family, SOCK_STREAM, 0); + if (sock < 0) { + error("socket: %.100s", strerror(errno)); + continue; } - sin.sin_family = hp->h_addrtype; - memcpy(&sin.sin_addr, hp->h_addr_list[0], - sizeof(sin.sin_addr)); - } - sin.sin_port = htons(host_port); + /* Connect to the host/port. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + error("connect %.100s port %s: %.100s", ntop, strport, + strerror(errno)); + close(sock); + continue; /* fail -- try next */ + } + break; /* success */ - /* Create the socket. */ - sock = socket(sin.sin_family, SOCK_STREAM, 0); - if (sock < 0) { - error("socket: %.100s", strerror(errno)); - goto fail; } - /* Connect to the host/port. */ - if (connect(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) { - error("connect %.100s:%d: %.100s", host, host_port, - strerror(errno)); - close(sock); + freeaddrinfo(aitop); + + if (!ai) { + error("connect %.100s port %d: failed.", host, host_port); goto fail; } + /* Successful connection. */ /* Allocate a channel for this connection. */ @@ -1115,48 +1156,73 @@ fail: * occurs. */ +#define NUM_SOCKS 10 + char * x11_create_display_inet(int screen_number, int x11_display_offset) { int display_number, sock; u_short port; - struct sockaddr_in sin; - char buf[512]; + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr, n, num_socks = 0, socks[NUM_SOCKS]; + char display[512]; char hostname[MAXHOSTNAMELEN]; for (display_number = x11_display_offset; display_number < MAX_DISPLAYS; display_number++) { port = 6000 + display_number; - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_ANY); - sin.sin_port = htons(port); - - sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) { - error("socket: %.100s", strerror(errno)); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_flags = AI_PASSIVE; /* XXX loopback only ? */ + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", port); + if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) { + error("getaddrinfo: %.100s", gai_strerror(gaierr)); return NULL; } - if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) { - debug("bind port %d: %.100s", port, strerror(errno)); - shutdown(sock, SHUT_RDWR); - close(sock); - continue; + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + sock = socket(ai->ai_family, SOCK_STREAM, 0); + if (sock < 0) { + error("socket: %.100s", strerror(errno)); + return NULL; + } + if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + debug("bind port %d: %.100s", port, strerror(errno)); + shutdown(sock, SHUT_RDWR); + close(sock); + for (n = 0; n < num_socks; n++) { + shutdown(socks[n], SHUT_RDWR); + close(socks[n]); + } + num_socks = 0; + break; + } + socks[num_socks++] = sock; + if (num_socks == NUM_SOCKS) + break; } - break; + if (num_socks > 0) + break; } if (display_number >= MAX_DISPLAYS) { error("Failed to allocate internet-domain X11 display socket."); return NULL; } /* Start listening for connections on the socket. */ - if (listen(sock, 5) < 0) { - error("listen: %.100s", strerror(errno)); - shutdown(sock, SHUT_RDWR); - close(sock); - return NULL; + for (n = 0; n < num_socks; n++) { + sock = socks[n]; + if (listen(sock, 5) < 0) { + error("listen: %.100s", strerror(errno)); + shutdown(sock, SHUT_RDWR); + close(sock); + return NULL; + } } + /* Set up a suitable value for the DISPLAY variable. */ if (gethostname(hostname, sizeof(hostname)) < 0) @@ -1192,21 +1258,24 @@ x11_create_display_inet(int screen_number, int x11_display_offset) memcpy(&my_addr, he->h_addr_list[0], sizeof(struct in_addr)); /* Set DISPLAY to :screen.display */ - snprintf(buf, sizeof(buf), "%.50s:%d.%d", inet_ntoa(my_addr), + snprintf(display, sizeof(display), "%.50s:%d.%d", inet_ntoa(my_addr), display_number, screen_number); } #else /* IPADDR_IN_DISPLAY */ /* Just set DISPLAY to hostname:screen.display */ - snprintf(buf, sizeof buf, "%.400s:%d.%d", hostname, + snprintf(display, sizeof display, "%.400s:%d.%d", hostname, display_number, screen_number); #endif /* IPADDR_IN_DISPLAY */ - /* Allocate a channel for the socket. */ - (void) channel_allocate(SSH_CHANNEL_X11_LISTENER, sock, - xstrdup("X11 inet listener")); + /* Allocate a channel for each socket. */ + for (n = 0; n < num_socks; n++) { + sock = socks[n]; + (void) channel_allocate(SSH_CHANNEL_X11_LISTENER, sock, + xstrdup("X11 inet listener")); + } /* Return a suitable value for the DISPLAY environment variable. */ - return xstrdup(buf); + return xstrdup(display); } #ifndef X_UNIX_PATH @@ -1252,12 +1321,13 @@ connect_local_xsocket(unsigned int dnr) void x11_input_open(int payload_len) { - int remote_channel, display_number, sock, newch; + int remote_channel, display_number, sock = 0, newch; const char *display; - struct sockaddr_in sin; char buf[1024], *cp, *remote_host; - struct hostent *hp; int remote_len; + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr; /* Get remote channel number. */ remote_channel = packet_get_int(); @@ -1323,42 +1393,38 @@ x11_input_open(int payload_len) display); goto fail; } - /* Try to parse the host name as a numeric IP address. */ - memset(&sin, 0, sizeof(sin)); - sin.sin_addr.s_addr = inet_addr(buf); - if ((sin.sin_addr.s_addr & 0xffffffff) != 0xffffffff) { - /* It was a valid numeric host address. */ - sin.sin_family = AF_INET; - } else { - /* Not a numeric IP address. */ - /* Look up the host address from the name servers. */ - hp = gethostbyname(buf); - if (!hp) { - error("%.100s: unknown host.", buf); - goto fail; - } - if (!hp->h_addr_list[0]) { - error("%.100s: host has no IP address.", buf); - goto fail; - } - sin.sin_family = hp->h_addrtype; - memcpy(&sin.sin_addr, hp->h_addr_list[0], - sizeof(sin.sin_addr)); - } - /* Set port number. */ - sin.sin_port = htons(6000 + display_number); - /* Create a socket. */ - sock = socket(sin.sin_family, SOCK_STREAM, 0); - if (sock < 0) { - error("socket: %.100s", strerror(errno)); + /* Look up the host address */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", 6000 + display_number); + if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { + error("%.100s: unknown host. (%s)", buf, gai_strerror(gaierr)); goto fail; } + for (ai = aitop; ai; ai = ai->ai_next) { + /* Create a socket. */ + sock = socket(ai->ai_family, SOCK_STREAM, 0); + if (sock < 0) { + debug("socket: %.100s", strerror(errno)); + continue; + } /* Connect it to the display. */ - if (connect(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) { - error("connect %.100s:%d: %.100s", buf, 6000 + display_number, - strerror(errno)); + if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) { + debug("connect %.100s port %d: %.100s", buf, 6000 + display_number, + strerror(errno)); close(sock); + continue; + } + /* Success */ + break; + + } /* (ai = aitop, ai; ai = ai->ai_next) */ + freeaddrinfo(aitop); + if (!ai) { + error("connect %.100s port %d: %.100s", buf, 6000 + display_number, + strerror(errno)); goto fail; } success: diff --git a/configure.in b/configure.in index a6654022..ab175d40 100644 --- a/configure.in +++ b/configure.in @@ -128,7 +128,7 @@ dnl Checks for header files. AC_CHECK_HEADERS(bstring.h endian.h lastlog.h login.h maillock.h netdb.h netgroup.h paths.h poll.h pty.h shadow.h security/pam_appl.h sys/bsdtty.h sys/cdefs.h sys/poll.h sys/select.h sys/stropts.h sys/time.h sys/ttcompat.h util.h utmp.h utmpx.h) dnl Checks for library functions. -AC_CHECK_FUNCS(arc4random getpagesize _getpty innetgr md5_crypt mkdtemp openpty setenv seteuid setlogin setproctitle setreuid snprintf strlcat strlcpy updwtmpx vsnprintf) +AC_CHECK_FUNCS(arc4random bindresvport_af freeaddrinfo gai_strerror getaddrinfo getpagesize getnameinfo _getpty innetgr md5_crypt mkdtemp openpty rresvport_af setenv seteuid setlogin setproctitle setreuid snprintf strlcat strlcpy updwtmpx vsnprintf) AC_CHECK_FUNC(login, [AC_DEFINE(HAVE_LOGIN)], @@ -169,7 +169,7 @@ AC_TRY_COMPILE( [AC_MSG_RESULT(no)] ) -AC_MSG_CHECKING([For uintXX_t types]) +AC_MSG_CHECKING([for uintXX_t types]) AC_TRY_COMPILE( [#include ], [uint16_t c; uint32_t d; c = 1235; d = 1235;], @@ -180,7 +180,7 @@ AC_TRY_COMPILE( [AC_MSG_RESULT(no)] ) -AC_MSG_CHECKING([For socklen_t]) +AC_MSG_CHECKING([for socklen_t]) AC_TRY_COMPILE( [ #include @@ -194,7 +194,7 @@ AC_TRY_COMPILE( [AC_MSG_RESULT(no)] ) -AC_MSG_CHECKING([For size_t]) +AC_MSG_CHECKING([for size_t]) AC_TRY_COMPILE( [#include ], [size_t foo; foo = 1235;], @@ -205,6 +205,53 @@ AC_TRY_COMPILE( [AC_MSG_RESULT(no)] ) +AC_MSG_CHECKING([for struct sockaddr_storage]) +AC_TRY_COMPILE( + [#include ], + [struct sockaddr_storage s;], + [ + AC_DEFINE(HAVE_STRUCT_SOCKADDR_STORAGE) + AC_MSG_RESULT(yes) + ], + [AC_MSG_RESULT(no)] +) + +AC_MSG_CHECKING([for struct sockaddr_in6]) +AC_TRY_COMPILE( + [#include ], + [struct sockaddr_in6 s; s.sin6_family = 0;], + [ + AC_DEFINE(HAVE_STRUCT_SOCKADDR_IN6) + AC_MSG_RESULT(yes) + ], + [AC_MSG_RESULT(no)] +) + +AC_MSG_CHECKING([for struct in6_addr]) +AC_TRY_COMPILE( + [#include ], + [struct in6_addr s; s.s6_addr[0] = 0;], + [ + AC_DEFINE(HAVE_STRUCT_IN6_ADDR) + AC_MSG_RESULT(yes) + ], + [AC_MSG_RESULT(no)] +) + +AC_MSG_CHECKING([for struct addrinfo]) +AC_TRY_COMPILE( + [ + #include + #include + ], + [struct addrinfo s; s.ai_flags = AI_PASSIVE;], + [ + AC_DEFINE(HAVE_STRUCT_ADDRINFO) + AC_MSG_RESULT(yes) + ], + [AC_MSG_RESULT(no)] +) + AC_ARG_WITH(pam, [ --without-pam Disable PAM support ], [ @@ -330,7 +377,38 @@ AC_EGREP_HEADER(ut_addr, utmp.h, ) AC_MSG_CHECKING([whether utmpx.h has ut_addr field]) AC_EGREP_HEADER(ut_addr, utmpx.h, - [AC_DEFINE(HAVE_ADDR_IN_UTMP) AC_MSG_RESULT(yes); ], + [AC_DEFINE(HAVE_ADDR_IN_UTMPX) AC_MSG_RESULT(yes); ], + [AC_MSG_RESULT(no)] +) +AC_MSG_CHECKING([whether utmp.h has ut_addr_v6 field]) +AC_EGREP_HEADER(ut_addr_v6, utmp.h, + [AC_DEFINE(HAVE_ADDR_V6_IN_UTMP) AC_MSG_RESULT(yes); ], + [AC_MSG_RESULT(no)] +) +AC_MSG_CHECKING([whether utmpx.h has ut_addr_v6 field]) +AC_EGREP_HEADER(ut_addr_v6, utmpx.h, + [AC_DEFINE(HAVE_ADDR_V6_IN_UTMPX) AC_MSG_RESULT(yes); ], + [AC_MSG_RESULT(no)] +) + +AC_MSG_CHECKING([whether struct sockaddr_storage has ss_family field]) +AC_TRY_COMPILE( + [#include ], + [struct sockaddr_storage s; s.ss_family = 1;], + [ + AC_DEFINE(HAVE_SS_FAMILY_IN_SS) + AC_MSG_RESULT(yes) + ], + [AC_MSG_RESULT(no)] +) +AC_MSG_CHECKING([whether struct sockaddr_storage has __ss_family field]) +AC_TRY_COMPILE( + [#include ], + [struct sockaddr_storage s; s.__ss_family = 1;], + [ + AC_DEFINE(HAVE___SS_FAMILY_IN_SS) + AC_MSG_RESULT(yes) + ], [AC_MSG_RESULT(no)] ) diff --git a/defines.h b/defines.h index 40c1a3ea..d2f61e26 100644 --- a/defines.h +++ b/defines.h @@ -112,6 +112,10 @@ typedef unsigned int size_t; # define HAVE_SIZE_T #endif /* HAVE_SIZE_T */ +#if !defined(HAVE_SS_FAMILY_IN_SS) && defined(HAVE___SS_FAMILY_IN_SS) +# define ss_family __ss_family +#endif /* !defined(HAVE_SS_FAMILY_IN_SS) && defined(HAVE_SA_FAMILY_IN_SS) */ + /* Paths */ /* If _PATH_LASTLOG is not defined by system headers, set it to the */ diff --git a/fake-gai-errnos.h b/fake-gai-errnos.h new file mode 100644 index 00000000..27f6089e --- /dev/null +++ b/fake-gai-errnos.h @@ -0,0 +1,12 @@ +/* + * fake library for ssh + * + * This file is included in getaddrinfo.c and getnameinfo.c. + * See getaddrinfo.c and getnameinfo.c. + */ + +/* for old netdb.h */ +#ifndef EAI_NODATA +#define EAI_NODATA 1 +#define EAI_MEMORY 2 +#endif diff --git a/fake-getaddrinfo.c b/fake-getaddrinfo.c new file mode 100644 index 00000000..b918798c --- /dev/null +++ b/fake-getaddrinfo.c @@ -0,0 +1,119 @@ +/* + * fake library for ssh + * + * This file includes getaddrinfo(), freeaddrinfo() and gai_strerror(). + * These funtions are defined in rfc2133. + * + * But these functions are not implemented correctly. The minimum subset + * is implemented for ssh use only. For exapmle, this routine assumes + * that ai_family is AF_INET. Don't use it for another purpose. + * + * In the case not using 'configure --enable-ipv6', this getaddrinfo.c + * will be used if you have broken getaddrinfo or no getaddrinfo. + */ + +#include "includes.h" +#include "ssh.h" + +#ifndef HAVE_GAI_STRERROR +char * +gai_strerror(ecode) +int ecode; +{ + switch (ecode) { + case EAI_NODATA: + return "no address associated with hostname."; + case EAI_MEMORY: + return "memory allocation failure."; + default: + return "unknown error."; + } +} +#endif /* !HAVE_GAI_STRERROR */ + +#ifndef HAVE_FREEADDRINFO +void +freeaddrinfo(ai) +struct addrinfo *ai; +{ + struct addrinfo *next; + + do { + next = ai->ai_next; + free(ai); + } while (ai = next); +} +#endif /* !HAVE_FREEADDRINFO */ + +#ifndef HAVE_GETADDRINFO +static struct addrinfo * +malloc_ai(port, addr) +int port; +u_long addr; +{ + struct addrinfo *ai; + + if (ai = (struct addrinfo *)malloc(sizeof(struct addrinfo) + + sizeof(struct sockaddr_in))) { + memset(ai, 0, sizeof(struct addrinfo) + sizeof(struct sockaddr_in)); + ai->ai_addr = (struct sockaddr *)(ai + 1); + /* XXX -- ssh doesn't use sa_len */ + ai->ai_addrlen = sizeof(struct sockaddr_in); + ai->ai_addr->sa_family = ai->ai_family = AF_INET; + ((struct sockaddr_in *)(ai)->ai_addr)->sin_port = port; + ((struct sockaddr_in *)(ai)->ai_addr)->sin_addr.s_addr = addr; + return ai; + } else { + return NULL; + } +} + +int +getaddrinfo(hostname, servname, hints, res) +const char *hostname, *servname; +const struct addrinfo *hints; +struct addrinfo **res; +{ + struct addrinfo *cur, *prev = NULL; + struct hostent *hp; + int i, port; + + if (servname) + port = htons(atoi(servname)); + else + port = 0; + if (hints && hints->ai_flags & AI_PASSIVE) + if (*res = malloc_ai(port, htonl(0x00000000))) + return 0; + else + return EAI_MEMORY; + if (!hostname) + if (*res = malloc_ai(port, htonl(0x7f000001))) + return 0; + else + return EAI_MEMORY; + if (inet_addr(hostname) != -1) + if (*res = malloc_ai(port, inet_addr(hostname))) + return 0; + else + return EAI_MEMORY; + if ((hp = gethostbyname(hostname)) && + hp->h_name && hp->h_name[0] && hp->h_addr_list[0]) { + for (i = 0; hp->h_addr_list[i]; i++) + if (cur = malloc_ai(port, + ((struct in_addr *)hp->h_addr_list[i])->s_addr)) { + if (prev) + prev->ai_next = cur; + else + *res = cur; + prev = cur; + } else { + if (*res) + freeaddrinfo(*res); + return EAI_MEMORY; + } + return 0; + } + return EAI_NODATA; +} +#endif /* !HAVE_GETADDRINFO */ diff --git a/fake-getaddrinfo.h b/fake-getaddrinfo.h new file mode 100644 index 00000000..de1748f6 --- /dev/null +++ b/fake-getaddrinfo.h @@ -0,0 +1,44 @@ +#ifndef _FAKE_GETADDRINFO_H +#define _FAKE_GETADDRINFO_H + +#include "config.h" + +#include "fake-gai-errnos.h" + +#ifndef AI_PASSIVE +# define AI_PASSIVE 1 +#endif + +#ifndef NI_NUMERICHOST +# define NI_NUMERICHOST 2 +# define NI_NAMEREQD 4 +# define NI_NUMERICSERV 8 +#endif + +#ifndef HAVE_STRUCT_ADDRINFO +struct addrinfo { + int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for hostname */ + struct sockaddr *ai_addr; /* binary address */ + struct addrinfo *ai_next; /* next structure in linked list */ +} +#endif /* !HAVE_STRUCT_ADDRINFO */ + +#ifndef HAVE_GETADDRINFO +int getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res); +#endif /* !HAVE_GETADDRINFO */ + +#ifndef HAVE_GAI_STRERROR +char *gai_strerror(int ecode); +#endif /* !HAVE_GAI_STRERROR */ + +#ifndef HAVE_FREEADDRINFO +void freeaddrinfo(struct addrinfo *ai); +#endif /* !HAVE_FREEADDRINFO */ + +#endif /* _FAKE_GETADDRINFO_H */ diff --git a/fake-getnameinfo.c b/fake-getnameinfo.c new file mode 100644 index 00000000..bf1184e2 --- /dev/null +++ b/fake-getnameinfo.c @@ -0,0 +1,61 @@ +/* + * fake library for ssh + * + * This file includes getnameinfo(). + * These funtions are defined in rfc2133. + * + * But these functions are not implemented correctly. The minimum subset + * is implemented for ssh use only. For exapmle, this routine assumes + * that ai_family is AF_INET. Don't use it for another purpose. + * + * In the case not using 'configure --enable-ipv6', this getnameinfo.c + * will be used if you have broken getnameinfo or no getnameinfo. + */ + +#include "includes.h" +#include "ssh.h" + +#ifndef HAVE_GETNAMEINFO +int +getnameinfo(sa, salen, host, hostlen, serv, servlen, flags) +const struct sockaddr *sa; +size_t salen; +char *host; +size_t hostlen; +char *serv; +size_t servlen; +int flags; +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + struct hostent *hp; + char tmpserv[16]; + + if (serv) { + sprintf(tmpserv, "%d", ntohs(sin->sin_port)); + if (strlen(tmpserv) > servlen) + return EAI_MEMORY; + else + strcpy(serv, tmpserv); + } + if (host) + if (flags & NI_NUMERICHOST) + if (strlen(inet_ntoa(sin->sin_addr)) > hostlen) + return EAI_MEMORY; + else { + strcpy(host, inet_ntoa(sin->sin_addr)); + return 0; + } + else + if (hp = gethostbyaddr((char *)&sin->sin_addr, sizeof(struct in_addr), + AF_INET)) + if (strlen(hp->h_name) > hostlen) + return EAI_MEMORY; + else { + strcpy(host, hp->h_name); + return 0; + } + else + return EAI_NODATA; + return 0; +} +#endif /* !HAVE_GETNAMEINFO */ diff --git a/fake-getnameinfo.h b/fake-getnameinfo.h new file mode 100644 index 00000000..ecf0df2c --- /dev/null +++ b/fake-getnameinfo.h @@ -0,0 +1,17 @@ +#ifndef _FAKE_GETNAMEINFO_H +#define _FAKE_GETNAMEINFO_H + +#include "config.h" +#ifndef HAVE_GETNAMEINFO +int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags); +#endif /* !HAVE_GETNAMEINFO */ + +#ifndef NI_MAXSERV +# define NI_MAXSERV 32 +#endif /* !NI_MAXSERV */ +#ifndef NI_MAXHOST +# define NI_MAXHOST 1025 +#endif /* !NI_MAXHOST */ + +#endif /* _FAKE_GETNAMEINFO_H */ diff --git a/fake-socket.h b/fake-socket.h new file mode 100644 index 00000000..e11ad44e --- /dev/null +++ b/fake-socket.h @@ -0,0 +1,49 @@ +#ifndef _FAKE_SOCKET_H +#define _FAKE_SOCKET_H + +#include "config.h" +#include "sys/types.h" + +#ifndef HAVE_STRUCT_SOCKADDR_STORAGE +#define _SS_MAXSIZE 128 /* Implementation specific max size */ +#define _SS_ALIGNSIZE (sizeof(int)) +#define _SS_PAD1SIZE (_SS_ALIGNSIZE - sizeof(u_short)) +#define _SS_PAD2SIZE (_SS_MAXSIZE - (sizeof(u_short) + \ + _SS_PAD1SIZE + _SS_ALIGNSIZE)) + +struct sockaddr_storage { + u_short ss_family; + char __ss_pad1[_SS_PAD1SIZE]; + int __ss_align; + char __ss_pad2[_SS_PAD2SIZE]; +}; +#endif /* !HAVE_STRUCT_SOCKADDR_STORAGE */ + +#ifndef IN6_IS_ADDR_LOOPBACK +#define IN6_IS_ADDR_LOOPBACK(a) \ + (((u_int32_t *) (a))[0] == 0 && ((u_int32_t *) (a))[1] == 0 && \ + ((u_int32_t *) (a))[2] == 0 && ((u_int32_t *) (a))[3] == htonl (1)) +#endif /* !IN6_IS_ADDR_LOOPBACK */ + +#ifndef HAVE_STRUCT_IN6_ADDR +struct in6_addr { + u_int8_t s6_addr[16]; +}; +#endif /* !HAVE_STRUCT_IN6_ADDR */ + +#ifndef HAVE_STRUCT_SOCKADDR_IN6 +struct sockaddr_in6 { + unsigned short sin6_family; + u_int16_t sin6_port; + u_int32_t sin6_flowinfo; + struct in6_addr sin6_addr; +}; +#endif /* !HAVE_STRUCT_SOCKADDR_IN6 */ + +#ifndef AF_INET6 +/* Define it to something that should never appear */ +#define AF_INET6 AF_MAX +#endif + +#endif /* !_FAKE_SOCKET_H */ + diff --git a/fixpaths b/fixpaths index 6a2a3a0b..8a674064 100755 --- a/fixpaths +++ b/fixpaths @@ -31,8 +31,6 @@ for $f (@ARGV) { $f =~ /(.*\/)*(.*)$/; $of = $2; $of =~ s/.in$//; - print("Making substitutions for $of\n"); - open(IN, "<$f") || die ("$0: input file $f missing!\n"); if (open(OUT, ">$of")) { while () { diff --git a/hostfile.c b/hostfile.c index 7060a899..831ac592 100644 --- a/hostfile.c +++ b/hostfile.c @@ -14,7 +14,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: hostfile.c,v 1.10 1999/12/02 20:18:59 markus Exp $"); +RCSID("$OpenBSD: hostfile.c,v 1.11 2000/01/04 00:07:59 markus Exp $"); #include "packet.h" #include "ssh.h" diff --git a/includes.h b/includes.h index bc7db419..b4af0c22 100644 --- a/includes.h +++ b/includes.h @@ -88,6 +88,10 @@ static /**/const char *const rcsid[] = { (char *)rcsid, "\100(#)" msg } #endif #include "version.h" + +/* BSD function replacements */ +#include "bsd-bindresvport.h" +#include "bsd-rresvport.h" #include "bsd-misc.h" #include "bsd-strlcpy.h" #include "bsd-strlcat.h" @@ -96,6 +100,11 @@ static /**/const char *const rcsid[] = { (char *)rcsid, "\100(#)" msg } #include "bsd-daemon.h" #include "bsd-login.h" +/* rfc2553 socket API replacements */ +#include "fake-getaddrinfo.h" +#include "fake-getnameinfo.h" +#include "fake-socket.h" + /* Define this to be the path of the xauth program. */ #ifndef XAUTH_PATH #define XAUTH_PATH "/usr/X11R6/bin/xauth" diff --git a/log.c b/log.c index 837a670d..03038b2f 100644 --- a/log.c +++ b/log.c @@ -3,7 +3,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: log.c,v 1.6 1999/11/24 19:53:47 markus Exp $"); +RCSID("$OpenBSD: log.c,v 1.7 2000/01/04 00:07:59 markus Exp $"); #include "ssh.h" #include "xmalloc.h" diff --git a/login.c b/login.c index f0cc61d1..633cc32b 100644 --- a/login.c +++ b/login.c @@ -137,7 +137,7 @@ get_last_login_time(uid_t uid, const char *logname, void record_login(int pid, const char *ttyname, const char *user, uid_t uid, - const char *host, struct sockaddr_in * addr) + const char *host, struct sockaddr * addr) { #if defined(_PATH_LASTLOG) && !defined(DISABLE_LASTLOG) struct lastlog ll; @@ -173,7 +173,22 @@ record_login(int pid, const char *ttyname, const char *user, uid_t uid, strncpy(u.ut_host, host, sizeof(u.ut_host)); #endif #if defined(HAVE_ADDR_IN_UTMP) - u.ut_addr = addr->sin_addr.s_addr; + switch (addr->sa_family) { + case AF_INET: { + struct sockaddr_in *in = (struct sockaddr_in*)addr; + memcpy(&(u.ut_addr), &(in->sin_addr), sizeof(&(in->sin_addr))); + break; + } +#if defined(HAVE_ADDR_V6_IN_UTMP) + case AF_INET6: { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*)addr; + memcpy(u.ut_addr_v6, &(in6->sin6_addr), sizeof(&(in6->sin6_addr))); + break; + } +#endif + default: + break; + } #endif #if defined(HAVE_UTMPX_H) && defined(USE_UTMPX) @@ -192,9 +207,24 @@ record_login(int pid, const char *ttyname, const char *user, uid_t uid, strncpy(utx.ut_host, host, sizeof(utx.ut_host)); # endif /* HAVE_SYSLEN_IN_UTMPX */ # endif -# if defined(HAVE_ADDR_IN_UTMPX) - utx.ut_addr = addr->sin_addr.s_addr; -# endif +#if defined(HAVE_ADDR_IN_UTMPX) + switch (addr->sa_family) { + case AF_INET: { + struct sockaddr_in *in = (struct sockaddr_in*)addr; + memcpy(&(utx.ut_addr), &(in->sin_addr), sizeof(&(in->sin_addr))); + break; + } +#if defined(HAVE_ADDR_V6_IN_UTMPX) + case AF_INET6: { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*)addr; + memcpy(utx.ut_addr_v6, &(in6->sin6_addr), sizeof(&(in6->sin6_addr))); + break; + } +#endif + default: + break; + } +#endif #endif /* defined(HAVE_UTMPX_H) && defined(USE_UTMPX) */ /*#if defined(HAVE_UTMPX_H) && defined(USE_UTMPX) && !defined(HAVE_LOGIN)*/ diff --git a/nchan.c b/nchan.c index b89b73e5..8021dd6c 100644 --- a/nchan.c +++ b/nchan.c @@ -41,7 +41,7 @@ static void chan_send_ieof(Channel *c); static void chan_send_oclose(Channel *c); static void chan_shutdown_write(Channel *c); static void chan_shutdown_read(Channel *c); -static void chan_delele_if_full_closed(Channel *c); +static void chan_delete_if_full_closed(Channel *c); /* * EVENTS update channel input/output states execute ACTIONS @@ -55,19 +55,25 @@ chan_rcvd_oclose(Channel *c) case CHAN_INPUT_WAIT_OCLOSE: debug("channel %d: INPUT_WAIT_OCLOSE -> INPUT_CLOSED [rcvd OCLOSE]", c->self); c->istate = CHAN_INPUT_CLOSED; - chan_delele_if_full_closed(c); break; case CHAN_INPUT_OPEN: debug("channel %d: INPUT_OPEN -> INPUT_CLOSED [rvcd OCLOSE, send IEOF]", c->self); chan_shutdown_read(c); chan_send_ieof(c); c->istate = CHAN_INPUT_CLOSED; - chan_delele_if_full_closed(c); + break; + case CHAN_INPUT_WAIT_DRAIN: + /* both local read_failed and remote write_failed */ + log("channel %d: INPUT_WAIT_DRAIN -> INPUT_CLOSED [rvcd OCLOSE, send IEOF]", c->self); + debug("channel %d: INPUT_WAIT_DRAIN -> INPUT_CLOSED [rvcd OCLOSE, send IEOF]", c->self); + chan_send_ieof(c); + c->istate = CHAN_INPUT_CLOSED; break; default: error("protocol error: chan_rcvd_oclose %d for istate %d", c->self, c->istate); - break; + return; } + chan_delete_if_full_closed(c); } void chan_read_failed(Channel *c) @@ -115,7 +121,7 @@ chan_rcvd_ieof(Channel *c) case CHAN_OUTPUT_WAIT_IEOF: debug("channel %d: OUTPUT_WAIT_IEOF -> OUTPUT_CLOSED [rvcd IEOF]", c->self); c->ostate = CHAN_OUTPUT_CLOSED; - chan_delele_if_full_closed(c); + chan_delete_if_full_closed(c); break; default: error("protocol error: chan_rcvd_ieof %d for ostate %d", c->self, c->ostate); @@ -135,7 +141,7 @@ chan_write_failed(Channel *c) debug("channel %d: OUTPUT_WAIT_DRAIN -> OUTPUT_CLOSED [write failed]", c->self); chan_send_oclose(c); c->ostate = CHAN_OUTPUT_CLOSED; - chan_delele_if_full_closed(c); + chan_delete_if_full_closed(c); break; default: error("internal error: chan_write_failed %d for ostate %d", c->self, c->ostate); @@ -154,7 +160,7 @@ chan_obuf_empty(Channel *c) debug("channel %d: OUTPUT_WAIT_DRAIN -> OUTPUT_CLOSED [obuf empty, send OCLOSE]", c->self); chan_send_oclose(c); c->ostate = CHAN_OUTPUT_CLOSED; - chan_delele_if_full_closed(c); + chan_delete_if_full_closed(c); break; default: error("internal error: chan_obuf_empty %d for ostate %d", c->self, c->ostate); @@ -213,14 +219,14 @@ chan_shutdown_read(Channel *c) { debug("channel %d: shutdown_read", c->self); if (shutdown(c->sock, SHUT_RD) < 0) - error("chan_shutdown_read failed for #%d/fd%d: %.100s", - c->self, c->sock, strerror(errno)); + error("chan_shutdown_read failed for #%d/fd%d [i%d o%d]: %.100s", + c->self, c->sock, c->istate, c->ostate, strerror(errno)); } static void -chan_delele_if_full_closed(Channel *c) +chan_delete_if_full_closed(Channel *c) { if (c->istate == CHAN_INPUT_CLOSED && c->ostate == CHAN_OUTPUT_CLOSED) { - debug("channel %d: closing", c->self); + debug("channel %d: full closed", c->self); channel_free(c->self); } } diff --git a/nchan.ms b/nchan.ms index 7b6c1617..eb49cd3b 100644 --- a/nchan.ms +++ b/nchan.ms @@ -52,6 +52,9 @@ arrow from S3.n to S4.s box invis "rcvd OCLOSE/" "-" with .w at last arrow.c ellipse wid .9*ellipsewid ht .9*ellipseht at S4 arrow "start" "" from S1.w+(-0.5,0) to S1.w +arrow from S2.ne to S4.sw +box invis "rcvd OCLOSE/ " with .e at last arrow.c +box invis " send IEOF" with .w at last arrow.c .PE .SH Channel Output State Diagram @@ -76,7 +79,7 @@ arrow "start" "" from S1.w+(-0.5,0) to S1.w Notes .PP The input buffer is filled with data from the socket -(the socket represents the local comsumer/producer of the +(the socket represents the local consumer/producer of the forwarded channel). The data is then sent over the INPUT-end (transmit-end) of the channel to the remote peer. @@ -85,7 +88,7 @@ saved in the output buffer and written to the socket. .PP If the local protocol instance has forwarded all data on the INPUT-end of the channel, it sends an IEOF message to the peer. -If the peer receives the IEOF and has comsumed all +If the peer receives the IEOF and has consumed all data he replies with an OCLOSE. When the local instance receives the OCLOSE he considers the INPUT-half of the channel closed. @@ -94,6 +97,6 @@ The peer has his OUTOUT-half closed. A channel can be deallocated by a protocol instance if both the INPUT- and the OUTOUT-half on his side of the channel are closed. -Note that when an instance is unable to comsume the +Note that when an instance is unable to consume the received data, he is permitted to send an OCLOSE before the matching IEOF is received. diff --git a/packet.c b/packet.c index c8acbd08..1b9f5947 100644 --- a/packet.c +++ b/packet.c @@ -104,6 +104,48 @@ packet_set_connection(int fd_in, int fd_out) fatal_add_cleanup((void (*) (void *)) packet_close, NULL); } +/* Returns 1 if remote host is connected via socket, 0 if not. */ + +int +packet_connection_is_on_socket() +{ + struct sockaddr_storage from, to; + socklen_t fromlen, tolen; + + /* filedescriptors in and out are the same, so it's a socket */ + if (connection_in == connection_out) + return 1; + fromlen = sizeof(from); + memset(&from, 0, sizeof(from)); + if (getpeername(connection_in, (struct sockaddr *) & from, &fromlen) < 0) + return 0; + tolen = sizeof(to); + memset(&to, 0, sizeof(to)); + if (getsockname(connection_out, (struct sockaddr *)&to, &tolen) < 0) + return 0; + if (fromlen != tolen || memcmp(&from, &to, fromlen) != 0) + return 0; + if (from.ss_family != AF_INET && from.ss_family != AF_INET6) + return 0; + return 1; +} + +/* returns 1 if connection is via ipv4 */ + +int +packet_connection_is_ipv4() +{ + struct sockaddr_storage to; + socklen_t tolen; + + memset(&to, 0, sizeof(to)); + if (getsockname(connection_out, (struct sockaddr *)&to, &tolen) < 0) + return 0; + if (to.ss_family != AF_INET) + return 0; + return 1; +} + /* Sets the connection into non-blocking mode. */ void @@ -735,19 +777,20 @@ packet_set_interactive(int interactive, int keepalives) /* Record that we are in interactive mode. */ interactive_mode = interactive; - /* - * Only set socket options if using a socket (as indicated by the - * descriptors being the same). - */ - if (connection_in != connection_out) + /* Only set socket options if using a socket. */ + if (!packet_connection_is_on_socket()) return; - if (keepalives) { /* Set keepalives if requested. */ if (setsockopt(connection_in, SOL_SOCKET, SO_KEEPALIVE, (void *) &on, - sizeof(on)) < 0) + sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); } + /* + * IPTOS_LOWDELAY, TCP_NODELAY and IPTOS_THROUGHPUT are IPv4 only + */ + if (!packet_connection_is_ipv4()) + return; if (interactive) { /* * Set IP options for an interactive connection. Use @@ -755,10 +798,10 @@ packet_set_interactive(int interactive, int keepalives) */ int lowdelay = IPTOS_LOWDELAY; if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *) &lowdelay, - sizeof(lowdelay)) < 0) + sizeof(lowdelay)) < 0) error("setsockopt IPTOS_LOWDELAY: %.100s", strerror(errno)); if (setsockopt(connection_in, IPPROTO_TCP, TCP_NODELAY, (void *) &on, - sizeof(on)) < 0) + sizeof(on)) < 0) error("setsockopt TCP_NODELAY: %.100s", strerror(errno)); } else { /* @@ -767,7 +810,7 @@ packet_set_interactive(int interactive, int keepalives) */ int throughput = IPTOS_THROUGHPUT; if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *) &throughput, - sizeof(throughput)) < 0) + sizeof(throughput)) < 0) error("setsockopt IPTOS_THROUGHPUT: %.100s", strerror(errno)); } } diff --git a/packet.h b/packet.h index 1b81e435..7befab9e 100644 --- a/packet.h +++ b/packet.h @@ -198,4 +198,8 @@ do { \ } \ } while (0) +/* remote host is connected via a socket/ipv4 */ +int packet_connection_is_on_socket(void); +int packet_connection_is_ipv4(void); + #endif /* PACKET_H */ diff --git a/scp.1.in b/scp.1.in index d8207664..91ff8935 100644 --- a/scp.1.in +++ b/scp.1.in @@ -19,7 +19,7 @@ .Nd secure copy (remote file copy program) .Sh SYNOPSIS .Nm scp -.Op Fl pqrvC +.Op Fl pqrvC46 .Op Fl P Ar port .Op Fl c Ar cipher .Op Fl i Ar identity_file @@ -93,6 +93,14 @@ because .Fl p is already reserved for preserving the times and modes of the file in .Xr rcp 1 . +.It Fl 4 +Forces +.Nm +to use IPv4 addresses only. +.It Fl 6 +Forces +.Nm +to use IPv6 addresses only. .Sh AUTHORS Timo Rinne and Tatu Ylonen .Sh HISTORY diff --git a/scp.c b/scp.c index 1051d0d3..300d10d0 100644 --- a/scp.c +++ b/scp.c @@ -74,6 +74,12 @@ off_t totalbytes = 0; /* Name of current file being transferred. */ char *curfile; +/* This is set to non-zero if IPv4 is desired. */ +int IPv4 = 0; + +/* This is set to non-zero if IPv6 is desired. */ +int IPv6 = 0; + /* This is set to non-zero to enable verbose mode. */ int verbose_mode = 0; @@ -145,6 +151,11 @@ do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout) args[i++] = SSH_PROGRAM; args[i++] = "-x"; args[i++] = "-oFallBackToRsh no"; + if (IPv4) + args[i++] = "-4"; + if (IPv6) + args[i++] = "-6"; + args[i++] = "-oFallBackToRsh no"; if (verbose_mode) args[i++] = "-v"; if (compress_flag) @@ -242,9 +253,15 @@ main(argc, argv) extern int optind; fflag = tflag = 0; - while ((ch = getopt(argc, argv, "dfprtvBCc:i:P:q")) != EOF) + while ((ch = getopt(argc, argv, "dfprtvBCc:i:P:q46")) != EOF) switch (ch) { /* User-visible flags. */ + case '4': + IPv4 = 1; + break; + case '6': + IPv6 = 1; + break; case 'p': pflag = 1; break; @@ -334,6 +351,17 @@ main(argc, argv) exit(errs != 0); } +char * +cleanhostname(host) + char *host; +{ + if (*host == '[' && host[strlen(host) - 1] == ']') { + host[strlen(host) - 1] = '\0'; + return (host + 1); + } else + return host; +} + void toremote(targ, argc, argv) char *targ, *argv[]; @@ -372,6 +400,7 @@ toremote(targ, argc, argv) bp = xmalloc(len); if (host) { *host++ = 0; + host = cleanhostname(host); suser = argv[i]; if (*suser == '\0') suser = pwd->pw_name; @@ -383,13 +412,15 @@ toremote(targ, argc, argv) suser, host, cmd, src, tuser ? tuser : "", tuser ? "@" : "", thost, targ); - } else + } else { + host = cleanhostname(argv[i]); (void) sprintf(bp, "exec %s%s -x -o'FallBackToRsh no' -n %s %s %s '%s%s%s:%s'", SSH_PROGRAM, verbose_mode ? " -v" : "", - argv[i], cmd, src, + host, cmd, src, tuser ? tuser : "", tuser ? "@" : "", thost, targ); + } if (verbose_mode) fprintf(stderr, "Executing: %s\n", bp); (void) system(bp); @@ -399,7 +430,7 @@ toremote(targ, argc, argv) len = strlen(targ) + CMDNEEDS + 20; bp = xmalloc(len); (void) sprintf(bp, "%s -t %s", cmd, targ); - host = thost; + host = cleanhostname(thost); if (do_cmd(host, tuser, bp, &remin, &remout) < 0) exit(1); @@ -449,6 +480,7 @@ tolocal(argc, argv) else if (!okname(suser)) continue; } + host = cleanhostname(host); len = strlen(src) + CMDNEEDS + 20; bp = xmalloc(len); (void) sprintf(bp, "%s -f %s", cmd, src); @@ -913,7 +945,7 @@ void usage() { (void) fprintf(stderr, - "usage: scp [-pqrvC] [-P port] [-c cipher] [-i identity] f1 f2; or:\n scp [options] f1 ... fn directory\n"); + "usage: scp [-pqrvC46] [-P port] [-c cipher] [-i identity] f1 f2; or:\n scp [options] f1 ... fn directory\n"); exit(1); } @@ -983,11 +1015,19 @@ char * colon(cp) char *cp; { + int flag = 0; + if (*cp == ':') /* Leading colon is part of file name. */ return (0); + if (*cp == '[') + flag = 1; for (; *cp; ++cp) { - if (*cp == ':') + if (*cp == '@' && *(cp+1) == '[') + flag = 1; + if (*cp == ']' && *(cp+1) == ':' && flag) + return (cp+1); + if (*cp == ':' && !flag) return (cp); if (*cp == '/') return (0); diff --git a/servconf.c b/servconf.c index 71f9cac5..98146b18 100644 --- a/servconf.c +++ b/servconf.c @@ -18,14 +18,18 @@ RCSID("$Id$"); #include "servconf.h" #include "xmalloc.h" +/* add listen address */ +void add_listen_addr(ServerOptions *options, char *addr); + /* Initializes the server options to their default values. */ void initialize_server_options(ServerOptions *options) { memset(options, 0, sizeof(*options)); - options->port = -1; - options->listen_addr.s_addr = htonl(INADDR_ANY); + options->num_ports = 0; + options->ports_from_cmdline = 0; + options->listen_addrs = NULL; options->host_key_file = NULL; options->server_key_bits = -1; options->login_grace_time = -1; @@ -68,16 +72,10 @@ initialize_server_options(ServerOptions *options) void fill_default_server_options(ServerOptions *options) { - if (options->port == -1) { - struct servent *sp; - - sp = getservbyname(SSH_SERVICE_NAME, "tcp"); - if (sp) - options->port = ntohs(sp->s_port); - else - options->port = SSH_DEFAULT_PORT; - endservent(); - } + if (options->num_ports == 0) + options->ports[options->num_ports++] = SSH_DEFAULT_PORT; + if (options->listen_addrs == NULL) + add_listen_addr(options, NULL); if (options->host_key_file == NULL) options->host_key_file = HOST_KEY_FILE; if (options->server_key_bits == -1) @@ -232,6 +230,37 @@ parse_token(const char *cp, const char *filename, return sBadOption; } +/* + * add listen address + */ +void +add_listen_addr(ServerOptions *options, char *addr) +{ + extern int IPv4or6; + struct addrinfo hints, *ai, *aitop; + char strport[NI_MAXSERV]; + int gaierr; + int i; + + if (options->num_ports == 0) + options->ports[options->num_ports++] = SSH_DEFAULT_PORT; + for (i = 0; i < options->num_ports; i++) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = (addr == NULL) ? AI_PASSIVE : 0; + snprintf(strport, sizeof strport, "%d", options->ports[i]); + if ((gaierr = getaddrinfo(addr, strport, &hints, &aitop)) != 0) + fatal("bad addr or host: %s (%s)\n", + addr ? addr : "", + gai_strerror(gaierr)); + for (ai = aitop; ai->ai_next; ai = ai->ai_next) + ; + ai->ai_next = options->listen_addrs; + options->listen_addrs = aitop; + } +} + /* Reads the server configuration file. */ void @@ -262,7 +291,24 @@ read_server_config(ServerOptions *options, const char *filename) bad_options++; continue; case sPort: - intptr = &options->port; + /* ignore ports from configfile if cmdline specifies ports */ + if (options->ports_from_cmdline) + continue; + if (options->listen_addrs != NULL) + fatal("%s line %d: ports must be specified before " + "ListenAdress.\n", filename, linenum); + if (options->num_ports >= MAX_PORTS) + fatal("%s line %d: too many ports.\n", + filename, linenum); + cp = strtok(NULL, WHITESPACE); + if (!cp) + fatal("%s line %d: missing port number.\n", + filename, linenum); + options->ports[options->num_ports++] = atoi(cp); + break; + + case sServerKeyBits: + intptr = &options->server_key_bits; parse_int: cp = strtok(NULL, WHITESPACE); if (!cp) { @@ -275,10 +321,6 @@ parse_int: *intptr = value; break; - case sServerKeyBits: - intptr = &options->server_key_bits; - goto parse_int; - case sLoginGraceTime: intptr = &options->login_grace_time; goto parse_int; @@ -289,12 +331,10 @@ parse_int: case sListenAddress: cp = strtok(NULL, WHITESPACE); - if (!cp) { - fprintf(stderr, "%s line %d: missing inet addr.\n", - filename, linenum); - exit(1); - } - options->listen_addr.s_addr = inet_addr(cp); + if (!cp) + fatal("%s line %d: missing inet addr.\n", + filename, linenum); + add_listen_addr(options, cp); break; case sHostKeyFile: diff --git a/servconf.h b/servconf.h index aea44973..26e6ea3f 100644 --- a/servconf.h +++ b/servconf.h @@ -18,15 +18,19 @@ #ifndef SERVCONF_H #define SERVCONF_H +#define MAX_PORTS 256 /* Max # ports. */ + #define MAX_ALLOW_USERS 256 /* Max # users on allow list. */ #define MAX_DENY_USERS 256 /* Max # users on deny list. */ #define MAX_ALLOW_GROUPS 256 /* Max # groups on allow list. */ #define MAX_DENY_GROUPS 256 /* Max # groups on deny list. */ typedef struct { - int port; /* Port number to listen on. */ - struct in_addr listen_addr; /* Address on which the server - * listens. */ + unsigned int num_ports; + unsigned int ports_from_cmdline; + u_short ports[MAX_PORTS]; /* Port number to listen on. */ + char *listen_addr; /* Address on which the server listens. */ + struct addrinfo *listen_addrs; /* Addresses on which the server listens. */ char *host_key_file; /* File containing host key. */ int server_key_bits;/* Size of the server key. */ int login_grace_time; /* Disconnect if no auth in this time diff --git a/ssh.1.in b/ssh.1.in index fe2bd62d..f7f10ea7 100644 --- a/ssh.1.in +++ b/ssh.1.in @@ -24,7 +24,7 @@ .Op Ar command .Pp .Nm ssh -.Op Fl afgknqtvxCPX +.Op Fl afgknqtvxCPX46 .Op Fl c Ar blowfish | 3des .Op Fl e Ar escape_char .Op Fl i Ar identity_file @@ -396,9 +396,13 @@ by allocating a socket to listen to on the local side, and whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to -.Ar host:hostport +.Ar host +port +.Ar hostport from the remote machine. Port forwardings can also be specified in the configuration file. Only root can forward privileged ports. +IPv6 addresses can be specified with an alternative syntax: +.Ar port/host/hostport .It Fl R Ar port:host:hostport Specifies that the given port on the remote (server) host is to be forwarded to the given host and port on the local side. This works @@ -407,10 +411,20 @@ by allocating a socket to listen to on the remote side, and whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to -.Ar host:hostport +.Ar host +port +.Ar hostport from the local machine. Port forwardings can also be specified in the configuration file. Privileged ports can be forwarded only when logging in as root on the remote machine. +.It Fl 4 +Forces +.Nm +to use IPv4 addresses only. +.It Fl 6 +Forces +.Nm +to use IPv6 addresses only. .El .Sh CONFIGURATION FILES .Nm diff --git a/ssh.c b/ssh.c index ff7c29c8..154f31a1 100644 --- a/ssh.c +++ b/ssh.c @@ -27,6 +27,10 @@ extern char *__progname; const char *__progname = "ssh"; #endif /* HAVE___PROGNAME */ +/* Flag indicating whether IPv4 or IPv6. This can be set on the command line. + Default value is AF_UNSPEC means both IPv4 and IPv6. */ +int IPv4or6 = AF_UNSPEC; + /* Flag indicating whether debug mode is on. This can be set on the command line. */ int debug_flag = 0; @@ -59,7 +63,7 @@ Options options; char *host; /* socket address the host resolves to */ -struct sockaddr_in hostaddr; +struct sockaddr_storage hostaddr; /* * Flag to indicate that we have received a window change signal which has @@ -114,6 +118,8 @@ usage() fprintf(stderr, " forward them to the other side by connecting to host:port.\n"); fprintf(stderr, " -C Enable compression.\n"); fprintf(stderr, " -g Allow remote hosts to connect to forwarded ports.\n"); + fprintf(stderr, " -4 Use IPv4 only.\n"); + fprintf(stderr, " -6 Use IPv6 only.\n"); fprintf(stderr, " -o 'option' Process the option as if it was read from a configuration file.\n"); exit(1); } @@ -227,6 +233,8 @@ main(int ac, char **av) if (host) break; if ((cp = strchr(av[optind], '@'))) { + if(cp == av[optind]) + usage(); options.user = av[optind]; *cp = '\0'; host = ++cp; @@ -250,6 +258,14 @@ main(int ac, char **av) optarg = NULL; } switch (opt) { + case '4': + IPv4or6 = AF_INET; + break; + + case '6': + IPv4or6 = AF_INET6; + break; + case 'n': stdin_null_flag = 1; break; @@ -351,8 +367,10 @@ main(int ac, char **av) break; case 'R': - if (sscanf(optarg, "%hu:%255[^:]:%hu", &fwd_port, buf, - &fwd_host_port) != 3) { + if (sscanf(optarg, "%hu/%255[^/]/%hu", &fwd_port, buf, + &fwd_host_port) != 3 && + sscanf(optarg, "%hu:%255[^:]:%hu", &fwd_port, buf, + &fwd_host_port) != 3) { fprintf(stderr, "Bad forwarding specification '%s'.\n", optarg); usage(); /* NOTREACHED */ @@ -361,8 +379,10 @@ main(int ac, char **av) break; case 'L': - if (sscanf(optarg, "%hu:%255[^:]:%hu", &fwd_port, buf, - &fwd_host_port) != 3) { + if (sscanf(optarg, "%hu/%255[^/]/%hu", &fwd_port, buf, + &fwd_host_port) != 3 && + sscanf(optarg, "%hu:%255[^:]:%hu", &fwd_port, buf, + &fwd_host_port) != 3) { fprintf(stderr, "Bad forwarding specification '%s'.\n", optarg); usage(); /* NOTREACHED */ @@ -473,14 +493,18 @@ main(int ac, char **av) /* Find canonic host name. */ if (strchr(host, '.') == 0) { - struct hostent *hp = gethostbyname(host); - if (hp != 0) { - if (strchr(hp->h_name, '.') != 0) - host = xstrdup(hp->h_name); - else if (hp->h_aliases != 0 - && hp->h_aliases[0] != 0 - && strchr(hp->h_aliases[0], '.') != 0) - host = xstrdup(hp->h_aliases[0]); + struct addrinfo hints; + struct addrinfo *ai = NULL; + int errgai; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + errgai = getaddrinfo(host, NULL, &hints, &ai); + if (errgai == 0) { + if (ai->ai_canonname != NULL) + host = xstrdup(ai->ai_canonname); + freeaddrinfo(ai); } } /* Disable rhosts authentication if not running as root. */ @@ -587,7 +611,7 @@ main(int ac, char **av) /* Log into the remote system. This never returns if the login fails. */ ssh_login(host_private_key_loaded, host_private_key, - host, &hostaddr, original_real_uid); + host, (struct sockaddr *)&hostaddr, original_real_uid); /* We no longer need the host private key. Clear it now. */ if (host_private_key_loaded) diff --git a/ssh.h b/ssh.h index 7d1cd4b0..d03fec91 100644 --- a/ssh.h +++ b/ssh.h @@ -21,6 +21,8 @@ #include /* For struct sockaddr_in */ #include /* For struct pw */ #include /* For va_list */ +#include /* For struct sockaddr_storage */ +#include "fake-socket.h" /* For struct sockaddr_storage */ #ifdef HAVE_SYS_SELECT_H # include #endif @@ -284,7 +286,7 @@ get_last_login_time(uid_t uid, const char *logname, */ void record_login(int pid, const char *ttyname, const char *user, uid_t uid, - const char *host, struct sockaddr_in * addr); + const char *host, struct sockaddr *addr); /* * Records that the user has logged out. This does many thigs normally done @@ -304,7 +306,7 @@ void record_logout(int pid, const char *ttyname); * packet_set_connection for the connection. */ int -ssh_connect(const char *host, struct sockaddr_in * hostaddr, +ssh_connect(const char *host, struct sockaddr_storage * hostaddr, u_short port, int connection_attempts, int anonymous, uid_t original_real_uid, const char *proxy_command); @@ -320,7 +322,7 @@ ssh_connect(const char *host, struct sockaddr_in * hostaddr, void ssh_login(int host_key_valid, RSA * host_key, const char *host, - struct sockaddr_in * hostaddr, uid_t original_real_uid); + struct sockaddr * hostaddr, uid_t original_real_uid); /*------------ Definitions for various authentication methods. -------*/ @@ -380,8 +382,10 @@ const char *get_remote_ipaddr(void); /* Returns the port number of the peer of the socket. */ int get_peer_port(int sock); -/* Returns the port number of the remote host. */ +/* Returns the port number of the remote/local host. */ int get_remote_port(void); +int get_local_port(void); + /* * Tries to match the host name (which must be in all lowercase) against the @@ -741,6 +745,9 @@ char *skey_fake_keyinfo(char *username); int auth_skey_password(struct passwd * pw, const char *password); #endif /* SKEY */ +/* AF_UNSPEC or AF_INET or AF_INET6 */ +extern int IPv4or6; + #ifdef USE_PAM #include "auth-pam.h" #endif /* USE_PAM */ diff --git a/sshconnect.c b/sshconnect.c index 98b42f07..56cc93f2 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -35,6 +35,7 @@ RCSID("$Id$"); unsigned char session_id[16]; extern Options options; +extern char *__progname; /* * Connect to the given ssh server using a proxy command. @@ -48,10 +49,10 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, char *command_string; int pin[2], pout[2]; int pid; - char portstring[100]; + char strport[NI_MAXSERV]; /* Convert the port number into a string. */ - snprintf(portstring, sizeof portstring, "%hu", port); + snprintf(strport, sizeof strport, "%hu", port); /* Build the final command string in the buffer by making the appropriate substitutions to the given proxy command. */ @@ -68,7 +69,7 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, continue; } if (cp[0] == '%' && cp[1] == 'p') { - buffer_append(&command, portstring, strlen(portstring)); + buffer_append(&command, strport, strlen(strport)); cp++; continue; } @@ -140,7 +141,7 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, * Creates a (possibly privileged) socket for use as the ssh connection. */ int -ssh_create_socket(uid_t original_real_uid, int privileged) +ssh_create_socket(uid_t original_real_uid, int privileged, int family) { int sock; @@ -150,10 +151,9 @@ ssh_create_socket(uid_t original_real_uid, int privileged) */ if (privileged) { int p = IPPORT_RESERVED - 1; - - sock = rresvport(&p); + sock = rresvport_af(&p, family); if (sock < 0) - fatal("rresvport: %.100s", strerror(errno)); + fatal("rresvport: af=%d %.100s", family, strerror(errno)); debug("Allocated local port %d.", p); } else { /* @@ -161,17 +161,18 @@ ssh_create_socket(uid_t original_real_uid, int privileged) * the user's uid to create the socket. */ temporarily_use_uid(original_real_uid); - sock = socket(AF_INET, SOCK_STREAM, 0); + sock = socket(family, SOCK_STREAM, 0); if (sock < 0) - fatal("socket: %.100s", strerror(errno)); + error("socket: %.100s", strerror(errno)); restore_uid(); } return sock; } /* - * Opens a TCP/IP connection to the remote server on the given host. If - * port is 0, the default port will be used. If anonymous is zero, + * Opens a TCP/IP connection to the remote server on the given host. + * The address of the remote host will be returned in hostaddr. + * If port is 0, the default port will be used. If anonymous is zero, * a privileged port will be allocated to make the connection. * This requires super-user privileges if anonymous is false. * Connection_attempts specifies the maximum number of tries (one per @@ -180,15 +181,16 @@ ssh_create_socket(uid_t original_real_uid, int privileged) * the daemon. */ int -ssh_connect(const char *host, struct sockaddr_in * hostaddr, +ssh_connect(const char *host, struct sockaddr_storage * hostaddr, u_short port, int connection_attempts, int anonymous, uid_t original_real_uid, const char *proxy_command) { - int sock = -1, attempt, i; - int on = 1; + int sock = -1, attempt; struct servent *sp; - struct hostent *hp; + struct addrinfo hints, *ai, *aitop; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int gaierr; struct linger linger; debug("ssh_connect: getuid %d geteuid %d anon %d", @@ -208,8 +210,13 @@ ssh_connect(const char *host, struct sockaddr_in * hostaddr, /* No proxy command. */ - /* No host lookup made yet. */ - hp = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof strport, "%d", port); + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) + fatal("%s: %.100s: %s", __progname, host, + gai_strerror(gaierr)); /* * Try to connect several times. On some machines, the first time @@ -220,82 +227,40 @@ ssh_connect(const char *host, struct sockaddr_in * hostaddr, if (attempt > 0) debug("Trying again..."); - /* Try to parse the host name as a numeric inet address. */ - memset(hostaddr, 0, sizeof(hostaddr)); - hostaddr->sin_family = AF_INET; - hostaddr->sin_port = htons(port); - hostaddr->sin_addr.s_addr = inet_addr(host); - if ((hostaddr->sin_addr.s_addr & 0xffffffff) != 0xffffffff) { - /* Valid numeric IP address */ - debug("Connecting to %.100s port %d.", - inet_ntoa(hostaddr->sin_addr), port); - - /* Create a socket. */ - sock = ssh_create_socket(original_real_uid, - !anonymous && geteuid() == 0 && - port < IPPORT_RESERVED); - - /* - * Connect to the host. We use the user's uid in the - * hope that it will help with the problems of - * tcp_wrappers showing the remote uid as root. + /* Loop through addresses for this host, and try each one in + sequence until the connection succeeds. */ + for (ai = aitop; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("ssh_connect: getnameinfo failed"); + continue; + } + debug("Connecting to %.200s [%.100s] port %s.", + host, ntop, strport); + + /* Create a socket for connecting. */ + sock = ssh_create_socket(original_real_uid, + !anonymous && geteuid() == 0 && port < IPPORT_RESERVED, + ai->ai_family); + if (sock < 0) + continue; + + /* Connect to the host. We use the user's uid in the + * hope that it will help with tcp_wrappers showing + * the remote uid as root. */ temporarily_use_uid(original_real_uid); - if (connect(sock, (struct sockaddr *) hostaddr, sizeof(*hostaddr)) - >= 0) { - /* Successful connect. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) { + /* Successful connection. */ + memcpy(hostaddr, ai->ai_addr, sizeof(*(ai->ai_addr))); restore_uid(); break; - } - debug("connect: %.100s", strerror(errno)); - restore_uid(); - - /* Destroy the failed socket. */ - shutdown(sock, SHUT_RDWR); - close(sock); - } else { - /* Not a valid numeric inet address. */ - /* Map host name to an address. */ - if (!hp) - hp = gethostbyname(host); - if (!hp) - fatal("Bad host name: %.100s", host); - if (!hp->h_addr_list[0]) - fatal("Host does not have an IP address: %.100s", host); - - /* Loop through addresses for this host, and try - each one in sequence until the connection - succeeds. */ - for (i = 0; hp->h_addr_list[i]; i++) { - /* Set the address to connect to. */ - hostaddr->sin_family = hp->h_addrtype; - memcpy(&hostaddr->sin_addr, hp->h_addr_list[i], - sizeof(hostaddr->sin_addr)); - - debug("Connecting to %.200s [%.100s] port %d.", - host, inet_ntoa(hostaddr->sin_addr), port); - - /* Create a socket for connecting. */ - sock = ssh_create_socket(original_real_uid, - !anonymous && geteuid() == 0 && - port < IPPORT_RESERVED); - - /* - * Connect to the host. We use the user's - * uid in the hope that it will help with - * tcp_wrappers showing the remote uid as - * root. - */ - temporarily_use_uid(original_real_uid); - if (connect(sock, (struct sockaddr *) hostaddr, - sizeof(*hostaddr)) >= 0) { - /* Successful connection. */ - restore_uid(); - break; - } + } else { debug("connect: %.100s", strerror(errno)); restore_uid(); - /* * Close the failed socket; there appear to * be some problems when reusing a socket for @@ -305,13 +270,16 @@ ssh_connect(const char *host, struct sockaddr_in * hostaddr, shutdown(sock, SHUT_RDWR); close(sock); } - if (hp->h_addr_list[i]) - break; /* Successful connection. */ } + if (ai) + break; /* Successful connection. */ /* Sleep a moment before retrying. */ sleep(1); } + + freeaddrinfo(aitop); + /* Return failure if we didn't get a successful connection. */ if (attempt >= connection_attempts) return 0; @@ -323,7 +291,6 @@ ssh_connect(const char *host, struct sockaddr_in * hostaddr, * as it has been closed for whatever reason. */ /* setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); */ - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *) &on, sizeof(on)); linger.l_onoff = 1; linger.l_linger = 5; setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); @@ -1095,17 +1062,43 @@ read_yes_or_no(const char *prompt, int defval) */ void -check_host_key(char *host, - struct sockaddr_in *hostaddr, - RSA *host_key) +check_host_key(char *host, struct sockaddr *hostaddr, RSA *host_key) { RSA *file_key; char *ip = NULL; char hostline[1000], *hostp; HostStatus host_status; HostStatus ip_status; - int host_ip_differ = 0; - int local = (ntohl(hostaddr->sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; + int local = 0, host_ip_differ = 0; + int sa_len; + char ntop[NI_MAXHOST]; + + /* + * Force accepting of the host key for loopback/localhost. The + * problem is that if the home directory is NFS-mounted to multiple + * machines, localhost will refer to a different machine in each of + * them, and the user will get bogus HOST_CHANGED warnings. This + * essentially disables host authentication for localhost; however, + * this is probably not a real problem. + */ + switch (hostaddr->sa_family) { + case AF_INET: + local = (ntohl(((struct sockaddr_in *)hostaddr)->sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + local = IN6_IS_ADDR_LOOPBACK(&(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); + sa_len = sizeof(struct sockaddr_in6); + break; + default: + local = 0; + sa_len = sizeof(struct sockaddr_storage); + break; + } + if (local) { + debug("Forcing accepting of host key for loopback/localhost."); + return; + } /* * Turn off check_host_ip for proxy connects, since @@ -1114,8 +1107,12 @@ check_host_key(char *host, if (options.proxy_command != NULL && options.check_host_ip) options.check_host_ip = 0; - if (options.check_host_ip) - ip = xstrdup(inet_ntoa(hostaddr->sin_addr)); + if (options.check_host_ip) { + if (getnameinfo(hostaddr, sa_len, ntop, sizeof(ntop), + NULL, 0, NI_NUMERICHOST) != 0) + fatal("check_host_key: getnameinfo failed"); + ip = xstrdup(ntop); + } /* * Store the host key from the known host file in here so that we can @@ -1136,18 +1133,6 @@ check_host_key(char *host, host_status = check_host_in_hostfile(options.system_hostfile, host, host_key->e, host_key->n, file_key->e, file_key->n); - /* - * Force accepting of the host key for localhost and 127.0.0.1. The - * problem is that if the home directory is NFS-mounted to multiple - * machines, localhost will refer to a different machine in each of - * them, and the user will get bogus HOST_CHANGED warnings. This - * essentially disables host authentication for localhost; however, - * this is probably not a real problem. - */ - if (local) { - debug("Forcing accepting of host key for localhost."); - host_status = HOST_OK; - } /* * Also perform check for the ip address, skip the check if we are * localhost or the hostname was an ip address to begin with @@ -1301,7 +1286,7 @@ void ssh_login(int host_key_valid, RSA *own_host_key, const char *orighost, - struct sockaddr_in *hostaddr, + struct sockaddr *hostaddr, uid_t original_real_uid) { int i, type; diff --git a/sshd.8.in b/sshd.8.in index d4585af3..ecbd848e 100644 --- a/sshd.8.in +++ b/sshd.8.in @@ -19,7 +19,7 @@ .Nd secure shell daemon .Sh SYNOPSIS .Nm sshd -.Op Fl diqQ +.Op Fl diqQ46 .Op Fl b Ar bits .Op Fl f Ar config_file .Op Fl g Ar login_grace_time @@ -173,6 +173,14 @@ When this options is specified assumes the client has sent the given version string and skips the Protocol Version Identification Exchange. +.It Fl 4 +Forces +.Nm +to use IPv4 addresses only. +.It Fl 6 +Forces +.Nm +to use IPv6 addresses only. .El .Sh CONFIGURATION FILE .Nm @@ -320,6 +328,10 @@ Specifies what local address .Nm should listen on. The default is to listen to all local addresses. +Multiple options of this type are permitted. +Additionally, the +.Cm Ports +options must precede this option. .It Cm LoginGraceTime The server disconnects after this time if the user has not successfully logged in. If the value is 0, there is no time limit. @@ -365,6 +377,7 @@ normally not allowed). Specifies the port number that .Nm listens on. The default is 22. +Multiple options of this type are permitted. .It Cm PrintMotd Specifies whether .Nm diff --git a/sshd.c b/sshd.c index 685fd27f..0eaffebd 100644 --- a/sshd.c +++ b/sshd.c @@ -45,6 +45,12 @@ ServerOptions options; /* Name of the server configuration file. */ char *config_file_name = SERVER_CONFIG_FILE; +/* + * Flag indicating whether IPv4 or IPv6. This can be set on the command line. + * Default value is AF_UNSPEC means both IPv4 and IPv6. + */ +int IPv4or6 = AF_UNSPEC; + /* * Debug mode flag. This can be set on the command line. If debug * mode is enabled, extra debugging output will be sent to the system @@ -66,10 +72,12 @@ char *av0; char **saved_argv; /* - * This is set to the socket that the server is listening; this is used in - * the SIGHUP signal handler. + * The sockets that the server is listening; this is used in the SIGHUP + * signal handler. */ -int listen_sock; +#define MAX_LISTEN_SOCKS 16 +int listen_socks[MAX_LISTEN_SOCKS]; +int num_listen_socks = 0; /* * the client's version string, passed by sshd2 in compat mode. if != NULL, @@ -135,6 +143,18 @@ void do_child(const char *command, struct passwd * pw, const char *term, const char *display, const char *auth_proto, const char *auth_data, const char *ttyname); +/* + * Close all listening sockets + */ +void +close_listen_socks(void) +{ + int i; + for (i = 0; i < num_listen_socks; i++) + close(listen_socks[i]); + num_listen_socks = -1; +} + /* * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; * the effect is to reread the configuration file (and to regenerate @@ -155,7 +175,7 @@ void sighup_restart() { log("Received SIGHUP; restarting."); - close(listen_sock); + close_listen_socks(); execv(saved_argv[0], saved_argv); log("RESTART FAILED: av0='%s', error: %s.", av0, strerror(errno)); exit(1); @@ -170,7 +190,7 @@ void sigterm_handler(int sig) { log("Received signal %d; terminating.", sig); - close(listen_sock); + close_listen_socks(); exit(255); } @@ -277,11 +297,12 @@ main(int ac, char **av) { extern char *optarg; extern int optind; - int opt, aux, sock_in, sock_out, newsock, i, pid, on = 1; + int opt, sock_in = 0, sock_out = 0, newsock, i, fdsetsz, pid, on = 1; + socklen_t fromlen; int remote_major, remote_minor; int silentrsa = 0; - struct pollfd fds; - struct sockaddr_in sin; + fd_set *fdset; + struct sockaddr_storage from; char buf[100]; /* Must not be larger than remote_version. */ char remote_version[100]; /* Must be at least as big as buf. */ const char *remote_ip; @@ -289,6 +310,9 @@ main(int ac, char **av) char *comment; FILE *f; struct linger linger; + struct addrinfo *ai; + char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int listen_sock, maxfd; /* Save argv[0]. */ saved_argv = av; @@ -301,8 +325,14 @@ main(int ac, char **av) initialize_server_options(&options); /* Parse command-line arguments. */ - while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:diqQ")) != EOF) { + while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:diqQ46")) != EOF) { switch (opt) { + case '4': + IPv4or6 = AF_INET; + break; + case '6': + IPv4or6 = AF_INET6; + break; case 'f': config_file_name = optarg; break; @@ -323,7 +353,10 @@ main(int ac, char **av) options.server_key_bits = atoi(optarg); break; case 'p': - options.port = atoi(optarg); + options.ports_from_cmdline = 1; + if (options.num_ports >= MAX_PORTS) + fatal("too many ports.\n"); + options.ports[options.num_ports++] = atoi(optarg); break; case 'g': options.login_grace_time = atoi(optarg); @@ -356,11 +389,22 @@ main(int ac, char **av) fprintf(stderr, " -g seconds Grace period for authentication (default: 300)\n"); fprintf(stderr, " -b bits Size of server RSA key (default: 768 bits)\n"); fprintf(stderr, " -h file File from which to read host key (default: %s)\n", - HOST_KEY_FILE); + HOST_KEY_FILE); + fprintf(stderr, " -4 Use IPv4 only\n"); + fprintf(stderr, " -6 Use IPv6 only\n"); exit(1); } } + /* + * Force logging to stderr until we have loaded the private host + * key (unless started from inetd) + */ + log_init(av0, + options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, + options.log_facility == -1 ? SYSLOG_FACILITY_AUTH : options.log_facility, + !inetd_flag); + /* check if RSA support exists */ if (rsa_alive() == 0) { if (silentrsa == 0) @@ -380,18 +424,11 @@ main(int ac, char **av) fprintf(stderr, "Bad server key size.\n"); exit(1); } - if (options.port < 1 || options.port > 65535) { - fprintf(stderr, "Bad port number.\n"); - exit(1); - } /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); exit(1); } - /* Force logging to stderr while loading the private host key - unless started from inetd */ - log_init(av0, options.log_level, options.log_facility, !inetd_flag); debug("sshd version %.100s", SSH_VERSION); @@ -480,32 +517,66 @@ main(int ac, char **av) arc4random_stir(); log("RSA key generation complete."); } else { - /* Create socket for listening. */ - listen_sock = socket(AF_INET, SOCK_STREAM, 0); - if (listen_sock < 0) - fatal("socket: %.100s", strerror(errno)); - - /* Set socket options. We try to make the port reusable - and have it close as fast as possible without waiting - in unnecessary wait states on close. */ - setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, - sizeof(on)); - linger.l_onoff = 1; - linger.l_linger = 5; - setsockopt(listen_sock, SOL_SOCKET, SO_LINGER, (void *) &linger, - sizeof(linger)); - - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_addr = options.listen_addr; - sin.sin_port = htons(options.port); - - if (bind(listen_sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) { - error("bind: %.100s", strerror(errno)); - shutdown(listen_sock, SHUT_RDWR); - close(listen_sock); - fatal("Bind to port %d failed.", options.port); + for (ai = options.listen_addrs; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + if (num_listen_socks >= MAX_LISTEN_SOCKS) + fatal("Too many listen sockets. " + "Enlarge MAX_LISTEN_SOCKS"); + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + error("getnameinfo failed"); + continue; + } + /* Create socket for listening. */ + listen_sock = socket(ai->ai_family, SOCK_STREAM, 0); + if (listen_sock < 0) { + /* kernel may not support ipv6 */ + verbose("socket: %.100s", strerror(errno)); + continue; + } + if (fcntl(listen_sock, F_SETFL, O_NONBLOCK) < 0) { + error("listen_sock O_NONBLOCK: %s", strerror(errno)); + close(listen_sock); + continue; + } + /* + * Set socket options. We try to make the port + * reusable and have it close as fast as possible + * without waiting in unnecessary wait states on + * close. + */ + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, + (void *) &on, sizeof(on)); + linger.l_onoff = 1; + linger.l_linger = 5; + setsockopt(listen_sock, SOL_SOCKET, SO_LINGER, + (void *) &linger, sizeof(linger)); + + debug("Bind to port %s on %s.", strport, ntop); + + /* Bind the socket to the desired port. */ + if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) { + error("Bind to port %s on %s failed: %.200s.", + strport, ntop, strerror(errno)); + close(listen_sock); + continue; + } + listen_socks[num_listen_socks] = listen_sock; + num_listen_socks++; + + /* Start listening on the port. */ + log("Server listening on %s port %s.", ntop, strport); + if (listen(listen_sock, 5) < 0) + fatal("listen: %.100s", strerror(errno)); + } + freeaddrinfo(options.listen_addrs); + + if (!num_listen_socks) + fatal("Cannot bind any address."); + if (!debug_flag) { /* * Record our pid in /etc/sshd_pid to make it easier @@ -521,10 +592,6 @@ main(int ac, char **av) } } - log("Server listening on port %d.", options.port); - if (listen(listen_sock, 5) < 0) - fatal("listen: %.100s", strerror(errno)); - public_key = RSA_new(); sensitive_data.private_key = RSA_new(); @@ -546,6 +613,14 @@ main(int ac, char **av) /* Arrange SIGCHLD to be caught. */ signal(SIGCHLD, main_sigchld_handler); + /* setup fd set for listen */ + maxfd = 0; + for (i = 0; i < num_listen_socks; i++) + if (listen_socks[i] > maxfd) + maxfd = listen_socks[i]; + fdsetsz = howmany(maxfd, NFDBITS) * sizeof(fd_mask); + fdset = (fd_set *)xmalloc(fdsetsz); + /* * Stay listening for connections until the system crashes or * the daemon is killed with a signal. @@ -553,26 +628,28 @@ main(int ac, char **av) for (;;) { if (received_sighup) sighup_restart(); - /* Wait in poll until there is a connection. */ - memset(&fds, 0, sizeof(fds)); - fds.fd = listen_sock; - fds.events = POLLIN; - if (poll(&fds, 1, -1) == -1) { - if (errno == EINTR) - continue; - fatal("poll: %.100s", strerror(errno)); - /*NOTREACHED*/ - } - if (fds.revents == 0) + /* Wait in select until there is a connection. */ + memset(fdset, 0, fdsetsz); + for (i = 0; i < num_listen_socks; i++) + FD_SET(listen_socks[i], fdset); + if (select(maxfd + 1, fdset, NULL, NULL, NULL) < 0) { + if (errno != EINTR) + error("select: %.100s", strerror(errno)); continue; - aux = sizeof(sin); - newsock = accept(listen_sock, (struct sockaddr *) & sin, &aux); - if (received_sighup) - sighup_restart(); - if (newsock < 0) { - if (errno == EINTR) + } + for (i = 0; i < num_listen_socks; i++) { + if (!FD_ISSET(listen_socks[i], fdset)) continue; - error("accept: %.100s", strerror(errno)); + fromlen = sizeof(from); + newsock = accept(listen_socks[i], (struct sockaddr *)&from, + &fromlen); + if (newsock < 0) { + if (errno != EINTR && errno != EWOULDBLOCK) + error("accept: %.100s", strerror(errno)); + continue; + } + if (fcntl(newsock, F_SETFL, 0) < 0) { + error("newsock del O_NONBLOCK: %s", strerror(errno)); continue; } /* @@ -586,7 +663,7 @@ main(int ac, char **av) * connection without forking. */ debug("Server will not fork when running in debugging mode."); - close(listen_sock); + close_listen_socks(); sock_in = newsock; sock_out = newsock; pid = getpid(); @@ -603,7 +680,7 @@ main(int ac, char **av) * accepted socket. Reinitialize logging (since our pid has * changed). We break out of the loop to handle the connection. */ - close(listen_sock); + close_listen_socks(); sock_in = newsock; sock_out = newsock; log_init(av0, options.log_level, options.log_facility, log_stderr); @@ -624,6 +701,10 @@ main(int ac, char **av) /* Close the new socket (the child is now taking care of it). */ close(newsock); + } /* for (i = 0; i < num_listen_socks; i++) */ + /* child process check (or debug mode) */ + if (num_listen_socks < 0) + break; } } @@ -662,6 +743,7 @@ main(int ac, char **av) /* Check whether logins are denied from this host. */ #ifdef LIBWRAP + /* XXX LIBWRAP noes not know about IPv6 */ { struct request_info req; @@ -673,12 +755,11 @@ main(int ac, char **av) close(sock_out); refuse(&req); } - verbose("Connection from %.500s port %d", eval_client(&req), remote_port); +/*XXX IPv6 verbose("Connection from %.500s port %d", eval_client(&req), remote_port); */ } -#else +#endif /* LIBWRAP */ /* Log the connection. */ verbose("Connection from %.500s port %d", remote_ip, remote_port); -#endif /* LIBWRAP */ /* * We don\'t want to listen forever unless the other side @@ -700,12 +781,12 @@ main(int ac, char **av) snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", PROTOCOL_MAJOR, PROTOCOL_MINOR, SSH_VERSION); if (atomicio(write, sock_out, buf, strlen(buf)) != strlen(buf)) - fatal("Could not write ident string to %s.", get_remote_ipaddr()); + fatal("Could not write ident string to %s.", remote_ip); /* Read other side\'s version identification. */ for (i = 0; i < sizeof(buf) - 1; i++) { if (read(sock_in, &buf[i], 1) != 1) - fatal("Did not receive ident string from %s.", get_remote_ipaddr()); + fatal("Did not receive ident string from %s.", remote_ip); if (buf[i] == '\r') { buf[i] = '\n'; buf[i + 1] = 0; @@ -732,7 +813,7 @@ main(int ac, char **av) close(sock_in); close(sock_out); fatal("Bad protocol version identification '%.100s' from %s", - buf, get_remote_ipaddr()); + buf, remote_ip); } debug("Client protocol version %d.%d; client software version %.100s", remote_major, remote_minor, remote_version); @@ -743,8 +824,7 @@ main(int ac, char **av) close(sock_in); close(sock_out); fatal("Protocol major versions differ for %s: %d vs. %d", - get_remote_ipaddr(), - PROTOCOL_MAJOR, remote_major); + remote_ip, PROTOCOL_MAJOR, remote_major); } /* Check that the client has sufficiently high software version. */ if (remote_major == 1 && remote_minor < 3) @@ -769,6 +849,14 @@ main(int ac, char **av) options.rhosts_authentication = 0; options.rhosts_rsa_authentication = 0; } +#ifdef KRB4 + if (!packet_connection_is_ipv4() && + options.kerberos_authentication) { + debug("Kerberos Authentication disabled, only available for IPv4."); + options.kerberos_authentication = 0; + } +#endif /* KRB4 */ + packet_set_nonblocking(); /* Handle the connection. */ @@ -1935,8 +2023,8 @@ do_exec_pty(const char *command, int ptyfd, int ttyfd, char line[256]; struct stat st; int quiet_login; - struct sockaddr_in from; - int fromlen; + struct sockaddr_storage from; + socklen_t fromlen; struct pty_cleanup_context cleanup_context; /* Get remote host name. */ @@ -1997,7 +2085,7 @@ do_exec_pty(const char *command, int ptyfd, int ttyfd, } /* Record that there was a login on that terminal. */ record_login(pid, ttyname, pw->pw_name, pw->pw_uid, hostname, - &from); + (struct sockaddr *)&from); /* Check if .hushlogin exists. */ snprintf(line, sizeof line, "%.200s/.hushlogin", pw->pw_dir); @@ -2318,7 +2406,7 @@ do_child(const char *command, struct passwd * pw, const char *term, } snprintf(buf, sizeof buf, "%.50s %d %d", - get_remote_ipaddr(), get_remote_port(), options.port); + get_remote_ipaddr(), get_remote_port(), get_local_port()); child_set_env(&env, &envsize, "SSH_CLIENT", buf); if (ttyname) @@ -2385,7 +2473,6 @@ do_child(const char *command, struct passwd * pw, const char *term, * descriptors left by system functions. They will be closed later. */ endpwent(); - endhostent(); /* * Close any extra open file descriptors so that we don\'t have them diff --git a/sshd_config.in b/sshd_config.in index 63e3d982..cb2c56e0 100644 --- a/sshd_config.in +++ b/sshd_config.in @@ -2,6 +2,7 @@ Port 22 ListenAddress 0.0.0.0 +#ListenAddress :: HostKey @sysconfdir@/ssh_host_key ServerKeyBits 768 LoginGraceTime 600 -- 2.45.1