~zulcss/samba/server-dailies-3.4

« back to all changes in this revision

Viewing changes to source3/client/smbspool.c

  • Committer: Chuck Short
  • Date: 2010-09-28 20:38:39 UTC
  • Revision ID: zulcss@ubuntu.com-20100928203839-pgjulytsi9ue63x1
Initial version

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
27
/*
 
28
 * Starting with CUPS 1.3, Kerberos support is provided by cupsd including
 
29
 * the forwarding of user credentials via the authenticated session between
 
30
 * user and server and the KRB5CCNAME environment variable which will point
 
31
 * to a temporary file or an in-memory representation depending on the version
 
32
 * of Kerberos you use.  As a result, all of the ticket code that used to
 
33
 * live here has been removed, and we depend on the user session (if you
 
34
 * run smbspool by hand) or cupsd to provide the necessary Kerberos info.
 
35
 *
 
36
 * Also, the AUTH_USERNAME and AUTH_PASSWORD environment variables provide
 
37
 * for per-job authentication for non-Kerberized printing.  We use those
 
38
 * if there is no username and password specified in the device URI.
 
39
 *
 
40
 * Finally, if we have an authentication failure we return exit code 2
 
41
 * which tells CUPS to hold the job for authentication and bug the user
 
42
 * to get the necessary credentials.
 
43
 */
 
44
 
 
45
#define MAX_RETRY_CONNECT        3
 
46
 
 
47
 
 
48
/*
 
49
 * Globals...
 
50
 */
 
51
 
 
52
 
 
53
 
 
54
/*
 
55
 * Local functions...
 
56
 */
 
57
 
 
58
static int      get_exit_code(struct cli_state * cli, NTSTATUS nt_status);
 
59
static void     list_devices(void);
 
60
static struct cli_state *smb_complete_connection(const char *, const char *,
 
61
        int, const char *, const char *, const char *, const char *, int, bool *need_auth);
 
62
static struct cli_state *smb_connect(const char *, const char *, int, const
 
63
        char *, const char *, const char *, const char *, bool *need_auth);
 
64
static int      smb_print(struct cli_state *, char *, FILE *);
 
65
static char    *uri_unescape_alloc(const char *);
 
66
#if 0
 
67
static bool     smb_encrypt;
 
68
#endif
 
69
 
 
70
/*
 
71
 * 'main()' - Main entry for SMB backend.
 
72
 */
 
73
 
 
74
int                             /* O - Exit status */
 
75
main(int argc,                  /* I - Number of command-line arguments */
 
76
     char *argv[])
 
