1 /* 2 Copyright (C) Andrew Tridgell 1995-1999 3 4 This software may be distributed either under the terms of the 5 BSD-style license that accompanies tcpdump or the GNU GPL version 2 6 or later */ 7 8 #ifdef HAVE_CONFIG_H 9 #include "config.h" 10 #endif 11 12 #ifndef lint 13 static const char rcsid[] = 14 "@(#) $Header: /tcpdump/master/tcpdump/smbutil.c,v 1.12 2000/12/04 00:35:45 guy Exp $"; 15 #endif 16 17 #include <sys/param.h> 18 #include <sys/time.h> 19 #include <sys/types.h> 20 #include <sys/socket.h> 21 22 23 #include <netinet/in.h> 24 25 #include <ctype.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <time.h> 30 31 #include "interface.h" 32 #include "smb.h" 33 34 extern const uchar *startbuf; 35 36 /******************************************************************* 37 interpret a 32 bit dos packed date/time to some parameters 38 ********************************************************************/ 39 static void interpret_dos_date(uint32 date,int *year,int *month,int *day,int *hour,int *minute,int *second) 40 { 41 uint32 p0,p1,p2,p3; 42 43 p0=date&0xFF; p1=((date&0xFF00)>>8)&0xFF; 44 p2=((date&0xFF0000)>>16)&0xFF; p3=((date&0xFF000000)>>24)&0xFF; 45 46 *second = 2*(p0 & 0x1F); 47 *minute = ((p0>>5)&0xFF) + ((p1&0x7)<<3); 48 *hour = (p1>>3)&0xFF; 49 *day = (p2&0x1F); 50 *month = ((p2>>5)&0xFF) + ((p3&0x1)<<3) - 1; 51 *year = ((p3>>1)&0xFF) + 80; 52 } 53 54 /******************************************************************* 55 create a unix date from a dos date 56 ********************************************************************/ 57 static time_t make_unix_date(const void *date_ptr) 58 { 59 uint32 dos_date=0; 60 struct tm t; 61 62 dos_date = IVAL(date_ptr,0); 63 64 if (dos_date == 0) return(0); 65 66 interpret_dos_date(dos_date,&t.tm_year,&t.tm_mon, 67 &t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec); 68 t.tm_wday = 1; 69 t.tm_yday = 1; 70 t.tm_isdst = 0; 71 72 return (mktime(&t)); 73 } 74 75 /******************************************************************* 76 create a unix date from a dos date 77 ********************************************************************/ 78 static time_t make_unix_date2(const void *date_ptr) 79 { 80 uint32 x,x2; 81 82 x = IVAL(date_ptr,0); 83 x2 = ((x&0xFFFF)<<16) | ((x&0xFFFF0000)>>16); 84 SIVAL(&x,0,x2); 85 86 return(make_unix_date((void *)&x)); 87 } 88 89 /**************************************************************************** 90 interpret an 8 byte "filetime" structure to a time_t 91 It's originally in "100ns units since jan 1st 1601" 92 ****************************************************************************/ 93 static time_t interpret_long_date(const char *p) 94 { 95 double d; 96 time_t ret; 97 98 /* this gives us seconds since jan 1st 1601 (approx) */ 99 d = (IVAL(p,4)*256.0 + CVAL(p,3)) * (1.0e-7 * (1<<24)); 100 101 /* now adjust by 369 years to make the secs since 1970 */ 102 d -= 369.0*365.25*24*60*60; 103 104 /* and a fudge factor as we got it wrong by a few days */ 105 d += (3*24*60*60 + 6*60*60 + 2); 106 107 if (d<0) 108 return(0); 109 110 ret = (time_t)d; 111 112 return(ret); 113 } 114 115 116 /**************************************************************************** 117 interpret the weird netbios "name". Return the name type, or -1 if 118 we run past the end of the buffer 119 ****************************************************************************/ 120 static int name_interpret(const uchar *in,const uchar *maxbuf,char *out) 121 { 122 int ret; 123 int len; 124 125 if (in >= maxbuf) 126 return(-1); /* name goes past the end of the buffer */ 127 TCHECK2(*in, 1); 128 len = (*in++) / 2; 129 130 *out=0; 131 132 if (len > 30 || len<1) return(0); 133 134 while (len--) 135 { 136 if (in + 1 >= maxbuf) 137 return(-1); /* name goes past the end of the buffer */ 138 TCHECK2(*in, 2); 139 if (in[0] < 'A' || in[0] > 'P' || in[1] < 'A' || in[1] > 'P') { 140 *out = 0; 141 return(0); 142 } 143 *out = ((in[0]-'A')<<4) + (in[1]-'A'); 144 in += 2; 145 out++; 146 } 147 *out = 0; 148 ret = out[-1]; 149 150 return(ret); 151 152 trunc: 153 return(-1); 154 } 155 156 /**************************************************************************** 157 find a pointer to a netbios name 158 ****************************************************************************/ 159 static const uchar *name_ptr(const uchar *buf,int ofs,const uchar *maxbuf) 160 { 161 const uchar *p; 162 uchar c; 163 164 p = buf+ofs; 165 if (p >= maxbuf) 166 return(NULL); /* name goes past the end of the buffer */ 167 TCHECK2(*p, 1); 168 169 c = *p; 170 171 /* XXX - this should use the same code that the DNS dissector does */ 172 if ((c & 0xC0) == 0xC0) 173 { 174 uint16 l = RSVAL(buf, ofs) & 0x3FFF; 175 if (l == 0) 176 { 177 /* We have a pointer that points to itself. */ 178 return(NULL); 179 } 180 p = buf + l; 181 if (p >= maxbuf) 182 return(NULL); /* name goes past the end of the buffer */ 183 TCHECK2(*p, 1); 184 return(buf + l); 185 } 186 else 187 return(buf+ofs); 188 189 trunc: 190 return(NULL); /* name goes past the end of the buffer */ 191 } 192 193 /**************************************************************************** 194 extract a netbios name from a buf 195 ****************************************************************************/ 196 static int name_extract(const uchar *buf,int ofs,const uchar *maxbuf,char *name) 197 { 198 const uchar *p = name_ptr(buf,ofs,maxbuf); 199 if (p == NULL) 200 return(-1); /* error (probably name going past end of buffer) */ 201 strcpy(name,""); 202 return(name_interpret(p,maxbuf,name)); 203 } 204 205 206 /**************************************************************************** 207 return the total storage length of a mangled name 208 ****************************************************************************/ 209 static int name_len(const unsigned char *s, const unsigned char *maxbuf) 210 { 211 const unsigned char *s0 = s; 212 unsigned char c; 213 214 if (s >= maxbuf) 215 return(-1); /* name goes past the end of the buffer */ 216 TCHECK2(*s, 1); 217 c = *s; 218 if ((c & 0xC0) == 0xC0) 219 return(2); 220 while (*s) 221 { 222 if (s >= maxbuf) 223 return(-1); /* name goes past the end of the buffer */ 224 TCHECK2(*s, 1); 225 s += (*s)+1; 226 } 227 return(PTR_DIFF(s,s0)+1); 228 229 trunc: 230 return(-1); /* name goes past the end of the buffer */ 231 } 232 233 static void print_asc(const unsigned char *buf,int len) 234 { 235 int i; 236 for (i=0;i<len;i++) 237 printf("%c",isprint(buf[i])?buf[i]:'.'); 238 } 239 240 static char *name_type_str(int name_type) 241 { 242 static char *f = NULL; 243 switch (name_type) { 244 case 0: f = "Workstation"; break; 245 case 0x03: f = "Client?"; break; 246 case 0x20: f = "Server"; break; 247 case 0x1d: f = "Master Browser"; break; 248 case 0x1b: f = "Domain Controller"; break; 249 case 0x1e: f = "Browser Server"; break; 250 default: f = "Unknown"; break; 251 } 252 return(f); 253 } 254 255 void print_data(const unsigned char *buf, int len) 256 { 257 int i=0; 258 if (len<=0) return; 259 printf("[%03X] ",i); 260 for (i=0;i<len;) { 261 printf("%02X ",(int)buf[i]); 262 i++; 263 if (i%8 == 0) printf(" "); 264 if (i%16 == 0) { 265 print_asc(&buf[i-16],8); printf(" "); 266 print_asc(&buf[i-8],8); printf("\n"); 267 if (i<len) printf("[%03X] ",i); 268 } 269 } 270 if (i%16) { 271 int n; 272 273 n = 16 - (i%16); 274 printf(" "); 275 if (n>8) printf(" "); 276 while (n--) printf(" "); 277 278 n = MIN(8,i%16); 279 print_asc(&buf[i-(i%16)],n); printf(" "); 280 n = (i%16) - n; 281 if (n>0) print_asc(&buf[i-n],n); 282 printf("\n"); 283 } 284 } 285 286 287 static void write_bits(unsigned int val,char *fmt) 288 { 289 char *p = fmt; 290 int i=0; 291 292 while ((p=strchr(fmt,'|'))) { 293 int l = PTR_DIFF(p,fmt); 294 if (l && (val & (1<<i))) 295 printf("%.*s ",l,fmt); 296 fmt = p+1; 297 i++; 298 } 299 } 300 301 /* convert a unicode string */ 302 static const char *unistr(const char *s, int *len) 303 { 304 static char buf[1000]; 305 int l=0; 306 static int use_unicode = -1; 307 308 if (use_unicode == -1) { 309 char *p = getenv("USE_UNICODE"); 310 if (p && (atoi(p) == 1)) 311 use_unicode = 1; 312 else 313 use_unicode = 0; 314 } 315 316 /* maybe it isn't unicode - a cheap trick */ 317 if (!use_unicode || (s[0] && s[1])) { 318 *len = strlen(s)+1; 319 return s; 320 } 321 322 *len = 0; 323 324 if (s[0] == 0 && s[1] != 0) { 325 s++; 326 *len = 1; 327 } 328 329 while (l < (sizeof(buf)-1) && s[0] && s[1] == 0) { 330 buf[l] = s[0]; 331 s += 2; l++; 332 *len += 2; 333 } 334 buf[l] = 0; 335 *len += 2; 336 return buf; 337 } 338 339 static const uchar *fdata1(const uchar *buf, const char *fmt, const uchar *maxbuf) 340 { 341 int reverse=0; 342 char *attrib_fmt = "READONLY|HIDDEN|SYSTEM|VOLUME|DIR|ARCHIVE|"; 343 int len; 344 345 while (*fmt && buf<maxbuf) { 346 switch (*fmt) { 347 case 'a': 348 write_bits(CVAL(buf,0),attrib_fmt); 349 buf++; fmt++; 350 break; 351 352 case 'A': 353 write_bits(SVAL(buf,0),attrib_fmt); 354 buf+=2; fmt++; 355 break; 356 357 case '{': 358 { 359 char bitfmt[128]; 360 char *p = strchr(++fmt,'}'); 361 int l = PTR_DIFF(p,fmt); 362 strncpy(bitfmt,fmt,l); 363 bitfmt[l]=0; 364 fmt = p+1; 365 write_bits(CVAL(buf,0),bitfmt); 366 buf++; 367 break; 368 } 369 370 case 'P': 371 { 372 int l = atoi(fmt+1); 373 buf += l; 374 fmt++; 375 while (isdigit(*fmt)) fmt++; 376 break; 377 } 378 case 'r': 379 reverse = !reverse; 380 fmt++; 381 break; 382 case 'D': 383 { 384 unsigned int x = reverse?RIVAL(buf,0):IVAL(buf,0); 385 printf("%d (0x%x)",x, x); 386 buf += 4; 387 fmt++; 388 break; 389 } 390 case 'L': 391 { 392 unsigned int x1 = reverse?RIVAL(buf,0):IVAL(buf,0); 393 unsigned int x2 = reverse?RIVAL(buf,4):IVAL(buf,4); 394 if (x2) { 395 printf("0x%08x:%08x",x2, x1); 396 } else { 397 printf("%d (0x%08x%08x)",x1, x2, x1); 398 } 399 buf += 8; 400 fmt++; 401 break; 402 } 403 case 'd': 404 { 405 unsigned int x = reverse?RSVAL(buf,0):SVAL(buf,0); 406 printf("%d (0x%x)",x, x); 407 buf += 2; 408 fmt++; 409 break; 410 } 411 case 'W': 412 { 413 unsigned int x = reverse?RIVAL(buf,0):IVAL(buf,0); 414 printf("0x%X",x); 415 buf += 4; 416 fmt++; 417 break; 418 } 419 case 'w': 420 { 421 unsigned int x = reverse?RSVAL(buf,0):SVAL(buf,0); 422 printf("0x%X",x); 423 buf += 2; 424 fmt++; 425 break; 426 } 427 case 'B': 428 { 429 unsigned int x = CVAL(buf,0); 430 printf("0x%X",x); 431 buf += 1; 432 fmt++; 433 break; 434 } 435 case 'b': 436 { 437 unsigned int x = CVAL(buf,0); 438 printf("%d (0x%x)",x, x); 439 buf += 1; 440 fmt++; 441 break; 442 } 443 case 'S': 444 { 445 printf("%.*s",(int)PTR_DIFF(maxbuf,buf),unistr(buf, &len)); 446 buf += len; 447 fmt++; 448 break; 449 } 450 case 'Z': 451 { 452 if (*buf != 4 && *buf != 2) 453 printf("Error! ASCIIZ buffer of type %d (safety=%d)\n", 454 *buf,(int)PTR_DIFF(maxbuf,buf)); 455 printf("%.*s",(int)PTR_DIFF(maxbuf,buf+1),unistr(buf+1, &len)); 456 buf += len+1; 457 fmt++; 458 break; 459 } 460 case 's': 461 { 462 int l = atoi(fmt+1); 463 printf("%-*.*s",l,l,buf); 464 buf += l; 465 fmt++; while (isdigit(*fmt)) fmt++; 466 break; 467 } 468 case 'h': 469 { 470 int l = atoi(fmt+1); 471 while (l--) printf("%02x",*buf++); 472 fmt++; while (isdigit(*fmt)) fmt++; 473 break; 474 } 475 case 'n': 476 { 477 int t = atoi(fmt+1); 478 char nbuf[255]; 479 int name_type; 480 int len; 481 switch (t) { 482 case 1: 483 name_type = name_extract(startbuf,PTR_DIFF(buf,startbuf),maxbuf, 484 nbuf); 485 if (name_type < 0) 486 goto trunc; 487 len = name_len(buf,maxbuf); 488 if (len < 0) 489 goto trunc; 490 buf += len; 491 printf("%-15.15s NameType=0x%02X (%s)", 492 nbuf,name_type,name_type_str(name_type)); 493 break; 494 case 2: 495 name_type = buf[15]; 496 printf("%-15.15s NameType=0x%02X (%s)", 497 buf,name_type,name_type_str(name_type)); 498 buf += 16; 499 break; 500 } 501 fmt++; while (isdigit(*fmt)) fmt++; 502 break; 503 } 504 case 'T': 505 { 506 time_t t; 507 int x = IVAL(buf,0); 508 switch (atoi(fmt+1)) { 509 case 1: 510 if (x==0 || x==-1 || x==0xFFFFFFFF) 511 t = 0; 512 else 513 t = make_unix_date(buf); 514 buf+=4; 515 break; 516 case 2: 517 if (x==0 || x==-1 || x==0xFFFFFFFF) 518 t = 0; 519 else 520 t = make_unix_date2(buf); 521 buf+=4; 522 break; 523 case 3: 524 t = interpret_long_date(buf); 525 buf+=8; 526 break; 527 } 528 printf("%s",t?asctime(localtime(&t)):"NULL\n"); 529 fmt++; while (isdigit(*fmt)) fmt++; 530 break; 531 } 532 default: 533 putchar(*fmt); 534 fmt++; 535 break; 536 } 537 } 538 539 if (buf>=maxbuf && *fmt) 540 printf("END OF BUFFER\n"); 541 542 return(buf); 543 544 trunc: 545 printf("\n"); 546 printf("WARNING: Short packet. Try increasing the snap length\n"); 547 return(NULL); 548 } 549 550 const uchar *fdata(const uchar *buf, const char *fmt, const uchar *maxbuf) 551 { 552 static int depth=0; 553 char s[128]; 554 char *p; 555 556 while (*fmt) { 557 switch (*fmt) { 558 case '*': 559 fmt++; 560 while (buf < maxbuf) { 561 const uchar *buf2; 562 depth++; 563 buf2 = fdata(buf,fmt,maxbuf); 564 depth--; 565 if (buf2 == buf) return(buf); 566 buf = buf2; 567 } 568 break; 569 570 case '|': 571 fmt++; 572 if (buf>=maxbuf) return(buf); 573 break; 574 575 case '%': 576 fmt++; 577 buf=maxbuf; 578 break; 579 580 case '#': 581 fmt++; 582 return(buf); 583 break; 584 585 case '[': 586 fmt++; 587 if (buf>=maxbuf) return(buf); 588 memset(s, 0, sizeof(s)); 589 p = strchr(fmt,']'); 590 strncpy(s,fmt,p-fmt); 591 fmt = p+1; 592 buf = fdata1(buf,s,maxbuf); 593 if (buf == NULL) 594 return(NULL); 595 break; 596 597 default: 598 putchar(*fmt); fmt++; 599 fflush(stdout); 600 break; 601 } 602 } 603 if (!depth && buf<maxbuf) { 604 int len = PTR_DIFF(maxbuf,buf); 605 printf("Data: (%d bytes)\n",len); 606 print_data(buf,len); 607 return(buf+len); 608 } 609 return(buf); 610 } 611 612 typedef struct 613 { 614 char *name; 615 int code; 616 char *message; 617 } err_code_struct; 618 619 /* Dos Error Messages */ 620 static err_code_struct dos_msgs[] = { 621 {"ERRbadfunc",1,"Invalid function."}, 622 {"ERRbadfile",2,"File not found."}, 623 {"ERRbadpath",3,"Directory invalid."}, 624 {"ERRnofids",4,"No file descriptors available"}, 625 {"ERRnoaccess",5,"Access denied."}, 626 {"ERRbadfid",6,"Invalid file handle."}, 627 {"ERRbadmcb",7,"Memory control blocks destroyed."}, 628 {"ERRnomem",8,"Insufficient server memory to perform the requested function."}, 629 {"ERRbadmem",9,"Invalid memory block address."}, 630 {"ERRbadenv",10,"Invalid environment."}, 631 {"ERRbadformat",11,"Invalid format."}, 632 {"ERRbadaccess",12,"Invalid open mode."}, 633 {"ERRbaddata",13,"Invalid data."}, 634 {"ERR",14,"reserved."}, 635 {"ERRbaddrive",15,"Invalid drive specified."}, 636 {"ERRremcd",16,"A Delete Directory request attempted to remove the server's current directory."}, 637 {"ERRdiffdevice",17,"Not same device."}, 638 {"ERRnofiles",18,"A File Search command can find no more files matching the specified criteria."}, 639 {"ERRbadshare",32,"The sharing mode specified for an Open conflicts with existing FIDs on the file."}, 640 {"ERRlock",33,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."}, 641 {"ERRfilexists",80,"The file named in a Create Directory, Make New File or Link request already exists."}, 642 {"ERRbadpipe",230,"Pipe invalid."}, 643 {"ERRpipebusy",231,"All instances of the requested pipe are busy."}, 644 {"ERRpipeclosing",232,"Pipe close in progress."}, 645 {"ERRnotconnected",233,"No process on other end of pipe."}, 646 {"ERRmoredata",234,"There is more data to be returned."}, 647 {NULL,-1,NULL}}; 648 649 /* Server Error Messages */ 650 err_code_struct server_msgs[] = { 651 {"ERRerror",1,"Non-specific error code."}, 652 {"ERRbadpw",2,"Bad password - name/password pair in a Tree Connect or Session Setup are invalid."}, 653 {"ERRbadtype",3,"reserved."}, 654 {"ERRaccess",4,"The requester does not have the necessary access rights within the specified context for the requested function. The context is defined by the TID or the UID."}, 655 {"ERRinvnid",5,"The tree ID (TID) specified in a command was invalid."}, 656 {"ERRinvnetname",6,"Invalid network name in tree connect."}, 657 {"ERRinvdevice",7,"Invalid device - printer request made to non-printer connection or non-printer request made to printer connection."}, 658 {"ERRqfull",49,"Print queue full (files) -- returned by open print file."}, 659 {"ERRqtoobig",50,"Print queue full -- no space."}, 660 {"ERRqeof",51,"EOF on print queue dump."}, 661 {"ERRinvpfid",52,"Invalid print file FID."}, 662 {"ERRsmbcmd",64,"The server did not recognize the command received."}, 663 {"ERRsrverror",65,"The server encountered an internal error, e.g., system file unavailable."}, 664 {"ERRfilespecs",67,"The file handle (FID) and pathname parameters contained an invalid combination of values."}, 665 {"ERRreserved",68,"reserved."}, 666 {"ERRbadpermits",69,"The access permissions specified for a file or directory are not a valid combination. The server cannot set the requested attribute."}, 667 {"ERRreserved",70,"reserved."}, 668 {"ERRsetattrmode",71,"The attribute mode in the Set File Attribute request is invalid."}, 669 {"ERRpaused",81,"Server is paused."}, 670 {"ERRmsgoff",82,"Not receiving messages."}, 671 {"ERRnoroom",83,"No room to buffer message."}, 672 {"ERRrmuns",87,"Too many remote user names."}, 673 {"ERRtimeout",88,"Operation timed out."}, 674 {"ERRnoresource",89,"No resources currently available for request."}, 675 {"ERRtoomanyuids",90,"Too many UIDs active on this session."}, 676 {"ERRbaduid",91,"The UID is not known as a valid ID on this session."}, 677 {"ERRusempx",250,"Temp unable to support Raw, use MPX mode."}, 678 {"ERRusestd",251,"Temp unable to support Raw, use standard read/write."}, 679 {"ERRcontmpx",252,"Continue in MPX mode."}, 680 {"ERRreserved",253,"reserved."}, 681 {"ERRreserved",254,"reserved."}, 682 {"ERRnosupport",0xFFFF,"Function not supported."}, 683 {NULL,-1,NULL}}; 684 685 /* Hard Error Messages */ 686 err_code_struct hard_msgs[] = { 687 {"ERRnowrite",19,"Attempt to write on write-protected diskette."}, 688 {"ERRbadunit",20,"Unknown unit."}, 689 {"ERRnotready",21,"Drive not ready."}, 690 {"ERRbadcmd",22,"Unknown command."}, 691 {"ERRdata",23,"Data error (CRC)."}, 692 {"ERRbadreq",24,"Bad request structure length."}, 693 {"ERRseek",25 ,"Seek error."}, 694 {"ERRbadmedia",26,"Unknown media type."}, 695 {"ERRbadsector",27,"Sector not found."}, 696 {"ERRnopaper",28,"Printer out of paper."}, 697 {"ERRwrite",29,"Write fault."}, 698 {"ERRread",30,"Read fault."}, 699 {"ERRgeneral",31,"General failure."}, 700 {"ERRbadshare",32,"A open conflicts with an existing open."}, 701 {"ERRlock",33,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."}, 702 {"ERRwrongdisk",34,"The wrong disk was found in a drive."}, 703 {"ERRFCBUnavail",35,"No FCBs are available to process request."}, 704 {"ERRsharebufexc",36,"A sharing buffer has been exceeded."}, 705 {NULL,-1,NULL}}; 706 707 708 static struct 709 { 710 int code; 711 char *class; 712 err_code_struct *err_msgs; 713 } err_classes[] = { 714 {0,"SUCCESS",NULL}, 715 {0x01,"ERRDOS",dos_msgs}, 716 {0x02,"ERRSRV",server_msgs}, 717 {0x03,"ERRHRD",hard_msgs}, 718 {0x04,"ERRXOS",NULL}, 719 {0xE1,"ERRRMX1",NULL}, 720 {0xE2,"ERRRMX2",NULL}, 721 {0xE3,"ERRRMX3",NULL}, 722 {0xFF,"ERRCMD",NULL}, 723 {-1,NULL,NULL}}; 724 725 726 /**************************************************************************** 727 return a SMB error string from a SMB buffer 728 ****************************************************************************/ 729 char *smb_errstr(int class,int num) 730 { 731 static char ret[128]; 732 int i,j; 733 734 ret[0]=0; 735 736 for (i=0;err_classes[i].class;i++) 737 if (err_classes[i].code == class) 738 { 739 if (err_classes[i].err_msgs) 740 { 741 err_code_struct *err = err_classes[i].err_msgs; 742 for (j=0;err[j].name;j++) 743 if (num == err[j].code) 744 { 745 snprintf(ret,sizeof(ret),"%s - %s (%s)",err_classes[i].class, 746 err[j].name,err[j].message); 747 return ret; 748 } 749 } 750 751 snprintf(ret,sizeof(ret),"%s - %d",err_classes[i].class,num); 752 return ret; 753 } 754 755 snprintf(ret,sizeof(ret),"ERROR: Unknown error (%d,%d)",class,num); 756 return(ret); 757 } 758