2001-11-20 Marcus Brinkmann <marcus@g10code.de>
[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 #include "io.h"
35
36 static const char *get_engine_info (void);
37
38
39 static void
40 do_subsystem_inits (void)
41 {
42     static int done = 0;
43
44     if (done)
45         return;
46     _gpgme_sema_subsystem_init ();
47     _gpgme_key_cache_init ();
48 }
49
50
51
52 static const char*
53 parse_version_number ( const char *s, int *number )
54 {
55     int val = 0;
56
57     if ( *s == '0' && isdigit(s[1]) )
58         return NULL; /* leading zeros are not allowed */
59     for ( ; isdigit(*s); s++ ) {
60         val *= 10;
61         val += *s - '0';
62     }
63     *number = val;
64     return val < 0? NULL : s;
65 }
66
67
68 static const char *
69 parse_version_string( const char *s, int *major, int *minor, int *micro )
70 {
71     s = parse_version_number ( s, major );
72     if ( !s || *s != '.' )
73         return NULL;
74     s++;
75     s = parse_version_number ( s, minor );
76     if ( !s || *s != '.' )
77         return NULL;
78     s++;
79     s = parse_version_number ( s, micro );
80     if ( !s )
81         return NULL;
82     return s; /* patchlevel */
83 }
84
85 const char *
86 _gpgme_compare_versions (const char *my_version,
87                          const char *req_version)
88 {
89   int my_major, my_minor, my_micro;
90   int rq_major, rq_minor, rq_micro;
91   const char *my_plvl, *rq_plvl;
92
93   if (!req_version)
94     return my_version;
95
96   my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
97   if (!my_plvl)
98     return NULL;        /* Very strange: our own version is bogus.  */
99   rq_plvl = parse_version_string(req_version,
100                                  &rq_major, &rq_minor, &rq_micro);
101   if (!rq_plvl)
102     return NULL;        /* Requested version string is invalid.  */
103
104   if (my_major > rq_major
105         || (my_major == rq_major && my_minor > rq_minor)
106       || (my_major == rq_major && my_minor == rq_minor 
107           && my_micro > rq_micro)
108       || (my_major == rq_major && my_minor == rq_minor
109           && my_micro == rq_micro
110           && strcmp( my_plvl, rq_plvl ) >= 0))
111     {
112       return my_version;
113     }
114   return NULL;
115 }
116
117
118 /**
119  * gpgme_check_version:
120  * @req_version: A string with a version
121  * 
122  * Check that the the version of the library is at minimum the requested one
123  * and return the version string; return NULL if the condition is not
124  * met.  If a NULL is passed to this function, no check is done and
125  * the version string is simply returned.  It is a pretty good idea to
126  * run this function as soon as possible, because it also intializes 
127  * some subsystems.  In a multithreaded environment if should be called
128  * before the first thread is created.
129  * 
130  * Return value: The version string or NULL
131  **/
132 const char *
133 gpgme_check_version (const char *req_version)
134 {
135   do_subsystem_inits ();
136   return _gpgme_compare_versions (VERSION, req_version);
137 }
138
139 /**
140  * gpgme_get_engine_info:
141  *  
142  * Return information about the underlying crypto engine.  This is an
143  * XML string with various information.  To get the version of the
144  * crypto engine it should be sufficient to grep for the first
145  * <literal>version</literal> tag and use it's content.  A string is
146  * always returned even if the crypto engine is not installed; in this
147  * case a XML string with some error information is returned.
148  * 
149  * Return value: A XML string with information about the crypto engine.
150  **/
151 const char *
152 gpgme_get_engine_info ()
153 {
154     do_subsystem_inits ();
155     return get_engine_info ();
156 }
157
158 /**
159  * gpgme_check_engine:
160  * 
161  * Check whether the installed crypto engine matches the requirement of
162  * GPGME.
163  *
164  * Return value: 0 or an error code.
165  **/
166 GpgmeError
167 gpgme_check_engine ()
168 {
169     const char *info = gpgme_get_engine_info ();
170     const char *s, *s2;
171
172     s = strstr (info, "<version>");
173     if (s) {
174         s += 9;
175         s2 = strchr (s, '<');
176         if (s2) {
177             char *ver = xtrymalloc (s2 - s + 1);
178             if (!ver)
179                 return mk_error (Out_Of_Core);
180             memcpy (ver, s, s2-s);
181             ver[s2-s] = 0;
182             s = _gpgme_compare_versions ( ver, NEED_GPG_VERSION );
183             xfree (ver);
184             if (s)
185                 return 0;
186         }
187     }
188     return mk_error (Invalid_Engine);
189 }
190
191
192 \f
193 #define LINELENGTH 80
194
195 char *
196 _gpgme_get_program_version (const char *const path)
197 {
198   char line[LINELENGTH] = "";
199   int linelen = 0;
200   char *mark = NULL;
201   int rp[2];
202   pid_t pid;
203   int nread;
204   char *argv[] = {(char *) path, "--version", 0};
205   struct spawn_fd_item_s pfd[] = { {0, -1}, {-1, -1} };
206   struct spawn_fd_item_s cfd[] = { {0, -1}, {-1, 1 /* STDOUT_FILENO */},
207                                    {-1, -1} };
208   int status, signal;
209
210   if (!path)
211     return NULL;
212
213   if (_gpgme_io_pipe (rp, 1) < 0)
214     return NULL;
215
216   pfd[0].fd = rp[1];
217   cfd[0].fd = rp[0];
218   cfd[1].fd = rp[1];
219
220   pid = _gpgme_io_spawn (path, argv, cfd, pfd);
221   if (pid < 0)
222     {
223       _gpgme_io_close (rp[0]);
224       _gpgme_io_close (rp[1]);
225       return NULL;
226     }
227
228   do
229     {
230       nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1);
231       if (nread > 0)
232         {
233           line[linelen + nread] = '\0';
234           mark = strchr (&line[linelen], '\n');
235           if (mark)
236             {
237               *mark = '\0';
238               break;
239             }
240           linelen += nread;
241         }
242     }
243   while (nread > 0 && linelen < LINELENGTH - 1);
244
245   _gpgme_io_close (rp[0]);
246   _gpgme_io_waitpid (pid, 1, &status, &signal);
247
248   if (mark)
249     {
250       mark = strrchr (line, ' ');
251       if (!mark)
252         return NULL;
253       return xtrystrdup (mark + 1);
254     }
255
256   return NULL;
257 }
258
259 static const char *
260 get_engine_info (void)
261 {
262     static const char *engine_info =NULL;
263     GpgmeError err = 0;
264     const char *path = NULL;
265     char *version;
266
267     /* FIXME: make sure that only one instance does run */
268     if (engine_info)
269         return engine_info;
270
271     path = _gpgme_get_gpg_path ();
272     if (!path)
273       {
274         engine_info = "<GnupgInfo>\n"
275           "  <error>Not supported</error>\n"
276           "</GnupgInfo>\n";
277         goto leave;
278       }
279     version = _gpgme_get_program_version (path);
280
281     if (version) {
282         const char *fmt;
283         char *p;
284
285         fmt = "<GnupgInfo>\n"
286               " <engine>\n"
287               "  <version>%s</version>\n"
288               "  <path>%s</path>\n"
289               " </engine>\n"
290               "</GnupgInfo>\n";
291         /*(yes, I know that we allocating 2 extra bytes)*/
292         p = xtrymalloc ( strlen(fmt) + strlen(path)
293                          + strlen (version) + 1);
294         if (!p) {
295             err = mk_error (Out_Of_Core);
296             goto leave;
297         }
298         sprintf (p, fmt, version, path);
299         engine_info = p;
300         xfree (version);
301     }
302     else {
303         err = mk_error (General_Error);
304     }
305
306  leave:
307     if (err) {
308         const char *fmt;
309         const char *errstr = gpgme_strerror (err);
310         char *p;
311
312         fmt = "<GnupgInfo>\n"
313             " <engine>\n"
314             "  <error>%s</error>\n"                
315             "  <path>%s</path>\n"
316             " </engine>\n"
317             "</GnupgInfo>\n";
318
319         p = xtrymalloc ( strlen(fmt) + strlen(errstr) + strlen(path) + 1);
320         if (p) { 
321             sprintf (p, fmt, errstr, path);
322             engine_info = p;
323         }
324         else {
325             engine_info = "<GnupgInfo>\n"
326                           "  <error>Out of core</error>\n"
327                           "</GnupgInfo>\n";
328         }
329     }
330     return engine_info;
331 }