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