基于FreeRadius ldap实现双因素认证

需求:

通过radius 连接ldap,并且实现双因素认证(每个用户每次在密码后,需要输入6位随机数字,作为动态密码认证)。

设计:

找到radius代码,验证用户ldap帐号的功能

提取出用户名和密码

提取密码的最后6位数字为随机PIN,新的密码去掉6位数字,保存

将用户名和PIN发送到远程进行校验

根据用户名和新密码,查询ldap,判断是否成功

实现:

1.验证过程分析 版本freeradius-server-4.0.x

1) \src\modules\rlm_ldap\rlm_ldap.c

line:662 mod_authenticate 实现ldap认证,传入的REQUEST中,保存了用户输入的用户名和密码

2) \src\include\radiusd.h

line:234 struct rad\_request 定义额REQUEST, username,password 都是VALUE_PAIR类型

3) \src\include\pair.h

line:74 typedef struct value_pair VALUE_PAIR, 其中数据都是fr_value_box_t data; 类型,并且定义宏

#define vp_strvalue data.vb_strvalue

4) \src\include\value.h

line:26 typedef struct value_box fr_value_box_t; 其中最关键的是datum 联合体,并且定义了宏

#define vb_strvalue datum.strvalue

5) \src\modules\rlm_ldap\rlm_ldap.c

line:740 读取用户名和密码的方法

request->username->vp_strvalue

request->password->vp_strvalue

但由于freeradius3.0 以上对环境要求比较苛刻,因此如果系统是版本内核,建议安装低版本freeradius,这里选择2.2.5

2.验证过程分析 版本freeradius-server-2.2.5

1) freeradius模块化实现,对接ldap的验证代码在 \src\modules\rlm_ldap\rlm_ldap.c

导出功能中,主要包括认证和授权函数:

/* globally exported name */
module_t rlm_ldap = {
    RLM_MODULE_INIT,
    "LDAP",
    RLM_TYPE_THREAD_SAFE,    /* type: reserved      */
    ldap_instantiate,    /* instantiation      */
    ldap_detach,        /* detach          */
    {
        ldap_authenticate,    /* authentication      */
        ldap_authorize,        /* authorization      */
        NULL,            /* preaccounting      */
        NULL,            /* accounting          */
        NULL,            /* checksimul          */
        NULL,            /* pre-proxy          */
        NULL,            /* post-proxy          */
#ifdef NOVELL
        ldap_postauth        /* post-auth          */
#else
        NULL
#endif
    },
};

通过对ldap_authenticate(Check the user's password against ldap database)和ldap_authorize( Check if user is authorized for remote access)的注释查看,ldap_authenticate 函数是做密码校验的

2) ldap_authenticate 函数的原型,传入主要参数是 REQUEST,里面包含主要寻找的用户名和密码

3) REQUEST是结构体,定义在 src\include\radiusd.h , 具体内容 typedef struct auth_req REQUEST; 其中用户名和密码 定义如下:

struct auth_req {
 ....
 VALUE_PAIR *config_items;
 VALUE_PAIR *username; //用户输入的用户名
 VALUE_PAIR *password; //用户输入的密码
}

4) 用户名和密码类型为 VALUE_PAIR ,也是结构体,具体定义在 \src\include\libradius.h, 如下所示,最主要的是 VALUE_PAIR_DATA 类型,具体定义也在 \src\include\libradius.h中。

typedef struct value_pair {
    ...
    struct value_pair   *next;
    uint32_t        lvalue;
    VALUE_PAIR_DATA     data;
} VALUE_PAIR;

typedef union value_pair_data {
    char            strvalue[MAX_STRING_LEN];  //保存密码或者用户名的变量
    ...
} VALUE_PAIR_DATA;

同时,该文件中定义了 #define vp_strvalue data.strvalue

因此,从一个request变量中,获得用户名或者密码字符串的写法为

request->username->vp_strvalue

request->password->vp_strvalue

对应获得的值为 char 数组。

5) 基于以上分析,可知,直接在\src\modules\rlm_ldap\rlm_ldap.c文件的ldap_authenticate 函数中添加代码实现,如下功能即可:

 1)  获得用户输入的用户名和密码,request->username->vp_strvalue 和request->password->vp_strvalue

 2) 校验密码长度,小于6(因为pin码为6位)直接返回认证失败

 3) 将用户名和密码 传入自己实现的函数 google_authenticate
   /*
   传入参数:
         req_username:请求用户名
         req_password:请求密码
   输出结果:
          0  : 输入错误,或者网络超时等原因失败
          1  : 双因素认证成功
          2  :双因素认证失败

   具体实现:
          1. 校验输入的用户名和密码是否合法,长度,pin码是否为6位数字
          2. 根据用户名和pin码作为参数,请求google authenticator接口, 尽可能排除网络问题,网络异常时,会连续请求三次
          3. 判断返回结果,如果包含\"errcode\": 0, 则表示校验成功,否则失败。

   可能问题:
          1. 发送请求 超时时间 2秒
          2. 验证双因素返回结果,由于c解析json相对麻烦,因此这里暂时使用字符串判断验证是否成功 
*/
static  int google_authenticate(char *req_username, char* req_password)

