本篇介绍的不是本地应用,而是体现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:1234send 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 krblinlinkrblinlin@CTP.NET's Password:linlin@vmcln:~$ ./krbclnconnected  sendauthsendauth succeeded, reply is:   认证成功>abcd                 输入字符发给服务器send to srv ok:abcdecho> abcd            从服务器返回字符>1234send to srv ok:1234echo> 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)                      = 4mmap(NULL, 1633841152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9d7f6ca000read(4, "efgh\n", 1633837924)           = 5read(4, "1234567890\n", 1633837919)     = 11...期间不断接收客户发来的数据read(4, 直到客户机强制退出read(4, "", 1633837807)                 = 0munmap(0x7f9d7f6ca000, 1633841152)      = 0write(1, " auth failed\n", 13 auth failed     认证失败)          = 13close(4)                                = 0   关闭连接write(1, "close\n", 6close)                  = 6accept(3,                                     进入下一个for循环等待客户连接

2)客户端(无krb5)

linlin@vmcln:~$ ./sendclnconnected>abcdefghsend to srv ok:abcdefgh>1234567890send 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 -ltpActive 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 psroot@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 -tpActive 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 -tpActive 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 -tpActive 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 psroot@vmsrv:~# 
©著作权归作者所有:来自51CTO博客作者lu_linlin的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. Mysql 读写分离、主从复制
  2. PHP:MySQL常用DDL数据定义语言, DML数据库操作语言,PDO连接数据
  3. 详解服务器处理器基础知识
  4. 详解服务器性能测试基准体系
  5. Nginx的五种负载均衡策略
  6. 电信大宽带服务器300M谁家有?
  7. PHP:oop->抽象类/接口/后期静态绑定/单例模式连接数据库 Db类中
  8. Python使用socket搭建TCP服务器(后期的客户端:GPRS模块)
  9. php之单例模式连接数据库

随机推荐

  1. Android Web development Note
  2. Android持久化技术之SharedPreferences存
  3. Android的多媒体框架Opencore代码阅读
  4. Android(安卓)的 ListView 的CheckBox标
  5. 不能找到相应的target
  6. Android(安卓)向桌面添加快捷方式
  7. android用NFS的形式挂载工作.
  8. Ubuntu11.10下编译android内核源码
  9. 25、Android中通过Intent传递对象、集合
  10. 用git下载Android的某个应用程序