f56dc323b6ef9e07737ba174acb03b76cb07c755
[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(NTSTATUS nt_status);
65 static void     list_devices(void);
66 static NTSTATUS
67 smb_connect(struct cli_state **output_cli,
68             const char *workgroup,
69             const char *server,
70             const int port,
71             const char *share,
72             const char *username,
73             const char *password,
74             const char *jobusername);
75 static int      smb_print(struct cli_state *, const char *, FILE *);
76 static char    *uri_unescape_alloc(const char *);
77 #if 0
78 static bool     smb_encrypt;
79 #endif
80
81 static const char *auth_info_required;
82
83 /*
84  * 'main()' - Main entry for SMB backend.
85  */
86
87 int                             /* O - Exit status */
88 main(int argc,                  /* I - Number of command-line arguments */
89      char *argv[])
90 {                               /* I - Command-line arguments */
91         int             i;      /* Looping var */
92         int             copies; /* Number of copies */
93         int             port;   /* Port number */
94         char            uri[1024],      /* URI */
95                        *sep,    /* Pointer to separator */
96                        *tmp, *tmp2;     /* Temp pointers to do escaping */
97         const char     *password = NULL;        /* Password */
98         const char     *username = NULL;        /* Username */
99         char           *server, /* Server name */
100                        *printer;/* Printer name */
101         const char     *workgroup;      /* Workgroup */
102         FILE           *fp;     /* File to print */
103         int             status = 1;     /* Status of LPD job */
104         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
105         struct cli_state *cli = NULL;   /* SMB interface */
106         int             tries = 0;
107         const char     *dev_uri = NULL;
108         const char     *env = NULL;
109         const char     *config_file = NULL;
110         TALLOC_CTX     *frame = talloc_stackframe();
111         const char *print_user = NULL;
112         const char *print_title = NULL;
113         const char *print_file = NULL;
114         const char *print_copies = NULL;
115         int cmp;
116         int len;
117
118         if (argc == 1) {
119                 /*
120                  * NEW!  In CUPS 1.1 the backends are run with no arguments
121                  * to list the available devices.  These can be devices
122                  * served by this backend or any other backends (i.e. you
123                  * can have an SNMP backend that is only used to enumerate
124                  * the available network printers... :)
125                  */
126
127                 list_devices();
128                 status = 0;
129                 goto done;
130         }
131
132         /*
133          * We need at least 5 options if the DEVICE_URI is passed via an env
134          * variable and printing data comes via stdin.
135          * We don't accept more than 7 options in total, including optional.
136          */
137         if (argc < 5 || argc > 8) {
138                 fprintf(stderr,
139 "Usage: %s [DEVICE_URI] job-id user title copies options [file]\n"
140 "       The DEVICE_URI environment variable can also contain the\n"
141 "       destination printer:\n"
142 "\n"
143 "           smb://[username:password@][workgroup/]server[:port]/printer\n",
144                         argv[0]);
145                 goto done;
146         }
147
148         /*
149          * Find out if we have the device_uri in the command line.
150          *
151          * If we are started as a CUPS backend argv[0] is normally the
152          * device_uri!
153          */
154         if (argc == 8) {
155                 /*
156                  * smbspool <uri> <job> <user> <title> <copies> <options> <file>
157                  * 0        1     2     3      4       5        6         7
158                  */
159
160                 dev_uri = argv[1];
161
162                 print_user = argv[3];
163                 print_title = argv[4];
164                 print_copies = argv[5];
165                 print_file = argv[7];
166         } else if (argc == 7) {
167                 int cmp1;
168                 int cmp2;
169
170                 /*
171                  * <uri>    <job> <user> <title> <copies> <options> <file>
172                  * smbspool <uri> <job>  <user>  <title>  <copies>  <options>
173                  * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI
174                  */
175                 cmp1 = strncmp(argv[0], "smb://", 6);
176                 cmp2 = strncmp(argv[1], "smb://", 6);
177
178                 if (cmp1 == 0) {
179                         /*
180                          * <uri>    <job> <user> <title> <copies> <options> <file>
181                          * 0        1     2      3       4        5         6
182                          */
183                         dev_uri = argv[0];
184
185                         print_user = argv[2];
186                         print_title = argv[3];
187                         print_copies = argv[4];
188                         print_file = argv[6];
189                 } else if (cmp2 == 0) {
190                         /*
191                          * smbspool <uri> <job>  <user>  <title>  <copies>  <options>
192                          * 0        1     2      3       4        5         6
193                          */
194                         dev_uri = argv[1];
195
196                         print_user = argv[3];
197                         print_title = argv[4];
198                         print_copies = argv[5];
199                         print_file = NULL;
200                 } else {
201                         /*
202                          * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI
203                          * 0        1     2      3       4        5         6
204                          */
205                         print_user = argv[2];
206                         print_title = argv[3];
207                         print_copies = argv[4];
208                         print_file = argv[6];
209                 }
210         } else if (argc == 6) {
211                 /*
212                  * <uri>    <job> <user> <title> <copies> <options>
213                  * smbspool <job> <user> <title> <copies> <options> | DEVICE_URI
214                  * 0        1     2      3       4        5
215                  */
216                 cmp = strncmp(argv[0], "smb://", 6);
217                 if (cmp == 0) {
218                         dev_uri = argv[0];
219                 }
220
221                 print_user = argv[2];
222                 print_title = argv[3];
223                 print_copies = argv[4];
224         }
225
226         if (print_file != NULL) {
227                 char *endp;
228
229                 fp = fopen(print_file, "rb");
230                 if (fp == NULL) {
231                         fprintf(stderr,
232                                 "ERROR: Unable to open print file: %s",
233                                 print_file);
234                         goto done;
235                 }
236
237                 copies = strtol(print_copies, &endp, 10);
238                 if (print_copies == endp) {
239                         perror("ERROR: Unable to determine number of copies");
240                         goto done;
241                 }
242         } else {
243                 fp = stdin;
244                 copies = 1;
245         }
246
247         /*
248          * Find the URI ...
249          *
250          * The URI in argv[0] is sanitized to remove username/password, so
251          * use DEVICE_URI if available. Otherwise keep the URI already
252          * discovered in argv.
253          */
254         env = getenv("DEVICE_URI");
255         if (env != NULL && env[0] != '\0') {
256           dev_uri = env;
257         }
258
259         if (dev_uri == NULL) {
260                 fprintf(stderr,
261                         "ERROR: No valid device URI has been specified\n");
262                 goto done;
263         }
264
265         cmp = strncmp(dev_uri, "smb://", 6);
266         if (cmp != 0) {
267                 fprintf(stderr,
268                         "ERROR: No valid device URI has been specified\n");
269                 goto done;
270         }
271         len = snprintf(uri, sizeof(uri), "%s", dev_uri);
272         if (len >= sizeof(uri)) {
273                 fprintf(stderr,
274                         "ERROR: The URI is too long.\n");
275                 goto done;
276         }
277
278         auth_info_required = getenv("AUTH_INFO_REQUIRED");
279         if (auth_info_required == NULL) {
280                 auth_info_required = "samba";
281         }
282
283         /*
284          * Extract the destination from the URI...
285          */
286
287         if ((sep = strrchr_m(uri, '@')) != NULL) {
288                 tmp = uri + 6;
289                 *sep++ = '\0';
290
291                 /* username is in tmp */
292
293                 server = sep;
294
295                 /*
296                  * Extract password as needed...
297                  */
298
299                 if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
300                         *tmp2++ = '\0';
301                         password = uri_unescape_alloc(tmp2);
302                 }
303                 username = uri_unescape_alloc(tmp);
304         } else {
305                 env = getenv("AUTH_USERNAME");
306                 if (env != NULL && strlen(env) > 0) {
307                         username = env;
308                 }
309
310                 env = getenv("AUTH_PASSWORD");
311                 if (env != NULL && strlen(env) > 0) {
312                         password = env;
313                 }
314
315                 server = uri + 6;
316         }
317
318         if (password != NULL) {
319                 auth_info_required = "username,password";
320         }
321
322         tmp = server;
323
324         if ((sep = strchr_m(tmp, '/')) == NULL) {
325                 fputs("ERROR: Bad URI - need printer name!\n", stderr);
326                 goto done;
327         }
328
329         *sep++ = '\0';
330         tmp2 = sep;
331
332         if ((sep = strchr_m(tmp2, '/')) != NULL) {
333                 /*
334                  * Convert to smb://[username:password@]workgroup/server/printer...
335                  */
336
337                 *sep++ = '\0';
338
339                 workgroup = uri_unescape_alloc(tmp);
340                 server = uri_unescape_alloc(tmp2);
341                 printer = uri_unescape_alloc(sep);
342         } else {
343                 workgroup = NULL;
344                 server = uri_unescape_alloc(tmp);
345                 printer = uri_unescape_alloc(tmp2);
346         }
347
348         if ((sep = strrchr_m(server, ':')) != NULL) {
349                 *sep++ = '\0';
350
351                 port = atoi(sep);
352         } else {
353                 port = 0;
354         }
355
356         /*
357          * Setup the SAMBA server state...
358          */
359
360         setup_logging("smbspool", DEBUG_STDERR);
361
362         smb_init_locale();
363
364         config_file = lp_default_path();
365         if (!lp_load_client(config_file)) {
366                 fprintf(stderr,
367                         "ERROR: Can't load %s - run testparm to debug it\n",
368                         config_file);
369                 goto done;
370         }
371
372         if (workgroup == NULL) {
373                 workgroup = lp_workgroup();
374         }
375
376         load_interfaces();
377
378         do {
379                 nt_status = smb_connect(&cli,
380                                         workgroup,
381                                         server,
382                                         port,
383                                         printer,
384                                         username,
385                                         password,
386                                         print_user);
387                 if (!NT_STATUS_IS_OK(nt_status)) {
388                         status = get_exit_code(nt_status);
389                         if (status == 2) {
390                                 fprintf(stderr,
391                                         "DEBUG: Unable to connect to CIFS "
392                                         "host: %s",
393                                         nt_errstr(nt_status));
394                                 goto done;
395                         } else if (getenv("CLASS") == NULL) {
396                                 fprintf(stderr,
397                                         "ERROR: Unable to connect to CIFS "
398                                         "host: %s. Will retry in 60 "
399                                         "seconds...\n",
400                                         nt_errstr(nt_status));
401                                 sleep(60);
402                                 tries++;
403                         } else {
404                                 fprintf(stderr,
405                                         "ERROR: Unable to connect to CIFS "
406                                         "host: %s. Trying next printer...\n",
407                                         nt_errstr(nt_status));
408                                 goto done;
409                         }
410                 }
411         } while (!NT_STATUS_IS_OK(nt_status) && (tries < MAX_RETRY_CONNECT));
412
413         if (cli == NULL) {
414                 fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
415                 goto done;
416         }
417
418         /*
419          * Now that we are connected to the server, ignore SIGTERM so that we
420          * can finish out any page data the driver sends (e.g. to eject the
421          * current page...  Only ignore SIGTERM if we are printing data from
422          * stdin (otherwise you can't cancel raw jobs...)
423          */
424
425         if (argc < 7) {
426                 CatchSignal(SIGTERM, SIG_IGN);
427         }
428
429         /*
430          * Queue the job...
431          */
432
433         for (i = 0; i < copies; i++) {
434                 status = smb_print(cli, print_title, fp);
435                 if (status != 0) {
436                         break;
437                 }
438         }
439
440         cli_shutdown(cli);
441
442         /*
443          * Return the queue status...
444          */
445
446 done:
447
448         TALLOC_FREE(frame);
449         return (status);
450 }
451
452
453 /*
454  * 'get_exit_code()' - Get the backend exit code based on the current error.
455  */
456
457 static int
458 get_exit_code(NTSTATUS nt_status)
459 {
460         size_t i;
461
462         /* List of NTSTATUS errors that are considered
463          * authentication errors
464          */
465         static const NTSTATUS auth_errors[] =
466         {
467                 NT_STATUS_ACCESS_DENIED,
468                 NT_STATUS_ACCESS_VIOLATION,
469                 NT_STATUS_ACCOUNT_DISABLED,
470                 NT_STATUS_ACCOUNT_LOCKED_OUT,
471                 NT_STATUS_ACCOUNT_RESTRICTION,
472                 NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND,
473                 NT_STATUS_INVALID_ACCOUNT_NAME,
474                 NT_STATUS_INVALID_COMPUTER_NAME,
475                 NT_STATUS_INVALID_LOGON_HOURS,
476                 NT_STATUS_INVALID_WORKSTATION,
477                 NT_STATUS_LOGON_FAILURE,
478                 NT_STATUS_NO_SUCH_USER,
479                 NT_STATUS_NO_SUCH_DOMAIN,
480                 NT_STATUS_NO_LOGON_SERVERS,
481                 NT_STATUS_PASSWORD_EXPIRED,
482                 NT_STATUS_PRIVILEGE_NOT_HELD,
483                 NT_STATUS_SHARING_VIOLATION,
484                 NT_STATUS_WRONG_PASSWORD,
485         };
486
487
488         fprintf(stderr,
489                 "DEBUG: get_exit_code(nt_status=%s [%x])\n",
490                 nt_errstr(nt_status), NT_STATUS_V(nt_status));
491
492         for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
493                 if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
494                         continue;
495                 }
496
497                 fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
498
499                 /*
500                  * 2 = authentication required...
501                  */
502
503                 return (2);
504
505         }
506
507         /*
508          * 1 = fail
509          */
510
511         return (1);
512 }
513
514
515 /*
516  * 'list_devices()' - List the available printers seen on the network...
517  */
518
519 static void
520 list_devices(void)
521 {
522         /*
523          * Eventually, search the local workgroup for available hosts and printers.
524          */
525
526         puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
527 }
528
529
530 static NTSTATUS
531 smb_complete_connection(struct cli_state **output_cli,
532                         const char *myname,
533                         const char *server,
534                         int port,
535                         const char *username,
536                         const char *password,
537                         const char *workgroup,
538                         const char *share,
539                         bool use_kerberos,
540                         bool fallback_after_kerberos)
541 {
542         struct cli_state *cli;  /* New connection */
543         NTSTATUS        nt_status;
544         struct cli_credentials *creds = NULL;
545
546         /* Start the SMB connection */
547         nt_status = cli_start_connection(&cli, myname, server, NULL, port,
548                                          SMB_SIGNING_DEFAULT, 0);
549         if (!NT_STATUS_IS_OK(nt_status)) {
550                 fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
551                 return nt_status;
552         }
553
554         creds = cli_session_creds_init(cli,
555                                        username,
556                                        workgroup,
557                                        NULL, /* realm */
558                                        password,
559                                        use_kerberos,
560                                        fallback_after_kerberos,
561                                        false, /* use_ccache */
562                                        false); /* password_is_nt_hash */
563         if (creds == NULL) {
564                 fprintf(stderr, "ERROR: cli_session_creds_init failed\n");
565                 cli_shutdown(cli);
566                 return NT_STATUS_NO_MEMORY;
567         }
568
569         nt_status = cli_session_setup_creds(cli, creds);
570         if (!NT_STATUS_IS_OK(nt_status)) {
571                 fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
572
573                 cli_shutdown(cli);
574
575                 return nt_status;
576         }
577
578         nt_status = cli_tree_connect_creds(cli, share, "?????", creds);
579         if (!NT_STATUS_IS_OK(nt_status)) {
580                 fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
581                         nt_errstr(nt_status));
582
583                 cli_shutdown(cli);
584
585                 return nt_status;
586         }
587 #if 0
588         /* Need to work out how to specify this on the URL. */
589         if (smb_encrypt) {
590                 if (!cli_cm_force_encryption_creds(cli, creds, share)) {
591                         fprintf(stderr, "ERROR: encryption setup failed\n");
592                         cli_shutdown(cli);
593                         return NULL;
594                 }
595         }
596 #endif
597
598         *output_cli = cli;
599         return NT_STATUS_OK;
600 }
601
602 static bool kerberos_ccache_is_valid(void) {
603         krb5_context ctx;
604         const char *ccache_name = NULL;
605         krb5_ccache ccache = NULL;
606         krb5_error_code code;
607
608         code = smb_krb5_init_context_common(&ctx);
609         if (code != 0) {
610                 DBG_ERR("kerberos init context failed (%s)\n",
611                         error_message(code));
612                 return false;
613         }
614
615         ccache_name = krb5_cc_default_name(ctx);
616         if (ccache_name == NULL) {
617                 DBG_ERR("Failed to get default ccache name\n");
618                 krb5_free_context(ctx);
619                 return false;
620         }
621
622         code = krb5_cc_resolve(ctx, ccache_name, &ccache);
623         if (code != 0) {
624                 DBG_ERR("Failed to resolve ccache name: %s\n",
625                         ccache_name);
626                 krb5_free_context(ctx);
627                 return false;
628         } else {
629                 krb5_principal default_princ = NULL;
630                 char *princ_name = NULL;
631
632                 code = krb5_cc_get_principal(ctx,
633                                              ccache,
634                                              &default_princ);
635                 if (code != 0) {
636                         DBG_ERR("Failed to get default principal from "
637                                 "ccache: %s\n",
638                                 ccache_name);
639                         krb5_cc_close(ctx, ccache);
640                         krb5_free_context(ctx);
641                         return false;
642                 }
643
644                 code = krb5_unparse_name(ctx,
645                                          default_princ,
646                                          &princ_name);
647                 if (code == 0) {
648                         fprintf(stderr,
649                                 "DEBUG: Try to authenticate as %s\n",
650                                 princ_name);
651                         krb5_free_unparsed_name(ctx, princ_name);
652                 }
653                 krb5_free_principal(ctx, default_princ);
654         }
655         krb5_cc_close(ctx, ccache);
656         krb5_free_context(ctx);
657
658         return true;
659 }
660
661 /*
662  * 'smb_connect()' - Return a connection to a server.
663  */
664
665 static NTSTATUS
666 smb_connect(struct cli_state **output_cli,
667             const char *workgroup,      /* I - Workgroup */
668             const char *server, /* I - Server */
669             const int port,     /* I - Port */
670             const char *share,  /* I - Printer */
671             const char *username,       /* I - Username */
672             const char *password,       /* I - Password */
673             const char *jobusername)    /* I - User who issued the print job */
674 {
675         struct cli_state *cli = NULL;   /* New connection */
676         char           *myname = NULL;  /* Client name */
677         struct passwd  *pwd;
678         bool use_kerberos = false;
679         bool fallback_after_kerberos = false;
680         const char *user = username;
681         NTSTATUS nt_status;
682
683         /*
684          * Get the names and addresses of the client and server...
685          */
686         myname = get_myname(talloc_tos());
687         if (!myname) {
688                 return NT_STATUS_NO_MEMORY;
689         }
690
691
692         if (strcmp(auth_info_required, "negotiate") == 0) {
693                 if (!kerberos_ccache_is_valid()) {
694                         fprintf(stderr,
695                                 "ERROR: No valid Kerberos credential cache found! "
696                                 "Using smbspool_krb5_wrapper may help.\n");
697                         return NT_STATUS_LOGON_FAILURE;
698                 }
699                 user = jobusername;
700
701                 use_kerberos = true;
702                 fprintf(stderr,
703                         "DEBUG: Try to connect using Kerberos ...\n");
704         } else if (strcmp(auth_info_required, "username,password") == 0) {
705                 if (username == NULL) {
706                         return NT_STATUS_INVALID_ACCOUNT_NAME;
707                 }
708
709                 /* Fallback to NTLM */
710                 fallback_after_kerberos = true;
711
712                 fprintf(stderr,
713                         "DEBUG: Try to connect using username/password ...\n");
714         } else if (strcmp(auth_info_required, "none") == 0) {
715                 goto anonymous;
716         } else if (strcmp(auth_info_required, "samba") == 0) {
717                 if (username != NULL) {
718                         fallback_after_kerberos = true;
719                 } else if (kerberos_ccache_is_valid()) {
720                         auth_info_required = "negotiate";
721
722                         user = jobusername;
723                         use_kerberos = true;
724                 } else {
725                         fprintf(stderr,
726                                 "DEBUG: This backend requires credentials!\n");
727                         return NT_STATUS_ACCESS_DENIED;
728                 }
729         } else {
730                 return NT_STATUS_ACCESS_DENIED;
731         }
732
733         nt_status = smb_complete_connection(&cli,
734                                             myname,
735                                             server,
736                                             port,
737                                             user,
738                                             password,
739                                             workgroup,
740                                             share,
741                                             true, /* try kerberos */
742                                             fallback_after_kerberos);
743         if (NT_STATUS_IS_OK(nt_status)) {
744                 fprintf(stderr, "DEBUG: SMB connection established.\n");
745
746                 *output_cli = cli;
747                 return NT_STATUS_OK;
748         }
749
750         if (!use_kerberos) {
751                 fprintf(stderr, "ERROR: SMB connection failed!\n");
752                 return nt_status;
753         }
754
755         /* give a chance for a passwordless NTLMSSP session setup */
756         pwd = getpwuid(geteuid());
757         if (pwd == NULL) {
758                 return NT_STATUS_ACCESS_DENIED;
759         }
760
761         nt_status = smb_complete_connection(&cli,
762                                             myname,
763                                             server,
764                                             port,
765                                             pwd->pw_name,
766                                             "",
767                                             workgroup,
768                                             share,
769                                             false, false);
770         if (NT_STATUS_IS_OK(nt_status)) {
771                 fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
772
773                 *output_cli = cli;
774                 return NT_STATUS_OK;
775         }
776
777         /*
778          * last try. Use anonymous authentication
779          */
780
781 anonymous:
782         nt_status = smb_complete_connection(&cli,
783                                             myname,
784                                             server,
785                                             port,
786                                             "",
787                                             "",
788                                             workgroup,
789                                             share,
790                                             false, false);
791         if (NT_STATUS_IS_OK(nt_status)) {
792                 *output_cli = cli;
793                 return NT_STATUS_OK;
794         }
795
796         return nt_status;
797 }
798
799
800 /*
801  * 'smb_print()' - Queue a job for printing using the SMB protocol.
802  */
803
804 static int                      /* O - 0 = success, non-0 = failure */
805 smb_print(struct cli_state * cli,       /* I - SMB connection */
806           const char *print_title,              /* I - Title/job name */
807           FILE * fp)
808 {                               /* I - File to print */
809         uint16_t             fnum;      /* File number */
810         int             nbytes, /* Number of bytes read */
811                         tbytes; /* Total bytes read */
812         char            buffer[8192],   /* Buffer for copy */
813                        *ptr;    /* Pointer into title */
814         char title[1024] = {0};
815         int len;
816         NTSTATUS nt_status;
817
818
819         /*
820          * Sanitize the title...
821          */
822         len = snprintf(title, sizeof(title), "%s", print_title);
823         if (len != strlen(print_title)) {
824                 return 2;
825         }
826
827         for (ptr = title; *ptr; ptr++) {
828                 if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
829                         *ptr = '_';
830                 }
831         }
832
833         /*
834          * Open the printer device...
835          */
836
837         nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
838                           &fnum);
839         if (!NT_STATUS_IS_OK(nt_status)) {
840                 fprintf(stderr, "ERROR: %s opening remote spool %s\n",
841                         nt_errstr(nt_status), title);
842                 return get_exit_code(nt_status);
843         }
844
845         /*
846          * Copy the file to the printer...
847          */
848
849         if (fp != stdin)
850                 rewind(fp);
851
852         tbytes = 0;
853
854         while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
855                 NTSTATUS status;
856
857                 status = cli_writeall(cli, fnum, 0, (uint8_t *)buffer,
858                                       tbytes, nbytes, NULL);
859                 if (!NT_STATUS_IS_OK(status)) {
860                         int ret = get_exit_code(status);
861                         fprintf(stderr, "ERROR: Error writing spool: %s\n",
862                                 nt_errstr(status));
863                         fprintf(stderr, "DEBUG: Returning status %d...\n",
864                                 ret);
865                         cli_close(cli, fnum);
866
867                         return (ret);
868                 }
869                 tbytes += nbytes;
870         }
871
872         nt_status = cli_close(cli, fnum);
873         if (!NT_STATUS_IS_OK(nt_status)) {
874                 fprintf(stderr, "ERROR: %s closing remote spool %s\n",
875                         nt_errstr(nt_status), title);
876                 return get_exit_code(nt_status);
877         } else {
878                 return (0);
879         }
880 }
881
882 static char *
883 uri_unescape_alloc(const char *uritok)
884 {
885         char *ret;
886         char *end;
887         ret = (char *) SMB_STRDUP(uritok);
888         if (!ret) {
889                 return NULL;
890         }
891
892         end = rfc1738_unescape(ret);
893         if (end == NULL) {
894                 free(ret);
895                 return NULL;
896         }
897         return ret;
898 }