具体源代码:

////////////////////////////new code
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <netdb.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#define HOST myserverip       //根据实际情况修改 google authenticate API server 地址
#define PORT 80           //根据实际情况修改 google authenticate API server 端口
#define USERAGENT "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.114 Safari/537.36"
#define ACCEPTLANGUAGE "zh-CN,zh;q=0.8,en;q=0.6,en-US;q=0.4,en-GB;q=0.2"
#define MAX_LEN  50
//#define ACCEPTENCODING "gzip,deflate,sdch"
///////////////////////////new code
//--------------------------new code
static int http_Get(char *data);
static int google_authenticate(char *username, char*password);

static  int http_Get(char *data){
    int sock;
    if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0){
        DEBUG("ldap_authenticate:Can't create TCP socket!\n");
        return 0;
    }
    struct timeval timeout = {2,0};
    char *get;
    char *tpl="POST /google_authenticate/ HTTP/1.1\r\nHost:%s\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\nUser-Agent:%s\r\nAccept-Language:%s\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length: %d\r\n\r\n%s"; //根据实际情况,修改google authenticate API server URI地址
    struct hostent *hent;
    int iplen=15;
    int tmpres;
    struct sockaddr_in *remote;
    char *ip=(char *)malloc(iplen+1);
    if(ip == NULL)
        return 0;
    setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(struct timeval));
    setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));
    memset(ip,0,iplen+1);
    if((hent=gethostbyname(HOST))==NULL){
        DEBUG("ldap_authenticate:Can't get ip from %s\n", HOST);
        if(ip) free(ip);
        return 0;
    }
    if(inet_ntop(AF_INET,(void *)hent->h_addr_list[0],ip,iplen)==NULL){
        DEBUG("ldap_authenticate:Can't resolve host! %s\n", HOST);
        if(ip) free(ip);
        return 0;
     }
    remote=(struct sockaddr_in *)malloc(sizeof(struct sockaddr_in*));
    if(remote == NULL)
        return 0;
    remote->sin_family=AF_INET;
    tmpres=inet_pton(AF_INET,ip,(void *)(&(remote->sin_addr.s_addr)));
    if(tmpres<0){
        DEBUG("ldap_authenticate:Can't set remote->sin_addr.s_addr\n");
        if(ip) free(ip);
        if(remote) free(remote);
        return 0;
    }else if(tmpres==0){
        DEBUG("ldap_authenticate:%s is not a valid IP address\n",ip);
        if(ip) free(ip);
        if(remote) free(remote);
        return 0;
    }
    remote->sin_port=htons(PORT);
    if(connect(sock,(struct sockaddr *)remote,sizeof(struct sockaddr))<0){
        if(ip) free(ip);
        if(remote) free(remote);
        DEBUG("ldap_authenticate:Could not connect %s!\n",HOST);
        return 0;
     }
    get=(char *)malloc(strlen(HOST)+strlen(data)+strlen(USERAGENT)+strlen(tpl)+strlen(ACCEPTLANGUAGE));
    memset(get,0,sizeof(get));
    if(get == NULL)
        return 0;
    sprintf(get,tpl,HOST,USERAGENT,ACCEPTLANGUAGE,strlen(data),data);
    int sent=0;
    while(sent<strlen(get)){
        tmpres=send(sock,get+sent,strlen(get)-sent,0);
        if(tmpres==-1){
            DEBUG("ldap_authenticate:Can't send query!\n");
            if(ip) free(ip);
            if(get) free(get);
            if(remote) free(remote);
            return 0;
        }
        sent+=tmpres;
    }
    char buf[BUFSIZ+1];
    memset(buf,0,sizeof(buf));
    int htmlstart=0;
    char *htmlcontent;
    while((tmpres=recv(sock,buf,BUFSIZ,0))>0){
        if(htmlstart==0){
            htmlcontent=strstr(buf,"\r\n\r\n");
            if(htmlcontent!=NULL){
                htmlstart=1;
                htmlcontent+=4;
            }
       }else{
           htmlcontent=buf;
       }

       if(htmlstart > 0){
           //fprintf(stdout,htmlcontent);
           break;
       }
       memset(buf,0,tmpres);      
    }
    if(get)
        free(get);
    if(remote)
        free(remote);
    if(ip)
        free(ip);
    close(sock);

    DEBUG("url:[%s],http_get %s  \n", data,htmlcontent);
    if(strlen(htmlcontent) >0 && strstr(htmlcontent,"\"errcode\": 0,") != NULL)
       return 1;
    else
       return 2;
}
static  int google_authenticate(char *req_username, char* req_password){

    int i=0,j=0,iPinNum = 6,iret = 0;
    char password[MAX_LEN+1];
    char pass[MAX_LEN+1];
    char pin[iPinNum];
    memset(password,0, sizeof(password));
    if(strlen(req_password) > MAX_LEN)
    {
        DEBUG("ldap_authenticate:google_authenticate the password is too long!");
        return iret;
    }
    if(strlen(req_username) > MAX_LEN)
    {
        DEBUG("ldap_authenticate:google_authenticate the username is too long!\n");
        return iret;
    }

    strcpy(password, req_password);
    memset(pass,0,sizeof(pass));
    for(i=0; i< strlen(password)-iPinNum; i++)
    {
       pass[i] = password[i];
    }
    pass[i] = '\0';
    for(j=0; j < iPinNum; j++,i++)
    {
         pin[j] = password[i];
         if(!isdigit(pin[j])){
           DEBUG("ldap_authenticate: google_authenticate pin code is not digit\n");
           return iret;
         }
    }
    char *tpl="username=%s&token=%s";
    char *cData ;
    cData=(char *)malloc(strlen(tpl) + strlen(pass) + iPinNum);
    if(cData == NULL)
        return iret;
    memset(cData,0,sizeof(cData));
    sprintf(cData,tpl,req_username,pin);
    int count = 0;
    while(1){
        int iTmp = http_Get(cData);
        count++;
        if(1 == iTmp){
            iret =1;
            break;
        }else if(count > 3){
            break;
        }else if(2 == iTmp){
            break;
        }
    }

    if(cData)
      free(cData);
    return iret;
}
//--------------------------------------------- end new code
//----------------------------------------ldap_authenticate 函数添加如下代码
if(strlen(request->password->vp_strvalue) < 7)
    {
        DEBUG("ldap_authenticate:ldap_authenticate username:%s,password:[%s]is too short\n ",request->username->vp_strvalue,request->password->vp_strvalue);
        return RLM_MODULE_FAIL;
    }
    if( 1 != google_authenticate(request->username->vp_strvalue,request->password->vp_strvalue))
    {
        DEBUG("ldap_authenticate:ldap_authenticate username:%s,password:[%s],google_authenticate is wrong\n ",request->username->vp_strvalue,request->password->vp_strvalue);
        return RLM_MODULE_FAIL;
    }
    request->password->vp_strvalue[strlen(request->password->vp_strvalue)-6] = '\0';
