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