一.     基础环境准备

操作系统:Ubuntu16.04Server

sudo apt-get install vim openssh-server

 便于后续上传源码以及调试。

看一下现在openssh的版本:

zjd@ubuntu:~$ ssh -V
OpenSSH_7.2p2 Ubuntu-4ubuntu2.6, OpenSSL 1.0.2g  1 Mar 2016

 安装编译所需要的库:

zjd@ubuntu:~$ sudo apt-get install build-essential

 二.     源码下载

从https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/下载最新的openssh-7.9p1.tar.gz;
从http://www.zlib.net/下载最新的zlib-1.2.11.tar.gz;
从https://www.openssl.org/source/下载最新的openssl-1.1.1a.tar.gz。
/home/zjd/下新建mySSH目录,然后将这三个压缩包上传上去后用tar分别解压:
zjd@ubuntu:~/mySSH$ tar -zxvf zlib-1.2.11.tar.gz

 。。。

zjd@ubuntu:~/mySSH$ ll
total 10312
drwxrwxrwx  5 root root    4096 Dec 17 16:41 ./
drwxr-xr-x  4 root root    4096 Dec 17 16:27 ../
drwxr-xr-x  5 zjd  zjd    12288 Oct 18 18:06 openssh-7.9p1/
-rw-rw-r--  1 zjd  zjd  1565384 Dec 17 16:39 openssh-7.9p1.tar.gz
drwxr-xr-x 19 zjd  zjd     4096 Nov 20 05:35 openssl-1.1.1a/
-rw-rw-r--  1 zjd  zjd  8350547 Dec 17 16:39 openssl-1.1.1a.tar.gz
drwxr-xr-x 14 zjd  zjd     4096 Jan 15  2017 zlib-1.2.11/
-rw-rw-r--  1 zjd  zjd   607698 Dec 17 16:39 zlib-1.2.11.tar.gz

 三.     编译安装

1. zlib

进文件夹后直接配置编译安装:

zjd@ubuntu:~/mySSH/zlib-1.2.11$ ./configure --prefix=/usr/local
zjd@ubuntu:~/mySSH/zlib-1.2.11$ make
zjd@ubuntu:~/mySSH/zlib-1.2.11$ sudo make install

 2. openssl

注意要先卸载旧版本:

zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ sudo apt-get purge openssl

 删除旧配置文件:

zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ rm -rf /etc/ssl

 然后配置编译安装新版本:

zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ ./config  --prefix=/usr/local --openssldir=/usr/local/ssl
zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ make
zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ sudo make install
zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ ./config shared --prefix=/usr/local --openssldir=/usr/local/ssl
zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ make clean
zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ make
zjd@ubuntu: ~/mySSH/openssl-1.1.1a$ sudo make install

 其中:prefix 是安装目录,openssldir 是配置文件目录,另外建议安装两次,shared 作用是生成动态连接库。

最后,因为是非root用户安装,因此需要增加两条软连接:

zjd@ubuntu:~/mySSH/openssl-1.1.1a$ sudo ln -s /usr/local/lib/libssl.so.1.1 /usr/lib/libssl.so.1.1
zjd@ubuntu:~/mySSH/openssl-1.1.1a$ sudo ln -s /usr/local/lib/libcrypto.so.1.1 /usr/lib/libcrypto.so.1.1

 OK,现在看一下openssl的版本:

zjd@ubuntu:~/mySSH/openssl-1.1.1a$ openssl version
OpenSSL 1.1.1a  20 Nov 2018

 3. openssh

zjd@ubuntu:~/mySSH/openssh-7.9p1$ ./configure -prefix=/usr/local -sysconfdir=/etc/ssh -with-ssl-dir=/usr/local/ssl
zjd@ubuntu:~/mySSH/openssh-7.9p1$ make
zjd@ubuntu:~/mySSH/openssh-7.9p1$ sudo make install

 安装完重启服务:

zjd@ubuntu:~/mySSH/openssh-7.9p1$ sudo systemctl restart sshd.service

 现在再看一下ssh的版本:

zjd@ubuntu:~/mySSH/openssh-7.9p1$ ssh -V
OpenSSH_7.9p1, OpenSSL 1.1.1a  20 Nov 2018

 四.     修改openssh源码

当修改了openssh源码后,为了不影响系统已启动的ssh服务的运行,应该重新配置编译输出路径,然后停掉系统已启动的ssh服务,启动新编译的sshd。过程如下:

