1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | ***************************************************************************/ |
22 | |
23 | #include "curl_setup.h" |
24 | |
25 | #include <curl/curl.h> |
26 | #include "urldata.h" |
27 | #include "vtls/vtls.h" |
28 | #include "http2.h" |
29 | #include "vssh/ssh.h" |
30 | #include "quic.h" |
31 | #include "curl_printf.h" |
32 | |
33 | #ifdef USE_ARES |
34 | # if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ |
35 | defined(WIN32) |
36 | # define CARES_STATICLIB |
37 | # endif |
38 | # include <ares.h> |
39 | #endif |
40 | |
41 | #ifdef USE_LIBIDN2 |
42 | #include <idn2.h> |
43 | #endif |
44 | |
45 | #ifdef USE_LIBPSL |
46 | #include <libpsl.h> |
47 | #endif |
48 | |
49 | #if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) |
50 | #include <iconv.h> |
51 | #endif |
52 | |
53 | #ifdef USE_LIBRTMP |
54 | #include <librtmp/rtmp.h> |
55 | #endif |
56 | |
57 | #ifdef HAVE_ZLIB_H |
58 | #include <zlib.h> |
59 | #endif |
60 | |
61 | #ifdef HAVE_BROTLI |
62 | #include <brotli/decode.h> |
63 | #endif |
64 | |
65 | #ifdef HAVE_ZSTD |
66 | #include <zstd.h> |
67 | #endif |
68 | |
69 | #ifdef USE_GSASL |
70 | #include <gsasl.h> |
71 | #endif |
72 | |
73 | #ifdef USE_OPENLDAP |
74 | #include <ldap.h> |
75 | #endif |
76 | |
77 | #ifdef HAVE_BROTLI |
78 | static void brotli_version(char *buf, size_t bufsz) |
79 | { |
80 | uint32_t brotli_version = BrotliDecoderVersion(); |
81 | unsigned int major = brotli_version >> 24; |
82 | unsigned int minor = (brotli_version & 0x00FFFFFF) >> 12; |
83 | unsigned int patch = brotli_version & 0x00000FFF; |
84 | (void)msnprintf(buf, bufsz, "%u.%u.%u" , major, minor, patch); |
85 | } |
86 | #endif |
87 | |
88 | #ifdef HAVE_ZSTD |
89 | static void zstd_version(char *buf, size_t bufsz) |
90 | { |
91 | unsigned long zstd_version = (unsigned long)ZSTD_versionNumber(); |
92 | unsigned int major = (unsigned int)(zstd_version / (100 * 100)); |
93 | unsigned int minor = (unsigned int)((zstd_version - |
94 | (major * 100 * 100)) / 100); |
95 | unsigned int patch = (unsigned int)(zstd_version - |
96 | (major * 100 * 100) - (minor * 100)); |
97 | (void)msnprintf(buf, bufsz, "%u.%u.%u" , major, minor, patch); |
98 | } |
99 | #endif |
100 | |
101 | /* |
102 | * curl_version() returns a pointer to a static buffer. |
103 | * |
104 | * It is implemented to work multi-threaded by making sure repeated invokes |
105 | * generate the exact same string and never write any temporary data like |
106 | * zeros in the data. |
107 | */ |
108 | |
109 | #define VERSION_PARTS 17 /* number of substrings we can concatenate */ |
110 | |
111 | char *curl_version(void) |
112 | { |
113 | static char out[300]; |
114 | char *outp; |
115 | size_t outlen; |
116 | const char *src[VERSION_PARTS]; |
117 | #ifdef USE_SSL |
118 | char ssl_version[200]; |
119 | #endif |
120 | #ifdef HAVE_LIBZ |
121 | char z_version[40]; |
122 | #endif |
123 | #ifdef HAVE_BROTLI |
124 | char br_version[40] = "brotli/" ; |
125 | #endif |
126 | #ifdef HAVE_ZSTD |
127 | char zst_version[40] = "zstd/" ; |
128 | #endif |
129 | #ifdef USE_ARES |
130 | char cares_version[40]; |
131 | #endif |
132 | #if defined(USE_LIBIDN2) |
133 | char idn_version[40]; |
134 | #endif |
135 | #ifdef USE_LIBPSL |
136 | char psl_version[40]; |
137 | #endif |
138 | #if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) |
139 | char iconv_version[40]="iconv" ; |
140 | #endif |
141 | #ifdef USE_SSH |
142 | char ssh_version[40]; |
143 | #endif |
144 | #ifdef USE_NGHTTP2 |
145 | char h2_version[40]; |
146 | #endif |
147 | #ifdef ENABLE_QUIC |
148 | char h3_version[40]; |
149 | #endif |
150 | #ifdef USE_LIBRTMP |
151 | char rtmp_version[40]; |
152 | #endif |
153 | #ifdef USE_HYPER |
154 | char hyper_buf[30]; |
155 | #endif |
156 | #ifdef USE_GSASL |
157 | char gsasl_buf[30]; |
158 | #endif |
159 | #ifdef USE_OPENLDAP |
160 | char ldap_buf[30]; |
161 | #endif |
162 | int i = 0; |
163 | int j; |
164 | |
165 | #ifdef DEBUGBUILD |
166 | /* Override version string when environment variable CURL_VERSION is set */ |
167 | const char *debugversion = getenv("CURL_VERSION" ); |
168 | if(debugversion) { |
169 | strncpy(out, debugversion, sizeof(out)-1); |
170 | out[sizeof(out)-1] = '\0'; |
171 | return out; |
172 | } |
173 | #endif |
174 | |
175 | src[i++] = LIBCURL_NAME "/" LIBCURL_VERSION; |
176 | #ifdef USE_SSL |
177 | Curl_ssl_version(ssl_version, sizeof(ssl_version)); |
178 | src[i++] = ssl_version; |
179 | #endif |
180 | #ifdef HAVE_LIBZ |
181 | msnprintf(z_version, sizeof(z_version), "zlib/%s" , zlibVersion()); |
182 | src[i++] = z_version; |
183 | #endif |
184 | #ifdef HAVE_BROTLI |
185 | brotli_version(&br_version[7], sizeof(br_version) - 7); |
186 | src[i++] = br_version; |
187 | #endif |
188 | #ifdef HAVE_ZSTD |
189 | zstd_version(&zst_version[5], sizeof(zst_version) - 5); |
190 | src[i++] = zst_version; |
191 | #endif |
192 | #ifdef USE_ARES |
193 | msnprintf(cares_version, sizeof(cares_version), |
194 | "c-ares/%s" , ares_version(NULL)); |
195 | src[i++] = cares_version; |
196 | #endif |
197 | #ifdef USE_LIBIDN2 |
198 | msnprintf(idn_version, sizeof(idn_version), |
199 | "libidn2/%s" , idn2_check_version(NULL)); |
200 | src[i++] = idn_version; |
201 | #elif defined(USE_WIN32_IDN) |
202 | src[i++] = (char *)"WinIDN" ; |
203 | #endif |
204 | |
205 | #ifdef USE_LIBPSL |
206 | msnprintf(psl_version, sizeof(psl_version), "libpsl/%s" , psl_get_version()); |
207 | src[i++] = psl_version; |
208 | #endif |
209 | #if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) |
210 | #ifdef _LIBICONV_VERSION |
211 | msnprintf(iconv_version, sizeof(iconv_version), "iconv/%d.%d" , |
212 | _LIBICONV_VERSION >> 8, _LIBICONV_VERSION & 255); |
213 | #else |
214 | /* version unknown, let the default stand */ |
215 | #endif /* _LIBICONV_VERSION */ |
216 | src[i++] = iconv_version; |
217 | #endif |
218 | #ifdef USE_SSH |
219 | Curl_ssh_version(ssh_version, sizeof(ssh_version)); |
220 | src[i++] = ssh_version; |
221 | #endif |
222 | #ifdef USE_NGHTTP2 |
223 | Curl_http2_ver(h2_version, sizeof(h2_version)); |
224 | src[i++] = h2_version; |
225 | #endif |
226 | #ifdef ENABLE_QUIC |
227 | Curl_quic_ver(h3_version, sizeof(h3_version)); |
228 | src[i++] = h3_version; |
229 | #endif |
230 | #ifdef USE_LIBRTMP |
231 | { |
232 | char suff[2]; |
233 | if(RTMP_LIB_VERSION & 0xff) { |
234 | suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; |
235 | suff[1] = '\0'; |
236 | } |
237 | else |
238 | suff[0] = '\0'; |
239 | |
240 | msnprintf(rtmp_version, sizeof(rtmp_version), "librtmp/%d.%d%s" , |
241 | RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, |
242 | suff); |
243 | src[i++] = rtmp_version; |
244 | } |
245 | #endif |
246 | #ifdef USE_HYPER |
247 | msnprintf(hyper_buf, sizeof(hyper_buf), "Hyper/%s" , hyper_version()); |
248 | src[i++] = hyper_buf; |
249 | #endif |
250 | #ifdef USE_GSASL |
251 | msnprintf(gsasl_buf, sizeof(gsasl_buf), "libgsasl/%s" , |
252 | gsasl_check_version(NULL)); |
253 | src[i++] = gsasl_buf; |
254 | #endif |
255 | #ifdef USE_OPENLDAP |
256 | { |
257 | LDAPAPIInfo api; |
258 | api.ldapai_info_version = LDAP_API_INFO_VERSION; |
259 | |
260 | if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) == LDAP_OPT_SUCCESS) { |
261 | unsigned int patch = api.ldapai_vendor_version % 100; |
262 | unsigned int major = api.ldapai_vendor_version / 10000; |
263 | unsigned int minor = |
264 | ((api.ldapai_vendor_version - major * 10000) - patch) / 100; |
265 | msnprintf(ldap_buf, sizeof(ldap_buf), "%s/%u.%u.%u" , |
266 | api.ldapai_vendor_name, major, minor, patch); |
267 | src[i++] = ldap_buf; |
268 | ldap_memfree(api.ldapai_vendor_name); |
269 | ber_memvfree((void **)api.ldapai_extensions); |
270 | } |
271 | } |
272 | #endif |
273 | |
274 | DEBUGASSERT(i <= VERSION_PARTS); |
275 | |
276 | outp = &out[0]; |
277 | outlen = sizeof(out); |
278 | for(j = 0; j < i; j++) { |
279 | size_t n = strlen(src[j]); |
280 | /* we need room for a space, the string and the final zero */ |
281 | if(outlen <= (n + 2)) |
282 | break; |
283 | if(j) { |
284 | /* prepend a space if not the first */ |
285 | *outp++ = ' '; |
286 | outlen--; |
287 | } |
288 | memcpy(outp, src[j], n); |
289 | outp += n; |
290 | outlen -= n; |
291 | } |
292 | *outp = 0; |
293 | |
294 | return out; |
295 | } |
296 | |
297 | /* data for curl_version_info |
298 | |
299 | Keep the list sorted alphabetically. It is also written so that each |
300 | protocol line has its own #if line to make things easier on the eye. |
301 | */ |
302 | |
303 | static const char * const protocols[] = { |
304 | #ifndef CURL_DISABLE_DICT |
305 | "dict" , |
306 | #endif |
307 | #ifndef CURL_DISABLE_FILE |
308 | "file" , |
309 | #endif |
310 | #ifndef CURL_DISABLE_FTP |
311 | "ftp" , |
312 | #endif |
313 | #if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) |
314 | "ftps" , |
315 | #endif |
316 | #ifndef CURL_DISABLE_GOPHER |
317 | "gopher" , |
318 | #endif |
319 | #if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER) |
320 | "gophers" , |
321 | #endif |
322 | #ifndef CURL_DISABLE_HTTP |
323 | "http" , |
324 | #endif |
325 | #if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) |
326 | "https" , |
327 | #endif |
328 | #ifndef CURL_DISABLE_IMAP |
329 | "imap" , |
330 | #endif |
331 | #if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) |
332 | "imaps" , |
333 | #endif |
334 | #ifndef CURL_DISABLE_LDAP |
335 | "ldap" , |
336 | #if !defined(CURL_DISABLE_LDAPS) && \ |
337 | ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ |
338 | (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) |
339 | "ldaps" , |
340 | #endif |
341 | #endif |
342 | #ifndef CURL_DISABLE_MQTT |
343 | "mqtt" , |
344 | #endif |
345 | #ifndef CURL_DISABLE_POP3 |
346 | "pop3" , |
347 | #endif |
348 | #if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) |
349 | "pop3s" , |
350 | #endif |
351 | #ifdef USE_LIBRTMP |
352 | "rtmp" , |
353 | #endif |
354 | #ifndef CURL_DISABLE_RTSP |
355 | "rtsp" , |
356 | #endif |
357 | #if defined(USE_SSH) && !defined(USE_WOLFSSH) |
358 | "scp" , |
359 | #endif |
360 | #ifdef USE_SSH |
361 | "sftp" , |
362 | #endif |
363 | #if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \ |
364 | (SIZEOF_CURL_OFF_T > 4) |
365 | "smb" , |
366 | # ifdef USE_SSL |
367 | "smbs" , |
368 | # endif |
369 | #endif |
370 | #ifndef CURL_DISABLE_SMTP |
371 | "smtp" , |
372 | #endif |
373 | #if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) |
374 | "smtps" , |
375 | #endif |
376 | #ifndef CURL_DISABLE_TELNET |
377 | "telnet" , |
378 | #endif |
379 | #ifndef CURL_DISABLE_TFTP |
380 | "tftp" , |
381 | #endif |
382 | |
383 | NULL |
384 | }; |
385 | |
386 | static curl_version_info_data version_info = { |
387 | CURLVERSION_NOW, |
388 | LIBCURL_VERSION, |
389 | LIBCURL_VERSION_NUM, |
390 | OS, /* as found by configure or set by hand at build-time */ |
391 | 0 /* features is 0 by default */ |
392 | #ifdef ENABLE_IPV6 |
393 | | CURL_VERSION_IPV6 |
394 | #endif |
395 | #ifdef USE_SSL |
396 | | CURL_VERSION_SSL |
397 | #endif |
398 | #ifdef USE_NTLM |
399 | | CURL_VERSION_NTLM |
400 | #endif |
401 | #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ |
402 | defined(NTLM_WB_ENABLED) |
403 | | CURL_VERSION_NTLM_WB |
404 | #endif |
405 | #ifdef USE_SPNEGO |
406 | | CURL_VERSION_SPNEGO |
407 | #endif |
408 | #ifdef USE_KERBEROS5 |
409 | | CURL_VERSION_KERBEROS5 |
410 | #endif |
411 | #ifdef HAVE_GSSAPI |
412 | | CURL_VERSION_GSSAPI |
413 | #endif |
414 | #ifdef USE_WINDOWS_SSPI |
415 | | CURL_VERSION_SSPI |
416 | #endif |
417 | #ifdef HAVE_LIBZ |
418 | | CURL_VERSION_LIBZ |
419 | #endif |
420 | #ifdef DEBUGBUILD |
421 | | CURL_VERSION_DEBUG |
422 | #endif |
423 | #ifdef CURLDEBUG |
424 | | CURL_VERSION_CURLDEBUG |
425 | #endif |
426 | #ifdef CURLRES_ASYNCH |
427 | | CURL_VERSION_ASYNCHDNS |
428 | #endif |
429 | #if (SIZEOF_CURL_OFF_T > 4) && \ |
430 | ( (SIZEOF_OFF_T > 4) || defined(USE_WIN32_LARGE_FILES) ) |
431 | | CURL_VERSION_LARGEFILE |
432 | #endif |
433 | #if defined(WIN32) && defined(UNICODE) && defined(_UNICODE) |
434 | | CURL_VERSION_UNICODE |
435 | #endif |
436 | #if defined(CURL_DOES_CONVERSIONS) |
437 | | CURL_VERSION_CONV |
438 | #endif |
439 | #if defined(USE_TLS_SRP) |
440 | | CURL_VERSION_TLSAUTH_SRP |
441 | #endif |
442 | #if defined(USE_NGHTTP2) || defined(USE_HYPER) |
443 | | CURL_VERSION_HTTP2 |
444 | #endif |
445 | #if defined(ENABLE_QUIC) |
446 | | CURL_VERSION_HTTP3 |
447 | #endif |
448 | #if defined(USE_UNIX_SOCKETS) |
449 | | CURL_VERSION_UNIX_SOCKETS |
450 | #endif |
451 | #if defined(USE_LIBPSL) |
452 | | CURL_VERSION_PSL |
453 | #endif |
454 | #if defined(CURL_WITH_MULTI_SSL) |
455 | | CURL_VERSION_MULTI_SSL |
456 | #endif |
457 | #if defined(HAVE_BROTLI) |
458 | | CURL_VERSION_BROTLI |
459 | #endif |
460 | #if defined(HAVE_ZSTD) |
461 | | CURL_VERSION_ZSTD |
462 | #endif |
463 | #ifndef CURL_DISABLE_ALTSVC |
464 | | CURL_VERSION_ALTSVC |
465 | #endif |
466 | #ifndef CURL_DISABLE_HSTS |
467 | | CURL_VERSION_HSTS |
468 | #endif |
469 | #if defined(USE_GSASL) |
470 | | CURL_VERSION_GSASL |
471 | #endif |
472 | , |
473 | NULL, /* ssl_version */ |
474 | 0, /* ssl_version_num, this is kept at zero */ |
475 | NULL, /* zlib_version */ |
476 | protocols, |
477 | NULL, /* c-ares version */ |
478 | 0, /* c-ares version numerical */ |
479 | NULL, /* libidn version */ |
480 | 0, /* iconv version */ |
481 | NULL, /* ssh lib version */ |
482 | 0, /* brotli_ver_num */ |
483 | NULL, /* brotli version */ |
484 | 0, /* nghttp2 version number */ |
485 | NULL, /* nghttp2 version string */ |
486 | NULL, /* quic library string */ |
487 | #ifdef CURL_CA_BUNDLE |
488 | CURL_CA_BUNDLE, /* cainfo */ |
489 | #else |
490 | NULL, |
491 | #endif |
492 | #ifdef CURL_CA_PATH |
493 | CURL_CA_PATH, /* capath */ |
494 | #else |
495 | NULL, |
496 | #endif |
497 | 0, /* zstd_ver_num */ |
498 | NULL, /* zstd version */ |
499 | NULL, /* Hyper version */ |
500 | NULL /* gsasl version */ |
501 | }; |
502 | |
503 | curl_version_info_data *curl_version_info(CURLversion stamp) |
504 | { |
505 | #if defined(USE_SSH) |
506 | static char ssh_buffer[80]; |
507 | #endif |
508 | #ifdef USE_SSL |
509 | #ifdef CURL_WITH_MULTI_SSL |
510 | static char ssl_buffer[200]; |
511 | #else |
512 | static char ssl_buffer[80]; |
513 | #endif |
514 | #endif |
515 | #ifdef HAVE_BROTLI |
516 | static char brotli_buffer[80]; |
517 | #endif |
518 | #ifdef HAVE_ZSTD |
519 | static char zstd_buffer[80]; |
520 | #endif |
521 | |
522 | #ifdef USE_SSL |
523 | Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); |
524 | version_info.ssl_version = ssl_buffer; |
525 | #ifndef CURL_DISABLE_PROXY |
526 | if(Curl_ssl->supports & SSLSUPP_HTTPS_PROXY) |
527 | version_info.features |= CURL_VERSION_HTTPS_PROXY; |
528 | else |
529 | version_info.features &= ~CURL_VERSION_HTTPS_PROXY; |
530 | #endif |
531 | #endif |
532 | |
533 | #ifdef HAVE_LIBZ |
534 | version_info.libz_version = zlibVersion(); |
535 | /* libz left NULL if non-existing */ |
536 | #endif |
537 | #ifdef USE_ARES |
538 | { |
539 | int aresnum; |
540 | version_info.ares = ares_version(&aresnum); |
541 | version_info.ares_num = aresnum; |
542 | } |
543 | #endif |
544 | #ifdef USE_LIBIDN2 |
545 | /* This returns a version string if we use the given version or later, |
546 | otherwise it returns NULL */ |
547 | version_info.libidn = idn2_check_version(IDN2_VERSION); |
548 | if(version_info.libidn) |
549 | version_info.features |= CURL_VERSION_IDN; |
550 | #elif defined(USE_WIN32_IDN) |
551 | version_info.features |= CURL_VERSION_IDN; |
552 | #endif |
553 | |
554 | #if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) |
555 | #ifdef _LIBICONV_VERSION |
556 | version_info.iconv_ver_num = _LIBICONV_VERSION; |
557 | #else |
558 | /* version unknown */ |
559 | version_info.iconv_ver_num = -1; |
560 | #endif /* _LIBICONV_VERSION */ |
561 | #endif |
562 | |
563 | #if defined(USE_SSH) |
564 | Curl_ssh_version(ssh_buffer, sizeof(ssh_buffer)); |
565 | version_info.libssh_version = ssh_buffer; |
566 | #endif |
567 | |
568 | #ifdef HAVE_BROTLI |
569 | version_info.brotli_ver_num = BrotliDecoderVersion(); |
570 | brotli_version(brotli_buffer, sizeof(brotli_buffer)); |
571 | version_info.brotli_version = brotli_buffer; |
572 | #endif |
573 | |
574 | #ifdef HAVE_ZSTD |
575 | version_info.zstd_ver_num = (unsigned int)ZSTD_versionNumber(); |
576 | zstd_version(zstd_buffer, sizeof(zstd_buffer)); |
577 | version_info.zstd_version = zstd_buffer; |
578 | #endif |
579 | |
580 | #ifdef USE_NGHTTP2 |
581 | { |
582 | nghttp2_info *h2 = nghttp2_version(0); |
583 | version_info.nghttp2_ver_num = h2->version_num; |
584 | version_info.nghttp2_version = h2->version_str; |
585 | } |
586 | #endif |
587 | |
588 | #ifdef ENABLE_QUIC |
589 | { |
590 | static char quicbuffer[80]; |
591 | Curl_quic_ver(quicbuffer, sizeof(quicbuffer)); |
592 | version_info.quic_version = quicbuffer; |
593 | } |
594 | #endif |
595 | |
596 | #ifdef USE_HYPER |
597 | { |
598 | static char hyper_buffer[30]; |
599 | msnprintf(hyper_buffer, sizeof(hyper_buffer), "Hyper/%s" , hyper_version()); |
600 | version_info.hyper_version = hyper_buffer; |
601 | } |
602 | #endif |
603 | |
604 | #ifdef USE_GSASL |
605 | { |
606 | version_info.gsasl_version = gsasl_check_version(NULL); |
607 | } |
608 | #endif |
609 | |
610 | (void)stamp; /* avoid compiler warnings, we don't use this */ |
611 | return &version_info; |
612 | } |
613 | |