some initial code for AddPrinterEx() project. Most is ifdef'd out
[samba.git] / source / rpc_client / msrpc_spoolss.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 1.9.
4    NT Domain Authentication SMB / MSRPC client
5    Copyright (C) Andrew Tridgell              1994-2000
6    Copyright (C) Luke Kenneth Casson Leighton 1996-2000
7    Copyright (C) Jean-Francois Micouleau      1999-2000
8    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "includes.h"
25 #include "nterr.h"
26 #include "rpc_parse.h"
27 #include "rpc_client.h"
28 #include "rpcclient.h"
29
30 extern int DEBUGLEVEL;
31
32 #define DEBUG_TESTING
33
34 extern FILE* out_hnd;
35
36 extern struct user_creds *usr_creds;
37
38 /********************************************************************
39 initialize a spoolss NEW_BUFFER.
40 ********************************************************************/
41 static void init_buffer(NEW_BUFFER *buffer, uint32 size)
42 {
43         buffer->ptr = (size!=0)? 1:0;
44         buffer->size=size;
45         buffer->string_at_end=size;
46         prs_init(&buffer->prs, size, 4, MARSHALL);
47         buffer->struct_start = prs_offset(&buffer->prs);
48 }
49
50 static void decode_printer_info_0(NEW_BUFFER *buffer, uint32 returned, 
51                                   PRINTER_INFO_0 **info)
52 {
53         uint32 i;
54         PRINTER_INFO_0  *inf;
55
56         inf=(PRINTER_INFO_0 *)malloc(returned*sizeof(PRINTER_INFO_0));
57
58         buffer->prs.data_offset=0;
59
60         for (i=0; i<returned; i++) {
61                 new_smb_io_printer_info_0("", buffer, &(inf[i]), 0);
62         }
63
64         *info=inf;
65 }
66
67 static void decode_printer_info_1(NEW_BUFFER *buffer, uint32 returned, 
68                                   PRINTER_INFO_1 **info)
69 {
70         uint32 i;
71         PRINTER_INFO_1  *inf;
72
73         inf=(PRINTER_INFO_1 *)malloc(returned*sizeof(PRINTER_INFO_1));
74
75         buffer->prs.data_offset=0;
76
77         for (i=0; i<returned; i++) {
78                 new_smb_io_printer_info_1("", buffer, &(inf[i]), 0);
79         }
80
81         *info=inf;
82 }
83
84 static void decode_printer_info_2(NEW_BUFFER *buffer, uint32 returned, 
85                                   PRINTER_INFO_2 **info)
86 {
87         uint32 i;
88         PRINTER_INFO_2  *inf;
89
90         inf=(PRINTER_INFO_2 *)malloc(returned*sizeof(PRINTER_INFO_2));
91
92         buffer->prs.data_offset=0;
93
94         for (i=0; i<returned; i++) {
95                 /* a little initialization as we go */
96                 inf[i].secdesc = NULL;
97                 new_smb_io_printer_info_2("", buffer, &(inf[i]), 0);
98         }
99
100         *info=inf;
101 }
102
103 static void decode_printer_info_3(NEW_BUFFER *buffer, uint32 returned, 
104                                   PRINTER_INFO_3 **info)
105 {
106         uint32 i;
107         PRINTER_INFO_3  *inf;
108
109         inf=(PRINTER_INFO_3 *)malloc(returned*sizeof(PRINTER_INFO_3));
110
111         buffer->prs.data_offset=0;
112
113         for (i=0; i<returned; i++) {
114                 new_smb_io_printer_info_3("", buffer, &(inf[i]), 0);
115         }
116
117         *info=inf;
118 }
119
120 static void decode_printer_driver_1(NEW_BUFFER *buffer, uint32 returned, 
121                                     DRIVER_INFO_1 **info)
122 {
123         uint32 i;
124         DRIVER_INFO_1 *inf;
125
126         inf=(DRIVER_INFO_1 *)malloc(returned*sizeof(DRIVER_INFO_1));
127
128         buffer->prs.data_offset=0;
129
130         for (i=0; i<returned; i++) {
131                 new_smb_io_printer_driver_info_1("", buffer, &(inf[i]), 0);
132         }
133
134         *info=inf;
135 }
136
137 static void decode_printer_driver_2(NEW_BUFFER *buffer, uint32 returned, 
138                                     DRIVER_INFO_2 **info)
139 {
140         uint32 i;
141         DRIVER_INFO_2 *inf;
142
143         inf=(DRIVER_INFO_2 *)malloc(returned*sizeof(DRIVER_INFO_2));
144
145         buffer->prs.data_offset=0;
146
147         for (i=0; i<returned; i++) {
148                 new_smb_io_printer_driver_info_2("", buffer, &(inf[i]), 0);
149         }
150
151         *info=inf;
152 }
153
154 static void decode_printer_driver_3(NEW_BUFFER *buffer, uint32 returned, 
155                                     DRIVER_INFO_3 **info)
156 {
157         uint32 i;
158         DRIVER_INFO_3 *inf;
159
160         inf=(DRIVER_INFO_3 *)malloc(returned*sizeof(DRIVER_INFO_3));
161
162         buffer->prs.data_offset=0;
163
164         for (i=0; i<returned; i++) {
165                 new_smb_io_printer_driver_info_3("", buffer, &(inf[i]), 0);
166         }
167
168         *info=inf;
169 }
170
171 static void decode_printerdriverdir_info_1(NEW_BUFFER *buffer, DRIVER_DIRECTORY_1 *info)
172 {
173 /*      DRIVER_DIRECTORY_1 *inf;
174
175         inf=(DRIVER_DIRECTORY_1 *)malloc(returned*sizeof(DRIVER_DIRECTORY_1));
176 */
177         prs_set_offset(&buffer->prs, 0);
178
179         new_smb_io_driverdir_1("", buffer, info, 0);
180
181 /*      *info=inf;*/
182 }
183
184
185 /**********************************************************************
186  Decode a PORT_INFO_2 struct from a NEW_BUFFER 
187 **********************************************************************/
188 void decode_port_info_2(NEW_BUFFER *buffer, uint32 returned, 
189                         PORT_INFO_2 **info)
190 {
191         uint32 i;
192         PORT_INFO_2 *inf;
193
194         inf=(PORT_INFO_2*)malloc(returned*sizeof(PORT_INFO_2));
195
196         prs_set_offset(&buffer->prs, 0);
197
198         for (i=0; i<returned; i++) {
199                 new_smb_io_port_info_2("", buffer, &(inf[i]), 0);
200         }
201
202         *info=inf;
203 }
204
205 /**********************************************************************
206  Decode a PORT_INFO_1 struct from a NEW_BUFFER 
207 **********************************************************************/
208 void decode_port_info_1(NEW_BUFFER *buffer, uint32 returned, 
209                         PORT_INFO_1 **info)
210 {
211         uint32 i;
212         PORT_INFO_1 *inf;
213
214         inf=(PORT_INFO_1*)malloc(returned*sizeof(PORT_INFO_1));
215
216         prs_set_offset(&buffer->prs, 0);
217
218         for (i=0; i<returned; i++) {
219                 /* WRITEME!!!! yet to be written --jerry */
220                 /* new_smb_io_port_info_1("", buffer, &(inf[i]), 0); */
221                 ;;
222         }
223
224         *info=inf;
225 }
226
227 /****************************************************************************
228 nt spoolss query
229 ****************************************************************************/
230 BOOL msrpc_spoolss_enum_printers(char* srv_name, uint32 flags, 
231                                  uint32 level, PRINTER_INFO_CTR ctr)
232 {
233         uint32 status;
234         NEW_BUFFER buffer;
235         uint32 needed;
236         uint32 returned;
237         
238         init_buffer(&buffer, 0);
239         
240         /* send a NULL buffer first */
241         status=spoolss_enum_printers(flags, srv_name, level, &buffer, 0, 
242                                      &needed, &returned);
243         
244         if (status==ERROR_INSUFFICIENT_BUFFER) {
245                 init_buffer(&buffer, needed);
246                 status=spoolss_enum_printers(flags, srv_name, level, &buffer, 
247                                              needed, &needed, &returned);
248         }
249         
250         report(out_hnd, "\tstatus:[%d (%x)]\n", status, status);
251         
252         if (status!=NT_STATUS_NO_PROBLEMO)
253                 return False;
254                 
255         /* is there anything to process? */
256         if (returned != 0)
257         {
258                 switch (level) {
259                 case 1:
260                         decode_printer_info_1(&buffer, returned, &(ctr.printers_1));
261                         break;
262                 case 2:
263                         decode_printer_info_2(&buffer, returned, &(ctr.printers_2));
264                         break;
265                 case 3:
266                         decode_printer_info_3(&buffer, returned, &(ctr.printers_3));
267                         break;
268                 }               
269
270                 display_printer_info_ctr(out_hnd, ACTION_HEADER   , level, returned, ctr);
271                 display_printer_info_ctr(out_hnd, ACTION_ENUMERATE, level, returned, ctr);
272                 display_printer_info_ctr(out_hnd, ACTION_FOOTER   , level, returned, ctr);
273         }
274
275         return True;
276 }
277
278 /****************************************************************************
279 nt spoolss query
280 ****************************************************************************/
281 BOOL msrpc_spoolss_enum_ports(char* srv_name, 
282                                  uint32 level, PORT_INFO_CTR *ctr)
283 {
284         uint32 status;
285         NEW_BUFFER buffer;
286         uint32 needed;
287         uint32 returned;
288         
289         init_buffer(&buffer, 0);
290         
291         /* send a NULL buffer first */
292         status=spoolss_enum_ports(srv_name, level, &buffer, 0, 
293                                      &needed, &returned);
294         
295         if (status==ERROR_INSUFFICIENT_BUFFER) {
296                 init_buffer(&buffer, needed);
297                 status=spoolss_enum_ports(srv_name, level, &buffer, 
298                                           needed, &needed, &returned);
299         }
300         
301         report(out_hnd, "\tstatus:[%d (%x)]\n", status, status);
302         
303         if (status!=NT_STATUS_NO_PROBLEMO)
304                 return False;
305                 
306         /* is there anything to process? */
307         if (returned != 0)
308         {
309                 switch (level) {
310                 case 1:
311                         decode_port_info_1(&buffer, returned, &ctr->port.info_1);
312                         break;
313                 case 2:
314                         decode_port_info_2(&buffer, returned, &ctr->port.info_2);
315                         break;
316                 default:
317                         DEBUG(0,("Unable to decode unknown PORT_INFO_%d\n", level));
318                         break;
319                 }               
320
321                 display_port_info_ctr(out_hnd, ACTION_HEADER   , level, returned, ctr);
322                 display_port_info_ctr(out_hnd, ACTION_ENUMERATE, level, returned, ctr);
323                 display_port_info_ctr(out_hnd, ACTION_FOOTER   , level, returned, ctr);
324         }
325
326         return True;
327 }
328
329 /****************************************************************************
330 nt spoolss query
331 ****************************************************************************/
332 uint32 msrpc_spoolss_getprinterdata( const char* printer_name,
333                                 const char* station,
334                                 const char* user_name,
335                                 const char* value_name,
336                                 uint32 *type,
337                                 NEW_BUFFER *buffer,
338                                 void *fn)
339 {
340         POLICY_HND hnd;
341         uint32 status;
342         uint32 needed;
343         uint32 size;
344         char *data;
345         UNISTR2 uni_val_name;
346
347         DEBUG(4,("spoolgetdata - printer: %s server: %s user: %s value: %s\n",
348                 printer_name, station, user_name, value_name));
349
350         if(!spoolss_open_printer_ex( printer_name, 0, 0, station, user_name,
351                                 &hnd))
352         {
353                 return NT_STATUS_ACCESS_DENIED;
354         }
355
356         init_unistr2(&uni_val_name, value_name, 0);
357         size = 0;
358         init_buffer(buffer, size);
359         data = NULL;
360         status = spoolss_getprinterdata(&hnd, &uni_val_name, size, type, &size,
361                         data, &needed);
362
363         if (status == ERROR_INSUFFICIENT_BUFFER)
364         {
365                 size = needed;
366                 init_buffer(buffer, size);
367                 data = prs_data_p(&buffer->prs);
368                 status = spoolss_getprinterdata(&hnd, &uni_val_name,
369                                 size, type, &size,
370                                 data, &needed);
371         }
372
373         if (status != NT_STATUS_NO_PROBLEMO) {
374                 if (!spoolss_closeprinter(&hnd))
375                         return NT_STATUS_ACCESS_DENIED;
376                 return status;
377         }
378
379 #if  0
380         if (fn != NULL)
381                 fn(printer_name, station, level, returned, *ctr);
382 #endif
383
384         return status;
385 }
386
387 /****************************************************************************
388 nt spoolss query
389 ****************************************************************************/
390 BOOL msrpc_spoolss_enum_jobs( const char* printer_name,
391                                 const char* station, const char* user_name,
392                                 uint32 level,
393                                 void ***ctr, JOB_INFO_FN(fn))
394 {
395         POLICY_HND hnd;
396         uint32 status;
397         NEW_BUFFER buffer;
398         uint32 needed;
399         uint32 returned;
400         uint32 firstjob=0;
401         uint32 numofjobs=0xffff;
402
403         DEBUG(4,("spoolopen - printer: %s server: %s user: %s\n",
404                 printer_name, station, user_name));
405
406         if(!spoolss_open_printer_ex( printer_name, 0, 0, station, user_name, &hnd))
407                 return False;
408
409         init_buffer(&buffer, 0);
410         status = spoolss_enum_jobs(&hnd, firstjob, numofjobs, level, &buffer, 0, &needed, &returned);
411
412         if (status == ERROR_INSUFFICIENT_BUFFER)
413         {
414                 init_buffer(&buffer, needed);
415                 status = spoolss_enum_jobs( &hnd, firstjob, numofjobs, level, &buffer, needed, &needed, &returned);
416         }
417
418         if (status!=NT_STATUS_NO_PROBLEMO) {
419                 if (!spoolss_closeprinter(&hnd))
420                         return False;
421                 return False;
422         }
423
424         if (fn != NULL)
425                 fn(printer_name, station, level, returned, *ctr);
426
427         return True;
428 }
429
430
431 /****************************************************************************
432 nt spoolss query
433 ****************************************************************************/
434 BOOL msrpc_spoolss_enum_printerdata( const char* printer_name, 
435                 const char* station, const char* user_name )
436 {
437         POLICY_HND hnd;
438         uint32 status;
439         uint32 idx;
440         uint32 valuelen;
441         uint16 *value;
442         uint32 rvaluelen;
443         uint32 type;
444         uint32 datalen;
445         uint8  *data;
446         uint32 rdatalen;
447
448         DEBUG(4,("spoolenum_printerdata - printer: %s\n", printer_name));
449
450         if(!spoolss_open_printer_ex( printer_name, 0, 0, station, user_name, &hnd))
451                 return False;
452
453         /* FIXME!!!!  --jerry
454            something is severly buggy about the use of 
455            data, datalen, value, & valuelen */
456         status = spoolss_enum_printerdata(&hnd, 0, &valuelen, value, 
457                                           &rvaluelen, &type, &datalen, 
458                                           data, &rdatalen);
459
460         valuelen=rvaluelen;
461         datalen=rdatalen;
462
463         value=(uint16 *)malloc(valuelen*sizeof(uint16));
464         data=(uint8 *)malloc(datalen*sizeof(uint8));
465
466         display_printer_enumdata(out_hnd, ACTION_HEADER, idx, valuelen, 
467                                  value, rvaluelen, type, datalen, data, rdatalen);
468         
469         do {
470
471                 status = spoolss_enum_printerdata(&hnd, idx, &valuelen, 
472                                                   value, &rvaluelen, &type, 
473                                                   &datalen, data, &rdatalen);
474                 display_printer_enumdata(out_hnd, ACTION_ENUMERATE, idx, 
475                                          valuelen, value, rvaluelen, type, 
476                                          datalen, data, rdatalen);
477                 idx++;
478
479         } while (status != 0x0103); /* NO_MORE_ITEMS */
480
481         display_printer_enumdata(out_hnd, ACTION_FOOTER, idx, valuelen, 
482                                  value, rvaluelen, type, datalen, data, rdatalen);
483
484         
485         if (status!=NT_STATUS_NO_PROBLEMO) {
486                 /* 
487                  * the check on this if statement is redundant
488                  * since is the status is bad we're going to 
489                  * return False anyways.  The caller will be 
490                  * unable to determine if there really was a problem
491                  * with the spoolss_closeprinter() call  --jerry
492                  */
493                 spoolss_closeprinter(&hnd);
494                 return False;
495         }
496         
497         return True;
498 }
499
500 /****************************************************************************
501 nt spoolss query
502 ****************************************************************************/
503 BOOL msrpc_spoolss_getprinter( const char* printer_name, const uint32 level,
504                 const char* station, const char* user_name,
505                 PRINTER_INFO_CTR ctr)
506 {
507         POLICY_HND hnd;
508         uint32 status=0;
509         NEW_BUFFER buffer;
510         uint32 needed;
511
512         DEBUG(4,("spoolenum_getprinter - printer: %s\n", printer_name));
513
514         if(!spoolss_open_printer_ex( printer_name, "", PRINTER_ALL_ACCESS, station, user_name, &hnd))
515                 return False;
516
517         init_buffer(&buffer, 0);
518
519         status = spoolss_getprinter(&hnd, level, &buffer, 0, &needed);
520
521         if (status==ERROR_INSUFFICIENT_BUFFER) {
522                 init_buffer(&buffer, needed);
523                 status = spoolss_getprinter(&hnd, level, &buffer, needed, &needed);
524         }
525
526         report(out_hnd, "\tstatus:[%d (%x)]\n", status, status);
527
528         if (status!=NT_STATUS_NO_PROBLEMO)
529                 return False;
530
531         switch (level) {
532         case 0:
533                 decode_printer_info_0(&buffer, 1, &(ctr.printers_0));
534                 break;
535         case 1:
536                 decode_printer_info_1(&buffer, 1, &(ctr.printers_1));
537                 break;
538         case 2:
539                 decode_printer_info_2(&buffer, 1, &(ctr.printers_2));
540                 break;
541         case 3:
542                 decode_printer_info_3(&buffer, 1, &(ctr.printers_3));
543                 break;
544         }
545
546         display_printer_info_ctr(out_hnd, ACTION_HEADER   , level, 1, ctr);
547         display_printer_info_ctr(out_hnd, ACTION_ENUMERATE, level, 1, ctr);
548         display_printer_info_ctr(out_hnd, ACTION_FOOTER   , level, 1, ctr);
549
550         if (status!=NT_STATUS_NO_PROBLEMO) {
551                 if (!spoolss_closeprinter(&hnd))
552                         return False;
553                 return False;
554         }
555
556         return True;
557 }
558
559 /****************************************************************************
560 nt spoolss query
561 ****************************************************************************/
562 BOOL msrpc_spoolss_getprinterdriver( const char* printer_name,
563                 const char *environment, const uint32 level,
564                 const char* station, const char* user_name,
565                 PRINTER_DRIVER_CTR ctr)
566 {
567         POLICY_HND hnd;
568         uint32 status=0;
569         NEW_BUFFER buffer;
570         uint32 needed;
571
572         DEBUG(4,("spoolenum_getprinterdriver - printer: %s\n", printer_name));
573
574         if(!spoolss_open_printer_ex( printer_name, "", PRINTER_ALL_ACCESS, station, user_name, &hnd))
575                 return False;
576
577         init_buffer(&buffer, 0);
578
579         status = spoolss_getprinterdriver(&hnd, environment, level, &buffer, 0, &needed);
580
581         if (status==ERROR_INSUFFICIENT_BUFFER) {
582                 init_buffer(&buffer, needed);
583                 status = spoolss_getprinterdriver(&hnd, environment, level, &buffer, needed, &needed);
584         }
585
586         report(out_hnd, "\tstatus:[%d (%x)]\n", status, status);
587
588         if (status!=NT_STATUS_NO_PROBLEMO)
589                 return False;
590
591         switch (level) {
592         case 1:
593                 decode_printer_driver_1(&buffer, 1, &(ctr.info1));
594                 break;
595         case 2:
596                 decode_printer_driver_2(&buffer, 1, &(ctr.info2));
597                 break;
598         case 3:
599                 decode_printer_driver_3(&buffer, 1, &(ctr.info3));
600                 break;
601         }
602
603         display_printer_driver_ctr(out_hnd, ACTION_HEADER   , level, 1, ctr);
604         display_printer_driver_ctr(out_hnd, ACTION_ENUMERATE, level, 1, ctr);
605         display_printer_driver_ctr(out_hnd, ACTION_FOOTER   , level, 1, ctr);
606
607         if (status!=NT_STATUS_NO_PROBLEMO) {
608                 if (!spoolss_closeprinter(&hnd))
609                         return False;
610                 return False;
611         }
612
613         return True;
614 }
615
616 /****************************************************************************
617 nt spoolss query
618 ****************************************************************************/
619 BOOL msrpc_spoolss_enumprinterdrivers( const char* srv_name,
620                 const char *environment, const uint32 level,
621                 PRINTER_DRIVER_CTR ctr)
622 {
623         uint32 status=0;
624         NEW_BUFFER buffer;
625         uint32 needed;
626         uint32 returned;
627
628         DEBUG(4,("spoolenum_enumprinterdrivers - server: %s\n", srv_name));
629
630         init_buffer(&buffer, 0);
631
632         status = spoolss_enum_printerdrivers(srv_name, environment,
633                                 level, &buffer, 0, &needed, &returned);
634
635         if (status == ERROR_INSUFFICIENT_BUFFER)
636         {
637                 init_buffer(&buffer, needed);
638                 status = spoolss_enum_printerdrivers( srv_name, environment,
639                                 level, &buffer, needed, &needed, &returned);
640         }
641
642         report(out_hnd, "\tstatus:[%d (%x)]\n", status, status);
643
644         if (status!=NT_STATUS_NO_PROBLEMO)
645                 return False;
646
647         switch (level)
648         {
649                 case 1:
650                 {
651                         decode_printer_driver_1(&buffer, returned, &(ctr.info1));
652                         break;
653                 }
654                 case 2:
655                 {
656                         decode_printer_driver_2(&buffer, returned, &(ctr.info2));
657                         break;
658                 }
659                 case 3:
660                 {
661                         decode_printer_driver_3(&buffer, returned, &(ctr.info3));
662                         break;
663                 }
664         }
665
666         display_printer_driver_ctr(out_hnd, ACTION_HEADER   , level, returned, ctr);
667         display_printer_driver_ctr(out_hnd, ACTION_ENUMERATE, level, returned, ctr);
668         display_printer_driver_ctr(out_hnd, ACTION_FOOTER   , level, returned, ctr);
669
670         return True;
671 }
672
673 /****************************************************************************
674 nt spoolss query
675 ****************************************************************************/
676 BOOL msrpc_spoolss_getprinterdriverdir(char* srv_name, char* env_name, uint32 level, DRIVER_DIRECTORY_CTR ctr)
677 {
678         uint32 status;
679         NEW_BUFFER buffer;
680         uint32 needed = 502;
681
682         init_buffer(&buffer, 0);
683
684 #if 0 /* JERRY */
685         /* send a NULL buffer first */
686         status=spoolss_getprinterdriverdir(srv_name, env_name, level, &buffer, 0, &needed);
687
688         if (status==ERROR_INSUFFICIENT_BUFFER) {
689 #endif
690                 init_buffer(&buffer, needed);
691                 status=spoolss_getprinterdriverdir(srv_name, env_name, level, &buffer, needed, &needed);
692 #if 0
693         }
694 #endif
695
696         report(out_hnd, "\tstatus:[%d (%x)]\n", status, status);
697
698         if (status!=NT_STATUS_NO_PROBLEMO)
699                 return False;
700
701         switch (level) {
702         case 1:
703                 decode_printerdriverdir_info_1(&buffer, &(ctr.driver.info_1));
704                 break;
705         }
706
707         display_printerdriverdir_info_ctr(out_hnd, ACTION_HEADER   , level, ctr);
708         display_printerdriverdir_info_ctr(out_hnd, ACTION_ENUMERATE, level, ctr);
709         display_printerdriverdir_info_ctr(out_hnd, ACTION_FOOTER   , level, ctr);
710         return True;
711 }
712