Move 'captivemodid' library to libcaptive.
[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 gchar *captive_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(captive_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 captive_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 captive_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 captive_captivemodid_load_module(struct captive_captivemodid_module *module)
101 {
102 struct captive_captivemodid_module *module_md5_conflict;
103 gpointer valid_length_value_gpointer;
104
105         module_md5_hash_init();
106         if ((module_md5_conflict=g_hash_table_lookup(module_md5_hash,module->md5))) {
107                 g_warning(_("Ignoring module \"%s\" as it has MD5 conflict with: %s"),
108                                 module->id,module_md5_conflict->id);
109                 return;
110                 }
111         g_hash_table_insert(module_md5_hash,(/* de-const */ xmlChar *)module->md5,module);
112
113         module_valid_length_hash_init();
114         if (!g_hash_table_lookup_extended(module_valid_length_hash,GINT_TO_POINTER(module->length),
115                         NULL,   /* orig_key */
116                         &valid_length_value_gpointer))  /* value */
117                 g_hash_table_insert(module_valid_length_hash,GINT_TO_POINTER(module->length),GINT_TO_POINTER(module->cabinet_used));
118         else {
119                 /* Conflicting 'cabinet_used' values for single 'cabinet size'? */
120                 if (valid_length_value_gpointer && GPOINTER_TO_INT(valid_length_value_gpointer)!=module->cabinet_used)
121                         g_hash_table_insert(module_valid_length_hash,GINT_TO_POINTER(module->length),NULL);
122                 }
123
124         if (strcmp((const char *)module->type,"cabinet")) {
125                 if (module->priority>captive_captivemodid_module_type_best_priority_lookup(module->type)) {
126                         module_type_best_priority_hash_init();
127                         g_hash_table_insert(module_type_best_priority_hash,
128                                         (/* de-const */ xmlChar *)module->type,GINT_TO_POINTER(module->priority));
129                         }
130                 }
131 }
132
133 gboolean captive_captivemodid_module_length_is_valid(GnomeVFSFileSize file_size)
134 {
135 gint file_size_gint;
136
137         if ((GnomeVFSFileSize)(file_size_gint=file_size)!=file_size)    /* Size too big to be valid. */
138                 return FALSE;
139         module_valid_length_hash_init();
140         return g_hash_table_lookup_extended(module_valid_length_hash,GINT_TO_POINTER(file_size_gint),
141                         NULL,   /* orig_key */
142                         NULL);  /* value */
143 }
144
145 gint captive_captivemodid_cabinet_length_to_used(gint cabinet_length)
146 {
147 gpointer valid_length_value_gpointer;
148
149         if (!g_hash_table_lookup_extended(module_valid_length_hash,GINT_TO_POINTER(cabinet_length),
150                         NULL,   /* orig_key */
151                         &valid_length_value_gpointer))  /* value */
152                 return 0;
153         return GPOINTER_TO_INT(valid_length_value_gpointer);
154 }
155
156 struct captive_captivemodid_module *captive_captivemodid_module_md5_lookup(const gchar *file_md5)
157 {
158         g_return_val_if_fail(file_md5!=NULL,NULL);
159
160         module_md5_hash_init();
161         return g_hash_table_lookup(module_md5_hash,file_md5);
162 }
163
164 gint captive_captivemodid_module_type_best_priority_lookup(const xmlChar *module_type)
165 {
166 gpointer r_gpointer;
167 gboolean errbool;
168
169         g_return_val_if_fail(module_type!=NULL,G_MININT);
170
171         module_type_best_priority_hash_init();
172         errbool=g_hash_table_lookup_extended(module_type_best_priority_hash,
173                         module_type,    /* lookup_key */
174                         NULL,   /* orig_key */
175                         &r_gpointer);   /* value */
176         if (!errbool)
177                 return G_MININT;
178
179         return GPOINTER_TO_INT(r_gpointer);
180 }
181
182 /* Returns: TRUE if all modules were found. */
183 gboolean captive_captivemodid_module_type_best_priority_found(const xmlChar *module_type)
184 {
185 gboolean errbool;
186
187         g_return_val_if_fail(module_type!=NULL,FALSE);
188
189         module_type_best_priority_hash_init();
190         errbool=g_hash_table_remove(module_type_best_priority_hash,module_type);
191         g_assert(errbool==TRUE);
192
193         return !g_hash_table_size(module_type_best_priority_hash);
194 }
195
196 static xmlChar *captive_captivemodid_load_module_xml_get_attr
197                 (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name)
198 {
199 xmlChar *r;
200
201         if (!(r=xmlTextReaderGetAttribute(xml_reader,BAD_CAST attr_name))) {
202                 /* FIXME: File line identification? */
203                 g_warning(_("%s: Undefined attributes: %s"),captive_captivemodid_pathname,attr_name);
204                 return NULL;
205                 }
206         return r;
207 }
208
209 static long captive_captivemodid_load_module_xml_get_attr_l
210                 (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name,long num_min,long num_max)
211 {
212 xmlChar *string;
213 long r;
214 char *ends;
215
216         g_return_val_if_fail(num_min-1<num_min,-1);
217         g_return_val_if_fail(num_min<=num_max,num_min-1);
218         g_return_val_if_fail(LONG_MIN<num_min,LONG_MIN);
219         g_return_val_if_fail(num_max<LONG_MAX,num_min-1);
220
221         if (!(string=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,attr_name)))
222                 return num_min-1;
223         r=strtol((const char *)string,&ends,0);
224         xmlFree(string);
225         if (r<num_min || r>num_max) {
226                 g_warning(_("%s: Numer of out range %ld..%ld: %ld"),captive_captivemodid_pathname,num_min,num_max,r);
227                 return num_min-1;
228                 }
229         return r;
230 }
231
232 static void captive_captivemodid_load_module_xml(const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader)
233 {
234 struct captive_captivemodid_module *module;
235 xmlChar *cabinet_used_string;
236
237         captive_new0(module);
238         if (!(module->type=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"type")))
239                 goto fail_free_module;
240         if (!(module->md5 =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"md5")))
241                 goto fail_free_module;
242         if (strlen((const char *)module->md5)!=strspn((const char *)module->md5,"0123456789abcdef")) {
243                 g_warning(_("%s: Attribute 'md5' can be only lower-cased hexstring: %s"),captive_captivemodid_pathname,module->md5);
244                 goto fail_free_module;
245                 }
246         if (strlen((const char *)module->md5)!=32) {
247                 g_warning(_("%s: Attribute 'md5' length must be 32: %s"),captive_captivemodid_pathname,module->md5);
248                 goto fail_free_module;
249                 }
250         if (!(module->id  =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"id")))
251                 goto fail_free_module;
252         if (0>=(module->length=captive_captivemodid_load_module_xml_get_attr_l(
253                         captive_captivemodid_pathname,xml_reader,"length",1,G_MAXINT-1)))
254                 goto fail_free_module;
255         if (!(cabinet_used_string=xmlTextReaderGetAttribute(xml_reader,BAD_CAST "cabinet_used")))
256                 module->cabinet_used=0;
257         else {
258                 xmlFree(cabinet_used_string);
259                 if (0>=(module->cabinet_used=captive_captivemodid_load_module_xml_get_attr_l(
260                                 captive_captivemodid_pathname,xml_reader,"cabinet_used",1,G_MAXINT-1)))
261                         goto fail_free_module;
262                 }
263         if (G_MININT>=(module->priority=captive_captivemodid_load_module_xml_get_attr_l(captive_captivemodid_pathname,xml_reader,"priority",
264                         G_MININT+1,G_MAXINT-1)))
265                 goto fail_free_module;
266         captive_captivemodid_load_module(module);
267         return;
268
269 fail_free_module:
270         xmlFree((xmlChar *)module->type);
271         xmlFree((xmlChar *)module->md5);
272         xmlFree((xmlChar *)module->id);
273         g_free(module);
274 }
275
276 static void captive_captivemodid_load_foreach
277                 (const xmlChar *type /* key */,gpointer priority_gpointer /* value */,gpointer user_data /* unused */)
278 {
279         g_return_if_fail(type!=NULL);
280
281         g_return_if_fail(captive_captivemodid_module_best_priority_notify!=NULL);
282
283         (*captive_captivemodid_module_best_priority_notify)((const gchar *)type);
284 }
285
286 void (*captive_captivemodid_module_best_priority_notify)(const gchar *module_type);
287
288 gboolean captive_captivemodid_load(const gchar *captive_captivemodid_pathname)
289 {
290 xmlTextReader *xml_reader;
291
292         if (!(xml_reader=xmlNewTextReaderFilename(captive_captivemodid_pathname)))
293                 return FALSE;
294         while (1==xmlTextReaderRead(xml_reader)) {
295                 switch (xmlTextReaderNodeType(xml_reader)) {
296
297                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT:
298                                 break;
299
300                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_SIGNIFICANT_WHITESPACE:
301                                 break;
302
303                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT:    /* Even empty nodes have some '#text'. */
304                                 break;
305
306                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END:     /* We do not track tag ends. */
307                                 break;
308
309                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: {
310 const xmlChar *xml_name;
311
312                                 xml_name=xmlTextReaderName(xml_reader);
313                                 /**/ if (!xmlStrcmp(xml_name,BAD_CAST "modid")) {       /* root tag */
314                                         }
315                                 else if (!xmlStrcmp(xml_name,BAD_CAST "module"))
316                                         captive_captivemodid_load_module_xml(captive_captivemodid_pathname,xml_reader);
317                                 else g_warning(_("%s: Unknown ELEMENT node: %s"),captive_captivemodid_pathname,xml_name);
318                                 xmlFree((xmlChar *)xml_name);
319                                 } break;
320
321                         default: g_assert_not_reached();
322                         }
323                 }
324         xmlFreeTextReader(xml_reader);
325
326         if (captive_captivemodid_module_best_priority_notify) {
327                 g_hash_table_foreach(module_type_best_priority_hash,
328                                                 (GHFunc)captive_captivemodid_load_foreach,NULL);
329                 }
330         return TRUE;
331 }