]> The Tcpdump Group git mirrors - tcpdump/blob - smbutil.c
more audit/style. fix a couple of signed/unsigned mixup,
[tcpdump] / smbutil.c
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.15 2001-06-25 18:58:09 itojun 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 name[0] = '\0';
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 safeputchar(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 *
340 fdata1(const uchar *buf, const char *fmt, const uchar *maxbuf)
341 {
342 int reverse = 0;
343 char *attrib_fmt = "READONLY|HIDDEN|SYSTEM|VOLUME|DIR|ARCHIVE|";
344 int len;
345
346 while (*fmt && buf<maxbuf) {
347 switch (*fmt) {
348 case 'a':
349 write_bits(CVAL(buf,0), attrib_fmt);
350 buf++;
351 fmt++;
352 break;
353
354 case 'A':
355 write_bits(SVAL(buf, 0), attrib_fmt);
356 buf += 2;
357 fmt++;
358 break;
359
360 case '{':
361 {
362 char bitfmt[128];
363 char *p = strchr(++fmt, '}');
364 int l = PTR_DIFF(p, fmt);
365 strncpy(bitfmt, fmt, l);
366 bitfmt[l] = 0;
367 fmt = p + 1;
368 write_bits(CVAL(buf, 0), bitfmt);
369 buf++;
370 break;
371 }
372
373 case 'P':
374 {
375 int l = atoi(fmt + 1);
376 buf += l;
377 fmt++;
378 while (isdigit(*fmt))
379 fmt++;
380 break;
381 }
382 case 'r':
383 reverse = !reverse;
384 fmt++;
385 break;
386 case 'D':
387 {
388 unsigned int x = reverse ? RIVAL(buf, 0) : IVAL(buf, 0);
389 printf("%d (0x%x)", x, x);
390 buf += 4;
391 fmt++;
392 break;
393 }
394 case 'L':
395 {
396 unsigned int x1 = reverse ? RIVAL(buf, 0) : IVAL(buf, 0);
397 unsigned int x2 = reverse ? RIVAL(buf, 4) : IVAL(buf, 4);
398 if (x2)
399 printf("0x%08x:%08x", x2, x1);
400 else
401 printf("%d (0x%08x%08x)", x1, x2, x1);
402 buf += 8;
403 fmt++;
404 break;
405 }
406 case 'd':
407 {
408 unsigned int x = reverse ? RSVAL(buf, 0) : SVAL(buf, 0);
409 printf("%d (0x%x)", x, x);
410 buf += 2;
411 fmt++;
412 break;
413 }
414 case 'W':
415 {
416 unsigned int x = reverse ? RIVAL(buf, 0) : IVAL(buf, 0);
417 printf("0x%X", x);
418 buf += 4;
419 fmt++;
420 break;
421 }
422 case 'w':
423 {
424 unsigned int x = reverse ? RSVAL(buf, 0) : SVAL(buf, 0);
425 printf("0x%X", x);
426 buf += 2;
427 fmt++;
428 break;
429 }
430 case 'B':
431 {
432 unsigned int x = CVAL(buf,0);
433 printf("0x%X", x);
434 buf += 1;
435 fmt++;
436 break;
437 }
438 case 'b':
439 {
440 unsigned int x = CVAL(buf, 0);
441 printf("%u (0x%x)", x, x);
442 buf += 1;
443 fmt++;
444 break;
445 }
446 case 'S':
447 {
448 printf("%.*s", (int)PTR_DIFF(maxbuf, buf), unistr(buf, &len));
449 buf += len;
450 fmt++;
451 break;
452 }
453 case 'Z':
454 {
455 if (*buf != 4 && *buf != 2)
456 printf("Error! ASCIIZ buffer of type %u (safety=%lu)\n", *buf,
457 (unsigned long)PTR_DIFF(maxbuf, buf));
458 printf("%.*s", (int)PTR_DIFF(maxbuf, buf + 1),
459 unistr(buf + 1, &len));
460 buf += len + 1;
461 fmt++;
462 break;
463 }
464 case 's':
465 {
466 int l = atoi(fmt + 1);
467 printf("%-*.*s", l, l, buf);
468 buf += l;
469 fmt++;
470 while (isdigit(*fmt))
471 fmt++;
472 break;
473 }
474 case 'h':
475 {
476 int l = atoi(fmt + 1);
477 while (l--)
478 printf("%02x", *buf++);
479 fmt++;
480 while (isdigit(*fmt))
481 fmt++;
482 break;
483 }
484 case 'n':
485 {
486 int t = atoi(fmt+1);
487 char nbuf[255];
488 int name_type;
489 int len;
490
491 switch (t) {
492 case 1:
493 name_type = name_extract(startbuf, PTR_DIFF(buf, startbuf),
494 maxbuf, nbuf);
495 if (name_type < 0)
496 goto trunc;
497 len = name_len(buf, maxbuf);
498 if (len < 0)
499 goto trunc;
500 buf += len;
501 printf("%-15.15s NameType=0x%02X (%s)", nbuf, name_type,
502 name_type_str(name_type));
503 break;
504 case 2:
505 name_type = buf[15];
506 printf("%-15.15s NameType=0x%02X (%s)", buf, name_type,
507 name_type_str(name_type));
508 buf += 16;
509 break;
510 }
511 fmt++;
512 while (isdigit(*fmt))
513 fmt++;
514 break;
515 }
516 case 'T':
517 {
518 time_t t;
519 int x = IVAL(buf,0);
520
521 switch (atoi(fmt + 1)) {
522 case 1:
523 if (x == 0 || x == -1 || x == 0xFFFFFFFF)
524 t = 0;
525 else
526 t = make_unix_date(buf);
527 buf += 4;
528 break;
529 case 2:
530 if (x == 0 || x == -1 || x == 0xFFFFFFFF)
531 t = 0;
532 else
533 t = make_unix_date2(buf);
534 buf += 4;
535 break;
536 case 3:
537 t = interpret_long_date(buf);
538 buf += 8;
539 break;
540 }
541 printf("%s", t ? asctime(localtime(&t)) : "NULL\n");
542 fmt++;
543 while (isdigit(*fmt))
544 fmt++;
545 break;
546 }
547 default:
548 putchar(*fmt);
549 fmt++;
550 break;
551 }
552 }
553
554 if (buf >= maxbuf && *fmt)
555 printf("END OF BUFFER\n");
556
557 return(buf);
558
559 trunc:
560 printf("\n");
561 printf("WARNING: Short packet. Try increasing the snap length\n");
562 return(NULL);
563 }
564
565 const uchar *
566 fdata(const uchar *buf, const char *fmt, const uchar *maxbuf)
567 {
568 static int depth = 0;
569 char s[128];
570 char *p;
571
572 while (*fmt) {
573 switch (*fmt) {
574 case '*':
575 fmt++;
576 while (buf < maxbuf) {
577 const uchar *buf2;
578 depth++;
579 buf2 = fdata(buf, fmt, maxbuf);
580 depth--;
581 if (buf2 == buf)
582 return(buf);
583 buf = buf2;
584 }
585 break;
586
587 case '|':
588 fmt++;
589 if (buf >= maxbuf)
590 return(buf);
591 break;
592
593 case '%':
594 fmt++;
595 buf = maxbuf;
596 break;
597
598 case '#':
599 fmt++;
600 return(buf);
601 break;
602
603 case '[':
604 fmt++;
605 if (buf >= maxbuf)
606 return(buf);
607 memset(s, 0, sizeof(s));
608 p = strchr(fmt, ']');
609 if (p - fmt + 1 > sizeof(s)) {
610 /* overrun */
611 return(buf);
612 }
613 strncpy(s, fmt, p - fmt);
614 s[p - fmt] = '\0';
615 fmt = p + 1;
616 buf = fdata1(buf, s, maxbuf);
617 if (buf == NULL)
618 return(NULL);
619 break;
620
621 default:
622 putchar(*fmt);
623 fmt++;
624 fflush(stdout);
625 break;
626 }
627 }
628 if (!depth && buf < maxbuf) {
629 size_t len = PTR_DIFF(maxbuf, buf);
630 printf("Data: (%lu bytes)\n", (unsigned long)len);
631 print_data(buf, len);
632 return(buf + len);
633 }
634 return(buf);
635 }
636
637 typedef struct {
638 char *name;
639 int code;
640 char *message;
641 } err_code_struct;
642
643 /* Dos Error Messages */
644 static err_code_struct dos_msgs[] = {
645 { "ERRbadfunc", 1, "Invalid function." },
646 { "ERRbadfile", 2, "File not found." },
647 { "ERRbadpath", 3, "Directory invalid." },
648 { "ERRnofids", 4, "No file descriptors available" },
649 { "ERRnoaccess", 5, "Access denied." },
650 { "ERRbadfid", 6, "Invalid file handle." },
651 { "ERRbadmcb", 7, "Memory control blocks destroyed." },
652 { "ERRnomem", 8, "Insufficient server memory to perform the requested function." },
653 { "ERRbadmem", 9, "Invalid memory block address." },
654 { "ERRbadenv", 10, "Invalid environment." },
655 { "ERRbadformat", 11, "Invalid format." },
656 { "ERRbadaccess", 12, "Invalid open mode." },
657 { "ERRbaddata", 13, "Invalid data." },
658 { "ERR", 14, "reserved." },
659 { "ERRbaddrive", 15, "Invalid drive specified." },
660 { "ERRremcd", 16, "A Delete Directory request attempted to remove the server's current directory." },
661 { "ERRdiffdevice", 17, "Not same device." },
662 { "ERRnofiles", 18, "A File Search command can find no more files matching the specified criteria." },
663 { "ERRbadshare", 32, "The sharing mode specified for an Open conflicts with existing FIDs on the file." },
664 { "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." },
665 { "ERRfilexists", 80, "The file named in a Create Directory, Make New File or Link request already exists." },
666 { "ERRbadpipe", 230, "Pipe invalid." },
667 { "ERRpipebusy", 231, "All instances of the requested pipe are busy." },
668 { "ERRpipeclosing", 232, "Pipe close in progress." },
669 { "ERRnotconnected", 233, "No process on other end of pipe." },
670 { "ERRmoredata", 234, "There is more data to be returned." },
671 { NULL, -1, NULL }
672 };
673
674 /* Server Error Messages */
675 err_code_struct server_msgs[] = {
676 { "ERRerror", 1, "Non-specific error code." },
677 { "ERRbadpw", 2, "Bad password - name/password pair in a Tree Connect or Session Setup are invalid." },
678 { "ERRbadtype", 3, "reserved." },
679 { "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." },
680 { "ERRinvnid", 5, "The tree ID (TID) specified in a command was invalid." },
681 { "ERRinvnetname", 6, "Invalid network name in tree connect." },
682 { "ERRinvdevice", 7, "Invalid device - printer request made to non-printer connection or non-printer request made to printer connection." },
683 { "ERRqfull", 49, "Print queue full (files) -- returned by open print file." },
684 { "ERRqtoobig", 50, "Print queue full -- no space." },
685 { "ERRqeof", 51, "EOF on print queue dump." },
686 { "ERRinvpfid", 52, "Invalid print file FID." },
687 { "ERRsmbcmd", 64, "The server did not recognize the command received." },
688 { "ERRsrverror", 65, "The server encountered an internal error, e.g., system file unavailable." },
689 { "ERRfilespecs", 67, "The file handle (FID) and pathname parameters contained an invalid combination of values." },
690 { "ERRreserved", 68, "reserved." },
691 { "ERRbadpermits", 69, "The access permissions specified for a file or directory are not a valid combination. The server cannot set the requested attribute." },
692 { "ERRreserved", 70, "reserved." },
693 { "ERRsetattrmode", 71, "The attribute mode in the Set File Attribute request is invalid." },
694 { "ERRpaused", 81, "Server is paused." },
695 { "ERRmsgoff", 82, "Not receiving messages." },
696 { "ERRnoroom", 83, "No room to buffer message." },
697 { "ERRrmuns", 87, "Too many remote user names." },
698 { "ERRtimeout", 88, "Operation timed out." },
699 { "ERRnoresource", 89, "No resources currently available for request." },
700 { "ERRtoomanyuids", 90, "Too many UIDs active on this session." },
701 { "ERRbaduid", 91, "The UID is not known as a valid ID on this session." },
702 { "ERRusempx", 250, "Temp unable to support Raw, use MPX mode." },
703 { "ERRusestd", 251, "Temp unable to support Raw, use standard read/write." },
704 { "ERRcontmpx", 252, "Continue in MPX mode." },
705 { "ERRreserved", 253, "reserved." },
706 { "ERRreserved", 254, "reserved." },
707 { "ERRnosupport", 0xFFFF, "Function not supported." },
708 { NULL, -1, NULL }
709 };
710
711 /* Hard Error Messages */
712 err_code_struct hard_msgs[] = {
713 { "ERRnowrite", 19, "Attempt to write on write-protected diskette." },
714 { "ERRbadunit", 20, "Unknown unit." },
715 { "ERRnotready", 21, "Drive not ready." },
716 { "ERRbadcmd", 22, "Unknown command." },
717 { "ERRdata", 23, "Data error (CRC)." },
718 { "ERRbadreq", 24, "Bad request structure length." },
719 { "ERRseek", 25 , "Seek error." },
720 { "ERRbadmedia", 26, "Unknown media type." },
721 { "ERRbadsector", 27, "Sector not found." },
722 { "ERRnopaper", 28, "Printer out of paper." },
723 { "ERRwrite", 29, "Write fault." },
724 { "ERRread", 30, "Read fault." },
725 { "ERRgeneral", 31, "General failure." },
726 { "ERRbadshare", 32, "A open conflicts with an existing open." },
727 { "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." },
728 { "ERRwrongdisk", 34, "The wrong disk was found in a drive." },
729 { "ERRFCBUnavail", 35, "No FCBs are available to process request." },
730 { "ERRsharebufexc", 36, "A sharing buffer has been exceeded." },
731 { NULL, -1, NULL }
732 };
733
734 static struct {
735 int code;
736 char *class;
737 err_code_struct *err_msgs;
738 } err_classes[] = {
739 { 0, "SUCCESS", NULL },
740 { 0x01, "ERRDOS", dos_msgs },
741 { 0x02, "ERRSRV", server_msgs },
742 { 0x03, "ERRHRD", hard_msgs },
743 { 0x04, "ERRXOS", NULL },
744 { 0xE1, "ERRRMX1", NULL },
745 { 0xE2, "ERRRMX2", NULL },
746 { 0xE3, "ERRRMX3", NULL },
747 { 0xFF, "ERRCMD", NULL },
748 { -1, NULL, NULL }
749 };
750
751 /*
752 * return a SMB error string from a SMB buffer
753 */
754 char *
755 smb_errstr(int class, int num)
756 {
757 static char ret[128];
758 int i, j;
759
760 ret[0] = 0;
761
762 for (i = 0; err_classes[i].class; i++)
763 if (err_classes[i].code == class) {
764 if (err_classes[i].err_msgs) {
765 err_code_struct *err = err_classes[i].err_msgs;
766 for (j = 0; err[j].name; j++)
767 if (num == err[j].code) {
768 snprintf(ret, sizeof(ret), "%s - %s (%s)",
769 err_classes[i].class, err[j].name, err[j].message);
770 return ret;
771 }
772 }
773
774 snprintf(ret, sizeof(ret), "%s - %d", err_classes[i].class, num);
775 return ret;
776 }
777
778 snprintf(ret, sizeof(ret), "ERROR: Unknown error (%d,%d)", class, num);
779 return(ret);
780 }