- (dtucker) [acconfig.h configure.ac defines.h session.c] Bug #252: Retrieve
   PATH (or SUPATH) and UMASK from /etc/default/login on platforms that have it
   (eg Solaris, Reliant Unix).  Patch from Robert.Dahlem at siemens.com.  ok djm@
diff --git a/ChangeLog b/ChangeLog
index 3f1a4cf..6a32bc1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+20030916
+ - (dtucker) [acconfig.h configure.ac defines.h session.c] Bug #252: Retrieve
+   PATH (or SUPATH) and UMASK from /etc/default/login on platforms that have it
+   (eg Solaris, Reliant Unix).  Patch from Robert.Dahlem at siemens.com.  ok djm@
+
 20030914
  - (dtucker) [Makefile regress/Makefile] Fix portability issues preventing
    the regression tests from running with Solaris' make.  Patch from Brian
@@ -1093,4 +1098,4 @@
  - Fix sshd BindAddress and -b options for systems using fake-getaddrinfo.
    Report from murple@murple.net, diagnosis from dtucker@zip.com.au
 
-$Id: ChangeLog,v 1.2991 2003/09/14 03:16:55 dtucker Exp $
+$Id: ChangeLog,v 1.2992 2003/09/16 01:52:19 dtucker Exp $
diff --git a/acconfig.h b/acconfig.h
index ea8fcb0..9bfb9b6 100644
--- a/acconfig.h
+++ b/acconfig.h
@@ -1,4 +1,4 @@
-/* $Id: acconfig.h,v 1.165 2003/09/08 21:35:17 tim Exp $ */
+/* $Id: acconfig.h,v 1.166 2003/09/16 01:52:19 dtucker Exp $ */
 
 /*
  * Copyright (c) 1999-2003 Damien Miller.  All rights reserved.
@@ -359,6 +359,9 @@
 /* Define in your struct dirent expects you to allocate extra space for d_name */
 #undef BROKEN_ONE_BYTE_DIRENT_D_NAME
 
+/* Define if your system has /etc/default/login */
+#undef HAVE_ETC_DEFAULT_LOGIN
+
 /* Define if your getopt(3) defines and uses optreset */
 #undef HAVE_GETOPT_OPTRESET
 
diff --git a/configure.ac b/configure.ac
index 3d5389c..ab63011 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-# $Id: configure.ac,v 1.153 2003/09/13 01:15:15 tim Exp $
+# $Id: configure.ac,v 1.154 2003/09/16 01:52:19 dtucker Exp $
 
 AC_INIT
 AC_CONFIG_SRCDIR([ssh.c])
@@ -250,6 +250,7 @@
 	AC_DEFINE(LOCKED_PASSWD_STRING, "*LK*")
 	# Pushing STREAMS modules will cause sshd to acquire a controlling tty.
 	AC_DEFINE(SSHD_ACQUIRES_CTTY)
+	external_path_file=/etc/default/login
 	# hardwire lastlog location (can't detect it on some versions)
 	conf_lastlog_location="/var/adm/lastlog"
 	AC_MSG_CHECKING(for obsolete utmp and wtmp in solaris2.x)
@@ -286,6 +287,7 @@
 	AC_DEFINE(USE_PIPES)
 	AC_DEFINE(IP_TOS_IS_BROKEN)
 	AC_DEFINE(SSHD_ACQUIRES_CTTY)
+	external_path_file=/etc/default/login
 	# /usr/ucblib/libucb.a no longer needed on ReliantUNIX
 	# Attention: always take care to bind libsocket and libnsl before libc,
 	# otherwise you will find lots of "SIOCGPGRP errno 22" on syslog
@@ -2180,30 +2182,48 @@
 	)
 fi
 
