ab54a948013e4981edd1e516402e68f124ed52f5
[yaz-moved-to-github.git] / server / seshigh.c
1 /*
2  * Copyright (c) 1995, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: seshigh.c,v $
7  * Revision 1.58  1996-02-20 12:53:04  quinn
8  * Chanes to SCAN
9  *
10  * Revision 1.57  1996/01/02  08:57:47  quinn
11  * Changed enums in the ASN.1 .h files to #defines. Changed oident.class to oclass
12  *
13  * Revision 1.56  1995/12/14  11:09:57  quinn
14  * Work on Explain
15  *
16  * Revision 1.55  1995/11/08  17:41:37  quinn
17  * Smallish.
18  *
19  * Revision 1.54  1995/11/08  15:11:29  quinn
20  * Log of close transmit.
21  *
22  * Revision 1.53  1995/11/01  13:54:58  quinn
23  * Minor adjustments
24  *
25  * Revision 1.52  1995/11/01  12:19:13  quinn
26  * Second attempt to fix same bug.
27  *
28  * Revision 1.50  1995/10/25  16:58:32  quinn
29  * Simple.
30  *
31  * Revision 1.49  1995/10/16  13:51:53  quinn
32  * Changes to provide Especs to the backend.
33  *
34  * Revision 1.48  1995/10/06  08:51:20  quinn
35  * Added Write-buffer.
36  *
37  * Revision 1.47  1995/08/29  14:24:16  quinn
38  * Added second half of close-handshake
39  *
40  * Revision 1.46  1995/08/29  11:17:58  quinn
41  * Added code to receive close
42  *
43  * Revision 1.45  1995/08/21  09:11:00  quinn
44  * Smallish fixes to suppport new formats.
45  *
46  * Revision 1.44  1995/08/17  12:45:25  quinn
47  * Fixed minor problems with GRS-1. Added support in c&s.
48  *
49  * Revision 1.43  1995/08/15  12:00:31  quinn
50  * Updated External
51  *
52  * Revision 1.42  1995/08/15  11:16:50  quinn
53  *
54  * Revision 1.41  1995/08/02  10:23:06  quinn
55  * Smallish
56  *
57  * Revision 1.40  1995/07/31  14:34:26  quinn
58  * Fixed bug in process_searchResponse (numberOfRecordsReturned).
59  *
60  * Revision 1.39  1995/06/27  13:21:00  quinn
61  * SUTRS support
62  *
63  * Revision 1.38  1995/06/19  12:39:11  quinn
64  * Fixed bug in timeout code. Added BER dumper.
65  *
66  * Revision 1.37  1995/06/16  13:16:14  quinn
67  * Fixed Defaultdiagformat.
68  *
69  * Revision 1.36  1995/06/16  10:31:36  quinn
70  * Added session timeout.
71  *
72  * Revision 1.35  1995/06/15  07:45:14  quinn
73  * Moving to v3.
74  *
75  * Revision 1.34  1995/06/14  15:26:46  quinn
76  * *** empty log message ***
77  *
78  * Revision 1.33  1995/06/06  14:57:05  quinn
79  * Better diagnostics.
80  *
81  * Revision 1.32  1995/06/06  08:41:44  quinn
82  * Better diagnostics.
83  *
84  * Revision 1.31  1995/06/06  08:15:37  quinn
85  * Cosmetic.
86  *
87  * Revision 1.30  1995/06/05  10:53:32  quinn
88  * Added a better SCAN.
89  *
90  * Revision 1.29  1995/06/01  11:25:03  quinn
91  * Smallish.
92  *
93  * Revision 1.28  1995/06/01  11:21:01  quinn
94  * Attempting to fix a bug in pack-records. replaced break with continue
95  * for large records, according to standard.
96  *
97  * Revision 1.27  1995/05/29  08:12:06  quinn
98  * Moved oid to util
99  *
100  * Revision 1.26  1995/05/18  13:02:12  quinn
101  * Smallish.
102  *
103  * Revision 1.25  1995/05/17  08:42:26  quinn
104  * Transfer auth info to backend. Allow backend to reject init gracefully.
105  *
106  * Revision 1.24  1995/05/16  08:51:04  quinn
107  * License, documentation, and memory fixes
108  *
109  * Revision 1.23  1995/05/15  13:25:10  quinn
110  * Fixed memory bug.
111  *
112  * Revision 1.22  1995/05/15  11:56:39  quinn
113  * Asynchronous facilities. Restructuring of seshigh code.
114  *
115  * Revision 1.21  1995/05/02  08:53:19  quinn
116  * Trying in vain to fix comm with ISODE
117  *
118  * Revision 1.20  1995/04/20  15:13:00  quinn
119  * Cosmetic
120  *
121  * Revision 1.19  1995/04/18  08:15:34  quinn
122  * Added dynamic memory allocation on encoding (whew). Code is now somewhat
123  * neater. We'll make the same change for decoding one day.
124  *
125  * Revision 1.18  1995/04/17  11:28:25  quinn
126  * Smallish
127  *
128  * Revision 1.17  1995/04/10  10:23:36  quinn
129  * Some work to add scan and other things.
130  *
131  * Revision 1.16  1995/03/31  09:18:55  quinn
132  * Added logging.
133  *
134  * Revision 1.15  1995/03/30  14:03:23  quinn
135  * Added RFC1006 as separate library
136  *
137  * Revision 1.14  1995/03/30  12:18:17  quinn
138  * Fixed bug.
139  *
140  * Revision 1.13  1995/03/30  09:09:24  quinn
141  * Added state-handle and some support for asynchronous activities.
142  *
143  * Revision 1.12  1995/03/29  15:40:16  quinn
144  * Ongoing work. Statserv is now dynamic by default
145  *
146  * Revision 1.11  1995/03/28  09:16:21  quinn
147  * Added record packing to the search request
148  *
149  * Revision 1.10  1995/03/27  08:34:24  quinn
150  * Added dynamic server functionality.
151  * Released bindings to session.c (is now redundant)
152  *
153  * Revision 1.9  1995/03/22  15:01:26  quinn
154  * Adjusting record packing.
155  *
156  * Revision 1.8  1995/03/22  10:13:21  quinn
157  * Working on record packer
158  *
159  * Revision 1.7  1995/03/21  15:53:31  quinn
160  * Little changes.
161  *
162  * Revision 1.6  1995/03/21  12:30:09  quinn
163  * Beginning to add support for record packing.
164  *
165  * Revision 1.5  1995/03/17  10:44:13  quinn
166  * Added catch of null-string in makediagrec
167  *
168  * Revision 1.4  1995/03/17  10:18:08  quinn
169  * Added memory management.
170  *
171  * Revision 1.3  1995/03/16  17:42:39  quinn
172  * Little changes
173  *
174  * Revision 1.2  1995/03/16  13:29:01  quinn
175  * Partitioned server.
176  *
177  * Revision 1.1  1995/03/15  16:02:10  quinn
178  * Modded session.c to seshigh.c
179  *
180  */
181
182 /*
183  * Frontend server logic.
184  *
185  * This code receives incoming APDUs, and handles client requests by means
186  * of the backend API.
187  *
188  * Some of the code is getting quite involved, compared to simpler servers -
189  * primarily because it is asynchronous both in the communication with
190  * the user and the backend. We think the complexity will pay off in
191  * the form of greater flexibility when more asynchronous facilities
192  * are implemented.
193  *
194  * Memory management has become somewhat involved. In the simple case, where
195  * only one PDU is pending at a time, it will simply reuse the same memory,
196  * once it has found its working size. When we enable multiple concurrent
197  * operations, perhaps even with multiple parallel calls to the backend, it
198  * will maintain a pool of buffers for encoding and decoding, trying to
199  * minimize memory allocation/deallocation during normal operation.
200  *
201  * TODOs include (and will be done in order of public interest):
202  * 
203  * Support for EXPLAIN - provide simple meta-database system.
204  * Support for access control.
205  * Support for resource control.
206  * Support for extended services - primarily Item Order.
207  * Rest of Z39.50-1994
208  *
209  */
210
211 #include <stdlib.h>
212 #include <stdio.h>
213 #include <unistd.h>
214 #include <assert.h>
215 #include <sys/time.h>
216
217 #include <xmalloc.h>
218 #include <comstack.h>
219 #include <eventl.h>
220 #include <session.h>
221 #include <proto.h>
222 #include <oid.h>
223 #include <log.h>
224 #include <statserv.h>
225
226 #include <backend.h>
227
228 static int process_request(association *assoc);
229 void backend_response(IOCHAN i, int event);
230 static int process_response(association *assoc, request *req, Z_APDU *res);
231 static Z_APDU *process_initRequest(association *assoc, request *reqb);
232 static Z_APDU *process_searchRequest(association *assoc, request *reqb,
233     int *fd);
234 static Z_APDU *response_searchRequest(association *assoc, request *reqb,
235     bend_searchresult *bsrt, int *fd);
236 static Z_APDU *process_presentRequest(association *assoc, request *reqb,
237     int *fd);
238 static Z_APDU *process_scanRequest(association *assoc, request *reqb, int *fd);
239 static void process_close(association *assoc, request *reqb);
240
241 static FILE *apduf = 0; /* for use in static mode */
242 static statserv_options_block *control_block = 0;
243
244 /*
245  * Create and initialize a new association-handle.
246  *  channel  : iochannel for the current line.
247  *  link     : communications channel.
248  * Returns: 0 or a new association handle.
249  */
250 association *create_association(IOCHAN channel, COMSTACK link)
251 {
252     association *new;
253
254     if (!control_block)
255         control_block = statserv_getcontrol();
256     if (!(new = xmalloc(sizeof(*new))))
257         return 0;
258     new->client_chan = channel;
259     new->client_link = link;
260     if (!(new->decode = odr_createmem(ODR_DECODE)) ||
261         !(new->encode = odr_createmem(ODR_ENCODE)))
262         return 0;
263     if (*control_block->apdufile)
264     {
265         char filename[256];
266         FILE *f;
267
268         strcpy(filename, control_block->apdufile);
269         if (!(new->print = odr_createmem(ODR_PRINT)))
270             return 0;
271         if (*control_block->apdufile != '-')
272         {
273             strcpy(filename, control_block->apdufile);
274             if (!control_block->dynamic)
275             {
276                 if (!apduf)
277                 {
278                     if (!(apduf = fopen(filename, "w")))
279                     {
280                         logf(LOG_WARN|LOG_ERRNO, "%s", filename);
281                         return 0;
282                     }
283                     setvbuf(apduf, 0, _IONBF, 0);
284                 }
285                 f = apduf;
286             }
287             else 
288             {
289                 sprintf(filename + strlen(filename), ".%d", getpid());
290                 if (!(f = fopen(filename, "w")))
291                 {
292                     logf(LOG_WARN|LOG_ERRNO, "%s", filename);
293                     return 0;
294                 }
295                 setvbuf(f, 0, _IONBF, 0);
296             }
297             odr_setprint(new->print, f);
298         }
299     }
300     else
301         new->print = 0;
302     new->input_buffer = 0;
303     new->input_buffer_len = 0;
304     new->backend = 0;
305     new->state = ASSOC_NEW;
306     request_initq(&new->incoming);
307     request_initq(&new->outgoing);
308     new->proto = cs_getproto(link);
309     return new;
310 }
311
312 /*
313  * Free association and release resources.
314  */
315 void destroy_association(association *h)
316 {
317     odr_destroy(h->decode);
318     odr_destroy(h->encode);
319     if (h->print)
320         odr_destroy(h->print);
321     if (h->input_buffer)
322     xfree(h->input_buffer);
323     if (h->backend)
324         bend_close(h->backend);
325     while (request_deq(&h->incoming));
326     while (request_deq(&h->outgoing));
327    xfree(h);
328 }
329
330 static void do_close(association *a, int reason, char *message)
331 {
332     Z_APDU apdu;
333     Z_Close *cls = zget_Close(a->encode);
334     request *req = request_get();
335
336     /* Purge request queue */
337     while (request_deq(&a->incoming));
338     while (request_deq(&a->outgoing));
339     if (a->version >= 3)
340     {
341         logf(LOG_LOG, "Sending Close PDU, reason=%d, message=%s",
342             reason, message ? message : "none");
343         apdu.which = Z_APDU_close;
344         apdu.u.close = cls;
345         *cls->closeReason = reason;
346         cls->diagnosticInformation = message;
347         process_response(a, req, &apdu);
348         iochan_settimeout(a->client_chan, 60);
349     }
350     else
351     {
352         logf(LOG_DEBUG, "v2 client. No Close PDU");
353         iochan_setevent(a->client_chan, EVENT_TIMEOUT); /* force imm close */
354     }
355     a->state = ASSOC_DEAD;
356 }
357
358 /*
359  * This is where PDUs from the client are read and the further
360  * processing is initiated. Flow of control moves down through the
361  * various process_* functions below, until the encoded result comes back up
362  * to the output handler in here.
363  * 
364  *  h     : the I/O channel that has an outstanding event.
365  *  event : the current outstanding event.
366  */
367 void ir_session(IOCHAN h, int event)
368 {
369     int res;
370     association *assoc = iochan_getdata(h);
371     COMSTACK conn = assoc->client_link;
372     request *req;
373
374     assert(h && conn && assoc);
375     if (event == EVENT_TIMEOUT)
376     {
377         if (assoc->state != ASSOC_UP)
378         {
379             logf(LOG_LOG, "Final timeout - closing connection.");
380             cs_close(conn);
381             destroy_association(assoc);
382             iochan_destroy(h);
383         }
384         else
385         {
386             logf(LOG_LOG, "Session idle too long. Sending close.");
387             do_close(assoc, Z_Close_lackOfActivity, 0);
388         }
389         return;
390     }
391     if (event & EVENT_INPUT || event & EVENT_WORK) /* input */
392     {
393         if (event & EVENT_INPUT)
394         {
395             logf(LOG_DEBUG, "ir_session (input)");
396             assert(assoc && conn);
397             /* We aren't speaking to this fellow */
398             if (assoc->state == ASSOC_DEAD)
399             {
400                 logf(LOG_LOG, "Closed connection after reject");
401                 cs_close(conn);
402                 destroy_association(assoc);
403                 iochan_destroy(h);
404                 return;
405             }
406             if ((res = cs_get(conn, &assoc->input_buffer,
407                 &assoc->input_buffer_len)) <= 0)
408             {
409                 logf(LOG_LOG, "Connection closed by client");
410                 cs_close(conn);
411                 destroy_association(assoc);
412                 iochan_destroy(h);
413                 return;
414             }
415             else if (res == 1) /* incomplete read - wait for more  */
416                 return;
417             if (cs_more(conn)) /* more stuff - call us again later, please */
418                 iochan_setevent(h, EVENT_INPUT);
419                 
420             /* we got a complete PDU. Let's decode it */
421             logf(LOG_DEBUG, "Got PDU, %d bytes", res);
422             req = request_get(); /* get a new request structure */
423             odr_reset(assoc->decode);
424             odr_setbuf(assoc->decode, assoc->input_buffer, res, 0);
425             if (!z_APDU(assoc->decode, &req->request, 0))
426             {
427                 logf(LOG_LOG, "ODR error on incoming PDU: %s [near byte %d] ",
428                     odr_errlist[odr_geterror(assoc->decode)],
429                     odr_offset(assoc->decode));
430                 logf(LOG_LOG, "PDU dump:");
431                 odr_dumpBER(log_file(), assoc->input_buffer, res);
432                 do_close(assoc, Z_Close_protocolError, "Malformed package");
433                 return;
434             }
435             req->request_mem = odr_extract_mem(assoc->decode);
436             if (assoc->print && !z_APDU(assoc->print, &req->request, 0))
437             {
438                 logf(LOG_WARN, "ODR print error: %s", 
439                     odr_errlist[odr_geterror(assoc->print)]);
440                 odr_reset(assoc->print);
441             }
442             request_enq(&assoc->incoming, req);
443         }
444
445         /* can we do something yet? */
446         req = request_head(&assoc->incoming);
447         if (req->state == REQUEST_IDLE)
448             if (process_request(assoc) < 0)
449                 do_close(assoc, Z_Close_systemProblem, "Unknown error");
450     }
451     if (event & EVENT_OUTPUT)
452     {
453         request *req = request_head(&assoc->outgoing);
454
455         logf(LOG_DEBUG, "ir_session (output)");
456         req->state = REQUEST_PENDING;
457         switch (res = cs_put(conn, req->response, req->len_response))
458         {
459             case -1:
460                 logf(LOG_LOG, "Connection closed by client");
461                 cs_close(conn);
462                 destroy_association(assoc);
463                 iochan_destroy(h);
464                 break;
465             case 0: /* all sent - release the request structure */
466                 logf(LOG_DEBUG, "Wrote PDU, %d bytes", req->len_response);
467                 nmem_destroy(req->request_mem);
468                 request_deq(&assoc->outgoing);
469                 request_release(req);
470                 if (!request_head(&assoc->outgoing))
471                     iochan_clearflag(h, EVENT_OUTPUT);
472                 break;
473             /* value of 1 -- partial send -- is simply ignored */
474         }
475     }
476     if (event & EVENT_EXCEPT)
477     {
478         logf(LOG_DEBUG, "ir_session (exception)");
479         cs_close(conn);
480         destroy_association(assoc);
481         iochan_destroy(h);
482     }
483 }
484
485 /*
486  * Initiate request processing.
487  */
488 static int process_request(association *assoc)
489 {
490     request *req = request_head(&assoc->incoming);
491     int fd = -1;
492     Z_APDU *res;
493
494     logf(LOG_DEBUG, "process_request");
495     assert(req && req->state == REQUEST_IDLE);
496     switch (req->request->which)
497     {
498         case Z_APDU_initRequest:
499             res = process_initRequest(assoc, req); break;
500         case Z_APDU_searchRequest:
501             res = process_searchRequest(assoc, req, &fd); break;
502         case Z_APDU_presentRequest:
503             res = process_presentRequest(assoc, req, &fd); break;
504         case Z_APDU_scanRequest:
505             res = process_scanRequest(assoc, req, &fd); break;
506         case Z_APDU_close:
507             process_close(assoc, req); return 0;
508         default:
509             logf(LOG_WARN, "Bad APDU received");
510             return -1;
511     }
512     if (res)
513     {
514         logf(LOG_DEBUG, "  result immediately available");
515         return process_response(assoc, req, res);
516     }
517     else if (fd < 0)
518     {
519         logf(LOG_WARN, "   bad result");
520         return -1;
521     }
522     else /* no result yet - one will be provided later */
523     {
524         IOCHAN chan;
525
526         /* Set up an I/O handler for the fd supplied by the backend */
527
528         logf(LOG_DEBUG, "   establishing handler for result");
529         req->state = REQUEST_PENDING;
530         if (!(chan = iochan_create(fd, backend_response, EVENT_INPUT)))
531             abort();
532         iochan_setdata(chan, assoc);
533         return 0;
534     }
535 }
536
537 /*
538  * Handle message from the backend.
539  */
540 void backend_response(IOCHAN i, int event)
541 {
542     association *assoc = iochan_getdata(i);
543     request *req = request_head(&assoc->incoming);
544     Z_APDU *res;
545     int fd;
546
547     logf(LOG_DEBUG, "backend_response");
548     assert(assoc && req && req->state != REQUEST_IDLE);
549     /* determine what it is we're waiting for */
550     switch (req->request->which)
551     {
552         case Z_APDU_searchRequest:
553             res = response_searchRequest(assoc, req, 0, &fd); break;
554 #if 0
555         case Z_APDU_presentRequest:
556             res = response_presentRequest(assoc, req, 0, &fd); break;
557         case Z_APDU_scanRequest:
558             res = response_scanRequest(assoc, req, 0, &fd); break;
559 #endif
560         default:
561             logf(LOG_WARN, "Serious programmer's lapse or bug");
562             abort();
563     }
564     if ((res && process_response(assoc, req, res) < 0) || fd < 0)
565     {
566         logf(LOG_LOG, "Fatal error when talking to backend");
567         do_close(assoc, Z_Close_systemProblem, 0);
568         iochan_destroy(i);
569         return;
570     }
571     else if (!res) /* no result yet - try again later */
572     {
573         logf(LOG_DEBUG, "   no result yet");
574         iochan_setfd(i, fd); /* in case fd has changed */
575     }
576 }
577
578 /*
579  * Encode response, and transfer the request structure to the outgoing queue.
580  */
581 static int process_response(association *assoc, request *req, Z_APDU *res)
582 {
583     odr_setbuf(assoc->encode, req->response, req->size_response, 1);
584     if (!z_APDU(assoc->encode, &res, 0))
585     {
586         logf(LOG_WARN, "ODR error when encoding response: %s",
587             odr_errlist[odr_geterror(assoc->decode)]);
588         odr_reset(assoc->encode);
589         return -1;
590     }
591     req->response = odr_getbuf(assoc->encode, &req->len_response,
592         &req->size_response);
593     odr_setbuf(assoc->encode, 0, 0, 0); /* don'txfree if we abort later */
594     odr_reset(assoc->encode);
595     if (assoc->print && !z_APDU(assoc->print, &res, 0))
596     {
597         logf(LOG_WARN, "ODR print error: %s", 
598             odr_errlist[odr_geterror(assoc->print)]);
599         odr_reset(assoc->print);
600     }
601     /* change this when we make the backend reentrant */
602     if (req == request_head(&assoc->incoming))
603     {
604         req->state = REQUEST_IDLE;
605         request_deq(&assoc->incoming);
606     }
607     request_enq(&assoc->outgoing, req);
608     /* turn the work over to the ir_session handler */
609     iochan_setflag(assoc->client_chan, EVENT_OUTPUT);
610     /* Is there more work to be done? give that to the input handler too */
611     if (request_head(&assoc->incoming))
612         iochan_setevent(assoc->client_chan, EVENT_WORK);
613     return 0;
614 }
615
616 /*
617  * Handle init request.
618  * At the moment, we don't check the options
619  * anywhere else in the code - we just try not to do anything that would
620  * break a naive client. We'll toss 'em into the association block when
621  * we need them there.
622  */
623 static Z_APDU *process_initRequest(association *assoc, request *reqb)
624 {
625     Z_InitRequest *req = reqb->request->u.initRequest;
626     Z_APDU *apdu = zget_APDU(assoc->encode, Z_APDU_initResponse);
627     Z_InitResponse *resp = apdu->u.initResponse;
628     bend_initrequest binitreq;
629     bend_initresult *binitres;
630     char options[100];
631
632     logf(LOG_LOG, "Got initRequest");
633     if (req->implementationId)
634         logf(LOG_LOG, "Id:        %s", req->implementationId);
635     if (req->implementationName)
636         logf(LOG_LOG, "Name:      %s", req->implementationName);
637     if (req->implementationVersion)
638         logf(LOG_LOG, "Version:   %s", req->implementationVersion);
639
640     binitreq.configname = "default-config";
641     binitreq.auth = req->idAuthentication;
642     if (!(binitres = bend_init(&binitreq)))
643     {
644         logf(LOG_WARN, "Bad response from backend.");
645         return 0;
646     }
647
648     assoc->backend = binitres->handle;
649     resp->referenceId = req->referenceId;
650     *options = '\0';
651     /* let's tell the client what we can do */
652     if (ODR_MASK_GET(req->options, Z_Options_search))
653     {
654         ODR_MASK_SET(resp->options, Z_Options_search);
655         strcat(options, "srch");
656     }
657     if (ODR_MASK_GET(req->options, Z_Options_present))
658     {
659         ODR_MASK_SET(resp->options, Z_Options_present);
660         strcat(options, " prst");
661     }
662 #if 0
663     if (ODR_MASK_GET(req->options, Z_Options_delSet))
664     {
665         ODR_MASK_SET(&options, Z_Options_delSet);
666         strcat(options, " del");
667     }
668 #endif
669     if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
670     {
671         ODR_MASK_SET(resp->options, Z_Options_namedResultSets);
672         strcat(options, " namedresults");
673     }
674     if (ODR_MASK_GET(req->options, Z_Options_scan))
675     {
676         ODR_MASK_SET(resp->options, Z_Options_scan);
677         strcat(options, " scan");
678     }
679     if (ODR_MASK_GET(req->options, Z_Options_concurrentOperations))
680     {
681         ODR_MASK_SET(resp->options, Z_Options_concurrentOperations);
682         strcat(options, " concurop");
683     }
684
685     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_1))
686     {
687         ODR_MASK_SET(resp->protocolVersion, Z_ProtocolVersion_1);
688         assoc->version = 2; /* 1 & 2 are equivalent */
689     }
690     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_2))
691     {
692         ODR_MASK_SET(resp->protocolVersion, Z_ProtocolVersion_2);
693         assoc->version = 2;
694     }
695     if (ODR_MASK_GET(req->protocolVersion, Z_ProtocolVersion_3))
696     {
697         ODR_MASK_SET(resp->protocolVersion, Z_ProtocolVersion_3);
698         assoc->version = 3;
699     }
700     logf(LOG_LOG, "Negotiated to v%d: %s", assoc->version, options);
701     assoc->maximumRecordSize = *req->maximumRecordSize;
702     if (assoc->maximumRecordSize > control_block->maxrecordsize)
703         assoc->maximumRecordSize = control_block->maxrecordsize;
704     assoc->preferredMessageSize = *req->preferredMessageSize;
705     if (assoc->preferredMessageSize > assoc->maximumRecordSize)
706         assoc->preferredMessageSize = assoc->maximumRecordSize;
707     resp->preferredMessageSize = &assoc->preferredMessageSize;
708     resp->maximumRecordSize = &assoc->maximumRecordSize;
709     resp->implementationName = "Index Data/YAZ Generic Frontend Server";
710     if (binitres->errcode)
711     {
712         logf(LOG_LOG, "Connection rejected by backend.");
713         *resp->result = 0;
714         assoc->state = ASSOC_DEAD;
715     }
716     else
717         assoc->state = ASSOC_UP;
718     return apdu;
719 }
720
721 /*
722  * These functions should be merged.
723  */
724
725 /*
726  * nonsurrogate diagnostic record.
727  */
728 static Z_Records *diagrec(oid_proto proto, int error, char *addinfo)
729 {
730     static Z_Records rec;
731     oident bib1;
732     static int err;
733 #ifdef Z_95
734     static Z_DiagRec drec;
735     static Z_DefaultDiagFormat dr;
736 #else
737     static Z_DiagRec dr;
738 #endif
739
740     bib1.proto = proto;
741     bib1.oclass = CLASS_DIAGSET;
742     bib1.value = VAL_BIB1;
743
744     logf(LOG_DEBUG, "Diagnostic: %d -- %s", error, addinfo ? addinfo :
745         "NULL");
746     err = error;
747     rec.which = Z_Records_NSD;
748 #ifdef Z_95
749     rec.u.nonSurrogateDiagnostic = &drec;
750     drec.which = Z_DiagRec_defaultFormat;
751     drec.u.defaultFormat = &dr;
752 #else
753     rec.u.nonSurrogateDiagnostic = &dr;
754 #endif
755     dr.diagnosticSetId = oid_getoidbyent(&bib1);
756     dr.condition = &err;
757     dr.which = Z_DiagForm_v2AddInfo;
758     dr.addinfo = addinfo ? addinfo : "";
759     return &rec;
760 }
761
762 /*
763  * surrogate diagnostic.
764  */
765 static Z_NamePlusRecord *surrogatediagrec(oid_proto proto, char *dbname,
766                                             int error, char *addinfo)
767 {
768     static Z_NamePlusRecord rec;
769     static int err;
770     oident bib1;
771 #ifdef Z_95
772     static Z_DiagRec drec;
773     static Z_DefaultDiagFormat dr;
774 #else
775     static Z_DiagRec dr;
776 #endif
777
778     bib1.proto = proto;
779     bib1.oclass = CLASS_DIAGSET;
780     bib1.value = VAL_BIB1;
781
782     logf(LOG_DEBUG, "SurrogateDiagnotic: %d -- %s", error, addinfo);
783     err = error;
784     rec.databaseName = dbname;
785     rec.which = Z_NamePlusRecord_surrogateDiagnostic;
786 #ifdef Z_95
787     rec.u.surrogateDiagnostic = &drec;
788     drec.which = Z_DiagRec_defaultFormat;
789     drec.u.defaultFormat = &dr;
790 #else
791     rec.u.surrogateDiagnostic = &dr;
792 #endif
793     dr.diagnosticSetId = oid_getoidbyent(&bib1);
794     dr.condition = &err;
795     dr.which = Z_DiagForm_v2AddInfo;
796     dr.addinfo = addinfo ? addinfo : "";
797     return &rec;
798 }
799
800 /*
801  * multiple nonsurrogate diagnostics.
802  */
803 static Z_DiagRecs *diagrecs(oid_proto proto, int error, char *addinfo)
804 {
805     static Z_DiagRecs recs;
806     static int err;
807     oident bib1;
808 #ifdef Z_95
809     static Z_DiagRec *recp[1], drec;
810     static Z_DefaultDiagFormat rec;
811 #else
812     static Z_DiagRec *recp[1], rec;
813 #endif
814
815     logf(LOG_DEBUG, "DiagRecs: %d -- %s", error, addinfo);
816     bib1.proto = proto;
817     bib1.oclass = CLASS_DIAGSET;
818     bib1.value = VAL_BIB1;
819
820     err = error;
821     recs.num_diagRecs = 1;
822     recs.diagRecs = recp;
823 #ifdef Z_95
824     recp[0] = &drec;
825     drec.which = Z_DiagRec_defaultFormat;
826     drec.u.defaultFormat = &rec;
827 #else
828     recp[0] = &rec;
829 #endif
830     rec.diagnosticSetId = oid_getoidbyent(&bib1);
831     rec.condition = &err;
832     rec.which = Z_DiagForm_v2AddInfo;
833     rec.addinfo = addinfo ? addinfo : "";
834     return &recs;
835 }
836
837 #define MAX_RECORDS 256
838
839 static Z_Records *pack_records(association *a, char *setname, int start,
840                                 int *num, Z_RecordComposition *comp,
841                                 int *next, int *pres, oid_value format)
842 {
843     int recno, total_length = 0, toget = *num;
844     static Z_Records records;
845     static Z_NamePlusRecordList reclist;
846     static Z_NamePlusRecord *list[MAX_RECORDS];
847     oident recform;
848
849     records.which = Z_Records_DBOSD;
850     records.u.databaseOrSurDiagnostics = &reclist;
851     reclist.num_records = 0;
852     reclist.records = list;
853     *pres = Z_PRES_SUCCESS;
854     *num = 0;
855     *next = 0;
856
857     logf(LOG_DEBUG, "Request to pack %d+%d", start, toget);
858     logf(LOG_DEBUG, "pms=%d, mrs=%d", a->preferredMessageSize,
859         a->maximumRecordSize);
860     for (recno = start; reclist.num_records < toget; recno++)
861     {
862         bend_fetchrequest freq;
863         bend_fetchresult *fres;
864         Z_NamePlusRecord *thisrec;
865         Z_DatabaseRecord *thisext;
866         int this_length;
867
868         /*
869          * we get the number of bytes allocated on the stream before any
870          * allocation done by the backend - this should give us a reasonable
871          * idea of the total size of the data so far.
872          */
873         total_length = odr_total(a->encode);
874         if (reclist.num_records == MAX_RECORDS - 1)
875         {
876             *pres = Z_PRES_PARTIAL_2;
877             break;
878         }
879         freq.setname = setname;
880         freq.number = recno;
881         freq.comp = comp;
882         freq.format = format;
883         freq.stream = a->encode;
884         if (!(fres = bend_fetch(a->backend, &freq, 0)))
885         {
886             *pres = Z_PRES_FAILURE;
887             return diagrec(a->proto, 2, "Backend interface problem");
888         }
889         /* backend should be able to signal whether error is system-wide
890            or only pertaining to current record */
891         if (fres->errcode)
892         {
893             *pres = Z_PRES_FAILURE;
894             return diagrec(a->proto, fres->errcode, fres->errstring);
895         }
896         if (fres->len >= 0)
897             this_length = fres->len;
898         else
899             this_length = odr_total(a->encode) - total_length;
900         logf(LOG_DEBUG, "  fetched record, len=%d, total=%d",
901             this_length, total_length);
902         if (this_length + total_length > a->preferredMessageSize)
903         {
904             /* record is small enough, really */
905             if (this_length <= a->preferredMessageSize)
906             {
907                 logf(LOG_DEBUG, "  Dropped last normal-sized record");
908                 *pres = Z_PRES_PARTIAL_2;
909                 break;
910             }
911             /* record can only be fetched by itself */
912             if (this_length < a->maximumRecordSize)
913             {
914                 logf(LOG_DEBUG, "  Record > prefmsgsz");
915                 if (toget > 1)
916                 {
917                     logf(LOG_DEBUG, "  Dropped it");
918                     reclist.records[reclist.num_records] =
919                          surrogatediagrec(a->proto, fres->basename, 16, 0);
920                     reclist.num_records++;
921                     continue;
922                 }
923             }
924             else /* too big entirely */
925             {
926                 logf(LOG_DEBUG, "Record > maxrcdsz");
927                 reclist.records[reclist.num_records] =
928                     surrogatediagrec(a->proto, fres->basename, 17, 0);
929                 reclist.num_records++;
930                 continue;
931             }
932         }
933         if (!(thisrec = odr_malloc(a->encode, sizeof(*thisrec))))
934             return 0;
935         if (!(thisrec->databaseName = odr_malloc(a->encode,
936             strlen(fres->basename) + 1)))
937             return 0;
938         strcpy(thisrec->databaseName, fres->basename);
939         thisrec->which = Z_NamePlusRecord_databaseRecord;
940         if (!(thisrec->u.databaseRecord = thisext = odr_malloc(a->encode,
941             sizeof(Z_DatabaseRecord))))
942             return 0;
943         recform.proto = a->proto;
944         recform.oclass = CLASS_RECSYN;
945         recform.value = fres->format;
946         thisext->direct_reference = odr_oiddup(a->encode,
947             oid_getoidbyent(&recform));
948         thisext->indirect_reference = 0;
949         thisext->descriptor = 0;
950         if (fres->len < 0) /* Structured data */
951         {
952             switch (fres->format)
953             {
954                 case VAL_SUTRS: thisext->which = Z_External_sutrs; break;
955                 case VAL_GRS1: thisext->which = Z_External_grs1; break;
956                 case VAL_EXPLAIN: thisext->which = Z_External_explainRecord;
957                     break;
958
959                 default:
960                     logf(LOG_FATAL, "Unknown structured format from backend.");
961                     return 0;
962             }
963
964             /*
965              * We cheat on the pointers here. Obviously, the record field
966              * of the backend-fetch structure should have been a union for
967              * correctness, but we're stuck with this for backwards
968              * compatibility.
969              */
970             thisext->u.grs1 = (Z_GenericRecord*) fres->record;
971         }
972         else if (fres->format == VAL_SUTRS) /* SUTRS is a single-ASN.1-type */
973         {
974             Odr_oct *sutrs = odr_malloc(a->encode, sizeof(*sutrs));
975
976             thisext->which = Z_External_sutrs;
977             thisext->u.sutrs = sutrs;
978             sutrs->buf = odr_malloc(a->encode, fres->len);
979             sutrs->len = sutrs->size = fres->len;
980             memcpy(sutrs->buf, fres->record, fres->len);
981         }
982         else /* octet-aligned record. */
983         {
984             thisext->which = Z_External_octet;
985             if (!(thisext->u.octet_aligned = odr_malloc(a->encode,
986                 sizeof(Odr_oct))))
987                 return 0;
988             if (!(thisext->u.octet_aligned->buf = odr_malloc(a->encode,
989                 fres->len)))
990                 return 0;
991             memcpy(thisext->u.octet_aligned->buf, fres->record, fres->len);
992             thisext->u.octet_aligned->len = thisext->u.octet_aligned->size =
993                 fres->len;
994         }
995         reclist.records[reclist.num_records] = thisrec;
996         reclist.num_records++;
997         *next = fres->last_in_set ? 0 : recno + 1;
998     }
999     *num = reclist.num_records;
1000     return &records;
1001 }
1002
1003 static Z_APDU *process_searchRequest(association *assoc, request *reqb,
1004     int *fd)
1005 {
1006     Z_SearchRequest *req = reqb->request->u.searchRequest;
1007     bend_searchrequest bsrq;
1008     bend_searchresult *bsrt;
1009
1010     logf(LOG_LOG, "Got SearchRequest.");
1011
1012     bsrq.setname = req->resultSetName;
1013     bsrq.replace_set = *req->replaceIndicator;
1014     bsrq.num_bases = req->num_databaseNames;
1015     bsrq.basenames = req->databaseNames;
1016     bsrq.query = req->query;
1017
1018     if (!(bsrt = bend_search(assoc->backend, &bsrq, fd)))
1019         return 0;
1020     return response_searchRequest(assoc, reqb, bsrt, fd);
1021 }
1022
1023 bend_searchresult *bend_searchresponse(void *handle) {return 0;}
1024
1025 /*
1026  * Prepare a searchresponse based on the backend results. We probably want
1027  * to look at making the fetching of records nonblocking as well, but
1028  * so far, we'll keep things simple.
1029  * If bsrt is null, that means we're called in response to a communications
1030  * event, and we'll have to get the response for ourselves.
1031  */
1032 static Z_APDU *response_searchRequest(association *assoc, request *reqb,
1033     bend_searchresult *bsrt, int *fd)
1034 {
1035     Z_SearchRequest *req = reqb->request->u.searchRequest;
1036     static Z_APDU apdu;
1037     static Z_SearchResponse resp;
1038     static int nulint = 0;
1039     static bool_t sr = 1;
1040     static int next = 0;
1041     static int none = Z_RES_NONE;
1042
1043     apdu.which = Z_APDU_searchResponse;
1044     apdu.u.searchResponse = &resp;
1045     resp.referenceId = req->referenceId;
1046 #ifdef Z_95
1047     resp.additionalSearchInfo = 0;
1048     resp.otherInfo = 0;
1049 #endif
1050     *fd = -1;
1051     if (!bsrt && !(bsrt = bend_searchresponse(assoc->backend)))
1052     {
1053         logf(LOG_FATAL, "Bad result from backend");
1054         return 0;
1055     }
1056     else if (bsrt->errcode)
1057     {
1058         resp.records = diagrec(assoc->proto, bsrt->errcode,
1059             bsrt->errstring);
1060         resp.resultCount = &nulint;
1061         resp.numberOfRecordsReturned = &nulint;
1062         resp.nextResultSetPosition = &nulint;
1063         resp.searchStatus = &nulint;
1064         resp.resultSetStatus = &none;
1065         resp.presentStatus = 0;
1066     }
1067     else
1068     {
1069         static int toget;
1070         Z_RecordComposition comp, *compp = 0;
1071         static int presst = 0;
1072
1073         resp.records = 0;
1074         resp.resultCount = &bsrt->hits;
1075
1076         comp.which = Z_RecordComp_simple;
1077         /* how many records does the user agent want, then? */
1078         if (bsrt->hits <= *req->smallSetUpperBound)
1079         {
1080             toget = bsrt->hits;
1081             if ((comp.u.simple = req->smallSetElementSetNames))
1082                 compp = &comp;
1083         }
1084         else if (bsrt->hits < *req->largeSetLowerBound)
1085         {
1086             toget = *req->mediumSetPresentNumber;
1087             if (toget > bsrt->hits)
1088                 toget = bsrt->hits;
1089             if ((comp.u.simple = req->mediumSetElementSetNames))
1090                 compp = &comp;
1091         }
1092         else
1093             toget = 0;
1094
1095         if (toget && !resp.records)
1096         {
1097             oident *prefformat;
1098             oid_value form;
1099
1100             if (!(prefformat = oid_getentbyoid(req->preferredRecordSyntax)) ||
1101                 prefformat->oclass != CLASS_RECSYN)
1102                 form = VAL_NONE;
1103             else
1104                 form = prefformat->value;
1105             resp.records = pack_records(assoc, req->resultSetName, 1,
1106                 &toget, compp, &next, &presst, form);
1107             if (!resp.records)
1108                 return 0;
1109             resp.numberOfRecordsReturned = &toget;
1110             resp.nextResultSetPosition = &next;
1111             resp.searchStatus = &sr;
1112             resp.resultSetStatus = 0;
1113             resp.presentStatus = &presst;
1114         }
1115         else
1116         {
1117             if (*resp.resultCount)
1118                 next = 1;
1119             resp.numberOfRecordsReturned = &nulint;
1120             resp.nextResultSetPosition = &next;
1121             resp.searchStatus = &sr;
1122             resp.resultSetStatus = 0;
1123             resp.presentStatus = 0;
1124         }
1125     }
1126     return &apdu;
1127 }
1128
1129 /*
1130  * Maybe we got a little over-friendly when we designed bend_fetch to
1131  * get only one record at a time. Some backends can optimise multiple-record
1132  * fetches, and at any rate, there is some overhead involved in
1133  * all that selecting and hopping around. Problem is, of course, that the
1134  * frontend can't know ahead of time how many records it'll need to
1135  * fill the negotiated PDU size. Annoying. Segmentation or not, Z/SR
1136  * is downright lousy as a bulk data transfer protocol.
1137  *
1138  * To start with, we'll do the fetching of records from the backend
1139  * in one operation: To save some trips in and out of the event-handler,
1140  * and to simplify the interface to pack_records. At any rate, asynch
1141  * operation is more fun in operations that have an unpredictable execution
1142  * speed - which is normally more true for search than for present.
1143  */
1144 static Z_APDU *process_presentRequest(association *assoc, request *reqb,
1145     int *fd)
1146 {
1147     Z_PresentRequest *req = reqb->request->u.presentRequest;
1148     static Z_APDU apdu;
1149     static Z_PresentResponse resp;
1150     static int presst, next, num;
1151     oident *prefformat;
1152     oid_value form;
1153
1154
1155     logf(LOG_LOG, "Got PresentRequest.");
1156     apdu.which = Z_APDU_presentResponse;
1157     apdu.u.presentResponse = &resp;
1158     resp.referenceId = req->referenceId;
1159 #ifdef Z_95
1160     resp.otherInfo = 0;
1161 #endif
1162
1163     if (!(prefformat = oid_getentbyoid(req->preferredRecordSyntax)) ||
1164         prefformat->oclass != CLASS_RECSYN)
1165         form = VAL_NONE;
1166     else
1167         form = prefformat->value;
1168     num = *req->numberOfRecordsRequested;
1169     resp.records = pack_records(assoc, req->resultSetId,
1170         *req->resultSetStartPoint, &num, req->recordComposition, &next,
1171         &presst, form);
1172     if (!resp.records)
1173         return 0;
1174     resp.numberOfRecordsReturned = &num;
1175     resp.presentStatus = &presst;
1176     resp.nextResultSetPosition = &next;
1177
1178     return &apdu;
1179 }
1180
1181 /*
1182  * Scan was implemented rather in a hurry, and with support for only the basic
1183  * elements of the service in the backend API. Suggestions are welcome.
1184  */
1185 static Z_APDU *process_scanRequest(association *assoc, request *reqb, int *fd)
1186 {
1187     Z_ScanRequest *req = reqb->request->u.scanRequest;
1188     static Z_APDU apdu;
1189     static Z_ScanResponse res;
1190     static int scanStatus = Z_Scan_failure;
1191     static int numberOfEntriesReturned = 0;
1192     oident *attent;
1193     static Z_ListEntries ents;
1194 #define SCAN_MAX_ENTRIES 200
1195     static Z_Entry *tab[SCAN_MAX_ENTRIES];
1196     bend_scanrequest srq;
1197     bend_scanresult *srs;
1198
1199     logf(LOG_LOG, "Got scanrequest");
1200     apdu.which = Z_APDU_scanResponse;
1201     apdu.u.scanResponse = &res;
1202     res.referenceId = req->referenceId;
1203     res.stepSize = 0;
1204     res.scanStatus = &scanStatus;
1205     res.numberOfEntriesReturned = &numberOfEntriesReturned;
1206     res.positionOfTerm = 0;
1207     res.entries = &ents;
1208     ents.which = Z_ListEntries_nonSurrogateDiagnostics;
1209     res.attributeSet = 0;
1210 #ifdef Z_95
1211     res.otherInfo = 0;
1212 #endif
1213
1214     if (req->attributeSet && (!(attent = oid_getentbyoid(req->attributeSet)) ||
1215         attent->oclass != CLASS_ATTSET || attent->value != VAL_BIB1))
1216         ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 121, 0);
1217     else if (req->stepSize && *req->stepSize > 0)
1218         ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 205, 0);
1219     else
1220     {
1221         if (req->termListAndStartPoint->term->which == Z_Term_general)
1222             logf(LOG_DEBUG, " term: '%.*s'",
1223                 req->termListAndStartPoint->term->u.general->len,
1224                 req->termListAndStartPoint->term->u.general->buf);
1225         srq.num_bases = req->num_databaseNames;
1226         srq.basenames = req->databaseNames;
1227         srq.num_entries = *req->numberOfTermsRequested;
1228         srq.term = req->termListAndStartPoint;
1229         srq.term_position = req->preferredPositionInResponse ?
1230             *req->preferredPositionInResponse : 1;
1231         if (!(srs = bend_scan(assoc->backend, &srq, 0)))
1232             ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto, 2, 0);
1233         else if (srs->errcode)
1234             ents.u.nonSurrogateDiagnostics = diagrecs(assoc->proto,
1235                 srs->errcode, srs->errstring);
1236         else
1237         {
1238             int i;
1239             static Z_Entries list;
1240
1241             if (srs->status == BEND_SCAN_PARTIAL)
1242                 scanStatus = Z_Scan_partial_5;
1243             else
1244                 scanStatus = Z_Scan_success;
1245             ents.which = Z_ListEntries_entries;
1246             ents.u.entries = &list;
1247             list.entries = tab;
1248             for (i = 0; i < srs->num_entries; i++)
1249             {
1250                 Z_Entry *e;
1251                 Z_TermInfo *t;
1252                 Odr_oct *o;
1253
1254                 if (i >= SCAN_MAX_ENTRIES)
1255                 {
1256                     scanStatus = Z_Scan_partial_4;
1257                     break;
1258                 }
1259                 list.entries[i] = e = odr_malloc(assoc->encode, sizeof(*e));
1260                 e->which = Z_Entry_termInfo;
1261                 e->u.termInfo = t = odr_malloc(assoc->encode, sizeof(*t));
1262                 t->suggestedAttributes = 0;
1263                 t->displayTerm = 0;
1264                 t->alternativeTerm = 0;
1265                 t->byAttributes = 0;
1266                 t->otherTermInfo = 0;
1267                 t->globalOccurrences = &srs->entries[i].occurrences;
1268                 t->term = odr_malloc(assoc->encode, sizeof(*t->term));
1269                 t->term->which = Z_Term_general;
1270                 t->term->u.general = o = odr_malloc(assoc->encode,
1271                     sizeof(Odr_oct));
1272                 o->buf = odr_malloc(assoc->encode, o->len = o->size =
1273                     strlen(srs->entries[i].term));
1274                 memcpy(o->buf, srs->entries[i].term, o->len);
1275                 logf(LOG_DEBUG, "  term #%d: '%s' (%d)", i,
1276                     srs->entries[i].term, srs->entries[i].occurrences);
1277             }
1278             list.num_entries = i;
1279             res.numberOfEntriesReturned = &list.num_entries;
1280             res.positionOfTerm = &srs->term_position;
1281         }
1282     }
1283
1284     return &apdu;
1285 }
1286
1287 static void process_close(association *assoc, request *reqb)
1288 {
1289     Z_Close *req = reqb->request->u.close;
1290     static char *reasons[] =
1291     {
1292         "finished",
1293         "shutdown",
1294         "systemProblem",
1295         "costLimit",
1296         "resources",
1297         "securityViolation",
1298         "protocolError",
1299         "lackOfActivity",
1300         "peerAbort",
1301         "unspecified"
1302     };
1303
1304     logf(LOG_LOG, "Got close, reason %s, message %s",
1305         reasons[*req->closeReason], req->diagnosticInformation ?
1306         req->diagnosticInformation : "NULL");
1307     if (assoc->version < 3) /* to make do_force respond with close */
1308         assoc->version = 3;
1309     do_close(assoc, Z_Close_finished, "Association terminated by client");
1310 }