stunnel用来对远程客户端和本地机(可启动inetd的:inetd-startable)或远程服务器间的SSL加密进行封装。它可以在不修改任何代码的情况下,为一般的使用inetd daemon的POP2、POP3和IMAP服务器添加SSL功能。它通过使用OpenSSL或SSLeay库建立SSL连接。
下载链接:http://down.51cto.com/data/157863
有一种方法可以将加密功能无缝添加到网络连接中,而不会将您原来的代码段基址搞乱。Stunnel 是一个程序,可以使用 OpenSSL 库对任意 TCP 会话进行加密。它作为服务器运行在程序外部。Stunnel 服务器主要执行两个功能:一,首先,接收未加密的数据流,进行 SSL 加密,然后将其通过网络发送;二,对已进行 SSL 加密的数据流进行解密,并将其通过网络发送给另一个程序(该程序通常驻留在同一机器上,以避免本地网络上的窥探攻击)。
这样,在必要时,您就可以很容易地运行未加密的程序,当您想要“嗅探”网络,看看到底有什么东西正在通过网络时,这一点很有用。
即使您是一个系统管理员,而不是一个开发者,Stunnel 对您来说也是一个强大的武器,因为它可以向不启用 SSL 的服务器端软件添加 SSL。例如,我已经使用 Stunnel 来保护 POP、SMTP 和 IMAP 服务器。唯一不太尽人意的地方是要使用这些服务器的安全版本,客户机必须是可识别 SSL 的。
Stunnel 要求已经安装了 OpenSSL。它已被移植到了 Windows,以及大多数 UNIX 平台。
一旦安装了 Stunnel,用它来保护服务器就很轻松。例如,您可以通过将常规服务绑定到本地主机使 IMAP 服务器启用 SSL,然后在外部 IP 地址(假设 IMAP 服务器已经在运行,且外部地址为 192.168.100.1)运行 Stunnel:
- stunnel -d 192.168.100.1:imap2 -r 127.0.0.1:imap2
-d 标志指定我们希望用来运行自己的安全服务的端口。 imap2 字符串指定使用标准 IMAP 端口;我们也可以将其设为 143。Stunnel 检查 "/etc/services" 文件以便将符号名映射到端口号。并非所有的机器都拥有这个文件(有些机器并不列出所有的标准服务),所以使用数字比使用服务名更方便。
-r 标志指定未加密的 IMAP 服务器运行所在的端口。
这个解决方案要求您的 IMAP 服务器只在回送(loopback)接口上侦听。如果 IMAP 服务器绑定到 IP 地址“0.0.0.0”,那么它将侦听机器上每个 IP 地址上的信息,包括 192.168.100.1;这会导致出现一条出错消息,指出我们的安全服务端口已在使用中。大多数服务都可以配置为只绑定到一个接口。不然的话,可能要更改一行代码。
另外,您可以将一个未加密的服务器设在一个非标准端口上。例如,您可以在端口 1143 上运行 IMAP,然后将安全的 IMAP 数据流转发到该端口。一般情况下,您不希望其它机器上的用户访问您未加密的服务。运行服务的机器上的个人防火墙可以加强这种策略。
使用 Stunnel 来保护如 IMAP 等服务面临的一个问题是服务器只接收来自我们提供的 Stunnel 服务器的连接。因此,所有的连接都看起来好象是来自本地机器。在 Linux 上,可以通过传递 -T 标志避开这个问题,传递 -T 标志可以使 Stunnel 服务器透明地代理信息包,这样真正的服务器就可以得到接收到的所有信息包中的正确的源地址。#p#
用于客户机的Stunnel
还可以使用客户机的 Stunnel 与服务器连接,不过要多做一些工作。首先,必须生成 Stunnel 作为外部进程。在基于 UNIX 的系统上,执行这个操作的最好方法是 fork() 客户机,并让子进程 execv() stunnel 。父进程必须准备两套文件描述符用来与子进程通信 ― 一对用于从 Stunnel 进程读取数据,另一对用于通过网络发送数据。这个工作量不小。实现这项功能的示例代码,提供一个简单的函数调用 run_cmd ,掩盖潜在的复杂性; run_cmd 使用一个字符串指出要运行的命令,并返回一个 PIPE 对象,该对象有一个文件描述符,套接字使用该文件描述符进行读写操作:
- pipe.h:
- #ifndef POPEN_H__
- #define POPEN_H__
- #include <sys/types.h>
- #include <stdio.h>
- #define EXITVAL 127
- typedef struct pipe_st {
- FILE *read_ptr;
- FILE *write_ptr;
- pid_t pid;
- } PIPE;
- PIPE *run_cmd(char *cmd);
- int pipe_close(PIPE *p);
- #endif POPEN_H__
- pipe.c:
- #include <sys/wait.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <errno.h>
- #include <stdlib.h>
- #include "pipe.h"
- /* We allow double quotes and \ to escape spaces.
- * All backslashes are "processed", despite the value
- * of the next character. (Though \\ -> \).
- * We don't care if there's a missing trailing quote,
- * even if it should really be a syntax error.
- */
- static char **
- to_words(char *arg) {
- char **arr;
- char *p = arg;
- int nw = 1;
- int slc = 0;
- int slm = 0;
- char c;
- short quote = 0;
- char *cur;
- /* Build a rough approximation of the number of words,
- * simply so we don't malloc too low.
- */
- while((c = *p++)) {
- if(c == '"' || c == ' ') {
- nw++;
- if(slm < slc) slm = slc;
- slc = 0;
- }
- }
- arr = (char **)malloc(sizeof(char *)*(nw+1));
- quote = nw = slc = 0;
- p = arg;
- cur = (char *)malloc(sizeof(char)*(slm+1));
- arr[nw++] = cur;
- while((c = *p++)) {
- switch(c) {
- case '"':
- quote = !quote;
- continue;
- case ' ':
- if(quote) {
- *cur++ = c;
- slc++;
- continue;
- } else {
- if(!slc) continue;
- *cur = 0;
- cur = (char *)malloc(sizeof(char)*(slm+1));
- arr[nw++] = cur;
- slc = 0;
- continue;
- }
- case '\\':
- if(*p) {
- *cur++ = *p++;
- slc++;
- continue;
- }
- default:
- *cur++ = c;
- slc++;
- continue;
- }
- }
- *cur = 0;
- arr[nw] = 0;
- return arr;
- }
- PIPE *
- run_cmd(char *cmd) {
- int prpd[2];
- int pwpd[2];
- pid_t pid;
- char **args;
- PIPE *ret;
- args = to_words(cmd);
- if(pipe(prpd) < 0 || pipe(pwpd) < 0) {
- return 0; /* Pipe failed. */
- }
- pid = fork();
- switch(pid) {
- case -1:
- close(prpd[STDIN_FILENO]);
- close(prpd[STDOUT_FILENO]);
- close(pwpd[STDIN_FILENO]);
- close(pwpd[STDOUT_FILENO]);
- return 0; /* Fork failed. */
- /* Here we can only exit on error. */
- case 0:
- /* Child... */
- if(dup2(pwpd[STDIN_FILENO], STDIN_FILENO) < 0) {
- exit(EXITVAL);
- }
- if(dup2(prpd[STDOUT_FILENO], STDOUT_FILENO) < 0) {
- exit(EXITVAL);
- }
- close(pwpd[STDIN_FILENO]);
- close(pwpd[STDOUT_FILENO]);
- close(prpd[STDIN_FILENO]);
- close(prpd[STDOUT_FILENO]);
- execv(args[0], args);
- exit(EXITVAL);
- default:
- ret = (PIPE *)malloc(sizeof(PIPE));
- ret->read_ptr = ret->write_ptr = 0;
- ret->pid = pid;
- close(pwpd[0]);
- fcntl(pwpd[1], F_SETFD, FD_CLOEXEC);
- ret->write_ptr = fdopen(pwpd[1], "wb");
- if(!ret->write_ptr) {
- int old = errno;
- kill(pid, SIGKILL);
- close(pwpd[1]);
- waitpid(pid, 0, 0);
- errno = old;
- free(ret);
- return 0;
- }
- close(prpd[1]);
- fcntl(prpd[0], F_SETFD, FD_CLOEXEC);
- ret->read_ptr = fdopen(prpd[0], "rb");
- if(!ret->read_ptr) {
- int old = errno;
- kill(pid, SIGKILL);
- close(prpd[0]);
- waitpid(pid, 0, 0);
- errno = old;
- free(ret);
- return 0;
- }
- return ret;
- }
- }
- int
- pipe_close(PIPE *p) {
- int status;
- if(!(p->read_ptr || p->write_ptr)) {
- return -1;
- }
- if(p->read_ptr && fclose(p->read_ptr)) {
- return -1;
- }
- if(p->write_ptr && fclose(p->write_ptr)) {
- return -1;
- }
- if(waitpid(p->pid, &status, 0) != p->pid) {
- return -1;
- }
- p->read_ptr = p->write_ptr = 0;
- return status;
- }
#p#
例如,要使用此函数建立一个连接到上面启用 SSL 的 IMAP 服务器的客户机,我们可以编写如下代码:
- PIPE *p;
- p = run_cmd("stunnel -c -r 192.168.100.1:imap2 -A /etc/ca_certs -v 3");
在上面的代码中,我们这样调用 Stunnel:
- stunnel -c -r 192.168.100.1:imap2 -A /etc/ca_certs -v 3
最后两个选项并非必需;客户机可以不考虑这两个选项进行连接。但是,如果我们省去了这两个选项,客户机将无法对服务器证书进行充分的验证,会使客户机随时可能遭到 man-in-the-middle 攻击。在这种攻击中,有人创建了一个虚假服务器,使客户机把它当作真正的服务器,而与之进行通信。虚假服务器代理到真正的服务器的连接,读取所有的数据。使用现成的工具如 Dsniff(请参阅 参考资料)很容易发动这种攻击。
v 参数指定验证级别。缺省值为 0,适用于使用其它方法验证客户机的服务器,也是如此。但级别 1 和级别 2 更好一些。级别 1 检查(非强制地)服务器证书是否有效,但可与无证书的服务器连接。级别 2 要求有效的服务器证书,但并不检查证书是否由权威认证中心如 VeriSign 签署。
A 参数指定一个必须包含可信证书列表的文件。一个服务器证书要被接受,它必须是在 A 参数指定的文件内,或者是一个用于签署推荐证书(通常是来自权威认证中心,或称 CA,如 VeriSign 的证书)的证书,该证书必须在指定的文件内。在 参考资料部分,您会找到几个主要认证中心的当前有效证书的链接,您可以将它作为必需内容放在这个文件中。
在使用大型 CA 时,甚至这种方法也会出现问题。例如,确保某个特定的证书是由 VeriSign 签署的是不错的。但您如何确保证书是来自您想要连接的站点呢?
不幸的是,在写这篇文章的时候,Stunnel 还不能使调用程序访问验证过的证书的信息。因此,您无法确定服务器正在使用的是谁的证书。例如,您可能如愿以偿被连接到 Amazon,或者可能被连接到一个服务器,该服务器使用的是偷来的(但有效的)Microsoft 证书。
由于这种局限性,您只能局限于下面四个选择:
希望没有人发动 man-in-the-middle 攻击(不正确的想法)。
在客户机端对每个可能的服务器证书进行硬编码(Hardcode)。
运行自己的认证中心,这样可以动态地添加可信的服务器。
在应用程序中使用 SSL 代替外部的通道,这样可以通过编程检查。
即使这些解决方案都可使用,也没有一个理想的。