2008年1月10日星期四

矿大联创Linux客户端源码分析(一)

把C Primer Plus大致复习了一遍之后,差不多就可以开始研究中国矿大的那个联创Linux客户端了。那个客户端可不是联创官方的版本,是经过逆向工程分析,由爱好者维护的版本。因为矿大的联创认证和本校的认证方式有较大的区别,因此我准备仔细研究该客户端,并进行修改,以适合本校的认证。

我把整个分析全部写出来,供大家参考,也可以方便后面的同学研究联创客户端,以应对联创以后的进一步修改。个人水平有限,初学C语言,有分析的不对的地方,请指正。这次是源码分析的第一部分,几个最简单的源码文件。

矿大的Linux客户端命令行版主要有如下几个 文件:configfile.h, configfile.c, global.h, linkthread.h, linkthread.c, myerr.h, myerr.c, md5.h, md5.c, sendpacket.h, sendpacket.c, main.c。这是主程序部分。还有一个Makefile,用于程序编译;jerry.conf是配置文件,用于保存配置信息;还有一个lk,是Shell脚本,用于调用链接程序,并控制命令行参数。

因为我们学校没有类似矿大的教育网@edu/外网@internet之分,只有一个@lan,因此,在我修改源代码的时候已经把一些没有用的判断去掉了。删除的部分包括:lk,这个Shell脚本;main()函数中的参数判断部分;以及一个network枚举类型。这些后面会提到。好了,进入正题,开始分析。这次分析的源文件包括:configfile.h, configfile.c, global.h和main.c。

golobal.h

#ifndef __GLOBAL_H__
#define __GLOBAL_H__
struct _USER
{
char *name;
char *password;
};

typedef struct _USER user_info;

#endif /* __GLOBAL_H__ */
//这个没啥好说的,定义了一个用户信息的结构体,并别名为user_info


configfile.h

#ifndef _CONFIGFILE_H_
#define _CONFIGFILE_H_
//定义一个用于保存配置文件信息的结构体,并别名为Config_Info
typedef struct _Config_Info
{
int auto_retry; //自动重连。1为是,0为否
int response_time_out, //服务器响应时间。这里的客户端默认为5秒,可根据实际需要
retry_times, //重试次数,这里默认为2次
refresh_time_out; //断线重连等待时间,默认为180秒
char *interface; //连线的网卡。Linux一般默认为eth0。
}Config_Info;

//此处删除了network的枚举类型定义

//定义了两个函数原型,可以被其他文件调用。
int load_config(void); //将配置信息读入Config_Info类型的结构体
void free_config(void); //释放为Config_Info类型的结构体分配的内存

#endif

configfile.c

/* This file contains functions which are used to read and write config files */
#include "global.h"
#include "configfile.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>

#define ENTRY_LENGTH 64 //定义一个常数。其实32就足够了
#define CONFIG_FILE "/etc/lknnu.conf"
//配置文件路径。我已经把jerry.conf修改为lknnu.conf

Config_Info *config_info; //声明一个Config_Info结构体
char *l_name,
*l_password;
//声明两个字符指针,分别指向用户名l_name,和密码l_password;

char tmp_value[ENTRY_LENGTH];
//全局变量,用于在函数之间传递临时存放的值。

static char *get_value(FILE *stream, const char *entry);
//声明一个函数原型get_value,后面有函数体

