1 /*
2  *  Copyright (C) 2009-2010 Howard Chu
3  *
4  *  This file is part of librtmp.
5  *
6  *  librtmp is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU Lesser General Public License as
8  *  published by the Free Software Foundation; either version 2.1,
9  *  or (at your option) any later version.
10  *
11  *  librtmp is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public License
17  *  along with librtmp see the file COPYING.  If not, write to
18  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  *  Boston, MA  02110-1301, USA.
20  *  http://www.gnu.org/copyleft/lgpl.html
21  */
22 
23 #ifdef USE_HASHSWF
24 #include "rtmp_sys.h"
25 #include "log.h"
26 #include "http.h"
27 
28 #ifdef CRYPTO
29 
30 #ifdef __APPLE__
31 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
32 #endif
33 
34 #if defined(USE_MBEDTLS)
35 #include <mbedtls/md.h>
36 #ifndef SHA256_DIGEST_LENGTH
37 #define SHA256_DIGEST_LENGTH	32
38 #endif
39 typedef mbedtls_md_context_t *HMAC_CTX;
40 #define HMAC_setup(ctx, key, len)	ctx = malloc(sizeof(mbedtls_md_context_t)); mbedtls_md_init(ctx); \
41   mbedtls_md_setup(ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); \
42   mbedtls_md_hmac_starts(ctx, (const unsigned char *)key, len)
43 #define HMAC_crunch(ctx, buf, len)	mbedtls_md_hmac_update(ctx, buf, len)
44 #define HMAC_finish(ctx, dig)		mbedtls_md_hmac_finish(ctx, dig)
45 #define HMAC_close(ctx) free(ctx);	mbedtls_md_free(ctx); ctx = NULL
46 
47 #elif defined(USE_POLARSSL)
48 #include <polarssl/sha2.h>
49 #ifndef SHA256_DIGEST_LENGTH
50 #define SHA256_DIGEST_LENGTH	32
51 #endif
52 #define HMAC_CTX	sha2_context
53 #define HMAC_setup(ctx, key, len)	sha2_hmac_starts(&ctx, (unsigned char *)key, len, 0)
54 #define HMAC_crunch(ctx, buf, len)	sha2_hmac_update(&ctx, buf, len)
55 #define HMAC_finish(ctx, dig)		sha2_hmac_finish(&ctx, dig)
56 #define HMAC_close(ctx)
57 
58 #elif defined(USE_GNUTLS)
59 #include <nettle/hmac.h>
60 #ifndef SHA256_DIGEST_LENGTH
61 #define SHA256_DIGEST_LENGTH	32
62 #endif
63 #undef HMAC_CTX
64 #define HMAC_CTX	struct hmac_sha256_ctx
65 #define HMAC_setup(ctx, key, len)	hmac_sha256_set_key(&ctx, len, key)
66 #define HMAC_crunch(ctx, buf, len)	hmac_sha256_update(&ctx, len, buf)
67 #define HMAC_finish(ctx, dig)		hmac_sha256_digest(&ctx, SHA256_DIGEST_LENGTH, dig)
68 #define HMAC_close(ctx)
69 
70 #else	/* USE_OPENSSL */
71 #include <openssl/ssl.h>
72 #include <openssl/sha.h>
73 #include <openssl/hmac.h>
74 #include <openssl/rc4.h>
75 #define HMAC_setup(ctx, key, len)	HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, (unsigned char *)key, len, EVP_sha256(), 0)
76 #define HMAC_crunch(ctx, buf, len)	HMAC_Update(&ctx, (unsigned char *)buf, len)
77 #define HMAC_finish(ctx, dig, len)	HMAC_Final(&ctx, (unsigned char *)dig, &len);
78 #define HMAC_close(ctx)	HMAC_CTX_cleanup(&ctx)
79 #endif
80 
81 #include <zlib.h>
82 
83 #endif /* CRYPTO */
84 
85 #define	AGENT	"Mozilla/5.0"
86 
87 HTTPResult
HTTP_get(struct HTTP_ctx * http,const char * url,HTTP_read_callback * cb)88 HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb)
89 {
90     char *host, *path;
91     char *p1, *p2;
92     char hbuf[256];
93     int port = 80;
94 #ifdef CRYPTO
95     int ssl = 0;
96 #endif
97     int hlen, flen = 0;
98     int rc, i;
99     int len_known;
100     HTTPResult ret = HTTPRES_OK;
101     struct sockaddr_in sa;
102     RTMPSockBuf sb = {0};
103 
104     http->status = -1;
105 
106     memset(&sa, 0, sizeof(struct sockaddr_in));
107     sa.sin_family = AF_INET;
108 
109     /* we only handle http here */
110     if (strncasecmp(url, "http", 4))
111         return HTTPRES_BAD_REQUEST;
112 
113     if (url[4] == 's')
114     {
115 #ifdef CRYPTO
116         ssl = 1;
117         port = 443;
118         if (!RTMP_TLS_ctx)
119             RTMP_TLS_Init();
120 #else
121         return HTTPRES_BAD_REQUEST;
122 #endif
123     }
124 
125     p1 = strchr(url + 4, ':');
126     if (!p1 || strncmp(p1, "://", 3))
127         return HTTPRES_BAD_REQUEST;
128 
129     host = p1 + 3;
130     path = strchr(host, '/');
131     hlen = path - host;
132     strncpy(hbuf, host, hlen);
133     hbuf[hlen] = '\0';
134     host = hbuf;
135     p1 = strrchr(host, ':');
136     if (p1)
137     {
138         *p1++ = '\0';
139         port = atoi(p1);
140     }
141 
142     sa.sin_addr.s_addr = inet_addr(host);
143     if (sa.sin_addr.s_addr == INADDR_NONE)
144     {
145         struct hostent *hp = gethostbyname(host);
146         if (!hp || !hp->h_addr)
147             return HTTPRES_LOST_CONNECTION;
148         sa.sin_addr = *(struct in_addr *)hp->h_addr;
149     }
150     sa.sin_port = htons(port);
151     sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
152     if (sb.sb_socket == INVALID_SOCKET)
153         return HTTPRES_LOST_CONNECTION;
154     i =
155         sprintf(sb.sb_buf,
156                 "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\nReferer: %.*s\r\n",
157                 path, AGENT, host, (int)(path - url + 1), url);
158     if (http->date[0])
159         i += sprintf(sb.sb_buf + i, "If-Modified-Since: %s\r\n", http->date);
160     i += sprintf(sb.sb_buf + i, "\r\n");
161 
162     if (connect
163             (sb.sb_socket, (struct sockaddr *)&sa, sizeof(struct sockaddr)) < 0)
164     {
165         ret = HTTPRES_LOST_CONNECTION;
166         goto leave;
167     }
168 #ifdef CRYPTO
169     if (ssl)
170     {
171 #ifdef NO_SSL
172         RTMP_Log(RTMP_LOGERROR, "%s, No SSL/TLS support", __FUNCTION__);
173         ret = HTTPRES_BAD_REQUEST;
174         goto leave;
175 #else
176         TLS_client(RTMP_TLS_ctx, sb.sb_ssl);
177 
178 #if defined(USE_MBEDTLS)
179         mbedtls_net_context *server_fd = &RTMP_TLS_ctx->net;
180         server_fd->fd = sb.sb_socket;
181         TLS_setfd(sb.sb_ssl, server_fd);
182 #else
183         TLS_setfd(sb.sb_ssl, sb.sb_socket);
184 #endif
185 
186         int connect_return = TLS_connect(sb.sb_ssl);
187         if (connect_return < 0)
188         {
189             RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
190             ret = HTTPRES_LOST_CONNECTION;
191             goto leave;
192         }
193 #endif
194     }
195 #endif
196     RTMPSockBuf_Send(&sb, sb.sb_buf, i);
197 
198     /* set timeout */
199 #define HTTP_TIMEOUT	5
200     {
201         SET_RCVTIMEO(tv, HTTP_TIMEOUT);
202         if (setsockopt
203                 (sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)))
204         {
205             RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
206                      __FUNCTION__, HTTP_TIMEOUT);
207         }
208     }
209 
210     sb.sb_size = 0;
211     sb.sb_timedout = FALSE;
212     if (RTMPSockBuf_Fill(&sb) < 1)
213     {
214         ret = HTTPRES_LOST_CONNECTION;
215         goto leave;
216     }
217     if (strncmp(sb.sb_buf, "HTTP/1", 6))
218     {
219         ret = HTTPRES_BAD_REQUEST;
220         goto leave;
221     }
222 
223     p1 = strchr(sb.sb_buf, ' ');
224     rc = atoi(p1 + 1);
225     http->status = rc;
226 
227     if (rc >= 300)
228     {
229         if (rc == 304)
230         {
231             ret = HTTPRES_OK_NOT_MODIFIED;
232             goto leave;
233         }
234         else if (rc == 404)
235             ret = HTTPRES_NOT_FOUND;
236         else if (rc >= 500)
237             ret = HTTPRES_SERVER_ERROR;
238         else if (rc >= 400)
239             ret = HTTPRES_BAD_REQUEST;
240         else
241             ret = HTTPRES_REDIRECTED;
242     }
243 
244     p1 = memchr(sb.sb_buf, '\n', sb.sb_size);
245     if (!p1)
246     {
247         ret = HTTPRES_BAD_REQUEST;
248         goto leave;
249     }
250     sb.sb_start = p1 + 1;
251     sb.sb_size -= sb.sb_start - sb.sb_buf;
252 
253     while ((p2 = memchr(sb.sb_start, '\r', sb.sb_size)))
254     {
255         if (*sb.sb_start == '\r')
256         {
257             sb.sb_start += 2;
258             sb.sb_size -= 2;
259             break;
260         }
261         else if (!strncasecmp
262                  (sb.sb_start, "Content-Length: ", sizeof("Content-Length: ") - 1))
263         {
264             flen = atoi(sb.sb_start + sizeof("Content-Length: ") - 1);
265         }
266         else if (!strncasecmp
267                  (sb.sb_start, "Last-Modified: ", sizeof("Last-Modified: ") - 1))
268         {
269             *p2 = '\0';
270             strcpy(http->date, sb.sb_start + sizeof("Last-Modified: ") - 1);
271         }
272         p2 += 2;
273         sb.sb_size -= p2 - sb.sb_start;
274         sb.sb_start = p2;
275         if (sb.sb_size < 1)
276         {
277             if (RTMPSockBuf_Fill(&sb) < 1)
278             {
279                 ret = HTTPRES_LOST_CONNECTION;
280                 goto leave;
281             }
282         }
283     }
284 
285     len_known = flen > 0;
286     while ((!len_known || flen > 0) &&
287             (sb.sb_size > 0 || RTMPSockBuf_Fill(&sb) > 0))
288     {
289         cb(sb.sb_start, 1, sb.sb_size, http->data);
290         if (len_known)
291             flen -= sb.sb_size;
292         http->size += sb.sb_size;
293         sb.sb_size = 0;
294     }
295 
296     if (flen > 0)
297         ret = HTTPRES_LOST_CONNECTION;
298 
299 leave:
300     RTMPSockBuf_Close(&sb);
301     return ret;
302 }
303 
304 #ifdef CRYPTO
305 
306 #define CHUNK	16384
307 
308 struct info
309 {
310     z_stream *zs;
311     HMAC_CTX ctx;
312     int first;
313     int zlib;
314     int size;
315 };
316 
317 static size_t
swfcrunch(void * ptr,size_t size,size_t nmemb,void * stream)318 swfcrunch(void *ptr, size_t size, size_t nmemb, void *stream)
319 {
320     struct info *i = stream;
321     char *p = ptr;
322     size_t len = size * nmemb;
323 
324     if (i->first)
325     {
326         i->first = 0;
327         /* compressed? */
328         if (!strncmp(p, "CWS", 3))
329         {
330             *p = 'F';
331             i->zlib = 1;
332         }
333         HMAC_crunch(i->ctx, (unsigned char *)p, 8);
334         p += 8;
335         len -= 8;
336         i->size = 8;
337     }
338 
339     if (i->zlib)
340     {
341         unsigned char out[CHUNK];
342         i->zs->next_in = (unsigned char *)p;
343         i->zs->avail_in = (uInt)len;
344         do
345         {
346             i->zs->avail_out = CHUNK;
347             i->zs->next_out = out;
348             inflate(i->zs, Z_NO_FLUSH);
349             len = CHUNK - i->zs->avail_out;
350             i->size += (int)len;
351             HMAC_crunch(i->ctx, out, len);
352         }
353         while (i->zs->avail_out == 0);
354     }
355     else
356     {
357         i->size += (int)len;
358         HMAC_crunch(i->ctx, (unsigned char *)p, len);
359     }
360     return size * nmemb;
361 }
362 
363 static int tzoff;
364 static int tzchecked;
365 
366 #define	JAN02_1980	318340800
367 
368 static const char *monthtab[12] = { "Jan", "Feb", "Mar",
369                                     "Apr", "May", "Jun",
370                                     "Jul", "Aug", "Sep",
371                                     "Oct", "Nov", "Dec"
372                                   };
373 static const char *days[] =
374 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
375 
376 /* Parse an HTTP datestamp into Unix time */
377 static time_t
make_unix_time(char * s)378 make_unix_time(char *s)
379 {
380     struct tm time;
381     int i, ysub = 1900, fmt = 0;
382     char *month;
383     char *n;
384     time_t res;
385 
386     if (s[3] != ' ')
387     {
388         fmt = 1;
389         if (s[3] != ',')
390             ysub = 0;
391     }
392     for (n = s; *n; ++n)
393         if (*n == '-' || *n == ':')
394             *n = ' ';
395 
396     time.tm_mon = 0;
397     n = strchr(s, ' ');
398     if (fmt)
399     {
400         /* Day, DD-MMM-YYYY HH:MM:SS GMT */
401         time.tm_mday = strtol(n + 1, &n, 0);
402         month = n + 1;
403         n = strchr(month, ' ');
404         time.tm_year = strtol(n + 1, &n, 0);
405         time.tm_hour = strtol(n + 1, &n, 0);
406         time.tm_min = strtol(n + 1, &n, 0);
407         time.tm_sec = strtol(n + 1, NULL, 0);
408     }
409     else
410     {
411         /* Unix ctime() format. Does not conform to HTTP spec. */
412         /* Day MMM DD HH:MM:SS YYYY */
413         month = n + 1;
414         n = strchr(month, ' ');
415         while (isspace(*n))
416             n++;
417         time.tm_mday = strtol(n, &n, 0);
418         time.tm_hour = strtol(n + 1, &n, 0);
419         time.tm_min = strtol(n + 1, &n, 0);
420         time.tm_sec = strtol(n + 1, &n, 0);
421         time.tm_year = strtol(n + 1, NULL, 0);
422     }
423     if (time.tm_year > 100)
424         time.tm_year -= ysub;
425 
426     for (i = 0; i < 12; i++)
427         if (!strncasecmp(month, monthtab[i], 3))
428         {
429             time.tm_mon = i;
430             break;
431         }
432     time.tm_isdst = 0;		/* daylight saving is never in effect in GMT */
433 
434     /* this is normally the value of extern int timezone, but some
435      * braindead C libraries don't provide it.
436      */
437     if (!tzchecked)
438     {
439         struct tm *tc;
440         time_t then = JAN02_1980;
441         tc = localtime(&then);
442         tzoff = (12 - tc->tm_hour) * 3600 + tc->tm_min * 60 + tc->tm_sec;
443         tzchecked = 1;
444     }
445     res = mktime(&time);
446     /* Unfortunately, mktime() assumes the input is in local time,
447      * not GMT, so we have to correct it here.
448      */
449     if (res != -1)
450         res += tzoff;
451     return res;
452 }
453 
454 /* Convert a Unix time to a network time string
455  * Weekday, DD-MMM-YYYY HH:MM:SS GMT
456  */
457 static void
strtime(time_t * t,char * s)458 strtime(time_t * t, char *s)
459 {
460     struct tm *tm;
461 
462     tm = gmtime((time_t *) t);
463     sprintf(s, "%s, %02d %s %d %02d:%02d:%02d GMT",
464             days[tm->tm_wday], tm->tm_mday, monthtab[tm->tm_mon],
465             tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec);
466 }
467 
468 #define HEX2BIN(a)      (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
469 
470 int
RTMP_HashSWF(const char * url,unsigned int * size,unsigned char * hash,int age)471 RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
472              int age)
473 {
474     FILE *f = NULL;
475     char *path, date[64], cctim[64];
476     long pos = 0;
477     time_t ctim = -1, cnow;
478     int i, got = 0, ret = 0;
479     unsigned int hlen;
480     struct info in = { 0 };
481     struct HTTP_ctx http = { 0 };
482     HTTPResult httpres;
483     z_stream zs = { 0 };
484     AVal home, hpre;
485 
486     date[0] = '\0';
487 #ifdef _WIN32
488 #ifdef XBMC4XBOX
489     hpre.av_val = "Q:";
490     hpre.av_len = 2;
491     home.av_val = "\\UserData";
492 #else
493     hpre.av_val = getenv("HOMEDRIVE");
494     hpre.av_len = (int)strlen(hpre.av_val);
495     home.av_val = getenv("HOMEPATH");
496 #endif
497 #define DIRSEP	"\\"
498 
499 #else /* !_WIN32 */
500     hpre.av_val = "";
501     hpre.av_len = 0;
502     home.av_val = getenv("HOME");
503 #define DIRSEP	"/"
504 #endif
505     if (!home.av_val)
506         home.av_val = ".";
507     home.av_len = (int)strlen(home.av_val);
508 
509     /* SWF hash info is cached in a fixed-format file.
510      * url: <url of SWF file>
511      * ctim: HTTP datestamp of when we last checked it.
512      * date: HTTP datestamp of the SWF's last modification.
513      * size: SWF size in hex
514      * hash: SWF hash in hex
515      *
516      * These fields must be present in this order. All fields
517      * besides URL are fixed size.
518      */
519     path = malloc(hpre.av_len + home.av_len + sizeof(DIRSEP ".swfinfo"));
520     sprintf(path, "%s%s" DIRSEP ".swfinfo", hpre.av_val, home.av_val);
521 
522     f = fopen(path, "r+");
523     while (f)
524     {
525         char buf[4096], *file, *p;
526 
527         file = strchr(url, '/');
528         if (!file)
529             break;
530         file += 2;
531         file = strchr(file, '/');
532         if (!file)
533             break;
534         file++;
535         hlen = file - url;
536         p = strrchr(file, '/');
537         if (p)
538             file = p;
539         else
540             file--;
541 
542         while (fgets(buf, sizeof(buf), f))
543         {
544             char *r1;
545 
546             got = 0;
547 
548             if (strncmp(buf, "url: ", 5))
549                 continue;
550             if (strncmp(buf + 5, url, hlen))
551                 continue;
552             r1 = strrchr(buf, '/');
553             i = (int)strlen(r1);
554             r1[--i] = '\0';
555             if (strncmp(r1, file, i))
556                 continue;
557             pos = ftell(f);
558             while (got < 4 && fgets(buf, sizeof(buf), f))
559             {
560                 if (!strncmp(buf, "size: ", 6))
561                 {
562                     *size = strtol(buf + 6, NULL, 16);
563                     got++;
564                 }
565                 else if (!strncmp(buf, "hash: ", 6))
566                 {
567                     unsigned char *ptr = hash, *in = (unsigned char *)buf + 6;
568                     int l = (int)strlen((char *)in) - 1;
569                     for (i = 0; i < l; i += 2)
570                         *ptr++ = (HEX2BIN(in[i]) << 4) | HEX2BIN(in[i + 1]);
571                     got++;
572                 }
573                 else if (!strncmp(buf, "date: ", 6))
574                 {
575                     buf[strlen(buf) - 1] = '\0';
576                     strncpy(date, buf + 6, sizeof(date));
577                     got++;
578                 }
579                 else if (!strncmp(buf, "ctim: ", 6))
580                 {
581                     buf[strlen(buf) - 1] = '\0';
582                     ctim = make_unix_time(buf + 6);
583                     got++;
584                 }
585                 else if (!strncmp(buf, "url: ", 5))
586                     break;
587             }
588             break;
589         }
590         break;
591     }
592 
593     cnow = time(NULL);
594     /* If we got a cache time, see if it's young enough to use directly */
595     if (age && ctim > 0)
596     {
597         ctim = cnow - ctim;
598         ctim /= 3600 * 24;	/* seconds to days */
599         if (ctim < age)		/* ok, it's new enough */
600             goto out;
601     }
602 
603     in.first = 1;
604     HMAC_setup(in.ctx, "Genuine Adobe Flash Player 001", 30);
605     inflateInit(&zs);
606     in.zs = &zs;
607 
608     http.date = date;
609     http.data = &in;
610 
611     httpres = HTTP_get(&http, url, swfcrunch);
612 
613     inflateEnd(&zs);
614 
615     if (httpres != HTTPRES_OK && httpres != HTTPRES_OK_NOT_MODIFIED)
616     {
617         ret = -1;
618         if (httpres == HTTPRES_LOST_CONNECTION)
619             RTMP_Log(RTMP_LOGERROR, "%s: connection lost while downloading swfurl %s",
620                      __FUNCTION__, url);
621         else if (httpres == HTTPRES_NOT_FOUND)
622             RTMP_Log(RTMP_LOGERROR, "%s: swfurl %s not found", __FUNCTION__, url);
623         else
624             RTMP_Log(RTMP_LOGERROR, "%s: couldn't contact swfurl %s (HTTP error %d)",
625                      __FUNCTION__, url, http.status);
626     }
627     else
628     {
629         if (got && pos)
630             fseek(f, pos, SEEK_SET);
631         else
632         {
633             char *q;
634             if (!f)
635                 f = fopen(path, "w");
636             if (!f)
637             {
638                 int err = errno;
639                 RTMP_Log(RTMP_LOGERROR,
640                          "%s: couldn't open %s for writing, errno %d (%s)",
641                          __FUNCTION__, path, err, strerror(err));
642                 ret = -1;
643                 goto out;
644             }
645             fseek(f, 0, SEEK_END);
646             q = strchr(url, '?');
647             if (q)
648                 i = q - url;
649             else
650                 i = (int)strlen(url);
651 
652             fprintf(f, "url: %.*s\n", i, url);
653         }
654         strtime(&cnow, cctim);
655         fprintf(f, "ctim: %s\n", cctim);
656 
657         if (!in.first)
658         {
659 #if defined(USE_MBEDTLS) || defined(USE_POLARSSL) || defined(USE_GNUTLS)
660             HMAC_finish(in.ctx, hash);
661 #else
662             HMAC_finish(in.ctx, hash, hlen);
663 #endif
664             *size = in.size;
665 
666             fprintf(f, "date: %s\n", date);
667             fprintf(f, "size: %08x\n", in.size);
668             fprintf(f, "hash: ");
669             for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
670                 fprintf(f, "%02x", hash[i]);
671             fprintf(f, "\n");
672         }
673     }
674     HMAC_close(in.ctx);
675 out:
676     free(path);
677     if (f)
678         fclose(f);
679     return ret;
680 }
681 #else
682 int
RTMP_HashSWF(const char * url,unsigned int * size,unsigned char * hash,int age)683 RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
684              int age)
685 {
686     (void)url;
687     (void)size;
688     (void)hash;
689     (void)age;
690     return -1;
691 }
692 #endif
693 #endif
694