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