77
{                               /* I - Command-line arguments */
 
78
        int             i;      /* Looping var */
 
79
        int             copies; /* Number of copies */
 
80
        int             port;   /* Port number */
 
81
        char            uri[1024],      /* URI */
 
82
                       *sep,    /* Pointer to separator */
 
83
                       *tmp, *tmp2,     /* Temp pointers to do escaping */
 
84
                       *password;       /* Password */
 
85
        char           *username,       /* Username */
 
86
                       *server, /* Server name */
 
87
                       *printer;/* Printer name */
 
88
        const char     *workgroup;      /* Workgroup */
 
89
        FILE           *fp;     /* File to print */
 
90
        int             status = 1;     /* Status of LPD job */
 
91
        struct cli_state *cli;  /* SMB interface */
 
92
        char            null_str[1];
 
93
        int             tries = 0;
 
94
        bool            need_auth = true;
 
95
        const char     *dev_uri;
 
96
        TALLOC_CTX     *frame = talloc_stackframe();
 
97
 
 
98
        null_str[0] = '\0';
 
99
 
 
100
        /*
 
101
         * we expect the URI in argv[0]. Detect the case where it is in
 
102
         * argv[1] and cope
 
103
         */
 
104
        if (argc > 2 && strncmp(argv[0], "smb://", 6) &&
 
105
            strncmp(argv[1], "smb://", 6) == 0) {
 
106
                argv++;
 
107
                argc--;
 
108
        }
 
109
 
 
110
        if (argc == 1) {
 
111
                /*
 
112
                 * NEW!  In CUPS 1.1 the backends are run with no arguments
 
113
                 * to list the available devices.  These can be devices
 
114
                 * served by this backend or any other backends (i.e. you
 
115
                 * can have an SNMP backend that is only used to enumerate
 
116
                 * the available network printers... :)
 
117
                 */
 
118
 
 
119
                list_devices();
 
120
                status = 0;
 
121
                goto done;
 
122
        }
 
123
 
 
124
        if (argc < 6 || argc > 7) {
 
125
                fprintf(stderr,
 
126
"Usage: %s [DEVICE_URI] job-id user title copies options [file]\n"
 
127
"       The DEVICE_URI environment variable can also contain the\n"
 
128
"       destination printer:\n"
 
129
"\n"
 
130
"           smb://[username:password@][workgroup/]server[:port]/printer\n",
 
131
                        argv[0]);
 
132
                goto done;
 
133
        }
 
134
 
 
135
        /*
 
136
         * If we have 7 arguments, print the file named on the command-line.
 
137
         * Otherwise, print data from stdin...
 
138
         */
 
139
 
 
140
        if (argc == 6) {
 
141
                /*
 
142
                 * Print from Copy stdin to a temporary file...
 
143
                 */
 
144
 
 
145
                fp = stdin;
 
146
                copies = 1;
 
147
        } else if ((fp = fopen(argv[6], "rb")) == NULL) {
 
148
                perror("ERROR: Unable to open print file");
 
149
                goto done;
 
150
        } else {
 
151
                copies = atoi(argv[4]);
 
152
        }
 
153
 
 
154
        /*
 
155
         * Find the URI...
 
156
         */
 
157
 
 
158
        dev_uri = getenv("DEVICE_URI");
 
159
        if (dev_uri) {
 
160
                strncpy(uri, dev_uri, sizeof(uri) - 1);
 
161
        } else if (strncmp(argv[0], "smb://", 6) == 0) {
 
162
                strncpy(uri, argv[0], sizeof(uri) - 1);
 
163
        } else {
 
164
                fputs("ERROR: No device URI found in DEVICE_URI environment variable or argv[0] !\n", stderr);
 
165
                goto done;
 
166
        }
 
167
 
 
168
        uri[sizeof(uri) - 1] = '\0';
 
169
 
 
170
        /*
 
171
         * Extract the destination from the URI...
 
172
         */
 
173
 
 
174
        if ((sep = strrchr_m(uri, '@')) != NULL) {
 
175
                tmp = uri + 6;
 
176
                *sep++ = '\0';
 
177
 
 
178
                /* username is in tmp */
 
179
 
 
180
                server = sep;
 
181
 
 
182
                /*
 
183
                 * Extract password as needed...
 
184
                 */
 
185
 
 
186
                if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
 
187
                        *tmp2++ = '\0';
 
188
                        password = uri_unescape_alloc(tmp2);
 
189
                } else {
 
190
                        password = null_str;
 
191
                }
 
192
                username = uri_unescape_alloc(tmp);
 
193
        } else {
 
194
                if ((username = getenv("AUTH_USERNAME")) == NULL) {
 
195
                        username = null_str;
 
196
                }
 
197
 
 
198
                if ((password = getenv("AUTH_PASSWORD")) == NULL) {
 
199
                        password = null_str;
 
200
                }
 
201
 
 
202
                server = uri + 6;
 
203
        }
 
204
 
 
205
        tmp = server;
 
206
 
 
207
        if ((sep = strchr_m(tmp, '/')) == NULL) {
 
208
                fputs("ERROR: Bad URI - need printer name!\n", stderr);
 
209
                goto done;
 
210
        }
 
211
 
 
212
        *sep++ = '\0';
 
213
        tmp2 = sep;
 
214
 
 
215
        if ((sep = strchr_m(tmp2, '/')) != NULL) {
 
216
                /*
 
217
                 * Convert to smb://[username:password@]workgroup/server/printer...
 
218
                 */
 
219
 
 
220
                *sep++ = '\0';
 
221
 
 
222
                workgroup = uri_unescape_alloc(tmp);
 
223
                server = uri_unescape_alloc(tmp2);
 
224
                printer = uri_unescape_alloc(sep);
 
225
        } else {
 
226
                workgroup = NULL;
 
227
                server = uri_unescape_alloc(tmp);
 
228
                printer = uri_unescape_alloc(tmp2);
 
229
        }
 
230
 
 
231
        if ((sep = strrchr_m(server, ':')) != NULL) {
 
232
                *sep++ = '\0';
 
233
 
 
234
                port = atoi(sep);
 
235
        } else {
 
236
                port = 0;
 
237
        }
 
238
 
 
239
        /*
 
240
         * Setup the SAMBA server state...
 
241
         */
 
242
 
 
243
        setup_logging("smbspool", True);
 
244
 
 
245
        lp_set_in_client(True); /* Make sure that we tell lp_load we are */
 
