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