前期工作准备完毕,接下来我们就正式研究Vsftpd中的源程序(源程序可从http://vsftpd.beasts.org/处获得,版本:vsftpd-1.2.1).首先从main.c文件着手,为了查看方便,我把main.c中的main函数贴出来.
1 int
2 main(int argc, const char* argv[])
3 {
4 struct vsf_session the_session =
5 {
6 /* Control connection */
7 0, 0,
8 /* Data connection */
9 -1, 0, -1, 0, 0, 0, 0,
10 /* Login */
11 1, INIT_MYSTR, INIT_MYSTR,
12 /* Protocol state */
13 0, 1, INIT_MYSTR, 0, 0,
14 /* Session state */
15 0,
16 /* Userids */
17 -1, -1, -1,
18 /* Pre-chroot() cache */
19 INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1,
20 /* Logging */
21 -1, -1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0,
22 /* Buffers */
23 INIT_MYSTR, INIT_MYSTR,
24 /* Parent <-> child comms */
25 0, -1, -1,
26 /* Number of clients */
27 0, 0
28 };
29 int config_specified = 0;
30 const char* p_config_name = VSFTP_DEFAULT_CONFIG;
31 /* Zero or one argument supported. If one argument is passed, it is the
32 * path to the config file
33 */
34 if (argc > 2)
35 {
36 die("vsftpd: too many arguments (I take an optional config file only)");
37 }
38 else if (argc == 0)
39 {
40 die("vsftpd: missing argv[0]");
41 }
42 if (argc == 2)
43 {
44 p_config_name = argv[1];
45 config_specified = 1;
46 }
47 /* Just get out unless we start with requisite privilege */
48 die_unless_privileged();
49 /* This might need to open /dev/zero on systems lacking MAP_ANON. Needs
50 * to be done early (i.e. before config file parse, which may use
51 * anonymous pages
52 */
53 /*******************************************/
54 vsf_sysutil_map_anon_pages_init();
55 /*****************************************/
56
57 /* 分析配置文件 */
58 {
59 struct vsf_sysutil_statbuf* p_statbuf = 0;
60 /*因为p_statbuf的指针中并无确定的地址,所以传送该指针的地址*/
61 int retval = vsf_sysutil_stat(p_config_name, &p_statbuf);/*p_statbuf指向stat结构*/
62 if (!vsf_sysutil_retval_is_error(retval))
63 {
64 vsf_parseconf_load_file(p_config_name, 1);
65 }
66 else if (config_specified)
67 {
68 die2("vsftpd: cannot open config file:", p_config_name);
69 }
70 vsf_sysutil_free(p_statbuf);
71 }/*分析配置文件结束*/
72
73 if (tunable_setproctitle_enable)/*设置进程标题*/
74 {
75 /* Warning -- warning -- may nuke argv, environ */
76 vsf_sysutil_setproctitle_init(argc, argv);
77 }
78
79 /*Standalone模式*/
80 if (tunable_listen || tunable_listen_ipv6)
81 {
82 /* Standalone mode */
83 struct vsf_client_launch ret = vsf_standalone_main();
84 /*父进程继续监听,0,1,2重定向到子进程Socket*/
85 the_session.num_clients = ret.num_children;
86 the_session.num_this_ip = ret.num_this_ip;
87 }
88
89 /* Sanity checks - exit with a graceful error message if our STDIN is not
90 * a socket. Also check various config options don't collide.
91 */
92 do_sanity_checks();
93 /* Initializes session globals - e.g. IP addr's etc. */
94 session_init(&the_session);
95 /* Set up "environment", e.g. process group etc. */
96 env_init();
97 /* Set up logging - must come after global init because we need the remote
98 * address to convert into text
99 */
100 vsf_log_init(&the_session);/*日志初始化*/
101
102 str_alloc_text(&the_session.remote_ip_str,
103 vsf_sysutil_inet_ntop(the_session.p_remote_addr));
104 /* Set up options on the command socket */
105 vsf_cmdio_sock_setup();/*设置控制连接属性*/
106 /*设置进程标题:"IP地址+connected"*/
107 if (tunable_setproctitle_enable)
108 {
109 vsf_sysutil_set_proctitle_prefix(&the_session.remote_ip_str);
110 vsf_sysutil_setproctitle("connected");
111 }
112 /* We might chroot() very soon (one process model), so we need to open
113 * any required config files here.
114 */
115 /*如果启用, 并且在编译 vsftpd 时加入了对 TCP_Wrappers 的支持,
116 *则连入请求转由 TCP_Wrappers 完成访问控制. 另外, 这是基于
117 *每个IP的配置机制. 如果 tcp_wrappers 设置了 VSFTPD_LOAD_CONF
118 * 环境变量, 则 vsftpd 会话将会试图加载在此变量中指定的 vsftpd
119 * 配置文件.
120 */
121 if (tunable_tcp_wrappers)
122 {
123 the_session.tcp_wrapper_ok = vsf_tcp_wrapper_ok(VSFTP_COMMAND_FD);
124 }
125 {
126 const char* p_load_conf = vsf_sysutil_getenv("VSFTPD_LOAD_CONF");
127 if (p_load_conf)
128 {
129 vsf_parseconf_load_file(p_load_conf, 1);
130 }
131 }
132 if (tunable_deny_email_enable)
133 {
134 int retval = str_fileread(&the_session.banned_email_str,
135 tunable_banned_email_file, VSFTP_CONF_FILE_MAX);
136 if (vsf_sysutil_retval_is_error(retval))
137 {
138 die2("cannot open anon e-mail list file:", tunable_banned_email_file);
139 }
140 }
141 if (tunable_banner_file)
142 {
143 int retval = str_fileread(&the_session.banner_str, tunable_banner_file,
144 VSFTP_CONF_FILE_MAX);
145 if (vsf_sysutil_retval_is_error(retval))
146 {
147 die2("cannot open banner file:", tunable_banner_file);
148 }
149 }
150 if (tunable_secure_email_list_enable)
151 {
152 int retval = str_fileread(&the_session.email_passwords_str,
153 tunable_email_password_file,
154 VSFTP_CONF_FILE_MAX);
155 if (vsf_sysutil_retval_is_error(retval))
156 {
157 die2("cannot open email passwords file:", tunable_email_password_file);
158 }
159 }
160 /* Special case - can force one process model if we've got a setup
161 * needing _no_ privs/*不需要特权*/
162 */
163 /*默认: NO
164 *connect_from_port_20
165 *用于控制在服务器端, 是否使用端口20(ftp-data)进行数据联接.
166 *基于安全的考虑, 有些客户端需要这样做. 相反, 禁用这个选项,
167 *可以使 vsftpd 以较少特权运行
168 */
169 if (!tunable_local_enable && !tunable_connect_from_port_20 &&
170 !tunable_chown_uploads)/*禁止本地登录&&禁止使用端口20进行数据连接&&禁止将匿名上传的文件宿主修改*/
171 {
172 tunable_one_process_model = 1;
173 }
174 if (tunable_one_process_model)
175 {
176 vsf_one_process_start(&the_session);
177 }
178 else
179 {
180 vsf_two_process_start(&the_session);
181 }
182 /* NOTREACHED */
183 bug("should not get here: main");
184 return 1;
185}从第34->46行是对命令行参数的分析,在此略过,第48行调用函数die_unless_privileged(),该函数是检查用户权限(root).第54行调用函数vsf_sysutil_map_anon_pages_init(),由于分析配置文件的过程中需要创建虚拟内存页,而且可能部分系统不支持MAP_ANON(创建匿名虚拟内存).对于不支持MAP_ANON的系统需要通过打开/dev/zero文件来为创建匿名虚拟内存页做好准备,这里我们只要了解该函数功能即可.以后我会对这部分进行详细描述.从58->71行便是分析配置文件的过程了.这里我们要着重描述作者对配置文件分析所采用的方法:
第61行调用vsf_sysutil_stat(p_config_name, &p_statbuf)函数,该函数调用stat系统函数得到配置文件(p_config_name)的stat结构信息.
第62行判断vsf_sysutil_stat(p_config_name, &p_statbuf)函数的返回值,如果正确,则在64行中调用函数vsf_parseconf_load_file(p_config_name, 1).该函数便是Vsftpd中分析配置文件的函数.该函数的实现在parseconf.c文件中.
1void
2vsf_parseconf_load_file(const char* p_filename, int errs_fatal)
3{
4 struct mystr config_file_str = INIT_MYSTR;
5 struct mystr config_setting_str = INIT_MYSTR;
6 struct mystr config_value_str = INIT_MYSTR;
7 unsigned int str_pos = 0;
8 int retval;
9 if (!p_filename)
10 {
11 p_filename = s_p_saved_filename;
12 }
13 else
14 {
15 if (s_p_saved_filename)
16 {
17 vsf_sysutil_free((char*)s_p_saved_filename);
18 }
19 s_p_saved_filename = vsf_sysutil_strdup(p_filename);
20 }
21 if (!p_filename)
22 {
23 bug("null filename in vsf_parseconf_load_file");
24 }
25 if (!s_strings_copied)
26 {
27 s_strings_copied = 1;
28 /* A minor hack to make sure all strings are malloc()'ed so we can free
29 * them at some later date. Specifically handles strings embedded in the
30 * binary.
31 */
32 copy_string_settings();
33 }
34 retval = str_fileread(&config_file_str, p_filename, VSFTP_CONF_FILE_MAX);
35 if (vsf_sysutil_retval_is_error(retval))
36 {
37 if (errs_fatal)
38 {
39 die2("cannot open config file:", p_filename);
40 }
41 else
42 {
43 return;
44 }
45 }
46 while (str_getline(&config_file_str, &config_setting_str, &str_pos))/*读取行记录(' ')*/
47 {
48 if (str_isempty(&config_setting_str) ||
49 str_get_char_at(&config_setting_str, 0) == '#')/*判断是否以#号开始*/
50 {
51 continue;
52 }
53 /* Split into name=value pair */
54 str_split_char(&config_setting_str, &config_value_str, '=');/*获取配置值*/
55 handle_config_setting(&config_setting_str, &config_value_str, errs_fatal);
56 }
57 str_free(&config_file_str);
58 str_free(&config_setting_str);
59 str_free(&config_value_str);
60}该函数的第34行调用函数retval = str_fileread(&config_file_str, p_filename, VSFTP_CONF_FILE_MAX),该函数在filestr.c文件中实现,在此我就不贴出代码.这里简要说明该函数的功能:1>打开配置文件;2>根据返回的文件描述符调用fstat函数得到stat结构信息;3>判断是否为普通文件.4>如果是普通文件,则创建一个匿名的虚拟内存页(何为匿名虚拟内存页呢?匿名虚拟内存页是指创建的虚拟内存,并不映射到任何物理文件上).这里还需特别强调的是,作者在创建虚拟内存时,多申请两个虚拟内存页,这两个虚拟内存页一个作为第一页,一个作为最后一页,并且调用函数mprotect采取保护.5>匿名虚拟内存创建成功后,读取配置文件,建立配置文件和匿名虚拟内存的映射。结果在虚拟内存中形成这样的情景:保护页+配置文件映射+保护页.6>将虚拟内存中的内容作为一个字符串,并在内存中开辟空间存放。同时释放虚拟内存空间。
我们回到parseconf.c中,str_fileread函数调用完毕,接着第46行便调用函数str_getline(&config_file_str, &config_setting_str, &str_pos)对str_fileread函数中建立的字符串(内存)中循环读取行记录(根据' '判断).
第48行判断行记录是否为空,或者是否以'#'开头.
第54行调用函数str_split_char(&config_setting_str, &config_value_str, '=')获取配置值.
第54行调用函数handle_config_setting(&config_setting_str, &config_value_str, errs_fatal)处理配置选项和配置值.
第57行调用函数str_free(&config_file_str),释放字符串占用的内存.
至此,Vsftpd中便完成了配置文件的读取分析工作,总结这个过程如下:
1>.创建匿名虚拟内存(额外增加两页.第一页和最后一页采取保护机制)
2>.读取配置文件,建立文件和虚拟内存的映射。
3>.将虚拟内存中的内容作为一个字符串,并在内存中开辟空间存放。同时释放虚拟内存空间。
4>.通过读取这个内存空间,分析配置选项。
5>.分析完毕,释放内存空间。这里我很纳闷,为什么要通过创建匿名虚拟内存中转一下呢?而不是直接开辟内存空间存放配置文件的内容呢?如果是考虑速度的原因,那为什么不直接对虚拟内存操作?为什么还采用页保护的机制?难道是出于安全方面的考虑吗?请各位高人指点,小弟在此先谢过.小弟联系方式:Emai:yeziyq@yahoo.com.cn MSN:yeziyq1983@hotmail.com