root/source3/client/smbspool.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. main
  2. get_exit_code
  3. list_devices
  4. smb_complete_connection
  5. smb_connect
  6. smb_print
  7. uri_unescape_alloc

   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 */
     /* [<][>][^][v][top][bottom][index][help] */
  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,
     /* [<][>][^][v][top][bottom][index][help] */
 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)
     /* [<][>][^][v][top][bottom][index][help] */
 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,
     /* [<][>][^][v][top][bottom][index][help] */
 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 */
     /* [<][>][^][v][top][bottom][index][help] */
 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 */
     /* [<][>][^][v][top][bottom][index][help] */
 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)
     /* [<][>][^][v][top][bottom][index][help] */
 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 }

/* [<][>][^][v][top][bottom][index][help] */