1189251Ssam/*
2189251Ssam * EAP peer method: EAP-FAST PAC file processing
3189251Ssam * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi>
4189251Ssam *
5252726Srpaulo * This software may be distributed under the terms of the BSD license.
6252726Srpaulo * See README for more details.
7189251Ssam */
8189251Ssam
9189251Ssam#include "includes.h"
10189251Ssam
11189251Ssam#include "common.h"
12189251Ssam#include "eap_config.h"
13189251Ssam#include "eap_i.h"
14189251Ssam#include "eap_fast_pac.h"
15189251Ssam
16189251Ssam/* TODO: encrypt PAC-Key in the PAC file */
17189251Ssam
18189251Ssam
19189251Ssam/* Text data format */
20189251Ssamstatic const char *pac_file_hdr =
21189251Ssam	"wpa_supplicant EAP-FAST PAC file - version 1";
22189251Ssam
23189251Ssam/*
24189251Ssam * Binary data format
25189251Ssam * 4-octet magic value: 6A E4 92 0C
26189251Ssam * 2-octet version (big endian)
27189251Ssam * <version specific data>
28189251Ssam *
29189251Ssam * version=0:
30189251Ssam * Sequence of PAC entries:
31189251Ssam *   2-octet PAC-Type (big endian)
32189251Ssam *   32-octet PAC-Key
33189251Ssam *   2-octet PAC-Opaque length (big endian)
34189251Ssam *   <variable len> PAC-Opaque data (length bytes)
35189251Ssam *   2-octet PAC-Info length (big endian)
36189251Ssam *   <variable len> PAC-Info data (length bytes)
37189251Ssam */
38189251Ssam
39189251Ssam#define EAP_FAST_PAC_BINARY_MAGIC 0x6ae4920c
40189251Ssam#define EAP_FAST_PAC_BINARY_FORMAT_VERSION 0
41189251Ssam
42189251Ssam
43189251Ssam/**
44189251Ssam * eap_fast_free_pac - Free PAC data
45189251Ssam * @pac: Pointer to the PAC entry
46189251Ssam *
47189251Ssam * Note that the PAC entry must not be in a list since this function does not
48189251Ssam * remove the list links.
49189251Ssam */
50189251Ssamvoid eap_fast_free_pac(struct eap_fast_pac *pac)
51189251Ssam{
52189251Ssam	os_free(pac->pac_opaque);
53189251Ssam	os_free(pac->pac_info);
54189251Ssam	os_free(pac->a_id);
55189251Ssam	os_free(pac->i_id);
56189251Ssam	os_free(pac->a_id_info);
57189251Ssam	os_free(pac);
58189251Ssam}
59189251Ssam
60189251Ssam
61189251Ssam/**
62189251Ssam * eap_fast_get_pac - Get a PAC entry based on A-ID
63189251Ssam * @pac_root: Pointer to root of the PAC list
64189251Ssam * @a_id: A-ID to search for
65189251Ssam * @a_id_len: Length of A-ID
66189251Ssam * @pac_type: PAC-Type to search for
67189251Ssam * Returns: Pointer to the PAC entry, or %NULL if A-ID not found
68189251Ssam */
69189251Ssamstruct eap_fast_pac * eap_fast_get_pac(struct eap_fast_pac *pac_root,
70189251Ssam				       const u8 *a_id, size_t a_id_len,
71189251Ssam				       u16 pac_type)
72189251Ssam{
73189251Ssam	struct eap_fast_pac *pac = pac_root;
74189251Ssam
75189251Ssam	while (pac) {
76189251Ssam		if (pac->pac_type == pac_type && pac->a_id_len == a_id_len &&
77189251Ssam		    os_memcmp(pac->a_id, a_id, a_id_len) == 0) {
78189251Ssam			return pac;
79189251Ssam		}
80189251Ssam		pac = pac->next;
81189251Ssam	}
82189251Ssam	return NULL;
83189251Ssam}
84189251Ssam
85189251Ssam
86189251Ssamstatic void eap_fast_remove_pac(struct eap_fast_pac **pac_root,
87189251Ssam				struct eap_fast_pac **pac_current,
88189251Ssam				const u8 *a_id, size_t a_id_len, u16 pac_type)
89189251Ssam{
90189251Ssam	struct eap_fast_pac *pac, *prev;
91189251Ssam
92189251Ssam	pac = *pac_root;
93189251Ssam	prev = NULL;
94189251Ssam
95189251Ssam	while (pac) {
96189251Ssam		if (pac->pac_type == pac_type && pac->a_id_len == a_id_len &&
97189251Ssam		    os_memcmp(pac->a_id, a_id, a_id_len) == 0) {
98189251Ssam			if (prev == NULL)
99189251Ssam				*pac_root = pac->next;
100189251Ssam			else
101189251Ssam				prev->next = pac->next;
102189251Ssam			if (*pac_current == pac)
103189251Ssam				*pac_current = NULL;
104189251Ssam			eap_fast_free_pac(pac);
105189251Ssam			break;
106189251Ssam		}
107189251Ssam		prev = pac;
108189251Ssam		pac = pac->next;
109189251Ssam	}
110189251Ssam}
111189251Ssam
112189251Ssam
113189251Ssamstatic int eap_fast_copy_buf(u8 **dst, size_t *dst_len,
114189251Ssam			     const u8 *src, size_t src_len)
115189251Ssam{
116189251Ssam	if (src) {
117189251Ssam		*dst = os_malloc(src_len);
118189251Ssam		if (*dst == NULL)
119189251Ssam			return -1;
120189251Ssam		os_memcpy(*dst, src, src_len);
121189251Ssam		*dst_len = src_len;
122189251Ssam	}
123189251Ssam	return 0;
124189251Ssam}
125189251Ssam
126189251Ssam
127189251Ssam/**
128189251Ssam * eap_fast_add_pac - Add a copy of a PAC entry to a list
129189251Ssam * @pac_root: Pointer to PAC list root pointer
130189251Ssam * @pac_current: Pointer to the current PAC pointer
131189251Ssam * @entry: New entry to clone and add to the list
132189251Ssam * Returns: 0 on success, -1 on failure
133189251Ssam *
134189251Ssam * This function makes a clone of the given PAC entry and adds this copied
135189251Ssam * entry to the list (pac_root). If an old entry for the same A-ID is found,
136189251Ssam * it will be removed from the PAC list and in this case, pac_current entry
137189251Ssam * is set to %NULL if it was the removed entry.
138189251Ssam */
139189251Ssamint eap_fast_add_pac(struct eap_fast_pac **pac_root,
140189251Ssam		     struct eap_fast_pac **pac_current,
141189251Ssam		     struct eap_fast_pac *entry)
142189251Ssam{
143189251Ssam	struct eap_fast_pac *pac;
144189251Ssam
145189251Ssam	if (entry == NULL || entry->a_id == NULL)
146189251Ssam		return -1;
147189251Ssam
148189251Ssam	/* Remove a possible old entry for the matching A-ID. */
149189251Ssam	eap_fast_remove_pac(pac_root, pac_current,
150189251Ssam			    entry->a_id, entry->a_id_len, entry->pac_type);
151189251Ssam
152189251Ssam	/* Allocate a new entry and add it to the list of PACs. */
153189251Ssam	pac = os_zalloc(sizeof(*pac));
154189251Ssam	if (pac == NULL)
155189251Ssam		return -1;
156189251Ssam
157189251Ssam	pac->pac_type = entry->pac_type;
158189251Ssam	os_memcpy(pac->pac_key, entry->pac_key, EAP_FAST_PAC_KEY_LEN);
159189251Ssam	if (eap_fast_copy_buf(&pac->pac_opaque, &pac->pac_opaque_len,
160189251Ssam			      entry->pac_opaque, entry->pac_opaque_len) < 0 ||
161189251Ssam	    eap_fast_copy_buf(&pac->pac_info, &pac->pac_info_len,
162189251Ssam			      entry->pac_info, entry->pac_info_len) < 0 ||
163189251Ssam	    eap_fast_copy_buf(&pac->a_id, &pac->a_id_len,
164189251Ssam			      entry->a_id, entry->a_id_len) < 0 ||
165189251Ssam	    eap_fast_copy_buf(&pac->i_id, &pac->i_id_len,
166189251Ssam			      entry->i_id, entry->i_id_len) < 0 ||
167189251Ssam	    eap_fast_copy_buf(&pac->a_id_info, &pac->a_id_info_len,
168189251Ssam			      entry->a_id_info, entry->a_id_info_len) < 0) {
169189251Ssam		eap_fast_free_pac(pac);
170189251Ssam		return -1;
171189251Ssam	}
172189251Ssam
173189251Ssam	pac->next = *pac_root;
174189251Ssam	*pac_root = pac;
175189251Ssam
176189251Ssam	return 0;
177189251Ssam}
178189251Ssam
179189251Ssam
180189251Ssamstruct eap_fast_read_ctx {
181189251Ssam	FILE *f;
182189251Ssam	const char *pos;
183189251Ssam	const char *end;
184189251Ssam	int line;
185189251Ssam	char *buf;
186189251Ssam	size_t buf_len;
187189251Ssam};
188189251Ssam
189189251Ssamstatic int eap_fast_read_line(struct eap_fast_read_ctx *rc, char **value)
190189251Ssam{
191189251Ssam	char *pos;
192189251Ssam
193189251Ssam	rc->line++;
194189251Ssam	if (rc->f) {
195189251Ssam		if (fgets(rc->buf, rc->buf_len, rc->f) == NULL)
196189251Ssam			return -1;
197189251Ssam	} else {
198189251Ssam		const char *l_end;
199189251Ssam		size_t len;
200189251Ssam		if (rc->pos >= rc->end)
201189251Ssam			return -1;
202189251Ssam		l_end = rc->pos;
203189251Ssam		while (l_end < rc->end && *l_end != '\n')
204189251Ssam			l_end++;
205189251Ssam		len = l_end - rc->pos;
206189251Ssam		if (len >= rc->buf_len)
207189251Ssam			len = rc->buf_len - 1;
208189251Ssam		os_memcpy(rc->buf, rc->pos, len);
209189251Ssam		rc->buf[len] = '\0';
210189251Ssam		rc->pos = l_end + 1;
211189251Ssam	}
212189251Ssam
213189251Ssam	rc->buf[rc->buf_len - 1] = '\0';
214189251Ssam	pos = rc->buf;
215189251Ssam	while (*pos != '\0') {
216189251Ssam		if (*pos == '\n' || *pos == '\r') {
217189251Ssam			*pos = '\0';
218189251Ssam			break;
219189251Ssam		}
220189251Ssam		pos++;
221189251Ssam	}
222189251Ssam
223189251Ssam	pos = os_strchr(rc->buf, '=');
224189251Ssam	if (pos)
225189251Ssam		*pos++ = '\0';
226189251Ssam	*value = pos;
227189251Ssam
228189251Ssam	return 0;
229189251Ssam}
230189251Ssam
231189251Ssam
232189251Ssamstatic u8 * eap_fast_parse_hex(const char *value, size_t *len)
233189251Ssam{
234189251Ssam	int hlen;
235189251Ssam	u8 *buf;
236189251Ssam
237189251Ssam	if (value == NULL)
238189251Ssam		return NULL;
239189251Ssam	hlen = os_strlen(value);
240189251Ssam	if (hlen & 1)
241189251Ssam		return NULL;
242189251Ssam	*len = hlen / 2;
243189251Ssam	buf = os_malloc(*len);
244189251Ssam	if (buf == NULL)
245189251Ssam		return NULL;
246189251Ssam	if (hexstr2bin(value, buf, *len)) {
247189251Ssam		os_free(buf);
248189251Ssam		return NULL;
249189251Ssam	}
250189251Ssam	return buf;
251189251Ssam}
252189251Ssam
253189251Ssam
254189251Ssamstatic int eap_fast_init_pac_data(struct eap_sm *sm, const char *pac_file,
255189251Ssam				  struct eap_fast_read_ctx *rc)
256189251Ssam{
257189251Ssam	os_memset(rc, 0, sizeof(*rc));
258189251Ssam
259189251Ssam	rc->buf_len = 2048;
260189251Ssam	rc->buf = os_malloc(rc->buf_len);
261189251Ssam	if (rc->buf == NULL)
262189251Ssam		return -1;
263189251Ssam
264189251Ssam	if (os_strncmp(pac_file, "blob://", 7) == 0) {
265189251Ssam		const struct wpa_config_blob *blob;
266189251Ssam		blob = eap_get_config_blob(sm, pac_file + 7);
267189251Ssam		if (blob == NULL) {
268189251Ssam			wpa_printf(MSG_INFO, "EAP-FAST: No PAC blob '%s' - "
269189251Ssam				   "assume no PAC entries have been "
270189251Ssam				   "provisioned", pac_file + 7);
271189251Ssam			os_free(rc->buf);
272189251Ssam			return -1;
273189251Ssam		}
274189251Ssam		rc->pos = (char *) blob->data;
275189251Ssam		rc->end = (char *) blob->data + blob->len;
276189251Ssam	} else {
277189251Ssam		rc->f = fopen(pac_file, "rb");
278189251Ssam		if (rc->f == NULL) {
279189251Ssam			wpa_printf(MSG_INFO, "EAP-FAST: No PAC file '%s' - "
280189251Ssam				   "assume no PAC entries have been "
281189251Ssam				   "provisioned", pac_file);
282189251Ssam			os_free(rc->buf);
283189251Ssam			return -1;
284189251Ssam		}
285189251Ssam	}
286189251Ssam
287189251Ssam	return 0;
288189251Ssam}
289189251Ssam
290189251Ssam
291189251Ssamstatic void eap_fast_deinit_pac_data(struct eap_fast_read_ctx *rc)
292189251Ssam{
293189251Ssam	os_free(rc->buf);
294189251Ssam	if (rc->f)
295189251Ssam		fclose(rc->f);
296189251Ssam}
297189251Ssam
298189251Ssam
299189251Ssamstatic const char * eap_fast_parse_start(struct eap_fast_pac **pac)
300189251Ssam{
301189251Ssam	if (*pac)
302189251Ssam		return "START line without END";
303189251Ssam
304189251Ssam	*pac = os_zalloc(sizeof(struct eap_fast_pac));
305189251Ssam	if (*pac == NULL)
306189251Ssam		return "No memory for PAC entry";
307189251Ssam	(*pac)->pac_type = PAC_TYPE_TUNNEL_PAC;
308189251Ssam	return NULL;
309189251Ssam}
310189251Ssam
311189251Ssam
312189251Ssamstatic const char * eap_fast_parse_end(struct eap_fast_pac **pac_root,
313189251Ssam				       struct eap_fast_pac **pac)
314189251Ssam{
315189251Ssam	if (*pac == NULL)
316189251Ssam		return "END line without START";
317189251Ssam	if (*pac_root) {
318189251Ssam		struct eap_fast_pac *end = *pac_root;
319189251Ssam		while (end->next)
320189251Ssam			end = end->next;
321189251Ssam		end->next = *pac;
322189251Ssam	} else
323189251Ssam		*pac_root = *pac;
324189251Ssam
325189251Ssam	*pac = NULL;
326189251Ssam	return NULL;
327189251Ssam}
328189251Ssam
329189251Ssam
330189251Ssamstatic const char * eap_fast_parse_pac_type(struct eap_fast_pac *pac,
331189251Ssam					    char *pos)
332189251Ssam{
333189251Ssam	pac->pac_type = atoi(pos);
334189251Ssam	if (pac->pac_type != PAC_TYPE_TUNNEL_PAC &&
335189251Ssam	    pac->pac_type != PAC_TYPE_USER_AUTHORIZATION &&
336189251Ssam	    pac->pac_type != PAC_TYPE_MACHINE_AUTHENTICATION)
337189251Ssam		return "Unrecognized PAC-Type";
338189251Ssam
339189251Ssam	return NULL;
340189251Ssam}
341189251Ssam
342189251Ssam
343189251Ssamstatic const char * eap_fast_parse_pac_key(struct eap_fast_pac *pac, char *pos)
344189251Ssam{
345189251Ssam	u8 *key;
346189251Ssam	size_t key_len;
347189251Ssam
348189251Ssam	key = eap_fast_parse_hex(pos, &key_len);
349189251Ssam	if (key == NULL || key_len != EAP_FAST_PAC_KEY_LEN) {
350189251Ssam		os_free(key);
351189251Ssam		return "Invalid PAC-Key";
352189251Ssam	}
353189251Ssam
354189251Ssam	os_memcpy(pac->pac_key, key, EAP_FAST_PAC_KEY_LEN);
355189251Ssam	os_free(key);
356189251Ssam
357189251Ssam	return NULL;
358189251Ssam}
359189251Ssam
360189251Ssam
361189251Ssamstatic const char * eap_fast_parse_pac_opaque(struct eap_fast_pac *pac,
362189251Ssam					      char *pos)
363189251Ssam{
364189251Ssam	os_free(pac->pac_opaque);
365189251Ssam	pac->pac_opaque = eap_fast_parse_hex(pos, &pac->pac_opaque_len);
366189251Ssam	if (pac->pac_opaque == NULL)
367189251Ssam		return "Invalid PAC-Opaque";
368189251Ssam	return NULL;
369189251Ssam}
370189251Ssam
371189251Ssam
372189251Ssamstatic const char * eap_fast_parse_a_id(struct eap_fast_pac *pac, char *pos)
373189251Ssam{
374189251Ssam	os_free(pac->a_id);
375189251Ssam	pac->a_id = eap_fast_parse_hex(pos, &pac->a_id_len);
376189251Ssam	if (pac->a_id == NULL)
377189251Ssam		return "Invalid A-ID";
378189251Ssam	return NULL;
379189251Ssam}
380189251Ssam
381189251Ssam
382189251Ssamstatic const char * eap_fast_parse_i_id(struct eap_fast_pac *pac, char *pos)
383189251Ssam{
384189251Ssam	os_free(pac->i_id);
385189251Ssam	pac->i_id = eap_fast_parse_hex(pos, &pac->i_id_len);
386189251Ssam	if (pac->i_id == NULL)
387189251Ssam		return "Invalid I-ID";
388189251Ssam	return NULL;
389189251Ssam}
390189251Ssam
391189251Ssam
392189251Ssamstatic const char * eap_fast_parse_a_id_info(struct eap_fast_pac *pac,
393189251Ssam					     char *pos)
394189251Ssam{
395189251Ssam	os_free(pac->a_id_info);
396189251Ssam	pac->a_id_info = eap_fast_parse_hex(pos, &pac->a_id_info_len);
397189251Ssam	if (pac->a_id_info == NULL)
398189251Ssam		return "Invalid A-ID-Info";
399189251Ssam	return NULL;
400189251Ssam}
401189251Ssam
402189251Ssam
403189251Ssam/**
404189251Ssam * eap_fast_load_pac - Load PAC entries (text format)
405189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
406189251Ssam * @pac_root: Pointer to root of the PAC list (to be filled)
407189251Ssam * @pac_file: Name of the PAC file/blob to load
408189251Ssam * Returns: 0 on success, -1 on failure
409189251Ssam */
410189251Ssamint eap_fast_load_pac(struct eap_sm *sm, struct eap_fast_pac **pac_root,
411189251Ssam		      const char *pac_file)
412189251Ssam{
413189251Ssam	struct eap_fast_read_ctx rc;
414189251Ssam	struct eap_fast_pac *pac = NULL;
415189251Ssam	int count = 0;
416189251Ssam	char *pos;
417189251Ssam	const char *err = NULL;
418189251Ssam
419189251Ssam	if (pac_file == NULL)
420189251Ssam		return -1;
421189251Ssam
422189251Ssam	if (eap_fast_init_pac_data(sm, pac_file, &rc) < 0)
423189251Ssam		return 0;
424189251Ssam
425252726Srpaulo	if (eap_fast_read_line(&rc, &pos) < 0) {
426252726Srpaulo		/* empty file - assume it is fine to overwrite */
427252726Srpaulo		eap_fast_deinit_pac_data(&rc);
428252726Srpaulo		return 0;
429252726Srpaulo	}
430252726Srpaulo	if (os_strcmp(pac_file_hdr, rc.buf) != 0)
431189251Ssam		err = "Unrecognized header line";
432189251Ssam
433189251Ssam	while (!err && eap_fast_read_line(&rc, &pos) == 0) {
434189251Ssam		if (os_strcmp(rc.buf, "START") == 0)
435189251Ssam			err = eap_fast_parse_start(&pac);
436189251Ssam		else if (os_strcmp(rc.buf, "END") == 0) {
437189251Ssam			err = eap_fast_parse_end(pac_root, &pac);
438189251Ssam			count++;
439189251Ssam		} else if (!pac)
440189251Ssam			err = "Unexpected line outside START/END block";
441189251Ssam		else if (os_strcmp(rc.buf, "PAC-Type") == 0)
442189251Ssam			err = eap_fast_parse_pac_type(pac, pos);
443189251Ssam		else if (os_strcmp(rc.buf, "PAC-Key") == 0)
444189251Ssam			err = eap_fast_parse_pac_key(pac, pos);
445189251Ssam		else if (os_strcmp(rc.buf, "PAC-Opaque") == 0)
446189251Ssam			err = eap_fast_parse_pac_opaque(pac, pos);
447189251Ssam		else if (os_strcmp(rc.buf, "A-ID") == 0)
448189251Ssam			err = eap_fast_parse_a_id(pac, pos);
449189251Ssam		else if (os_strcmp(rc.buf, "I-ID") == 0)
450189251Ssam			err = eap_fast_parse_i_id(pac, pos);
451189251Ssam		else if (os_strcmp(rc.buf, "A-ID-Info") == 0)
452189251Ssam			err = eap_fast_parse_a_id_info(pac, pos);
453189251Ssam	}
454189251Ssam
455189251Ssam	if (pac) {
456189251Ssam		err = "PAC block not terminated with END";
457189251Ssam		eap_fast_free_pac(pac);
458189251Ssam	}
459189251Ssam
460189251Ssam	eap_fast_deinit_pac_data(&rc);
461189251Ssam
462189251Ssam	if (err) {
463189251Ssam		wpa_printf(MSG_INFO, "EAP-FAST: %s in '%s:%d'",
464189251Ssam			   err, pac_file, rc.line);
465189251Ssam		return -1;
466189251Ssam	}
467189251Ssam
468189251Ssam	wpa_printf(MSG_DEBUG, "EAP-FAST: Read %d PAC entries from '%s'",
469189251Ssam		   count, pac_file);
470189251Ssam
471189251Ssam	return 0;
472189251Ssam}
473189251Ssam
474189251Ssam
475189251Ssamstatic void eap_fast_write(char **buf, char **pos, size_t *buf_len,
476189251Ssam			   const char *field, const u8 *data,
477189251Ssam			   size_t len, int txt)
478189251Ssam{
479189251Ssam	size_t i, need;
480189251Ssam	int ret;
481214734Srpaulo	char *end;
482189251Ssam
483214734Srpaulo	if (data == NULL || buf == NULL || *buf == NULL ||
484214734Srpaulo	    pos == NULL || *pos == NULL || *pos < *buf)
485189251Ssam		return;
486189251Ssam
487189251Ssam	need = os_strlen(field) + len * 2 + 30;
488189251Ssam	if (txt)
489189251Ssam		need += os_strlen(field) + len + 20;
490189251Ssam
491189251Ssam	if (*pos - *buf + need > *buf_len) {
492189251Ssam		char *nbuf = os_realloc(*buf, *buf_len + need);
493189251Ssam		if (nbuf == NULL) {
494189251Ssam			os_free(*buf);
495189251Ssam			*buf = NULL;
496189251Ssam			return;
497189251Ssam		}
498252726Srpaulo		*pos = nbuf + (*pos - *buf);
499189251Ssam		*buf = nbuf;
500189251Ssam		*buf_len += need;
501189251Ssam	}
502214734Srpaulo	end = *buf + *buf_len;
503189251Ssam
504214734Srpaulo	ret = os_snprintf(*pos, end - *pos, "%s=", field);
505214734Srpaulo	if (ret < 0 || ret >= end - *pos)
506189251Ssam		return;
507189251Ssam	*pos += ret;
508214734Srpaulo	*pos += wpa_snprintf_hex(*pos, end - *pos, data, len);
509214734Srpaulo	ret = os_snprintf(*pos, end - *pos, "\n");
510214734Srpaulo	if (ret < 0 || ret >= end - *pos)
511189251Ssam		return;
512189251Ssam	*pos += ret;
513189251Ssam
514189251Ssam	if (txt) {
515214734Srpaulo		ret = os_snprintf(*pos, end - *pos, "%s-txt=", field);
516214734Srpaulo		if (ret < 0 || ret >= end - *pos)
517189251Ssam			return;
518189251Ssam		*pos += ret;
519189251Ssam		for (i = 0; i < len; i++) {
520214734Srpaulo			ret = os_snprintf(*pos, end - *pos, "%c", data[i]);
521214734Srpaulo			if (ret < 0 || ret >= end - *pos)
522189251Ssam				return;
523189251Ssam			*pos += ret;
524189251Ssam		}
525214734Srpaulo		ret = os_snprintf(*pos, end - *pos, "\n");
526214734Srpaulo		if (ret < 0 || ret >= end - *pos)
527189251Ssam			return;
528189251Ssam		*pos += ret;
529189251Ssam	}
530189251Ssam}
531189251Ssam
532189251Ssam
533189251Ssamstatic int eap_fast_write_pac(struct eap_sm *sm, const char *pac_file,
534189251Ssam			      char *buf, size_t len)
535189251Ssam{
536189251Ssam	if (os_strncmp(pac_file, "blob://", 7) == 0) {
537189251Ssam		struct wpa_config_blob *blob;
538189251Ssam		blob = os_zalloc(sizeof(*blob));
539189251Ssam		if (blob == NULL)
540189251Ssam			return -1;
541189251Ssam		blob->data = (u8 *) buf;
542189251Ssam		blob->len = len;
543189251Ssam		buf = NULL;
544189251Ssam		blob->name = os_strdup(pac_file + 7);
545189251Ssam		if (blob->name == NULL) {
546189251Ssam			os_free(blob);
547189251Ssam			return -1;
548189251Ssam		}
549189251Ssam		eap_set_config_blob(sm, blob);
550189251Ssam	} else {
551189251Ssam		FILE *f;
552189251Ssam		f = fopen(pac_file, "wb");
553189251Ssam		if (f == NULL) {
554189251Ssam			wpa_printf(MSG_INFO, "EAP-FAST: Failed to open PAC "
555189251Ssam				   "file '%s' for writing", pac_file);
556189251Ssam			return -1;
557189251Ssam		}
558189251Ssam		if (fwrite(buf, 1, len, f) != len) {
559189251Ssam			wpa_printf(MSG_INFO, "EAP-FAST: Failed to write all "
560189251Ssam				   "PACs into '%s'", pac_file);
561189251Ssam			fclose(f);
562189251Ssam			return -1;
563189251Ssam		}
564189251Ssam		os_free(buf);
565189251Ssam		fclose(f);
566189251Ssam	}
567189251Ssam
568189251Ssam	return 0;
569189251Ssam}
570189251Ssam
571189251Ssam
572189251Ssamstatic int eap_fast_add_pac_data(struct eap_fast_pac *pac, char **buf,
573189251Ssam				 char **pos, size_t *buf_len)
574189251Ssam{
575189251Ssam	int ret;
576189251Ssam
577189251Ssam	ret = os_snprintf(*pos, *buf + *buf_len - *pos,
578189251Ssam			  "START\nPAC-Type=%d\n", pac->pac_type);
579189251Ssam	if (ret < 0 || ret >= *buf + *buf_len - *pos)
580189251Ssam		return -1;
581189251Ssam
582189251Ssam	*pos += ret;
583189251Ssam	eap_fast_write(buf, pos, buf_len, "PAC-Key",
584189251Ssam		       pac->pac_key, EAP_FAST_PAC_KEY_LEN, 0);
585189251Ssam	eap_fast_write(buf, pos, buf_len, "PAC-Opaque",
586189251Ssam		       pac->pac_opaque, pac->pac_opaque_len, 0);
587189251Ssam	eap_fast_write(buf, pos, buf_len, "PAC-Info",
588189251Ssam		       pac->pac_info, pac->pac_info_len, 0);
589189251Ssam	eap_fast_write(buf, pos, buf_len, "A-ID",
590189251Ssam		       pac->a_id, pac->a_id_len, 0);
591189251Ssam	eap_fast_write(buf, pos, buf_len, "I-ID",
592189251Ssam		       pac->i_id, pac->i_id_len, 1);
593189251Ssam	eap_fast_write(buf, pos, buf_len, "A-ID-Info",
594189251Ssam		       pac->a_id_info, pac->a_id_info_len, 1);
595189251Ssam	if (*buf == NULL) {
596189251Ssam		wpa_printf(MSG_DEBUG, "EAP-FAST: No memory for PAC "
597189251Ssam			   "data");
598189251Ssam		return -1;
599189251Ssam	}
600189251Ssam	ret = os_snprintf(*pos, *buf + *buf_len - *pos, "END\n");
601189251Ssam	if (ret < 0 || ret >= *buf + *buf_len - *pos)
602189251Ssam		return -1;
603189251Ssam	*pos += ret;
604189251Ssam
605189251Ssam	return 0;
606189251Ssam}
607189251Ssam
608189251Ssam
609189251Ssam/**
610189251Ssam * eap_fast_save_pac - Save PAC entries (text format)
611189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
612189251Ssam * @pac_root: Root of the PAC list
613189251Ssam * @pac_file: Name of the PAC file/blob
614189251Ssam * Returns: 0 on success, -1 on failure
615189251Ssam */
616189251Ssamint eap_fast_save_pac(struct eap_sm *sm, struct eap_fast_pac *pac_root,
617189251Ssam		      const char *pac_file)
618189251Ssam{
619189251Ssam	struct eap_fast_pac *pac;
620189251Ssam	int ret, count = 0;
621189251Ssam	char *buf, *pos;
622189251Ssam	size_t buf_len;
623189251Ssam
624189251Ssam	if (pac_file == NULL)
625189251Ssam		return -1;
626189251Ssam
627189251Ssam	buf_len = 1024;
628189251Ssam	pos = buf = os_malloc(buf_len);
629189251Ssam	if (buf == NULL)
630189251Ssam		return -1;
631189251Ssam
632189251Ssam	ret = os_snprintf(pos, buf + buf_len - pos, "%s\n", pac_file_hdr);
633189251Ssam	if (ret < 0 || ret >= buf + buf_len - pos) {
634189251Ssam		os_free(buf);
635189251Ssam		return -1;
636189251Ssam	}
637189251Ssam	pos += ret;
638189251Ssam
639189251Ssam	pac = pac_root;
640189251Ssam	while (pac) {
641189251Ssam		if (eap_fast_add_pac_data(pac, &buf, &pos, &buf_len)) {
642189251Ssam			os_free(buf);
643189251Ssam			return -1;
644189251Ssam		}
645189251Ssam		count++;
646189251Ssam		pac = pac->next;
647189251Ssam	}
648189251Ssam
649189251Ssam	if (eap_fast_write_pac(sm, pac_file, buf, pos - buf)) {
650189251Ssam		os_free(buf);
651189251Ssam		return -1;
652189251Ssam	}
653189251Ssam
654189251Ssam	wpa_printf(MSG_DEBUG, "EAP-FAST: Wrote %d PAC entries into '%s'",
655189251Ssam		   count, pac_file);
656189251Ssam
657189251Ssam	return 0;
658189251Ssam}
659189251Ssam
660189251Ssam
661189251Ssam/**
662189251Ssam * eap_fast_pac_list_truncate - Truncate a PAC list to the given length
663189251Ssam * @pac_root: Root of the PAC list
664189251Ssam * @max_len: Maximum length of the list (>= 1)
665189251Ssam * Returns: Number of PAC entries removed
666189251Ssam */
667189251Ssamsize_t eap_fast_pac_list_truncate(struct eap_fast_pac *pac_root,
668189251Ssam				  size_t max_len)
669189251Ssam{
670189251Ssam	struct eap_fast_pac *pac, *prev;
671189251Ssam	size_t count;
672189251Ssam
673189251Ssam	pac = pac_root;
674189251Ssam	prev = NULL;
675189251Ssam	count = 0;
676189251Ssam
677189251Ssam	while (pac) {
678189251Ssam		count++;
679189251Ssam		if (count > max_len)
680189251Ssam			break;
681189251Ssam		prev = pac;
682189251Ssam		pac = pac->next;
683189251Ssam	}
684189251Ssam
685189251Ssam	if (count <= max_len || prev == NULL)
686189251Ssam		return 0;
687189251Ssam
688189251Ssam	count = 0;
689189251Ssam	prev->next = NULL;
690189251Ssam
691189251Ssam	while (pac) {
692189251Ssam		prev = pac;
693189251Ssam		pac = pac->next;
694189251Ssam		eap_fast_free_pac(prev);
695189251Ssam		count++;
696189251Ssam	}
697189251Ssam
698189251Ssam	return count;
699189251Ssam}
700189251Ssam
701189251Ssam
702189251Ssamstatic void eap_fast_pac_get_a_id(struct eap_fast_pac *pac)
703189251Ssam{
704189251Ssam	u8 *pos, *end;
705189251Ssam	u16 type, len;
706189251Ssam
707189251Ssam	pos = pac->pac_info;
708189251Ssam	end = pos + pac->pac_info_len;
709189251Ssam
710189251Ssam	while (pos + 4 < end) {
711189251Ssam		type = WPA_GET_BE16(pos);
712189251Ssam		pos += 2;
713189251Ssam		len = WPA_GET_BE16(pos);
714189251Ssam		pos += 2;
715189251Ssam		if (pos + len > end)
716189251Ssam			break;
717189251Ssam
718189251Ssam		if (type == PAC_TYPE_A_ID) {
719189251Ssam			os_free(pac->a_id);
720189251Ssam			pac->a_id = os_malloc(len);
721189251Ssam			if (pac->a_id == NULL)
722189251Ssam				break;
723189251Ssam			os_memcpy(pac->a_id, pos, len);
724189251Ssam			pac->a_id_len = len;
725189251Ssam		}
726189251Ssam
727189251Ssam		if (type == PAC_TYPE_A_ID_INFO) {
728189251Ssam			os_free(pac->a_id_info);
729189251Ssam			pac->a_id_info = os_malloc(len);
730189251Ssam			if (pac->a_id_info == NULL)
731189251Ssam				break;
732189251Ssam			os_memcpy(pac->a_id_info, pos, len);
733189251Ssam			pac->a_id_info_len = len;
734189251Ssam		}
735189251Ssam
736189251Ssam		pos += len;
737189251Ssam	}
738189251Ssam}
739189251Ssam
740189251Ssam
741189251Ssam/**
742189251Ssam * eap_fast_load_pac_bin - Load PAC entries (binary format)
743189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
744189251Ssam * @pac_root: Pointer to root of the PAC list (to be filled)
745189251Ssam * @pac_file: Name of the PAC file/blob to load
746189251Ssam * Returns: 0 on success, -1 on failure
747189251Ssam */
748189251Ssamint eap_fast_load_pac_bin(struct eap_sm *sm, struct eap_fast_pac **pac_root,
749189251Ssam			  const char *pac_file)
750189251Ssam{
751189251Ssam	const struct wpa_config_blob *blob = NULL;
752189251Ssam	u8 *buf, *end, *pos;
753189251Ssam	size_t len, count = 0;
754189251Ssam	struct eap_fast_pac *pac, *prev;
755189251Ssam
756189251Ssam	*pac_root = NULL;
757189251Ssam
758189251Ssam	if (pac_file == NULL)
759189251Ssam		return -1;
760189251Ssam
761189251Ssam	if (os_strncmp(pac_file, "blob://", 7) == 0) {
762189251Ssam		blob = eap_get_config_blob(sm, pac_file + 7);
763189251Ssam		if (blob == NULL) {
764189251Ssam			wpa_printf(MSG_INFO, "EAP-FAST: No PAC blob '%s' - "
765189251Ssam				   "assume no PAC entries have been "
766189251Ssam				   "provisioned", pac_file + 7);
767189251Ssam			return 0;
768189251Ssam		}
769189251Ssam		buf = blob->data;
770189251Ssam		len = blob->len;
771189251Ssam	} else {
772189251Ssam		buf = (u8 *) os_readfile(pac_file, &len);
773189251Ssam		if (buf == NULL) {
774189251Ssam			wpa_printf(MSG_INFO, "EAP-FAST: No PAC file '%s' - "
775189251Ssam				   "assume no PAC entries have been "
776189251Ssam				   "provisioned", pac_file);
777189251Ssam			return 0;
778189251Ssam		}
779189251Ssam	}
780189251Ssam
781189251Ssam	if (len == 0) {
782189251Ssam		if (blob == NULL)
783189251Ssam			os_free(buf);
784189251Ssam		return 0;
785189251Ssam	}
786189251Ssam
787189251Ssam	if (len < 6 || WPA_GET_BE32(buf) != EAP_FAST_PAC_BINARY_MAGIC ||
788189251Ssam	    WPA_GET_BE16(buf + 4) != EAP_FAST_PAC_BINARY_FORMAT_VERSION) {
789189251Ssam		wpa_printf(MSG_INFO, "EAP-FAST: Invalid PAC file '%s' (bin)",
790189251Ssam			   pac_file);
791189251Ssam		if (blob == NULL)
792189251Ssam			os_free(buf);
793189251Ssam		return -1;
794189251Ssam	}
795189251Ssam
796189251Ssam	pac = prev = NULL;
797189251Ssam	pos = buf + 6;
798189251Ssam	end = buf + len;
799189251Ssam	while (pos < end) {
800189251Ssam		if (end - pos < 2 + 32 + 2 + 2)
801189251Ssam			goto parse_fail;
802189251Ssam
803189251Ssam		pac = os_zalloc(sizeof(*pac));
804189251Ssam		if (pac == NULL)
805189251Ssam			goto parse_fail;
806189251Ssam
807189251Ssam		pac->pac_type = WPA_GET_BE16(pos);
808189251Ssam		pos += 2;
809189251Ssam		os_memcpy(pac->pac_key, pos, EAP_FAST_PAC_KEY_LEN);
810189251Ssam		pos += EAP_FAST_PAC_KEY_LEN;
811189251Ssam		pac->pac_opaque_len = WPA_GET_BE16(pos);
812189251Ssam		pos += 2;
813189251Ssam		if (pos + pac->pac_opaque_len + 2 > end)
814189251Ssam			goto parse_fail;
815189251Ssam		pac->pac_opaque = os_malloc(pac->pac_opaque_len);
816189251Ssam		if (pac->pac_opaque == NULL)
817189251Ssam			goto parse_fail;
818189251Ssam		os_memcpy(pac->pac_opaque, pos, pac->pac_opaque_len);
819189251Ssam		pos += pac->pac_opaque_len;
820189251Ssam		pac->pac_info_len = WPA_GET_BE16(pos);
821189251Ssam		pos += 2;
822189251Ssam		if (pos + pac->pac_info_len > end)
823189251Ssam			goto parse_fail;
824189251Ssam		pac->pac_info = os_malloc(pac->pac_info_len);
825189251Ssam		if (pac->pac_info == NULL)
826189251Ssam			goto parse_fail;
827189251Ssam		os_memcpy(pac->pac_info, pos, pac->pac_info_len);
828189251Ssam		pos += pac->pac_info_len;
829189251Ssam		eap_fast_pac_get_a_id(pac);
830189251Ssam
831189251Ssam		count++;
832189251Ssam		if (prev)
833189251Ssam			prev->next = pac;
834189251Ssam		else
835189251Ssam			*pac_root = pac;
836189251Ssam		prev = pac;
837189251Ssam	}
838189251Ssam
839189251Ssam	if (blob == NULL)
840189251Ssam		os_free(buf);
841189251Ssam
842189251Ssam	wpa_printf(MSG_DEBUG, "EAP-FAST: Read %lu PAC entries from '%s' (bin)",
843189251Ssam		   (unsigned long) count, pac_file);
844189251Ssam
845189251Ssam	return 0;
846189251Ssam
847189251Ssamparse_fail:
848189251Ssam	wpa_printf(MSG_INFO, "EAP-FAST: Failed to parse PAC file '%s' (bin)",
849189251Ssam		   pac_file);
850189251Ssam	if (blob == NULL)
851189251Ssam		os_free(buf);
852189251Ssam	if (pac)
853189251Ssam		eap_fast_free_pac(pac);
854189251Ssam	return -1;
855189251Ssam}
856189251Ssam
857189251Ssam
858189251Ssam/**
859189251Ssam * eap_fast_save_pac_bin - Save PAC entries (binary format)
860189251Ssam * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init()
861189251Ssam * @pac_root: Root of the PAC list
862189251Ssam * @pac_file: Name of the PAC file/blob
863189251Ssam * Returns: 0 on success, -1 on failure
864189251Ssam */
865189251Ssamint eap_fast_save_pac_bin(struct eap_sm *sm, struct eap_fast_pac *pac_root,
866189251Ssam			  const char *pac_file)
867189251Ssam{
868189251Ssam	size_t len, count = 0;
869189251Ssam	struct eap_fast_pac *pac;
870189251Ssam	u8 *buf, *pos;
871189251Ssam
872189251Ssam	len = 6;
873189251Ssam	pac = pac_root;
874189251Ssam	while (pac) {
875189251Ssam		if (pac->pac_opaque_len > 65535 ||
876189251Ssam		    pac->pac_info_len > 65535)
877189251Ssam			return -1;
878189251Ssam		len += 2 + EAP_FAST_PAC_KEY_LEN + 2 + pac->pac_opaque_len +
879189251Ssam			2 + pac->pac_info_len;
880189251Ssam		pac = pac->next;
881189251Ssam	}
882189251Ssam
883189251Ssam	buf = os_malloc(len);
884189251Ssam	if (buf == NULL)
885189251Ssam		return -1;
886189251Ssam
887189251Ssam	pos = buf;
888189251Ssam	WPA_PUT_BE32(pos, EAP_FAST_PAC_BINARY_MAGIC);
889189251Ssam	pos += 4;
890189251Ssam	WPA_PUT_BE16(pos, EAP_FAST_PAC_BINARY_FORMAT_VERSION);
891189251Ssam	pos += 2;
892189251Ssam
893189251Ssam	pac = pac_root;
894189251Ssam	while (pac) {
895189251Ssam		WPA_PUT_BE16(pos, pac->pac_type);
896189251Ssam		pos += 2;
897189251Ssam		os_memcpy(pos, pac->pac_key, EAP_FAST_PAC_KEY_LEN);
898189251Ssam		pos += EAP_FAST_PAC_KEY_LEN;
899189251Ssam		WPA_PUT_BE16(pos, pac->pac_opaque_len);
900189251Ssam		pos += 2;
901189251Ssam		os_memcpy(pos, pac->pac_opaque, pac->pac_opaque_len);
902189251Ssam		pos += pac->pac_opaque_len;
903189251Ssam		WPA_PUT_BE16(pos, pac->pac_info_len);
904189251Ssam		pos += 2;
905189251Ssam		os_memcpy(pos, pac->pac_info, pac->pac_info_len);
906189251Ssam		pos += pac->pac_info_len;
907189251Ssam
908189251Ssam		pac = pac->next;
909189251Ssam		count++;
910189251Ssam	}
911189251Ssam
912189251Ssam	if (eap_fast_write_pac(sm, pac_file, (char *) buf, len)) {
913189251Ssam		os_free(buf);
914189251Ssam		return -1;
915189251Ssam	}
916189251Ssam
917189251Ssam	wpa_printf(MSG_DEBUG, "EAP-FAST: Wrote %lu PAC entries into '%s' "
918189251Ssam		   "(bin)", (unsigned long) count, pac_file);
919189251Ssam
920189251Ssam	return 0;
921189251Ssam}
922