// 读取/etc/lknnu中的配置信息,并保存到cinfig_info结构体中。
int
load_config(void)
{
FILE *fp = NULL; //声明一个文件指针

if ( (fp = fopen(CONFIG_FILE, "rt")) == NULL){
fprintf(stderr, "open configfile err: %s", strerror(errno));
return -1;
}
//只读方式打开配置文件。

config_info = (Config_Info *)malloc(sizeof(Config_Info));
//分配Config_Info结构体所需额内存
get_value((FILE *) fp, "AutoRetry");
config_info->auto_retry = atoi(tmp_value);
//读取自动重连的值,并保存到结构体中
get_value((FILE *) fp, "MaxServerResponseTime");
config_info->response_time_out = atoi(tmp_value);
//读取服务器响应时间的值,保存到结构体中
get_value((FILE *) fp, "RetryNumber");
config_info->retry_times = atoi(tmp_value);
//读取重试次数,保存到结构体中
get_value((FILE *) fp, "WaitTime");
config_info->refresh_time_out = atoi(tmp_value);
//读取断线重连等待时间,保存到结构体中
get_value((FILE *) fp, "Device");
config_info->interface = (char *)malloc(sizeof(tmp_value) + 1);
//分配保存一个字符串所需的空间。加1是给''的。
strcpy(config_info->interface, tmp_value);
//读取网络设备名称,保存到结构体中
get_value((FILE *) fp, "AccountName");
//读取用户名
l_name = (char *) malloc (13);
//分配保存用户名所需空间。因为随园和仙林的用户名长度不同,这里需要修改一下。
//随园的用户名长度为12,仙林为10,因此分配13个char的空间足够了。
//如果有必要,也可以使用预处理器定义成常量,这个是后话了。

strncpy(l_name, tmp_value, strlen(tmp_value));
//这里,我把strncpy的最后一个参数改成了strlen(tmp_value),是为了用户名长度不一致的考量
l_name[strlen(tmp_value)] = '';
//基于和上面一行代码一样的原因,这里的数组引用也需要修改成strlen(tmp_value)了。
strcat(l_name, "@lan");
//因为不需要判断教育网还是外网,此处的删除了关于网络的判断,直接连接上@lan,并将最终用户名保存到l_name中。
get_value((FILE *) fp, "AccountPasswd");
l_password = (char *) malloc (strlen(tmp_value) + 1);
//分配保存密码所需的空间。
strcpy(l_password, tmp_value);
//读取密码,保存到l_password中。
fclose(fp); //关闭文件
return 1; //返回1。这是为啥呢?
}

//释放内存,防止内存泄漏
void
free_config(void)
{
free(config_info->interface); //释放网卡字符串内存
free(config_info); //释放配置信息内存
free(l_name); //释放用户名内存
free(l_password); //释放密码内存
}

//从文件中读取字符串,并保存到tmp_value字符数组中,传入文件指针,和属性名字符指针。
//返回tmp_value的指针

char *
get_value(FILE *stream, const char *entry_name)
{
assert(stream != NULL); //确认配置文件可读

char *tmp = NULL; //声明一个临时字符指针变量tmp
int len = strlen(entry_name);
//定义一个长度变量,供后面处理字符串使用
tmp = (char *)calloc(1, ENTRY_LENGTH + 1);
//为tmp分配空间。因为配置文件每行的最大长度为都小于64,因此分配65个char长度已经完全足够。
//下面这个循环用户忽略空行和无用的行。
//fget()从文件中读取一行,如果读取成功,则进入循环,如果读取失败,则略去循环,直接返回NULL指针。
while ( fgets( tmp, ENTRY_LENGTH, stream) != NULL){
if(tmp[strlen(tmp) - 1] == '\n')
tmp[strlen(tmp) - 1] = ''; //去除字符串尾部的换行符

if ( isalpha(*tmp))
//判断是否为字母,不是字母(空行或带有[]的行)则略去,继续读取下一行
//指定长度判断该行是否符合配置文件需求。这个判断实际上并不完美,先不去考虑这些
if ( !strncmp(tmp, entry_name, len)){
strcpy(tmp_value, tmp + len + 1); //复制=后面的值到tmp_value中。
free(tmp); //释放临时字符串tmp指针
return tmp_value; //返回tmp_value指针
}

}
return NULL; //返回NULL指针,读取失败。
}

main.c

#include "global.h"
#include "linkthread.h"
#include "configfile.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

int
main (void) //这里,删除了main()函数的参数
{
//删除了通过判断命令行参数,给network枚举类型赋值的部分。
//传入信号SIGINT,调用un_link函数。不太懂Linux编程,所以不是很明白。
//应该是退出程序时断开连接之用。
//un_link函数在linkthread.c中定义。

signal(SIGINT, un_link);

//判断是否具有root权限,否则退出,并打印出错信息。
if (getuid() != 0)
{
fputs(">> Only root users can run this procedure\n", stdout);
return EXIT_FAILURE;
}

init();//调用init(),在linkthread.c中定义,将在以后分析
link_thread();//调用init(),在linkthread.c中定义,将在以后分析
return EXIT_SUCCESS; //成功返回。
}

嗯,基本上把这几个源代码文件分析完毕了。这几个是最简单的。其他的源码比这个难多了,所以下一篇不知道要等到什么时候才能写得出来了。Anyway,在锲而不舍的阅读源代码之后一定会有所收获的。:)

没有评论:

发表评论