1/* 2 Unix SMB/CIFS implementation. 3 SMB backend for the Common UNIX Printing System ("CUPS") 4 Copyright 1999 by Easy Software Products 5 Copyright Andrew Tridgell 1994-1998 6 Copyright Andrew Bartlett 2002 7 Copyright Rodrigo Fernandez-Vizarra 2005 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 2 of the License, or 12 (at your option) any later version. 13 14 This program is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with this program; if not, write to the Free Software 21 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 22*/ 23 24#include "includes.h" 25 26#define TICKET_CC_DIR "/tmp" 27#define CC_PREFIX "krb5cc_" /* prefix of the ticket cache */ 28#define CC_MAX_FILE_LEN 24 29#define CC_MAX_FILE_PATH_LEN (sizeof(TICKET_CC_DIR)-1)+ CC_MAX_FILE_LEN+2 30#define OVERWRITE 1 31#define KRB5CCNAME "KRB5CCNAME" 32#define MAX_RETRY_CONNECT 3 33 34 35/* 36 * Globals... 37 */ 38 39extern BOOL in_client; /* Boolean for client library */ 40 41 42/* 43 * Local functions... 44 */ 45 46static void list_devices(void); 47static struct cli_state *smb_complete_connection(const char *, const char *,int , const char *, const char *, const char *, const char *, int); 48static struct cli_state *smb_connect(const char *, const char *, int, const char *, const char *, const char *, const char *); 49static int smb_print(struct cli_state *, char *, FILE *); 50static char * uri_unescape_alloc(const char *); 51 52 53/* 54 * 'main()' - Main entry for SMB backend. 55 */ 56 57 int /* O - Exit status */ 58 main(int argc, /* I - Number of command-line arguments */ 59 char *argv[]) /* I - Command-line arguments */ 60{ 61 int i; /* Looping var */ 62 int copies; /* Number of copies */ 63 int port; /* Port number */ 64 char uri[1024], /* URI */ 65 *sep, /* Pointer to separator */ 66 *tmp, *tmp2, /* Temp pointers to do escaping */ 67 *password; /* Password */ 68 char *username, /* Username */ 69 *server, /* Server name */ 70 *printer; /* Printer name */ 71 const char *workgroup; /* Workgroup */ 72 FILE *fp; /* File to print */ 73 int status=0; /* Status of LPD job */ 74 struct cli_state *cli; /* SMB interface */ 75 char null_str[1]; 76 int tries = 0; 77 const char *dev_uri; 78 79 null_str[0] = '\0'; 80 81 /* we expect the URI in argv[0]. Detect the case where it is in argv[1] and cope */ 82 if (argc > 2 && strncmp(argv[0],"smb://", 6) && !strncmp(argv[1],"smb://", 6)) { 83 argv++; 84 argc--; 85 } 86 87 if (argc == 1) 88 { 89 /* 90 * NEW! In CUPS 1.1 the backends are run with no arguments to list the 91 * available devices. These can be devices served by this backend 92 * or any other backends (i.e. you can have an SNMP backend that 93 * is only used to enumerate the available network printers... :) 94 */ 95 96 list_devices(); 97 return (0); 98 } 99 100 if (argc < 6 || argc > 7) 101 { 102 fprintf(stderr, "Usage: %s [DEVICE_URI] job-id user title copies options [file]\n", 103 argv[0]); 104 fputs(" The DEVICE_URI environment variable can also contain the\n", stderr); 105 fputs(" destination printer:\n", stderr); 106 fputs("\n", stderr); 107 fputs(" smb://[username:password@][workgroup/]server[:port]/printer\n", stderr); 108 return (1); 109 } 110 111 /* 112 * If we have 7 arguments, print the file named on the command-line. 113 * Otherwise, print data from stdin... 114 */ 115 116 117 if (argc == 6) 118 { 119 /* 120 * Print from Copy stdin to a temporary file... 121 */ 122 123 fp = stdin; 124 copies = 1; 125 } 126 else if ((fp = fopen(argv[6], "rb")) == NULL) 127 { 128 perror("ERROR: Unable to open print file"); 129 return (1); 130 } 131 else 132 copies = atoi(argv[4]); 133 134 /* 135 * Find the URI... 136 */ 137 138 dev_uri = getenv("DEVICE_URI"); 139 if (dev_uri) 140 strncpy(uri, dev_uri, sizeof(uri) - 1); 141 else if (strncmp(argv[0], "smb://", 6) == 0) 142 strncpy(uri, argv[0], sizeof(uri) - 1); 143 else 144 { 145 fputs("ERROR: No device URI found in DEVICE_URI environment variable or argv[0] !\n", stderr); 146 return (1); 147 } 148 149 uri[sizeof(uri) - 1] = '\0'; 150 151 /* 152 * Extract the destination from the URI... 153 */ 154 155 if ((sep = strrchr_m(uri, '@')) != NULL) 156 { 157 tmp = uri + 6; 158 *sep++ = '\0'; 159 160 /* username is in tmp */ 161 162 server = sep; 163 164 /* 165 * Extract password as needed... 166 */ 167 168 if ((tmp2 = strchr_m(tmp, ':')) != NULL) { 169 *tmp2++ = '\0'; 170 password = uri_unescape_alloc(tmp2); 171 } else { 172 password = null_str; 173 } 174 username = uri_unescape_alloc(tmp); 175 } 176 else 177 { 178 username = null_str; 179 password = null_str; 180 server = uri + 6; 181 } 182 183 tmp = server; 184 185 if ((sep = strchr_m(tmp, '/')) == NULL) 186 { 187 fputs("ERROR: Bad URI - need printer name!\n", stderr); 188 return (1); 189 } 190 191 *sep++ = '\0'; 192 tmp2 = sep; 193 194 if ((sep = strchr_m(tmp2, '/')) != NULL) 195 { 196 /* 197 * Convert to smb://[username:password@]workgroup/server/printer... 198 */ 199 200 *sep++ = '\0'; 201 202 workgroup = uri_unescape_alloc(tmp); 203 server = uri_unescape_alloc(tmp2); 204 printer = uri_unescape_alloc(sep); 205 } 206 else { 207 workgroup = NULL; 208 server = uri_unescape_alloc(tmp); 209 printer = uri_unescape_alloc(tmp2); 210 } 211 212 if ((sep = strrchr_m(server, ':')) != NULL) 213 { 214 *sep++ = '\0'; 215 216 port=atoi(sep); 217 } 218 else 219 port=0; 220 221 222 /* 223 * Setup the SAMBA server state... 224 */ 225 226 setup_logging("smbspool", True); 227 228 in_client = True; /* Make sure that we tell lp_load we are */ 229 230 load_case_tables(); 231 232 if (!lp_load(dyn_CONFIGFILE, True, False, False, True)) 233 { 234 fprintf(stderr, "ERROR: Can't load %s - run testparm to debug it\n", dyn_CONFIGFILE); 235 return (1); 236 } 237 238 if (workgroup == NULL) 239 workgroup = lp_workgroup(); 240 241 load_interfaces(); 242 243 do 244 { 245 if ((cli = smb_connect(workgroup, server, port, printer, username, password, argv[2])) == NULL) 246 { 247 if (getenv("CLASS") == NULL) 248 { 249 fprintf(stderr, "ERROR: Unable to connect to CIFS host, will retry in 60 seconds...\n"); 250 sleep (60); /* should just waiting and retrying fix authentication ??? */ 251 tries++; 252 } 253 else 254 { 255 fprintf(stderr, "ERROR: Unable to connect to CIFS host, trying next printer...\n"); 256 return (1); 257 } 258 } 259 } 260 while ((cli == NULL) && (tries < MAX_RETRY_CONNECT)); 261 262 if (cli == NULL) { 263 fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries); 264 return (1); 265 } 266 267 /* 268 * Now that we are connected to the server, ignore SIGTERM so that we 269 * can finish out any page data the driver sends (e.g. to eject the 270 * current page... Only ignore SIGTERM if we are printing data from 271 * stdin (otherwise you can't cancel raw jobs...) 272 */ 273 274 if (argc < 7) 275 CatchSignal(SIGTERM, SIG_IGN); 276 277 /* 278 * Queue the job... 279 */ 280 281 for (i = 0; i < copies; i ++) 282 if ((status = smb_print(cli, argv[3] /* title */, fp)) != 0) 283 break; 284 285 cli_shutdown(cli); 286 287 /* 288 * Return the queue status... 289 */ 290 291 return (status); 292} 293 294 295/* 296 * 'list_devices()' - List the available printers seen on the network... 297 */ 298 299static void 300list_devices(void) 301{ 302 /* 303 * Eventually, search the local workgroup for available hosts and printers. 304 */ 305 306 puts("network smb \"Unknown\" \"Windows Printer via SAMBA\""); 307} 308 309 310/* 311 * get the name of the newest ticket cache for the uid user. 312 * pam_krb5 defines a non default ticket cache for each user 313 */ 314static 315char * get_ticket_cache( uid_t uid ) 316{ 317 char *ticket_file = NULL; 318 SMB_STRUCT_DIR *tcdir; /* directory where ticket caches are stored */ 319 SMB_STRUCT_DIRENT *dirent; /* directory entry */ 320 char *filename = NULL; /* holds file names on the tmp directory */ 321 SMB_STRUCT_STAT buf; 322 char user_cache_prefix[CC_MAX_FILE_LEN]; 323 char file_path[CC_MAX_FILE_PATH_LEN]; 324 time_t t = 0; 325 326 snprintf(user_cache_prefix, CC_MAX_FILE_LEN, "%s%d", CC_PREFIX, uid ); 327 tcdir = sys_opendir( TICKET_CC_DIR ); 328 if ( tcdir == NULL ) 329 return NULL; 330 331 while ( (dirent = sys_readdir( tcdir ) ) ) 332 { 333 filename = dirent->d_name; 334 snprintf(file_path, CC_MAX_FILE_PATH_LEN,"%s/%s", TICKET_CC_DIR, filename); 335 if (sys_stat(file_path, &buf) == 0 ) 336 { 337 if ( ( buf.st_uid == uid ) && ( S_ISREG(buf.st_mode) ) ) 338 { 339 /* 340 * check the user id of the file to prevent denial of 341 * service attacks by creating fake ticket caches for the 342 * user 343 */ 344 if ( strstr( filename, user_cache_prefix ) ) 345 { 346 if ( buf.st_mtime > t ) 347 { 348 /* 349 * a newer ticket cache found 350 */ 351 free(ticket_file); 352 ticket_file=SMB_STRDUP(file_path); 353 t = buf.st_mtime; 354 } 355 } 356 } 357 } 358 } 359 360 sys_closedir(tcdir); 361 362 if ( ticket_file == NULL ) 363 { 364 /* no ticket cache found */ 365 fprintf(stderr, "ERROR: No ticket cache found for userid=%d\n", uid); 366 return NULL; 367 } 368 369 return ticket_file; 370} 371 372static struct cli_state 373*smb_complete_connection(const char *myname, 374 const char *server, 375 int port, 376 const char *username, 377 const char *password, 378 const char *workgroup, 379 const char *share, 380 int flags) 381{ 382 struct cli_state *cli; /* New connection */ 383 NTSTATUS nt_status; 384 385 /* Start the SMB connection */ 386 nt_status = cli_start_connection( &cli, myname, server, NULL, port, 387 Undefined, flags, NULL); 388 if (!NT_STATUS_IS_OK(nt_status)) 389 { 390 return NULL; 391 } 392 393 /* We pretty much guarentee password must be valid or a pointer 394 to a 0 char. */ 395 if (!password) { 396 return NULL; 397 } 398 399 if ( (username) && (*username) && 400 (strlen(password) == 0 ) && 401 (cli->use_kerberos) ) 402 { 403 /* Use kerberos authentication */ 404 struct passwd *pw; 405 char *cache_file; 406 407 408 if ( !(pw = sys_getpwnam(username)) ) { 409 fprintf(stderr,"ERROR Can not get %s uid\n", username); 410 cli_shutdown(cli); 411 return NULL; 412 } 413 414 /* 415 * Get the ticket cache of the user to set KRB5CCNAME env 416 * variable 417 */ 418 cache_file = get_ticket_cache( pw->pw_uid ); 419 if ( cache_file == NULL ) 420 { 421 fprintf(stderr, "ERROR: Can not get the ticket cache for %s\n", username); 422 cli_shutdown(cli); 423 return NULL; 424 } 425 426 if ( setenv(KRB5CCNAME, cache_file, OVERWRITE) < 0 ) 427 { 428 fprintf(stderr, "ERROR: Can not add KRB5CCNAME to the environment"); 429 cli_shutdown(cli); 430 free(cache_file); 431 return NULL; 432 } 433 free(cache_file); 434 435 /* 436 * Change the UID of the process to be able to read the kerberos 437 * ticket cache 438 */ 439 setuid(pw->pw_uid); 440 441 } 442 443 444 if (!NT_STATUS_IS_OK(cli_session_setup(cli, username, 445 password, strlen(password)+1, 446 password, strlen(password)+1, 447 workgroup))) 448 { 449 fprintf(stderr,"ERROR: Session setup failed: %s\n", cli_errstr(cli)); 450 if (NT_STATUS_V(cli_nt_error(cli)) == 451 NT_STATUS_V(NT_STATUS_MORE_PROCESSING_REQUIRED)) 452 { 453 fprintf(stderr, "did you forget to run kinit?\n"); 454 } 455 cli_shutdown(cli); 456 457 return NULL; 458 } 459 460 if (!cli_send_tconX(cli, share, "?????", password, strlen(password)+1)) 461 { 462 fprintf(stderr, "ERROR: Tree connect failed (%s)\n", cli_errstr(cli)); 463 cli_shutdown(cli); 464 return NULL; 465 } 466 467 return cli; 468} 469 470/* 471 * 'smb_connect()' - Return a connection to a server. 472 */ 473 474static struct cli_state * /* O - SMB connection */ 475smb_connect(const char *workgroup, /* I - Workgroup */ 476 const char *server, /* I - Server */ 477 const int port, /* I - Port */ 478 const char *share, /* I - Printer */ 479 const char *username, /* I - Username */ 480 const char *password, /* I - Password */ 481 const char *jobusername) /* I - User who issued the print job */ 482{ 483 struct cli_state *cli; /* New connection */ 484 pstring myname; /* Client name */ 485 struct passwd *pwd; 486 487 /* 488 * Get the names and addresses of the client and server... 489 */ 490 491 get_myname(myname); 492 493 /* See if we have a username first. This is for backwards compatible 494 behavior with 3.0.14a */ 495 496 if ( username && *username ) 497 { 498 cli = smb_complete_connection(myname, server, port, username, 499 password, workgroup, share, 0 ); 500 if (cli) 501 return cli; 502 } 503 504 /* 505 * Try to use the user kerberos credentials (if any) to authenticate 506 */ 507 cli = smb_complete_connection(myname, server, port, jobusername, "", 508 workgroup, share, 509 CLI_FULL_CONNECTION_USE_KERBEROS ); 510 511 if (cli ) { return cli; } 512 513 /* give a chance for a passwordless NTLMSSP session setup */ 514 515 pwd = getpwuid(geteuid()); 516 if (pwd == NULL) { 517 return NULL; 518 } 519 520 cli = smb_complete_connection(myname, server, port, pwd->pw_name, "", 521 workgroup, share, 0); 522 523 if (cli) { return cli; } 524 525 /* 526 * last try. Use anonymous authentication 527 */ 528 529 cli = smb_complete_connection(myname, server, port, "", "", 530 workgroup, share, 0); 531 /* 532 * Return the new connection... 533 */ 534 535 return (cli); 536} 537 538 539/* 540 * 'smb_print()' - Queue a job for printing using the SMB protocol. 541 */ 542 543static int /* O - 0 = success, non-0 = failure */ 544smb_print(struct cli_state *cli, /* I - SMB connection */ 545 char *title, /* I - Title/job name */ 546 FILE *fp) /* I - File to print */ 547{ 548 int fnum; /* File number */ 549 int nbytes, /* Number of bytes read */ 550 tbytes; /* Total bytes read */ 551 char buffer[8192], /* Buffer for copy */ 552 *ptr; /* Pointer into tile */ 553 554 555 /* 556 * Sanitize the title... 557 */ 558 559 for (ptr = title; *ptr; ptr ++) 560 if (!isalnum((int)*ptr) && !isspace((int)*ptr)) 561 *ptr = '_'; 562 563 /* 564 * Open the printer device... 565 */ 566 567 if ((fnum = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE)) == -1) 568 { 569 fprintf(stderr, "ERROR: %s opening remote spool %s\n", 570 cli_errstr(cli), title); 571 return (1); 572 } 573 574 /* 575 * Copy the file to the printer... 576 */ 577 578 if (fp != stdin) 579 rewind(fp); 580 581 tbytes = 0; 582 583 while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) 584 { 585 if (cli_write(cli, fnum, 0, buffer, tbytes, nbytes) != nbytes) 586 { 587 fprintf(stderr, "ERROR: Error writing spool: %s\n", cli_errstr(cli)); 588 break; 589 } 590 591 tbytes += nbytes; 592 } 593 594 if (!cli_close(cli, fnum)) 595 { 596 fprintf(stderr, "ERROR: %s closing remote spool %s\n", 597 cli_errstr(cli), title); 598 return (1); 599 } 600 else 601 return (0); 602} 603 604static char *uri_unescape_alloc(const char *uritok) 605{ 606 char *ret; 607 608 ret = (char *)SMB_STRDUP(uritok); 609 if (!ret) return NULL; 610 611 rfc1738_unescape(ret); 612 return ret; 613} 614