s3:client: Fix smbspool device uri handling
[samba.git] / source3 / client / smbspool.c
1 /*
2    Unix SMB/CIFS implementation.
3    SMB backend for the Common UNIX Printing System ("CUPS")
4
5    Copyright (C) Michael R Sweet            1999
6    Copyright (C) Andrew Tridgell            1994-1998
7    Copyright (C) Andrew Bartlett            2002
8    Copyright (C) Rodrigo Fernandez-Vizarra  2005
9    Copyright (C) James Peach                2008
10
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 3 of the License, or
14    (at your option) any later version.
15
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20
21    You should have received a copy of the GNU General Public License
22    along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 #include "includes.h"
26 #include "system/filesys.h"
27 #include "system/passwd.h"
28 #include "system/kerberos.h"
29 #include "libsmb/libsmb.h"
30 #include "lib/param/param.h"
31 #include "lib/krb5_wrap/krb5_samba.h"
32
33 /*
34  * Starting with CUPS 1.3, Kerberos support is provided by cupsd including
35  * the forwarding of user credentials via the authenticated session between
36  * user and server and the KRB5CCNAME environment variable which will point
37  * to a temporary file or an in-memory representation depending on the version
38  * of Kerberos you use.  As a result, all of the ticket code that used to
39  * live here has been removed, and we depend on the user session (if you
40  * run smbspool by hand) or cupsd to provide the necessary Kerberos info.
41  *
42  * Also, the AUTH_USERNAME and AUTH_PASSWORD environment variables provide
43  * for per-job authentication for non-Kerberized printing.  We use those
44  * if there is no username and password specified in the device URI.
45  *
46  * Finally, if we have an authentication failure we return exit code 2
47  * which tells CUPS to hold the job for authentication and bug the user
48  * to get the necessary credentials.
49  */
50
51 #define MAX_RETRY_CONNECT        3
52
53
54 /*
55  * Globals...
56  */
57
58
59
60 /*
61  * Local functions...
62  */
63
64 static int      get_exit_code(struct cli_state * cli, NTSTATUS nt_status);
65 static void     list_devices(void);
66 static struct cli_state *smb_complete_connection(const char *, const char *,
67         int, const char *, const char *, const char *, const char *, int, bool *need_auth);
68 static struct cli_state *smb_connect(const char *, const char *, int, const
69         char *, const char *, const char *, const char *, bool *need_auth);
70 static int      smb_print(struct cli_state *, const char *, FILE *);
71 static char    *uri_unescape_alloc(const char *);
72 #if 0
73 static bool     smb_encrypt;
74 #endif
75
76 static const char *auth_info_required;
77
78 /*
79  * 'main()' - Main entry for SMB backend.
80  */
81
82 int                             /* O - Exit status */
83 main(int argc,                  /* I - Number of command-line arguments */
84      char *argv[])
85 {                               /* I - Command-line arguments */
86         int             i;      /* Looping var */
87         int             copies; /* Number of copies */
88         int             port;   /* Port number */
89         char            uri[1024],      /* URI */
90                        *sep,    /* Pointer to separator */
91                        *tmp, *tmp2,     /* Temp pointers to do escaping */
92                        *password;       /* Password */
93         char           *username,       /* Username */
94                        *server, /* Server name */
95                        *printer;/* Printer name */
96         const char     *workgroup;      /* Workgroup */
97         FILE           *fp;     /* File to print */
98         int             status = 1;     /* Status of LPD job */
99         struct cli_state *cli;  /* SMB interface */
100         char            empty_str[] = "";
101         int             tries = 0;
102         bool            need_auth = true;
103         const char     *dev_uri = NULL;
104         const char     *env = NULL;
105         const char     *config_file = NULL;
106         TALLOC_CTX     *frame = talloc_stackframe();
107         const char *print_user = NULL;
108         const char *print_title = NULL;
109         const char *print_file = NULL;
110         const char *print_copies = NULL;
111         int cmp;
112         int len;
113
114         if (argc == 1) {
115                 /*
116                  * NEW!  In CUPS 1.1 the backends are run with no arguments
117                  * to list the available devices.  These can be devices
118                  * served by this backend or any other backends (i.e. you
119                  * can have an SNMP backend that is only used to enumerate
120                  * the available network printers... :)
121                  */
122
123                 list_devices();
124                 status = 0;
125                 goto done;
126         }
127
128         /*
129          * We need at least 5 options if the DEVICE_URI is passed via an env
130          * variable and printing data comes via stdin.
131          * We don't accept more than 7 options in total, including optional.
132          */
133         if (argc < 5 || argc > 8) {
134                 fprintf(stderr,
135 "Usage: %s [DEVICE_URI] job-id user title copies options [file]\n"
136 "       The DEVICE_URI environment variable can also contain the\n"
137 "       destination printer:\n"
138 "\n"
139 "           smb://[username:password@][workgroup/]server[:port]/printer\n",
140                         argv[0]);
141                 goto done;
142         }
143
144         /*
145          * Find out if we have the device_uri in the command line.
146          *
147          * If we are started as a CUPS backend argv[0] is normally the
148          * device_uri!
149          */
150         if (argc == 8) {
151                 /*
152                  * smbspool <uri> <job> <user> <title> <copies> <options> <file>
153                  * 0        1     2     3      4       5        6         7
154                  */
155
156                 dev_uri = argv[1];
157
158                 print_user = argv[3];
159                 print_title = argv[4];
160                 print_copies = argv[5];
161                 print_file = argv[7];
162         } else if (argc == 7) {
163                 int cmp1;
164                 int cmp2;
165
166                 /*
167                  * <uri>    <job> <user> <title> <copies> <options> <file>
168                  * smbspool <uri> <job>  <user>  <title>  <copies>  <options>
169                  * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI
170                  */
171                 cmp1 = strncmp(argv[0], "smb://", 6);
172                 cmp2 = strncmp(argv[1], "smb://", 6);
173
174                 if (cmp1 == 0) {
175                         /*
176                          * <uri>    <job> <user> <title> <copies> <options> <file>
177                          * 0        1     2      3       4        5         6
178                          */
179                         dev_uri = argv[0];
180
181                         print_user = argv[2];
182                         print_title = argv[3];
183                         print_copies = argv[4];
184                         print_file = argv[6];
185                 } else if (cmp2 == 0) {
186                         /*
187                          * smbspool <uri> <job>  <user>  <title>  <copies>  <options>
188                          * 0        1     2      3       4        5         6
189                          */
190                         dev_uri = argv[1];
191
192                         print_user = argv[3];
193                         print_title = argv[4];
194                         print_copies = argv[5];
195                         print_file = NULL;
196                 } else {
197                         /*
198                          * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI
199                          * 0        1     2      3       4        5         6
200                          */
201                         print_user = argv[2];
202                         print_title = argv[3];
203                         print_copies = argv[4];
204                         print_file = argv[6];
205                 }
206         } else if (argc == 6) {
207                 /*
208                  * <uri>    <job> <user> <title> <copies> <options>
209                  * smbspool <job> <user> <title> <copies> <options> | DEVICE_URI
210                  * 0        1     2      3       4        5
211                  */
212                 cmp = strncmp(argv[0], "smb://", 6);
213                 if (cmp == 0) {
214                         dev_uri = argv[0];
215                 }
216
217                 print_user = argv[2];
218                 print_title = argv[3];
219                 print_copies = argv[4];
220         }
221
222         if (print_file != NULL) {
223                 char *endp;
224
225                 fp = fopen(print_file, "rb");
226                 if (fp == NULL) {
227                         perror("ERROR: Unable to open print file");
228                         goto done;
229                 }
230
231                 copies = strtol(print_copies, &endp, 10);
232                 if (print_copies == endp) {
233                         perror("ERROR: Unable to determine number of copies");
234                         goto done;
235                 }
236         } else {
237                 fp = stdin;
238                 copies = 1;
239         }
240
241         /*
242          * Find the URI ...
243          */
244         if (dev_uri == NULL) {
245                 env = getenv("DEVICE_URI");
246                 if (env != NULL && env[0] != '\0') {
247                         dev_uri = env;
248                 }
249         }
250
251         if (dev_uri == NULL) {
252                 fprintf(stderr,
253                         "ERROR: No valid device URI has been specified\n");
254                 goto done;
255         }
256
257         cmp = strncmp(dev_uri, "smb://", 6);
258         if (cmp != 0) {
259                 fprintf(stderr,
260                         "ERROR: No valid device URI has been specified\n");
261                 goto done;
262         }
263         len = snprintf(uri, sizeof(uri), "%s", dev_uri);
264         if (len >= sizeof(uri)) {
265                 fprintf(stderr,
266                         "ERROR: The URI is too long.\n");
267                 goto done;
268         }
269
270         auth_info_required = getenv("AUTH_INFO_REQUIRED");
271         if (auth_info_required == NULL) {
272                 auth_info_required = "none";
273         }
274
275         /*
276          * Extract the destination from the URI...
277          */
278
279         if ((sep = strrchr_m(uri, '@')) != NULL) {
280                 tmp = uri + 6;
281                 *sep++ = '\0';
282
283                 /* username is in tmp */
284
285                 server = sep;
286
287                 /*
288                  * Extract password as needed...
289                  */
290
291                 if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
292                         *tmp2++ = '\0';
293                         password = uri_unescape_alloc(tmp2);
294                 } else {
295                         password = empty_str;
296                 }
297                 username = uri_unescape_alloc(tmp);
298         } else {
299                 if ((username = getenv("AUTH_USERNAME")) == NULL) {
300                         username = empty_str;
301                 }
302
303                 if ((password = getenv("AUTH_PASSWORD")) == NULL) {
304                         password = empty_str;
305                 }
306
307                 server = uri + 6;
308         }
309
310         if (password != empty_str) {
311                 auth_info_required = "username,password";
312         }
313
314         tmp = server;
315
316         if ((sep = strchr_m(tmp, '/')) == NULL) {
317                 fputs("ERROR: Bad URI - need printer name!\n", stderr);
318                 goto done;
319         }
320
321         *sep++ = '\0';
322         tmp2 = sep;
323
324         if ((sep = strchr_m(tmp2, '/')) != NULL) {
325                 /*
326                  * Convert to smb://[username:password@]workgroup/server/printer...
327                  */
328
329                 *sep++ = '\0';
330
331                 workgroup = uri_unescape_alloc(tmp);
332                 server = uri_unescape_alloc(tmp2);
333                 printer = uri_unescape_alloc(sep);
334         } else {
335                 workgroup = NULL;
336                 server = uri_unescape_alloc(tmp);
337                 printer = uri_unescape_alloc(tmp2);
338         }
339
340         if ((sep = strrchr_m(server, ':')) != NULL) {
341                 *sep++ = '\0';
342
343                 port = atoi(sep);
344         } else {
345                 port = 0;
346         }
347
348         /*
349          * Setup the SAMBA server state...
350          */
351
352         setup_logging("smbspool", DEBUG_STDERR);
353
354         smb_init_locale();
355
356         config_file = lp_default_path();
357         if (!lp_load_client(config_file)) {
358                 fprintf(stderr,
359                         "ERROR: Can't load %s - run testparm to debug it\n",
360                         config_file);
361                 goto done;
362         }
363
364         if (workgroup == NULL) {
365                 workgroup = lp_workgroup();
366         }
367
368         load_interfaces();
369
370         do {
371                 cli = smb_connect(workgroup,
372                                   server,
373                                   port,
374                                   printer,
375                                   username,
376                                   password,
377                                   print_user,
378                                   &need_auth);
379                 if (cli == NULL) {
380                         if (need_auth) {
381                                 exit(2);
382                         } else if (getenv("CLASS") == NULL) {
383                                 fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
384                                 sleep(60);
385                                 tries++;
386                         } else {
387                                 fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
388                                 goto done;
389                         }
390                 }
391         } while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
392
393         if (cli == NULL) {
394                 fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
395                 goto done;
396         }
397
398         /*
399          * Now that we are connected to the server, ignore SIGTERM so that we
400          * can finish out any page data the driver sends (e.g. to eject the
401          * current page...  Only ignore SIGTERM if we are printing data from
402          * stdin (otherwise you can't cancel raw jobs...)
403          */
404
405         if (argc < 7) {
406                 CatchSignal(SIGTERM, SIG_IGN);
407         }
408
409         /*
410          * Queue the job...
411          */
412
413         for (i = 0; i < copies; i++) {
414                 status = smb_print(cli, print_title, fp);
415                 if (status != 0) {
416                         break;
417                 }
418         }
419
420         cli_shutdown(cli);
421
422         /*
423          * Return the queue status...
424          */
425
426 done:
427
428         TALLOC_FREE(frame);
429         return (status);
430 }
431
432
433 /*
434  * 'get_exit_code()' - Get the backend exit code based on the current error.
435  */
436
437 static int
438 get_exit_code(struct cli_state * cli,
439               NTSTATUS nt_status)
440 {
441         int i;
442
443         /* List of NTSTATUS errors that are considered
444          * authentication errors
445          */
446         static const NTSTATUS auth_errors[] =
447         {
448                 NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCESS_VIOLATION,
449                 NT_STATUS_SHARING_VIOLATION, NT_STATUS_PRIVILEGE_NOT_HELD,
450                 NT_STATUS_INVALID_ACCOUNT_NAME, NT_STATUS_NO_SUCH_USER,
451                 NT_STATUS_WRONG_PASSWORD, NT_STATUS_LOGON_FAILURE,
452                 NT_STATUS_ACCOUNT_RESTRICTION, NT_STATUS_INVALID_LOGON_HOURS,
453                 NT_STATUS_PASSWORD_EXPIRED, NT_STATUS_ACCOUNT_DISABLED
454         };
455
456
457         fprintf(stderr, "DEBUG: get_exit_code(cli=%p, nt_status=%s [%x])\n",
458                 cli, nt_errstr(nt_status), NT_STATUS_V(nt_status));
459
460         for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
461                 if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
462                         continue;
463                 }
464
465                 if (cli) {
466                         fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
467                 }
468
469                 /*
470                  * 2 = authentication required...
471                  */
472
473                 return (2);
474
475         }
476
477         /*
478          * 1 = fail
479          */
480
481         return (1);
482 }
483
484
485 /*
486  * 'list_devices()' - List the available printers seen on the network...
487  */
488
489 static void
490 list_devices(void)
491 {
492         /*
493          * Eventually, search the local workgroup for available hosts and printers.
494          */
495
496         puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
497 }
498
499
500 static struct cli_state *
501 smb_complete_connection(const char *myname,
502                         const char *server,
503                         int port,
504                         const char *username,
505                         const char *password,
506                         const char *workgroup,
507                         const char *share,
508                         int flags,
509                         bool *need_auth)
510 {
511         struct cli_state *cli;  /* New connection */
512         NTSTATUS        nt_status;
513         struct cli_credentials *creds = NULL;
514         bool use_kerberos = false;
515
516         /* Start the SMB connection */
517         *need_auth = false;
518         nt_status = cli_start_connection(&cli, myname, server, NULL, port,
519                                          SMB_SIGNING_DEFAULT, flags);
520         if (!NT_STATUS_IS_OK(nt_status)) {
521                 fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
522                 return NULL;
523         }
524
525         /*
526          * We pretty much guarantee password must be valid or a pointer to a
527          * 0 char.
528          */
529         if (!password) {
530                 *need_auth = true;
531                 return NULL;
532         }
533
534         if (flags & CLI_FULL_CONNECTION_USE_KERBEROS) {
535                 auth_info_required = "negotiate";
536                 use_kerberos = true;
537         }
538
539         creds = cli_session_creds_init(cli,
540                                        username,
541                                        workgroup,
542                                        NULL, /* realm */
543                                        password,
544                                        use_kerberos,
545                                        false, /* fallback_after_kerberos */
546                                        false, /* use_ccache */
547                                        false); /* password_is_nt_hash */
548         if (creds == NULL) {
549                 fprintf(stderr, "ERROR: cli_session_creds_init failed\n");
550                 cli_shutdown(cli);
551                 return NULL;
552         }
553
554         nt_status = cli_session_setup_creds(cli, creds);
555         if (!NT_STATUS_IS_OK(nt_status)) {
556                 fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
557
558                 if (get_exit_code(cli, nt_status) == 2) {
559                         *need_auth = true;
560                 }
561
562                 cli_shutdown(cli);
563
564                 return NULL;
565         }
566
567         nt_status = cli_tree_connect_creds(cli, share, "?????", creds);
568         if (!NT_STATUS_IS_OK(nt_status)) {
569                 fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
570                         nt_errstr(nt_status));
571
572                 if (get_exit_code(cli, nt_status) == 2) {
573                         *need_auth = true;
574                 }
575
576                 cli_shutdown(cli);
577
578                 return NULL;
579         }
580 #if 0
581         /* Need to work out how to specify this on the URL. */
582         if (smb_encrypt) {
583                 if (!cli_cm_force_encryption_creds(cli, creds, share)) {
584                         fprintf(stderr, "ERROR: encryption setup failed\n");
585                         cli_shutdown(cli);
586                         return NULL;
587                 }
588         }
589 #endif
590
591         return cli;
592 }
593
594 static bool kerberos_ccache_is_valid(void) {
595         krb5_context ctx;
596         const char *ccache_name = NULL;
597         krb5_ccache ccache = NULL;
598         krb5_error_code code;
599
600         code = smb_krb5_init_context_common(&ctx);
601         if (code != 0) {
602                 DBG_ERR("kerberos init context failed (%s)\n",
603                         error_message(code));
604                 return false;
605         }
606
607         ccache_name = krb5_cc_default_name(ctx);
608         if (ccache_name == NULL) {
609                 krb5_free_context(ctx);
610                 return false;
611         }
612
613         code = krb5_cc_resolve(ctx, ccache_name, &ccache);
614         if (code != 0) {
615                 krb5_free_context(ctx);
616                 return false;
617         } else {
618                 krb5_principal default_princ = NULL;
619
620                 code = krb5_cc_get_principal(ctx,
621                                              ccache,
622                                              &default_princ);
623                 if (code != 0) {
624                         krb5_cc_close(ctx, ccache);
625                         krb5_free_context(ctx);
626                         return false;
627                 }
628                 krb5_free_principal(ctx, default_princ);
629         }
630         krb5_cc_close(ctx, ccache);
631         krb5_free_context(ctx);
632
633         return true;
634 }
635
636 /*
637  * 'smb_connect()' - Return a connection to a server.
638  */
639
640 static struct cli_state *       /* O - SMB connection */
641 smb_connect(const char *workgroup,      /* I - Workgroup */
642             const char *server, /* I - Server */
643             const int port,     /* I - Port */
644             const char *share,  /* I - Printer */
645             const char *username,       /* I - Username */
646             const char *password,       /* I - Password */
647             const char *jobusername,    /* I - User who issued the print job */
648             bool *need_auth)
649 {                               /* O - Need authentication? */
650         struct cli_state *cli;  /* New connection */
651         char           *myname = NULL;  /* Client name */
652         struct passwd  *pwd;
653
654         /*
655          * Get the names and addresses of the client and server...
656          */
657         myname = get_myname(talloc_tos());
658         if (!myname) {
659                 return NULL;
660         }
661
662         /*
663          * See if we have a username first.  This is for backwards compatible
664          * behavior with 3.0.14a
665          */
666
667         if (username != NULL && username[0] != '\0') {
668                 if (kerberos_ccache_is_valid()) {
669                         goto kerberos_auth;
670                 }
671         }
672
673         cli = smb_complete_connection(myname,
674                                       server,
675                                       port,
676                                       username,
677                                       password,
678                                       workgroup,
679                                       share,
680                                       0,
681                                       need_auth);
682         if (cli != NULL) {
683                 fputs("DEBUG: Connected with username/password...\n", stderr);
684                 return (cli);
685         }
686
687 kerberos_auth:
688         /*
689          * Try to use the user kerberos credentials (if any) to authenticate
690          */
691         cli = smb_complete_connection(myname, server, port, jobusername, "",
692                                       workgroup, share,
693                                  CLI_FULL_CONNECTION_USE_KERBEROS, need_auth);
694
695         if (cli) {
696                 fputs("DEBUG: Connected using Kerberos...\n", stderr);
697                 return (cli);
698         }
699
700         /* give a chance for a passwordless NTLMSSP session setup */
701         pwd = getpwuid(geteuid());
702         if (pwd == NULL) {
703                 return NULL;
704         }
705
706         cli = smb_complete_connection(myname, server, port, pwd->pw_name, "",
707                                       workgroup, share, 0, need_auth);
708
709         if (cli) {
710                 fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
711                 return (cli);
712         }
713
714         /*
715          * last try. Use anonymous authentication
716          */
717
718         cli = smb_complete_connection(myname, server, port, "", "",
719                                       workgroup, share, 0, need_auth);
720         /*
721          * Return the new connection...
722          */
723
724         return (cli);
725 }
726
727
728 /*
729  * 'smb_print()' - Queue a job for printing using the SMB protocol.
730  */
731
732 static int                      /* O - 0 = success, non-0 = failure */
733 smb_print(struct cli_state * cli,       /* I - SMB connection */
734           const char *print_title,              /* I - Title/job name */
735           FILE * fp)
736 {                               /* I - File to print */
737         uint16_t             fnum;      /* File number */
738         int             nbytes, /* Number of bytes read */
739                         tbytes; /* Total bytes read */
740         char            buffer[8192],   /* Buffer for copy */
741                        *ptr;    /* Pointer into title */
742         char title[1024] = {0};
743         int len;
744         NTSTATUS nt_status;
745
746
747         /*
748          * Sanitize the title...
749          */
750         len = snprintf(title, sizeof(title), "%s", print_title);
751         if (len != strlen(print_title)) {
752                 return 2;
753         }
754
755         for (ptr = title; *ptr; ptr++) {
756                 if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
757                         *ptr = '_';
758                 }
759         }
760
761         /*
762          * Open the printer device...
763          */
764
765         nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
766                           &fnum);
767         if (!NT_STATUS_IS_OK(nt_status)) {
768                 fprintf(stderr, "ERROR: %s opening remote spool %s\n",
769                         nt_errstr(nt_status), title);
770                 return get_exit_code(cli, nt_status);
771         }
772
773         /*
774          * Copy the file to the printer...
775          */
776
777         if (fp != stdin)
778                 rewind(fp);
779
780         tbytes = 0;
781
782         while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
783                 NTSTATUS status;
784
785                 status = cli_writeall(cli, fnum, 0, (uint8_t *)buffer,
786                                       tbytes, nbytes, NULL);
787                 if (!NT_STATUS_IS_OK(status)) {
788                         int ret = get_exit_code(cli, status);
789                         fprintf(stderr, "ERROR: Error writing spool: %s\n",
790                                 nt_errstr(status));
791                         fprintf(stderr, "DEBUG: Returning status %d...\n",
792                                 ret);
793                         cli_close(cli, fnum);
794
795                         return (ret);
796                 }
797                 tbytes += nbytes;
798         }
799
800         nt_status = cli_close(cli, fnum);
801         if (!NT_STATUS_IS_OK(nt_status)) {
802                 fprintf(stderr, "ERROR: %s closing remote spool %s\n",
803                         nt_errstr(nt_status), title);
804                 return get_exit_code(cli, nt_status);
805         } else {
806                 return (0);
807         }
808 }
809
810 static char *
811 uri_unescape_alloc(const char *uritok)
812 {
813         char *ret;
814         char *end;
815         ret = (char *) SMB_STRDUP(uritok);
816         if (!ret) {
817                 return NULL;
818         }
819
820         end = rfc1738_unescape(ret);
821         if (end == NULL) {
822                 free(ret);
823                 return NULL;
824         }
825         return ret;
826 }