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