1 0 stevel /* 2 0 stevel * CDDL HEADER START 3 0 stevel * 4 0 stevel * The contents of this file are subject to the terms of the 5 1676 jpk * Common Development and Distribution License (the "License"). 6 1676 jpk * You may not use this file except in compliance with the License. 7 0 stevel * 8 0 stevel * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 0 stevel * or http://www.opensolaris.org/os/licensing. 10 0 stevel * See the License for the specific language governing permissions 11 0 stevel * and limitations under the License. 12 0 stevel * 13 0 stevel * When distributing Covered Code, include this CDDL HEADER in each 14 0 stevel * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 0 stevel * If applicable, add the following below this CDDL HEADER, with the 16 0 stevel * fields enclosed by brackets "[]" replaced with your own identifying 17 0 stevel * information: Portions Copyright [yyyy] [name of copyright owner] 18 0 stevel * 19 0 stevel * CDDL HEADER END 20 0 stevel */ 21 0 stevel /* 22 3431 carlsonj * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 0 stevel * Use is subject to license terms. 24 0 stevel */ 25 0 stevel 26 0 stevel 27 0 stevel #pragma ident "%Z%%M% %I% %E% SMI" /* SunOS */ 28 0 stevel 29 1676 jpk #include <stdio.h> 30 1676 jpk #include <stdlib.h> 31 0 stevel #include <ctype.h> 32 1676 jpk #include <strings.h> 33 0 stevel #include <sys/sysmacros.h> 34 0 stevel #include <sys/types.h> 35 0 stevel #include <sys/errno.h> 36 0 stevel #include <setjmp.h> 37 0 stevel #include <sys/socket.h> 38 0 stevel #include <net/if.h> 39 0 stevel #include <netinet/in_systm.h> 40 0 stevel #include <netinet/in.h> 41 0 stevel #include <netinet/ip.h> 42 0 stevel #include <netinet/if_ether.h> 43 0 stevel #include "snoop.h" 44 0 stevel 45 0 stevel struct porttable { 46 0 stevel int pt_num; 47 0 stevel char *pt_short; 48 0 stevel }; 49 0 stevel 50 1676 jpk static const struct porttable pt_udp[] = { 51 1676 jpk { IPPORT_ECHO, "ECHO" }, 52 1676 jpk { IPPORT_DISCARD, "DISCARD" }, 53 1676 jpk { IPPORT_DAYTIME, "DAYTIME" }, 54 3431 carlsonj { IPPORT_CHARGEN, "CHARGEN" }, 55 1676 jpk { IPPORT_TIMESERVER, "TIME" }, 56 1676 jpk { IPPORT_NAMESERVER, "NAME" }, 57 3431 carlsonj { IPPORT_DOMAIN, "DNS" }, 58 4904 rs200217 { IPPORT_MDNS, "MDNS" }, 59 1676 jpk { IPPORT_BOOTPS, "BOOTPS" }, 60 1676 jpk { IPPORT_BOOTPC, "BOOTPC" }, 61 1676 jpk { IPPORT_TFTP, "TFTP" }, 62 1676 jpk { IPPORT_FINGER, "FINGER" }, 63 1676 jpk /* { 111, "PORTMAP" }, Just Sun RPC */ 64 3431 carlsonj { IPPORT_NTP, "NTP" }, 65 3431 carlsonj { IPPORT_NETBIOS_NS, "NBNS" }, 66 3431 carlsonj { IPPORT_NETBIOS_DGM, "NBDG" }, 67 3431 carlsonj { IPPORT_LDAP, "LDAP" }, 68 3431 carlsonj { IPPORT_SLP, "SLP" }, 69 0 stevel /* Mobile IP defines a set of new control messages sent over UDP port 434 */ 70 3431 carlsonj { IPPORT_MIP, "Mobile IP" }, 71 1676 jpk { IPPORT_BIFFUDP, "BIFF" }, 72 1676 jpk { IPPORT_WHOSERVER, "WHO" }, 73 3431 carlsonj { IPPORT_SYSLOG, "SYSLOG" }, 74 3431 carlsonj { IPPORT_TALK, "TALK" }, 75 1676 jpk { IPPORT_ROUTESERVER, "RIP" }, 76 3431 carlsonj { IPPORT_RIPNG, "RIPng" }, 77 3431 carlsonj { IPPORT_DHCPV6C, "DHCPv6C" }, 78 3431 carlsonj { IPPORT_DHCPV6S, "DHCPv6S" }, 79 1676 jpk { 550, "NEW-RWHO" }, 80 1676 jpk { 560, "RMONITOR" }, 81 1676 jpk { 561, "MONITOR" }, 82 3431 carlsonj { IPPORT_SOCKS, "SOCKS" }, 83 1676 jpk { 0, NULL } 84 0 stevel }; 85 0 stevel 86 1676 jpk static struct porttable pt_tcp[] = { 87 1676 jpk { 1, "TCPMUX" }, 88 1676 jpk { IPPORT_ECHO, "ECHO" }, 89 1676 jpk { IPPORT_DISCARD, "DISCARD" }, 90 1676 jpk { IPPORT_SYSTAT, "SYSTAT" }, 91 1676 jpk { IPPORT_DAYTIME, "DAYTIME" }, 92 1676 jpk { IPPORT_NETSTAT, "NETSTAT" }, 93 3431 carlsonj { IPPORT_CHARGEN, "CHARGEN" }, 94 1676 jpk { 20, "FTP-DATA" }, 95 1676 jpk { IPPORT_FTP, "FTP" }, 96 1676 jpk { IPPORT_TELNET, "TELNET" }, 97 1676 jpk { IPPORT_SMTP, "SMTP" }, 98 1676 jpk { IPPORT_TIMESERVER, "TIME" }, 99 1676 jpk { 39, "RLP" }, 100 1676 jpk { IPPORT_NAMESERVER, "NAMESERVER" }, 101 1676 jpk { IPPORT_WHOIS, "NICNAME" }, 102 3431 carlsonj { IPPORT_DOMAIN, "DNS" }, 103 1676 jpk { 70, "GOPHER" }, 104 1676 jpk { IPPORT_RJE, "RJE" }, 105 1676 jpk { IPPORT_FINGER, "FINGER" }, 106 3431 carlsonj { IPPORT_HTTP, "HTTP" }, 107 1676 jpk { IPPORT_TTYLINK, "LINK" }, 108 1676 jpk { IPPORT_SUPDUP, "SUPDUP" }, 109 1676 jpk { 101, "HOSTNAME" }, 110 1676 jpk { 102, "ISO-TSAP" }, 111 1676 jpk { 103, "X400" }, 112 1676 jpk { 104, "X400-SND" }, 113 1676 jpk { 105, "CSNET-NS" }, 114 1676 jpk { 109, "POP-2" }, 115 1676 jpk /* { 111, "PORTMAP" }, Just Sun RPC */ 116 1676 jpk { 113, "AUTH" }, 117 1676 jpk { 117, "UUCP-PATH" }, 118 1676 jpk { 119, "NNTP" }, 119 3431 carlsonj { IPPORT_NTP, "NTP" }, 120 3431 carlsonj { IPPORT_NETBIOS_SSN, "NBT" }, 121 1676 jpk { 143, "IMAP" }, 122 1676 jpk { 144, "NeWS" }, 123 3431 carlsonj { IPPORT_LDAP, "LDAP" }, 124 3431 carlsonj { IPPORT_SLP, "SLP" }, 125 1676 jpk { 443, "HTTPS" }, 126 1676 jpk { 445, "SMB" }, 127 1676 jpk { IPPORT_EXECSERVER, "EXEC" }, 128 1676 jpk { IPPORT_LOGINSERVER, "RLOGIN" }, 129 1676 jpk { IPPORT_CMDSERVER, "RSHELL" }, 130 3431 carlsonj { IPPORT_PRINTER, "PRINTER" }, 131 1676 jpk { 530, "COURIER" }, 132 1676 jpk { 540, "UUCP" }, 133 1676 jpk { 600, "PCSERVER" }, 134 3431 carlsonj { IPPORT_SOCKS, "SOCKS" }, 135 1676 jpk { 1524, "INGRESLOCK" }, 136 1676 jpk { 2904, "M2UA" }, 137 1676 jpk { 2905, "M3UA" }, 138 1676 jpk { 6000, "XWIN" }, 139 3431 carlsonj { IPPORT_HTTP_ALT, "HTTP (proxy)" }, 140 1676 jpk { 9900, "IUA" }, 141 1676 jpk { 0, NULL }, 142 0 stevel }; 143 0 stevel 144 0 stevel char * 145 0 stevel getportname(int proto, in_port_t port) 146 0 stevel { 147 1676 jpk const struct porttable *p, *pt; 148 0 stevel 149 0 stevel switch (proto) { 150 0 stevel case IPPROTO_SCTP: /* fallthru */ 151 0 stevel case IPPROTO_TCP: pt = pt_tcp; break; 152 0 stevel case IPPROTO_UDP: pt = pt_udp; break; 153 0 stevel default: return (NULL); 154 0 stevel } 155 0 stevel 156 0 stevel for (p = pt; p->pt_num; p++) { 157 0 stevel if (port == p->pt_num) 158 0 stevel return (p->pt_short); 159 0 stevel } 160 0 stevel return (NULL); 161 0 stevel } 162 0 stevel 163 0 stevel int 164 0 stevel reservedport(int proto, int port) 165 0 stevel { 166 1676 jpk const struct porttable *p, *pt; 167 0 stevel 168 0 stevel switch (proto) { 169 0 stevel case IPPROTO_TCP: pt = pt_tcp; break; 170 0 stevel case IPPROTO_UDP: pt = pt_udp; break; 171 0 stevel default: return (NULL); 172 0 stevel } 173 0 stevel for (p = pt; p->pt_num; p++) { 174 0 stevel if (port == p->pt_num) 175 0 stevel return (1); 176 0 stevel } 177 0 stevel return (0); 178 0 stevel } 179 0 stevel 180 0 stevel /* 181 0 stevel * Need to be able to register an 182 0 stevel * interpreter for transient ports. 183 0 stevel * See TFTP interpreter. 184 0 stevel */ 185 0 stevel #define MAXTRANS 64 186 1676 jpk static struct ttable { 187 0 stevel int t_port; 188 1676 jpk int (*t_proc)(int, char *, int); 189 0 stevel } transients [MAXTRANS]; 190 0 stevel 191 0 stevel int 192 1676 jpk add_transient(int port, int (*proc)(int, char *, int)) 193 0 stevel { 194 0 stevel static struct ttable *next = transients; 195 0 stevel 196 0 stevel next->t_port = port; 197 0 stevel next->t_proc = proc; 198 0 stevel 199 0 stevel if (++next >= &transients[MAXTRANS]) 200 0 stevel next = transients; 201 0 stevel 202 0 stevel return (1); 203 0 stevel } 204 0 stevel 205 0 stevel static struct ttable * 206 0 stevel is_transient(int port) 207 0 stevel { 208 0 stevel struct ttable *p; 209 0 stevel 210 0 stevel for (p = transients; p->t_port && p < &transients[MAXTRANS]; p++) { 211 0 stevel if (port == p->t_port) 212 0 stevel return (p); 213 0 stevel } 214 0 stevel 215 0 stevel return (NULL); 216 0 stevel } 217 0 stevel 218 0 stevel void 219 0 stevel del_transient(int port) 220 0 stevel { 221 0 stevel struct ttable *p; 222 0 stevel 223 0 stevel for (p = transients; p->t_port && p < &transients[MAXTRANS]; p++) { 224 0 stevel if (port == p->t_port) 225 0 stevel p->t_port = -1; 226 0 stevel } 227 0 stevel } 228 0 stevel 229 0 stevel static void 230 0 stevel interpret_syslog(int flags, char dir, int port, const char *syslogstr, 231 0 stevel int dlen) 232 0 stevel { 233 0 stevel static const char *pris[] = { 234 0 stevel "emerg", "alert", "crit", "error", "warn", "notice", "info", "debug" 235 0 stevel }; 236 0 stevel static const char *facs[] = { 237 0 stevel "kern", "user", "mail", "daemon", "auth", "syslog", "lpr", "news", 238 0 stevel "uucp", NULL, NULL, NULL, NULL, "audit", NULL, "cron", "local0", 239 0 stevel "local1", "local2", "local3", "local4", "local5", "local6", "local7" 240 0 stevel }; 241 0 stevel 242 0 stevel int composit; 243 0 stevel int pri = -1; 244 0 stevel int facil = -1; 245 0 stevel boolean_t bogus = B_TRUE; 246 0 stevel int priostrlen = 0; 247 0 stevel int datalen = dlen; 248 0 stevel char unknown[4]; /* for unrecognized ones */ 249 0 stevel const char *facilstr = "BAD"; 250 0 stevel const char *pristr = "FMT"; 251 0 stevel const char *data = syslogstr; 252 0 stevel 253 0 stevel /* 254 0 stevel * Is there enough data to interpret (left bracket + at least 3 chars 255 0 stevel * which could be digits, right bracket, or space)? 256 0 stevel */ 257 0 stevel if (datalen >= 4 && data != NULL) { 258 0 stevel if (*data == '<') { 259 0 stevel const int FACS_LEN = sizeof (facs) / sizeof (facs[0]); 260 0 stevel char buffer[4]; 261 0 stevel char *end; 262 0 stevel 263 0 stevel data++; 264 0 stevel datalen--; 265 0 stevel 266 1676 jpk (void) strlcpy(buffer, data, sizeof (buffer)); 267 0 stevel composit = strtoul(buffer, &end, 0); 268 0 stevel data += end - buffer; 269 0 stevel if (*data == '>') { 270 0 stevel data++; 271 0 stevel datalen -= end - buffer + 1; 272 0 stevel 273 0 stevel pri = composit & 0x7; 274 0 stevel facil = (composit & 0xF8) >> 3; 275 0 stevel 276 0 stevel if ((facil >= FACS_LEN) || 277 0 stevel (facs[facil] == NULL)) { 278 0 stevel snprintf(unknown, sizeof (unknown), 279 0 stevel "%d", facil); 280 0 stevel facilstr = unknown; 281 0 stevel } else { 282 0 stevel facilstr = facs[facil]; 283 0 stevel } 284 0 stevel pristr = pris[pri]; 285 0 stevel priostrlen = dlen - datalen; 286 0 stevel bogus = B_FALSE; 287 0 stevel } else { 288 0 stevel data = syslogstr; 289 0 stevel datalen = dlen; 290 0 stevel } 291 0 stevel } 292 0 stevel } 293 0 stevel 294 0 stevel if (flags & F_SUM) { 295 0 stevel (void) snprintf(get_sum_line(), MAXLINE, 296 0 stevel "SYSLOG %c port=%d %s.%s: %s", 297 0 stevel dir, port, facilstr, pristr, 298 0 stevel show_string(syslogstr, dlen, 20)); 299 0 stevel 300 0 stevel } 301 0 stevel 302 0 stevel if (flags & F_DTAIL) { 303 0 stevel static char syslog[] = "SYSLOG: "; 304 0 stevel show_header(syslog, syslog, dlen); 305 0 stevel show_space(); 306 1676 jpk (void) snprintf(get_detail_line(0, 0), MAXLINE, 307 0 stevel "%s%sPriority: %.*s%s(%s.%s)", prot_nest_prefix, syslog, 308 0 stevel priostrlen, syslogstr, bogus ? "" : " ", 309 0 stevel facilstr, pristr); 310 1676 jpk (void) snprintf(get_line(0, 0), get_line_remain(), 311 4904 rs200217 "\"%s\"", 312 4904 rs200217 show_string(syslogstr, dlen, 60)); 313 0 stevel show_trailer(); 314 0 stevel } 315 0 stevel } 316 0 stevel 317 0 stevel int src_port, dst_port, curr_proto; 318 0 stevel 319 0 stevel int 320 0 stevel interpret_reserved(int flags, int proto, in_port_t src, in_port_t dst, 321 0 stevel char *data, int dlen) 322 0 stevel { 323 1676 jpk const char *pn; 324 0 stevel int dir, port, which; 325 0 stevel char pbuff[16], hbuff[32]; 326 0 stevel struct ttable *ttabp; 327 0 stevel 328 0 stevel src_port = src; 329 0 stevel dst_port = dst; 330 0 stevel curr_proto = proto; 331 0 stevel 332 0 stevel pn = getportname(proto, src); 333 0 stevel if (pn != NULL) { 334 0 stevel dir = 'R'; 335 0 stevel port = dst; 336 0 stevel which = src; 337 0 stevel } else { 338 0 stevel pn = getportname(proto, dst); 339 0 stevel if (pn == NULL) { 340 0 stevel ttabp = is_transient(src); 341 0 stevel if (ttabp) { 342 0 stevel (ttabp->t_proc)(flags, data, dlen); 343 0 stevel return (1); 344 0 stevel } 345 0 stevel ttabp = is_transient(dst); 346 0 stevel if (ttabp) { 347 0 stevel (ttabp->t_proc)(flags, data, dlen); 348 0 stevel return (1); 349 0 stevel } 350 0 stevel return (0); 351 0 stevel } 352 0 stevel 353 0 stevel dir = 'C'; 354 0 stevel port = src; 355 0 stevel which = dst; 356 0 stevel } 357 0 stevel 358 4904 rs200217 if ((dst == IPPORT_DOMAIN || src == IPPORT_DOMAIN || 359 4904 rs200217 dst == IPPORT_MDNS || src == IPPORT_MDNS) && 360 3431 carlsonj proto != IPPROTO_TCP) { 361 4904 rs200217 interpret_dns(flags, proto, (uchar_t *)data, dlen, which); 362 0 stevel return (1); 363 0 stevel } 364 0 stevel 365 3431 carlsonj if (dst == IPPORT_SYSLOG && proto != IPPROTO_TCP) { 366 0 stevel /* 367 0 stevel * TCP port 514 is rshell. UDP port 514 is syslog. 368 0 stevel */ 369 0 stevel interpret_syslog(flags, dir, port, (const char *)data, dlen); 370 0 stevel return (1); 371 0 stevel } 372 0 stevel 373 0 stevel if (dlen > 0) { 374 0 stevel switch (which) { 375 1676 jpk case IPPORT_BOOTPS: 376 1676 jpk case IPPORT_BOOTPC: 377 1676 jpk (void) interpret_dhcp(flags, (struct dhcp *)data, 378 1676 jpk dlen); 379 0 stevel return (1); 380 3431 carlsonj case IPPORT_DHCPV6S: 381 3431 carlsonj case IPPORT_DHCPV6C: 382 3431 carlsonj (void) interpret_dhcpv6(flags, (uint8_t *)data, dlen); 383 3431 carlsonj return (1); 384 1676 jpk case IPPORT_TFTP: 385 1676 jpk (void) interpret_tftp(flags, (struct tftphdr *)data, 386 1676 jpk dlen); 387 0 stevel return (1); 388 3431 carlsonj case IPPORT_HTTP: 389 3431 carlsonj case IPPORT_HTTP_ALT: 390 1676 jpk (void) interpret_http(flags, data, dlen); 391 0 stevel return (1); 392 3431 carlsonj case IPPORT_NTP: 393 1676 jpk (void) interpret_ntp(flags, (struct ntpdata *)data, 394 1676 jpk dlen); 395 0 stevel return (1); 396 3431 carlsonj case IPPORT_NETBIOS_NS: 397 1676 jpk interpret_netbios_ns(flags, (uchar_t *)data, dlen); 398 0 stevel return (1); 399 3431 carlsonj case IPPORT_NETBIOS_DGM: 400 1676 jpk interpret_netbios_datagram(flags, (uchar_t *)data, 401 1676 jpk dlen); 402 0 stevel return (1); 403 3431 carlsonj case IPPORT_NETBIOS_SSN: 404 0 stevel case 445: 405 0 stevel /* 406 0 stevel * SMB on port 445 is a subset of NetBIOS SMB 407 0 stevel * on port 139. The same interpreter can be used 408 0 stevel * for both. 409 0 stevel */ 410 1676 jpk interpret_netbios_ses(flags, (uchar_t *)data, dlen); 411 0 stevel return (1); 412 3431 carlsonj case IPPORT_LDAP: 413 0 stevel interpret_ldap(flags, data, dlen, src, dst); 414 0 stevel return (1); 415 3431 carlsonj case IPPORT_SLP: 416 0 stevel interpret_slp(flags, data, dlen); 417 0 stevel return (1); 418 3431 carlsonj case IPPORT_MIP: 419 0 stevel interpret_mip_cntrlmsg(flags, (uchar_t *)data, dlen); 420 0 stevel return (1); 421 1676 jpk case IPPORT_ROUTESERVER: 422 1676 jpk (void) interpret_rip(flags, (struct rip *)data, dlen); 423 0 stevel return (1); 424 3431 carlsonj case IPPORT_RIPNG: 425 1676 jpk (void) interpret_rip6(flags, (struct rip6 *)data, 426 1676 jpk dlen); 427 0 stevel return (1); 428 3431 carlsonj case IPPORT_SOCKS: 429 0 stevel if (dir == 'C') 430 1676 jpk (void) interpret_socks_call(flags, data, dlen); 431 0 stevel else 432 1676 jpk (void) interpret_socks_reply(flags, data, 433 1676 jpk dlen); 434 0 stevel return (1); 435 0 stevel } 436 0 stevel } 437 0 stevel 438 0 stevel if (flags & F_SUM) { 439 1676 jpk (void) snprintf(get_sum_line(), MAXLINE, 440 4904 rs200217 "%s %c port=%d %s", 441 4904 rs200217 pn, dir, port, 442 4904 rs200217 show_string(data, dlen, 20)); 443 0 stevel } 444 0 stevel 445 0 stevel if (flags & F_DTAIL) { 446 1676 jpk (void) snprintf(pbuff, sizeof (pbuff), "%s: ", pn); 447 1676 jpk (void) snprintf(hbuff, sizeof (hbuff), "%s: ", pn); 448 0 stevel show_header(pbuff, hbuff, dlen); 449 0 stevel show_space(); 450 1676 jpk (void) snprintf(get_line(0, 0), get_line_remain(), 451 4904 rs200217 "\"%s\"", 452 4904 rs200217 show_string(data, dlen, 60)); 453 0 stevel show_trailer(); 454 0 stevel } 455 0 stevel return (1); 456 0 stevel } 457 0 stevel 458 0 stevel char * 459 0 stevel show_string(const char *str, int dlen, int maxlen) 460 0 stevel /* 461 0 stevel * Prints len bytes from str enclosed in quotes. 462 0 stevel * If len is negative, length is taken from strlen(str). 463 0 stevel * No more than maxlen bytes will be printed. Longer 464 0 stevel * strings are flagged with ".." after the closing quote. 465 0 stevel * Non-printing characters are converted to C-style escape 466 0 stevel * codes or octal digits. 467 0 stevel */ 468 0 stevel { 469 0 stevel #define TBSIZE 256 470 0 stevel static char tbuff[TBSIZE]; 471 0 stevel const char *p; 472 0 stevel char *pp; 473 0 stevel int printable = 0; 474 0 stevel int c, len; 475 0 stevel 476 0 stevel len = dlen > maxlen ? maxlen : dlen; 477 0 stevel dlen = len; 478 0 stevel 479 0 stevel for (p = str, pp = tbuff; len; p++, len--) { 480 0 stevel switch (c = *p & 0xFF) { 481 0 stevel case '\n': (void) strcpy(pp, "\\n"); pp += 2; break; 482 0 stevel case '\b': (void) strcpy(pp, "\\b"); pp += 2; break; 483 0 stevel case '\t': (void) strcpy(pp, "\\t"); pp += 2; break; 484 0 stevel case '\r': (void) strcpy(pp, "\\r"); pp += 2; break; 485 0 stevel case '\f': (void) strcpy(pp, "\\f"); pp += 2; break; 486 0 stevel default: 487 0 stevel if (isascii(c) && isprint(c)) { 488 0 stevel *pp++ = c; 489 0 stevel printable++; 490 0 stevel } else { 491 1676 jpk (void) snprintf(pp, TBSIZE - (pp - tbuff), 492 0 stevel isdigit(*(p + 1)) ? 493 0 stevel "\\%03o" : "\\%o", c); 494 0 stevel pp += strlen(pp); 495 0 stevel } 496 0 stevel break; 497 0 stevel } 498 0 stevel *pp = '\0'; 499 0 stevel /* 500 0 stevel * Check for overflow of temporary buffer. Allow for 501 0 stevel * the next character to be a \nnn followed by a trailing 502 0 stevel * null. If not, then just bail with what we have. 503 0 stevel */ 504 0 stevel if (pp + 5 >= &tbuff[TBSIZE]) { 505 0 stevel break; 506 0 stevel } 507 0 stevel } 508 0 stevel return (printable > dlen / 2 ? tbuff : ""); 509 0 stevel } 510