Implemented pz:allow (turn access to targets on or off)
[pazpar2-moved-to-github.git] / src / database.c
1 /* $Id: database.c,v 1.10 2007-04-11 04:33:41 quinn Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 #include <libxml/parser.h>
23 #include <libxml/tree.h>
24 #include <libxslt/xslt.h>
25 #include <libxslt/transform.h>
26 #include <libxslt/xsltutils.h>
27 #include <assert.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30
31 #include "pazpar2.h"
32 #include "config.h"
33 #include "settings.h"
34 #include "http.h"
35 #include "zeerex.h"
36
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <netdb.h>
40 #include <netinet/in.h>
41
42 static struct host *hosts = 0;  // The hosts we know about 
43 static struct database *databases = 0; // The databases we know about
44 static NMEM nmem = 0;
45
46 static xmlDoc *get_explain_xml(const char *id)
47 {
48     struct stat st;
49     char *dir;
50     char path[256];
51     char ide[256];
52     if (!config || !config->targetprofiles)
53     {
54         yaz_log(YLOG_WARN, "Config must be loaded and specify targetprofiles");
55         return 0;
56     }
57     if (config->targetprofiles->type != Targetprofiles_local)
58     {
59         yaz_log(YLOG_FATAL, "Only supports local type");
60         return 0;
61     }
62     dir = config->targetprofiles->src;
63     urlencode(id, ide);
64     sprintf(path, "%s/%s", dir, ide);
65     if (!stat(path, &st))
66         return xmlParseFile(path);
67     else
68         return 0;
69 }
70
71 // Create a new host structure for hostport
72 static struct host *create_host(const char *hostport)
73 {
74     struct addrinfo *addrinfo, hints;
75     struct host *host;
76     char *port;
77     char ipport[128];
78     unsigned char addrbuf[4];
79     int res;
80
81     host = xmalloc(sizeof(struct host));
82     host->hostport = xstrdup(hostport);
83     host->connections = 0;
84
85     if ((port = strchr(hostport, ':')))
86         *(port++) = '\0';
87     else
88         port = "210";
89
90     hints.ai_flags = 0;
91     hints.ai_family = PF_INET;
92     hints.ai_socktype = SOCK_STREAM;
93     hints.ai_protocol = IPPROTO_TCP;
94     hints.ai_addrlen = 0;
95     hints.ai_addr = 0;
96     hints.ai_canonname = 0;
97     hints.ai_next = 0;
98     // This is not robust code. It assumes that getaddrinfo always
99     // returns AF_INET address.
100     if ((res = getaddrinfo(hostport, port, &hints, &addrinfo)))
101     {
102         yaz_log(YLOG_WARN, "Failed to resolve %s: %s", hostport, gai_strerror(res));
103         xfree(host->hostport);
104         xfree(host);
105         return 0;
106     }
107     assert(addrinfo->ai_family == PF_INET);
108     memcpy(addrbuf, &((struct sockaddr_in*)addrinfo->ai_addr)->sin_addr.s_addr, 4);
109     sprintf(ipport, "%u.%u.%u.%u:%s",
110             addrbuf[0], addrbuf[1], addrbuf[2], addrbuf[3], port);
111     host->ipport = xstrdup(ipport);
112     freeaddrinfo(addrinfo);
113     host->next = hosts;
114     hosts = host;
115     return host;
116 }
117
118 static struct host *find_host(const char *hostport)
119 {
120     struct host *p;
121     for (p = hosts; p; p = p->next)
122         if (!strcmp(p->hostport, hostport))
123             return p;
124     return create_host(hostport);
125 }
126
127 static struct database *load_database(const char *id)
128 {
129     xmlDoc *doc = get_explain_xml(id);
130     struct zr_explain *explain = 0;
131     struct database *db;
132     struct host *host;
133     char hostport[256];
134     char *dbname;
135
136     if (!nmem)
137         nmem = nmem_create();
138     if (doc)
139     {
140         explain = zr_read_xml(nmem, xmlDocGetRootElement(doc));
141         if (!explain)
142             return 0;
143     }
144     if (strlen(id) > 255)
145         return 0;
146     strcpy(hostport, id);
147     if ((dbname = strchr(hostport, '/')))
148         *(dbname++) = '\0';
149     else
150         dbname = "Default";
151     if (!(host = find_host(hostport)))
152         return 0;
153     db = nmem_malloc(nmem, sizeof(*db));
154     memset(db, 0, sizeof(*db));
155     db->host = host;
156     db->url = nmem_strdup(nmem, id);
157     db->name = 0;
158     db->databases = xmalloc(2 * sizeof(char *));
159     db->databases[0] = nmem_strdup(nmem, dbname);
160     db->databases[1] = 0;
161     db->errors = 0;
162     db->explain = explain;
163     db->settings = 0;
164     db->next = databases;
165     db->ccl_map = 0;
166     db->yaz_marc = 0;
167     db->map = 0;
168     databases = db;
169
170     return db;
171 }
172
173 // Return a database structure by ID. Load and add to list if necessary
174 // new==1 just means we know it's not in the list
175 struct database *find_database(const char *id, int new)
176 {
177     struct database *p;
178     if (!new)
179     {
180         for (p = databases; p; p = p->next)
181             if (!strcmp(p->url, id))
182                 return p;
183     }
184     return load_database(id);
185 }
186
187 // This whole session_grep database thing should be moved to pazpar2.c
188
189 int match_zurl(const char *zurl, const char *pattern)
190 {
191     if (!strcmp(pattern, "*"))
192         return 1;
193     else if (!strncmp(pattern, "*/", 2))
194     {
195         char *db = strchr(zurl, '/');
196         if (!db)
197             return 0;
198         if (!strcmp(pattern + 2, db))
199             return 1;
200         else
201             return 0;
202     }
203     else if (!strcmp(pattern, zurl))
204         return 1;
205     else
206         return 0;
207 }
208
209 // This will be generalized at some point
210 static int match_criterion(struct setting **settings, struct database_criterion *c)
211 {
212     int offset = settings_offset(c->name);
213     struct database_criterion_value *v;
214
215     if (offset < 0)
216     {
217         yaz_log(YLOG_WARN, "Criterion not found: %s", c->name);
218         return 0;
219     }
220     if (!settings[offset])
221         return 0;
222     for (v = c->values; v; v = v->next)
223     {
224         if (offset == PZ_ID)
225         {
226             if (match_zurl(settings[offset]->value, v->value))
227                 return 1;
228             else
229                 return 0;
230         }
231         else 
232         {
233             if (!strcmp(settings[offset]->value, v->value))
234                 return 1;
235             else
236                 return 0;
237         }
238     }
239     return 0;
240 }
241
242 int database_match_criteria(struct setting **settings, struct database_criterion *cl)
243 {
244     if (settings[PZ_ALLOW] && *settings[PZ_ALLOW]->value == '0')
245         return 0;
246     for (; cl; cl = cl->next)
247         if (!match_criterion(settings, cl))
248             break;
249     if (cl) // one of the criteria failed to match -- skip this db
250         return 0;
251     else
252         return 1;
253 }
254
255 // Cycles through databases, calling a handler function on the ones for
256 // which all criteria matched.
257 int session_grep_databases(struct session *se, struct database_criterion *cl,
258         void (*fun)(void *context, struct session_database *db))
259 {
260     struct session_database *p;
261     int i = 0;
262
263     for (p = se->databases; p; p = p->next)
264         if (database_match_criteria(p->settings, cl))
265         {
266             (*fun)(se, p);
267             i++;
268         }
269     return i;
270 }
271
272 int grep_databases(void *context, struct database_criterion *cl,
273         void (*fun)(void *context, struct database *db))
274 {
275     struct database *p;
276     int i = 0;
277
278     for (p = databases; p; p = p->next)
279         if (database_match_criteria(p->settings, cl))
280         {
281             (*fun)(context, p);
282             i++;
283         }
284     return i;
285 }
286
287 // Initialize CCL map for a target
288 // Note: This approach ignores user-specific CCL maps, for which I
289 // don't presently see any application.
290 static void prepare_cclmap(void *ignore, struct database *db)
291 {
292     struct setting *s;
293
294     if (!db->settings)
295         return;
296     db->ccl_map = ccl_qual_mk();
297     for (s = db->settings[PZ_CCLMAP]; s; s = s->next)
298         if (!*s->user)
299         {
300             char *p = strchr(s->name + 3, ':');
301             if (!p)
302             {
303                 yaz_log(YLOG_FATAL, "Malformed cclmap name: %s", s->name);
304                 exit(1);
305             }
306             p++;
307             ccl_qual_fitem(db->ccl_map, s->value, p);
308         }
309 }
310
311 // Initialize YAZ Map structures for MARC-based targets
312 static void prepare_yazmarc(void *ignore, struct database *db)
313 {
314     struct setting *s;
315
316     if (!db->settings)
317         return;
318     for (s = db->settings[PZ_NATIVESYNTAX]; s; s = s->next)
319         if (!*s->user && !strcmp(s->value, "iso2709"))
320         {
321             char *encoding = "marc-8s";
322             yaz_iconv_t cm;
323
324             db->yaz_marc = yaz_marc_create();
325             yaz_marc_subfield_str(db->yaz_marc, "\t");
326             // See if a native encoding is specified
327             for (s = db->settings[PZ_ENCODING]; s; s = s->next)
328                 if (!*s->user)
329                 {
330                     encoding = s->value;
331                     break;
332                 }
333             if (!(cm = yaz_iconv_open("utf-8", encoding)))
334             {
335                 yaz_log(YLOG_FATAL, "Unable to map from %s to UTF-8", encoding);
336                 exit(1);
337             }
338             yaz_marc_iconv(db->yaz_marc, cm);
339             break;
340         }
341 }
342
343 // Prepare XSLT stylesheets for record normalization
344 static void prepare_map(void *ignore, struct database *db)
345 {
346     struct setting *s;
347
348     if (!db->settings)
349         return;
350     for (s = db->settings[PZ_XSLT]; s; s = s->next)
351         if (!*s->user)
352         {
353             char **stylesheets;
354             struct database_retrievalmap **m = &db->map;
355             int num, i;
356
357             nmem_strsplit(nmem, ",", s->value, &stylesheets, &num);
358             for (i = 0; i < num; i++)
359             {
360                 (*m) = nmem_malloc(nmem, sizeof(**m));
361                 (*m)->next = 0;
362                 if (!((*m)->stylesheet = conf_load_stylesheet(stylesheets[i])))
363                 {
364                     yaz_log(YLOG_FATAL, "Unable to load stylesheet: %s",
365                             stylesheets[i]);
366                     exit(1);
367                 }
368                 m = &(*m)->next;
369             }
370             break;
371         }
372     if (!s)
373         yaz_log(YLOG_WARN, "No Normalization stylesheet for target %s", db->url);
374 }
375
376 // Read settings for each database, and prepare support data structures
377 void prepare_databases(void)
378 {
379     grep_databases(0, 0, prepare_cclmap);
380     grep_databases(0, 0, prepare_yazmarc);
381     grep_databases(0, 0, prepare_map);
382 }
383
384 // This function will most likely vanish when a proper target profile mechanism is
385 // introduced.
386 void load_simpletargets(const char *fn)
387 {
388     FILE *f = fopen(fn, "r");
389     char line[256];
390
391     if (!f)
392     {
393         yaz_log(YLOG_WARN|YLOG_ERRNO, "open %s", fn);
394         exit(1);
395     }
396
397     while (fgets(line, 255, f))
398     {
399         char *url;
400         char *name;
401         struct database *db;
402
403         if (strncmp(line, "target ", 7))
404             continue;
405         line[strlen(line) - 1] = '\0';
406
407         if ((name = strchr(line, ';')))
408             *(name++) = '\0';
409
410         url = line + 7;
411
412         if (!(db = find_database(url, 0)))
413             yaz_log(YLOG_WARN, "Unable to load database %s", url);
414         if (name && db)
415             db->name = nmem_strdup(nmem, name);
416     }
417     fclose(f);
418 }
419
420
421 /*
422  * Local variables:
423  * c-basic-offset: 4
424  * indent-tabs-mode: nil
425  * End:
426  * vim: shiftwidth=4 tabstop=8 expandtab
427  */