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