3344896b1a6cb7854e9d8b16fd4cc9ba36d5bda2
[captive.git] / src / libcaptive / captivemodid / captivemodid.c
1 /* $Id$
2  * W32 disk modules identifier for libcaptive and its clients
3  * Copyright (C) 2003-2005 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 "captive/captivemodid.h"       /* self */
23 #include "captive/macros.h"
24 #include "captive/libxml.h"
25 #include <glib/gmessages.h>
26 #include <libxml/xmlreader.h>
27 #include <glib/ghash.h>
28 #include <limits.h>
29 #include <stdlib.h>
30 #include <libgnomevfs/gnome-vfs-file-size.h>
31 #include <openssl/md5.h>
32 #include <openssl/bn.h>
33 #include <openssl/crypto.h>
34 #include <glib/gstrfuncs.h>
35 #include <ctype.h>
36
37
38 struct _CaptiveCaptivemodidObject {
39         GObject parent_instance;
40
41         /* map: GINT_TO_POINTER(captive_captivemodid_module.length) -> !=NULL */
42         /* No allocations needed. */
43         GHashTable *module_valid_length_hash;
44
45         /* map: (const xmlChar *)md5 -> (struct captive_captivemodid_module *) */
46         /* 'key' is not allocated it is shared with: 'val'->md5 */
47         /* 'val' is allocated, to be automatically freed by: captive_captivemodid_module_free() */
48         GHashTable *module_md5_hash;
49
50         /* map: (const xmlChar *)type -> (gpointer)GINT_TO_POINTER(priority) */
51         /* We remove entry for module with already the best priority found,
52          * therefore captive_captivemodid_module_type_best_priority_lookup() will return
53          * 'G_MININT' afterwards.
54          * 'key' is not allocated - it is shared with: module_md5_hash */
55         /* No allocations needed. */
56         GHashTable *module_type_best_priority_hash;
57
58         /* g_strdup()ped */
59         gchar *pathname_loaded;
60         };
61 struct _CaptiveCaptivemodidObjectClass {
62         GObjectClass parent_class;
63         };
64
65
66 static gpointer captive_captivemodid_object_parent_class=NULL;
67
68
69 static void captive_captivemodid_object_finalize(CaptiveCaptivemodidObject *captive_captivemodid_object)
70 {
71         g_return_if_fail(captive_captivemodid_object!=NULL);
72
73         g_hash_table_destroy(captive_captivemodid_object->module_valid_length_hash);
74         g_hash_table_destroy(captive_captivemodid_object->module_md5_hash);
75         g_hash_table_destroy(captive_captivemodid_object->module_type_best_priority_hash);
76         g_free(captive_captivemodid_object->pathname_loaded);
77
78         G_OBJECT_CLASS(captive_captivemodid_object_parent_class)->finalize((GObject *)captive_captivemodid_object);
79 }
80
81
82 static void captive_captivemodid_object_class_init(CaptiveCaptivemodidObjectClass *class)
83 {
84 GObjectClass *gobject_class=G_OBJECT_CLASS(class);
85
86         captive_captivemodid_object_parent_class=g_type_class_ref(g_type_parent(G_TYPE_FROM_CLASS(class)));
87         gobject_class->finalize=(void (*)(GObject *object))captive_captivemodid_object_finalize;
88 }
89
90
91 static void captive_captivemodid_module_free(struct captive_captivemodid_module *module)
92 {
93         xmlFree((xmlChar *)module->type);
94         xmlFree((xmlChar *)module->md5);
95         xmlFree((xmlChar *)module->id);
96         g_free(module);
97 }
98
99 static void captive_captivemodid_object_init(CaptiveCaptivemodidObject *captive_captivemodid_object)
100 {
101         captive_captivemodid_object->module_valid_length_hash=g_hash_table_new(g_direct_hash,g_direct_equal);
102         captive_captivemodid_object->module_md5_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
103                         NULL,   /* key_destroy_func */
104                         (GDestroyNotify)captive_captivemodid_module_free);      /* value_destroy_func */
105         captive_captivemodid_object->module_type_best_priority_hash=g_hash_table_new(g_str_hash,g_str_equal);
106 }
107
108
109 GType captive_captivemodid_object_get_type(void)
110 {
111 static GType captive_captivemodid_object_type=0;
112
113         if (!captive_captivemodid_object_type) {
114 static const GTypeInfo captive_captivemodid_object_info={
115                                 sizeof(CaptiveCaptivemodidObjectClass),
116                                 NULL,   /* base_init */
117                                 NULL,   /* base_finalize */
118                                 (GClassInitFunc)captive_captivemodid_object_class_init,
119                                 NULL,   /* class_finalize */
120                                 NULL,   /* class_data */
121                                 sizeof(CaptiveCaptivemodidObject),
122                                 5,      /* n_preallocs */
123                                 (GInstanceInitFunc)captive_captivemodid_object_init,
124                                 };
125
126                 captive_captivemodid_object_type=g_type_register_static(G_TYPE_OBJECT,
127                                 "CaptiveCaptivemodidObject",&captive_captivemodid_object_info,0);
128                 }
129
130         return captive_captivemodid_object_type;
131 }
132
133
134 static void captive_captivemodid_load_module
135                 (CaptiveCaptivemodidObject *captivemodid,struct captive_captivemodid_module *module)
136 {
137 struct captive_captivemodid_module *module_md5_conflict;
138 gpointer valid_length_value_gpointer;
139
140         if ((module_md5_conflict=g_hash_table_lookup(captivemodid->module_md5_hash,module->md5))) {
141                 g_warning(_("Ignoring module \"%s\" as it has MD5 conflict with: %s"),
142                                 module->id,module_md5_conflict->id);
143                 return;
144                 }
145         g_hash_table_insert(captivemodid->module_md5_hash,(/* de-const */ xmlChar *)module->md5,module);
146
147         if (!g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(module->length),
148                         NULL,   /* orig_key */
149                         &valid_length_value_gpointer))  /* value */
150                 g_hash_table_insert(captivemodid->module_valid_length_hash,
151                                 GINT_TO_POINTER(module->length),GINT_TO_POINTER(module->cabinet_used));
152         else {
153                 /* Conflicting 'cabinet_used' values for single 'cabinet size'? */
154                 if (valid_length_value_gpointer && GPOINTER_TO_INT(valid_length_value_gpointer)!=module->cabinet_used)
155                         g_hash_table_insert(captivemodid->module_valid_length_hash,GINT_TO_POINTER(module->length),NULL);
156                 }
157
158         if (strcmp((const char *)module->type,"cabinet")) {
159                 if (module->priority>captive_captivemodid_module_type_best_priority_lookup(captivemodid,module->type)) {
160                         g_hash_table_insert(captivemodid->module_type_best_priority_hash,
161                                         (/* de-const */ xmlChar *)module->type,GINT_TO_POINTER(module->priority));
162                         }
163                 }
164 }
165
166 gboolean captive_captivemodid_module_length_is_valid(CaptiveCaptivemodidObject *captivemodid,GnomeVFSFileSize file_size)
167 {
168 gint file_size_gint;
169
170         if ((GnomeVFSFileSize)(file_size_gint=file_size)!=file_size)    /* Size too big to be valid. */
171                 return FALSE;
172         return g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(file_size_gint),
173                         NULL,   /* orig_key */
174                         NULL);  /* value */
175 }
176
177 gint captive_captivemodid_cabinet_length_to_used(CaptiveCaptivemodidObject *captivemodid,gint cabinet_length)
178 {
179 gpointer valid_length_value_gpointer;
180
181         if (!g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(cabinet_length),
182                         NULL,   /* orig_key */
183                         &valid_length_value_gpointer))  /* value */
184                 return 0;
185         return GPOINTER_TO_INT(valid_length_value_gpointer);
186 }
187
188 struct captive_captivemodid_module *captive_captivemodid_module_md5_lookup
189                 (CaptiveCaptivemodidObject *captivemodid,const gchar *file_md5)
190 {
191         g_return_val_if_fail(file_md5!=NULL,NULL);
192
193         return g_hash_table_lookup(captivemodid->module_md5_hash,file_md5);
194 }
195
196 gint captive_captivemodid_module_type_best_priority_lookup(CaptiveCaptivemodidObject *captivemodid,const xmlChar *module_type)
197 {
198 gpointer r_gpointer;
199 gboolean errbool;
200
201         g_return_val_if_fail(module_type!=NULL,G_MININT);
202
203         errbool=g_hash_table_lookup_extended(captivemodid->module_type_best_priority_hash,
204                         module_type,    /* lookup_key */
205                         NULL,   /* orig_key */
206                         &r_gpointer);   /* value */
207         if (!errbool)
208                 return G_MININT;
209
210         return GPOINTER_TO_INT(r_gpointer);
211 }
212
213 /* Returns: TRUE if all modules were found. */
214 gboolean captive_captivemodid_module_type_best_priority_found
215                 (CaptiveCaptivemodidObject *captivemodid,const xmlChar *module_type)
216 {
217 gboolean errbool;
218
219         g_return_val_if_fail(module_type!=NULL,FALSE);
220
221         errbool=g_hash_table_remove(captivemodid->module_type_best_priority_hash,module_type);
222         g_assert(errbool==TRUE);
223
224         return !g_hash_table_size(captivemodid->module_type_best_priority_hash);
225 }
226
227 static xmlChar *captive_captivemodid_load_module_xml_get_attr
228                 (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name)
229 {
230 xmlChar *r;
231
232         if (!(r=xmlTextReaderGetAttribute(xml_reader,BAD_CAST attr_name))) {
233                 /* FIXME: File line identification? */
234                 g_warning(_("%s: Undefined attributes: %s"),captive_captivemodid_pathname,attr_name);
235                 return NULL;
236                 }
237         return r;
238 }
239
240 static long captive_captivemodid_load_module_xml_get_attr_l
241                 (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name,long num_min,long num_max)
242 {
243 xmlChar *string;
244 long r;
245 char *ends;
246
247         g_return_val_if_fail(num_min-1<num_min,-1);
248         g_return_val_if_fail(num_min<=num_max,num_min-1);
249         g_return_val_if_fail(LONG_MIN<num_min,LONG_MIN);
250         g_return_val_if_fail(num_max<LONG_MAX,num_min-1);
251
252         if (!(string=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,attr_name)))
253                 return num_min-1;
254         r=strtol((const char *)string,&ends,0);
255         xmlFree(string);
256         if (r<num_min || r>num_max) {
257                 g_warning(_("%s: Numer of out range %ld..%ld: %ld"),captive_captivemodid_pathname,num_min,num_max,r);
258                 return num_min-1;
259                 }
260         return r;
261 }
262
263 static void captive_captivemodid_load_module_xml
264                 (CaptiveCaptivemodidObject *captivemodid,const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader)
265 {
266 struct captive_captivemodid_module *module;
267 xmlChar *cabinet_used_string;
268
269         captive_new0(module);
270         if (!(module->type=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"type")))
271                 goto fail_free_module;
272         if (!(module->md5 =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"md5")))
273                 goto fail_free_module;
274         if (strlen((const char *)module->md5)!=strspn((const char *)module->md5,"0123456789abcdef")) {
275                 g_warning(_("%s: Attribute 'md5' can be only lower-cased hexstring: %s"),captive_captivemodid_pathname,module->md5);
276                 goto fail_free_module;
277                 }
278         if (strlen((const char *)module->md5)!=32) {
279                 g_warning(_("%s: Attribute 'md5' length must be 32: %s"),captive_captivemodid_pathname,module->md5);
280                 goto fail_free_module;
281                 }
282         if (!(module->id  =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"id")))
283                 goto fail_free_module;
284         if (0>=(module->length=captive_captivemodid_load_module_xml_get_attr_l(
285                         captive_captivemodid_pathname,xml_reader,"length",1,G_MAXINT-1)))
286                 goto fail_free_module;
287         if (!(cabinet_used_string=xmlTextReaderGetAttribute(xml_reader,BAD_CAST "cabinet_used")))
288                 module->cabinet_used=0;
289         else {
290                 xmlFree(cabinet_used_string);
291                 if (0>=(module->cabinet_used=captive_captivemodid_load_module_xml_get_attr_l(
292                                 captive_captivemodid_pathname,xml_reader,"cabinet_used",1,G_MAXINT-1)))
293                         goto fail_free_module;
294                 }
295         if (G_MININT>=(module->priority=captive_captivemodid_load_module_xml_get_attr_l(captive_captivemodid_pathname,xml_reader,"priority",
296                         G_MININT+1,G_MAXINT-1)))
297                 goto fail_free_module;
298         captive_captivemodid_load_module(captivemodid,module);
299         return;
300
301 fail_free_module:
302         captive_captivemodid_module_free(module);
303 }
304
305 static void captive_captivemodid_load_foreach
306                 (const xmlChar *type /* key */,gpointer priority_gpointer /* value */,gpointer user_data /* unused */)
307 {
308         g_return_if_fail(type!=NULL);
309
310         g_return_if_fail(captive_captivemodid_module_best_priority_notify!=NULL);
311
312         (*captive_captivemodid_module_best_priority_notify)((const gchar *)type);
313 }
314
315 void (*captive_captivemodid_module_best_priority_notify)(const gchar *module_type);
316
317 CaptiveCaptivemodidObject *captive_captivemodid_load(const gchar *captive_captivemodid_pathname)
318 {
319 CaptiveCaptivemodidObject *captivemodid;
320 xmlTextReader *xml_reader;
321
322         if (!(xml_reader=xmlNewTextReaderFilename(captive_captivemodid_pathname)))
323                 return FALSE;
324
325         captivemodid=g_object_new(
326                 CAPTIVE_CAPTIVEMODID_TYPE_OBJECT,       /* object_type */
327                 NULL);  /* first_property_name; FIXME: support properties */
328         captivemodid->pathname_loaded=g_strdup(captive_captivemodid_pathname);
329
330         while (1==xmlTextReaderRead(xml_reader)) {
331                 switch (xmlTextReaderNodeType(xml_reader)) {
332
333                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT:
334                                 break;
335
336                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_SIGNIFICANT_WHITESPACE:
337                                 break;
338
339                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT:    /* Even empty nodes have some '#text'. */
340                                 break;
341
342                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END:     /* We do not track tag ends. */
343                                 break;
344
345                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: {
346 const xmlChar *xml_name;
347
348                                 xml_name=xmlTextReaderName(xml_reader);
349                                 /**/ if (!xmlStrcmp(xml_name,BAD_CAST "modid")) {       /* root tag */
350                                         }
351                                 else if (!xmlStrcmp(xml_name,BAD_CAST "module"))
352                                         captive_captivemodid_load_module_xml(captivemodid,captive_captivemodid_pathname,xml_reader);
353                                 else g_warning(_("%s: Unknown ELEMENT node: %s"),captive_captivemodid_pathname,xml_name);
354                                 xmlFree((xmlChar *)xml_name);
355                                 } break;
356
357                         default: g_assert_not_reached();
358                         }
359                 }
360         xmlFreeTextReader(xml_reader);
361
362         if (captive_captivemodid_module_best_priority_notify) {
363                 g_hash_table_foreach(captivemodid->module_type_best_priority_hash,
364                                                 (GHFunc)captive_captivemodid_load_foreach,
365                                                 captivemodid);  /* user_data */
366                 }
367
368         return captivemodid;
369 }
370
371 CaptiveCaptivemodidObject *captive_captivemodid_load_default(gboolean fatal)
372 {
373 CaptiveCaptivemodidObject *captivemodid=NULL;
374 const gchar *pathname_default=G_STRINGIFY(SYSCONFDIR) "/w32-mod-id.captivemodid.xml";
375 const gchar *msg;
376
377         if ((captivemodid=captive_captivemodid_load(pathname_default)))
378                 return captivemodid;
379         if ((captivemodid=captive_captivemodid_load("./w32-mod-id.captivemodid.xml")))
380                 return captivemodid;
381         msg=_("Unable to load modid database: %s");
382         if (fatal)
383                 g_error(msg,pathname_default);
384         g_warning(msg,pathname_default);
385         return NULL;
386 }
387
388 const gchar *captive_captivemodid_get_pathname_loaded(CaptiveCaptivemodidObject *captivemodid)
389 {
390         return captivemodid->pathname_loaded;
391 }
392
393 gchar *captive_calc_md5(gconstpointer base,size_t length)
394 {
395 unsigned char md5_bin[1+128/8]; /* 128 bits==16 bytes; '1+' for leading stub to prevent shorter output of BN_bn2hex() */
396 BIGNUM *bignum;
397 char *hex;
398 gchar *r,*gs;
399
400         /* already done above */
401         /* Calculate MD5 sum and convert it to hex string: */
402         MD5(base,length,md5_bin+1);
403         md5_bin[0]=0xFF;  /* stub to prevent shorter output of BN_bn2hex() */
404         bignum=BN_bin2bn(md5_bin,1+128/8,NULL);
405         hex=BN_bn2hex(bignum);
406         g_assert(strlen(hex)==2*(1+128/8));
407         r=g_strdup(hex+2);
408         OPENSSL_free(hex);
409         BN_free(bignum);
410
411         g_assert(strlen(r)==32);
412         for (gs=r;*gs;gs++) {
413                 g_assert(isxdigit(*gs));
414                 *gs=tolower(*gs);
415                 g_assert(isxdigit(*gs));
416                 }
417         return r;
418 }