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