LDAP/SASL/GSSAPI/Kerberos编程API(5)--krb5应用服务
2021/5/16 12:28:39
本文主要是介绍LDAP/SASL/GSSAPI/Kerberos编程API(5)--krb5应用服务,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本篇介绍的不是本地应用,而是体现krb5真正价值的应用服务.分服务器端和客户端,即C/S,简单的说,server不架设自己的鉴权功能,client访问server的用户认证交由Kerberos处理
本篇要用到开发库libkrb5-dev,参考了MIT krb5源码中的演示例子appl/sample/sserver/sserver.c(Sample Kerberos v5 server)、appl/sample/sclient/sclient.c(Sample Kerberos v5 client)
本实验的目的:以最少设置实现最基本krb5应用服务
本实验的功能:服务器是一个面向连接TCP循环服务器,没使用并发功能,即多个客户连接到服务器,先到先服务,只有上一个客户完成服务才轮到下个客户;
客户和服务器成功建立一条连接后可开始传输数据,客户需先传输用户鉴权,认证失败服务器关闭这条连接退出服务,认证成功便在这条连接上进入服务;
客户端发送一串字符到服务器,然后服务器echo(回显)这些字符串给客户端;
说明:1)因为是面向连接,所以认证成功服务器继续维系这条连接的客/服传输,因此客户是可信的;客户不可信在鉴权失败后被服务器关闭掉连接了;
2)MIT krb5源码中有面向无连接UDP的演示例子,就不象TCP那样认证成功后直接传输数据,UDP那样的客户有可能不可信的;UDP例子好像每次的传输数据都要利用krb5进行加解密以达到客户可信效果,本人没深究不表
3)按TCP/UDP协议,面向连接是可靠的,面向无连接是不可靠的;协议的'可靠'术语是指数据包能够完整无误的送达;
本文面向连接/面向无连接的'连接'是基于本实验的语境下,按本人方便自己理解的'可信/不可信'类似于一个登录会话,这样理解可能不妥,请读者判别;
一.准备工作
Kerberos服务器(KDC) vmkdc 应用服务器 vmsrv.ctp.net 192.168.1.20 客户机 vmcln.ctp.net
以上主机的安装配置请参考<Kerberos+LDAP+NFSv4 实现单点登录>系列文章
领域为CTP.NET,并创建了krblinlin@CTP.NET用户主体和mysv/vmsrv.ctp.net应用服务主体
mysv为本篇实验的应用服务名,常见的应用服务有ldap、nfs
KDC上添加应用服务器
root@vmkdc:~# kadmin -l
kadmin> add -r mysv/vmsrv.ctp.net
新增
Max ticket life [unlimited]:
Max renewable life [unlimited]:
Principal expiration time [never]:
Password expiration time [never]:
Attributes [disallow-svr, disallow-proxiable, disallow-renewable, disallow-forwardable, disallow-postdated]:
Policy [default]:
上面一路回车缺省
kadmin> modify -a -disallow-svr mysv/vmsrv.ctp.net
删除disallow-svr,使mysv/vmsrv.ctp.net成为应用服务器
kadmin> ext -k /home/linlin/srv/krb5.keytab mysv/vmsrv.ctp.net
导出keytab
将krb5.keytab复制到vmsrv.ctp.net服务器的/etc目录下,确保krb5.keytab权限为root拥有,仅root可读
二.应用服务器
1.源代码
//源文件名:krbsrv.c #include <krb5.h> #include <stdio.h> #include <netdb.h> int main(int argc, char *argv[]) { krb5_context context; krb5_auth_context auth_context = NULL; krb5_ticket * ticket; struct sockaddr_in peername; int namelen = sizeof(peername); krb5_error_code retval; krb5_principal server; retval = krb5_init_context(&context); if (retval) { exit(1); } retval = krb5_sname_to_principal(context, NULL, "mysv",//应用服务名 KRB5_NT_SRV_HST, &server); if (retval) { exit(1); } int acc=0;// #1 //--v-- #2 int on = 1; int sock = -1; /* incoming connection fd */ struct sockaddr_in sockin; if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { exit(3); } // Let the socket be reused right away (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on,sizeof(on)); sockin.sin_family = AF_INET; sockin.sin_addr.s_addr = 0; sockin.sin_port = htons(12345);//端口号 if (bind(sock, (struct sockaddr *) &sockin, sizeof(sockin))) { exit(3); } if (listen(sock, 5) == -1) { exit(3); } for(;;) //--^-- { //--v-- #3 if ((acc = accept(sock, (struct sockaddr *)&peername, &namelen)) == -1){ exit(3); } //--^-- //--v-- #4 //--^-- char str[100]; int done ,n; // #5 retval = krb5_recvauth(context, &auth_context, (krb5_pointer)&acc, NULL, //应用服务版本,如"KRB5_sample_protocol_v1.0",这里设为NULL不验证版本 server, 0, // no flags NULL, //缺省NULL会读取/etc/krb5.keytab &ticket); if (retval) { printf(" auth failed\n");// 鉴权失败要关闭连接,见#6处 } else { printf(" auth ok\n"); done=0; do{ n=recv(acc,str,100,0); if (n<=0) //因为客户端一次性发过来,服务端有可能要分多次recv,所以要while循环,直到n=0 { if (n<0) printf("error-recv\n"); done=1; //退出while循环 printf("recv done:%i\n",done); } printf("recv from client:%s\n",str); sleep(10);//观察测试目的 if (!done) { if (send(acc,str,n,0)<0) { printf("error-send\n"); done=1; printf("send done:%i\n",done); } printf("send to client ok:%s",str); } }while(!done); } close(acc);// #6 printf("close\n"); //--v-- #7 //--^-- krb5_auth_con_free(context, auth_context); auth_context = NULL; //一定要此句,否则进入下个for循环出现段错误 };//for无限循环,进入下一个accept(),因是循环服务器,所以多个客户的连接是要排队,要等到上一个close() krb5_free_ticket(context, ticket); // #8 krb5_free_principal(context, server); krb5_free_context(context); exit(0); }
2.解析
1)见代码注释
2)
krb5_recvauth是阻塞的,不能在#5处设置非阻塞,否则客户端在#12处出错
见MIT krb5源码../src/krb5/1.18.3-4/src/lib/krb5/os/net_read.c中注释
/* * krb5_net_read() reads from the file descriptor "fd" to the buffer * "buf", until either 1) "len" bytes have been read or 2) cannot * read anymore from "fd". It returns the number of bytes read * or a read() error. (The calling interface is identical to * read(2).) * * XXX must not use non-blocking I/O */
3)
#8处语句不能提前放在for循环里#7处;测试放在#7处票据过期auth failed,进入下个for循环会出现段错误
3.编译
linlin@debian:~$ gcc -o krbsrv krbsrv.c -lkrb5
三.客户机
1.源代码
//源文件名:krbcln.c #include <krb5.h> #include <stdio.h> #include <string.h> #include <netdb.h> #include <signal.h> int main(int argc, char *argv[]) { int sock; krb5_context context; krb5_error_code retval; krb5_ccache ccdef; krb5_principal client, server; krb5_error *err_ret; krb5_ap_rep_enc_part *rep_ret; krb5_auth_context auth_context = 0; retval = krb5_init_context(&context); if (retval) { printf("err:while initializing krb5\n"); exit(1); } (void) signal(SIGPIPE, SIG_IGN); retval = krb5_sname_to_principal(context, "vmsrv.ctp.net", "mysv",//应用服务名,同服务端,这两个应构成 mysv/vmsrv.ctp.net KRB5_NT_SRV_HST, &server); if (retval) { printf("err:creating server name for host\n"); exit(1); } sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { printf( " socket: error\n" ); exit(1); } struct sockaddr_in remote; remote.sin_addr.s_addr=inet_addr("192.168.1.20");// #10 vmsrv.ctp.net地址 remote.sin_family = AF_INET; remote.sin_port = htons(12345); //端口号 bzero( &(remote.sin_zero) ,8); if (connect(sock, (struct sockaddr *)&remote, sizeof(struct sockaddr)) < 0) { printf( " connect: error\n"); close(sock); sock = -1; exit(1); } if (sock == -1) /* Already printed error message above. */ exit(1); printf("connected\n"); retval = krb5_cc_default(context, &ccdef); if (retval) { printf("err:while getting default ccache\n"); exit(1); } retval = krb5_cc_get_principal(context, ccdef, &client); if (retval) { printf("err:while getting client principal name\n"); exit(1); } retval = krb5_sendauth(context, &auth_context, (krb5_pointer) &sock, "",//应用服务版本,同服务端,如"KRB5_sample_protocol_v1.0";服务端用NULL没问题,经测试客户端用NULL运行段错误,所以这里用"" client, server, AP_OPTS_MUTUAL_REQUIRED, NULL, // #11 0, // no creds, use ccache instead ccdef, &err_ret, &rep_ret, NULL); printf(" sendauth\n"); krb5_free_principal(context, server); // finished using it krb5_free_principal(context, client); krb5_cc_close(context, ccdef); if (auth_context) krb5_auth_con_free(context, auth_context); if (retval && retval != KRB5_SENDAUTH_REJECTED) { printf("err:while using sendauth\n"); // #12 exit(1); } if (retval == KRB5_SENDAUTH_REJECTED) { printf("sendauth rejected, error reply is:\n\t\"%*s\"\n", err_ret->text.length, err_ret->text.data); } else if (rep_ret) { krb5_free_ap_rep_enc_part(context, rep_ret); printf("sendauth succeeded, reply is:\n"); char str[100]; int t; while( printf(">"),fgets(str,100,stdin),!feof(stdin)) { if (send(sock,str,strlen(str),0)==-1) { printf("error-send\n"); exit(1); } printf("send to srv ok:%s",str); if ((t=recv(sock,str,100,0))>0) { str[t]='\0'; printf("echo> %s",str); } else { if (t<0)printf("error-recv\n"); else printf("close\n"); exit(1); } } } close(sock); krb5_free_context(context); exit(0); }
2.解析
1)见代码注释
2)
#10处可改为域名解析
struct hostent *he; if ((he=gethostbyname("vmzhsvr.ctp.net"))==NULL) { printf("error vmsrvs\n"); exit(1); } remote.sin_addr= ( *((struct in_addr *)he->h_addr) );
3)
#11处为校验数据,本文不校验,用NULL参数
3.编译
linlin@debian:~$ gcc -o krbcln krbcln.c -lkrb5
四.运行测试
1.服务端
应用服务krbsrv需读取/etc/krb5.keytab,为方便在普通用户linlin下测试,设置该文件用户linlin可访问
运行应用服务
linlin@vmsrv:~$ ./krbsrv auth failed 认证失败 close 关闭连接 --^--未有票据(对应章节2.客户端) --v--获取票据(对应章节2.客户端) auth ok 认证成功 recv from client:abcd 接收了客户 send to client ok:abcd 发送到客户 recv from client:1234 send to client ok:1234
2.客户端
linlin@vmcln:~$ klist
klist: No ticket file: /tmp/krb5cc_1000
1)未有票据
linlin@vmcln:~$ ./krbcln
connected
err:while getting client principal name
认证失败
2)获取票据
linlin@vmcln:~$ kinit --no-forwardable krblinlin krblinlin@CTP.NET's Password: linlin@vmcln:~$ ./krbcln connected sendauth sendauth succeeded, reply is: 认证成功 >abcd 输入字符发给服务器 send to srv ok:abcd echo> abcd 从服务器返回字符 >1234 send to srv ok:1234 echo> 1234 >
3)票据过期
linlin@vmcln:~$ ./krbcln
connected
sendauth
err:while using sendauth 失败,即客户端代码#12处
服务端认证失败、关闭连接
五.客户机(无krb5)
1.源代码
//源文件名:sendcln.c #include <stdio.h> #include <string.h> #include <netdb.h> int main(int argc, char *argv[]) { int sock; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { printf( " socket: error\n" ); exit(1); } struct sockaddr_in remote; remote.sin_addr.s_addr=inet_addr("192.168.1.20");//vmsrv.ctp.net地址 remote.sin_family = AF_INET; remote.sin_port = htons(12345); //端口号 bzero( &(remote.sin_zero) ,8); if (connect(sock, (struct sockaddr *)&remote, sizeof(struct sockaddr)) < 0) { printf( " connect: error\n"); close(sock); sock = -1; exit(1); } if (sock == -1) /* Already printed error message above. */ exit(1); printf("connected\n"); { char str[100]; while( printf(">"),fgets(str,100,stdin),!feof(stdin)) { if (send(sock,str,strlen(str),0)==-1) { printf("error-send\n"); exit(1); } printf("send to srv ok:%s",str); } } close(sock); exit(0); }
2.解析
不发出krb5验证,只发送数据,不接收数据
3.编译
linlin@debian:~$ gcc -o sendcln sendcln.c
4.跟踪运行
1)服务端
linlin@vmsrv:~$ strace ./krbsrv ... read(4, "abcd", 4) = 4 mmap(NULL, 1633841152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9d7f6ca000 read(4, "efgh\n", 1633837924) = 5 read(4, "1234567890\n", 1633837919) = 11 ...期间不断接收客户发来的数据 read(4, 直到客户机强制退出 read(4, "", 1633837807) = 0 munmap(0x7f9d7f6ca000, 1633841152) = 0 write(1, " auth failed\n", 13 auth failed 认证失败 ) = 13 close(4) = 0 关闭连接 write(1, "close\n", 6close ) = 6 accept(3, 进入下一个for循环等待客户连接
2)客户端(无krb5)
linlin@vmcln:~$ ./sendcln
connected
abcdefgh
send to srv ok:abcdefgh
1234567890
send to srv ok:1234567890
...不断发送数据
^C 强制退出
linlin@vmcln:~$
3)小结
可见服务端的库函数krb5_recvauth()没有识别无效数据,客户端一直发垃圾数据,服务端krb5_recvauth就一直读取,客/服的连接一直维系下去,直到客户端退出
3.1)
//--v-- 超时处理 int rc; fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(acc,&fds); tv.tv_sec = tv.tv_usec = 15; //超时15秒 rc = select(acc+1, &fds, NULL, NULL, &tv); if (rc < 0) { printf(" select failed\n"); close(acc); //exit(1); } if (FD_ISSET(acc,&fds)) printf(" select ok\n"); else { printf(" time out\n"); close(acc); //exit(1); } //--^--
服务端在#4处,即accept和krb5_recvauth之间加上超时处理,这也仅仅能处理客户端只连接不发送数据的情况(即一定时间内未有数据到来就不要进行读操作krb5_recvauth,避免读操作阻塞);
一旦客户端发送垃圾数据,krb5_recvauth总跳不出,我也找不到好的超时处理方法
六.超级服务器inetd
inetd可以简单地提供并发功能
应用服务器krbsrv.c中将#2、#3注释掉,重新编译
在inetd已将连接套接字复制到描述符0、1、2,所以#1处该行连接套接字描述符acc为0
用inetd编程,规范读、写分别0、1,为方便,本文只用描述符0(在inetd里0、1、2为同一个套接字)
1.应用服务器配置
1)安装inetd
root@vmsrv:~# apt-get install openbsd-inetd
2)修改/etc/services文件
增加一行:
mykrbsrv 12345/tcp
#服务名 端口号/网络类型
3)修改/etc/inetd.conf文件
增加一行:
mykrbsrv stream tcp nowait root /home/linlin/krbsrv
#服务名 套接字类型 采用的协议 等待否 用户名 应用程序及路径
2.运行测试
服务器已启动inetd
root@vmsrv:~# ps -e PID TTY TIME CMD 1 ? 00:00:00 systemd 18 ? 00:00:00 systemd-journal 39 ? 00:00:00 dhclient 51 ? 00:00:00 dbus-daemon 81 ? 00:00:00 inetd 84 pts/3 00:00:00 login 85 ? 00:00:00 sshd 106 pts/3 00:00:00 bash 129 pts/3 00:00:00 ps
查看服务器监听端口,进程名inetd一行的端口服务名mykrbsrv(即可从/etc/services找出对应端口号)
root@vmsrv:~# netstat -ltp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN 85/sshd: /usr/sbin/ tcp 0 0 0.0.0.0:mykrbsrv 0.0.0.0:* LISTEN 81/inetd tcp6 0 0 [::]:ssh [::]:* LISTEN 85/sshd: /usr/sbin/ root@vmsrv:~#
客户机vmcln(192.168.1.28)运行./krbcln连上服务器后,服务器inetd启动了krbsrv进程
root@vmsrv:~# ps -e PID TTY TIME CMD ... 81 ? 00:00:00 inetd ... 136 ? 00:00:00 krbsrv 137 pts/3 00:00:00 ps root@vmsrv:~#
客户机vmcln再运行./krbcln,应用服务器可见两个krbsrv进程
root@vmsrv:~# ps -e PID TTY TIME CMD ... 136 ? 00:00:00 krbsrv 138 ? 00:00:00 krbsrv 139 pts/3 00:00:00 ps
查看服务器连接信息
root@vmsrv:~# netstat -tp Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 192.168.1.20:mykrbsrv 192.168.1.28:35850 ESTABLISHED 136/krbsrv tcp 0 0 192.168.1.20:mykrbsrv 192.168.1.28:35860 ESTABLISHED 138/krbsrv root@vmsrv:~#
客户机终结其中一个客户程序后
root@vmsrv:~# netstat -tp Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 192.168.1.20:mykrbsrv 192.168.1.28:35850 ESTABLISHED 136/krbsrv tcp 0 0 192.168.1.20:mykrbsrv 192.168.1.28:35860 CLOSE_WAIT 138/krbsrv root@vmsrv:~#
等会儿就剩下一个krbsrv进程
root@vmsrv:~# netstat -tp Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 192.168.1.20:mykrbsrv 192.168.1.28:35850 ESTABLISHED 136/krbsrv root@vmsrv:~# ps -e PID TTY TIME CMD ... 136 ? 00:00:00 krbsrv 155 pts/3 00:00:00 ps root@vmsrv:~#
这篇关于LDAP/SASL/GSSAPI/Kerberos编程API(5)--krb5应用服务的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南