一, 说明
等保测评主机测评中需要查询主机的超时退出配置, 具体在 CentOS 中的话, 主要有两种方式可以实现超时退出的功能. 其实这方面的资料很多, 但是仍然存在一些地方没有说清楚(sshd_config 的一个参数), 所以本文的目的之一就是把那些问题说清楚.
注: 我使用的是 Centos6
另外本文也顺便说一说在 Linux 系统中, 查询配置的一个注意点.
二, 设置 TMOUT 方式
这个是比较通用, 简单的方式, 通过设置 TMOUT, 就可以至少对本地 tty 登录和远程使用 SSH 登录的用户起作用, 但应该对图形化界面无效, 当然进入图形化界面你再打开终端, 对于打开的终端也是起作用的.
2.1. 实现方式
在 / etc/profile,~/.bashrc,~/.bash_profile 等文件的最后加入 export TMOUT=900 语句即可(单位是秒), 然后想要不重新登录就起效就, 还需要用 source 命令解析上述文件.
上述文件中,/etc/profile 针对所有用户其效果, 而~/.bashrc,~/.bash_profile 则只对当前用户其效果, 实际上从文件位置就能看出来.
2.2. 具体查询方式
从上面可以知道, 理论上可以在好几个地方对 TMOUT 进行配置, 不过一般应该是在 / etc/profile 这个文件中对所有用户进行设置, 可能有极个别的会单独为每个用户配置超时时间.
所以直接查看 / etc/profile 文件内容, 然后再用 echo $TMOUT 语句看看运行环境中的 TMOUT 变量到底是多少.
2.3. 配置查询的注意点
这里多说一点, 在查配置时, 对于 Linux 系统最好是 配置文件 以及 实际情况 一块查.
为什么要一块查?
因为配置文件里写了不代表就起效了, 比如 / etc/profile 修改后需要用 source 命令才能起效. 另外, 配置文件中的配置即使起效了, 但未必就等同于现在实际执行的规则.
比如 iptables 的规则可以用命令动态修改, 当然如果没有使用命令持久化 (也就是将当前规则存入 iptables 的配置文件中) 的话, 重启 iptables 服务那些临时的规则就没了.
所以同样的, 配置文件里啥都没写, 不代表现在运行的环境中没有规则, 比如 iptables 的规则可以用命令临时添加进来.
所以如果想在测评的时候更全面的了解情况, 最好就是一块查.
三, 修改 sshd_config 文件方式
一般来说, 远程对 Linux 服务器进行管理都是通过 SSH 协议, 所以对 sshd_config 文件进行配置, 也是一种方法, 虽然只对通过 SSH 登录的所有用户有效.
记住修改完 sshd_config 文件后需要重启才能生效.
在 sshd_config 文件中有两个参数, 分别是 ClientAliveInterval 和 ClientAliveCountMax.
这里网上好像没说清楚, 这里根据 ClientAliveCountMax 的取值是不是 0, 会有两种效果.
3.1. ClientAliveCountMax 的值是 0
这种情况下, 就是我们想要的操作超时自动退出的效果, 也就是当客户端多久没有操作, 服务器端就直接断开 SSH 连接.
这个 "多久" 当然就是由 ClientAliveInterval 的值来决定, 它的单位是秒.
比如 ClientAliveInterval 是 600,ClientAliveCountMax 是 0, 则代表着如果 600 秒内终端没有操作, 则断开 SSH 连接.
3.2. ClientAliveCountMax 的值大于 0
这种情况下, 和我们想要的效果有区别:
ClientAliveInterval: 指定了服务器端向客户端请求消息的时间间隔, 默认值是 0;
ClientAliveCountMax: 则指定这种请求服务器端发送后, 客户端最多的无响应次数(但网上一般是说服务器端最多向客户端发送这种消息多少次, 我觉得不太对), 默认值是 3.
如果 ClientAliveInterval 是 60,ClientAliveCountMax 是 1, 表面上看它的意思就是如果 60s 内客户端没有响应, 服务器端就会给客户端发送一个请求判断还它存不存在, 如果 1 次也就是 60 秒都没有任何回复, 就断开连接.
所以咋一看上去, 和 ClientAliveCountMax 的值是 0 时没啥区别, 还是 60 秒后不操作就自动断开不了啊.
但实际上压根不一样, 因为当服务器端给客户端发送一个请求判断还它存不存在时, 客户端应该是会 自动回复 的, 同时, ClientAliveCountMax 并不是指会发送这种消息多少次, 或者好像和这个压根就没关系, 它应该是指 服务器端发送这种请求后, 客户端最多的无响应次数 , 而且还得是连续的, 因为从源代码里面看(见下文), 只要有一次正常相应, 这个次数就会被清空.
也就是说, 这里判断的应该是客户端那边网络有没有出现问题, 比如断线了之类的 .
因为只要网络正常, 客户端这边永远会自动回复服务器端发送过来的请求, 则这个计数永远达不到限定的阈值 1, 也永远不会自动退出(理想情况下啊), 和你有没有进行操作没有任何关系......
我自己测试过, 将 ClientAliveInterval 设置为 60,ClientAliveCountMax 设置 1, 然后我一直不操作, 同时我把 xshell 里保持活动状态的选项关掉:
结果就是到了 60 秒后根本不会自动退出......
然后我又把 ClientAliveInterval 和 ClientAliveCountMax 都设置成 1, 结果就是服务器那边每隔 1 秒就发个消息过来(有消息传输的时候那个箭头会亮):
要是按照网上的解释, 最多只发送 1 次这种消息就结束会话, 那压根解释不通, 这都给我发了无数次了......
我跑去看 man 里的解释, 也没看出所以然:
- ClientAliveCountMax
- Sets the number of client alive messages (see below) which may be sent without sshd(8) receiving any messages back from the client. If this threshold is reached while client alive messages are being sent, sshd will disconnect the client, terminating the session. It is important to note that the use of client alive messages is very different from TCPKeepAlive (below). The client alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by TCPKeepAlive is spoofable. The client alive mechanism is valuable when the client or server depend on knowing when a connection has become inactive. The default value is 3. If ClientAliveInterval (see below) is set to 15, and ClientAliveCountMax is left at the default, unresponsive SSH clients will be disconnected after approximately 45 seconds. This option applies to protocol version 2 only.
- ClientAliveInterval
- Sets a timeout interval in seconds after which if no data has been received from the client, sshd(8) will send a message through the encrypted channel to request a response from the client. The default is 0, indicating that these messages will not be sent to the client. This option applies to protocol version 2 only.
3.3. 源代码解释
于是我就去翻了翻源代码, 不过我对 c 语言不熟, 只能大概猜一猜了(有错误请见谅):
wait_until_can_do_something 函数里有这么一段:
- /* Wait for something to happen, or the timeout to expire. */
- ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
- if (ret == -1) {
- memset(*readsetp, 0, *nallocp);
- memset(*writesetp, 0, *nallocp);
- if (errno != EINTR)
- error("select: %.100s", strerror(errno));
- } else {
- if (ret == 0 && client_alive_scheduled)
- client_alive_check();
- if (!compat20 && program_alive_scheduled && fdin_is_tty) {
- if (!fdout_eof)
- FD_SET(fdout, *readsetp);
- if (!fderr_eof)
- FD_SET(fderr, *readsetp);
- }
- }
里面的 select 函数定义是这样的:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
它能够监视我们需要监视的文件描述符的变化情况 -- 读写或是异常, 它的最后一个参数 timeval*timeout 是一个超时时间, 如果 timeout 的值大于 0, 这就是等待的超时时间, 即 select 在 timeout 时间内阻塞, 超时时间之内有事件到来就返回了, 否则在超时后不管怎样一定返回.
如果 timeout 的值是 null, 则代表将 select 置于阻塞状态, 一定等到监视文件描述符集合中某个文件描述符发生变化为止.
而返回值为 - 1 代表出异常了, 为 0 则代表超时时间内, 监视的这些文件即没有可写的也没有可读的, 换句话说, 为 0 就是意味着客户端那没有任何操作.
在代码中, 在一定的条件下, 这个 timeout 的值就是我们设置的 ClientAliveInterval 的值(如果设置值大于 0), 如果 ClientAliveInterval 是 0, 则 timeout 的值根据一些条件则为 null.
然后根据 client_alive_scheduled 的值, 就有可能调用 client_alive_check(), 不过如果是使用 ssh2 协议然后设置了 ClientAliveInterval, 应该 client_alive_scheduled 的值就是 1, 代码如下:
- if (compat20 &&
- max_time_milliseconds == 0 && options.client_alive_interval) {
- client_alive_scheduled = 1;
- max_time_milliseconds = options.client_alive_interval * 1000;
- }
client_alive_check 的定义如下:
- static void
- client_alive_check(void)
- {
- int channel_id;
- /* timeout, check to see how many we have had */
- if (packet_inc_alive_timeouts()> options.client_alive_count_max) {
- logit("Timeout, client not responding.");
- cleanup_exit(255);
- }
- /*
- * send a bogus global/channel request with "wantreply",
- * we should get back a failure
- */
- if ((channel_id = channel_find_open()) == -1) {
- packet_start(SSH2_MSG_GLOBAL_REQUEST);
- packet_put_cstring("keepalive@openssh.com");
- packet_put_char(1); /* boolean: want reply */
- } else {
- channel_request_start(channel_id, "keepalive@openssh.com", 1);
- }
- packet_send();
- }
可以看到如果 packet_inc_alive_timeouts()大于 options.client_alive_count_max, 则就结束了.
而 packet_inc_alive_timeouts 的定义很简单, 就是把累积的 timeouts 加个 1, 然后返回.
- int
- packet_inc_alive_timeouts(void)
- {
- return ++active_state->keep_alive_timeouts;
- }
所以, 如果设置的 ClientAliveCountMax 是 0, 到这里就直接结束了(0+1>0), 不会执行下面的发送请求的代码.
如果 (channel_id = channel_find_open()) 不为 - 1, 应该代表 channel 没问题的话, 就会执行:
channel_request_start(channel_id, "keepalive@openssh.com", 1);
channel_request_start 中有这么一句:
packet_start(SSH2_MSG_CHANNEL_REQUEST);
而 SSH2_MSG_CHANNEL_REQUEST 应该是绑定了一个函数
dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &server_input_channel_req);
所以就会调用 server_input_channel_req 函数, server_input_channel_req 函数在有这么一段:
- reply = packet_get_char();
- ............
- if (reply) {
- packet_start(success ?
- SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE);
- packet_put_int(c->remote_id);
- packet_send();
- }
这里根据 success 的值又会调用一个函数, 其实好像调用的函数是一样的:
- dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &server_input_keep_alive);
- dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_keep_alive);
- dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &server_input_keep_alive);
- dispatch_set(SSH2_MSG_REQUEST_FAILURE, &server_input_keep_alive);
都是 server_input_keep_alive 函数, 这个函数的意思就很简单了:
- static void
- server_input_keep_alive(int type, u_int32_t seq, void *ctxt)
- {
- debug("Got %d/%u for keepalive", type, seq);
- /*
- * reset timeout, since we got a sane answer from the client.
- * even if this was generated by something other than
- * the bogus CHANNEL_REQUEST we send for keepalives.
- */
- packet_set_alive_timeouts(0);
- }
packet_set_alive_timeouts(0)就是把 active_state->keep_alive_timeouts 的值设为 0, 也就是重置了这个计数.
所以饶了一圈, 这些代码的意思大概应该是这样, 如果客户端在规定时间内没有响应, 就先判断未响应次数是否超过设置的值, 如果超过就结束.
如果没超过, 就把这个次数加 1, 然后发送一个请求, 看看客户端还在不在, 如果客户端网络正常(应该会自动回复), 就会得到客户端的回复, 于是就重置这个未响应计数.
四, 两个方式的不同
TMOUT 方式可以针对所有用户通过本地 tty 或远程 SSH 登录时起作用, 而修改 sshd_config 只针对使用 SSH 登录的用户.
另外一点就是 TMOUT 判断你有没有在操作, 好像是看你有没有输入什么字符然后敲回车执行, 你输入正确的命令敲回车执行命令, 就算. 输入无意义的字符敲回车没有找到可执行的命令, 那也算. 光输入字符不敲回车就不算, 而判断你处于空闲状态, 超时就会登出.
而 SSH 是基于网络来判断, 只要客户端对服务器有发送信息, 那就算有在操作.
这也是两者的一些细微的不同之处.
来源: http://www.tuicool.com/articles/YBjQbaQ