246
 
 
247
        load_case_tables();
 
248
 
 
249
        if (!lp_load(get_dyn_CONFIGFILE(), True, False, False, True)) {
 
250
                fprintf(stderr, "ERROR: Can't load %s - run testparm to debug it\n", get_dyn_CONFIGFILE());
 
251
                goto done;
 
252
        }
 
253
 
 
254
        if (workgroup == NULL) {
 
255
                workgroup = lp_workgroup();
 
256
        }
 
257
 
 
258
        load_interfaces();
 
259
 
 
260
        do {
 
261
                cli = smb_connect(workgroup, server, port, printer,
 
262
                        username, password, argv[2], &need_auth);
 
263
                if (cli == NULL) {
 
264
                        if (need_auth) {
 
265
                                exit(2);
 
266
                        } else if (getenv("CLASS") == NULL) {
 
267
                                fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n");
 
268
                                sleep(60);
 
269
                                tries++;
 
270
                        } else {
 
271
                                fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n");
 
272
                                goto done;
 
273
                        }
 
274
                }
 
275
        } while ((cli == NULL) && (tries < MAX_RETRY_CONNECT));
 
276
 
 
277
        if (cli == NULL) {
 
278
                fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
 
279
                goto done;
 
280
        }
 
281
 
 
282
        /*
 
283
         * Now that we are connected to the server, ignore SIGTERM so that we
 
284
         * can finish out any page data the driver sends (e.g. to eject the
 
285
         * current page...  Only ignore SIGTERM if we are printing data from
 
286
         * stdin (otherwise you can't cancel raw jobs...)
 
287
         */
 
288
 
 
289
        if (argc < 7) {
 
290
                CatchSignal(SIGTERM, SIG_IGN);
 
291
        }
 
292
 
 
293
        /*
 
294
         * Queue the job...
 
295
         */
 
296
 
 
297
        for (i = 0; i < copies; i++) {
 
298
                status = smb_print(cli, argv[3] /* title */ , fp);
 
299
                if (status != 0) {
 
300
                        break;
 
301
                }
 
302
        }
 
303
 
 
304
        cli_shutdown(cli);
 
305
 
 
306
        /*
 
307
         * Return the queue status...
 
308
         */
 
309
 
 
310
done:
 
311
 
 
312
        TALLOC_FREE(frame);
 
313
        return (status);
 
314
}
 
315
 
 
316
 
 
317
/*
 
318
 * 'get_exit_code()' - Get the backend exit code based on the current error.
 
319
 */
 
320
 
 
321
static int
 
322
get_exit_code(struct cli_state * cli,
 
323
              NTSTATUS nt_status)
 
324
{
 
325
        int i;
 
326
 
 
327
        /* List of NTSTATUS errors that are considered
 
328
         * authentication errors
 
329
         */
 
330
        static const NTSTATUS auth_errors[] =
 
331
        {
 
332
                NT_STATUS_ACCESS_DENIED, NT_STATUS_ACCESS_VIOLATION,
 
333
                NT_STATUS_SHARING_VIOLATION, NT_STATUS_PRIVILEGE_NOT_HELD,
 
334
                NT_STATUS_INVALID_ACCOUNT_NAME, NT_STATUS_NO_SUCH_USER,
 
335
                NT_STATUS_WRONG_PASSWORD, NT_STATUS_LOGON_FAILURE,
 
336
                NT_STATUS_ACCOUNT_RESTRICTION, NT_STATUS_INVALID_LOGON_HOURS,
 
337
                NT_STATUS_PASSWORD_EXPIRED, NT_STATUS_ACCOUNT_DISABLED
 
338
        };
 
339
 
 
340
 
 
341
        fprintf(stderr, "DEBUG: get_exit_code(cli=%p, nt_status=%x)\n",
 
342
                cli, NT_STATUS_V(nt_status));
 
343
 
 
344
        for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
 
345
                if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
 
346
                        continue;
 
347
                }
 
348
 
 
349
                if (cli) {
 
350
                        if (cli->use_kerberos && cli->got_kerberos_mechanism)
 
351
                                fputs("ATTR: auth-info-required=negotiate\n", stderr);
 
352
                        else
 
353
                                fputs("ATTR: auth-info-required=username,password\n", stderr);
 
354
                }
 
355
 
 
356
                /*
 
357
                 * 2 = authentication required...
 
358
                 */
 
359
 
 
360
                return (2);
 
361
 
 
362
        }
 
363
 
 
364
        /*
 
365
         * 1 = fail
 
366
         */
 
