コード例 #1
0
void
fix_wpa(nas_t *nas, nas_sta_t *sta, char *key, int len)
{
	eapol_header_t eapol;
	eapol_wpa_key_header_t wpa_key;
	unsigned char buffer[sizeof(eapol) + sizeof(wpa_key)];
	wpa_t *wpa = (wpa_t *)nas->wpa;

	dbg(nas, "use MPPE recv key as PMK");
	/* Use the RADIUS key for a PMK. */
	if (len > PMK_LEN)
		len = PMK_LEN;
	memcpy(sta->suppl.pmk, key, len);
	sta->suppl.pmk_len = len;
	/* generate pmkid */
	if (sta->mode & WPA2) {
		nas_wpa_calc_pmkid(wpa, sta);
		if (sta->flags & STA_FLAG_PRE_AUTH) {
			/* Once Pre authed don't worry about this variable */
			sta->flags &= ~STA_FLAG_PRE_AUTH;
			return;
		}
	}

	/* Fake enough of an EAPOL key message to make WPA happy. */
	memcpy(&eapol.eth.ether_shost, &sta->ea, ETHER_ADDR_LEN);
	memcpy(&eapol.eth.ether_dhost, &nas->ea, ETHER_ADDR_LEN);
	eapol.eth.ether_type = htons(ETHER_TYPE_802_1X);
	eapol.version = sta->eapol_version;
	eapol.type = EAPOL_KEY;
	eapol.length = EAPOL_WPA_KEY_LEN;
	memset(&wpa_key, 0, sizeof(wpa_key));
	if (sta->mode & (WPA2_PSK | WPA2))
		wpa_key.type = EAPOL_WPA2_KEY;
	else
		wpa_key.type = EAPOL_WPA_KEY;
	wpa_key.key_info = htons(WPA_KEY_PAIRWISE | WPA_KEY_REQ);
	/* Put the message pieces together.  (They probably were together,
	 *  but it's not valid to assume so.
	 */
	memcpy(buffer, &eapol, sizeof(eapol));
	memcpy(&buffer[offsetof(eapol_header_t, body)], &wpa_key,
	       sizeof(wpa_key));

	/* WPA state should be okay now to start 4-way handshake.  */
	if (process_wpa(wpa, (eapol_header_t *)buffer, sta)) {
		cleanup_sta(nas, sta, DOT11_RC_8021X_AUTH_FAIL, 0);
	}
}
コード例 #2
0
void
eapol_sup_dispatch(nas_t *nas, eapol_header_t *eapol)
{
	nas_sta_t *sta;
#ifdef BCMDBG
	char eabuf[ETHER_ADDR_STR_LEN];
#endif

	if (!eapol) {
		dbg(nas, "Missing EAPOL header");
		return;
	}

	sta = lookup_sta(nas, (struct ether_addr *) eapol->eth.ether_shost,
	                 SEARCH_ENTER);
	if (!sta) {
		dbg(nas, "No STA struct available");
		return;
	}

	dbg(nas, "%s message from %s", eapol_msg_type_name(eapol->type),
	    ether_etoa((uchar *)&sta->ea, eabuf));
	if (eapol->version < sta->eapol_version) {
		dbg(nas, "EAPOL version %d packet received, current version is %d", eapol->version,
		    sta->eapol_version);
	}

	/* If this is a WPA key pkt then process it accordingly */
	if ((eapol->type == EAPOL_KEY) &&
	    (CHECK_EAPOL_KEY(eapol->body[0]))) {
		/* Expect to do this only for WPA_PSK or for WPA either very
		 * early or after RADIUS acceptance.
		 */
		if ((CHECK_NAS(sta->mode)) &&
		    ((sta->pae.state == AUTHENTICATED) || (sta->pae.state == INITIALIZE))) {
			/* process WPA key pkt */
			if (process_sup_wpa(nas->wpa, eapol, sta)) {
				/* Something wrong in WPA.  Lose this pae. */
				cleanup_sta(nas, sta, DOT11_RC_8021X_AUTH_FAIL, 0);
			}
			return;
		}
	}

	err(nas, "unknown EAPOL type %d", eapol->type);
}
コード例 #3
0
ファイル: nas_radius.c プロジェクト: NieHao/Tomato-RAF
/* Proxy EAP packet from RADIUS server to PAE */
void
radius_dispatch(nas_t *nas, radius_header_t *response)
{
	nas_sta_t *sta = &nas->sta[response->id % MAX_SUPPLICANTS];
	radius_header_t *request;

	int left, type, length = 0, index, authenticated = 0;
	unsigned char buf[16], *cur;
	eapol_header_t eapol;
	eap_header_t *eap = NULL;
	unsigned int vendor, ssnto;
	unsigned char *mppe_send = NULL, *mppe_recv = NULL, *mppe_key;
	struct iovec frags[RADIUS_MAX_ATTRIBUTES];
	int nfrags = 0;
#ifdef BCMDBG
	char eabuf[ETHER_ADDR_STR_LEN];
#endif

	/* The STA could have been toss during the wait. */
	if (!sta->used)
		return;

	request =  sta->pae.radius.request;
	if (!request || request->id != response->id) {
		dbg(nas, "bogus RADIUS packet response->id=%d request->id=%d", response->id,
		    request->id);
		return;
	}

	/* Parse attributes */
	left = ntohs(response->length) - RADIUS_HEADER_LEN;
	cur = response->attributes;
	while (left >= 2) {
		int attribute_error = 0;

		type = *cur++;
		length = *cur++ - 2;
		left -= 2;

		/* Bad attribute length */
		if (length > left) {
			dbg(nas, "bad attribute length %d", length);
			break;
		}

		switch (type) {

		case RD_TP_MESSAGE_AUTHENTICATOR:
			if (length < 16) {
				dbg(nas, "bad signature length %d", length);
				attribute_error = 1;
				break;
			}

			/* Validate HMAC-MD5 checksum */
			memcpy(buf, cur, 16);
			memset(cur, 0, 16);
			memcpy(response->vector, request->vector, 16);

			/* Calculate HMAC-MD5 checksum with request vector and null signature */
			hmac_md5((unsigned char *) response, ntohs(response->length),
			         nas->secret.data, nas->secret.length, cur);
			if ((authenticated = !memcmp(buf, cur, 16)) == 0) {
				dbg(nas, "Invalid signature");
				attribute_error = 1;
			}
			break;

		case RD_TP_STATE:
			/* Preserve server state unmodified */
			sta->pae.radius.state.length = length;
			if (sta->pae.radius.state.data)
				free(sta->pae.radius.state.data);
			sta->pae.radius.state.length = length;
			if (!(sta->pae.radius.state.data = malloc(sta->pae.radius.state.length))) {
				perror("malloc");
				attribute_error = 1;
			} else
				memcpy(sta->pae.radius.state.data, cur,
				       sta->pae.radius.state.length);
			break;

		case RD_TP_EAP_MESSAGE:
			/* Initialize EAPOL header */
			if (!nfrags) {
				memcpy(&eapol.eth.ether_dhost, &sta->ea, ETHER_ADDR_LEN);
				memcpy(&eapol.eth.ether_shost, &nas->ea, ETHER_ADDR_LEN);
#ifdef BCMWPA2
				if (sta->flags & STA_FLAG_PRE_AUTH)
					eapol.eth.ether_type = htons(ETHER_TYPE_802_1X_PREAUTH);
				else
#endif
					eapol.eth.ether_type = htons(ETHER_TYPE_802_1X);
				eapol.version = sta->eapol_version;
				eapol.type = EAP_PACKET;
				eapol.length = htons(0);
				eap = (eap_header_t *) cur;
				frags[nfrags].iov_base = (caddr_t) &eapol;
				frags[nfrags].iov_len = EAPOL_HEADER_LEN;
				nfrags++;
				/* Set up internal flags */
				if (eap->code == EAP_SUCCESS)
					sta->pae.flags |= PAE_FLAG_EAP_SUCCESS;
			}
			/* Gather fragmented EAP messages */
			if (nfrags < ARRAYSIZE(frags)) {
				eapol.length = htons(ntohs(eapol.length) + length);
				frags[nfrags].iov_base = (caddr_t) cur;
				frags[nfrags].iov_len = length;
				nfrags++;
			}
			break;

		case RD_TP_VENDOR_SPECIFIC:
			if (length < 6) {
				dbg(nas, "bad vendor attribute length %d", length);
				attribute_error = 1;
				break;
			}
			memcpy(&vendor, cur, 4);
			vendor = ntohl(vendor);
			cur += 4;
			type = *cur++;
			length = *cur++ - 2;
			left -= 6;

			/* Bad attribute length */
			if (length > left) {
				dbg(nas, "bad vendor attribute length %d", length);
				attribute_error = 1;
				break;
			}

			/* Parse vendor-specific attributes */
			switch (vendor) {

			case RD_VENDOR_MICROSOFT:
				switch (type) {

				case RD_MS_MPPE_SEND:
				case RD_MS_MPPE_RECV:
					if (response->code != RADIUS_ACCESS_ACCEPT) {
						dbg(nas, "ignore MS-MPPE-Key in non"
						    " RADIUS_ACCESS_ACCEPT packet");
						break;
					}

					/* Key length (minus salt) must be a multiple of 16 and
					 * greater than 32
					 */
					if ((length - 2) % 16 || (length - 2) <= 32) {
						dbg(nas, "bad MS-MPPE-Key length %d", length);
						attribute_error = 1;
						break;
					}
					/* Allocate key */
					if (!(mppe_key = malloc(length - 2))) {
						perror("malloc");
						attribute_error = 1;
						break;
					}
					/* Decrypt key */
					memcpy(mppe_key, &cur[2], length - 2);
					mppe_crypt(cur, mppe_key, length - 2,
					           nas->secret.data, nas->secret.length,
					           request->vector, 0);
					/* Set key pointers */
					if (type == RD_MS_MPPE_SEND)
						mppe_send = mppe_key;
					else
						mppe_recv = mppe_key;
					break;
				}
				break;


			default:
				dbg(nas, "unknown vendor attribute = %d", vendor);
				dbg(nas, "    vendor type = %d", type);
				dbg(nas, "    attribute string = %s", cur);
				break;
			}
			break;

		case RD_TP_SESSION_TIMEOUT:
			if (response->code != RADIUS_ACCESS_ACCEPT)
				break;
			if (length < 4) {
				dbg(nas, "bad session timeout attribute length %d", length);
				attribute_error = 1;
				break;
			}
			memcpy(&ssnto, cur, 4);
			sta->pae.ssnto = ntohl(ssnto);
			dbg(nas, "session timeout in %d seconds", sta->pae.ssnto);
			break;

		default:
			/* Ignore all other attributes */
			break;
		}
		/* Don't go on looking if something already went wrong. */
		if (attribute_error)
			goto done;

		left -= length;
		cur += length;
	}

	if (!authenticated && response->code != RADIUS_ACCESS_REJECT) {
		dbg(nas, "missing signature");
		goto done;
	}

	if (eap)
		sta->pae.id = eap->id;

	if (eap &&
	    (eap->code != EAP_SUCCESS || response->code != RADIUS_ACCESS_ACCEPT) &&
	    (eap->code != EAP_FAILURE || response->code != RADIUS_ACCESS_REJECT) &&
	    nfrags) {
#ifdef BCMWPA2
		if (sta->flags & STA_FLAG_PRE_AUTH)
			nas_preauth_send_packet(nas, frags, nfrags);
		else
#endif
			nas_eapol_send_packet(nas, frags, nfrags);
	}

	/* RADIUS event */
	switch (response->code) {

	case RADIUS_ACCESS_ACCEPT:
		/* Check for EAP-Success before allowing complete access */
		if (!(sta->pae.flags & PAE_FLAG_EAP_SUCCESS)) {
			dbg(nas, "Radius success without EAP success?!");
			pae_state(nas, sta, HELD);
			dbg(nas, "deauthenticating %s", ether_etoa((uint8 *)&sta->ea, eabuf));
			nas_deauthorize(nas, &sta->ea);
			goto done;
		}

		dbg(nas, "Access Accept");
		pae_state(nas, sta, AUTHENTICATED);

		/* overwrite session timeout with global setting */
		if (!sta->pae.ssnto || sta->pae.ssnto > nas->ssn_to)
			sta->pae.ssnto = nas->ssn_to;

		/* WPA-mode needs to do the 4-way handshake here instead. */
		if (CHECK_WPA(sta->mode) && mppe_recv) {
			fix_wpa(nas, sta, (char *)&mppe_recv[1], (int)mppe_recv[0]);
			break;
		}

		/* Plump the keys to driver and send them to peer as well */
		if (mppe_recv) {
			/* Cobble a multicast key if there isn't one yet. */
			if (!(nas->flags & NAS_FLAG_GTK_PLUMBED)) {
				nas->wpa->gtk_index = GTK_INDEX_1;
				if (nas->wpa->gtk_len == 0)
					nas->wpa->gtk_len = WEP128_KEY_SIZE;
				nas_rand128(nas->wpa->gtk);
				if (nas_set_key(nas, NULL, nas->wpa->gtk,
				                nas->wpa->gtk_len, nas->wpa->gtk_index,
				                1, 0, 0) < 0) {
					err(nas, "invalid multicast key");
					nas_handle_error(nas, 1);
				}
				nas->flags |= NAS_FLAG_GTK_PLUMBED;
			}
			sta->rc4keysec = -1;
			sta->rc4keyusec = -1;
			/* Send multicast key */
			index = nas->wpa->gtk_index;
			length = nas->wpa->gtk_len;
			if (mppe_send)
				eapol_key(nas, sta, &mppe_send[1], mppe_send[0],
					&mppe_recv[1], mppe_recv[0],
					nas->wpa->gtk, length, index, 0);
			else
				eapol_key(nas, sta, NULL, 0,
					&mppe_recv[1], mppe_recv[0],
					nas->wpa->gtk, length, index, 0);

			/* MS-MPPE-Recv-Key is MS-MPPE-Send-Key on the Suppl */
			index = DOT11_MAX_DEFAULT_KEYS - 1;
			length = WEP128_KEY_SIZE;
			if (nas_set_key(nas, &sta->ea, &mppe_recv[1], length, index, 1, 0, 0) < 0) {
				dbg(nas, "unicast key rejected by driver, assuming too many"
				    " associated STAs");
				cleanup_sta(nas, sta, DOT11_RC_BUSY, 0);
			}
			/* Set unicast key index */
			if (mppe_send)
				eapol_key(nas, sta, &mppe_send[1], mppe_send[0],
					NULL, 0,
					NULL, length, index, 1);
			else
				eapol_key(nas, sta, NULL, 0,
					NULL, 0,
					NULL, length, index, 1);
			dbg(nas, "authorize %s (802.1x)", ether_etoa((uint8 *)&sta->ea, eabuf));
			nas_authorize(nas, &sta->ea);
		}
		break;

	case RADIUS_ACCESS_REJECT:
		dbg(nas, "Access Reject");
		pae_state(nas, sta, HELD);
		dbg(nas, "deauthenticating %s", ether_etoa((uint8 *)&sta->ea, eabuf));
		nas_deauthorize(nas, &sta->ea);
		sta->pae.ssnto = 0;
		break;

	case RADIUS_ACCESS_CHALLENGE:
		dbg(nas, "Access Challenge");
		break;

	default:
		dbg(nas, "unknown RADIUS code %d", response->code);
		break;
	}

done:
	if (mppe_send)
		free(mppe_send);
	if (mppe_recv)
		free(mppe_recv);
	free(request);
	sta->pae.radius.request = NULL;
}
コード例 #4
0
void
driver_message_dispatch(nas_t *nas, bcm_event_t *dpkt)
{
	wl_event_msg_t *event = &(dpkt->event);
	int type = ntohl(event->event_type);
	uint8 *addr = (uint8 *)&(event->addr);
	nas_sta_t *sta;
#ifdef BCMDBG
	char eabuf[ETHER_ADDR_STR_LEN];
#endif

	/* !!!THESE ARE THE MESSAGES WE CARE!!! */
	dbg(nas, "received event of type : %d\n", type);
	switch (type) {
	case WLC_E_LINK:
		/* WLC_E_LINK evnet on WDS is use for trigger nas_start */
		if (nas->flags & NAS_FLAG_WDS)
			return;
		/* authenticator is not interested in LINK event */
		if (nas->flags & NAS_FLAG_AUTHENTICATOR)
			return;
		/* and the supplicant is only interested in link up */
		if (!(ntohs(event->flags) & WLC_EVENT_MSG_LINK)) {
			return;
		}
	case WLC_E_ASSOC_IND:
	case WLC_E_REASSOC_IND:
	case WLC_E_DISASSOC_IND:
	case WLC_E_DEAUTH_IND:
	case WLC_E_MIC_ERROR:
		break;
	default:
		/* quietly discard unwanted events */
		return;
	}

	dbg(nas, "start");

	dbg(nas, "driver %s message received for %s", driver_msg_name(type),
	    ether_etoa(addr, eabuf));

	/* Look for the STA struct, but don't create one if the goal is
	 *  to remove it.
	 */
	sta = lookup_sta(nas, (struct ether_addr *)addr,
		(type == WLC_E_DISASSOC_IND || type == WLC_E_DEAUTH_IND ||
		type == WLC_E_MIC_ERROR) ? SEARCH_ONLY : SEARCH_ENTER);

	switch (type) {

	case WLC_E_LINK:
	case WLC_E_ASSOC_IND:
	case WLC_E_REASSOC_IND:
		if (!(CHECK_NAS(nas->mode))) {
			dbg(nas, "Unexpected driver %s message in mode %d", driver_msg_name(type),
			    nas->mode);
			return;
		}
		if (wpa_driver_assoc_msg(nas->wpa, dpkt, sta) == 0)
			break;

		/* clean-up stuff if there was a problem. */
		if (sta)
			cleanup_sta(nas, sta, 0, 1);

		break;

	case WLC_E_DISASSOC_IND:
	case WLC_E_DEAUTH_IND:
		if (wpa_driver_disassoc_msg(nas->wpa, dpkt, sta) == 0)
			break;

		/* clean-up stuff if there was a problem. */
		if (sta)
			cleanup_sta(nas, sta, 0, 1);

		break;

	case WLC_E_MIC_ERROR:
		if ((ntohs(event->flags) & WLC_EVENT_MSG_GROUP) &&
		    (nas->flags & NAS_FLAG_AUTHENTICATOR)) {
			/* flags - authenticator and supplicant are mutually exclusive. */
			assert(!(nas->flags & NAS_FLAG_SUPPLICANT));
			dbg(nas, "Group addressed MIC error notification received by AP");
			return;
		}

		dbg(nas, "%scast MIC error notification received - %sknown STA",
			((ntohs(event->flags) & WLC_EVENT_MSG_GROUP) ? "multi" : "uni"),
			(!sta ? "un" : ""));

		/* ignore errors from STAs we don't know about. */
		if (sta)
			wpa_mic_error(nas->wpa, sta, TRUE);
		break;

	default:
		dbg(nas, "Tossing unexpected event #%u", type);
	}

	dbg(nas, "done");
}
コード例 #5
0
static void
eapol_dispatch_ex(nas_t *nas, eapol_header_t *eapol, int preauth, int bytes)
{
	nas_sta_t *sta;
	eap_header_t *eap;
#ifdef BCMDBG
	char eabuf[ETHER_ADDR_STR_LEN];
#endif

	/* Validate EAPOL version */
	if (!eapol) {
		dbg(nas, "Missing EAPOL header");
		return;
	}

#ifdef NAS_GTK_PER_STA
	if (ETHER_ISNULLADDR(eapol->eth.ether_shost) && (eapol->type == 0xFF)) {
		wpa_set_gtk_per_sta(nas->wpa, eapol->body[0]);
		return;
	}
#endif

	sta = lookup_sta(nas, (struct ether_addr *) eapol->eth.ether_shost,
	                 SEARCH_ENTER);
	if (!sta) {
		dbg(nas, "no STA struct available");
		return;
	}
	if (sta->pae.state == HELD) {
		dbg(nas, "nothing done since in HELD state");
		return;
	}
	dbg(nas, "%s message from %s", eapol_msg_type_name(eapol->type),
	    ether_etoa((uchar *)&sta->ea, eabuf));
	if (eapol->version < sta->eapol_version) {
		dbg(nas, "EAPOL version %d packet received, current version is %d", eapol->version,
		    sta->eapol_version);
	}

	if (!preauth) {
		/* If this is a WPA key pkt then process it accordingly */
		if ((eapol->type == EAPOL_KEY) &&
		    (CHECK_EAPOL_KEY(eapol->body[0]))) {
			/*
			 * Expect to do this only for WPA_PSK or for WPA either very
			 * early or after RADIUS acceptance.
			 */
			if ((CHECK_NAS(sta->mode)) &&
			    ((sta->pae.state == AUTHENTICATED) ||
			          (sta->pae.state == INITIALIZE))) {
				/* process WPA key pkt */
				if (process_wpa(nas->wpa, eapol, sta)) {
					/* Something wrong in WPA.  Lose this sta. */
					cleanup_sta(nas, sta, DOT11_RC_8021X_AUTH_FAIL, 0);
				}
				return;
			}
			/* return; */
		}

		/* break out if STA is only PSK */
		if (CHECK_PSK(sta->mode))
			return;
		sta->flags &= ~STA_FLAG_PRE_AUTH;
	}
	/*
	* instead of passing the state variable saying whether this data is from a pre auth
	* sta or from a normal  store the variable in the sta struct and
	* would be reset once the message is handled ...  state variable makes sense
	* only for this one message.
	*/
	else {
		sta->flags |= STA_FLAG_PRE_AUTH;
		sta->mode = WPA2;
		sta->eapol_version = WPA2_EAPOL_VERSION;
	}

	/* EAPOL event : Radius support */
	switch (eapol->type) {

	case EAP_PACKET:
		dbg(nas, "EAP Packet.Preauth=%d", preauth);
		if (ntohs(eapol->length) >= (EAP_HEADER_LEN + 1)) {
			eap = (eap_header_t *) eapol->body;
			dbg(nas, "STA State=%d EAP Packet Type=%d Id=%d code=%d",
			    sta->pae.state, eap->type, eap->id, eap->code);

			switch (eap->type) {

			case EAP_IDENTITY:
				/* Bogus packet */
				if (eap->id != sta->pae.id ||
					eap->code != EAP_RESPONSE) {
					dbg(nas, "bogus EAP packet id %d code %d, expected %d",
					    eap->id, eap->code, sta->pae.id);
					break;
				}

				if (sta->pae.state == AUTHENTICATING) {
					dbg(nas, "NAS currently authenticating STA. "
					    "Tossing packet id=%d code=%d.",
					    eap->id, eap->code);

					break;
				}

				/*
				 * Do eap length checking to prevent attacker responds to
				 * an EAP Identity Request with an EAP Identity Response,
				 * whose Message Length value is larger than the actual
				 * EAP packet, nas sends out a Radius message with more
				 * data than contained in the EAP Identity Response.  That
				 * cause nas over-reads the heap memory when forming
				 * the Radius message. An attack may result in exposure
				 * of sensitive data from the heap or may allow to cause
				 * a denial-of-service.
				 */
				if (ntohs(eap->length) > bytes - EAPOL_HEADER_LEN) {
					dbg(nas, "Invalid EAP length %d, received %d.",
					    ntohs(eap->length), bytes - EAPOL_HEADER_LEN);
					break;
				}

				/* Record identity */
				if (ntohs(eap->length) > (EAP_HEADER_LEN + 1)) {
					if (sta->pae.radius.username.data)
						free(sta->pae.radius.username.data);
					sta->pae.radius.username.length = ntohs(eap->length) -
					        EAP_HEADER_LEN - 1;
					if (!(sta->pae.radius.username.data =
					      malloc(sta->pae.radius.username.length))) {
						perror("malloc");
						return;
					} else
						memcpy(sta->pae.radius.username.data, eap->data,
						       sta->pae.radius.username.length);
				}
				pae_state(nas, sta, AUTHENTICATING);
				/* Fall through */

			default:
				/* Bogus packet */

				/* Forward to authentication server */
				RADIUS_FORWARD(nas, sta, eap);
				break;
			}
		}
		break;

	case EAPOL_START:
		dbg(nas, "Start");
		sta->pae.id = 0;	/* reset counter  */
		switch (sta->pae.state) {
		case AUTHENTICATING:
			pae_state(nas, sta, ABORTING);
			/* Fall through */

		default:
			pae_state(nas, sta, CONNECTING);
			break;
		}
		sta->suppl.state = sta->suppl.retry_state = WPA_PTKSTART;
		break;

	case EAPOL_LOGOFF:
		dbg(nas, "Logoff");
		switch (sta->pae.state) {
		default:
			dbg(nas, "Unexpected pae state %d", sta->pae.state);
			/* Fall through */
		case AUTHENTICATING:
			pae_state(nas, sta, ABORTING);
			/* Fall through */

		case CONNECTING:
		case AUTHENTICATED:
			pae_state(nas, sta, DISCONNECTED);
			dbg(nas, "deauthenticating %s", ether_etoa((uchar *)&sta->ea, eabuf));
			nas_deauthorize(nas, &sta->ea);
			break;
		}
		break;

	case EAPOL_KEY:
		dbg(nas, "Key");
		break;

	case EAPOL_ASF:
		dbg(nas, "Encapsulated ASF Alert");
		break;

	default:
		dbg(nas, "unknown EAPOL type %d", eapol->type);
		break;
	}
}