Blob Blame History Raw
--- contrib/mod_sftp/keys.c
+++ contrib/mod_sftp/keys.c
@@ -766,15 +766,23 @@ static int pkey_cb(char *buf, int buflen
   return 0;
 }
 
-static int has_req_perms(int fd) {
+static int has_req_perms(int fd, const char *path) {
   struct stat st;
 
-  if (fstat(fd, &st) < 0)
+  if (fstat(fd, &st) < 0) {
     return -1;
+  }
 
   if (st.st_mode & (S_IRWXG|S_IRWXO)) {
-    errno = EACCES;
-    return -1;
+    if (!(sftp_opts & SFTP_OPT_INSECURE_HOSTKEY_PERMS)) {
+      errno = EACCES;
+      return -1;
+    }
+
+    pr_log_pri(PR_LOG_INFO, MOD_SFTP_VERSION
+      "notice: the permissions on SFTPHostKey '%s' (%04o) allow "
+      "group-readable and/or world-readable access, increasing chances of "
+      "system users reading the private key", path, st.st_mode);
   }
 
   return 0;
@@ -2014,7 +2022,7 @@ static int load_file_hostkey(pool *p, co
     return -1;
   }
 
-  if (has_req_perms(fd) < 0) {
+  if (has_req_perms(fd, path) < 0) {
     if (errno == EACCES) {
       (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
         "'%s' is accessible by group or world, which is not allowed", path);
--- contrib/mod_sftp/mod_sftp.c
+++ contrib/mod_sftp/mod_sftp.c
@@ -1076,8 +1076,31 @@ MODRET set_sftphostkey(cmd_rec *cmd) {
 
     if ((st.st_mode & S_IRWXG) ||
         (st.st_mode & S_IRWXO)) {
-      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", cmd->argv[1],
-        "' as host key, as it is group- or world-accessible", NULL));
+      int insecure_hostkey_perms = FALSE;
+      config_rec *c;
+
+      /* Check for the InsecureHostKeyPerms SFTPOption. */
+      c = find_config(cmd->server->conf, CONF_PARAM, "SFTPOptions", FALSE);
+      while (c != NULL) {
+        unsigned long opts;
+
+        pr_signals_handle();
+
+        opts = *((unsigned long *) c->argv[0]);
+        if (opts & SFTP_OPT_INSECURE_HOSTKEY_PERMS) {
+          insecure_hostkey_perms = TRUE;
+          break;
+        }
+      }
+
+      if (insecure_hostkey_perms) {
+        pr_log_pri(PR_LOG_NOTICE, MOD_SFTP_VERSION ": unable to use '%s' "
+          "as host key, as it is group- or world-accessible", cmd->argv[1]);
+
+      } else {
+        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '", cmd->argv[1],
+          "' as host key, as it is group- or world-accessible", NULL));
+      }
     }
   }
 
@@ -1235,6 +1258,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
     } else if (strcmp(cmd->argv[1], "AllowInsecureLogin") == 0) {
       opts |= SFTP_OPT_ALLOW_INSECURE_LOGIN;
 
+    } else if (strcmp(cmd->argv[1], "InsecureHostKeyPerms") == 0) {
+      opts |= SFTP_OPT_INSECURE_HOSTKEY_PERMS;
+
     } else {
       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown SFTPOption '",
         cmd->argv[i], "'", NULL));
