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