367
 
 
368
        return (1);
 
369
}
 
370
 
 
371
 
 
372
/*
 
373
 * 'list_devices()' - List the available printers seen on the network...
 
374
 */
 
375
 
 
376
static void
 
377
list_devices(void)
 
378
{
 
379
        /*
 
380
         * Eventually, search the local workgroup for available hosts and printers.
 
381
         */
 
382
 
 
383
        puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
 
384
}
 
385
 
 
386
 
 
387
static struct cli_state *
 
388
smb_complete_connection(const char *myname,
 
389
                        const char *server,
 
390
                        int port,
 
391
                        const char *username,
 
392
                        const char *password,
 
393
                        const char *workgroup,
 
394
                        const char *share,
 
395
                        int flags,
 
396
                        bool *need_auth)
 
397
{
 
398
        struct cli_state *cli;  /* New connection */
 
399
        NTSTATUS        nt_status;
 
400
 
 
401
        /* Start the SMB connection */
 
402
        *need_auth = false;
 
403
        nt_status = cli_start_connection(&cli, myname, server, NULL, port,
 
404
                                         Undefined, flags, NULL);
 
405
        if (!NT_STATUS_IS_OK(nt_status)) {
 
406
                fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
 
407
                return NULL;
 
408
        }
 
409
 
 
410
        /*
 
411
         * We pretty much guarantee password must be valid or a pointer to a
 
412
         * 0 char.
 
413
         */
 
414
        if (!password) {
 
415
                *need_auth = true;
 
416
                return NULL;
 
417
        }
 
418
 
 
419
        nt_status = cli_session_setup(cli, username,
 
420
                                      password, strlen(password) + 1,
 
421
                                      password, strlen(password) + 1,
 
422
                                      workgroup);
 
423
        if (!NT_STATUS_IS_OK(nt_status)) {
 
424
                fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
 
425
 
 
426
                if (get_exit_code(cli, nt_status) == 2) {
 
427
                        *need_auth = true;
 
428
                }
 
429
 
 
430
                cli_shutdown(cli);
 
431
 
 
432
                return NULL;
 
433
        }
 
434
 
 
435
        nt_status = cli_tcon_andx(cli, share, "?????", password,
 
436
                                  strlen(password) + 1);
 
437
        if (!NT_STATUS_IS_OK(nt_status)) {
 
438
                fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
 
439
                        nt_errstr(nt_status));
 
440
 
 
441
                if (get_exit_code(cli, nt_status) == 2) {
 
442
                        *need_auth = true;
 
443
                }
 
444
 
 
445
                cli_shutdown(cli);
 
446
 
 
447
                return NULL;
 
448
        }
 
449
#if 0
 
450
        /* Need to work out how to specify this on the URL. */
 
451
        if (smb_encrypt) {
 
452
                if (!cli_cm_force_encryption(cli,
 
453
                                             username,
 
454
                                             password,
 
455
                                             workgroup,
 
456
                                             share)) {
 
457
                        fprintf(stderr, "ERROR: encryption setup failed\n");
 
458
                        cli_shutdown(cli);
 
459
                        return NULL;
 
460
                }
 
461
        }
 
462
#endif
 
463
 
 
464
        return cli;
 
465
}
 
466
 
 
467
/*
 
468
 * 'smb_connect()' - Return a connection to a server.
 
469
 */
 
470
 
 
471
static struct cli_state *       /* O - SMB connection */
 
472
smb_connect(const char *workgroup,      /* I - Workgroup */
 
473
            const char *server, /* I - Server */
 
474
            const int port,     /* I - Port */
 
475
            const char *share,  /* I - Printer */
 
476
            const char *username,       /* I - Username */
 
477
            const char *password,       /* I - Password */
 
478
            const char *jobusername,    /* I - User who issued the print job */
 
479
            bool *need_auth)
 
