Implemented captive-install-acquire Gnome interface.
[captive.git] / src / install / acquire / captivemodid.c
1 /* $Id$
2  * W32 disk modules identifier for acquiration installation utility
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "captivemodid.h"       /* self */
23 #include <glib/gmessages.h>
24 #include <libxml/xmlreader.h>
25 #include <glib/ghash.h>
26 #include <limits.h>
27 #include <stdlib.h>
28 #include <libgnomevfs/gnome-vfs-file-size.h>
29 #include <openssl/md5.h>
30 #include <openssl/bn.h>
31 #include <openssl/crypto.h>
32 #include <glib/gstrfuncs.h>
33 #include <ctype.h>
34
35 #include <captive/macros.h>
36
37
38 gchar *calc_md5(gconstpointer base,size_t length)
39 {
40 unsigned char md5_bin[1+128/8]; /* 128 bits==16 bytes; '1+' for leading stub to prevent shorter output of BN_bn2hex() */
41 BIGNUM *bignum;
42 char *hex;
43 gchar *r,*gs;
44
45         /* already done above */
46         /* Calculate MD5 sum and convert it to hex string: */
47         MD5(base,length,md5_bin+1);
48         md5_bin[0]=0xFF;  /* stub to prevent shorter output of BN_bn2hex() */
49         bignum=BN_bin2bn(md5_bin,1+128/8,NULL);
50         hex=BN_bn2hex(bignum);
51         g_assert(strlen(hex)==2*(1+128/8));
52         r=g_strdup(hex+2);
53         OPENSSL_free(hex);
54         BN_free(bignum);
55
56         g_assert(strlen(r)==32);
57         for (gs=r;*gs;gs++) {
58                 g_assert(isxdigit(*gs));
59                 *gs=tolower(*gs);
60                 g_assert(isxdigit(*gs));
61                 }
62         return r;
63 }
64
65
66 /* map: GINT_TO_POINTER(captivemodid_module.length) -> !=NULL */
67 static GHashTable *module_valid_length_hash;
68
69 static void module_valid_length_hash_init(void)
70 {
71         if (module_valid_length_hash)
72                 return;
73         module_valid_length_hash=g_hash_table_new(g_direct_hash,g_direct_equal);
74 }
75
76 /* map: (const xmlChar *)md5 -> (struct captivemodid_module *) */
77 static GHashTable *module_md5_hash;
78
79 static void module_md5_hash_init(void)
80 {
81         if (module_md5_hash)
82                 return;
83         module_md5_hash=g_hash_table_new(g_str_hash,g_str_equal);
84 }
85
86 /* map: (const xmlChar *)type -> (gpointer)GINT_TO_POINTER(priority) */
87 /* We remove entry for module with already the best priority found,
88  * therefore captivemodid_module_type_best_priority_lookup() will return
89  * 'G_MININT' afterwards.
90  */
91 static GHashTable *module_type_best_priority_hash;
92
93 static void module_type_best_priority_hash_init(void)
94 {
95         if (module_type_best_priority_hash)
96                 return;
97         module_type_best_priority_hash=g_hash_table_new(g_str_hash,g_str_equal);
98 }
99
100 static void captivemodid_load_module(struct captivemodid_module *module)
101 {
102 struct captivemodid_module *module_md5_conflict;
103
104         module_md5_hash_init();
105         if ((module_md5_conflict=g_hash_table_lookup(module_md5_hash,module->md5))) {
106                 g_warning(_("Ignoring module \"%s\" as it has MD5 conflict with: %s"),
107                                 module->id,module_md5_conflict->id);
108                 return;
109                 }
110         g_hash_table_insert(module_md5_hash,(/* de-const */ xmlChar *)module->md5,module);
111
112         module_valid_length_hash_init();
113         g_hash_table_insert(module_valid_length_hash,GINT_TO_POINTER(module->length),module_valid_length_hash);
114
115         if (strcmp(module->type,"cabinet")) {
116                 if (module->priority>captivemodid_module_type_best_priority_lookup(module->type)) {
117                         module_type_best_priority_hash_init();
118                         g_hash_table_insert(module_type_best_priority_hash,
119                                         (/* de-const */ xmlChar *)module->type,GINT_TO_POINTER(module->priority));
120                         }
121                 }
122 }
123
124 gboolean captivemodid_module_length_is_valid(GnomeVFSFileSize file_size)
125 {
126 gint file_size_gint;
127
128         if ((GnomeVFSFileSize)(file_size_gint=file_size)!=file_size)    /* Size too big to be valid. */
129                 return FALSE;
130         module_valid_length_hash_init();
131         return !!g_hash_table_lookup(module_valid_length_hash,GINT_TO_POINTER(file_size_gint));
132 }
133
134 struct captivemodid_module *captivemodid_module_md5_lookup(const gchar *file_md5)
135 {
136         g_return_val_if_fail(file_md5!=NULL,NULL);
137
138         module_md5_hash_init();
139         return g_hash_table_lookup(module_md5_hash,file_md5);
140 }
141
142 gint captivemodid_module_type_best_priority_lookup(const xmlChar *module_type)
143 {
144 gpointer r_gpointer;
145 gboolean errbool;
146
147         g_return_val_if_fail(module_type!=NULL,G_MININT);
148
149         module_type_best_priority_hash_init();
150         errbool=g_hash_table_lookup_extended(module_type_best_priority_hash,
151                         module_type,    /* lookup_key */
152                         NULL,   /* orig_key */
153                         &r_gpointer);   /* value */
154         if (!errbool)
155                 return G_MININT;
156
157         return GPOINTER_TO_INT(r_gpointer);
158 }
159
160 /* Returns: TRUE if all modules were found. */
161 gboolean captivemodid_module_type_best_priority_found(const xmlChar *module_type)
162 {
163 gboolean errbool;
164
165         g_return_val_if_fail(module_type!=NULL,FALSE);
166
167         module_type_best_priority_hash_init();
168         errbool=g_hash_table_remove(module_type_best_priority_hash,module_type);
169         g_assert(errbool==TRUE);
170
171         return !g_hash_table_size(module_type_best_priority_hash);
172 }
173
174 static xmlChar *captivemodid_load_module_xml_get_attr
175                 (const gchar *captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name)
176 {
177 xmlChar *r;
178
179         if (!(r=xmlTextReaderGetAttribute(xml_reader,attr_name))) {
180                 /* FIXME: File line identification? */
181                 g_warning(_("%s: Undefined attributes: %s"),captivemodid_pathname,attr_name);
182                 return NULL;
183                 }
184         return r;
185 }
186
187 static long captivemodid_load_module_xml_get_attr_l
188                 (const gchar *captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name,long num_min,long num_max)
189 {
190 xmlChar *string;
191 long r;
192 char *ends;
193
194         g_return_val_if_fail(num_min-1<num_min,-1);
195         g_return_val_if_fail(num_min<=num_max,num_min-1);
196         g_return_val_if_fail(LONG_MIN<num_min,LONG_MIN);
197         g_return_val_if_fail(num_max<LONG_MAX,num_min-1);
198
199         if (!(string=captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,attr_name)))
200                 return num_min-1;
201         r=strtol(string,&ends,0);
202         xmlFree(string);
203         if (r<num_min || r>num_max) {
204                 g_warning(_("%s: Numer of out range %ld..%ld: %ld"),captivemodid_pathname,num_min,num_max,r);
205                 return num_min-1;
206                 }
207         return r;
208 }
209
210 static void captivemodid_load_module_xml(const gchar *captivemodid_pathname,xmlTextReader *xml_reader)
211 {
212 struct captivemodid_module *module;
213
214         captive_new0(module);
215         if (!(module->type=captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,"type")))
216                 goto fail_free_module;
217         if (!(module->md5 =captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,"md5")))
218                 goto fail_free_module;
219         if (strlen(module->md5)!=strspn(module->md5,"0123456789abcdef")) {
220                 g_warning(_("%s: Attribute 'md5' can be only lower-cased hexstring: %s"),captivemodid_pathname,module->md5);
221                 goto fail_free_module;
222                 }
223         if (strlen(module->md5)!=32) {
224                 g_warning(_("%s: Attribute 'md5' length must be 32: %s"),captivemodid_pathname,module->md5);
225                 goto fail_free_module;
226                 }
227         if (!(module->id  =captivemodid_load_module_xml_get_attr(captivemodid_pathname,xml_reader,"id")))
228                 goto fail_free_module;
229         if (0>=(module->length=captivemodid_load_module_xml_get_attr_l(captivemodid_pathname,xml_reader,"length",1,G_MAXINT-1)))
230                 goto fail_free_module;
231         if (G_MININT>=(module->priority=captivemodid_load_module_xml_get_attr_l(captivemodid_pathname,xml_reader,"priority",
232                         G_MININT+1,G_MAXINT-1)))
233                 goto fail_free_module;
234         captivemodid_load_module(module);
235         return;
236
237 fail_free_module:
238         xmlFree((xmlChar *)module->type);
239         xmlFree((xmlChar *)module->md5);
240         xmlFree((xmlChar *)module->id);
241         g_free(module);
242 }
243
244 static void captivemodid_load_foreach
245                 (const xmlChar *type /* key */,gpointer priority_gpointer /* value */,gpointer user_data /* unused */)
246 {
247         g_return_if_fail(type!=NULL);
248
249         g_return_if_fail(captivemodid_module_best_priority_notify!=NULL);
250
251         (*captivemodid_module_best_priority_notify)(type);
252 }
253
254 void (*captivemodid_module_best_priority_notify)(const gchar *module_type);
255
256 gboolean captivemodid_load(const gchar *captivemodid_pathname)
257 {
258 xmlTextReader *xml_reader;
259
260         if (!(xml_reader=xmlNewTextReaderFilename(captivemodid_pathname)))
261                 return FALSE;
262         while (1==xmlTextReaderRead(xml_reader)) {
263                 switch (xmlTextReaderNodeType(xml_reader)) {
264
265                         case XML_READER_TYPE_COMMENT:
266                                 break;
267
268                         case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
269                                 break;
270
271                         case XML_READER_TYPE_TEXT:      /* Even empty nodes have some '#text'. */
272                                 break;
273
274                         case XML_READER_TYPE_END_ELEMENT:       /* We do not track tag ends. */
275                                 break;
276
277                         case XML_READER_TYPE_ELEMENT: {
278 const xmlChar *xml_name;
279
280                                 xml_name=xmlTextReaderName(xml_reader);
281                                 /**/ if (!xmlStrcmp(xml_name,"modid")) {        /* root tag */
282                                         }
283                                 else if (!xmlStrcmp(xml_name,"module"))
284                                         captivemodid_load_module_xml(captivemodid_pathname,xml_reader);
285                                 else g_warning(_("%s: Unknown ELEMENT node: %s"),captivemodid_pathname,xml_name);
286                                 xmlFree((xmlChar *)xml_name);
287                                 } break;
288
289                         default: g_assert_not_reached();
290                         }
291                 }
292         xmlFreeTextReader(xml_reader);
293
294         if (captivemodid_module_best_priority_notify) {
295                 g_hash_table_foreach(module_type_best_priority_hash,
296                                                 (GHFunc)captivemodid_load_foreach,NULL);
297                 }
298         return TRUE;
299 }