hostapd的radius/eap server代码分析(3)-初始化及一次认证过程
这组代码分析只是不才准备发在csdn的blog中的,内容从hostapd到eapserver到具体eapType最后是openssl。
主要偏重于网络安全,而PEDIY似乎片中软件安全居多,发在这里不知是否合适。
注册PEDIY的目的是为了在密码学板块交流的。
发此文为了申请邀请码,希望斑竹赏脸

本文给出的代码中可能缺少某些结构及变量的定义说明,最好结合hostapd代码查看,所用版本为0.6.10
如有某些内容不通顺或叙述方法不好还望指出,以便后续修改。谢谢


前两篇是:
hostapd的radius/eap server代码分析(1)-main
http://blog.csdn.net/NJZhuJinhua/archive/2010/04/11/5473970.aspx
hostapd的radius/eap server代码分析(2)-hostapd配置
http://blog.csdn.net/NJZhuJinhua/archive/2010/04/13/5479573.aspx

njzjh@PEDIY    May.6,2010
NJZhuJinhua@csdn   May.6,2010
转载请注明出处

上会说道hostapd的配置部分,下面来看一下这些配置是怎么用到radius/eap server的实现中去的。
static int hostapd_setup_radius_srv(struct hostapd_data *hapd,
            struct hostapd_bss_config *conf)
{
  struct radius_server_conf srv;
  os_memset(&srv, 0, sizeof(srv));
  srv.client_file = conf->radius_server_clients;
  srv.auth_port = conf->radius_server_auth_port;
......
  hapd->radius_srv = radius_server_init(&srv);
  if (hapd->radius_srv == NULL) {
    wpa_printf(MSG_ERROR, "RADIUS server initialization failed.");
    return -1;
  }

  return 0;
}
作为参数的hostapd_data *hapd以及hostapd_bss_config *conf前面已有说明,其实这里只用第一个参数即可,第二个参数实际是第一个参数的一个成员变量。
radius_server_conf srv:顾名思义,存储本radius server的配置信息。
用hapd以及conf进行设置,然后调用radius_server_init完成radius server的初始化。函数调用返回struct radius_server_data指针并将此指针赋值给hapd->radius_srv。
这些配置信息均是全局唯一的。
radius_server_init内对radius_server_data进行初始化,其中包括对客户端安全关联的初始化,打开套接字准备接收消息,注册套接字上的事件处理回调函数等。

其中注册事件处理回调函数代码如下:
  if (eloop_register_read_sock(data->auth_sock,
             radius_server_receive_auth,
             data, NULL)) {
    radius_server_deinit(data);
    return NULL;
  }
即在data->auth_sock上有消息到达时调用radius_server_receive_auth进行处理
[对radius消息的收发用ACE的EventHandler重新实现,效果也不错]

radius_server_receive_auth实现的内容主要如下:
1:recvfrom接收数据
2:取得客户端信息,包括安全关联等
3:msg = radius_msg_parse(buf, len);解析radius属性。其中msg为struct radius_msg *msg = NULL;
4:radius_msg_verify_msg_auth校验80属性
5:处理radius请求  
if (radius_server_request(data, msg, (struct sockaddr *) &from,
          fromlen, client, abuf, from_port, NULL) == -2)
    return; /* msg was stored with the session */
-2的含义为PENDING。

下面直接给出radius_server_request的实现。
static int radius_server_request(struct radius_server_data *data,
         struct radius_msg *msg,
         struct sockaddr *from, socklen_t fromlen,
         struct radius_client *client,
         const char *from_addr, int from_port,
         struct radius_session *force_sess)
{
  u8 *eap = NULL;
  size_t eap_len;
  int res, state_included = 0;
  u8 statebuf[4];
  unsigned int state;
  struct radius_session *sess;
  struct radius_msg *reply;
  int is_complete = 0;

  if (force_sess)    直接指定已有session,譬如上一次pending了,现在complete_cb后继续执行时把刚才的session在这里指定一下即可,如果记下刚才生成session得到的state,这里也可以通过在msg中增加RADIUS_ATTR_STATE值来实现,太难看了,还是force_sess实现的好!
    sess = force_sess;
  else {
    //根据请求中的state属性找session
    res = radius_msg_get_attr(msg, RADIUS_ATTR_STATE, statebuf,
            sizeof(statebuf));
    state_included = res >= 0;
    if (res == sizeof(statebuf)) {
      state = WPA_GET_BE32(statebuf);
      sess = radius_server_get_session(client, state);
    } else {
      sess = NULL;
    }
  }

  if (sess) {
    RADIUS_DEBUG("Request for session 0x%x", sess->sess_id);
  } else if (state_included) {
  //含有state但是没找到session,拒绝
    RADIUS_DEBUG("State attribute included but no session found");
    radius_server_reject(data, client, msg, from, fromlen,
             from_addr, from_port);
    return -1;
  } else {
  //没带state 认为第一次接入,生成新的session
    sess = radius_server_get_new_session(data, client, msg);
    if (sess == NULL) {
      RADIUS_DEBUG("Could not create a new session");
      radius_server_reject(data, client, msg, from, fromlen,
               from_addr, from_port);
      return -1;
    }
  }

//判断是否重发的消息,及遇上一次端口一致,radiusid一致,radius头中的鉴别码一致。
//对重发的请求消息,则重发上一次的challenge消息(如果sess->last_reply有的话)
  if (sess->last_from_port == from_port &&
      sess->last_identifier == msg->hdr->identifier &&
      os_memcmp(sess->last_authenticator, msg->hdr->authenticator, 16) ==
      0) {
    RADIUS_DEBUG("Duplicate message from %s", from_addr);
    data->counters.dup_access_requests++;
    client->counters.dup_access_requests++;

    if (sess->last_reply) {
      res = sendto(data->auth_sock, sess->last_reply->buf,
             sess->last_reply->buf_used, 0,
             (struct sockaddr *) from, fromlen);
      if (res < 0) {
        perror("sendto[RADIUS SRV]");
      }
      return 0;
    }

    RADIUS_DEBUG("No previous reply available for duplicate "
           "message");
    return -1;
  }
          
