给FreeBSD写了个802.1X认证客户端(见http://my.chinaunix.net/xdkui)
实验室里上网先要经过802.1X认证。网关用的FreeBSD,但没有找到其下面的认证客户端,所以自己写了个,基于BPF。还好BPF就是来自BSD,不需要libpcap和libnet就可以实现对数据链路层的控制。代码写的很烂,拿出来都嫌丢能,只能自己用^_^不过程序已用了几个月,可以正常工作。
主要文件mysupplicant.c部分代码如下(其实上层交换机是实达的,它的802.1X认证和标准的不同,添加了自己的认证算法,但因为某些原因不方便公开,所以本文中删除了实达认证算法的代码。有需要的话可以找一个程序mystarV0[1].1-src,感谢其作者netxray@byhh,里面对实达的认证机制分析的很详细。不过我用mystar里代码的时候还是有点问题,分析了些截获的认证数据包才搞定^_^):
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <net/bpf.h>
#include <net/if.h>
#include <string.h>
#include <sys/select.h>
#include <signal.h>
#include "global.h"
#include "md5.h"
#define ETHERTYPE_8021X 0x888e
#define EAPOL_Packet 0x00
#define EAPOL_Start 0x01
#define EAPOL_Logoff 0x02
#define EAP_Request 1
#define EAP_Response 2
#define EAP_Success 3
#define EAP_Failure 4
#define EAP_TYPE_Identity 1
#define EAP_TYPE_MD5Challenge 4
typedef unsigned char int8;
typedef unsigned short int16;
typedef unsigned long int32;
struct bpf_insn insns[] = {
BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),//加载halfword以太网链路层的type
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_8021X, 0, 1),//判断是否是802.1X数据包,是则返回给本程序
BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
BPF_STMT(BPF_RET+BPF_K, 0),
};
typedef struct EAPOLforEthernet//EAPOL在Ethernet上的帧格式
{
int16 ethertype;
int8 version;
int8 type;
int16 length;
}EAPOL;
typedef struct EAPformat//EAP的帧格式
{
int8 code;
int8 id;
int16 length;
int8 type;
}__attribute__((packed)) EAP;
typedef union
{
u_int32_t ulValue;
u_int8_t btValue[4];
}ULONG_BYTEARRAY;
ULONG_BYTEARRAY m_serialNo; //序列号,收到第一个有效的Authentication-Success-packet时初始化
ULONG_BYTEARRAY m_key; //密码加密键值,在main()函数开始时初始化
char standardMAC[6]=;//标准802.1X组播MAC地址
char name[7]="username";//用户名
char pass[6]="password";//密码
char * nic="lnc0";//网卡名
void sig_intr(int signo); //发送logoff包 on exit with Ctrl+C
char * buf;
char * p;
int8 dstMAC[6]=;
int bpf;
int blen;
int main()
{
int tmp;
char challengelen;
struct ifreq ifr;
struct bpf_program bpf_pro=;
struct timeval timeout=;//设置BPF 5秒的超时
char * bufmd5;
int8 id;
char md5Hash[16];
MD5_CTX context;
fd_set readset;
ULONG_BYTEARRAY uTemp;
printf(" My 802.1X Supplicant for FreeBSD/n");
printf(" Coded by xdkui 5/22/2005/n");
printf(" E-mail:xdkui@sina.com.cn/n");
if((bpf=open("/dev/bpf1",O_RDWR))==-1)
{
perror("open /dev/bpf1 error");
exit(1);
}
strcpy(ifr.ifr_name,nic);//设置BPF的网络接口
if((-1==ioctl(bpf,BIOCGBLEN,&blen))||(-1==ioctl(bpf,BIOCSETIF,&ifr))||
(-1==ioctl(bpf,BIOCSETF,&bpf_pro))||(-1==ioctl(bpf,BIOCFLUSH))||(-1==ioctl(bpf,BIOCSRTIMEOUT,&timeout)))
{
perror("ioctl error");
close(bpf);
exit(1);
}
if((buf=(char *)malloc(blen))==NULL)
{
perror("malloc buf error");
close(bpf);
exit(1);
}
signal(SIGINT,sig_intr); //exit with Ctrl+C
signal(SIGTERM,sig_intr); //关机时发logoff EAPOL
retry:
//构造802.1X的EAPOL-Start帧
printf("send EAPOL_Start packet!/n");
memset(buf,0,blen);
memcpy(buf,standardMAC,6);
((EAPOL *)(buf+12))->;ethertype=htons(0x888E);
((EAPOL *)(buf+12))->;version=1;
((EAPOL *)(buf+12))->;type=EAPOL_Start;
((EAPOL *)(buf+12))->;length=0;
memcpy(buf+12+sizeof(EAPOL),pad,sizeof(pad));
if(1000!= write(bpf,buf,1000)) //发送802.1X的EAPOL-Start帧
{
perror("write EAPOL_Start error");
goto retry;
}
//读取EAP Request Identity帧
FD_ZERO(&readset);
FD_SET(bpf, &readset);
ioctl(bpf,BIOCFLUSH);
if(1!=(tmp=select(bpf+1,&readset,NULL,NULL,&timeout)))
{
perror("select read EAP Request Identiry");
goto retry;
}
if(-1==read(bpf,buf,blen))
{
perror("read EAP Request Identity error");
goto retry;
}
p=buf+((struct bpf_hdr *)buf)->;bh_hdrlen;
if((((EAPOL *)(p+12))->;type!=EAPOL_Packet)||(((EAP *)(p+12+sizeof(EAPOL)))->;code!=EAP_Request)
||(((EAP *)(p+12+sizeof(EAPOL)))->;type!=EAP_TYPE_Identity))
{
printf("EAP Request Identity format error!/n");
goto retry;
}
id=((EAP *)(p+12+sizeof(EAPOL)))->;id;
memcpy(dstMAC,p+6,6);
//构造EAP Response Identity帧
printf("send EAP Response Identity packet!/n");
memset(buf,0,blen);
memcpy(buf,dstMAC,6);
((EAPOL *)(buf+12))->;ethertype=htons(0x888E);
((EAPOL *)(buf+12))->;version=1;
((EAPOL *)(buf+12))->;type=EAPOL_Packet;
((EAPOL *)(buf+12))->;length=htons(sizeof(EAP)+sizeof(name));
((EAP *)(buf+12+sizeof(EAPOL)))->;code=EAP_Response;
((EAP *)(buf+12+sizeof(EAPOL)))->;id=id;
((EAP *)(buf+12+sizeof(EAPOL)))->;length=htons(sizeof(EAP)+sizeof(name));
((EAP *)(buf+12+sizeof(EAPOL)))->;type=EAP_TYPE_Identity;
memcpy(buf+12+sizeof(EAPOL)+sizeof(EAP),name,sizeof(name));
memcpy(buf+12+sizeof(EAPOL)+sizeof(EAP)+sizeof(name),pad,sizeof(pad));
if(1000!=write(bpf,buf,1000)) //发送EAP Response Identity帧
{
perror("write EAP Response Identity error");
goto retry;
}
//读取EAP Request MD5-Challenge帧
FD_ZERO(&readset);
FD_SET(bpf, &readset);
ioctl(bpf,BIOCFLUSH);
if(1!=select(bpf+1,&readset,NULL,NULL,&timeout))
{
perror("select read EAP Request MD5-Challenge");
goto retry;
}
if(-1==read(bpf,buf,blen))
{
perror("read EAP Request MD5-Challenge errorr");
goto retry;
}
p=buf+((struct bpf_hdr *)buf)->;bh_hdrlen;
if((((EAPOL *)(p+12))->;type!=EAPOL_Packet)||(((EAP *)(p+12+sizeof(EAPOL)))->;code!=EAP_Request)
||(((EAP *)(p+12+sizeof(EAPOL)))->;type!=EAP_TYPE_MD5Challenge))
{
printf("EAP Request MD5-Challenge format error!/n");
goto retry;
}
id=((EAP *)(p+12+sizeof(EAPOL)))->;id;
challengelen=*((char *)(p+12+sizeof(EAPOL)+sizeof(EAP)));
//printf("challengelen %d/n",challengelen);
if((bufmd5=(char *)malloc(1+challengelen+sizeof(pass)))==NULL)
{
perror("malloc bufmd5 error");
close(bpf);
exit(1);
}
/*The Response Value is the one-way hash calculated over a stream of
octets consisting of the Identifier, followed by (concatenated
with) the "secret", followed by (concatenated with) the Challenge
Value. The length of the Response Value depends upon the hash
algorithm used (16 octets for MD5).见RFC1994*/
memset(bufmd5,0,1+challengelen+sizeof(pass));
*bufmd5=id;
memcpy(bufmd5+1,pass,sizeof(pass));
memcpy(bufmd5+1+sizeof(pass),p+12+sizeof(EAPOL)+sizeof(EAP)+1,challengelen);
MD5Init(&context);//计算md5值
MD5Update(&context, bufmd5, 1+challengelen+sizeof(pass));
MD5Final(md5Hash, &context);
//构造EAP Response MD5-Challenge帧
printf("send EAP Response MD5-Challenge packet!/n");
memset(buf,0,blen);
memcpy(buf,dstMAC,6);
((EAPOL *)(buf+12))->;ethertype=htons(0x888E);
((EAPOL *)(buf+12))->;version=1;
((EAPOL *)(buf+12))->;type=EAPOL_Packet;
((EAPOL *)(buf+12))->;length=htons(sizeof(EAP)+sizeof(name)+challengelen+1);//这里的1是EAP里的value-size
((EAP *)(buf+12+sizeof(EAPOL)))->;code=EAP_Response;
((EAP *)(buf+12+sizeof(EAPOL)))->;id=id;
((EAP *)(buf+12+sizeof(EAPOL)))->;length=htons(sizeof(EAP)+sizeof(name)+challengelen+1);
((EAP *)(buf+12+sizeof(EAPOL)))->;type=EAP_TYPE_MD5Challenge;
*(char *)(buf+12+sizeof(EAPOL)+sizeof(EAP))=16;//md5 hash长度
memcpy(buf+12+sizeof(EAPOL)+sizeof(EAP)+1,md5Hash,16);
memcpy(buf+12+sizeof(EAPOL)+sizeof(EAP)+1+16,name,sizeof(name));
memcpy(buf+12+sizeof(EAPOL)+sizeof(EAP)+1+16+sizeof(name),pad,sizeof(pad));
if(1000!=write(bpf,buf,1000))
//发送EAP Response MD5-Challenge帧
{
perror("write EAP Response MD5-Challenge error");
goto retry;
}
//读取EAP success或fail帧
FD_ZERO(&readset);
FD_SET(bpf, &readset);
ioctl(bpf,BIOCFLUSH);
if(1!=select(bpf+1,&readset,NULL,NULL,&timeout))
{
perror("select read EAP authentication result");
goto retry;
}
if(-1==read(bpf,buf,blen))
{
perror("read EAP authentication result errorr");
goto retry;
}
p=buf+((struct bpf_hdr *)buf)->;bh_hdrlen;
if((((EAPOL *)(p+12))->;type!=EAPOL_Packet)||(((EAP *)(p+12+sizeof(EAPOL)))->;id!=id))
{
printf("EAP result packet error!/n");
goto retry;
}
if(((EAP *)(p+12+sizeof(EAPOL)))->;code==EAP_Success)
{
printf("EAP authentication success!/n");
//printf("Keeping sending echo every 20s... /n");
}
}
else
{
printf("EAP authentication fail!/n");
goto retry;
}
//sleep(3600);
//goto retry; //睡眠1小时后重新认证
close(bpf);//不会到这
return 0;
}
void sig_intr(int signo)
{
if(buf!=NULL)
{
//构造802.1X的EAPOL-Logoff帧
memset(buf,0,blen);
if((dstMAC[0]==0)&&(dstMAC[1]==0)&&(dstMAC[2]==0))
memcpy(buf,standardMAC,6);
else
memcpy(buf,dstMAC,6);
((EAPOL *)(buf+12))->;ethertype=htons(0x888E);
((EAPOL *)(buf+12))->;version=1;
((EAPOL *)(buf+12))->;type=EAPOL_Logoff;
((EAPOL *)(buf+12))->;length=0;
if((12+sizeof(EAPOL))!= write(bpf,buf,12+sizeof(EAPOL))) //发送802.1X的EAPOL-Logoff帧
{
perror("write EAPOL_Logoff error");
}
}
_exit(0);
}
程序参考了IEEE Std 802.1X-2001,RFC1994 PPP Challenge Handshake Authentication Protocol (CHAP)和RFC2284 PPP Extensible Authentication Protocol (EAP)。
[[i] 本帖最后由 xdkui 于 2005-11-7 22:49 编辑 [/i]]
-----------------------------------------------
呵呵,今天过来查BSD的精华,没想到看到自己的文章近4个月后被加精,hoho
本文也就是利用BSD里的BPF(BPF的开发可以见man bpf)发送和读取数据链路层的数据包,实际上程序实现了标准的802.1x认证协议和实达的认证算法(这部分参考的mystar),文中只写出了标准的协议部分
make即可编译,会生成名为mysupplicant的可执行文件,拷贝的任何地方,运行即开始认证。(附上完整代码)
需要在mysupplicant.c里更改用户名和密码
我这里认证完后要DHCP获取IP,估计认证过程也就几秒钟,所以我在freebsd的启动脚本里延迟了10秒,然后直接调用dhclient动态获取ip。呵呵,方法比较笨拙
程序是半年前写的,写的很烂,没有注意注释什么的,
用了这么久一直正常
[备注:]
完整代码:(由于上传不了) MySupplicantForBSD.zip