//-------------------------------------------ldap_authenticate 函数 添加如上内容

实际测试:

  1. 下载 freeradius-server-2.2.5源代码,地址:

    https://github.com/FreeRADIUS/freeradius-server/archive/release_2_2_5.tar.gz

    1. 修改源代码 \src\modules\rlm_ldap\rlm_ldap.c 添加如上代码
yum install openssl openssl-devel openldap-devel
wget   https://github.com/FreeRADIUS/freeradius-server/archive/release_2_2_5.tar.gz
tar -zxvf release_2_2_5
cd freeradius-server-release_2_2_5/
./configure > /tmp/log.txt 
make > /tmp/log.txt
make install > /tmp/log.txt

#执行radius
radiusd -X
#出现如下错误需要更新openssl版本,Refusing to start with libssl version OpenSSL 1.0.1e-fips 11 Feb 2013 (in range 1.0.1 - 1.0.1f).  Security advisory CVE-2014-0160 (Heartbleed)
#需要升级openssl,只为了测试 可以修改 src\main\radiusd.c中 line:284 注释掉代码 if (ssl_check_version(mainconfig.allow_vulnerable_openssl) < 0) {exit(1);}

#进行配置,  默认安装后,配置文件地址  /usr/local/etc/raddb/
[root@localhost ldap]# egrep -v "^#|^$|#" /usr/local/etc/raddb/sites-available/default
authorize {
        ldap
}
authenticate {
        Auth-Type LDAP {
                ldap
        }
}
preacct {
        preprocess
        acct_unique
        suffix
        files
}
accounting {
}
session {
        radutmp
}
post-auth {
}
pre-proxy {
}
post-proxy {
}
[root@localhost ldap]# egrep -v "^#|^$|#" /usr/local/etc/raddb/modules/ldap
ldap {
        server = "127.0.0.1"
        identity = "cn=Manager,dc=website80,dc=com"  #需要管理员 帐号和密码
        password = "newpass"
        basedn = "ou=People,dc=website80,dc=com"
        filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
        ldap_connections_number = 5
        max_uses = 0
        timeout = 4
        timelimit = 3
        net_timeout = 1
        tls {
                start_tls = no
        }
        dictionary_mapping = ${confdir}/ldap.attrmap
        edir_account_policy_check = no
        keepalive {
                idle = 60
                probes = 3
                interval = 3
        }
}


