Added YAZ version of zaccess
[egate.git] / zlayer / zaccess-yaz.c
1 /*
2  * Europagate, 1995
3  *
4  * Z39.50 API for the Email gateway - YAZ version
5  *
6  * $Log: zaccess-yaz.c,v $
7  * Revision 1.1  1995/04/17 11:26:53  quinn
8  * Added YAZ version of zaccess
9  *
10  *
11  */
12
13 /*
14  * Interface to the Z39.50 toolkit.
15  */
16
17 #include <stdlib.h>
18 #include <assert.h>
19 #include <ctype.h>
20
21 #include <gw-log.h>
22 #include <proto.h>
23 #include <comstack.h>
24 #include <tcpip.h>
25 #ifdef USE_XTIMOSI
26 #include <xmosi.h>
27 #endif
28 #include <oid.h>
29
30 #include <ccl.h>
31 #include <zaccess.h>
32
33 struct zass    /* Z-assoc */
34 {
35     COMSTACK ass;              /* comstack association handle */
36     ODR encode;
37     ODR decode;
38     int fd;                         /* low-level socket (for select only) */
39     int maxrecordsize;
40     int preferredmessagesize;
41     char *outbuf;                      /* intermediary buffer */
42     char *inbuf;
43     int inbuflen;
44 };
45
46 static Z_APDU *get_apdu(struct zass *z)
47 {
48     int res;
49     Z_APDU *ap;
50
51     if ((res = cs_get(z->ass, &z->inbuf, &z->inbuflen)) <= 0)
52     {
53         gw_log(GW_LOG_WARN, ZASS_TYPE, "cs_get failed");
54         return 0;
55     }
56     odr_reset(z->decode);
57     odr_setbuf(z->decode, z->inbuf, res);
58     if (!z_APDU(z->decode, &ap, 0))
59     {
60         gw_log(GW_LOG_WARN, ZASS_TYPE, "decode: %s",
61             odr_errlist[odr_geterror(z->decode)]);
62         return 0;
63     }
64     return ap;
65 }
66
67 static int send_apdu(struct zass *z, Z_APDU *a)
68 {
69     char *buf;
70     int len;
71
72     if (!z_APDU(z->encode, &a, 0))
73     {
74         gw_log(GW_LOG_FATAL, ZASS_TYPE, "encoding initreq");
75         return -1;
76     }
77     buf = odr_getbuf(z->encode, &len);
78     if (cs_put(z->ass, buf, len) < 0)
79     {
80         gw_log(GW_LOG_FATAL, ZASS_TYPE, "cs_put");
81         return -1;
82     }
83     odr_reset(z->encode);
84     return 0;
85 }
86
87 static int send_initreq(struct zass *p)
88 {
89     Z_APDU a;
90     Z_InitRequest init;
91     Odr_bitmask options, protocolVersion;
92     char name[512];
93
94     a.which = Z_APDU_initRequest;
95     a.u.initRequest = &init;
96     init.referenceId = 0;
97     init.options = &options;
98     ODR_MASK_ZERO(&options);
99     ODR_MASK_SET(&options, Z_Options_search);
100     ODR_MASK_SET(&options, Z_Options_present);
101     ODR_MASK_SET(&options, Z_Options_delSet);
102     init.protocolVersion = &protocolVersion;
103     ODR_MASK_ZERO(&protocolVersion);
104     ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_1);
105     ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_2);
106     init.preferredMessageSize = &p->preferredmessagesize;
107     init.maximumRecordSize = &p->maxrecordsize;
108     init.idAuthentication = 0;
109     init.implementationId = ZASS_ID;
110     sprintf(name, "%s (YAZ protocol layer)", ZASS_NAME);
111     init.implementationName = name;
112     init.implementationVersion = ZASS_VERSION;
113     init.userInformationField = 0;
114     if (send_apdu(p, &a) < 0)
115         return -1;
116     return 0;
117 }
118
119 static int receive_initres(struct zass *p)
120 {
121     Z_APDU *ap;
122     Z_InitResponse *res;
123
124     if (!(ap = get_apdu(p)))
125         return -1;
126     if (ap->which != Z_APDU_initResponse)
127     {
128         gw_log(GW_LOG_WARN, ZASS_TYPE, "bad APDU");
129         return -1;
130     }
131     res = ap->u.initResponse;
132     if (!*res->result)
133     {
134         gw_log(GW_LOG_WARN, ZASS_TYPE, "Negative result on initres");
135         return -1;
136     }
137     p->preferredmessagesize = *res->preferredMessageSize;
138     p->maxrecordsize = *res->maximumRecordSize;
139     if (res->implementationId)
140         gw_log(GW_LOG_NOTICE, ZASS_TYPE, "imp. ID  : %s",
141             res->implementationId);
142     if (res->implementationName)
143         gw_log(GW_LOG_NOTICE, ZASS_TYPE, "imp. ID  : %s",
144             res->implementationName);
145     if (res->implementationVersion)
146         gw_log(GW_LOG_NOTICE, ZASS_TYPE, "imp. ID  : %s",
147             res->implementationVersion);
148     gw_log(ZASS_DEBUG, ZASS_TYPE, "Initialized ok");
149     return 0;
150 }
151
152 ZASS zass_open(char *host, int port)
153 {
154     struct zass *p;
155     char addstr[512];
156     void *address;
157
158     if (!(p = malloc(sizeof(*p))))
159     {
160         gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "malloc");
161         return 0;
162     }
163     if (!(p->encode = odr_createmem(ODR_ENCODE)) ||
164         !(p->decode = odr_createmem(ODR_DECODE)))
165     {
166         gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "odr_createmem");
167         return 0;
168     }
169     p->maxrecordsize = ZASS_MAXRECORDSIZE;
170     p->preferredmessagesize = ZASS_PREFERREDMESSAGESIZE;
171     if (!(p->outbuf = malloc(p->maxrecordsize + 1024)))
172     {
173         gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "malloc");
174         return 0;
175     }
176     odr_setbuf(p->encode, p->outbuf, p->maxrecordsize + 1024);
177     if (!(p->ass = cs_create(tcpip_type, 1, CS_Z3950)))
178     {
179         gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "cs_create");
180         return 0;
181     }
182     sprintf(addstr, "%s:%d", host, port);
183     if (!(address = tcpip_strtoaddr(addstr)))
184     {
185         gw_log(GW_LOG_FATAL, ZASS_TYPE, "failed to resolve %s", addstr);
186         return 0;
187     }
188     p->fd = cs_fileno(p->ass);
189     gw_log(ZASS_DEBUG, ZASS_TYPE, "Connecting to %s", addstr);
190     if (cs_connect(p->ass, address) < 0)
191     {
192         gw_log(GW_LOG_FATAL, ZASS_TYPE, "Failed to connect to %s", addstr);
193         return 0;
194     }
195     gw_log(ZASS_DEBUG, ZASS_TYPE, "connected ok");
196     if (send_initreq(p) < 0 || receive_initres(p) < 0)
197     {
198         gw_log(GW_LOG_FATAL, ZASS_TYPE, "Failed to initialize");
199         return 0;
200     }
201     gw_log(ZASS_DEBUG, ZASS_TYPE, "Sent init request");
202     p->inbuf = 0;
203     p->inbuflen = 0;
204     return p;
205 }
206
207 static Z_RPNStructure *rpn2rpn(ODR o, struct ccl_rpn_node *q)
208 {
209     Z_RPNStructure *r = odr_malloc(o, sizeof(*r));
210     Z_AttributesPlusTerm *t;
211     struct ccl_rpn_attr *i;
212     int len;
213     static int op[] = { Z_Operator_and, Z_Operator_or, Z_Operator_and_not };
214
215     switch (q->kind)
216     {
217         case CCL_RPN_TERM:
218             r->which = Z_RPNStructure_simple;
219             r->u.simple = odr_malloc(o, sizeof(Z_Operand));
220             r->u.simple->which = Z_Operand_APT;
221             r->u.simple->u.attributesPlusTerm = t = odr_malloc(o, sizeof(*t));
222             t->term = odr_malloc(o, sizeof(Z_Term));
223             t->term->which = Z_Term_general;
224             t->term->u.general = odr_malloc(o, sizeof(Odr_oct));
225             t->term->u.general->len = t->term->u.general->size =
226                 strlen(q->u.t.term);
227             t->term->u.general->buf = odr_malloc(o, t->term->u.general->size);
228             memcpy(t->term->u.general->buf, q->u.t.term,
229                 t->term->u.general->len);
230             t->num_attributes = 0;
231             t->attributeList = odr_malloc(o, sizeof(Z_AttributeElement*) * 100);
232             for (i = q->u.t.attr_list; i && t->num_attributes < 100;
233                 i = i->next)
234             {
235                 Z_AttributeElement *a;
236                 
237                 t->attributeList[t->num_attributes++] = a =
238                     odr_malloc(o, sizeof(*a));
239                 a->attributeType = odr_malloc(o, sizeof(int));
240                 *a->attributeType = i->type;
241                 a->attributeValue = odr_malloc(o, sizeof(*a));
242                 *a->attributeValue = i->value;
243             }
244             return r;
245         case CCL_RPN_SET:
246             r->which = Z_RPNStructure_simple;
247             r->u.simple = odr_malloc(o, sizeof(Z_Operand));
248             r->u.simple->which = Z_Operand_resultSetId;
249             r->u.simple->u.resultSetId = odr_malloc(o, len =
250                 strlen(q->u.setname));
251             memcpy(r->u.simple->u.resultSetId, q->u.setname, len);
252             return r;
253         case CCL_RPN_AND: case CCL_RPN_OR: case CCL_RPN_NOT:
254             r->which = Z_RPNStructure_complex;
255             r->u.complex = odr_malloc(o, sizeof(Z_Complex));
256             if (!(r->u.complex->s1 = rpn2rpn(o, q->u.p[0])) ||
257                 !(r->u.complex->s2 = rpn2rpn(o, q->u.p[1])))
258                     return 0;
259             r->u.complex->operator = odr_malloc(o, sizeof(Z_Operator));
260             r->u.complex->operator->which = op[q->kind];
261             r->u.complex->operator->u.and = "";
262             return r;
263         default:
264             gw_log(GW_LOG_FATAL, ZASS_TYPE, "Bad operator in RPN");
265             return 0;
266     }
267 }
268
269 static const struct zass_searchent *search_result(ZASS a)
270 {
271     static struct zass_searchent r;
272     Z_APDU *apdu;
273     Z_SearchResponse *res;
274
275     if (!(apdu = get_apdu(a)))
276         return 0;
277     if (apdu->which != Z_APDU_searchResponse)
278     {
279         gw_log(GW_LOG_FATAL, ZASS_TYPE, "Expected searchresponse, got #%d",
280             apdu->which);
281         return 0;
282     }
283     res = apdu->u.searchResponse;
284     r.status = *res->searchStatus;
285     r.num = *res->resultCount;
286     r.status = res->resultSetStatus ? *res->resultSetStatus : 0;
287     r.errcode = -1;
288     *r.errstring = '\0';
289     if (res->records)
290     {
291         if (res->records->which != Z_Records_NSD)
292             gw_log(GW_LOG_WARN, ZASS_TYPE, "Unexpected record types in SchR");
293         else
294         {
295             oident *id;
296             Z_DiagRec *dr = res->records->u.nonSurrogateDiagnostic;
297
298             if (!(id = oid_getentbyoid(dr->diagnosticSetId)) ||
299                 id->class != CLASS_DIAGSET || id->value != VAL_BIB1)
300                     gw_log(GW_LOG_WARN, ZASS_TYPE,
301                         "Missing or unknown diagset - ignoring error!");
302             else
303             {
304                 r.errcode = *dr->condition;
305                 if (dr->addinfo)
306                 {
307                     strncpy(r.errstring, dr->addinfo, 512);
308                     r.errstring[511] = '\0';
309                 }
310             }
311         }
312     }
313     return &r;
314 }
315
316 const struct zass_searchent *zass_search(ZASS a, struct ccl_rpn_node *query,
317     char *resname, char *databases)
318 {
319     Z_Query q;
320     Z_RPNQuery rpnq;
321     Z_APDU apdu;
322     Z_SearchRequest req;
323     oident bib1;
324     int smallSetUpperBound = 0;
325     int largeSetLowerBound = 1;
326     int mediumSetPresentNumber = 0;
327     int replaceIndicator = 1;
328     char *datab[100];
329
330     apdu.which = Z_APDU_searchRequest;
331     apdu.u.searchRequest = &req;
332     req.referenceId = 0;
333     req.smallSetUpperBound = &smallSetUpperBound;
334     req.largeSetLowerBound = &largeSetLowerBound;
335     req.mediumSetPresentNumber = &mediumSetPresentNumber;
336     req.replaceIndicator = &replaceIndicator;
337     req.resultSetName = resname;
338     req.num_databaseNames = 0;
339     req.databaseNames = datab;
340     while (*databases && req.num_databaseNames < 100)
341     {
342         char *p = databases;
343         int more;
344
345         while (*p && !isspace(*p))
346             p++;
347         if (isspace(*p))
348             more = 1;
349         else
350             more = 0;
351         *p = '\0';
352         if (p - databases)
353         {
354             req.databaseNames[req.num_databaseNames] = odr_malloc(a->encode,
355                 (p - databases) + 1);
356             strcpy(req.databaseNames[req.num_databaseNames++], databases);
357         }
358         databases = p + more;
359     }
360     req.smallSetElementSetNames = 0;
361     req.mediumSetElementSetNames = 0;
362     req.preferredRecordSyntax = 0;
363     req.query = &q;
364     q.which = Z_Query_type_1;
365     q.u.type_1 = &rpnq;
366     bib1.proto = PROTO_Z3950;
367     bib1.class = CLASS_ATTSET;
368     bib1.value = VAL_BIB1;
369     rpnq.attributeSetId = oid_getoidbyent(&bib1);
370     if (!(rpnq.RPNStructure = rpn2rpn(a->encode, query)))
371         return 0;
372     if (send_apdu(a, &apdu) < 0)
373         return 0;
374
375     return search_result(a);
376 }
377
378 /*
379  * Triple indirection - that's kinda heavy. We'll fix it later.
380  * There are worse things around, though. Like ZDist.
381  */
382 void get_diagrec(zass_record ***p, Z_DiagRec *r)
383 {
384     **p = malloc(sizeof(***p));
385     (**p)->next = 0;
386     (**p)->errcode = *r->condition;
387     if (r->addinfo)
388     {
389         strncpy((**p)->errstring, r->addinfo, 200);
390         (**p)->errstring[200] = 0;
391     }
392     else
393         (**p)->errstring[0] = '\0';
394     (**p)->which = ZASS_REC_DIAG;
395     *p = &(**p)->next;
396 }
397
398 void get_responserecords(zass_record ***p, Z_NamePlusRecordList *recs)
399 {
400     int i;
401
402     for (i = 0; i < recs->num_records; i++)
403     {
404         Z_NamePlusRecord *record;
405
406         record = recs->records[i];
407         if (record->which == Z_NamePlusRecord_surrogateDiagnostic)
408             get_diagrec(p, record->u.surrogateDiagnostic);
409         else
410         {
411             Z_DatabaseRecord *r = record->u.databaseRecord;
412             oident *recform;
413
414             **p = malloc(sizeof(***p));
415             (**p)->next = 0;
416
417             if (!(recform = oid_getentbyoid(r->direct_reference)) ||
418                  recform->class != CLASS_RECSYN)
419             {
420                 gw_log(GW_LOG_WARN, ZASS_TYPE, "Unknown or bad record syntax");
421                 (**p)->which = ZASS_REC_UNKNOWN;
422             }
423             else
424                 switch (recform->value)
425                 {
426                     case VAL_USMARC: (**p)->which = ZASS_REC_USMARC; break;
427                     default:
428                         gw_log(GW_LOG_WARN, ZASS_TYPE, "Unknown recsyn");
429                         (**p)->which = ZASS_REC_UNKNOWN;
430                 }
431             if (r->which != ODR_EXTERNAL_octet)
432             {
433                 gw_log(GW_LOG_WARN, ZASS_TYPE, "Record wasn't octet-aligned");
434                 (**p)->record = 0;
435             }
436             else
437             {
438                 if (!((**p)->record = malloc(r->u.octet_aligned->len + 1)))
439                 {
440                     gw_log(GW_LOG_FATAL, ZASS_TYPE, "malloc");
441                     return;
442                 }
443                 memcpy((**p)->record, r->u.octet_aligned->buf,
444                     r->u.octet_aligned->len);
445                 (**p)->record[r->u.octet_aligned->len] = '\0';
446                 gw_log(ZASS_DEBUG, ZASS_TYPE, "Got a record of %d bytes",
447                     r->u.octet_aligned->len);
448             }
449         }
450         (*p) = &(**p)->next;
451     }
452 }
453
454 static void zass_records_free(zass_record *p)
455 {
456 }
457
458 static int send_present(ZASS a, char *name, int start, int num,
459     enum oid_value form)
460 {
461     Z_APDU apdu;
462     Z_PresentRequest req;
463     oident recsyn;
464
465     apdu.which = Z_APDU_presentRequest;
466     apdu.u.presentRequest = &req;
467     req.referenceId = 0;
468     req.resultSetId = name;
469     req.resultSetStartPoint = &start;
470     req.numberOfRecordsRequested = &num;
471     req.elementSetNames = 0;
472     recsyn.proto = PROTO_Z3950;
473     recsyn.class = CLASS_RECSYN;
474     recsyn.value = form;
475     req.preferredRecordSyntax = oid_getoidbyent(&recsyn);
476     return send_apdu(a, &apdu);
477 }
478
479 /*
480  * Note that 1== first record.
481  */
482 const struct zass_presentent *zass_present(ZASS a, char *resname, int start,
483     int num)
484 {
485     static struct zass_presentent r;
486     zass_record **rec = &r.records;
487
488     r.num = 0;
489     if (r.records)
490     {
491         zass_records_free(r.records);
492         r.records = 0;
493     }
494     do
495     {
496         Z_APDU *apdu;
497         Z_PresentResponse *res;
498
499         gw_log(ZASS_DEBUG, ZASS_TYPE,
500             "Fetching %d records from # %d", num - r.num, start);
501         if (send_present(a, resname, start, num - r.num, VAL_USMARC) < 0)
502             return 0;
503         if (!(apdu = get_apdu(a)))
504             return 0;
505         if (apdu->which != Z_APDU_presentResponse)
506         {
507             gw_log(GW_LOG_FATAL, ZASS_TYPE, "Expected presentresponse, got #%d",
508                 apdu->which);
509             return 0;
510         }
511         res = apdu->u.presentResponse;
512         r.presentstatus = *res->presentStatus;
513         r.num += *res->numberOfRecordsReturned;
514         if (*res->numberOfRecordsReturned == 0)
515         {
516             gw_log(GW_LOG_WARN, ZASS_TYPE, "Got 0 records from target");
517             return 0;
518         }
519         r.nextpos = *res->nextResultSetPosition;
520         start = r.nextpos;
521         switch (res->records->which)
522         {
523             case Z_Records_DBOSD:
524                 get_responserecords(&rec,
525                     res->records->u.databaseOrSurDiagnostics);
526                 break;
527             case Z_Records_NSD:
528                 get_diagrec(&rec, res->records->u.nonSurrogateDiagnostic);
529                 break;
530             default:
531                 gw_log(GW_LOG_WARN, ZASS_TYPE, "Bad tag in response rec.");
532                 return 0;
533         }
534     }
535     while (num - r.num && start);
536     return &r;
537 }