Added support for compiling on win32 with Visual C and 'nmake'. It compiles,
[obnox/wireshark/wip.git] / packet-ncp.c
1 /* packet-ncp.c
2  * Routines for NetWare Core Protocol
3  * Gilbert Ramirez <gram@verdict.uthscsa.edu>
4  *
5  * $Id: packet-ncp.c,v 1.17 1999/07/13 02:52:52 gram Exp $
6  *
7  * Ethereal - Network traffic analyzer
8  * By Gerald Combs <gerald@unicom.net>
9  * Copyright 1998 Gerald Combs
10  *
11  * 
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  * 
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  * 
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25  */
26
27 #undef DEBUG_NCP_HASH
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #ifdef HAVE_SYS_TYPES_H
34 # include <sys/types.h>
35 #endif
36
37 #ifdef HAVE_NETINET_IN_H
38 # include <netinet/in.h>
39 #endif
40
41 #include <glib.h>
42 #include "packet.h"
43 #include "packet-ipx.h"
44 #include "packet-ncp.h"
45
46 struct svc_record;
47
48 static void
49 dissect_ncp_request(const u_char *pd, int offset, frame_data *fd, proto_tree *ncp_tree, proto_tree *tree);
50
51 static void
52 dissect_ncp_reply(const u_char *pd, int offset, frame_data *fd, proto_tree *ncp_tree, proto_tree *tree);
53
54 static struct ncp2222_record *
55 ncp2222_find(guint8 func, guint8 subfunc);
56
57 static void
58 parse_ncp_svc_fields(const u_char *pd, proto_tree *ncp_tree, int offset,
59         struct svc_record *svc);
60
61
62 /* Hash functions */
63 gint  ncp_equal (gconstpointer v, gconstpointer v2);
64 guint ncp_hash  (gconstpointer v);
65
66 int ncp_packet_init_count = 200;
67
68 /* The information in this module comes from:
69         NetWare LAN Analysis, Second Edition
70         Laura A. Chappell and Dan E. Hakes
71         (c) 1994 Novell, Inc.
72         Novell Press, San Jose.
73         ISBN: 0-7821-1362-1
74
75   And from the ncpfs source code by Volker Lendecke
76
77   And:
78         Programmer's Guide to the NetWare Core Protocol
79         Steve Conner & Diane Conner
80         (c) 1996 by Steve Conner & Diane Conner
81         Published by Annabooks, San Diego, California
82         ISBN: 0-929392-31-0
83
84 */
85
86 /* Every NCP packet has this common header */
87 struct ncp_common_header {
88         guint16 type;
89         guint8  sequence;
90         guint8  conn_low;
91         guint8  task;
92         guint8  conn_high;
93 };
94
95 /* NCP request packets */
96 struct ncp_request_header {
97         guint16 type;
98         guint8  sequence;
99         guint8  conn_low;
100         guint8  task;
101         guint8  conn_high;
102         guint8  function;
103         guint16 length;
104         guint8  subfunc;
105 };
106
107 /* NCP reply packets */
108 struct ncp_reply_header {
109         guint16 type;
110         guint8  sequence;
111         guint8  conn_low;
112         guint8  task;
113         guint8  conn_high;
114         guint8  completion_code;
115         guint8  connection_state;
116 };
117
118
119 static value_string request_reply_values[] = {
120         { 0x1111, "Create a service connection" },
121         { 0x2222, "Service request" },
122         { 0x3333, "Service reply" },
123         { 0x5555, "Destroy service connection" },
124         { 0x7777, "Burst mode transfer" },
125         { 0x9999, "Request being processed" },
126         { 0x0000, NULL }
127 };
128
129 /* These are the field types in an NCP packet */
130 enum ntype {
131         nend,           /* end of the NCP field list */
132         nbyte,          /* one byte of data */
133         nhex,           /* bytes to be shown as hex digits */
134         nbelong,        /* 4-byte big-endian long int */
135         nbeshort,       /* 2-byte big-endian short int */
136         ndata,          /* unstructured data */
137         nbytevar,       /* a variable number of bytes */
138         ndatetime,      /* date-time stamp */
139         nasciile,       /* length-encoded ASCII string. First byte is length */
140         nasciiz         /* null-terminated string of ASCII characters */
141 };
142
143 /* These are the broad families that the different NCP request types belong
144  * to.
145  */
146 enum nfamily {
147                 NCP_UNKNOWN_SERVICE,    /* unknown or n/a */
148                 NCP_QUEUE_SERVICES,             /* print queues */
149                 NCP_FILE_SERVICES,              /* file serving */
150                 NCP_BINDERY_SERVICES,   /* bindery database */
151                 NCP_CONNECTION_SERVICES, /* communication */
152 };
153
154 /* I had to put this function prototype after the enum nfamily declaration */
155 static char*
156 ncp_completion_code(guint8 ccode, enum nfamily family);
157
158
159 /* Information on the NCP field */
160 typedef struct svc_record {
161         enum ntype      type;
162         guint8          length; /* max-length for variable-sized fields */
163         gchar           *description;
164 } svc_record;
165
166 typedef struct ncp2222_record {
167         guint8          func;
168         guint8          subfunc;
169         guint8          submask;        /* Does this function have subfunctions?
170                                          * SUBFUNC or NOSUB */
171         gchar           *funcname;
172
173         svc_record      *req;
174         svc_record      *rep;
175         enum nfamily    family;
176
177 } ncp2222_record;
178
179
180 /* ------------------------------------------------------------ */
181
182 /* Get Bindery Object ID REQUEST */
183 static svc_record ncp_17_35_C[] = {
184                 { nbeshort,     2,      "Object Type: 0x%04x" },
185                 { nasciile,     48,     "Object Name: %.*s" },
186                 { nend,         0,      NULL }
187 };
188 /* Get Bindery Object ID REPLY has no fields*/
189
190
191 /* Service Queue Job REQUEST */
192 static svc_record ncp_17_7C_C[] = {
193                 { nbelong,      4,      "The queue the job resides in" },
194                 { nbeshort,     2,      "Job Type" },
195                 { nend,         0,      NULL }
196 };
197 /* Service Queue Job REPLY */
198 static svc_record ncp_17_7C_R[] = {
199                 { nbelong,      4,      "Client station number: %d" },
200                 { nbelong,      4,      "Task Number: %d" },
201                 { nbelong,      4,      "User: %d" },
202                 { nbelong,      4,      "Server specifed to service queue entry: %08X" },
203                 { ndatetime,    6,      "Earliest time to execute" },
204                 { ndatetime,    6,      "When job entered queue" },
205                 { nbelong,      4,      "Job Number" },
206                 { nbeshort,     2,      "Job Type" },
207                 { nbeshort,     2,      "Job Position" },
208                 { nbeshort,     2,      "Current status of job: 0x%02x" },
209                 { nasciiz,      14,     "Name of file" },
210                 { nbelong,      4,      "File handle" },
211                 { nbelong,      4,      "Client station number" },
212                 { nbelong,      4,      "Task number" },
213                 { nbelong,      4,      "Job server" },
214                 { nend,         0,      NULL }
215 };
216
217
218
219 /* Negotiate Buffer Size REQUEST */
220 static svc_record ncp_21_00_C[] = {
221                 { nbeshort,     2,      "Caller's maximum packet size: %d bytes" },
222                 { nend,         0,      NULL }
223 };
224 /* Negotiate Buffer Size RESPONSE */
225 static svc_record ncp_21_00_R[] = {
226                 { nbeshort,     2,      "Packet size decided upon by file server: %d bytes" },
227                 { nend,         0,      NULL }
228 };
229
230
231 /* Close File REQUEST */
232 static svc_record ncp_42_00_C[] = {
233                 { nhex,         6,      "File Handle: 02x:02x:02x:02x:02x:02x"},
234                 { nend,         0,      NULL }
235 };
236 /* Close File RESPONSE */
237 static svc_record ncp_42_00_R[] = {
238                 { nend,         0,      NULL }
239 };
240
241
242 /* Read from a file REQUEST */
243 static svc_record ncp_48_00_C[] = {
244                 { nbyte,        1,      "Unknown" },
245                 { nhex,         6,      "File Handle" },
246                 { nbelong,      4,      "Byte offset within file" },
247                 { nbeshort,     2,      "Maximum data bytes to return" },
248                 { nend,         0,      NULL }
249 };
250 /* RESPONSE */
251 static svc_record ncp_48_00_R[] = {
252                 { nbeshort,     2,      "Data bytes returned" },
253                 { nbytevar,     1,      "Padding" },
254                 { ndata,        0,      NULL }
255 };
256
257 /* ------------------------------------------------------------ */
258 /* Any svc_record that has no fields is not created.
259  *  Store a NULL in the ncp2222_record instead */
260
261 #define SUBFUNC 0xff
262 #define NOSUB   0x00
263
264 static ncp2222_record ncp2222[] = {
265
266 { 0x17, 0x35, SUBFUNC, "Get Bindery Object ID",
267         ncp_17_35_C, NULL, NCP_BINDERY_SERVICES
268 },
269
270 { 0x17, 0x7C, SUBFUNC, "Service Queue Job",
271         ncp_17_7C_C, ncp_17_7C_R, NCP_QUEUE_SERVICES
272 },
273
274 { 0x18, 0x00, NOSUB, "End of Job",
275         NULL, NULL, NCP_CONNECTION_SERVICES
276 },
277
278 { 0x19, 0x00, NOSUB, "Logout",
279         NULL, NULL, NCP_CONNECTION_SERVICES
280 },
281
282 { 0x21, 0x00, NOSUB, "Negotiate Buffer Size",
283         ncp_21_00_C, ncp_21_00_R, NCP_CONNECTION_SERVICES
284 },
285
286 { 0x42, 0x00, NOSUB, "Close File",
287         ncp_42_00_C, ncp_42_00_R, NCP_FILE_SERVICES
288 },
289
290 { 0x48, 0x00, NOSUB, "Read from a file",
291         ncp_48_00_C, ncp_48_00_R, NCP_FILE_SERVICES
292 },
293
294 { 0x00, 0x00, NOSUB, NULL,
295         NULL, NULL, NCP_UNKNOWN_SERVICE
296 }
297
298 };
299
300
301 /* NCP packets come in request/reply pairs. The request packets tell the type
302  * of NCP request and give a sequence ID. The response, unfortunately, only
303  * identifies itself via the sequence ID; you have to know what type of NCP
304  * request the request packet contained in order to successfully parse the NCP
305  * response. A global method for doing this does not exist in ethereal yet
306  * (NFS also requires it), so for now the NCP section will keep its own hash
307  * table keeping track of NCP packet types.
308  *
309  * The key representing the unique NCP request is composed of 3 variables:
310  *
311  * ServerIPXNetwork.Connection.SequenceNumber
312  *     4 bytes        2 bytes      1 byte
313  *     guint32        guint16      guint8     (all are host order)
314  *
315  * This assumes that all NCP connection is between a client and server.
316  * Servers can be identified by having a 00:00:00:00:00:01 IPX Node address.
317  * We have to let the IPX layer pass us the ServerIPXNetwork via a global
318  * variable (nw_server_address). In the future, if we decode NCP over TCP/UDP,
319  * then nw_server_address will represent the IP address of the server, which
320  * conveniently, is also 4 bytes long.
321  *
322  * The value stored in the hash table is the ncp_request_val pointer. This
323  * struct tells us the NCP type and gives the ncp2222_record pointer, if
324  * ncp_type == 0x2222.
325  */
326 guint32 nw_server_address = 0; /* set by IPX layer */
327 guint16 nw_connection = 0; /* set by dissect_ncp */
328 guint8  nw_sequence = 0; /* set by dissect_ncp */
329 guint16 nw_ncp_type = 0; /* set by dissect_ncp */
330
331 struct ncp_request_key {
332         guint32 nw_server_address;
333         guint16 nw_connection;
334         guint8  nw_sequence;
335 };
336
337 struct ncp_request_val {
338         guint32                                 ncp_type;
339         struct ncp2222_record*  ncp_record;
340 };
341
342 GHashTable *ncp_request_hash = NULL;
343 GMemChunk *ncp_request_keys = NULL;
344 GMemChunk *ncp_request_records = NULL;
345
346 /* Hash Functions */
347 gint  ncp_equal (gconstpointer v, gconstpointer v2)
348 {
349         struct ncp_request_key  *val1 = (struct ncp_request_key*)v;
350         struct ncp_request_key  *val2 = (struct ncp_request_key*)v2;
351
352         #if defined(DEBUG_NCP_HASH)
353         printf("Comparing %08X:%d:%d and %08X:%d:%d\n",
354                 val1->nw_server_address, val1->nw_connection, val1->nw_sequence,
355                 val2->nw_server_address, val2->nw_connection, val2->nw_sequence);
356         #endif
357
358         if (val1->nw_server_address == val2->nw_server_address &&
359                 val1->nw_connection == val2->nw_connection &&
360                 val1->nw_sequence   == val2->nw_sequence ) {
361                 return 1;
362         }
363         return 0;
364 }
365
366 guint ncp_hash  (gconstpointer v)
367 {
368         struct ncp_request_key  *ncp_key = (struct ncp_request_key*)v;
369 #if defined(DEBUG_NCP_HASH)
370         printf("hash calculated as %d\n", ncp_key->nw_server_address +
371                         ((guint32) ncp_key->nw_connection << 16) +
372                         ncp_key->nw_sequence);
373 #endif
374         return ncp_key->nw_server_address +
375                         ((guint32) ncp_key->nw_connection << 16) +
376                         ncp_key->nw_sequence;
377 }
378
379 /* Initializes the hash table and the mem_chunk area each time a new
380  * file is loaded or re-loaded in ethereal */
381 void
382 ncp_init_protocol(void)
383 {
384         #if defined(DEBUG_NCP_HASH)
385         printf("Initializing NCP hashtable and mem_chunk area\n");
386         #endif
387         if (ncp_request_hash)
388                 g_hash_table_destroy(ncp_request_hash);
389         if (ncp_request_keys)
390                 g_mem_chunk_destroy(ncp_request_keys);
391         if (ncp_request_records)
392                 g_mem_chunk_destroy(ncp_request_records);
393
394         ncp_request_hash = g_hash_table_new(ncp_hash, ncp_equal);
395         ncp_request_keys = g_mem_chunk_new("ncp_request_keys",
396                         sizeof(struct ncp_request_key),
397                         ncp_packet_init_count * sizeof(struct ncp_request_key), G_ALLOC_AND_FREE);
398         ncp_request_records = g_mem_chunk_new("ncp_request_records",
399                         sizeof(struct ncp_request_val),
400                         ncp_packet_init_count * sizeof(struct ncp_request_val), G_ALLOC_AND_FREE);
401 }
402
403 static struct ncp2222_record *
404 ncp2222_find(guint8 func, guint8 subfunc)
405 {
406         struct ncp2222_record *ncp_record, *retval = NULL;
407
408         ncp_record = ncp2222;
409
410         while(ncp_record->func != 0) {
411                 if (ncp_record->func == func &&
412                         ncp_record->subfunc == (subfunc & ncp_record->submask)) {
413                         retval = ncp_record;
414                         break;
415                 }
416                 ncp_record++;
417         }
418
419         return retval;
420 }
421
422 void
423 dissect_ncp(const u_char *pd, int offset, frame_data *fd, proto_tree *tree,
424         int max_data) {
425
426         proto_tree      *ncp_tree = NULL;
427         proto_item      *ti;
428         int             ncp_hdr_length = 0;
429         struct ncp_common_header        header;
430
431         memcpy(&header, &pd[offset], sizeof(header));
432         header.type = ntohs(header.type);
433
434         if (header.type == 0x1111 ||
435                         header.type == 0x2222 ||
436                         header.type == 0x5555 ||
437                         header.type == 0x7777) {
438                 ncp_hdr_length = 7;
439         }
440         else if (header.type == 0x3333 || header.type == 0x9999) {
441                 ncp_hdr_length = 8;
442         }
443
444         if (check_col(fd, COL_PROTOCOL))
445                 col_add_str(fd, COL_PROTOCOL, "NCP");
446
447         nw_connection = (header.conn_high << 16) + header.conn_low;
448         nw_sequence = header.sequence;
449         nw_ncp_type = header.type;
450
451         if (tree) {
452                 ti = proto_tree_add_text(tree, offset, END_OF_FRAME,
453                         "NetWare Core Protocol");
454                 ncp_tree = proto_item_add_subtree(ti, ETT_NCP);
455
456                 proto_tree_add_text(ncp_tree, offset,      2,
457                         "Type: %s", val_to_str( header.type,
458                         request_reply_values, "Unknown (%04X)"));
459
460                 proto_tree_add_text(ncp_tree, offset+2,    1,
461                         "Sequence Number: %d", header.sequence);
462
463                 proto_tree_add_text(ncp_tree, offset+3,    3,
464                         "Connection Number: %d", nw_connection);
465
466                 proto_tree_add_text(ncp_tree, offset+4,    1,
467                         "Task Number: %d", header.task);
468         }
469
470         /* Note how I use ncp_tree *and* tree in my args for ncp request/reply */
471         if (ncp_hdr_length == 7)
472                 dissect_ncp_request(pd, offset, fd, ncp_tree, tree);
473         else if (ncp_hdr_length == 8)
474                 dissect_ncp_reply(pd, offset, fd, ncp_tree, tree);
475         else
476                 dissect_data(pd, offset, fd, tree);
477 }
478
479 void
480 dissect_ncp_request(const u_char *pd, int offset, frame_data *fd,
481         proto_tree *ncp_tree, proto_tree *tree) {
482
483         struct ncp_request_header       request;
484         struct ncp2222_record           *ncp_request;
485         gchar                           *description = "";
486         struct ncp_request_val          *request_val;
487         struct ncp_request_key          *request_key;
488         proto_tree                      *field_tree = NULL;
489         proto_item                      *ti = NULL;
490
491         /*memcpy(&request, &pd[offset], sizeof(request));*/
492         request.function = pd[offset+6];
493         request.subfunc = pd[offset+9];
494
495         ncp_request = ncp2222_find(request.function, request.subfunc);
496
497         if (ncp_request)
498                 description = ncp_request->funcname;
499
500         if (check_col(fd, COL_INFO)) {
501                 if (description[0]) {
502                         col_add_fstr(fd, COL_INFO, "C %s", description);
503                 }
504                 else {
505                         col_add_fstr(fd, COL_INFO, "C Unknown Function %02X/%02X",
506                                 request.function, request.subfunc);
507                 }
508         }
509
510         if (ncp_tree) {
511                 proto_tree_add_text(ncp_tree, offset+6, 1,
512                         "Function Code: 0x%02X (%s)",
513                         request.function, description);
514
515                 if (ncp_request) {
516
517                         if (ncp_request->submask == SUBFUNC) {
518                                 proto_tree_add_text(ncp_tree, offset+7, 2,
519                                         "Packet Length: %d bytes", pntohs(&pd[offset+7]));
520                                 proto_tree_add_text(ncp_tree, offset+9, 1,
521                                         "Subfunction Code: 0x%02x", pd[offset+9]);
522                                 offset += 7 + 3;
523                         }
524                         else {
525                                 offset += 7;
526                         }
527
528                         if (ncp_request->req) {
529                                 ti = proto_tree_add_text(ncp_tree, offset, END_OF_FRAME,
530                                 "NCP Request Packet");
531                                 field_tree = proto_item_add_subtree(ti, ETT_NCP_REQUEST_FIELDS);
532
533                                 parse_ncp_svc_fields(pd, field_tree, offset, ncp_request->req);
534                         }
535                 }
536         }
537         else { /* ! tree */
538                 request_key = g_mem_chunk_alloc(ncp_request_keys);
539                 request_key->nw_server_address = nw_server_address;
540                 request_key->nw_connection = nw_connection;
541                 request_key->nw_sequence = nw_sequence;
542
543                 request_val = g_mem_chunk_alloc(ncp_request_records);
544                 request_val->ncp_type = nw_ncp_type;
545                 request_val->ncp_record = ncp2222_find(request.function, request.subfunc);
546
547                 g_hash_table_insert(ncp_request_hash, request_key, request_val);
548                 #if defined(DEBUG_NCP_HASH)
549                 printf("Inserted server %08X connection %d sequence %d (val=%08X)\n",
550                         nw_server_address, nw_connection, nw_sequence, request_val);
551                 #endif
552         }
553
554 }
555
556 void
557 dissect_ncp_reply(const u_char *pd, int offset, frame_data *fd,
558         proto_tree *ncp_tree, proto_tree *tree) {
559
560         struct ncp_reply_header         reply;
561         struct ncp2222_record           *ncp_request = NULL;
562         struct ncp_request_val          *request_val;
563         struct ncp_request_key          request_key;
564         proto_tree                      *field_tree = NULL;
565         proto_item                      *ti = NULL;
566
567         memcpy(&reply, &pd[offset], sizeof(reply));
568
569         /* find the record telling us the request made that caused this reply */
570         request_key.nw_server_address = nw_server_address;
571         request_key.nw_connection = nw_connection;
572         request_key.nw_sequence = nw_sequence;
573
574         request_val = (struct ncp_request_val*)
575                 g_hash_table_lookup(ncp_request_hash, &request_key);
576
577         #if defined(DEBUG_NCP_HASH)
578         printf("Looking for server %08X connection %d sequence %d (retval=%08X)\n",
579                 nw_server_address, nw_connection, nw_sequence, request_val);
580         #endif
581
582         if (request_val)
583                 ncp_request = request_val->ncp_record;
584
585         if (check_col(fd, COL_INFO)) {
586                 if (reply.completion_code == 0) {
587                         col_add_fstr(fd, COL_INFO, "R OK");
588                 }
589                 else {
590                         col_add_fstr(fd, COL_INFO, "R Not OK");
591                 }
592         }
593
594         if (ncp_tree) {
595                 /* A completion code of 0 always means OK. Other values have different
596                  * meanings */
597                 if (ncp_request) {
598                         proto_tree_add_text(ncp_tree, offset+6,    1,
599                                 "Completion Code: 0x%02x (%s)", reply.completion_code,
600                                 ncp_completion_code(reply.completion_code, ncp_request->family));
601                 }
602                 else {
603                         proto_tree_add_text(ncp_tree, offset+6,    1,
604                                 "Completion Code: 0x%02x (%s)", reply.completion_code,
605                                 reply.completion_code == 0 ? "OK" : "Unknown");
606                 }
607
608                 proto_tree_add_text(ncp_tree, offset+7,    1,
609                         "Connection Status: %d", reply.connection_state);
610
611                 if (ncp_request) {
612
613                         if (ncp_request->rep) {
614                                 ti = proto_tree_add_text(ncp_tree, offset+8, END_OF_FRAME,
615                                 "NCP Reply Packet");
616                                 field_tree = proto_item_add_subtree(ti, ETT_NCP_REPLY_FIELDS);
617
618                                 parse_ncp_svc_fields(pd, field_tree, offset+8, ncp_request->rep);
619                         }
620                 }
621         }
622
623 }
624
625 /* Populates the protocol tree with information about the svc_record fields */
626 static void
627 parse_ncp_svc_fields(const u_char *pd, proto_tree *ncp_tree, int offset,
628         struct svc_record *svc)
629 {
630         struct svc_record *rec = svc;
631         int field_offset = offset;
632         int field_length = 0;
633
634         while (rec->type != nend) {
635                 switch(rec->type) {
636                         case nbeshort:
637                                 field_length = 2;
638                                 proto_tree_add_text(ncp_tree, field_offset,
639                                         field_length, rec->description, pntohs(&pd[field_offset]));
640                                 break;
641
642                         case nasciile:
643                                 field_length = pd[field_offset];
644                                 proto_tree_add_text(ncp_tree, field_offset,
645                                         field_length + 1, rec->description, field_length,
646                                         &pd[field_offset+1]);
647                                 break;
648
649                         case nhex:
650                                 field_length = rec->length;
651                                 proto_tree_add_text(ncp_tree, field_offset,
652                                         field_length, rec->description);
653                                 break;  
654
655                          default:
656                                 ; /* nothing */
657                                 break;
658                 }
659                 field_offset += field_length;
660                 rec++;
661         }       
662 }
663
664 static char*
665 ncp_completion_code(guint8 ccode, enum nfamily family)
666 {
667                 char    *text;
668
669 #define NCP_CCODE_MIN 0x7e
670 #define NCP_CCODE_MAX 0xff
671
672         /* From Appendix C of "Programmer's Guide to NetWare Core Protocol" */
673         static char     *ccode_text[] = {
674                 /* 7e */ "NCP boundary check failed",
675                 /* 7f */ "Unknown",
676                 /* 80 */ "Lock fail. The file is already open",
677                 /* 81 */ "A file handle could not be allocated by the file server",
678                 /* 82 */ "Unauthorized to open file",
679                 /* 83 */ "Unable to read/write the volume. Possible bad sector on the file server",
680                 /* 84 */ "Unauthorized to create the file",
681                 /* 85 */ "",
682                 /* 86 */ "Unknown",
683                 /* 87 */ "An unexpected character was encountered in the filename",
684                 /* 88 */ "FileHandle is not valid",
685                 /* 89 */ "Unauthorized to search this directory",
686                 /* 8a */ "Unauthorized to delete a file in this directory",
687                 /* 8b */ "Unauthorized to rename a file in this directory",
688                 /* 8c */ "Unauthorized to modify a file in this directory",
689                 /* 8d */ "Some of the affected files are in use by another client",
690                 /* 8e */ "All of the affected files are in use by another client",
691                 /* 8f */ "Some of the affected file are read only",
692                 /* 90 */ "",
693                 /* 91 */ "Some of the affected files already exist",
694                 /* 92 */ "All of the affected files already exist",
695                 /* 93 */ "Unauthorized to read from this file",
696                 /* 94 */ "Unauthorized to write to this file",
697                 /* 95 */ "The affected file is detached",
698                 /* 96 */ "The file server has run out of memory to service this request",
699                 /* 97 */ "Unknown",
700                 /* 98 */ "The affected volume is not mounted",
701                 /* 99 */ "The file server has run out of directory space on the affected volume",
702                 /* 9a */ "The request attempted to rename the affected file to another volume",
703                 /* 9b */ "DirHandle is not associated with a valid directory path",
704                 /* 9c */ "",
705                 /* 9d */ "A directory handle was not available for allocation",
706                 /* 9e */ "The filename does not conform to a legal name for this name space",
707                 /* 9f */ "The request attempted to delete a directory that is in use by another client",
708                 /* a0 */ "The request attempted to delete a directory that is not empty",
709                 /* a1 */ "An unrecoverable error occurred on the affected directory",
710                 /* a2 */ "The request attempted to read from a file region that is physically locked",
711                 /* a3 */ "Unknown",
712                 /* a4 */ "Unknown",
713                 /* a5 */ "Unknown",
714                 /* a6 */ "Unknown",
715                 /* a7 */ "Unknown",
716                 /* a8 */ "Unknown",
717                 /* a9 */ "Unknown",
718                 /* aa */ "Unknown",
719                 /* ab */ "Unknown",
720                 /* ac */ "Unknown",
721                 /* ad */ "Unknown",
722                 /* ae */ "Unknown",
723                 /* af */ "Unknown",
724                 /* b0 */ "Unknown",
725                 /* b1 */ "Unknown",
726                 /* b2 */ "Unknown",
727                 /* b3 */ "Unknown",
728                 /* b4 */ "Unknown",
729                 /* b5 */ "Unknown",
730                 /* b6 */ "Unknown",
731                 /* b7 */ "Unknown",
732                 /* b8 */ "Unknown",
733                 /* b9 */ "Unknown",
734                 /* ba */ "Unknown",
735                 /* bb */ "Unknown",
736                 /* bc */ "Unknown",
737                 /* bd */ "Unknown",
738                 /* be */ "Unknown",
739                 /* bf */ "Requests for this name space are not valid on this volume",
740                 /* c0 */ "Unauthorized to retrieve accounting data",
741                 /* c1 */ "The 'account balance' property does not exist",
742                 /* c2 */ "The object has exceeded its credit limit",
743                 /* c3 */ "Too many holds have been placed against this account",
744                 /* c4 */ "The account for this bindery object has been disabled",
745                 /* c5 */ "Access to the account has been denied because of intruder detections",
746                 /* c6 */ "The caller does not have operator privileges",
747                 /* c7 */ "Unknown",
748                 /* c8 */ "Unknown",
749                 /* c9 */ "Unknown",
750                 /* ca */ "Unknown",
751                 /* cb */ "Unknown",
752                 /* cc */ "Unknown",
753                 /* cd */ "Unknown",
754                 /* ce */ "Unknown",
755                 /* cf */ "Unknown",
756                 /* d0 */ "Queue error",
757                 /* d1 */ "The queue associated with Object ID does not exist",
758                 /* d2 */ "A queue server is not associated with the selected queue",
759                 /* d3 */ "No queue rights",
760                 /* d4 */ "The queue associated with Object ID is full and cannot accept another request",
761                 /* d5 */ "The job associated with Job Number does not exist in this queue",
762                 /* d6 */ "",
763                 /* d7 */ "",
764                 /* d8 */ "Queue not active",
765                 /* d9 */ "",
766                 /* da */ "",
767                 /* db */ "",
768                 /* dc */ "Unknown",
769                 /* dd */ "Unknown",
770                 /* de */ "Attempted to login to the file server with an incorrect password",
771                 /* df */ "Attempted to login to the file server with a password that has expired",
772                 /* e0 */ "Unknown",
773                 /* e1 */ "Unknown",
774                 /* e2 */ "Unknown",
775                 /* e3 */ "Unknown",
776                 /* e4 */ "Unknown",
777                 /* e5 */ "Unknown",
778                 /* e6 */ "Unknown",
779                 /* e7 */ "No disk track",
780                 /* e8 */ "",
781                 /* e9 */ "Unknown",
782                 /* ea */ "The bindery object is not a member of this set",
783                 /* eb */ "The property is not a set property",
784                 /* ec */ "The set property does not exist",
785                 /* ed */ "The property already exists",
786                 /* ee */ "The bindery object already exists",
787                 /* ef */ "Illegal characters in Object Name field",
788                 /* f0 */ "A wildcard was detected in a field that does not support wildcards",
789                 /* f1 */ "The client does not have the rights to access this bindery objecs",
790                 /* f2 */ "Unauthorized to read from this object",
791                 /* f3 */ "Unauthorized to rename this object",
792                 /* f4 */ "Unauthorized to delete this object",
793                 /* f5 */ "Unauthorized to create this object",
794                 /* f6 */ "Unauthorized to delete the property of this object",
795                 /* f7 */ "Unauthorized to create this property",
796                 /* f8 */ "Unauthorized to write to this property",
797                 /* f9 */ "Unauthorized to read this property",
798                 /* fa */ "Temporary remap error",
799                 /* fb */ "",
800                 /* fc */ "",
801                 /* fd */ "",
802                 /* fe */ "",
803                 /* ff */ ""
804         };
805
806         switch (ccode) {
807                 case 0:
808                         return "OK";
809                         break;
810
811                 case 3:
812                         return "Client not accepting messages";
813                         break;
814         }
815
816         if (ccode >= NCP_CCODE_MIN && ccode <= NCP_CCODE_MAX) {
817                 text = ccode_text[ccode - NCP_CCODE_MIN];
818                 /* If there really is text, return it */
819                 if (text[0] != 0)
820                         return text;
821         }
822         else {
823                 return "Unknown";
824         }
825
826         /* We have a completion code with multiple translations. We'll use the
827          * nfamily that this request type belongs to to give the right
828          * translation.
829          */
830         switch (ccode) {
831
832                 case 0xfc:
833                         switch(family) {
834                                 case NCP_QUEUE_SERVICES:
835                                         return "The message queue cannot accept another message";
836                                         break;
837                                 case NCP_BINDERY_SERVICES:
838                                         return "The specified bindery object does not exist";
839                                         break;
840                                 default:
841                                         return "Unknown";
842                                         break;
843                         }
844                         break;
845
846                 default:
847                         return "I don't know how to parse this completion code. Please send this packet trace to Gilbert Ramirez <gram@xiexie.org> for analysis";
848         }
849 }