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