w32: Better protect the IO-system's fd_table
[gpgme.git] / src / version.c
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 \f
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 do_subsystem_inits (void)
62 {
63   static int done = 0;
64
65   if (done)
66     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   _gpgme_sema_subsystem_init ();
78   _gpgme_debug_subsystem_init ();
79   _gpgme_io_subsystem_init ();
80   _gpgme_status_init ();
81
82   done = 1;
83 }
84
85
86 /* Put vesion information into the binary.  */
87 static const char *
88 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   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 parse_version_number (const char *str, int *number)
106 {
107 #define MAXVAL ((INT_MAX - 10) / 10)
108   int val = 0;
109
110   /* Leading zeros are not allowed.  */
111   if (*str == '0' && isdigit(str[1]))
112     return NULL;
113
114   while (isdigit (*str) && val <= MAXVAL)
115     {
116       val *= 10;
117       val += *(str++) - '0';
118     }
119   *number = val;
120   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 the 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 parse_version_string (const char *str, int *major, int *minor, int *micro)
132 {
133   str = parse_version_number (str, major);
134   if (!str || *str != '.')
135     return NULL;
136   str++;
137
138   str = parse_version_number (str, minor);
139   if (!str || *str != '.')
140     return NULL;
141   str++;
142
143   str = parse_version_number (str, micro);
144   if (!str)
145     return NULL;
146
147   /* A patchlevel might follow.  */
148   return str;
149 }
150
151
152 /* Return true if MY_VERSION is at least REQ_VERSION, and false
153    otherwise.  */
154 int
155 _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   if (!rq_version)
163     return 1;
164   if (!my_version)
165     return 0;
166
167   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
168   if (!my_plvl)
169     return 0;
170
171   rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
172   if (!rq_plvl)
173     return 0;
174
175   if (my_major > rq_major
176       || (my_major == rq_major && my_minor > rq_minor)
177       || (my_major == rq_major && my_minor == rq_minor
178           && my_micro > rq_micro)
179       || (my_major == rq_major && my_minor == rq_minor
180           && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
181     return 1;
182
183   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 gpgme_check_version (const char *req_version)
198 {
199   const char *result;
200   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   TRACE2 (DEBUG_INIT, "gpgme_check_version", 0,
207           "req_version=%s, VERSION=%s",
208           req_version? req_version:"(null)", VERSION);
209
210   result = _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL;
211   if (result != NULL)
212     _gpgme_selftest = 0;
213
214   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 gpgme_check_version_internal (const char *req_version,
222                               size_t offset_sig_validity)
223 {
224   const char *result;
225
226   if (req_version && req_version[0] == 1 && req_version[1] == 1)
227     return cright_blurb ();
228   result = gpgme_check_version (req_version);
229   if (result == NULL)
230     return result;
231
232   /* Catch-22, see above.  */
233   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   if (offset_sig_validity != offsetof (struct _gpgme_signature, validity))
238     {
239       TRACE1 (DEBUG_INIT, "gpgme_check_version_internal", 0,
240               "offset_sig_validity mismatch: expected %i",
241               offsetof (struct _gpgme_signature, validity));
242       _gpgme_selftest = GPG_ERR_SELFTEST_FAILED;
243     }
244
245   return result;
246 }
247
248 \f
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 extract_version_string (const char *string, size_t *r_len)
266 {
267   const char *s;
268   int count, len;
269
270   for (s=string; *s; s++)
271     if (*s == ' ' || *s == '\t')
272         break;
273   while (*s == ' ' || *s == '\t')
274     s++;
275   if (*s == '(')
276     {
277       for (count=1, s++; count && *s; s++)
278         if (*s == '(')
279           count++;
280         else if (*s == ')')
281           count--;
282     }
283   /* For robustness we look for a digit.  */
284   while ( *s && !(*s >= '0' && *s <= '9') )
285     s++;
286   if (*s >= '0' && *s <= '9')
287     {
288       for (len=0; s[len]; len++)
289         if (s[len] == ' ' || s[len] == '\t')
290           break;
291     }
292   else
293     len = 0;
294
295   *r_len = len;
296   return s;
297 }
298
299
300 /* Retrieve the version number from the --version output of the
301    program FILE_NAME.  */
302 char *
303 _gpgme_get_program_version (const char *const file_name)
304 {
305   char line[LINELENGTH] = "";
306   int linelen = 0;
307   char *mark = NULL;
308   int rp[2];
309   int nread;
310   char *argv[] = {NULL /* file_name */, (char*)"--version", 0};
311   struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0},
312                                    {-1, -1} };
313   int status;
314
315   if (!file_name)
316     return NULL;
317   argv[0] = (char *) file_name;
318
319   if (_gpgme_io_pipe (rp, 1) < 0)
320     return NULL;
321
322   cfd[0].fd = rp[1];
323
324   status = _gpgme_io_spawn (file_name, argv,
325                             IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL);
326   if (status < 0)
327     {
328       _gpgme_io_close (rp[0]);
329       _gpgme_io_close (rp[1]);
330       return NULL;
331     }
332
333   do
334     {
335       nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
336       if (nread > 0)
337         {
338           line[linelen + nread] = '\0';
339           mark = strchr (&line[linelen], '\n');
340           if (mark)
341             {
342               if (mark > &line[0] && *mark == '\r')
343                 mark--;
344               *mark = '\0';
345               break;
346             }
347           linelen += nread;
348         }
349     }
350   while (nread > 0 && linelen < LINELENGTH - 1);
351
352   _gpgme_io_close (rp[0]);
353
354   if (mark)
355     {
356       size_t len;
357       const char *s;
358
359       s = extract_version_string (line, &len);
360       if (!len)
361         return NULL;
362       mark = malloc (len + 1);
363       if (!mark)
364         return NULL;
365       memcpy (mark, s, len);
366       mark[len] = 0;
367       return mark;
368     }
369
370   return NULL;
371 }