zjd@ubuntu:~/mySSH/openssh-7.9p1$ sudo vim session.c
zjd@ubuntu:~/mySSH/openssh-7.9p1$ make clean
zjd@ubuntu:~/mySSH/openssh-7.9p1$  ./configure -prefix=/usr/local/myssh -sysconfdir=/etc/ssh -with-ssl-dir=/usr/local/ssl
zjd@ubuntu:~/mySSH/openssh-7.9p1$ make
zjd@ubuntu:~/mySSH/openssh-7.9p1$ sudo make install
zjd@ubuntu:~/mySSH/openssh-7.9p1$ sudo /etc/init.d/ssh stop
zjd@ubuntu:~/mySSH/openssh-7.9p1$ sudo /usr/local/myssh/sbin/sshd

 再次继续修改时,最后两行停止和启动服务的操作就不用执行了。

如果重启了系统,则最后两步要重新执行一下。当然,也可以写个开机启动脚本,这样系统启动时就自动执行最后两行了,方法如下:

在/etc/init.d/目录下新建一个脚本文件startmyssh.sh,并赋予运行权限:

zjd@ubuntu:/etc/init.d$ sudo chmod 755 startmyssh.sh

 vim编辑脚本内容为:

#!/bin/bash
### BEGIN INIT INFO
# Provides:          Zhang Jidong
# Required-Start:    $local_fs
# Required-Stop:     $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: sshd service
# Description:       mysshd service
### END INIT INFO
sudo /etc/init.d/ssh stop
sudo /usr/local/myssh/sbin/sshd

exit 0

 注意前面的注释部分格式一定要一样,否则会有各种警告;

最后添加启动调用:

zjd@ubuntu:/etc/init.d$ sudo update-rc.d startmyssh.sh defaults 90

 好了,现在可以重启试试了!

五.     openssh日志

openssh的配置文件位于:

zjd@ubuntu:/etc/ssh$ sudo vim sshd_config

  

# Logging
SyslogFacility AUTH
LogLevel INFO

 详细定义位于log.h,如下:

/* Supported syslog facilities and levels. */
typedef enum {
    SYSLOG_FACILITY_DAEMON,
    SYSLOG_FACILITY_USER,
    SYSLOG_FACILITY_AUTH,
#ifdef LOG_AUTHPRIV
    SYSLOG_FACILITY_AUTHPRIV,
#endif
    SYSLOG_FACILITY_LOCAL0,
    SYSLOG_FACILITY_LOCAL1,
    SYSLOG_FACILITY_LOCAL2,
    SYSLOG_FACILITY_LOCAL3,
    SYSLOG_FACILITY_LOCAL4,
    SYSLOG_FACILITY_LOCAL5,
    SYSLOG_FACILITY_LOCAL6,
    SYSLOG_FACILITY_LOCAL7,
    SYSLOG_FACILITY_NOT_SET = -1
}       SyslogFacility;

typedef enum {
    SYSLOG_LEVEL_QUIET,
    SYSLOG_LEVEL_FATAL,
    SYSLOG_LEVEL_ERROR,
    SYSLOG_LEVEL_INFO,
    SYSLOG_LEVEL_VERBOSE,
    SYSLOG_LEVEL_DEBUG1,
    SYSLOG_LEVEL_DEBUG2,
    SYSLOG_LEVEL_DEBUG3,
    SYSLOG_LEVEL_NOT_SET = -1
}       LogLevel;

 SyslogFacility配置为AUTH,则日志会输出到/var/log/auth.log

六.     sftp日志

sftp是openssh根据功能需要启动的子进程,缺省是关闭了日志的。打开方法为:

 

sudo vim /etc/ssh/sshd_config

 在Subsystem所在行最后增加 -l DEBUG3,如下:

Subsystem   sftp    /usr/libexec/openssh/sftp-server -l DEBUG3

 将sftp的日志单独存放:

 

sudo vim /etc/rsyslog.conf

 最后面增加如下内容:

# Log sftp-server in a separate file
:programname, isequal, "sftp-server" /var/log/sftp.log

 现在就可以重启sshd服务和rsyslog服务来生效了:

sudo systemctl restart rsyslog.service
sudo systemctl restart sshd.service

 看:

zjd@ubuntu:/var/log$ ll -t
total 1532
-rw-r-----  1 syslog adm    379366 Dec 20 04:58 syslog
-rw-r-----  1 syslog adm     61409 Dec 20 04:53 auth.log
-rw-r-----  1 syslog adm      6460 Dec 20 04:41 sftp.log
drwxrwxr-x  7 root   syslog   4096 Dec 20 04:40 ./
drwxr-xr-x 12 root   root     4096 Dec 20 04:31 ../
-rw-r--r--  1 root   root   568865 Dec 20 04:25 dpkg.log
-rw-rw-r--  1 root   utmp   292292 Dec 20 04:22 lastlog
-rw-rw-r--  1 root   utmp     5376 Dec 20 04:22 wtmp
-rw-r-----  1 syslog adm    348026 Dec 20 04:22 kern.log

 ……

建议设置logrotate避免日志过大。方法是sudo vim创建/etc/logrotate.d/sftp文件,输入下面内容并保存:

/var/log/sftp.log
{
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

 如果此时通过FileZilla客户端访问openssh-server,查看文件列表以及上传下载等操作,可以通过日志发现这些操作的实现都在sftp-server.c中。

 七.     限制sftp用户只能访问指定目录

 添加用户test1:

 

zjd@ubuntu:/home$ sudo useradd test1

 为test1设置密码:

 

zjd@ubuntu:/home$ sudo passwd test1

 编辑

sudo vim /etc/ssh/sshd_config

 将已有的Subsystem sftp /usr/lib/openssh/sftp-server -l DEBUG3改为:

 

Subsystem sftp internal-sftp -l DEBUG3

 并在最后为test1增加如下配置:

 

Match User test1
        ChrootDirectory /home/test1
        X11Forwarding no
        AllowTcpForwarding no
        ForceCommand internal-sftp -l DEBUG3

 创建好目录:

 

zjd@ubuntu:/home# sudo mkdir test1

 权限设置:

 

zjd@ubuntu:/home$ sudo chown root:root /home/test1
zjd@ubuntu:/home$ sudo chmod 755 /home/test1
zjd@ubuntu:/home$ sudo usermod test1 -s /sbin/nologin

 现在重启sshd服务,可以发现已经是只能登录到/home/test1目录中了。但此时没有写权限,那就继续如下配置,给test1配置一个可以读写的data文件夹:

 

zjd@ubuntu:/home/test1# sudo mkdir data
zjd@ubuntu:/home/test1# sudo chown test1:test1 /home/test1/data/
zjd@ubuntu:/home/test1# sudo chmod 755 /home/test1/data/

 好了,现在用户通过sftp登录后,就能看到一个data文件夹,并且在这个data文件夹里面可以进行读写操作了。

 此时前面加的-l DEBUG3参加(打印sftp.log)无效了,需要再进行如下配置:

 /home/test1下新建dev文件夹:

 

zjd@ubuntu:/home/test1# sudo mkdir dev

 编辑sudo vim /etc/rsyslog.conf,最后面新增:

 

# Create an additional socket for some of the sshd chrooted users.
$AddUnixListenSocket /home/test1/dev/log
# Log internal-sftp in a separate file
:programname, isequal, "sftp" /var/log/sftp.log

 (如果增加多个用户,只需重复增加AddUnixListenSocket命令即可。)

 编辑

root@ubuntu:/home# vim /etc/selinux/config

 新增:

 

SELINUX=disabled

 重启一下日志服务:

 

zjd@ubuntu:/home/test1# sudo systemctl restart rsyslog.service

 好了,现在重启系统吧!

 此时sftp用户登录后看到的是两个文件夹data和dev,如果自动进入data文件夹,则体验更好。此时就需要修改sftp-server.c,在函数process_realpath中增加如下

if (strlen(path) == 1 && !strcmp(path, "."))部分的内容:

 

static void
process_realpath(u_int32_t id)
{
        char resolvedname[PATH_MAX];
        char *path;
        int r;
        if ((r = sshbuf_get_cstring(iqueue, &path, NULL)) != 0)
                fatal("%s: buffer error: %s", __func__, ssh_err(r));
        if (path[0] == '\0') {
                free(path);
                path = xstrdup(".");
        }
        debug3("request %u: realpath", id);
        verbose("realpath \"%s\"", path);
        if (strlen(path) == 1 && !strcmp(path, "."))
        {
                logit("======== Change path from '.' to '/data/.'");
                free(path);
                path = xstrdup("/data/.");
        }
        if (realpath(path, resolvedname) == NULL) {
                send_status(id, errno_to_portable(errno));
        } else {
                Stat s;
                attrib_clear(&s.attrib);
                s.name = s.long_name = resolvedname;
                send_names(id, 1, &s);
        }
        free(path);
}