- markus@cvs.openbsd.org 2001/05/20 17:20:36
     [auth-rsa.c auth.c auth.h auth2.c servconf.c servconf.h sshd.8
      sshd_config]
     configurable authorized_keys{,2} location; originally from peter@;
     ok djm@
diff --git a/auth.c b/auth.c
index 1f976ee..07e5b8f 100644
--- a/auth.c
+++ b/auth.c
@@ -23,7 +23,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: auth.c,v 1.21 2001/03/19 17:07:23 markus Exp $");
+RCSID("$OpenBSD: auth.c,v 1.22 2001/05/20 17:20:35 markus Exp $");
 
 #ifdef HAVE_LOGIN_H
 #include <login.h>
@@ -32,6 +32,8 @@
 #include <shadow.h>
 #endif /* defined(HAVE_SHADOW_H) && !defined(DISABLE_SHADOW) */
 
+#include <libgen.h>
+
 #include "xmalloc.h"
 #include "match.h"
 #include "groupaccess.h"
@@ -40,6 +42,8 @@
 #include "auth.h"
 #include "auth-options.h"
 #include "canohost.h"
+#include "buffer.h"
+#include "bufaux.h"
 
 /* import */
 extern ServerOptions options;
@@ -222,3 +226,132 @@
 	log("ROOT LOGIN REFUSED FROM %.200s", get_remote_ipaddr());
 	return 0;
 }
+
+
+/*
+ * Given a template and a passwd structure, build a filename
+ * by substituting % tokenised options. Currently, %% becomes '%',
+ * %h becomes the home directory and %u the username.
+ *
+ * This returns a buffer allocated by xmalloc.
+ */
+char *
+expand_filename(const char *filename, struct passwd *pw)
+{
+	Buffer buffer;
+	char *file;
+	const char *cp;
+
+	/*
+	 * Build the filename string in the buffer by making the appropriate
+	 * substitutions to the given file name.
+	 */
+	buffer_init(&buffer);
+	for (cp = filename; *cp; cp++) {
+		if (cp[0] == '%' && cp[1] == '%') {
+			buffer_append(&buffer, "%", 1);
+			cp++;
+			continue;
+		}
+		if (cp[0] == '%' && cp[1] == 'h') {
+			buffer_append(&buffer, pw->pw_dir, strlen(pw->pw_dir));
+			cp++;
+			continue;
+		}
+		if (cp[0] == '%' && cp[1] == 'u') {
+			buffer_append(&buffer, pw->pw_name,
+			     strlen(pw->pw_name));
+			cp++;
+			continue;
+		}
+		buffer_append(&buffer, cp, 1);
+	}
+	buffer_append(&buffer, "\0", 1);
+
+	/*
+	 * Ensure that filename starts anchored. If not, be backward
+	 * compatible and prepend the '%h/'
+	 */
+	file = xmalloc(MAXPATHLEN);
+	cp = buffer_ptr(&buffer);
+	if (*cp != '/')
+		snprintf(file, MAXPATHLEN, "%s/%s", pw->pw_dir, cp);
+	else
+		strlcpy(file, cp, MAXPATHLEN);
+
+	buffer_free(&buffer);
+	return file;
+}
+
+char *
+authorized_keys_file(struct passwd *pw)
+{
+	return expand_filename(options.authorized_keys_file, pw);
+}
+
+char *
+authorized_keys_file2(struct passwd *pw)
+{
+	return expand_filename(options.authorized_keys_file2, pw);
+}
+
+/*
+ * Check a given file for security. This is defined as all components
+ * of the path to the file must either be owned by either the owner of
+ * of the file or root and no directories must be world writable.
+ *
+ * XXX Should any specific check be done for sym links ?
+ *
+ * Takes an open file descriptor, the file name, a uid and and
+ * error buffer plus max size as arguments.
+ *
+ * Returns 0 on success and -1 on failure
+ */
+int
+secure_filename(FILE *f, const char *file, uid_t uid, char *err, size_t errlen)
+{
+	char buf[MAXPATHLEN];
+	char *cp;
+	struct stat st;
+
+	if (realpath(file, buf) == NULL) {
+		snprintf(err, errlen, "realpath %s failed: %s", file,
+		    strerror(errno));
+		return -1;
+	}
+
+	/* check the open file to avoid races */
+	if (fstat(fileno(f), &st) < 0 ||
+	    (st.st_uid != 0 && st.st_uid != uid) ||
+	    (st.st_mode & 022) != 0) {
+		snprintf(err, errlen, "bad ownership or modes for file %s",
+		    buf);
+		return -1;
+	}
+
+	/* for each component of the canonical path, walking upwards */
+	for (;;) {
+		if ((cp = dirname(buf)) == NULL) {
+			snprintf(err, errlen, "dirname() failed");
+			return -1;
+		}
+		strlcpy(buf, cp, sizeof(buf));
+
+		debug3("secure_filename: checking '%s'", buf);
+		if (stat(buf, &st) < 0 ||
+		    (st.st_uid != 0 && st.st_uid != uid) ||
+		    (st.st_mode & 022) != 0) {
+			snprintf(err, errlen, 
+			    "bad ownership or modes for directory %s", buf);
+			return -1;
+		}
+
+		/*
+		 * dirname should always complete with a "/" path,
+		 * but we can be paranoid and check for "." too
+		 */
+		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
+			break;
+	}
+	return 0;
+}