#输入用户名和密码进行校验
radtest user1 passw0rd  localhost 0 testing123

#校验执行结果
rad_recv: Access-Request packet from host 127.0.0.1 port 52304, id=171, length=75
        User-Name = "user1"
        User-Password = "passw0rd"
        NAS-IP-Address = 127.0.0.1
        NAS-Port = 0
        Message-Authenticator = 0x0ae2384ba299eddef6ebfb8fc950d304
Wed Jul  5 08:34:43 2017 : Info: # Executing section authorize from file /usr/local/etc/raddb/sites-enabled/default
Wed Jul  5 08:34:43 2017 : Info: +group authorize {
Wed Jul  5 08:34:43 2017 : Info: [ldap] performing user authorization for user1
Wed Jul  5 08:34:43 2017 : Info: [ldap]         expand: %{Stripped-User-Name} ->
Wed Jul  5 08:34:43 2017 : Info: [ldap]         ... expanding second conditional
Wed Jul  5 08:34:43 2017 : Info: [ldap]         expand: %{User-Name} -> user1
Wed Jul  5 08:34:43 2017 : Info: [ldap]         expand: (uid=%{%{Stripped-User-Name}:-%{User-Name}}) -> (uid=user1)
Wed Jul  5 08:34:43 2017 : Info: [ldap]         expand: ou=People,dc=website80,dc=com -> ou=People,dc=website80,dc=com
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] ldap_get_conn: Checking Id: 0
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] ldap_get_conn: Got Id: 0
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] performing search in ou=People,dc=website80,dc=com, with filter (uid=user1)
Wed Jul  5 08:34:43 2017 : Info: [ldap] looking for check items in directory...
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] userPassword -> Password-With-Header == "{SSHA}NtHfW8IAaWNvobkwrvIm//D5Gnk7YB85"
Wed Jul  5 08:34:43 2017 : Info: [ldap] looking for reply items in directory...
Wed Jul  5 08:34:43 2017 : Info: [ldap] Setting Auth-Type = LDAP
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] ldap_release_conn: Release Id: 0
Wed Jul  5 08:34:43 2017 : Info: ++[ldap] = ok
Wed Jul  5 08:34:43 2017 : Info: +} # group authorize = ok
Wed Jul  5 08:34:43 2017 : Info: Found Auth-Type = LDAP
Wed Jul  5 08:34:43 2017 : Info: # Executing group from file /usr/local/etc/raddb/sites-enabled/default
Wed Jul  5 08:34:43 2017 : Info: +group LDAP {
Wed Jul  5 08:34:43 2017 : Info: [ldap] login attempt by "user1" with password "passw0rd"
Wed Jul  5 08:34:43 2017 : Info: [ldap] user DN: uid=user1,ou=People,dc=website80,dc=com
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] (re)connect to 127.0.0.1:389, authentication 1
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] bind as uid=user1,ou=People,dc=website80,dc=com/passw0rd to 127.0.0.1:389
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] waiting for bind result ...
Wed Jul  5 08:34:43 2017 : Debug:   [ldap] Bind was successful
Wed Jul  5 08:34:43 2017 : Info: [ldap] user user1 authenticated succesfully
Wed Jul  5 08:34:43 2017 : Info: ++[ldap] = ok
Wed Jul  5 08:34:43 2017 : Info: +} # group LDAP = ok
Wed Jul  5 08:34:43 2017 : Info:   WARNING: Empty post-auth section.  Using default return values.
Sending Access-Accept of id 171 to 127.0.0.1 port 52304
Wed Jul  5 08:34:43 2017 : Info: Finished request 1.
Wed Jul  5 08:34:43 2017 : Debug: Going to the next request
Wed Jul  5 08:34:43 2017 : Debug: Waking up in 4.9 seconds.
Wed Jul  5 08:34:48 2017 : Info: Cleaning up request 1 ID 171 with timestamp +24
Wed Jul  5 08:34:48 2017 : Info: Ready to process requests.

参考文献:

radius在centos上安装

http://wiki.freeradius.org/building/RHEL and Centos

升级gcc

http://blog.csdn.net/origin_lee/article/details/43231397

升级gcc可能出现的问题,缺少g++

https://stackoverflow.com/questions/25055259/how-to-resolve-no-usable-dependency-style-found-error-while-making-gcc-4-9-1

c语言socket请求

http://blog.csdn.net/yangguo_2011/article/details/14106223

http://blog.csdn.net/sjin_1314/article/details/41776679

results matching ""

    No results matching ""