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