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