+# check for /etc/default/login and use it if present.
+AC_CHECK_FILE("/etc/default/login", [ external_path_file=/etc/default/login ])
+
+if test "x$external_path_file" = "x/etc/default/login"; then
+	AC_DEFINE(HAVE_ETC_DEFAULT_LOGIN)
+fi
+
 dnl BSD systems use /etc/login.conf so --with-default-path= has no effect
 if test $ac_cv_func_login_getcapbool = "yes" -a \
 	$ac_cv_header_login_cap_h = "yes" ; then
-	USES_LOGIN_CONF=yes
+	external_path_file=/etc/login.conf
 fi
+
 # Whether to mess with the default path
 SERVER_PATH_MSG="(default)" 
 AC_ARG_WITH(default-path,
 	[  --with-default-path=    Specify default \$PATH environment for server],
 	[
-		if test "$USES_LOGIN_CONF" = "yes" ; then
+		if test "x$external_path_file" = "x/etc/login.conf" ; then
 			AC_MSG_WARN([
 --with-default-path=PATH has no effect on this system.
 Edit /etc/login.conf instead.])
 		elif test "x$withval" != "xno" ; then	
+			if ! test -z "$external_path_file" ; then
+				AC_MSG_WARN([
+--with-default-path=PATH will only be used if PATH is not defined in
+$external_path_file .])
+			fi
 			user_path="$withval"
 			SERVER_PATH_MSG="$withval" 
 		fi
 	],
-	[ if test "$USES_LOGIN_CONF" = "yes" ; then
-	AC_MSG_WARN([Make sure the path to scp is in /etc/login.conf])
+	[ if test "x$external_path_file" = "x/etc/login.conf" ; then
+		AC_MSG_WARN([Make sure the path to scp is in /etc/login.conf])
 	else
-	AC_TRY_RUN(
-		[
+		if ! test -z "$external_path_file" ; then
+			AC_MSG_WARN([
+If PATH is defined in $external_path_file, ensure the path to scp is included,
+otherwise scp will not work.])
+		fi
+		AC_TRY_RUN(
+			[
 /* find out what STDPATH is */
 #include <stdio.h>
 #ifdef HAVE_PATHS_H
@@ -2257,7 +2277,7 @@
 		fi
 	fi ]
 )
-if test "$USES_LOGIN_CONF" != "yes" ; then
+if test "x$external_path_file" != "x/etc/login.conf" ; then
 	AC_DEFINE_UNQUOTED(USER_PATH, "$user_path")
 	AC_SUBST(user_path)
 fi
@@ -2627,10 +2647,15 @@
 echo "                      Manual pages: $F"
 echo "                          PID file: $G"
 echo "  Privilege separation chroot path: $H"
-if test "$USES_LOGIN_CONF" = "yes" ; then
-echo "   At runtime, sshd will use the path defined in /etc/login.conf"
+if test "x$external_path_file" = "x/etc/login.conf" ; then
+echo "   At runtime, sshd will use the path defined in $external_path_file"
+echo "   Make sure the path to scp is present, otherwise scp will not work"
 else
 echo "            sshd default user PATH: $I"
+	if ! test -z "$external_path_file"; then
+echo "   (If PATH is set in $external_path_file it will be used instead. If"
+echo "   used, ensure the path to scp is present, otherwise scp will not work.)"
+	fi
 fi
 if test ! -z "$superuser_path" ; then
 echo "          sshd superuser user PATH: $J"
diff --git a/defines.h b/defines.h
index 7bff839..e662966 100644
--- a/defines.h
+++ b/defines.h
@@ -25,7 +25,7 @@
 #ifndef _DEFINES_H
 #define _DEFINES_H
 
-/* $Id: defines.h,v 1.102 2003/08/26 01:58:16 dtucker Exp $ */
+/* $Id: defines.h,v 1.103 2003/09/16 01:52:19 dtucker Exp $ */
 
 
 /* Constants */
@@ -321,6 +321,10 @@
 # define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin"
 #endif
 
+#ifndef SUPERUSER_PATH
+# define SUPERUSER_PATH	_PATH_STDPATH
+#endif
+
 #ifndef _PATH_DEVNULL
 # define _PATH_DEVNULL "/dev/null"
 #endif
diff --git a/session.c b/session.c
index 35328ec..4497f5c 100644
--- a/session.c
+++ b/session.c
@@ -802,6 +802,16 @@
 	char **env;
 
 	/*
+	 * If we're passed an uninitialized list, allocate a single null
+	 * entry before continuing.
+	 */
+	if (*envp == NULL && *envsizep == 0) {
+		*envp = xmalloc(sizeof(char *));
+		*envp[0] = NULL;
+		*envsizep = 1;
+	}
+
+	/*
 	 * Find the slot where the value should be stored.  If the variable
 	 * already exists, we reuse the slot; otherwise we append a new slot
 	 * at the end of the array, expanding if necessary.
@@ -877,6 +887,59 @@
 	fclose(f);
 }
 
+#ifdef HAVE_ETC_DEFAULT_LOGIN
+/*
+ * Return named variable from specified environment, or NULL if not present.
+ */
+static char *
+child_get_env(char **env, const char *name)
+{
+	int i;
+	size_t len;
+
+	len = strlen(name);
+	for (i=0; env[i] != NULL; i++)
+		if (strncmp(name, env[i], len) == 0 && env[i][len] == '=')
+			return(env[i] + len + 1);
+	return NULL;
+}
+
+/*
+ * Read /etc/default/login.
+ * We pick up the PATH (or SUPATH for root) and UMASK.
+ */
+static void
+read_etc_default_login(char ***env, u_int *envsize, uid_t uid)
+{
+	char **tmpenv = NULL, *var;
+	u_int i;
+	size_t tmpenvsize = 0;
+	mode_t mask;
+
+	/*
+	 * We don't want to copy the whole file to the child's environment,
+	 * so we use a temporary environment and copy the variables we're
+	 * interested in.
+	 */
+	read_environment_file(&tmpenv, &tmpenvsize, "/etc/default/login");
+
+	if (uid == 0)
+		var = child_get_env(tmpenv, "SUPATH");
+	else
+		var = child_get_env(tmpenv, "PATH");
+	if (var != NULL)
+		child_set_env(env, envsize, "PATH", var);
+	
+	if ((var = child_get_env(tmpenv, "UMASK")) != NULL)
+		if (sscanf(var, "%5lo", &mask) == 1)
+			umask(mask);
+	
+	for (i = 0; tmpenv[i] != NULL; i++)
+		xfree(tmpenv[i]);
+	xfree(tmpenv);
+}
+#endif /* HAVE_ETC_DEFAULT_LOGIN */
+
 void copy_environment(char **source, char ***env, u_int *envsize)
 {
 	char *var_name, *var_val;
@@ -905,7 +968,7 @@
 {
 	char buf[256];
 	u_int i, envsize;
-	char **env, *laddr;
+	char **env, *laddr, *path = NULL;
 	struct passwd *pw = s->pw;
 
 	/* Initialize the environment. */
@@ -949,12 +1012,15 @@
 		 * needed for loading shared libraries. So the path better
 		 * remains intact here.
 		 */
-#  ifdef SUPERUSER_PATH
-		child_set_env(&env, &envsize, "PATH", 
-		    s->pw->pw_uid == 0 ? SUPERUSER_PATH : _PATH_STDPATH);
-#  else 
-		child_set_env(&env, &envsize, "PATH", _PATH_STDPATH);
-#  endif /* SUPERUSER_PATH */
+#  ifdef HAVE_ETC_DEFAULT_LOGIN
+		read_etc_default_login(&env, &envsize, pw->pw_uid);
+		path = child_get_env(env, "PATH");
+#  endif /* HAVE_ETC_DEFAULT_LOGIN */
+		if (path == NULL || *path == '\0') {
+			child_set_env(&env, &envsize, "PATH", 
+			    s->pw->pw_uid == 0 ?
+				SUPERUSER_PATH : _PATH_STDPATH);
+		}
 # endif /* HAVE_CYGWIN */
 #endif /* HAVE_LOGIN_CAP */