diff options
Diffstat (limited to '')
-rw-r--r-- | source3/client/smbspool.c | 888 |
1 files changed, 888 insertions, 0 deletions
diff --git a/source3/client/smbspool.c b/source3/client/smbspool.c new file mode 100644 index 0000000..16a8d44 --- /dev/null +++ b/source3/client/smbspool.c @@ -0,0 +1,888 @@ +/* + Unix SMB/CIFS implementation. + SMB backend for the Common UNIX Printing System ("CUPS") + + Copyright (C) Michael R Sweet 1999 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Rodrigo Fernandez-Vizarra 2005 + Copyright (C) James Peach 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "system/passwd.h" +#include "system/kerberos.h" +#include "libsmb/libsmb.h" +#include "lib/param/param.h" +#include "lib/krb5_wrap/krb5_samba.h" + +/* + * Starting with CUPS 1.3, Kerberos support is provided by cupsd including + * the forwarding of user credentials via the authenticated session between + * user and server and the KRB5CCNAME environment variable which will point + * to a temporary file or an in-memory representation depending on the version + * of Kerberos you use. As a result, all of the ticket code that used to + * live here has been removed, and we depend on the user session (if you + * run smbspool by hand) or cupsd to provide the necessary Kerberos info. + * + * Also, the AUTH_USERNAME and AUTH_PASSWORD environment variables provide + * for per-job authentication for non-Kerberized printing. We use those + * if there is no username and password specified in the device URI. + * + * Finally, if we have an authentication failure we return exit code 2 + * which tells CUPS to hold the job for authentication and bug the user + * to get the necessary credentials. + */ + +#define MAX_RETRY_CONNECT 3 + + +/* + * Globals... + */ + + + +/* + * Local functions... + */ + +static int get_exit_code(NTSTATUS nt_status); +static void list_devices(void); +static NTSTATUS +smb_connect(struct cli_state **output_cli, + const char *workgroup, + const char *server, + const int port, + const char *share, + const char *username, + const char *password, + const char *jobusername); +static int smb_print(struct cli_state *, const char *, FILE *); +static char *uri_unescape_alloc(const char *); +#if 0 +static bool smb_encrypt; +#endif + +static const char *auth_info_required; + +/* + * 'main()' - Main entry for SMB backend. + */ + +int /* O - Exit status */ +main(int argc, /* I - Number of command-line arguments */ + char *argv[]) +{ /* I - Command-line arguments */ + int i; /* Looping var */ + int copies; /* Number of copies */ + int port; /* Port number */ + char uri[1024], /* URI */ + *sep, /* Pointer to separator */ + *tmp, *tmp2; /* Temp pointers to do escaping */ + const char *password = NULL; /* Password */ + const char *username = NULL; /* Username */ + char *server, /* Server name */ + *printer;/* Printer name */ + const char *workgroup; /* Workgroup */ + FILE *fp; /* File to print */ + int status = 1; /* Status of LPD job */ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct cli_state *cli = NULL; /* SMB interface */ + int tries = 0; + const char *dev_uri = NULL; + const char *env = NULL; + const char *config_file = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + const char *print_user = NULL; + const char *print_title = NULL; + const char *print_file = NULL; + const char *print_copies = NULL; + int cmp; + int len; + + if (argc == 1) { + /* + * NEW! In CUPS 1.1 the backends are run with no arguments + * to list the available devices. These can be devices + * served by this backend or any other backends (i.e. you + * can have an SNMP backend that is only used to enumerate + * the available network printers... :) + */ + + list_devices(); + status = 0; + goto done; + } + + /* + * We need at least 5 options if the DEVICE_URI is passed via an env + * variable and printing data comes via stdin. + * We don't accept more than 7 options in total, including optional. + */ + if (argc < 5 || argc > 8) { + fprintf(stderr, +"Usage: %s [DEVICE_URI] job-id user title copies options [file]\n" +" The DEVICE_URI environment variable can also contain the\n" +" destination printer:\n" +"\n" +" smb://[username:password@][workgroup/]server[:port]/printer\n", + argv[0]); + goto done; + } + + /* + * Find out if we have the device_uri in the command line. + * + * If we are started as a CUPS backend argv[0] is normally the + * device_uri! + */ + if (argc == 8) { + /* + * smbspool <uri> <job> <user> <title> <copies> <options> <file> + * 0 1 2 3 4 5 6 7 + */ + + dev_uri = argv[1]; + + print_user = argv[3]; + print_title = argv[4]; + print_copies = argv[5]; + print_file = argv[7]; + } else if (argc == 7) { + int cmp1; + int cmp2; + + /* + * <uri> <job> <user> <title> <copies> <options> <file> + * smbspool <uri> <job> <user> <title> <copies> <options> + * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI + */ + cmp1 = strncmp(argv[0], "smb://", 6); + cmp2 = strncmp(argv[1], "smb://", 6); + + if (cmp1 == 0) { + /* + * <uri> <job> <user> <title> <copies> <options> <file> + * 0 1 2 3 4 5 6 + */ + dev_uri = argv[0]; + + print_user = argv[2]; + print_title = argv[3]; + print_copies = argv[4]; + print_file = argv[6]; + } else if (cmp2 == 0) { + /* + * smbspool <uri> <job> <user> <title> <copies> <options> + * 0 1 2 3 4 5 6 + */ + dev_uri = argv[1]; + + print_user = argv[3]; + print_title = argv[4]; + print_copies = argv[5]; + print_file = NULL; + } else { + /* + * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI + * 0 1 2 3 4 5 6 + */ + print_user = argv[2]; + print_title = argv[3]; + print_copies = argv[4]; + print_file = argv[6]; + } + } else if (argc == 6) { + /* + * <uri> <job> <user> <title> <copies> <options> + * smbspool <job> <user> <title> <copies> <options> | DEVICE_URI + * 0 1 2 3 4 5 + */ + cmp = strncmp(argv[0], "smb://", 6); + if (cmp == 0) { + dev_uri = argv[0]; + } + + print_user = argv[2]; + print_title = argv[3]; + print_copies = argv[4]; + } + + if (print_file != NULL) { + char *endp; + + fp = fopen(print_file, "rb"); + if (fp == NULL) { + fprintf(stderr, + "ERROR: Unable to open print file: %s", + print_file); + goto done; + } + + copies = strtol(print_copies, &endp, 10); + if (print_copies == endp) { + perror("ERROR: Unable to determine number of copies"); + goto done; + } + } else { + fp = stdin; + copies = 1; + } + + /* + * Find the URI ... + * + * The URI in argv[0] is sanitized to remove username/password, so + * use DEVICE_URI if available. Otherwise keep the URI already + * discovered in argv. + */ + env = getenv("DEVICE_URI"); + if (env != NULL && env[0] != '\0') { + dev_uri = env; + } + + if (dev_uri == NULL) { + fprintf(stderr, + "ERROR: No valid device URI has been specified\n"); + goto done; + } + + cmp = strncmp(dev_uri, "smb://", 6); + if (cmp != 0) { + fprintf(stderr, + "ERROR: No valid device URI has been specified\n"); + goto done; + } + len = snprintf(uri, sizeof(uri), "%s", dev_uri); + if (len >= sizeof(uri)) { + fprintf(stderr, + "ERROR: The URI is too long.\n"); + goto done; + } + + auth_info_required = getenv("AUTH_INFO_REQUIRED"); + if (auth_info_required == NULL) { + auth_info_required = "samba"; + } + + /* + * Extract the destination from the URI... + */ + + if ((sep = strrchr_m(uri, '@')) != NULL) { + tmp = uri + 6; + *sep++ = '\0'; + + /* username is in tmp */ + + server = sep; + + /* + * Extract password as needed... + */ + + if ((tmp2 = strchr_m(tmp, ':')) != NULL) { + *tmp2++ = '\0'; + password = uri_unescape_alloc(tmp2); + } + username = uri_unescape_alloc(tmp); + } else { + env = getenv("AUTH_USERNAME"); + if (env != NULL && strlen(env) > 0) { + username = env; + } + + env = getenv("AUTH_PASSWORD"); + if (env != NULL && strlen(env) > 0) { + password = env; + } + + server = uri + 6; + } + + if (password != NULL) { + auth_info_required = "username,password"; + } + + tmp = server; + + if ((sep = strchr_m(tmp, '/')) == NULL) { + fputs("ERROR: Bad URI - need printer name!\n", stderr); + goto done; + } + + *sep++ = '\0'; + tmp2 = sep; + + if ((sep = strchr_m(tmp2, '/')) != NULL) { + /* + * Convert to smb://[username:password@]workgroup/server/printer... + */ + + *sep++ = '\0'; + + workgroup = uri_unescape_alloc(tmp); + server = uri_unescape_alloc(tmp2); + printer = uri_unescape_alloc(sep); + } else { + workgroup = NULL; + server = uri_unescape_alloc(tmp); + printer = uri_unescape_alloc(tmp2); + } + + if ((sep = strrchr_m(server, ':')) != NULL) { + *sep++ = '\0'; + + port = atoi(sep); + } else { + port = 0; + } + + /* + * Setup the SAMBA server state... + */ + + setup_logging("smbspool", DEBUG_STDERR); + + smb_init_locale(); + + config_file = lp_default_path(); + if (!lp_load_client(config_file)) { + fprintf(stderr, + "ERROR: Can't load %s - run testparm to debug it\n", + config_file); + goto done; + } + + if (workgroup == NULL) { + workgroup = lp_workgroup(); + } + + load_interfaces(); + + do { + nt_status = smb_connect(&cli, + workgroup, + server, + port, + printer, + username, + password, + print_user); + if (!NT_STATUS_IS_OK(nt_status)) { + status = get_exit_code(nt_status); + if (status == 2) { + fprintf(stderr, + "DEBUG: Unable to connect to CIFS " + "host: %s", + nt_errstr(nt_status)); + goto done; + } else if (getenv("CLASS") == NULL) { + fprintf(stderr, + "ERROR: Unable to connect to CIFS " + "host: %s. Will retry in 60 " + "seconds...\n", + nt_errstr(nt_status)); + sleep(60); + tries++; + } else { + fprintf(stderr, + "ERROR: Unable to connect to CIFS " + "host: %s. Trying next printer...\n", + nt_errstr(nt_status)); + goto done; + } + } + } while (!NT_STATUS_IS_OK(nt_status) && (tries < MAX_RETRY_CONNECT)); + + if (cli == NULL) { + fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries); + goto done; + } + + /* + * Now that we are connected to the server, ignore SIGTERM so that we + * can finish out any page data the driver sends (e.g. to eject the + * current page... Only ignore SIGTERM if we are printing data from + * stdin (otherwise you can't cancel raw jobs...) + */ + + if (argc < 7) { + CatchSignal(SIGTERM, SIG_IGN); + } + + /* + * Queue the job... + */ + + for (i = 0; i < copies; i++) { + status = smb_print(cli, print_title, fp); + if (status != 0) { + break; + } + } + + cli_shutdown(cli); + + /* + * Return the queue status... + */ + +done: + + TALLOC_FREE(frame); + return (status); +} + + +/* + * 'get_exit_code()' - Get the backend exit code based on the current error. + */ + +static int +get_exit_code(NTSTATUS nt_status) +{ + size_t i; + + /* List of NTSTATUS errors that are considered + * authentication errors + */ + static const NTSTATUS auth_errors[] = + { + NT_STATUS_ACCESS_DENIED, + NT_STATUS_ACCESS_VIOLATION, + NT_STATUS_ACCOUNT_DISABLED, + NT_STATUS_ACCOUNT_LOCKED_OUT, + NT_STATUS_ACCOUNT_RESTRICTION, + NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND, + NT_STATUS_INVALID_ACCOUNT_NAME, + NT_STATUS_INVALID_COMPUTER_NAME, + NT_STATUS_INVALID_LOGON_HOURS, + NT_STATUS_INVALID_WORKSTATION, + NT_STATUS_LOGON_FAILURE, + NT_STATUS_NO_SUCH_USER, + NT_STATUS_NO_SUCH_DOMAIN, + NT_STATUS_NO_LOGON_SERVERS, + NT_STATUS_PASSWORD_EXPIRED, + NT_STATUS_PRIVILEGE_NOT_HELD, + NT_STATUS_SHARING_VIOLATION, + NT_STATUS_WRONG_PASSWORD, + }; + + + fprintf(stderr, + "DEBUG: get_exit_code(nt_status=%s [%x])\n", + nt_errstr(nt_status), NT_STATUS_V(nt_status)); + + for (i = 0; i < ARRAY_SIZE(auth_errors); i++) { + if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) { + continue; + } + + fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required); + + /* + * 2 = authentication required... + */ + + return (2); + + } + + /* + * 1 = fail + */ + + return (1); +} + + +/* + * 'list_devices()' - List the available printers seen on the network... + */ + +static void +list_devices(void) +{ + /* + * Eventually, search the local workgroup for available hosts and printers. + */ + + puts("network smb \"Unknown\" \"Windows Printer via SAMBA\""); +} + + +static NTSTATUS +smb_complete_connection(struct cli_state **output_cli, + const char *myname, + const char *server, + int port, + const char *username, + const char *password, + const char *workgroup, + const char *share, + bool use_kerberos, + bool fallback_after_kerberos) +{ + struct cli_state *cli; /* New connection */ + NTSTATUS nt_status; + struct cli_credentials *creds = NULL; + + /* Start the SMB connection */ + nt_status = cli_start_connection(&cli, myname, server, NULL, port, + SMB_SIGNING_DEFAULT, 0); + if (!NT_STATUS_IS_OK(nt_status)) { + fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status)); + return nt_status; + } + + creds = cli_session_creds_init(cli, + username, + workgroup, + NULL, /* realm */ + password, + use_kerberos, + fallback_after_kerberos, + false, /* use_ccache */ + false); /* password_is_nt_hash */ + if (creds == NULL) { + fprintf(stderr, "ERROR: cli_session_creds_init failed\n"); + cli_shutdown(cli); + return NT_STATUS_NO_MEMORY; + } + + nt_status = cli_session_setup_creds(cli, creds); + if (!NT_STATUS_IS_OK(nt_status)) { + fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status)); + + cli_shutdown(cli); + + return nt_status; + } + + nt_status = cli_tree_connect_creds(cli, share, "?????", creds); + if (!NT_STATUS_IS_OK(nt_status)) { + fprintf(stderr, "ERROR: Tree connect failed (%s)\n", + nt_errstr(nt_status)); + + cli_shutdown(cli); + + return nt_status; + } + + *output_cli = cli; + return NT_STATUS_OK; +} + +static bool kerberos_ccache_is_valid(void) { + krb5_context ctx; + const char *ccache_name = NULL; + krb5_ccache ccache = NULL; + krb5_error_code code; + + code = smb_krb5_init_context_common(&ctx); + if (code != 0) { + DBG_ERR("kerberos init context failed (%s)\n", + error_message(code)); + return false; + } + + ccache_name = krb5_cc_default_name(ctx); + if (ccache_name == NULL) { + DBG_ERR("Failed to get default ccache name\n"); + krb5_free_context(ctx); + return false; + } + + code = krb5_cc_resolve(ctx, ccache_name, &ccache); + if (code != 0) { + DBG_ERR("Failed to resolve ccache name: %s\n", + ccache_name); + krb5_free_context(ctx); + return false; + } else { + krb5_principal default_princ = NULL; + char *princ_name = NULL; + + code = krb5_cc_get_principal(ctx, + ccache, + &default_princ); + if (code != 0) { + DBG_ERR("Failed to get default principal from " + "ccache: %s\n", + ccache_name); + krb5_cc_close(ctx, ccache); + krb5_free_context(ctx); + return false; + } + + code = krb5_unparse_name(ctx, + default_princ, + &princ_name); + if (code == 0) { + fprintf(stderr, + "DEBUG: Try to authenticate as %s\n", + princ_name); + krb5_free_unparsed_name(ctx, princ_name); + } + krb5_free_principal(ctx, default_princ); + } + krb5_cc_close(ctx, ccache); + krb5_free_context(ctx); + + return true; +} + +/* + * 'smb_connect()' - Return a connection to a server. + */ + +static NTSTATUS +smb_connect(struct cli_state **output_cli, + const char *workgroup, /* I - Workgroup */ + const char *server, /* I - Server */ + const int port, /* I - Port */ + const char *share, /* I - Printer */ + const char *username, /* I - Username */ + const char *password, /* I - Password */ + const char *jobusername) /* I - User who issued the print job */ +{ + struct cli_state *cli = NULL; /* New connection */ + char *myname = NULL; /* Client name */ + struct passwd *pwd; + bool use_kerberos = false; + bool fallback_after_kerberos = false; + const char *user = username; + NTSTATUS nt_status; + + /* + * Get the names and addresses of the client and server... + */ + myname = get_myname(talloc_tos()); + if (!myname) { + return NT_STATUS_NO_MEMORY; + } + + + if (strcmp(auth_info_required, "negotiate") == 0) { + if (!kerberos_ccache_is_valid()) { + fprintf(stderr, + "ERROR: No valid Kerberos credential cache found! " + "Using smbspool_krb5_wrapper may help.\n"); + return NT_STATUS_LOGON_FAILURE; + } + user = jobusername; + + use_kerberos = true; + fprintf(stderr, + "DEBUG: Try to connect using Kerberos ...\n"); + } else if (strcmp(auth_info_required, "username,password") == 0) { + if (username == NULL) { + return NT_STATUS_INVALID_ACCOUNT_NAME; + } + + /* Fallback to NTLM */ + fallback_after_kerberos = true; + + fprintf(stderr, + "DEBUG: Try to connect using username/password ...\n"); + } else if (strcmp(auth_info_required, "none") == 0) { + goto anonymous; + } else if (strcmp(auth_info_required, "samba") == 0) { + if (username != NULL) { + fallback_after_kerberos = true; + } else if (kerberos_ccache_is_valid()) { + auth_info_required = "negotiate"; + + user = jobusername; + use_kerberos = true; + } else { + fprintf(stderr, + "DEBUG: This backend requires credentials!\n"); + return NT_STATUS_ACCESS_DENIED; + } + } else { + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = smb_complete_connection(&cli, + myname, + server, + port, + user, + password, + workgroup, + share, + true, /* try kerberos */ + fallback_after_kerberos); + if (NT_STATUS_IS_OK(nt_status)) { + fprintf(stderr, "DEBUG: SMB connection established.\n"); + + *output_cli = cli; + return NT_STATUS_OK; + } + + if (!use_kerberos) { + fprintf(stderr, "ERROR: SMB connection failed!\n"); + return nt_status; + } + + /* give a chance for a passwordless NTLMSSP session setup */ + pwd = getpwuid(geteuid()); + if (pwd == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = smb_complete_connection(&cli, + myname, + server, + port, + pwd->pw_name, + "", + workgroup, + share, + false, false); + if (NT_STATUS_IS_OK(nt_status)) { + fputs("DEBUG: Connected with NTLMSSP...\n", stderr); + + *output_cli = cli; + return NT_STATUS_OK; + } + + /* + * last try. Use anonymous authentication + */ + +anonymous: + nt_status = smb_complete_connection(&cli, + myname, + server, + port, + "", + "", + workgroup, + share, + false, false); + if (NT_STATUS_IS_OK(nt_status)) { + *output_cli = cli; + return NT_STATUS_OK; + } + + return nt_status; +} + + +/* + * 'smb_print()' - Queue a job for printing using the SMB protocol. + */ + +static int /* O - 0 = success, non-0 = failure */ +smb_print(struct cli_state * cli, /* I - SMB connection */ + const char *print_title, /* I - Title/job name */ + FILE * fp) +{ /* I - File to print */ + uint16_t fnum; /* File number */ + int nbytes, /* Number of bytes read */ + tbytes; /* Total bytes read */ + char buffer[8192], /* Buffer for copy */ + *ptr; /* Pointer into title */ + char title[1024] = {0}; + int len; + NTSTATUS nt_status; + + + /* + * Sanitize the title... + */ + len = snprintf(title, sizeof(title), "%s", print_title); + if (len != strlen(print_title)) { + return 2; + } + + for (ptr = title; *ptr; ptr++) { + if (!isalnum((int) *ptr) && !isspace((int) *ptr)) { + *ptr = '_'; + } + } + + /* + * Open the printer device... + */ + + nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, + &fnum); + if (!NT_STATUS_IS_OK(nt_status)) { + fprintf(stderr, "ERROR: %s opening remote spool %s\n", + nt_errstr(nt_status), title); + return get_exit_code(nt_status); + } + + /* + * Copy the file to the printer... + */ + + if (fp != stdin) + rewind(fp); + + tbytes = 0; + + while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) { + NTSTATUS status; + + status = cli_writeall(cli, fnum, 0, (uint8_t *)buffer, + tbytes, nbytes, NULL); + if (!NT_STATUS_IS_OK(status)) { + int ret = get_exit_code(status); + fprintf(stderr, "ERROR: Error writing spool: %s\n", + nt_errstr(status)); + fprintf(stderr, "DEBUG: Returning status %d...\n", + ret); + cli_close(cli, fnum); + + return (ret); + } + tbytes += nbytes; + } + + nt_status = cli_close(cli, fnum); + if (!NT_STATUS_IS_OK(nt_status)) { + fprintf(stderr, "ERROR: %s closing remote spool %s\n", + nt_errstr(nt_status), title); + return get_exit_code(nt_status); + } else { + return (0); + } +} + +static char * +uri_unescape_alloc(const char *uritok) +{ + char *ret; + char *end; + ret = (char *) SMB_STRDUP(uritok); + if (!ret) { + return NULL; + } + + end = rfc1738_unescape(ret); + if (end == NULL) { + free(ret); + return NULL; + } + return ret; +} |