  //取得radius请求消息中的eapmsg
  eap = radius_msg_get_eap(msg, &eap_len);
  if (eap == NULL) {
    RADIUS_DEBUG("No EAP-Message in RADIUS packet from %s",
           from_addr);
    data->counters.packets_dropped++;
    client->counters.packets_dropped++;
    return -1;
  }

  RADIUS_DUMP("Received EAP data", eap, eap_len);

  /* FIX: if Code is Request, Success, or Failure, send Access-Reject;
   * RFC3579 Sect. 2.6.2.
   * Include EAP-Response/Nak with no preferred method if
   * code == request.
   * If code is not 1-4, discard the packet silently.
   * Or is this already done by the EAP state machine? */
//上面注释看,eapserver跟eapstatemachine看来不是一个人写的。

  wpabuf_free(sess->eap_if->eapRespData);
//用收到的eapmsg给sess->eap_if->eapRespData赋值
  sess->eap_if->eapRespData = wpabuf_alloc_ext_data(eap, eap_len);
  if (sess->eap_if->eapRespData == NULL)
    os_free(eap);
  eap = NULL;
  sess->eap_if->eapResp = TRUE;

//运行 eap state machine,所处理数据即为sess->eap_if->eapRespData 。
  eap_server_sm_step(sess->eap);

  if ((sess->eap_if->eapReq || sess->eap_if->eapSuccess ||
       sess->eap_if->eapFail) && sess->eap_if->eapReqData) {
//状态为三者之一置位且含有EAPReq数据的话,打印请求数据
    RADIUS_DUMP("EAP data from the state machine",
          wpabuf_head(sess->eap_if->eapReqData),
          wpabuf_len(sess->eap_if->eapReqData));
  } else if (sess->eap_if->eapFail) {
    RADIUS_DEBUG("No EAP data from the state machine, but eapFail "
           "set");
  } else if (eap_sm_method_pending(sess->eap)) {
//eap处理过程中,有可能是具体eapmethod如eap-sim/aka等在处理过程中需要外发请求数据等待响应等情况。
    if (sess->last_msg) {
      radius_msg_free(sess->last_msg);
      os_free(sess->last_msg);
    }
    sess->last_msg = msg;
    sess->last_from_port = from_port;
    os_free(sess->last_from_addr);
    sess->last_from_addr = os_strdup(from_addr);
    sess->last_fromlen = fromlen;
    os_memcpy(&sess->last_from, from, fromlen);
    return -2;
  } else {
    RADIUS_DEBUG("No EAP data from the state machine - ignore this"
           " Access-Request silently (assuming it was a "
           "duplicate)");
    data->counters.packets_dropped++;
    client->counters.packets_dropped++;
    return -1;
  }

//多轮交互后sess完成的标志,成功 or 失败?
  if (sess->eap_if->eapSuccess || sess->eap_if->eapFail)
    is_complete = 1;

//封装eap消息
  reply = radius_server_encapsulate_eap(data, client, sess, msg);

  if (reply) {
    RADIUS_DEBUG("Reply to %s:%d", from_addr, from_port);
    if (wpa_debug_level <= MSG_MSGDUMP) {
      radius_msg_dump(reply);
    }

//性能统计
    switch (reply->hdr->code) {
    case RADIUS_CODE_ACCESS_ACCEPT:
      data->counters.access_accepts++;
      client->counters.access_accepts++;
      break;
    case RADIUS_CODE_ACCESS_REJECT:
      data->counters.access_rejects++;
      client->counters.access_rejects++;
      break;
    case RADIUS_CODE_ACCESS_CHALLENGE:
      data->counters.access_challenges++;
      client->counters.access_challenges++;
      break;
    }
//发送AccessChallenge消息
    res = sendto(data->auth_sock, reply->buf, reply->buf_used, 0,
           (struct sockaddr *) from, fromlen);
    if (res < 0) {
      perror("sendto[RADIUS SRV]");
    }
    if (sess->last_reply) {
      radius_msg_free(sess->last_reply);
      os_free(sess->last_reply);
    }
    sess->last_reply = reply;
    sess->last_from_port = from_port;
    sess->last_identifier = msg->hdr->identifier;
//保存port id authenticator等,用于判断下一个请求是不是这一次aceessRequest的重发
    os_memcpy(sess->last_authenticator, msg->hdr->authenticator,
        16);
  } else {
    data->counters.packets_dropped++;
    client->counters.packets_dropped++;
  }

//session结束后,在下一次定时器到达后删除这个sess
  if (is_complete) {
    RADIUS_DEBUG("Removing completed session 0x%x after timeout",
           sess->sess_id);
    eloop_cancel_timeout(radius_server_session_remove_timeout,
             data, sess);
    eloop_register_timeout(10, 0,
               radius_server_session_remove_timeout,
               data, sess);
  }

  return 0;
}


刚才想发上去了,可惜注册24小时内不让发帖,继续补充如下:
hostapd的radius/eap server代码分析 标题太冗长,有点泛。后面小主题决定自成系列了,下一步计划是
eap状态机代码分析-(1)结构定义
eap状态机代码分析-(2)eap算法协商机制
eap状态机代码分析-(3)eap超时重传机制
eap状态机代码分析-(4)待定