--- /dev/null
+/*
+ * Europagate, 1995
+ *
+ * Z39.50 API for the Email gateway - YAZ version
+ *
+ * $Log: zaccess-yaz.c,v $
+ * Revision 1.1 1995/04/17 11:26:53 quinn
+ * Added YAZ version of zaccess
+ *
+ *
+ */
+
+/*
+ * Interface to the Z39.50 toolkit.
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include <gw-log.h>
+#include <proto.h>
+#include <comstack.h>
+#include <tcpip.h>
+#ifdef USE_XTIMOSI
+#include <xmosi.h>
+#endif
+#include <oid.h>
+
+#include <ccl.h>
+#include <zaccess.h>
+
+struct zass /* Z-assoc */
+{
+ COMSTACK ass; /* comstack association handle */
+ ODR encode;
+ ODR decode;
+ int fd; /* low-level socket (for select only) */
+ int maxrecordsize;
+ int preferredmessagesize;
+ char *outbuf; /* intermediary buffer */
+ char *inbuf;
+ int inbuflen;
+};
+
+static Z_APDU *get_apdu(struct zass *z)
+{
+ int res;
+ Z_APDU *ap;
+
+ if ((res = cs_get(z->ass, &z->inbuf, &z->inbuflen)) <= 0)
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "cs_get failed");
+ return 0;
+ }
+ odr_reset(z->decode);
+ odr_setbuf(z->decode, z->inbuf, res);
+ if (!z_APDU(z->decode, &ap, 0))
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "decode: %s",
+ odr_errlist[odr_geterror(z->decode)]);
+ return 0;
+ }
+ return ap;
+}
+
+static int send_apdu(struct zass *z, Z_APDU *a)
+{
+ char *buf;
+ int len;
+
+ if (!z_APDU(z->encode, &a, 0))
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "encoding initreq");
+ return -1;
+ }
+ buf = odr_getbuf(z->encode, &len);
+ if (cs_put(z->ass, buf, len) < 0)
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "cs_put");
+ return -1;
+ }
+ odr_reset(z->encode);
+ return 0;
+}
+
+static int send_initreq(struct zass *p)
+{
+ Z_APDU a;
+ Z_InitRequest init;
+ Odr_bitmask options, protocolVersion;
+ char name[512];
+
+ a.which = Z_APDU_initRequest;
+ a.u.initRequest = &init;
+ init.referenceId = 0;
+ init.options = &options;
+ ODR_MASK_ZERO(&options);
+ ODR_MASK_SET(&options, Z_Options_search);
+ ODR_MASK_SET(&options, Z_Options_present);
+ ODR_MASK_SET(&options, Z_Options_delSet);
+ init.protocolVersion = &protocolVersion;
+ ODR_MASK_ZERO(&protocolVersion);
+ ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_1);
+ ODR_MASK_SET(&protocolVersion, Z_ProtocolVersion_2);
+ init.preferredMessageSize = &p->preferredmessagesize;
+ init.maximumRecordSize = &p->maxrecordsize;
+ init.idAuthentication = 0;
+ init.implementationId = ZASS_ID;
+ sprintf(name, "%s (YAZ protocol layer)", ZASS_NAME);
+ init.implementationName = name;
+ init.implementationVersion = ZASS_VERSION;
+ init.userInformationField = 0;
+ if (send_apdu(p, &a) < 0)
+ return -1;
+ return 0;
+}
+
+static int receive_initres(struct zass *p)
+{
+ Z_APDU *ap;
+ Z_InitResponse *res;
+
+ if (!(ap = get_apdu(p)))
+ return -1;
+ if (ap->which != Z_APDU_initResponse)
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "bad APDU");
+ return -1;
+ }
+ res = ap->u.initResponse;
+ if (!*res->result)
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Negative result on initres");
+ return -1;
+ }
+ p->preferredmessagesize = *res->preferredMessageSize;
+ p->maxrecordsize = *res->maximumRecordSize;
+ if (res->implementationId)
+ gw_log(GW_LOG_NOTICE, ZASS_TYPE, "imp. ID : %s",
+ res->implementationId);
+ if (res->implementationName)
+ gw_log(GW_LOG_NOTICE, ZASS_TYPE, "imp. ID : %s",
+ res->implementationName);
+ if (res->implementationVersion)
+ gw_log(GW_LOG_NOTICE, ZASS_TYPE, "imp. ID : %s",
+ res->implementationVersion);
+ gw_log(ZASS_DEBUG, ZASS_TYPE, "Initialized ok");
+ return 0;
+}
+
+ZASS zass_open(char *host, int port)
+{
+ struct zass *p;
+ char addstr[512];
+ void *address;
+
+ if (!(p = malloc(sizeof(*p))))
+ {
+ gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "malloc");
+ return 0;
+ }
+ if (!(p->encode = odr_createmem(ODR_ENCODE)) ||
+ !(p->decode = odr_createmem(ODR_DECODE)))
+ {
+ gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "odr_createmem");
+ return 0;
+ }
+ p->maxrecordsize = ZASS_MAXRECORDSIZE;
+ p->preferredmessagesize = ZASS_PREFERREDMESSAGESIZE;
+ if (!(p->outbuf = malloc(p->maxrecordsize + 1024)))
+ {
+ gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "malloc");
+ return 0;
+ }
+ odr_setbuf(p->encode, p->outbuf, p->maxrecordsize + 1024);
+ if (!(p->ass = cs_create(tcpip_type, 1, CS_Z3950)))
+ {
+ gw_log(GW_LOG_FATAL|GW_LOG_ERRNO, ZASS_TYPE, "cs_create");
+ return 0;
+ }
+ sprintf(addstr, "%s:%d", host, port);
+ if (!(address = tcpip_strtoaddr(addstr)))
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "failed to resolve %s", addstr);
+ return 0;
+ }
+ p->fd = cs_fileno(p->ass);
+ gw_log(ZASS_DEBUG, ZASS_TYPE, "Connecting to %s", addstr);
+ if (cs_connect(p->ass, address) < 0)
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "Failed to connect to %s", addstr);
+ return 0;
+ }
+ gw_log(ZASS_DEBUG, ZASS_TYPE, "connected ok");
+ if (send_initreq(p) < 0 || receive_initres(p) < 0)
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "Failed to initialize");
+ return 0;
+ }
+ gw_log(ZASS_DEBUG, ZASS_TYPE, "Sent init request");
+ p->inbuf = 0;
+ p->inbuflen = 0;
+ return p;
+}
+
+static Z_RPNStructure *rpn2rpn(ODR o, struct ccl_rpn_node *q)
+{
+ Z_RPNStructure *r = odr_malloc(o, sizeof(*r));
+ Z_AttributesPlusTerm *t;
+ struct ccl_rpn_attr *i;
+ int len;
+ static int op[] = { Z_Operator_and, Z_Operator_or, Z_Operator_and_not };
+
+ switch (q->kind)
+ {
+ case CCL_RPN_TERM:
+ r->which = Z_RPNStructure_simple;
+ r->u.simple = odr_malloc(o, sizeof(Z_Operand));
+ r->u.simple->which = Z_Operand_APT;
+ r->u.simple->u.attributesPlusTerm = t = odr_malloc(o, sizeof(*t));
+ t->term = odr_malloc(o, sizeof(Z_Term));
+ t->term->which = Z_Term_general;
+ t->term->u.general = odr_malloc(o, sizeof(Odr_oct));
+ t->term->u.general->len = t->term->u.general->size =
+ strlen(q->u.t.term);
+ t->term->u.general->buf = odr_malloc(o, t->term->u.general->size);
+ memcpy(t->term->u.general->buf, q->u.t.term,
+ t->term->u.general->len);
+ t->num_attributes = 0;
+ t->attributeList = odr_malloc(o, sizeof(Z_AttributeElement*) * 100);
+ for (i = q->u.t.attr_list; i && t->num_attributes < 100;
+ i = i->next)
+ {
+ Z_AttributeElement *a;
+
+ t->attributeList[t->num_attributes++] = a =
+ odr_malloc(o, sizeof(*a));
+ a->attributeType = odr_malloc(o, sizeof(int));
+ *a->attributeType = i->type;
+ a->attributeValue = odr_malloc(o, sizeof(*a));
+ *a->attributeValue = i->value;
+ }
+ return r;
+ case CCL_RPN_SET:
+ r->which = Z_RPNStructure_simple;
+ r->u.simple = odr_malloc(o, sizeof(Z_Operand));
+ r->u.simple->which = Z_Operand_resultSetId;
+ r->u.simple->u.resultSetId = odr_malloc(o, len =
+ strlen(q->u.setname));
+ memcpy(r->u.simple->u.resultSetId, q->u.setname, len);
+ return r;
+ case CCL_RPN_AND: case CCL_RPN_OR: case CCL_RPN_NOT:
+ r->which = Z_RPNStructure_complex;
+ r->u.complex = odr_malloc(o, sizeof(Z_Complex));
+ if (!(r->u.complex->s1 = rpn2rpn(o, q->u.p[0])) ||
+ !(r->u.complex->s2 = rpn2rpn(o, q->u.p[1])))
+ return 0;
+ r->u.complex->operator = odr_malloc(o, sizeof(Z_Operator));
+ r->u.complex->operator->which = op[q->kind];
+ r->u.complex->operator->u.and = "";
+ return r;
+ default:
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "Bad operator in RPN");
+ return 0;
+ }
+}
+
+static const struct zass_searchent *search_result(ZASS a)
+{
+ static struct zass_searchent r;
+ Z_APDU *apdu;
+ Z_SearchResponse *res;
+
+ if (!(apdu = get_apdu(a)))
+ return 0;
+ if (apdu->which != Z_APDU_searchResponse)
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "Expected searchresponse, got #%d",
+ apdu->which);
+ return 0;
+ }
+ res = apdu->u.searchResponse;
+ r.status = *res->searchStatus;
+ r.num = *res->resultCount;
+ r.status = res->resultSetStatus ? *res->resultSetStatus : 0;
+ r.errcode = -1;
+ *r.errstring = '\0';
+ if (res->records)
+ {
+ if (res->records->which != Z_Records_NSD)
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Unexpected record types in SchR");
+ else
+ {
+ oident *id;
+ Z_DiagRec *dr = res->records->u.nonSurrogateDiagnostic;
+
+ if (!(id = oid_getentbyoid(dr->diagnosticSetId)) ||
+ id->class != CLASS_DIAGSET || id->value != VAL_BIB1)
+ gw_log(GW_LOG_WARN, ZASS_TYPE,
+ "Missing or unknown diagset - ignoring error!");
+ else
+ {
+ r.errcode = *dr->condition;
+ if (dr->addinfo)
+ {
+ strncpy(r.errstring, dr->addinfo, 512);
+ r.errstring[511] = '\0';
+ }
+ }
+ }
+ }
+ return &r;
+}
+
+const struct zass_searchent *zass_search(ZASS a, struct ccl_rpn_node *query,
+ char *resname, char *databases)
+{
+ Z_Query q;
+ Z_RPNQuery rpnq;
+ Z_APDU apdu;
+ Z_SearchRequest req;
+ oident bib1;
+ int smallSetUpperBound = 0;
+ int largeSetLowerBound = 1;
+ int mediumSetPresentNumber = 0;
+ int replaceIndicator = 1;
+ char *datab[100];
+
+ apdu.which = Z_APDU_searchRequest;
+ apdu.u.searchRequest = &req;
+ req.referenceId = 0;
+ req.smallSetUpperBound = &smallSetUpperBound;
+ req.largeSetLowerBound = &largeSetLowerBound;
+ req.mediumSetPresentNumber = &mediumSetPresentNumber;
+ req.replaceIndicator = &replaceIndicator;
+ req.resultSetName = resname;
+ req.num_databaseNames = 0;
+ req.databaseNames = datab;
+ while (*databases && req.num_databaseNames < 100)
+ {
+ char *p = databases;
+ int more;
+
+ while (*p && !isspace(*p))
+ p++;
+ if (isspace(*p))
+ more = 1;
+ else
+ more = 0;
+ *p = '\0';
+ if (p - databases)
+ {
+ req.databaseNames[req.num_databaseNames] = odr_malloc(a->encode,
+ (p - databases) + 1);
+ strcpy(req.databaseNames[req.num_databaseNames++], databases);
+ }
+ databases = p + more;
+ }
+ req.smallSetElementSetNames = 0;
+ req.mediumSetElementSetNames = 0;
+ req.preferredRecordSyntax = 0;
+ req.query = &q;
+ q.which = Z_Query_type_1;
+ q.u.type_1 = &rpnq;
+ bib1.proto = PROTO_Z3950;
+ bib1.class = CLASS_ATTSET;
+ bib1.value = VAL_BIB1;
+ rpnq.attributeSetId = oid_getoidbyent(&bib1);
+ if (!(rpnq.RPNStructure = rpn2rpn(a->encode, query)))
+ return 0;
+ if (send_apdu(a, &apdu) < 0)
+ return 0;
+
+ return search_result(a);
+}
+
+/*
+ * Triple indirection - that's kinda heavy. We'll fix it later.
+ * There are worse things around, though. Like ZDist.
+ */
+void get_diagrec(zass_record ***p, Z_DiagRec *r)
+{
+ **p = malloc(sizeof(***p));
+ (**p)->next = 0;
+ (**p)->errcode = *r->condition;
+ if (r->addinfo)
+ {
+ strncpy((**p)->errstring, r->addinfo, 200);
+ (**p)->errstring[200] = 0;
+ }
+ else
+ (**p)->errstring[0] = '\0';
+ (**p)->which = ZASS_REC_DIAG;
+ *p = &(**p)->next;
+}
+
+void get_responserecords(zass_record ***p, Z_NamePlusRecordList *recs)
+{
+ int i;
+
+ for (i = 0; i < recs->num_records; i++)
+ {
+ Z_NamePlusRecord *record;
+
+ record = recs->records[i];
+ if (record->which == Z_NamePlusRecord_surrogateDiagnostic)
+ get_diagrec(p, record->u.surrogateDiagnostic);
+ else
+ {
+ Z_DatabaseRecord *r = record->u.databaseRecord;
+ oident *recform;
+
+ **p = malloc(sizeof(***p));
+ (**p)->next = 0;
+
+ if (!(recform = oid_getentbyoid(r->direct_reference)) ||
+ recform->class != CLASS_RECSYN)
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Unknown or bad record syntax");
+ (**p)->which = ZASS_REC_UNKNOWN;
+ }
+ else
+ switch (recform->value)
+ {
+ case VAL_USMARC: (**p)->which = ZASS_REC_USMARC; break;
+ default:
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Unknown recsyn");
+ (**p)->which = ZASS_REC_UNKNOWN;
+ }
+ if (r->which != ODR_EXTERNAL_octet)
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Record wasn't octet-aligned");
+ (**p)->record = 0;
+ }
+ else
+ {
+ if (!((**p)->record = malloc(r->u.octet_aligned->len + 1)))
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "malloc");
+ return;
+ }
+ memcpy((**p)->record, r->u.octet_aligned->buf,
+ r->u.octet_aligned->len);
+ (**p)->record[r->u.octet_aligned->len] = '\0';
+ gw_log(ZASS_DEBUG, ZASS_TYPE, "Got a record of %d bytes",
+ r->u.octet_aligned->len);
+ }
+ }
+ (*p) = &(**p)->next;
+ }
+}
+
+static void zass_records_free(zass_record *p)
+{
+}
+
+static int send_present(ZASS a, char *name, int start, int num,
+ enum oid_value form)
+{
+ Z_APDU apdu;
+ Z_PresentRequest req;
+ oident recsyn;
+
+ apdu.which = Z_APDU_presentRequest;
+ apdu.u.presentRequest = &req;
+ req.referenceId = 0;
+ req.resultSetId = name;
+ req.resultSetStartPoint = &start;
+ req.numberOfRecordsRequested = #
+ req.elementSetNames = 0;
+ recsyn.proto = PROTO_Z3950;
+ recsyn.class = CLASS_RECSYN;
+ recsyn.value = form;
+ req.preferredRecordSyntax = oid_getoidbyent(&recsyn);
+ return send_apdu(a, &apdu);
+}
+
+/*
+ * Note that 1== first record.
+ */
+const struct zass_presentent *zass_present(ZASS a, char *resname, int start,
+ int num)
+{
+ static struct zass_presentent r;
+ zass_record **rec = &r.records;
+
+ r.num = 0;
+ if (r.records)
+ {
+ zass_records_free(r.records);
+ r.records = 0;
+ }
+ do
+ {
+ Z_APDU *apdu;
+ Z_PresentResponse *res;
+
+ gw_log(ZASS_DEBUG, ZASS_TYPE,
+ "Fetching %d records from # %d", num - r.num, start);
+ if (send_present(a, resname, start, num - r.num, VAL_USMARC) < 0)
+ return 0;
+ if (!(apdu = get_apdu(a)))
+ return 0;
+ if (apdu->which != Z_APDU_presentResponse)
+ {
+ gw_log(GW_LOG_FATAL, ZASS_TYPE, "Expected presentresponse, got #%d",
+ apdu->which);
+ return 0;
+ }
+ res = apdu->u.presentResponse;
+ r.presentstatus = *res->presentStatus;
+ r.num += *res->numberOfRecordsReturned;
+ if (*res->numberOfRecordsReturned == 0)
+ {
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Got 0 records from target");
+ return 0;
+ }
+ r.nextpos = *res->nextResultSetPosition;
+ start = r.nextpos;
+ switch (res->records->which)
+ {
+ case Z_Records_DBOSD:
+ get_responserecords(&rec,
+ res->records->u.databaseOrSurDiagnostics);
+ break;
+ case Z_Records_NSD:
+ get_diagrec(&rec, res->records->u.nonSurrogateDiagnostic);
+ break;
+ default:
+ gw_log(GW_LOG_WARN, ZASS_TYPE, "Bad tag in response rec.");
+ return 0;
+ }
+ }
+ while (num - r.num && start);
+ return &r;
+}