2010-06-09 Marcus Brinkmann <marcus@g10code.de>
[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 ().  */
39 #include "sema.h"
40
41 #ifdef HAVE_ASSUAN_H
42 #include "assuan.h"
43 #endif
44
45 #ifdef HAVE_W32_SYSTEM
46 #include "windows.h"
47 #endif
48
49 /* We implement this function, so we have to disable the overriding
50    macro.  */
51 #undef gpgme_check_version
52
53 \f
54 /* Bootstrap the subsystems needed for concurrent operation.  This
55    must be done once at startup.  We can not guarantee this using a
56    lock, though, because the semaphore subsystem needs to be
57    initialized itself before it can be used.  So we expect that the
58    user performs the necessary synchronization.  */
59 static void
60 do_subsystem_inits (void)
61 {
62   static int done = 0;
63
64   if (done)
65     return;
66
67 #ifdef HAVE_W32_SYSTEM
68   /* We need to make sure that the sockets are initialized.  */
69   {
70     WSADATA wsadat;
71     
72     WSAStartup (0x202, &wsadat);
73   }
74 #endif
75
76   _gpgme_sema_subsystem_init ();
77   _gpgme_debug_subsystem_init ();
78   _gpgme_io_subsystem_init ();
79
80   done = 1;
81 }
82
83
84 /* Read the next number in the version string STR and return it in
85    *NUMBER.  Return a pointer to the tail of STR after parsing, or
86    *NULL if the version string was invalid.  */
87 static const char *
88 parse_version_number (const char *str, int *number)
89 {
90 #define MAXVAL ((INT_MAX - 10) / 10)
91   int val = 0;
92
93   /* Leading zeros are not allowed.  */
94   if (*str == '0' && isdigit(str[1]))
95     return NULL;
96
97   while (isdigit (*str) && val <= MAXVAL)
98     {
99       val *= 10;
100       val += *(str++) - '0';
101     }
102   *number = val;
103   return val > MAXVAL ? NULL : str;
104 }
105
106
107 /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
108    example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
109    as integers.  The function returns the tail of the string that
110    follows the version number.  This might be te empty string if there
111    is nothing following the version number, or a patchlevel.  The
112    function returns NULL if the version string is not valid.  */
113 static const char *
114 parse_version_string (const char *str, int *major, int *minor, int *micro)
115 {
116   str = parse_version_number (str, major);
117   if (!str || *str != '.')
118     return NULL;
119   str++;
120
121   str = parse_version_number (str, minor);
122   if (!str || *str != '.')
123     return NULL;
124   str++;
125
126   str = parse_version_number (str, micro);
127   if (!str)
128     return NULL;
129
130   /* A patchlevel might follow.  */
131   return str;
132 }
133
134
135 /* Return true if MY_VERSION is at least REQ_VERSION, and false
136    otherwise.  */
137 int
138 _gpgme_compare_versions (const char *my_version,
139                          const char *rq_version)
140 {
141   int my_major, my_minor, my_micro;
142   int rq_major, rq_minor, rq_micro;
143   const char *my_plvl, *rq_plvl;
144
145   if (!rq_version)
146     return 1;
147   if (!my_version)
148     return 0;
149
150   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
151   if (!my_plvl)
152     return 0;
153
154   rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
155   if (!rq_plvl)
156     return 0;
157
158   if (my_major > rq_major
159       || (my_major == rq_major && my_minor > rq_minor)
160       || (my_major == rq_major && my_minor == rq_minor 
161           && my_micro > rq_micro)
162       || (my_major == rq_major && my_minor == rq_minor
163           && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
164     return 1;
165
166   return 0;
167 }
168
169
170 /* Check that the the version of the library is at minimum the
171    requested one and return the version string; return NULL if the
172    condition is not met.  If a NULL is passed to this function, no
173    check is done and the version string is simply returned.
174
175    This function must be run once at startup, as it also initializes
176    some subsystems.  Its invocation must be synchronized against
177    calling any of the other functions in a multi-threaded
178    environments.  */
179 const char *
180 gpgme_check_version (const char *req_version)
181 {
182   char *result;
183   do_subsystem_inits ();
184
185   /* Catch-22: We need to get at least the debug subsystem ready
186      before using the trace facility.  If we won't the trace would
187      automagically initialize the debug system with out the locks
188      being initialized and missing the assuan log level setting. */
189   TRACE2 (DEBUG_INIT, "gpgme_check_version", 0,
190           "req_version=%s, VERSION=%s",
191           req_version? req_version:"(null)", VERSION);
192  
193   result = _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL;
194   if (result != NULL)
195     _gpgme_selftest = 0;
196
197   return result;
198 }
199
200 /* Check the version and also at runtime if the struct layout of the
201    library matches the one of the user.  This is particular useful for
202    Windows targets (-mms-bitfields).  */
203 const char *
204 gpgme_check_version_internal (const char *req_version,
205                               size_t offset_sig_validity)
206 {
207   const char *result;
208
209   result = gpgme_check_version (req_version);
210   if (result == NULL)
211     return result;
212
213   /* Catch-22, see above.  */
214   TRACE2 (DEBUG_INIT, "gpgme_check_version_internal", 0,
215           "req_version=%s, offset_sig_validity=%i",
216           req_version ? req_version : "(null)", offset_sig_validity);
217
218   if (offset_sig_validity != offsetof (struct _gpgme_signature, validity))
219     {
220       TRACE1 (DEBUG_INIT, "gpgme_check_version_internal", 0,
221               "offset_sig_validity mismatch: expected %i",
222               offsetof (struct _gpgme_signature, validity));
223       _gpgme_selftest = GPG_ERR_SELFTEST_FAILED;
224     }
225
226   return result;
227 }
228
229 \f
230 #define LINELENGTH 80
231
232 /* Extract the version string of a program from STRING.  The version
233    number is expected to be in GNU style format:
234    
235      foo 1.2.3
236      foo (bar system) 1.2.3
237      foo 1.2.3 cruft
238      foo (bar system) 1.2.3 cruft.
239
240   Spaces and tabs are skipped and used as delimiters, a term in
241   (nested) parenthesis before the version string is skipped, the
242   version string may consist of any non-space and non-tab characters
243   but needs to bstart with a digit.
244 */
245 static const char *
246 extract_version_string (const char *string, size_t *r_len)
247 {
248   const char *s;
249   int count, len;
250
251   for (s=string; *s; s++)
252     if (*s == ' ' || *s == '\t')
253         break;
254   while (*s == ' ' || *s == '\t')
255     s++;
256   if (*s == '(')
257     {
258       for (count=1, s++; count && *s; s++)
259         if (*s == '(')
260           count++;
261         else if (*s == ')')
262           count--;
263     }
264   /* For robustness we look for a digit.  */
265   while ( *s && !(*s >= '0' && *s <= '9') )
266     s++;
267   if (*s >= '0' && *s <= '9')
268     {
269       for (len=0; s[len]; len++)
270         if (s[len] == ' ' || s[len] == '\t')
271           break;
272     }
273   else
274     len = 0;
275
276   *r_len = len;
277   return s;
278 }
279
280
281 /* Retrieve the version number from the --version output of the
282    program FILE_NAME.  */
283 char *
284 _gpgme_get_program_version (const char *const file_name)
285 {
286   char line[LINELENGTH] = "";
287   int linelen = 0;
288   char *mark = NULL;
289   int rp[2];
290   int nread;
291   char *argv[] = {NULL /* file_name */, "--version", 0};
292   struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0},
293                                    {-1, -1} };
294   int status;
295
296   if (!file_name)
297     return NULL;
298   argv[0] = (char *) file_name;
299
300   if (_gpgme_io_pipe (rp, 1) < 0)
301     return NULL;
302
303   cfd[0].fd = rp[1];
304
305   status = _gpgme_io_spawn (file_name, argv, 0, cfd, NULL, NULL, NULL);
306   if (status < 0)
307     {
308       _gpgme_io_close (rp[0]);
309       _gpgme_io_close (rp[1]);
310       return NULL;
311     }
312
313   do
314     {
315       nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
316       if (nread > 0)
317         {
318           line[linelen + nread] = '\0';
319           mark = strchr (&line[linelen], '\n');
320           if (mark)
321             {
322               if (mark > &line[0] && *mark == '\r')
323                 mark--;
324               *mark = '\0';
325               break;
326             }
327           linelen += nread;
328         }
329     }
330   while (nread > 0 && linelen < LINELENGTH - 1);
331
332   _gpgme_io_close (rp[0]);
333
334   if (mark)
335     {
336       size_t len;
337       const char *s;
338
339       s = extract_version_string (line, &len);
340       if (!len)
341         return NULL;
342       mark = malloc (len + 1);
343       if (!mark)
344         return NULL;
345       memcpy (mark, s, len);
346       mark[len] = 0;
347       return mark;
348     }
349
350   return NULL;
351 }