Changes by Jose and Tommy.
[gpgme.git] / gpgme / version.c
1 /* version.c -  version check
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *      Copyright (C) 2001 g10 Code GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27
28 #include "gpgme.h"
29 #include "context.h"
30 #include "rungpg.h"
31 #include "sema.h"
32 #include "util.h"
33 #include "key.h" /* for key_cache_init */
34
35 static int lineno;
36 static char *tmp_engine_version;
37
38 static const char *get_engine_info (void);
39
40
41 static void
42 do_subsystem_inits (void)
43 {
44     static int done = 0;
45
46     if (done)
47         return;
48     _gpgme_sema_subsystem_init ();
49     _gpgme_key_cache_init ();
50 }
51
52
53
54 static const char*
55 parse_version_number ( const char *s, int *number )
56 {
57     int val = 0;
58
59     if ( *s == '0' && isdigit(s[1]) )
60         return NULL; /* leading zeros are not allowed */
61     for ( ; isdigit(*s); s++ ) {
62         val *= 10;
63         val += *s - '0';
64     }
65     *number = val;
66     return val < 0? NULL : s;
67 }
68
69
70 static const char *
71 parse_version_string( const char *s, int *major, int *minor, int *micro )
72 {
73     s = parse_version_number ( s, major );
74     if ( !s || *s != '.' )
75         return NULL;
76     s++;
77     s = parse_version_number ( s, minor );
78     if ( !s || *s != '.' )
79         return NULL;
80     s++;
81     s = parse_version_number ( s, micro );
82     if ( !s )
83         return NULL;
84     return s; /* patchlevel */
85 }
86
87 static const char *
88 compare_versions ( const char *my_version, const char *req_version )
89 {
90     int my_major, my_minor, my_micro;
91     int rq_major, rq_minor, rq_micro;
92     const char *my_plvl, *rq_plvl;
93
94     if ( !req_version )
95         return my_version;
96
97     my_plvl = parse_version_string ( my_version,
98                                      &my_major, &my_minor, &my_micro );
99     if ( !my_plvl )
100         return NULL;  /* very strange: our own version is bogus */
101     rq_plvl = parse_version_string( req_version,
102                                     &rq_major, &rq_minor, &rq_micro );
103     if ( !rq_plvl )
104         return NULL;  /* req version string is invalid */
105
106     if ( my_major > rq_major
107          || (my_major == rq_major && my_minor > rq_minor)
108          || (my_major == rq_major && my_minor == rq_minor 
109              && my_micro > rq_micro)
110          || (my_major == rq_major && my_minor == rq_minor
111              && my_micro == rq_micro
112              && strcmp( my_plvl, rq_plvl ) >= 0) ) {
113         return my_version;
114     }
115     return NULL;
116 }
117
118
119 /**
120  * gpgme_check_version:
121  * @req_version: A string with a version
122  * 
123  * Check that the the version of the library is at minimum the requested one
124  * and return the version string; return NULL if the condition is not
125  * met.  If a NULL is passed to this function, no check is done and
126  * the version string is simply returned.  It is a pretty good idea to
127  * run this function as soon as poossible, becuase it also intializes 
128  * some subsystems.  In a multithreaded environment if should be called
129  * before the first thread is created.
130  * 
131  * Return value: The version string or NULL
132  **/
133 const char *
134 gpgme_check_version ( const char *req_version )
135 {
136     do_subsystem_inits ();
137     return compare_versions ( VERSION, req_version );
138 }
139
140
141 /**
142  * gpgme_get_engine_info:
143  *  
144  * Return information about the underlying crypto engine.  This is an
145  * XML string with various information.  To get the version of the
146  * crypto engine it should be sufficient to grep for the first
147  * <literal>version</literal> tag and use it's content.  A string is
148  * always returned even if the crypto engine is not installed; in this
149  * case a XML string with some error information is returned.
150  * 
151  * Return value: A XML string with information about the crypto engine.
152  **/
153 const char *
154 gpgme_get_engine_info ()
155 {
156     do_subsystem_inits ();
157     return get_engine_info ();
158 }
159
160 /**
161  * gpgme_check_engine:
162  * 
163  * Check whether the installed crypto engine matches the requirement of
164  * GPGME.
165  *
166  * Return value: 0 or an error code.
167  **/
168 GpgmeError
169 gpgme_check_engine ()
170 {
171     const char *info = gpgme_get_engine_info ();
172     const char *s, *s2;
173
174     s = strstr (info, "<version>");
175     if (s) {
176         s += 9;
177         s2 = strchr (s, '<');
178         if (s2) {
179             char *ver = xtrymalloc (s2 - s + 1);
180             if (!ver)
181                 return mk_error (Out_Of_Core);
182             memcpy (ver, s, s2-s);
183             ver[s2-s] = 0;
184             s = compare_versions ( ver, NEED_GPG_VERSION );
185             xfree (ver);
186             if (s)
187                 return 0;
188         }
189     }
190     return mk_error (Invalid_Engine);
191 }
192
193
194 \f
195 static void
196 version_line_handler ( GpgmeCtx c, char *line )
197 {
198     char *p;
199     size_t len;
200
201     lineno++;
202     if ( c->out_of_core )
203         return;
204     if (!line)
205         return; /* EOF */
206     if (lineno==1) {
207         if ( memcmp (line, "gpg ", 4) )
208             return;
209         if ( !(p = strpbrk (line, "0123456789")) )
210             return;
211         len = strcspn (p, " \t\r\n()<>" );
212         p[len] = 0;
213         tmp_engine_version = xtrystrdup (p);
214     }
215 }
216
217
218 static const char *
219 get_engine_info (void)
220 {
221     static const char *engine_info =NULL;
222     GpgmeCtx c = NULL;
223     GpgmeError err = 0;
224     const char *path = NULL;
225
226     /* FIXME: make sure that only one instance does run */
227     if (engine_info)
228         return engine_info;
229
230     path = _gpgme_get_gpg_path ();
231     err = gpgme_new (&c);
232     if (err) 
233         goto leave;
234     err = _gpgme_gpg_new ( &c->gpg );
235     if (err)
236         goto leave;
237
238     err = _gpgme_gpg_set_simple_line_handler ( c->gpg,
239                                                version_line_handler, c );
240     if (err)
241         goto leave;
242
243     _gpgme_gpg_add_arg ( c->gpg, "--version" );
244     lineno = 0;
245     xfree (tmp_engine_version); tmp_engine_version = NULL;
246     err = _gpgme_gpg_spawn ( c->gpg, c );
247     if (err)
248         goto leave;
249     gpgme_wait (c, 1);
250     if (tmp_engine_version) {
251         const char *fmt;
252         char *p;
253
254         fmt = "<GnupgInfo>\n"
255               " <engine>\n"
256               "  <version>%s</version>\n"
257               "  <path>%s</path>\n"
258               " </engine>\n"
259               "</GnupgInfo>\n";
260         /*(yes, I know that we allocating 2 extra bytes)*/
261         p = xtrymalloc ( strlen(fmt) + strlen(path)
262                          + strlen (tmp_engine_version) + 1);
263         if (!p) {
264             err = mk_error (Out_Of_Core);
265             goto leave;
266         }
267         sprintf (p, fmt, tmp_engine_version, path);
268         engine_info = p;
269         xfree (tmp_engine_version); tmp_engine_version = NULL;
270     }
271     else {
272         err = mk_error (General_Error);
273     }
274
275  leave:
276     if (err) {
277         const char *fmt;
278         const char *errstr = gpgme_strerror (err);
279         char *p;
280
281         fmt = "<GnupgInfo>\n"
282             " <engine>\n"
283             "  <error>%s</error>\n"                
284             "  <path>%s</path>\n"
285             " </engine>\n"
286             "</GnupgInfo>\n";
287
288         p = xtrymalloc ( strlen(fmt) + strlen(errstr) + strlen(path) + 1);
289         if (p) { 
290             sprintf (p, fmt, errstr, path);
291             engine_info = p;
292         }
293         else {
294             engine_info = "<GnupgInfo>\n"
295                           "  <error>Out of core</error>\n"
296                           "</GnupgInfo>\n";
297         }
298     }
299     gpgme_release ( c );
300     return engine_info;
301 }
302
303
304
305
306
307