Line data Source code
1 : /* version.c - Version check routines.
2 : Copyright (C) 2000 Werner Koch (dd9jn)
3 : Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 g10 Code GmbH
4 :
5 : This file is part of GPGME.
6 :
7 : GPGME is free software; you can redistribute it and/or modify it
8 : under the terms of the GNU Lesser General Public License as
9 : published by the Free Software Foundation; either version 2.1 of
10 : the License, or (at your option) any later version.
11 :
12 : GPGME is distributed in the hope that it will be useful, but
13 : WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : Lesser General Public License for more details.
16 :
17 : You should have received a copy of the GNU Lesser General Public
18 : License along with this program; if not, write to the Free Software
19 : Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 : 02111-1307, USA. */
21 :
22 : #if HAVE_CONFIG_H
23 : #include <config.h>
24 : #endif
25 : #include <stdlib.h>
26 : #include <string.h>
27 : #include <limits.h>
28 : #include <ctype.h>
29 : #ifdef HAVE_W32_SYSTEM
30 : #include <winsock2.h>
31 : #endif
32 :
33 : #include "gpgme.h"
34 : #include "priv-io.h"
35 : #include "debug.h"
36 : #include "context.h"
37 :
38 : /* For _gpgme_sema_subsystem_init and _gpgme_status_init. */
39 : #include "sema.h"
40 : #include "util.h"
41 :
42 : #ifdef HAVE_ASSUAN_H
43 : #include "assuan.h"
44 : #endif
45 :
46 : #ifdef HAVE_W32_SYSTEM
47 : #include "windows.h"
48 : #endif
49 :
50 : /* We implement this function, so we have to disable the overriding
51 : macro. */
52 : #undef gpgme_check_version
53 :
54 :
55 : /* Bootstrap the subsystems needed for concurrent operation. This
56 : must be done once at startup. We can not guarantee this using a
57 : lock, though, because the semaphore subsystem needs to be
58 : initialized itself before it can be used. So we expect that the
59 : user performs the necessary synchronization. */
60 : static void
61 51 : do_subsystem_inits (void)
62 : {
63 : static int done = 0;
64 :
65 51 : if (done)
66 73 : return;
67 :
68 : #ifdef HAVE_W32_SYSTEM
69 : /* We need to make sure that the sockets are initialized. */
70 : {
71 : WSADATA wsadat;
72 :
73 : WSAStartup (0x202, &wsadat);
74 : }
75 : #endif
76 :
77 29 : _gpgme_sema_subsystem_init ();
78 29 : _gpgme_debug_subsystem_init ();
79 29 : _gpgme_io_subsystem_init ();
80 29 : _gpgme_status_init ();
81 :
82 29 : done = 1;
83 : }
84 :
85 :
86 : /* Put vesion information into the binary. */
87 : static const char *
88 0 : cright_blurb (void)
89 : {
90 : static const char blurb[] =
91 : "\n\n"
92 : "This is GPGME " PACKAGE_VERSION " - The GnuPG Made Easy library\n"
93 : CRIGHTBLURB
94 : "\n"
95 : "(" BUILD_REVISION " " BUILD_TIMESTAMP ")\n"
96 : "\n\n";
97 0 : return blurb;
98 : }
99 :
100 :
101 : /* Read the next number in the version string STR and return it in
102 : *NUMBER. Return a pointer to the tail of STR after parsing, or
103 : *NULL if the version string was invalid. */
104 : static const char *
105 300 : parse_version_number (const char *str, int *number)
106 : {
107 : #define MAXVAL ((INT_MAX - 10) / 10)
108 300 : int val = 0;
109 :
110 : /* Leading zeros are not allowed. */
111 300 : if (*str == '0' && isdigit(str[1]))
112 0 : return NULL;
113 :
114 948 : while (isdigit (*str) && val <= MAXVAL)
115 : {
116 348 : val *= 10;
117 348 : val += *(str++) - '0';
118 : }
119 300 : *number = val;
120 300 : return val > MAXVAL ? NULL : str;
121 : }
122 :
123 :
124 : /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
125 : example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
126 : as integers. The function returns the tail of the string that
127 : follows the version number. This might be te empty string if there
128 : is nothing following the version number, or a patchlevel. The
129 : function returns NULL if the version string is not valid. */
130 : static const char *
131 100 : parse_version_string (const char *str, int *major, int *minor, int *micro)
132 : {
133 100 : str = parse_version_number (str, major);
134 100 : if (!str || *str != '.')
135 0 : return NULL;
136 100 : str++;
137 :
138 100 : str = parse_version_number (str, minor);
139 100 : if (!str || *str != '.')
140 0 : return NULL;
141 100 : str++;
142 :
143 100 : str = parse_version_number (str, micro);
144 100 : if (!str)
145 0 : return NULL;
146 :
147 : /* A patchlevel might follow. */
148 100 : return str;
149 : }
150 :
151 :
152 : /* Return true if MY_VERSION is at least REQ_VERSION, and false
153 : otherwise. */
154 : int
155 99 : _gpgme_compare_versions (const char *my_version,
156 : const char *rq_version)
157 : {
158 : int my_major, my_minor, my_micro;
159 : int rq_major, rq_minor, rq_micro;
160 : const char *my_plvl, *rq_plvl;
161 :
162 99 : if (!rq_version)
163 49 : return 1;
164 50 : if (!my_version)
165 0 : return 0;
166 :
167 50 : my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
168 50 : if (!my_plvl)
169 0 : return 0;
170 :
171 50 : rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
172 50 : if (!rq_plvl)
173 0 : return 0;
174 :
175 50 : if (my_major > rq_major
176 9 : || (my_major == rq_major && my_minor > rq_minor)
177 2 : || (my_major == rq_major && my_minor == rq_minor
178 2 : && my_micro > rq_micro)
179 2 : || (my_major == rq_major && my_minor == rq_minor
180 2 : && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
181 49 : return 1;
182 :
183 1 : return 0;
184 : }
185 :
186 :
187 : /* Check that the the version of the library is at minimum the
188 : requested one and return the version string; return NULL if the
189 : condition is not met. If a NULL is passed to this function, no
190 : check is done and the version string is simply returned.
191 :
192 : This function must be run once at startup, as it also initializes
193 : some subsystems. Its invocation must be synchronized against
194 : calling any of the other functions in a multi-threaded
195 : environments. */
196 : const char *
197 51 : gpgme_check_version (const char *req_version)
198 : {
199 : char *result;
200 51 : do_subsystem_inits ();
201 :
202 : /* Catch-22: We need to get at least the debug subsystem ready
203 : before using the trace facility. If we won't the trace would
204 : automagically initialize the debug system with out the locks
205 : being initialized and missing the assuan log level setting. */
206 51 : TRACE2 (DEBUG_INIT, "gpgme_check_version", 0,
207 : "req_version=%s, VERSION=%s",
208 : req_version? req_version:"(null)", VERSION);
209 :
210 51 : result = _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL;
211 51 : if (result != NULL)
212 50 : _gpgme_selftest = 0;
213 :
214 51 : return result;
215 : }
216 :
217 : /* Check the version and also at runtime if the struct layout of the
218 : library matches the one of the user. This is particular useful for
219 : Windows targets (-mms-bitfields). */
220 : const char *
221 51 : gpgme_check_version_internal (const char *req_version,
222 : size_t offset_sig_validity)
223 : {
224 : const char *result;
225 :
226 51 : if (req_version && req_version[0] == 1 && req_version[1] == 1)
227 0 : return cright_blurb ();
228 51 : result = gpgme_check_version (req_version);
229 51 : if (result == NULL)
230 1 : return result;
231 :
232 : /* Catch-22, see above. */
233 50 : TRACE2 (DEBUG_INIT, "gpgme_check_version_internal", 0,
234 : "req_version=%s, offset_sig_validity=%i",
235 : req_version ? req_version : "(null)", offset_sig_validity);
236 :
237 50 : if (offset_sig_validity != offsetof (struct _gpgme_signature, validity))
238 : {
239 0 : TRACE1 (DEBUG_INIT, "gpgme_check_version_internal", 0,
240 : "offset_sig_validity mismatch: expected %i",
241 : offsetof (struct _gpgme_signature, validity));
242 0 : _gpgme_selftest = GPG_ERR_SELFTEST_FAILED;
243 : }
244 :
245 50 : return result;
246 : }
247 :
248 :
249 : #define LINELENGTH 80
250 :
251 : /* Extract the version string of a program from STRING. The version
252 : number is expected to be in GNU style format:
253 :
254 : foo 1.2.3
255 : foo (bar system) 1.2.3
256 : foo 1.2.3 cruft
257 : foo (bar system) 1.2.3 cruft.
258 :
259 : Spaces and tabs are skipped and used as delimiters, a term in
260 : (nested) parenthesis before the version string is skipped, the
261 : version string may consist of any non-space and non-tab characters
262 : but needs to bstart with a digit.
263 : */
264 : static const char *
265 136 : extract_version_string (const char *string, size_t *r_len)
266 : {
267 : const char *s;
268 : int count, len;
269 :
270 714 : for (s=string; *s; s++)
271 714 : if (*s == ' ' || *s == '\t')
272 : break;
273 408 : while (*s == ' ' || *s == '\t')
274 136 : s++;
275 136 : if (*s == '(')
276 : {
277 952 : for (count=1, s++; count && *s; s++)
278 816 : if (*s == '(')
279 0 : count++;
280 816 : else if (*s == ')')
281 136 : count--;
282 : }
283 : /* For robustness we look for a digit. */
284 408 : while ( *s && !(*s >= '0' && *s <= '9') )
285 136 : s++;
286 272 : if (*s >= '0' && *s <= '9')
287 : {
288 1904 : for (len=0; s[len]; len++)
289 1768 : if (s[len] == ' ' || s[len] == '\t')
290 : break;
291 : }
292 : else
293 0 : len = 0;
294 :
295 136 : *r_len = len;
296 136 : return s;
297 : }
298 :
299 :
300 : /* Retrieve the version number from the --version output of the
301 : program FILE_NAME. */
302 : char *
303 136 : _gpgme_get_program_version (const char *const file_name)
304 : {
305 136 : char line[LINELENGTH] = "";
306 136 : int linelen = 0;
307 136 : char *mark = NULL;
308 : int rp[2];
309 : int nread;
310 136 : char *argv[] = {NULL /* file_name */, "--version", 0};
311 136 : struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0},
312 : {-1, -1} };
313 : int status;
314 :
315 136 : if (!file_name)
316 0 : return NULL;
317 136 : argv[0] = (char *) file_name;
318 :
319 136 : if (_gpgme_io_pipe (rp, 1) < 0)
320 0 : return NULL;
321 :
322 136 : cfd[0].fd = rp[1];
323 :
324 136 : status = _gpgme_io_spawn (file_name, argv,
325 : IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL);
326 136 : if (status < 0)
327 : {
328 0 : _gpgme_io_close (rp[0]);
329 0 : _gpgme_io_close (rp[1]);
330 0 : return NULL;
331 : }
332 :
333 : do
334 : {
335 136 : nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
336 136 : if (nread > 0)
337 : {
338 136 : line[linelen + nread] = '\0';
339 136 : mark = strchr (&line[linelen], '\n');
340 136 : if (mark)
341 : {
342 136 : if (mark > &line[0] && *mark == '\r')
343 0 : mark--;
344 136 : *mark = '\0';
345 136 : break;
346 : }
347 0 : linelen += nread;
348 : }
349 : }
350 0 : while (nread > 0 && linelen < LINELENGTH - 1);
351 :
352 136 : _gpgme_io_close (rp[0]);
353 :
354 136 : if (mark)
355 : {
356 : size_t len;
357 : const char *s;
358 :
359 136 : s = extract_version_string (line, &len);
360 136 : if (!len)
361 0 : return NULL;
362 136 : mark = malloc (len + 1);
363 136 : if (!mark)
364 0 : return NULL;
365 136 : memcpy (mark, s, len);
366 136 : mark[len] = 0;
367 136 : return mark;
368 : }
369 :
370 0 : return NULL;
371 : }
|