@@ -1747,6 +1773,18 @@ static int sftp_sess_init(void) {
   sftp_pool = make_sub_pool(session.pool);
   pr_pool_tag(sftp_pool, MOD_SFTP_VERSION);
 
+  c = find_config(main_server->conf, CONF_PARAM, "SFTPOptions", FALSE);
+  while (c != NULL) {
+    unsigned long opts;
+
+    pr_signals_handle();
+
+    opts = *((unsigned long *) c->argv[0]);
+    sftp_opts |= opts;
+
+    c = find_config_next(c, c->next, CONF_PARAM, "SFTPOptions", FALSE);
+  }
+
   c = find_config(main_server->conf, CONF_PARAM, "SFTPHostKey", FALSE);
   while (c) {
     const char *path = c->argv[0];
@@ -1791,18 +1829,6 @@ static int sftp_sess_init(void) {
     sftp_channel_set_max_count(*((unsigned int *) c->argv[0]));
   }
 
-  c = find_config(main_server->conf, CONF_PARAM, "SFTPOptions", FALSE);
-  while (c != NULL) {
-    unsigned long opts;
-
-    pr_signals_handle();
-
-    opts = *((unsigned long *) c->argv[0]);
-    sftp_opts |= opts;
-
-    c = find_config_next(c, c->next, CONF_PARAM, "SFTPOptions", FALSE);
-  }
-
   c = find_config(main_server->conf, CONF_PARAM, "DisplayLogin", FALSE);
   if (c) {
     const char *path;
--- contrib/mod_sftp/mod_sftp.h.in
+++ contrib/mod_sftp/mod_sftp.h.in
@@ -105,6 +105,7 @@
 #define SFTP_OPT_IGNORE_SFTP_SET_OWNERS		0x0080
 #define SFTP_OPT_IGNORE_SCP_UPLOAD_TIMES	0x0100
 #define SFTP_OPT_ALLOW_INSECURE_LOGIN		0x0200
+#define SFTP_OPT_INSECURE_HOSTKEY_PERMS		0x0400
 
 /* mod_sftp service flags */
 #define SFTP_SERVICE_FL_SFTP		0x0001
--- doc/contrib/mod_sftp.html
+++ doc/contrib/mod_sftp.html
@@ -929,6 +929,14 @@ The currently implemented options are:
     permissions sent by the SFTP client, use this option.
 
   <p>
+  <li><code>InsecureHostKeyPerms</code><br>
+    <p>
+    When this option is used, <code>mod_sftp</code> will ignore insecure
+    permissions (<i>i.e.</i> group- or world-readable) on
+    <code>SFTPHostKey</code> files.
+  </li>
+
+  <p>
   <li><code>MatchKeySubject</code><br>
     <p>
     When this option is used, if public key authentication is used, the
--- tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
+++ tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
@@ -993,6 +993,11 @@ my $TESTS = {
     test_class => [qw(bug forking sftp ssh2)],
   },
 
+  sftp_config_insecure_hostkey_perms_bug4098 => {
+    order => ++$order,
+    test_class => [qw(bug forking sftp ssh2)],
+  },
+
   sftp_multi_channels => {
     order => ++$order,
     test_class => [qw(forking sftp ssh2)],
@@ -33901,6 +33906,61 @@ sub sftp_multi_channels {
   unlink($log_file);
 }
 
+sub sftp_config_insecure_hostkey_perms_bug4098 {
+  my $self = shift;
+  my $tmpdir = $self->{tmpdir};
+  my $setup = test_setup($tmpdir, 'sftp');
+
+  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+  my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+  # Deliberately set insecure perms on the hostkeys
+  unless (chmod(0444, $rsa_host_key, $dsa_host_key)) {
+    die("Can't set perms on $rsa_host_key, $dsa_host_key: $!");
+  }
+
+  my $config = {
+    PidFile => $setup->{pid_file},
+    ScoreboardFile => $setup->{scoreboard_file},
+    SystemLog => $setup->{log_file},
+    TraceLog => $setup->{log_file},
+    Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20',
+
+    AuthUserFile => $setup->{auth_user_file},
+    AuthGroupFile => $setup->{auth_group_file},
+
+    IfModules => {
+      'mod_delay.c' => {
+        DelayEngine => 'off',
+      },
+
+      'mod_sftp.c' => [
+        "SFTPEngine on",
+        "SFTPLog $setup->{log_file}",
+        "SFTPOptions InsecureHostKeyPerms",
+        "SFTPHostKey $rsa_host_key",
+        "SFTPHostKey $dsa_host_key",
+      ],
+    },
+  };
+
+  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+    $config);
+
+  my $ex;
+
+  # First, start the server.
+  eval { server_start($setup->{config_file}, $setup->{pid_file}) };
+  if ($@) {
+    $ex = "Server failed to start up with world-readable SFTPHostKey";
+
+  } else {
+    server_stop($setup->{pid_file});
+  }
+
+  test_cleanup($setup->{log_file}, $ex);
+}
+
 sub sftp_multi_channel_downloads {
   my $self = shift;
   my $tmpdir = $self->{tmpdir};
--- tests/t/lib/ProFTPD/TestSuite/Utils.pm
+++ tests/t/lib/ProFTPD/TestSuite/Utils.pm
@@ -857,7 +857,7 @@ sub server_restart {
     close($fh);
 
   } else {
-    die("Can't read $pid_file: $!");
+    croak("Can't read $pid_file: $!");
   }
 
   my $cmd = "kill -HUP $pid";
@@ -1047,11 +1047,7 @@ sub test_append_logfile {
   my $out_file = File::Spec->rel2abs('tests.log');
 
   unless (open($outfh, ">> $out_file")) {
-    die("Can't append to $out_file: $!");
-  }
-
-  unless (open($infh, "< $log_file")) {
-    die("Can't read $log_file: $!");
+    croak("Can't append to $out_file: $!");
   }
 
   my ($pkg, $filename, $lineno, $func) = (caller(1))[0, 1, 2, 3];
@@ -1061,8 +1057,12 @@ sub test_append_logfile {
 
   print $outfh "-----BEGIN $func-----\n";
 
-  while (my $line = <$infh>) {
-    print $outfh $line;
+  if (open($infh, "+< $log_file")) {
+    while (my $line = <$infh>) {
+      print $outfh $line;
+    }
+
+    close($infh);
   }
 
   # If an exception was provided, write that out to the log file, too.
@@ -1072,10 +1072,8 @@ sub test_append_logfile {
 
   print $outfh "-----END $func-----\n";
 
-  close($infh);
-
   unless (close($outfh)) {
-    die("Can't write $out_file: $!");
+    croak("Can't write $out_file: $!");
   }
 }