480
{                               /* O - Need authentication? */
 
481
        struct cli_state *cli;  /* New connection */
 
482
        char           *myname = NULL;  /* Client name */
 
483
        struct passwd  *pwd;
 
484
 
 
485
        /*
 
486
         * Get the names and addresses of the client and server...
 
487
         */
 
488
        myname = get_myname(talloc_tos());
 
489
        if (!myname) {
 
490
                return NULL;
 
491
        }
 
492
 
 
493
        /*
 
494
         * See if we have a username first.  This is for backwards compatible
 
495
         * behavior with 3.0.14a
 
496
         */
 
497
 
 
498
        if (username && *username && !getenv("KRB5CCNAME")) {
 
499
                cli = smb_complete_connection(myname, server, port, username,
 
500
                                    password, workgroup, share, 0, need_auth);
 
501
                if (cli) {
 
502
                        fputs("DEBUG: Connected with username/password...\n", stderr);
 
503
                        return (cli);
 
504
                }
 
505
        }
 
506
 
 
507
        /*
 
508
         * Try to use the user kerberos credentials (if any) to authenticate
 
509
         */
 
510
        cli = smb_complete_connection(myname, server, port, jobusername, "",
 
511
                                      workgroup, share,
 
512
                                 CLI_FULL_CONNECTION_USE_KERBEROS, need_auth);
 
513
 
 
514
        if (cli) {
 
515
                fputs("DEBUG: Connected using Kerberos...\n", stderr);
 
516
                return (cli);
 
517
        }
 
518
 
 
519
        /* give a chance for a passwordless NTLMSSP session setup */
 
520
        pwd = getpwuid(geteuid());
 
521
        if (pwd == NULL) {
 
522
                return NULL;
 
523
        }
 
524
 
 
525
        cli = smb_complete_connection(myname, server, port, pwd->pw_name, "",
 
526
                                      workgroup, share, 0, need_auth);
 
527
 
 
528
        if (cli) {
 
529
                fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
 
530
                return (cli);
 
531
        }
 
532
 
 
533
        /*
 
534
         * last try. Use anonymous authentication
 
535
         */
 
536
 
 
537
        cli = smb_complete_connection(myname, server, port, "", "",
 
538
                                      workgroup, share, 0, need_auth);
 
539
        /*
 
540
         * Return the new connection...
 
541
         */
 
542
 
 
543
        return (cli);
 
544
}
 
545
 
 
546
 
 
547
/*
 
548
 * 'smb_print()' - Queue a job for printing using the SMB protocol.
 
549
 */
 
550
 
 
551
static int                      /* O - 0 = success, non-0 = failure */
 
552
smb_print(struct cli_state * cli,       /* I - SMB connection */
 
553
          char *title,          /* I - Title/job name */
 
554
          FILE * fp)
 
555
{                               /* I - File to print */
 
556
        int             fnum;   /* File number */
 
557
        int             nbytes, /* Number of bytes read */
 
558
                        tbytes; /* Total bytes read */
 
559
        char            buffer[8192],   /* Buffer for copy */
 
560
                       *ptr;    /* Pointer into title */
 
561
 
 
562
 
 
563
        /*
 
564
         * Sanitize the title...
 
565
         */
 
566
 
 
567
        for (ptr = title; *ptr; ptr++) {
 
568
                if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
 
569
                        *ptr = '_';
 
570
                }
 
571
        }
 
572
 
 
573
        /*
 
574
         * Open the printer device...
 
575
         */
 
576
 
 
577
        fnum = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE);
 
578
        if (fnum == -1) {
 
579
                fprintf(stderr, "ERROR: %s opening remote spool %s\n",
 
580
                        cli_errstr(cli), title);
 
581
                return (get_exit_code(cli, cli_nt_error(cli)));
 
582
        }
 
583
 
 
584
        /*
 
585
         * Copy the file to the printer...
 
586
         */
 
587
 
 
588
        if (fp != stdin)
 
589
                rewind(fp);
 
590
 
 
591
        tbytes = 0;
 
592
 
 
593
        while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
 
594
                if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes) {
 
595
                        int status = get_exit_code(cli, cli_nt_error(cli));
 
596
 
 
597
                        fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli));
 
598
                        fprintf(stderr, "DEBUG: Returning status %d...\n", status);
 
599
                        cli_close(cli, fnum);
 
600
 
 
601
                        return (status);
 
602
                }
 
603
                tbytes += nbytes;
 
604
        }
 
605
 
 
606
        if (!cli_close(cli, fnum)) {
 
607
                fprintf(stderr, "ERROR: %s closing remote spool %s\n",
 
608
                        cli_errstr(cli), title);
 
609
                return (get_exit_code(cli, cli_nt_error(cli)));
 
610
        } else {
 
611
                return (0);
 
612
        }
 
613
}
 
614
 
 
615
static char *
 
616
uri_unescape_alloc(const char *uritok)
 
617
{
 
618
        char *ret;
 
619
 
 
620
        ret = (char *) SMB_STRDUP(uritok);
 
621
        if (!ret) {
 
622
                return NULL;
 
623
        }
 
624
 
 
625
        rfc1738_unescape(ret);
 
626
        return ret;
 
627
}