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