xref: /freebsd/lib/libradius/radlib.c (revision 380a989b3223d455375b4fae70fd0b9bdd43bafb)
1 /*-
2  * Copyright 1998 Juniper Networks, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  *	$FreeBSD$
27  */
28 
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/time.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 
35 #include <errno.h>
36 #include <md5.h>
37 #include <netdb.h>
38 #include <stdarg.h>
39 #include <stddef.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #include "radlib_private.h"
46 
47 static void	 clear_password(struct rad_handle *);
48 static void	 generr(struct rad_handle *, const char *, ...)
49 		    __printflike(2, 3);
50 static void	 insert_scrambled_password(struct rad_handle *, int);
51 static int	 is_valid_response(struct rad_handle *, int,
52 		    const struct sockaddr_in *);
53 static int	 put_password_attr(struct rad_handle *, int,
54 		    const void *, size_t);
55 static int	 put_raw_attr(struct rad_handle *, int,
56 		    const void *, size_t);
57 static int	 split(char *, char *[], int, char *, size_t);
58 
59 static void
60 clear_password(struct rad_handle *h)
61 {
62 	if (h->pass_len != 0) {
63 		memset(h->pass, 0, h->pass_len);
64 		h->pass_len = 0;
65 		h->pass_pos = 0;
66 	}
67 }
68 
69 static void
70 generr(struct rad_handle *h, const char *format, ...)
71 {
72 	va_list		 ap;
73 
74 	va_start(ap, format);
75 	vsnprintf(h->errmsg, ERRSIZE, format, ap);
76 	va_end(ap);
77 }
78 
79 static void
80 insert_scrambled_password(struct rad_handle *h, int srv)
81 {
82 	MD5_CTX ctx;
83 	unsigned char md5[16];
84 	const struct rad_server *srvp;
85 	int padded_len;
86 	int pos;
87 
88 	srvp = &h->servers[srv];
89 	padded_len = h->pass_len == 0 ? 16 : (h->pass_len+15) & ~0xf;
90 
91 	memcpy(md5, &h->request[POS_AUTH], LEN_AUTH);
92 	for (pos = 0;  pos < padded_len;  pos += 16) {
93 		int i;
94 
95 		/* Calculate the new scrambler */
96 		MD5Init(&ctx);
97 		MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
98 		MD5Update(&ctx, md5, 16);
99 		MD5Final(md5, &ctx);
100 
101 		/*
102 		 * Mix in the current chunk of the password, and copy
103 		 * the result into the right place in the request.  Also
104 		 * modify the scrambler in place, since we will use this
105 		 * in calculating the scrambler for next time.
106 		 */
107 		for (i = 0;  i < 16;  i++)
108 			h->request[h->pass_pos + pos + i] =
109 			    md5[i] ^= h->pass[pos + i];
110 	}
111 }
112 
113 /*
114  * Return true if the current response is valid for a request to the
115  * specified server.
116  */
117 static int
118 is_valid_response(struct rad_handle *h, int srv,
119     const struct sockaddr_in *from)
120 {
121 	MD5_CTX ctx;
122 	unsigned char md5[16];
123 	const struct rad_server *srvp;
124 	int len;
125 
126 	srvp = &h->servers[srv];
127 
128 	/* Check the source address */
129 	if (from->sin_family != srvp->addr.sin_family ||
130 	    from->sin_addr.s_addr != srvp->addr.sin_addr.s_addr ||
131 	    from->sin_port != srvp->addr.sin_port)
132 		return 0;
133 
134 	/* Check the message length */
135 	if (h->resp_len < POS_ATTRS)
136 		return 0;
137 	len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1];
138 	if (len > h->resp_len)
139 		return 0;
140 
141 	/* Check the response authenticator */
142 	MD5Init(&ctx);
143 	MD5Update(&ctx, &h->response[POS_CODE], POS_AUTH - POS_CODE);
144 	MD5Update(&ctx, &h->request[POS_AUTH], LEN_AUTH);
145 	MD5Update(&ctx, &h->response[POS_ATTRS], len - POS_ATTRS);
146 	MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
147 	MD5Final(md5, &ctx);
148 	if (memcmp(&h->response[POS_AUTH], md5, sizeof md5) != 0)
149 		return 0;
150 
151 	return 1;
152 }
153 
154 static int
155 put_password_attr(struct rad_handle *h, int type, const void *value, size_t len)
156 {
157 	int padded_len;
158 	int pad_len;
159 
160 	if (h->pass_pos != 0) {
161 		generr(h, "Multiple User-Password attributes specified");
162 		return -1;
163 	}
164 	if (len > PASSSIZE)
165 		len = PASSSIZE;
166 	padded_len = len == 0 ? 16 : (len+15) & ~0xf;
167 	pad_len = padded_len - len;
168 
169 	/*
170 	 * Put in a place-holder attribute containing all zeros, and
171 	 * remember where it is so we can fill it in later.
172 	 */
173 	clear_password(h);
174 	put_raw_attr(h, type, h->pass, padded_len);
175 	h->pass_pos = h->req_len - padded_len;
176 
177 	/* Save the cleartext password, padded as necessary */
178 	memcpy(h->pass, value, len);
179 	h->pass_len = len;
180 	memset(h->pass + len, 0, pad_len);
181 	return 0;
182 }
183 
184 static int
185 put_raw_attr(struct rad_handle *h, int type, const void *value, size_t len)
186 {
187 	if (len > 253) {
188 		generr(h, "Attribute too long");
189 		return -1;
190 	}
191 	if (h->req_len + 2 + len > MSGSIZE) {
192 		generr(h, "Maximum message length exceeded");
193 		return -1;
194 	}
195 	h->request[h->req_len++] = type;
196 	h->request[h->req_len++] = len + 2;
197 	memcpy(&h->request[h->req_len], value, len);
198 	h->req_len += len;
199 	return 0;
200 }
201 
202 int
203 rad_add_server(struct rad_handle *h, const char *host, int port,
204     const char *secret, int timeout, int tries)
205 {
206 	struct rad_server *srvp;
207 
208 	if (h->num_servers >= MAXSERVERS) {
209 		generr(h, "Too many RADIUS servers specified");
210 		return -1;
211 	}
212 	srvp = &h->servers[h->num_servers];
213 
214 	memset(&srvp->addr, 0, sizeof srvp->addr);
215 	srvp->addr.sin_len = sizeof srvp->addr;
216 	srvp->addr.sin_family = AF_INET;
217 	if (!inet_aton(host, &srvp->addr.sin_addr)) {
218 		struct hostent *hent;
219 
220 		if ((hent = gethostbyname(host)) == NULL) {
221 			generr(h, "%s: host not found", host);
222 			return -1;
223 		}
224 		memcpy(&srvp->addr.sin_addr, hent->h_addr,
225 		    sizeof srvp->addr.sin_addr);
226 	}
227 	if (port != 0)
228 		srvp->addr.sin_port = htons(port);
229 	else {
230 		struct servent *sent;
231 
232 		srvp->addr.sin_port =
233 		    (sent = getservbyname("radius", "udp")) != NULL ?
234 			sent->s_port : htons(RADIUS_PORT);
235 	}
236 	if ((srvp->secret = strdup(secret)) == NULL) {
237 		generr(h, "Out of memory");
238 		return -1;
239 	}
240 	srvp->timeout = timeout;
241 	srvp->max_tries = tries;
242 	srvp->num_tries = 0;
243 	h->num_servers++;
244 	return 0;
245 }
246 
247 void
248 rad_close(struct rad_handle *h)
249 {
250 	int srv;
251 
252 	if (h->fd != -1)
253 		close(h->fd);
254 	for (srv = 0;  srv < h->num_servers;  srv++) {
255 		memset(h->servers[srv].secret, 0,
256 		    strlen(h->servers[srv].secret));
257 		free(h->servers[srv].secret);
258 	}
259 	clear_password(h);
260 	free(h);
261 }
262 
263 int
264 rad_config(struct rad_handle *h, const char *path)
265 {
266 	FILE *fp;
267 	char buf[MAXCONFLINE];
268 	int linenum;
269 	int retval;
270 
271 	if (path == NULL)
272 		path = PATH_RADIUS_CONF;
273 	if ((fp = fopen(path, "r")) == NULL) {
274 		generr(h, "Cannot open \"%s\": %s", path, strerror(errno));
275 		return -1;
276 	}
277 	retval = 0;
278 	linenum = 0;
279 	while (fgets(buf, sizeof buf, fp) != NULL) {
280 		int len;
281 		char *fields[4];
282 		int nfields;
283 		char msg[ERRSIZE];
284 		char *host;
285 		char *port_str;
286 		char *secret;
287 		char *timeout_str;
288 		char *maxtries_str;
289 		char *end;
290 		unsigned long timeout;
291 		unsigned long maxtries;
292 		int port;
293 
294 		linenum++;
295 		len = strlen(buf);
296 		/* We know len > 0, else fgets would have returned NULL. */
297 		if (buf[len - 1] != '\n') {
298 			if (len == sizeof buf - 1)
299 				generr(h, "%s:%d: line too long", path,
300 				    linenum);
301 			else
302 				generr(h, "%s:%d: missing newline", path,
303 				    linenum);
304 			retval = -1;
305 			break;
306 		}
307 		buf[len - 1] = '\0';
308 
309 		/* Extract the fields from the line. */
310 		nfields = split(buf, fields, 4, msg, sizeof msg);
311 		if (nfields == -1) {
312 			generr(h, "%s:%d: %s", path, linenum, msg);
313 			retval = -1;
314 			break;
315 		}
316 		if (nfields == 0)
317 			continue;
318 		if (nfields < 2) {
319 			generr(h, "%s:%d: missing shared secret", path,
320 			    linenum);
321 			retval = -1;
322 			break;
323 		}
324 		host = fields[0];
325 		secret = fields[1];
326 		timeout_str = fields[2];
327 		maxtries_str = fields[3];
328 
329 		/* Parse and validate the fields. */
330 		host = strtok(host, ":");
331 		port_str = strtok(NULL, ":");
332 		if (port_str != NULL) {
333 			port = strtoul(port_str, &end, 10);
334 			if (*end != '\0') {
335 				generr(h, "%s:%d: invalid port", path,
336 				    linenum);
337 				retval = -1;
338 				break;
339 			}
340 		} else
341 			port = 0;
342 		if (timeout_str != NULL) {
343 			timeout = strtoul(timeout_str, &end, 10);
344 			if (*end != '\0') {
345 				generr(h, "%s:%d: invalid timeout", path,
346 				    linenum);
347 				retval = -1;
348 				break;
349 			}
350 		} else
351 			timeout = TIMEOUT;
352 		if (maxtries_str != NULL) {
353 			maxtries = strtoul(maxtries_str, &end, 10);
354 			if (*end != '\0') {
355 				generr(h, "%s:%d: invalid maxtries", path,
356 				    linenum);
357 				retval = -1;
358 				break;
359 			}
360 		} else
361 			maxtries = MAXTRIES;
362 
363 		if (rad_add_server(h, host, port, secret, timeout, maxtries) ==
364 		    -1) {
365 			char msg[ERRSIZE];
366 
367 			strcpy(msg, h->errmsg);
368 			generr(h, "%s:%d: %s", path, linenum, msg);
369 			retval = -1;
370 			break;
371 		}
372 	}
373 	/* Clear out the buffer to wipe a possible copy of a shared secret */
374 	memset(buf, 0, sizeof buf);
375 	fclose(fp);
376 	return retval;
377 }
378 
379 int
380 rad_create_request(struct rad_handle *h, int code)
381 {
382 	int i;
383 
384 	h->request[POS_CODE] = code;
385 	h->request[POS_IDENT] = ++h->ident;
386 	/* Create a random authenticator */
387 	for (i = 0;  i < LEN_AUTH;  i += 2) {
388 		long r;
389 		r = random();
390 		h->request[POS_AUTH+i] = r;
391 		h->request[POS_AUTH+i+1] = r >> 8;
392 	}
393 	h->req_len = POS_ATTRS;
394 	clear_password(h);
395 	return 0;
396 }
397 
398 struct in_addr
399 rad_cvt_addr(const void *data)
400 {
401 	struct in_addr value;
402 
403 	memcpy(&value.s_addr, data, sizeof value.s_addr);
404 	return value;
405 }
406 
407 u_int32_t
408 rad_cvt_int(const void *data)
409 {
410 	u_int32_t value;
411 
412 	memcpy(&value, data, sizeof value);
413 	return ntohl(value);
414 }
415 
416 char *
417 rad_cvt_string(const void *data, size_t len)
418 {
419 	char *s;
420 
421 	s = malloc(len + 1);
422 	if (s != NULL) {
423 		memcpy(s, data, len);
424 		s[len] = '\0';
425 	}
426 	return s;
427 }
428 
429 /*
430  * Returns the attribute type.  If none are left, returns 0.  On failure,
431  * returns -1.
432  */
433 int
434 rad_get_attr(struct rad_handle *h, const void **value, size_t *len)
435 {
436 	int type;
437 
438 	if (h->resp_pos >= h->resp_len)
439 		return 0;
440 	if (h->resp_pos + 2 > h->resp_len) {
441 		generr(h, "Malformed attribute in response");
442 		return -1;
443 	}
444 	type = h->response[h->resp_pos++];
445 	*len = h->response[h->resp_pos++] - 2;
446 	if (h->resp_pos + *len > h->resp_len) {
447 		generr(h, "Malformed attribute in response");
448 		return -1;
449 	}
450 	*value = &h->response[h->resp_pos];
451 	h->resp_pos += *len;
452 	return type;
453 }
454 
455 /*
456  * Create and initialize a rad_handle structure, and return it to the
457  * caller.  Can fail only if the necessary memory cannot be allocated.
458  * In that case, it returns NULL.
459  */
460 struct rad_handle *
461 rad_open(void)
462 {
463 	struct rad_handle *h;
464 
465 	h = (struct rad_handle *)malloc(sizeof(struct rad_handle));
466 	if (h != NULL) {
467 		srandomdev();
468 		h->fd = -1;
469 		h->num_servers = 0;
470 		h->ident = random();
471 		h->errmsg[0] = '\0';
472 		memset(h->pass, 0, sizeof h->pass);
473 		h->pass_len = 0;
474 		h->pass_pos = 0;
475 	}
476 	return h;
477 }
478 
479 int
480 rad_put_addr(struct rad_handle *h, int type, struct in_addr addr)
481 {
482 	return rad_put_attr(h, type, &addr.s_addr, sizeof addr.s_addr);
483 }
484 
485 int
486 rad_put_attr(struct rad_handle *h, int type, const void *value, size_t len)
487 {
488 	return type == RAD_USER_PASSWORD ?
489 	    put_password_attr(h, type, value, len) :
490 	    put_raw_attr(h, type, value, len);
491 }
492 
493 int
494 rad_put_int(struct rad_handle *h, int type, u_int32_t value)
495 {
496 	u_int32_t nvalue;
497 
498 	nvalue = htonl(value);
499 	return rad_put_attr(h, type, &nvalue, sizeof nvalue);
500 }
501 
502 int
503 rad_put_string(struct rad_handle *h, int type, const char *str)
504 {
505 	return rad_put_attr(h, type, str, strlen(str));
506 }
507 
508 /*
509  * Returns the response type code on success, or -1 on failure.
510  */
511 int
512 rad_send_request(struct rad_handle *h)
513 {
514 	int total_tries;
515 	int try;
516 	int srv;
517 	int n;
518 	int got_valid_response;
519 
520 	/* Make sure we have a socket to use */
521 	if (h->fd == -1) {
522 		struct sockaddr_in sin;
523 
524 		if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
525 			generr(h, "Cannot create socket: %s", strerror(errno));
526 			return -1;
527 		}
528 		memset(&sin, 0, sizeof sin);
529 		sin.sin_len = sizeof sin;
530 		sin.sin_family = AF_INET;
531 		sin.sin_addr.s_addr = INADDR_ANY;
532 		sin.sin_port = htons(0);
533 		if (bind(h->fd, (const struct sockaddr *)&sin,
534 		    sizeof sin) == -1) {
535 			generr(h, "bind: %s", strerror(errno));
536 			close(h->fd);
537 			h->fd = -1;
538 			return -1;
539 		}
540 	}
541 
542 	/* Make sure the user gave us a password */
543 	if (h->pass_pos == 0) {
544 		generr(h, "No User-Password attribute given");
545 		return -1;
546 	}
547 
548 	/* Fill in the length field in the message */
549 	h->request[POS_LENGTH] = h->req_len >> 8;
550 	h->request[POS_LENGTH+1] = h->req_len;
551 
552 	/*
553 	 * Count the total number of tries we will make, and zero the
554 	 * counter for each server.
555 	 */
556 	total_tries = 0;
557 	for (srv = 0;  srv < h->num_servers;  srv++) {
558 		total_tries += h->servers[srv].max_tries;
559 		h->servers[srv].num_tries = 0;
560 	}
561 	if (total_tries == 0) {
562 		generr(h, "No RADIUS servers specified");
563 		return -1;
564 	}
565 
566 	srv = 0;
567 	got_valid_response = 0;
568 	for (try = 0;  try < total_tries;  try++) {
569 		struct timeval timelimit;
570 		struct timeval tv;
571 
572 		/*
573                  * Scan round-robin to the next server that has some
574                  * tries left.  There is guaranteed to be one, or we
575                  * would have exited this loop by now.
576 		 */
577 		while (h->servers[srv].num_tries >=
578 		    h->servers[srv].max_tries)
579 			if (++srv >= h->num_servers)
580 				srv = 0;
581 
582 		/* Insert the scrambled password into the request */
583 		insert_scrambled_password(h, srv);
584 
585 		/* Send the request */
586 		n = sendto(h->fd, h->request, h->req_len, 0,
587 		    (const struct sockaddr *)&h->servers[srv].addr,
588 		    sizeof h->servers[srv].addr);
589 		if (n != h->req_len) {
590 			if (n == -1)
591 				generr(h, "sendto: %s", strerror(errno));
592 			else
593 				generr(h, "sendto: short write");
594 			return -1;
595 		}
596 		h->servers[srv].num_tries++;
597 
598 		/* Wait for a valid response */
599 		gettimeofday(&timelimit, NULL);
600 		timelimit.tv_sec += h->servers[srv].timeout;
601 
602 		tv.tv_sec = h->servers[srv].timeout;
603 		tv.tv_usec = 0;
604 		for ( ; ; ) {
605 			fd_set readfds;
606 
607 			FD_ZERO(&readfds);
608 			FD_SET(h->fd, &readfds);
609 			n = select(h->fd + 1, &readfds, NULL, NULL, &tv);
610 			if (n == -1) {
611 				generr(h, "select: %s", strerror(errno));
612 				return -1;
613 			}
614 			if (n == 0)	/* Timed out */
615 				break;
616 			if (FD_ISSET(h->fd, &readfds)) {
617 				struct sockaddr_in from;
618 				int fromlen;
619 
620 				fromlen = sizeof from;
621 				h->resp_len = recvfrom(h->fd, h->response,
622 				    MSGSIZE, MSG_WAITALL,
623 				    (struct sockaddr *)&from, &fromlen);
624 				if (h->resp_len == -1) {
625 					generr(h, "recvfrom: %s",
626 					    strerror(errno));
627 					return -1;
628 				}
629 				if (is_valid_response(h, srv, &from)) {
630 					got_valid_response = 1;
631 					break;
632 				}
633 			}
634 			/* Compute a new timeout */
635 			gettimeofday(&tv, NULL);
636 			timersub(&timelimit, &tv, &tv);
637 			if (tv.tv_sec < 0)	/* Still poll once more */
638 				timerclear(&tv);
639 		}
640 		if (got_valid_response)
641 			break;
642 		/* Advance to the next server */
643 		if (++srv >= h->num_servers)
644 			srv = 0;
645 	}
646 	if (!got_valid_response) {
647 		generr(h, "No valid RADIUS responses received");
648 		return -1;
649 	}
650 	h->resp_len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1];
651 	h->resp_pos = POS_ATTRS;
652 	return h->response[POS_CODE];
653 }
654 
655 const char *
656 rad_strerror(struct rad_handle *h)
657 {
658 	return h->errmsg;
659 }
660 
661 /*
662  * Destructively split a string into fields separated by white space.
663  * `#' at the beginning of a field begins a comment that extends to the
664  * end of the string.  Fields may be quoted with `"'.  Inside quoted
665  * strings, the backslash escapes `\"' and `\\' are honored.
666  *
667  * Pointers to up to the first maxfields fields are stored in the fields
668  * array.  Missing fields get NULL pointers.
669  *
670  * The return value is the actual number of fields parsed, and is always
671  * <= maxfields.
672  *
673  * On a syntax error, places a message in the msg string, and returns -1.
674  */
675 static int
676 split(char *str, char *fields[], int maxfields, char *msg, size_t msglen)
677 {
678 	char *p;
679 	int i;
680 	static const char ws[] = " \t";
681 
682 	for (i = 0;  i < maxfields;  i++)
683 		fields[i] = NULL;
684 	p = str;
685 	i = 0;
686 	while (*p != '\0') {
687 		p += strspn(p, ws);
688 		if (*p == '#' || *p == '\0')
689 			break;
690 		if (i >= maxfields) {
691 			snprintf(msg, msglen, "line has too many fields");
692 			return -1;
693 		}
694 		if (*p == '"') {
695 			char *dst;
696 
697 			dst = ++p;
698 			fields[i] = dst;
699 			while (*p != '"') {
700 				if (*p == '\\') {
701 					p++;
702 					if (*p != '"' && *p != '\\' &&
703 					    *p != '\0') {
704 						snprintf(msg, msglen,
705 						    "invalid `\\' escape");
706 						return -1;
707 					}
708 				}
709 				if (*p == '\0') {
710 					snprintf(msg, msglen,
711 					    "unterminated quoted string");
712 					return -1;
713 				}
714 				*dst++ = *p++;
715 			}
716 			*dst = '\0';
717 			p++;
718 			if (*fields[i] == '\0') {
719 				snprintf(msg, msglen,
720 				    "empty quoted string not permitted");
721 				return -1;
722 			}
723 			if (*p != '\0' && strspn(p, ws) == 0) {
724 				snprintf(msg, msglen, "quoted string not"
725 				    " followed by white space");
726 				return -1;
727 			}
728 		} else {
729 			fields[i] = p;
730 			p += strcspn(p, ws);
731 			if (*p != '\0')
732 				*p++ = '\0';
733 		}
734 		i++;
735 	}
736 	return i;
737 }
738