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 58static int get_exit_code(struct cli_state * cli, NTSTATUS nt_status); 59static void list_devices(void); 60static struct cli_state *smb_complete_connection(const char *, const char *, 61 int, const char *, const char *, const char *, const char *, int, bool *need_auth); 62static struct cli_state *smb_connect(const char *, const char *, int, const 63 char *, const char *, const char *, const char *, bool *need_auth); 64static int smb_print(struct cli_state *, char *, FILE *); 65static char *uri_unescape_alloc(const char *); 66#if 0 67static bool smb_encrypt; 68#endif 69 70/* 71 * 'main()' - Main entry for SMB backend. 72 */ 73 74int /* O - Exit status */ 75main(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 310done: 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 321static int 322get_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 376static void 377list_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 387static struct cli_state * 388smb_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 471static struct cli_state * /* O - SMB connection */ 472smb_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 551static int /* O - 0 = success, non-0 = failure */ 552smb_print(struct cli_state * cli, /* I - SMB connection */ 553 char *title, /* I - Title/job name */ 554 FILE * fp) 555{ /* I - File to print */ 556 uint16_t 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 if (!NT_STATUS_IS_OK(cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum))) { 578 fprintf(stderr, "ERROR: %s opening remote spool %s\n", 579 cli_errstr(cli), title); 580 return (get_exit_code(cli, cli_nt_error(cli))); 581 } 582 583 /* 584 * Copy the file to the printer... 585 */ 586 587 if (fp != stdin) 588 rewind(fp); 589 590 tbytes = 0; 591 592 while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) { 593 if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes) { 594 int status = get_exit_code(cli, cli_nt_error(cli)); 595 596 fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli)); 597 fprintf(stderr, "DEBUG: Returning status %d...\n", status); 598 cli_close(cli, fnum); 599 600 return (status); 601 } 602 tbytes += nbytes; 603 } 604 605 if (!NT_STATUS_IS_OK(cli_close(cli, fnum))) { 606 fprintf(stderr, "ERROR: %s closing remote spool %s\n", 607 cli_errstr(cli), title); 608 return (get_exit_code(cli, cli_nt_error(cli))); 609 } else { 610 return (0); 611 } 612} 613 614static char * 615uri_unescape_alloc(const char *uritok) 616{ 617 char *ret; 618 619 ret = (char *) SMB_STRDUP(uritok); 620 if (!ret) { 621 return NULL; 622 } 623 624 rfc1738_unescape(ret); 625 return ret; 626} 627