1 /*
2  * Copyright (c) 1994-2009, 2013-2014 Paul Mattes.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the names of Paul Mattes nor the names of his contributors
13  *       may be used to endorse or promote products derived from this software
14  *       without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /*
29  *	sf.c
30  *		This module handles 3270 structured fields.
31  *
32  */
33 
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include "globals.h"
38 #include "3270ds.h"
39 #include <string.h>
40 
41 #include "ctlrc.h"
42 #if !defined(PR3287) /*[*/
43 # include "ft_dft.h"
44 #endif /*]*/
45 #include "sf.h"
46 #if defined(_WIN32) /*[*/
47 # include "ws2tcpip.h"
48 #else /*][*/
49 # include <sys/socket.h>
50 #endif /*]*/
51 #include "telnet_core.h"
52 #include "trace.h"
53 
54 /* Statics */
55 static bool  qr_in_progress = false;
56 static enum pds sf_read_part(unsigned char buf[], unsigned buflen);
57 static enum pds sf_erase_reset(unsigned char buf[], int buflen);
58 static enum pds sf_set_reply_mode(unsigned char buf[], int buflen);
59 static enum pds sf_outbound_ds(unsigned char buf[], int buflen);
60 static void query_reply_start(void);
61 static void do_query_reply(unsigned char code);
62 static void query_reply_end(void);
63 
64 /* Some permanent substitutions. */
65 #define maxROWS 72
66 #define maxCOLS 66
67 #define char_width 10
68 #define char_height 20
69 #define standard_font 0
70 
71 static unsigned char supported_replies[] = {
72     QR_SUMMARY,		/* 0x80 */
73     QR_USABLE_AREA,	/* 0x81 */
74     QR_ALPHA_PART,	/* 0x84 */
75     QR_CHARSETS,	/* 0x85 */
76     QR_COLOR,		/* 0x86 */
77     QR_HIGHLIGHTING,	/* 0x87 */
78     QR_REPLY_MODES,	/* 0x88 */
79     QR_DBCS_ASIA,	/* 0x91 */
80     QR_IMP_PART,	/* 0xa6 */
81     QR_DDM,		/* 0x95 */
82 };
83 #define NSR	(sizeof(supported_replies)/sizeof(unsigned char))
84 
85 
86 /*
87  * Process a 3270 Write Structured Field command
88  */
89 enum pds
write_structured_field(unsigned char buf[],size_t buflen)90 write_structured_field(unsigned char buf[], size_t buflen)
91 {
92     size_t fieldlen;
93     unsigned char *cp = buf;
94     bool first = true;
95     enum pds rv = PDS_OKAY_NO_OUTPUT;
96     enum pds rv_this = PDS_OKAY_NO_OUTPUT;
97     bool bad_cmd = false;
98 
99     /* Skip the WSF command itself. */
100     cp++;
101     buflen--;
102 
103     /* Interpret fields. */
104     while (buflen > 0) {
105 
106 	if (first) {
107 	    trace_ds(" ");
108 	} else {
109 	    trace_ds("< WriteStructuredField ");
110 	}
111 	first = false;
112 
113 	/* Pick out the field length. */
114 	if (buflen < 2) {
115 	    trace_ds("error: single byte at end of message\n");
116 	    return rv ? rv : PDS_BAD_CMD;
117 	}
118 	fieldlen = (cp[0] << 8) + cp[1];
119 	if (fieldlen == 0) {
120 	    fieldlen = buflen;
121 	}
122 	if (fieldlen < 3) {
123 	    trace_ds("error: field length %d too small\n", (int)fieldlen);
124 	    return rv ? rv : PDS_BAD_CMD;
125 	}
126 	if (fieldlen > buflen) {
127 	    trace_ds("error: field length %d exceeds remaining "
128 		    "message length %d\n",
129 		    (int)fieldlen, (int)buflen);
130 	    return rv ? rv : PDS_BAD_CMD;
131 	}
132 
133 	/* Dispatch on the ID. */
134 	switch (cp[2]) {
135 	case SF_READ_PART:
136 	    trace_ds("ReadPartition");
137 	    rv_this = sf_read_part(cp, (int)fieldlen);
138 	    break;
139 	case SF_ERASE_RESET:
140 	    trace_ds("EraseReset");
141 	    rv_this = sf_erase_reset(cp, (int)fieldlen);
142 	    break;
143 	case SF_SET_REPLY_MODE:
144 	    trace_ds("SetReplyMode");
145 	    rv_this = sf_set_reply_mode(cp, (int)fieldlen);
146 	    break;
147 	case SF_OUTBOUND_DS:
148 	    trace_ds("OutboundDS");
149 	    rv_this = sf_outbound_ds(cp, (int)fieldlen);
150 	    break;
151 #if !defined(PR3287) /*[*/
152 	case SF_TRANSFER_DATA:   /* File transfer data         */
153 	    trace_ds("FileTransferData");
154 	    ft_dft_data(cp, (int)fieldlen);
155 	    break;
156 #endif /*]*/
157 	default:
158 	    trace_ds("unsupported ID 0x%02x\n", cp[2]);
159 	    rv_this = PDS_BAD_CMD;
160 	    break;
161 	}
162 
163 	/*
164 	 * Accumulate errors or output flags.
165 	 * One real ugliness here is that if we have already
166 	 * generated some output, then we have already positively
167 	 * acknowledged the request, so if we fail here, we have no
168 	 * way to return the error indication.
169 	 */
170 	if (rv_this < 0) {
171 	    bad_cmd = true;
172 	} else {
173 	    rv |= rv_this;
174 	}
175 
176 	/* Skip to the next field. */
177 	cp += fieldlen;
178 	buflen -= fieldlen;
179     }
180     if (first) {
181 	trace_ds(" (null)\n");
182     }
183 
184     if (bad_cmd && !rv) {
185 	return PDS_BAD_CMD;
186     } else {
187 	return rv;
188     }
189 }
190 
191 static enum pds
sf_read_part(unsigned char buf[],unsigned buflen)192 sf_read_part(unsigned char buf[], unsigned buflen)
193 {
194     unsigned char partition;
195     unsigned i;
196     int any = 0;
197     const char *comma = "";
198 
199     if (buflen < 5) {
200 	trace_ds(" error: field length %d too small\n", buflen);
201 	return PDS_BAD_CMD;
202     }
203 
204     partition = buf[3];
205     trace_ds("(0x%02x)", partition);
206 
207     switch (buf[4]) {
208     case SF_RP_QUERY:
209 	trace_ds(" Query");
210 	if (partition != 0xff) {
211 	    trace_ds(" error: illegal partition\n");
212 	    return PDS_BAD_CMD;
213 	}
214 	trace_ds("\n");
215 	query_reply_start();
216 	for (i = 0; i < NSR; i++) {
217 	    if (dbcs || supported_replies[i] != QR_DBCS_ASIA) {
218 		do_query_reply(supported_replies[i]);
219 	    }
220 	}
221 	query_reply_end();
222 	break;
223     case SF_RP_QLIST:
224 	trace_ds(" QueryList ");
225 	if (partition != 0xff) {
226 	    trace_ds("error: illegal partition\n");
227 	    return PDS_BAD_CMD;
228 	}
229 	if (buflen < 6) {
230 	    trace_ds("error: missing request type\n");
231 	    return PDS_BAD_CMD;
232 	}
233 	query_reply_start();
234 	switch (buf[5]) {
235 	case SF_RPQ_LIST:
236 	    trace_ds("List(");
237 	    if (buflen < 7) {
238 		trace_ds(")\n");
239 		do_query_reply(QR_NULL);
240 	    } else {
241 		for (i = 6; i < buflen; i++) {
242 		    trace_ds("%s%s", comma, see_qcode(buf[i]));
243 		    comma = ",";
244 		}
245 		trace_ds(")\n");
246 		for (i = 0; i < NSR; i++) {
247 		    if (memchr((char *)&buf[6], (char)supported_replies[i],
248 				buflen - 6) &&
249 			    (dbcs || supported_replies[i] != QR_DBCS_ASIA)) {
250 			do_query_reply(supported_replies[i]);
251 			any++;
252 		    }
253 		}
254 		if (!any) {
255 		    do_query_reply(QR_NULL);
256 		}
257 	    }
258 	    break;
259 	case SF_RPQ_EQUIV:
260 	    trace_ds("Equivlent+List(");
261 	    for (i = 6; i < buflen; i++) {
262 		trace_ds("%s%s", comma, see_qcode(buf[i]));
263 		comma = ",";
264 	    }
265 	    trace_ds(")\n");
266 	    for (i = 0; i < NSR; i++) {
267 		if (dbcs || supported_replies[i] != QR_DBCS_ASIA) {
268 		    do_query_reply(supported_replies[i]);
269 		}
270 	    }
271 	    break;
272 	case SF_RPQ_ALL:
273 	    trace_ds("All\n");
274 	    for (i = 0; i < NSR; i++) {
275 		if (dbcs || supported_replies[i] != QR_DBCS_ASIA) {
276 		    do_query_reply(supported_replies[i]);
277 		}
278 	    }
279 	    break;
280 	default:
281 	    trace_ds("unknown request type 0x%02x\n", buf[5]);
282 	    return PDS_BAD_CMD;
283 	}
284 	query_reply_end();
285 	break;
286     case SNA_CMD_RMA:
287 	trace_ds(" ReadModifiedAll");
288 	if (partition != 0x00) {
289 	    trace_ds(" error: illegal partition\n");
290 	    return PDS_BAD_CMD;
291 	}
292 	trace_ds("\n");
293 	return PDS_BAD_CMD;
294 	break;
295     case SNA_CMD_RB:
296 	trace_ds(" ReadBuffer");
297 	if (partition != 0x00) {
298 	    trace_ds(" error: illegal partition\n");
299 	    return PDS_BAD_CMD;
300 	}
301 	trace_ds("\n");
302 	return PDS_BAD_CMD;
303 	break;
304     case SNA_CMD_RM:
305 	trace_ds(" ReadModified");
306 	if (partition != 0x00) {
307 	    trace_ds(" error: illegal partition\n");
308 	    return PDS_BAD_CMD;
309 	}
310 	trace_ds("\n");
311 	return PDS_BAD_CMD;
312 	break;
313     default:
314 	trace_ds(" unknown type 0x%02x\n", buf[4]);
315 	return PDS_BAD_CMD;
316     }
317 
318     return PDS_OKAY_OUTPUT;
319 }
320 
321 static enum pds
sf_erase_reset(unsigned char buf[],int buflen)322 sf_erase_reset(unsigned char buf[], int buflen)
323 {
324     if (buflen != 4) {
325 	trace_ds(" error: wrong field length %d\n", buflen);
326 	return PDS_BAD_CMD;
327     }
328 
329     switch (buf[3]) {
330 	case SF_ER_DEFAULT:
331 	    trace_ds(" Default\n");
332 	    break;
333 	case SF_ER_ALT:
334 	    trace_ds(" Alternate\n");
335 	    break;
336 	default:
337 	    trace_ds(" unknown type 0x%02x\n", buf[3]);
338 	    return PDS_BAD_CMD;
339     }
340 
341     return PDS_OKAY_NO_OUTPUT;
342 }
343 
344 static enum pds
sf_set_reply_mode(unsigned char buf[],int buflen)345 sf_set_reply_mode(unsigned char buf[], int buflen)
346 {
347     unsigned char partition;
348 
349     if (buflen < 5) {
350 	trace_ds(" error: wrong field length %d\n", buflen);
351 	return PDS_BAD_CMD;
352     }
353 
354     partition = buf[3];
355     trace_ds("(0x%02x)", partition);
356     if (partition != 0x00) {
357 	trace_ds(" error: illegal partition\n");
358 	return PDS_BAD_CMD;
359     }
360 
361     switch (buf[4]) {
362     case SF_SRM_FIELD:
363 	trace_ds(" Field\n");
364 	break;
365     case SF_SRM_XFIELD:
366 	trace_ds(" ExtendedField\n");
367 	break;
368     case SF_SRM_CHAR:
369 	trace_ds(" Character");
370 	break;
371     default:
372 	trace_ds(" unknown mode 0x%02x\n", buf[4]);
373 	return PDS_BAD_CMD;
374     }
375 
376     return PDS_OKAY_NO_OUTPUT;
377 }
378 
379 static enum pds
sf_outbound_ds(unsigned char buf[],int buflen)380 sf_outbound_ds(unsigned char buf[], int buflen)
381 {
382     if (buflen < 5) {
383 	trace_ds(" error: field length %d too short\n", buflen);
384 	return PDS_BAD_CMD;
385     }
386 
387     trace_ds("(0x%02x)", buf[3]);
388     if (buf[3] != 0x00) {
389 	trace_ds(" error: illegal partition 0x%0x\n", buf[3]);
390 	return PDS_BAD_CMD;
391     }
392 
393     switch (buf[4]) {
394     case SNA_CMD_W:
395 	trace_ds(" Write");
396 	if (buflen > 5) {
397 	    ctlr_write(&buf[4], buflen-4, false);
398 	} else {
399 	    trace_ds("\n");
400 	}
401 	break;
402     case SNA_CMD_EW:
403 	trace_ds(" EraseWrite");
404 	if (buflen > 5) {
405 	    ctlr_write(&buf[4], buflen-4, true);
406 	} else {
407 	    trace_ds("\n");
408 	}
409 	break;
410     case SNA_CMD_EWA:
411 	trace_ds(" EraseWriteAlternate");
412 	if (buflen > 5) {
413 	    ctlr_write(&buf[4], buflen-4, true);
414 	} else {
415 	    trace_ds("\n");
416 	}
417 	break;
418     case SNA_CMD_EAU:
419 	trace_ds(" EraseAllUnprotected\n");
420 	break;
421     default:
422 	trace_ds(" unknown type 0x%02x\n", buf[4]);
423 	return PDS_BAD_CMD;
424     }
425 
426     return PDS_OKAY_NO_OUTPUT;
427 }
428 
429 static void
query_reply_start(void)430 query_reply_start(void)
431 {
432     obptr = obuf;
433     space3270out(1);
434     *obptr++ = AID_SF;
435     qr_in_progress = true;
436 }
437 
438 static void
do_query_reply(unsigned char code)439 do_query_reply(unsigned char code)
440 {
441     size_t len;
442     unsigned i;
443     const char *comma = "";
444     size_t obptr0 = obptr - obuf;
445     unsigned char *obptr_len;
446     unsigned short num, denom;
447 
448     if (qr_in_progress) {
449 	trace_ds("> StructuredField\n");
450 	qr_in_progress = false;
451     }
452 
453     space3270out(4);
454     obptr += 2;	/* skip length for now */
455     *obptr++ = SFID_QREPLY;
456     *obptr++ = code;
457     switch (code) {
458 
459     case QR_CHARSETS:
460 	trace_ds("> QueryReply(CharacterSets)\n");
461 	space3270out(64);
462 	if (dbcs) {
463 	    *obptr++ = 0x8e;	/* flags: GE, CGCSGID, DBCS */
464 	} else {
465 	    *obptr++ = 0x82;	/* flags: GE, CGCSGID present */
466 	}
467 	*obptr++ = 0x00;		/* more flags */
468 	*obptr++ = char_width;		/* SDW */
469 	*obptr++ = char_height;		/* SDW */
470 	*obptr++ = 0x00;		/* no load PS */
471 	*obptr++ = 0x00;
472 	*obptr++ = 0x00;
473 	*obptr++ = 0x00;
474 	if (dbcs) {
475 	    *obptr++ = 0x0b;	/* DL (11 bytes) */
476 	} else {
477 	    *obptr++ = 0x07;	/* DL (7 bytes) */
478 	}
479 
480 	*obptr++ = 0x00;		/* SET 0: */
481 	if (dbcs) {
482 	    *obptr++ = 0x00;	/*  FLAGS: non-load, single- */
483 	}				/*   plane, single-bute */
484 	else {
485 	    *obptr++ = 0x10;	/*  FLAGS: non-loadable, */
486 	}				/*    single-plane, single-byte,
487 					    no compare */
488 	*obptr++ = 0x00;		/*  LCID 0 */
489 	if (dbcs) {
490 	    *obptr++ = 0x00;	/*  SW 0 */
491 	    *obptr++ = 0x00;	/*  SH 0 */
492 	    *obptr++ = 0x00;	/*  SUBSN */
493 	    *obptr++ = 0x00;	/*  SUBSN */
494 	}
495 	SET32(obptr, cgcsgid);	/*  CGCSGID */
496 	if (!standard_font) {
497 	    /* special 3270 font, includes APL */
498 	    *obptr++ = 0x01;/* SET 1: */
499 	    *obptr++ = 0x10;/*  FLAGS: non-loadable, single-plane,
500 				 single-byte, no compare */
501 	    *obptr++ = 0xf1;/*  LCID */
502 	    if (dbcs) {
503 		*obptr++ = 0x00;/*  SW 0 */
504 		*obptr++ = 0x00;/*  SH 0 */
505 		*obptr++ = 0x00;/*  SUBSN */
506 		*obptr++ = 0x00;/*  SUBSN */
507 	    }
508 	    *obptr++ = 0x03;/*  CGCSGID: 3179-style APL2 */
509 	    *obptr++ = 0xc3;
510 	    *obptr++ = 0x01;
511 	    *obptr++ = 0x36;
512 	}
513 	if (dbcs) {
514 	    *obptr++ = 0x80;	/* SET 0x80: */
515 	    *obptr++ = 0x20;	/*  FLAGS: DBCS */
516 	    *obptr++ = 0xf8;	/*  LCID: 0xf8 */
517 	    *obptr++ = char_width * 2; /* SW */
518 	    *obptr++ = char_height; /* SH */
519 	    *obptr++ = 0x41;	/*  SUBSN */
520 	    *obptr++ = 0x7f;	/*  SUBSN */
521 	    SET32(obptr, cgcsgid_dbcs); /* CGCSGID */
522 	}
523 	break;
524 
525     case QR_IMP_PART:
526 	trace_ds("> QueryReply(ImplicitPartition)\n");
527 	space3270out(13);
528 	*obptr++ = 0x0;		/* reserved */
529 	*obptr++ = 0x0;
530 	*obptr++ = 0x0b;	/* length of display size */
531 	*obptr++ = 0x01;	/* "implicit partition size" */
532 	*obptr++ = 0x00;	/* reserved */
533 	SET16(obptr, 72);	/* implicit partition width */
534 	SET16(obptr, 66);	/* implicit partition height */
535 	SET16(obptr, maxCOLS);	/* alternate height */
536 	SET16(obptr, maxROWS);	/* alternate width */
537 	break;
538 
539     case QR_NULL:
540 	trace_ds("> QueryReply(Null)\n");
541 	break;
542 
543     case QR_SUMMARY:
544 	trace_ds("> QueryReply(Summary(");
545 	space3270out(NSR);
546 	for (i = 0; i < NSR; i++) {
547 	    if (dbcs || supported_replies[i] != QR_DBCS_ASIA) {
548 		trace_ds("%s%s", comma,
549 			see_qcode(supported_replies[i]));
550 		comma = ",";
551 		*obptr++ = supported_replies[i];
552 	    }
553 	}
554 	trace_ds("))\n");
555 	break;
556 
557     case QR_USABLE_AREA:
558 	trace_ds("> QueryReply(UsableArea)\n");
559 	space3270out(19);
560 	*obptr++ = 0x01;	/* 12/14-bit addressing */
561 	*obptr++ = 0x00;	/* no special character features */
562 	SET16(obptr, maxCOLS);	/* usable width */
563 	SET16(obptr, maxROWS);	/* usable height */
564 	*obptr++ = 0x01;	/* units (mm) */
565 	num = /*display_widthMM()*/ 8 * 5 / 4;
566 	denom = /*display_width()*/ 7 * 72;
567 	while (!(num % 2) && !(denom % 2)) {
568 	    num /= 2;
569 	    denom /= 2;
570 	}
571 	SET16(obptr, (int)num);	/* Xr numerator */
572 	SET16(obptr, (int)denom); /* Xr denominator */
573 	num = /*display_heightMM()*/ 11 * 5 / 4;
574 	denom = /*display_height()*/ 9 * 66;
575 	while (!(num % 2) && !(denom % 2)) {
576 	    num /= 2;
577 	    denom /= 2;
578 	}
579 	SET16(obptr, (int)num);	/* Yr numerator */
580 	SET16(obptr, (int)denom); /* Yr denominator */
581 	*obptr++ = char_width;	/* AW */
582 	*obptr++ = char_height;	/* AH */
583 	SET16(obptr, 0);	/* buffer */
584 	break;
585 
586     case QR_COLOR:
587 	trace_ds("> QueryReply(Color)\n");
588 	space3270out(4 + 2*15);
589 	*obptr++ = 0x00;	/* no options */
590 	*obptr++ = 16;		/* report on 16 colors */
591 	*obptr++ = 0x00;	/* default color: */
592 	*obptr++ = 0xf0 + HOST_COLOR_GREEN;	/*  green */
593 	for (i = 0xf1; i <= 0xff; i++) {
594 	    *obptr++ = i;
595 	    *obptr++ = i;
596 	}
597 	break;
598 
599     case QR_HIGHLIGHTING:
600 	trace_ds("> QueryReply(Highlighting)\n");
601 	space3270out(11);
602 	*obptr++ = 5;		/* report on 5 pairs */
603 	*obptr++ = XAH_DEFAULT;	/* default: */
604 	*obptr++ = XAH_NORMAL;	/*  normal */
605 	*obptr++ = XAH_BLINK;	/* blink: */
606 	*obptr++ = XAH_BLINK;	/*  blink */
607 	*obptr++ = XAH_REVERSE;	/* reverse: */
608 	*obptr++ = XAH_REVERSE;	/*  reverse */
609 	*obptr++ = XAH_UNDERSCORE; /* underscore: */
610 	*obptr++ = XAH_UNDERSCORE; /*  underscore */
611 	*obptr++ = XAH_INTENSIFY; /* intensify: */
612 	*obptr++ = XAH_INTENSIFY; /*  intensify */
613 	break;
614 
615     case QR_REPLY_MODES:
616 	trace_ds("> QueryReply(ReplyModes)\n");
617 	space3270out(3);
618 	*obptr++ = SF_SRM_FIELD;
619 	*obptr++ = SF_SRM_XFIELD;
620 	*obptr++ = SF_SRM_CHAR;
621 	break;
622 
623     case QR_DBCS_ASIA:
624 	/* XXX: Should we support this, even when not in DBCS mode? */
625 	trace_ds("> QueryReply(DbcsAsia)\n");
626 	space3270out(7);
627 	*obptr++ = 0x00;	/* flags (none) */
628 	*obptr++ = 0x03;	/* field length 3 */
629 	*obptr++ = 0x01;	/* SI/SO supported */
630 	*obptr++ = 0x80;	/* character set ID 0x80 */
631 	*obptr++ = 0x03;	/* field length 3 */
632 	*obptr++ = 0x02;	/* input control */
633 	*obptr++ = 0x01;	/* creation supported */
634 	break;
635 
636     case QR_ALPHA_PART:
637 	trace_ds("> QueryReply(AlphanumericPartitions)\n");
638 	space3270out(4);
639 	*obptr++ = 0;		/* 1 partition */
640 	SET16(obptr, maxROWS*maxCOLS);	/* buffer space */
641 	*obptr++ = 0;		/* no special features */
642 	break;
643 
644     case QR_DDM:
645 	trace_ds("> QueryReply(DistributedDataManagement)\n");
646 	space3270out(8);
647 	SET16(obptr,0);		/* set reserved field to 0 */
648 	SET16(obptr,2048);	/* set inbound length limit */
649 	SET16(obptr,2048);	/* set outbound length limit */
650 	SET16(obptr,0x0101);	/* NSS=01, DDMSS=01 */
651 	break;
652 
653     default:
654 	return;	/* internal error */
655     }
656 
657     obptr_len = obuf + obptr0;
658     len = (obptr - obuf) - obptr0;
659     SET16(obptr_len, len);
660 }
661 
662 static void
query_reply_end(void)663 query_reply_end(void)
664 {
665     net_output();
666 }
667