/* * "$Id: cupsaddsmb.c,v 1.1.1.17 2005/01/04 19:16:39 jlovell Exp $" * * "cupsaddsmb" command for the Common UNIX Printing System (CUPS). * * Copyright 2001-2005 by Easy Software Products. * * These coded instructions, statements, and computer programs are the * property of Easy Software Products and are protected by Federal * copyright law. Distribution and use rights are outlined in the file * "LICENSE.txt" which should have been included with this file. If this * file is missing or damaged please contact Easy Software Products * at: * * Attn: CUPS Licensing Information * Easy Software Products * 44141 Airport View Drive, Suite 204 * Hollywood, Maryland 20636 USA * * Voice: (301) 373-9600 * EMail: cups-info@cups.org * WWW: http://www.cups.org * * Contents: * * main() - Export printers on the command-line. * convert_ppd() - Convert a PPD file to a form usable by any of the * Windows PostScript printer drivers. * do_samba_command() - Do a SAMBA command, asking for a password as needed. * export_dest() - Export a destination to SAMBA. * ppd_gets() - Get a CR and/or LF-terminated line. * usage() - Show program usage and exit... * write_option() - Write a CUPS option to a PPD file. */ /* * Include necessary headers... */ #include #include #include #include #include #include /* * Local globals... */ int Verbosity = 0; const char *SAMBAUser, *SAMBAServer; /* * Local functions... */ int convert_ppd(const char *src, char *dst, int dstsize, ipp_t *info); int do_samba_command(const char *command, const char *subcommand); int export_dest(const char *dest); char *ppd_gets(FILE *fp, char *buf, int buflen); void usage(void); int write_option(FILE *dstfp, int order, const char *name, const char *text, const char *attrname, ipp_attribute_t *suppattr, ipp_attribute_t *defattr, int defval, int valcount); /* * 'main()' - Export printers on the command-line. */ int /* O - Exit status */ main(int argc, /* I - Number of command-line arguments */ char *argv[]) /* I - Command-line arguments */ { int i, j; /* Looping vars */ int status; /* Status from export_dest() */ int export_all; /* Export all printers? */ int num_printers; /* Number of printers */ char **printers; /* Printers */ /* * Parse command-line arguments... */ export_all = 0; SAMBAUser = cupsUser(); SAMBAServer = NULL; for (i = 1; i < argc; i ++) if (strcmp(argv[i], "-a") == 0) export_all = 1; else if (strcmp(argv[i], "-U") == 0) { i ++; if (i >= argc) usage(); SAMBAUser = argv[i]; } else if (strcmp(argv[i], "-H") == 0) { i ++; if (i >= argc) usage(); SAMBAServer = argv[i]; } else if (strcmp(argv[i], "-h") == 0) { i ++; if (i >= argc) usage(); cupsSetServer(argv[i]); } else if (strcmp(argv[i], "-v") == 0) Verbosity = 1; else if (argv[i][0] != '-') { if (SAMBAServer == NULL) SAMBAServer = cupsServer(); if ((status = export_dest(argv[i])) != 0) return (status); } else usage(); /* * See if the user specified "-a"... */ if (export_all) { /* * Export all printers... */ if (SAMBAServer == NULL) SAMBAServer = cupsServer(); num_printers = cupsGetPrinters(&printers); for (j = 0, status = 0; j < num_printers; j ++) if ((status = export_dest(printers[j])) != 0) break; for (j = 0; j < num_printers; j ++) free(printers[j]); if (num_printers) free(printers); if (status) return (status); } return (0); } /* * 'convert_ppd()' - Convert a PPD file to a form usable by any of the * Windows PostScript printer drivers. */ int /* O - 0 on success, 1 on failure */ convert_ppd(const char *src, /* I - Source (original) PPD */ char *dst, /* O - Destination PPD */ int dstsize, /* I - Size of destination buffer */ ipp_t *info) /* I - Printer attributes */ { FILE *srcfp, /* Source file */ *dstfp; /* Destination file */ int dstfd; /* Destination file descriptor */ ipp_attribute_t *suppattr, /* IPP -supported attribute */ *defattr; /* IPP -default attribute */ char line[256], /* Line from PPD file */ junk[256], /* Extra junk to throw away */ *ptr, /* Pointer into line */ option[41], /* Option */ choice[41]; /* Choice */ int jcloption, /* In a JCL option? */ linenum; /* Current line number */ time_t curtime; /* Current time */ struct tm *curdate; /* Current date */ /* * Open the original PPD file... */ if ((srcfp = fopen(src, "rb")) == NULL) return (1); /* * Create a temporary output file using the destination buffer... */ if ((dstfd = cupsTempFd(dst, dstsize)) < 0) { fclose(srcfp); return (1); } if ((dstfp = fdopen(dstfd, "w")) == NULL) { /* * Unable to convert to FILE *... */ close(dstfd); fclose(srcfp); return (1); } /* * Write a new header explaining that this isn't the original PPD... */ fputs("*PPD-Adobe: \"4.3\"\n", dstfp); curtime = time(NULL); curdate = gmtime(&curtime); fprintf(dstfp, "*%% Modified on %04d%02d%02d%02d%02d%02d+0000 by cupsaddsmb\n", curdate->tm_year + 1900, curdate->tm_mon + 1, curdate->tm_mday, curdate->tm_hour, curdate->tm_min, curdate->tm_sec); /* * Read the existing PPD file, converting all PJL commands to CUPS * job ticket comments... */ jcloption = 0; linenum = 0; while (ppd_gets(srcfp, line, sizeof(line)) != NULL) { linenum ++; if (!strncmp(line, "*PPD-Adobe:", 11)) { /* * Already wrote the PPD header... */ continue; } else if (!strncmp(line, "*JCLBegin:", 10) || !strncmp(line, "*JCLToPSInterpreter:", 20) || !strncmp(line, "*JCLEnd:", 8) || !strncmp(line, "*Protocols:", 11)) { /* * Don't use existing JCL keywords; we'll create our own, below... */ fprintf(dstfp, "*%% Commented out by cupsaddsmb...\n*%%%s", line + 1); continue; } else if (!strncmp(line, "*JCLOpenUI", 10)) { jcloption = 1; fputs(line, dstfp); } else if (!strncmp(line, "*JCLCloseUI", 11)) { jcloption = 0; fputs(line, dstfp); } else if (jcloption && strncmp(line, "*End", 4) && strncmp(line, "*Default", 8) && strncmp(line, "*OrderDependency", 16)) { if ((ptr = strchr(line, ':')) == NULL) { fprintf(stderr, "cupsaddsmb: Missing value on line %d!\n", linenum); fclose(srcfp); fclose(dstfp); close(dstfd); unlink(dst); return (1); } if ((ptr = strchr(ptr, '\"')) == NULL) { fprintf(stderr, "cupsaddsmb: Missing double quote on line %d!\n", linenum); fclose(srcfp); fclose(dstfp); close(dstfd); unlink(dst); return (1); } if (sscanf(line, "*%40s%*[ \t]%40[^/]", option, choice) != 2) { fprintf(stderr, "cupsaddsmb: Bad option + choice on line %d!\n", linenum); fclose(srcfp); fclose(dstfp); close(dstfd); unlink(dst); return (1); } if (strchr(ptr + 1, '\"') == NULL) { /* * Skip remaining... */ while (ppd_gets(srcfp, junk, sizeof(junk)) != NULL) { linenum ++; if (!strncmp(junk, "*End", 4)) break; } } snprintf(ptr + 1, sizeof(line) - (ptr - line + 1), "%%cupsJobTicket: %s=%s\n\"\n*End\n", option, choice); fprintf(dstfp, "*%% Changed by cupsaddsmb...\n%s", line); } else fputs(line, dstfp); } fclose(srcfp); /* * Now add the CUPS-specific attributes and options... */ fputs("\n*% CUPS Job Ticket support and options...\n", dstfp); fputs("*Protocols: PJL\n", dstfp); fputs("*JCLBegin: \"%!PS-Adobe-3.0<0A>\"\n", dstfp); fputs("*JCLToPSInterpreter: \"\"\n", dstfp); fputs("*JCLEnd: \"\"\n", dstfp); fputs("\n*OpenGroup: CUPS/CUPS Options\n\n", dstfp); if ((defattr = ippFindAttribute(info, "job-hold-until-default", IPP_TAG_ZERO)) != NULL && (suppattr = ippFindAttribute(info, "job-hold-until-supported", IPP_TAG_ZERO)) != NULL) write_option(dstfp, 10, "cupsJobHoldUntil", "Hold Until", "job-hold-until", suppattr, defattr, 0, 1); if ((defattr = ippFindAttribute(info, "job-priority-default", IPP_TAG_INTEGER)) != NULL && (suppattr = ippFindAttribute(info, "job-priority-supported", IPP_TAG_RANGE)) != NULL) write_option(dstfp, 11, "cupsJobPriority", "Priority", "job-priority", suppattr, defattr, 0, 1); if ((defattr = ippFindAttribute(info, "job-sheets-default", IPP_TAG_ZERO)) != NULL && (suppattr = ippFindAttribute(info, "job-sheets-supported", IPP_TAG_ZERO)) != NULL) { write_option(dstfp, 20, "cupsJobSheetsStart", "Start Banner", "job-sheets", suppattr, defattr, 0, 2); write_option(dstfp, 21, "cupsJobSheetsEnd", "End Banner", "job-sheets", suppattr, defattr, 1, 2); } fputs("*CloseGroup: CUPS\n", dstfp); fclose(dstfp); close(dstfd); return (0); } /* * 'do_samba_command()' - Do a SAMBA command, asking for * a password as needed. */ int /* O - Status of command */ do_samba_command(const char *command, /* I - Command to run */ const char *subcmd) /* I - Sub-command */ { int status; /* Status of command */ char temp[4096]; /* Command/prompt string */ static const char *p = NULL; /* Password data */ for (status = 1;;) { if (p == NULL) { snprintf(temp, sizeof(temp), "Password for %s required to access %s via SAMBA: ", SAMBAUser, SAMBAServer); if ((p = cupsGetPassword(temp)) == NULL) break; } snprintf(temp, sizeof(temp), "%s -N -U\'%s%%%s\' -c \'%s\'", command, SAMBAUser, p, subcmd); if (Verbosity) printf("Running command: %s\n", temp); else strlcat(temp, " /dev/null 2>/dev/null", sizeof(temp)); if ((status = system(temp)) != 0) { if (Verbosity) puts(""); if (p[0]) p = NULL; else break; } else { if (Verbosity) puts(""); break; } } return (status); } /* * 'export_dest()' - Export a destination to SAMBA. */ int /* O - 0 on success, non-zero on error */ export_dest(const char *dest) /* I - Destination to export */ { int status; /* Status of smbclient/rpcclient commands */ const char *ppdfile; /* PPD file for printer drivers */ char newppd[1024], /* New PPD file for printer drivers */ file[1024], /* File to test for */ command[1024], /* Command to run */ subcmd[1024]; /* Sub-command */ const char *datadir; /* CUPS_DATADIR */ http_t *http; /* Connection to server */ cups_lang_t *language; /* Default language */ ipp_t *request, /* IPP request */ *response; /* IPP response */ static const char *pattrs[] = /* Printer attributes we want */ { "job-hold-until-supported", "job-hold-until-default", "job-sheets-supported", "job-sheets-default", "job-priority-supported", "job-priority-default" }; /* * Get the location of the printer driver files... */ if ((datadir = getenv("CUPS_DATADIR")) == NULL) datadir = CUPS_DATADIR; /* * Open a connection to the scheduler... */ if ((http = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption())) == NULL) { fprintf(stderr, "cupsaddsmb: Unable to connect to server \"%s\" for %s - %s\n", cupsServer(), dest, strerror(errno)); return (1); } /* * Get the PPD file... */ if ((ppdfile = cupsGetPPD2(http, dest)) == NULL) { fprintf(stderr, "cupsaddsmb: No PPD file for printer \"%s\" - skipping!\n", dest); httpClose(http); return (0); } /* * Append the supported banner pages to the PPD file... */ request = ippNew(); request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES; request->request.op.request_id = 1; language = cupsLangDefault(); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, cupsLangEncoding(language)); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, language->language); snprintf(command, sizeof(command), "ipp://localhost/printers/%s", dest); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, command); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]), NULL, pattrs); /* * Do the request and get back a response... */ if ((response = cupsDoRequest(http, request, "/")) != NULL) { if (response->request.status.status_code > IPP_OK_CONFLICT) { fprintf(stderr, "cupsaddsmb: get-printer-attributes failed for \"%s\": %s\n", dest, ippErrorString(response->request.status.status_code)); ippDelete(response); cupsLangFree(language); httpClose(http); unlink(ppdfile); return (2); } } else { fprintf(stderr, "cupsaddsmb: get-printer-attributes failed for \"%s\": %s\n", dest, ippErrorString(cupsLastError())); cupsLangFree(language); httpClose(http); unlink(ppdfile); return (2); } /* * Convert the PPD file to the Windows driver format... */ if (convert_ppd(ppdfile, newppd, sizeof(newppd), response)) { fprintf(stderr, "cupsaddsmb: Unable to convert PPD file for %s - %s\n", dest, strerror(errno)); ippDelete(response); cupsLangFree(language); httpClose(http); unlink(ppdfile); return (3); } ippDelete(response); cupsLangFree(language); httpClose(http); /* * Remove the old PPD and point to the new one... */ unlink(ppdfile); ppdfile = newppd; /* * See which drivers are available; the new CUPS v6 and Adobe drivers * depend on the Windows 2k PS driver, so copy that driver first: * * Files: * * ps5ui.dll * pscript.hlp * pscript.ntf * pscript5.dll */ snprintf(file, sizeof(file), "%s/drivers/pscript5.dll", datadir); if (!access(file, 0)) { /* * Windows 2k driver is installed; do the smbclient commands needed * to copy the Win2k drivers over... */ snprintf(command, sizeof(command), "smbclient //%s/print\\$", SAMBAServer); snprintf(subcmd, sizeof(subcmd), "mkdir W32X86;" "put %s W32X86/%s.ppd;" "put %s/drivers/ps5ui.dll W32X86/ps5ui.dll;" "put %s/drivers/pscript.hlp W32X86/pscript.hlp;" "put %s/drivers/pscript.ntf W32X86/pscript.ntf;" "put %s/drivers/pscript5.dll W32X86/pscript5.dll", ppdfile, dest, datadir, datadir, datadir, datadir); if ((status = do_samba_command(command, subcmd)) != 0) { fprintf(stderr, "cupsaddsmb: Unable to copy Windows 2000 printer driver files (%d)!\n", status); unlink(ppdfile); return (4); } /* * See if we also have the CUPS driver files; if so, use them! */ snprintf(file, sizeof(file), "%s/drivers/cupsdrv6.dll", datadir); if (!access(file, 0)) { /* * Copy the CUPS driver files over... */ snprintf(subcmd, sizeof(subcmd), "put %s/drivers/cupsdrv6.dll W32X86/cupsdrv6.dll;" "put %s/drivers/cupsui6.dll W32X86/cupsui6.dll", datadir, datadir); if ((status = do_samba_command(command, subcmd)) != 0) { fprintf(stderr, "cupsaddsmb: Unable to copy CUPS printer driver files (%d)!\n", status); unlink(ppdfile); return (4); } /* * Do the rpcclient command needed for the CUPS drivers... */ snprintf(subcmd, sizeof(subcmd), "adddriver \"Windows NT x86\" \"%s:" "pscript5.dll:%s.ppd:ps5ui.dll:pscript.hlp:NULL:RAW:" "cupsdrv6.dll,cupsui6.dll,pscript.ntf\"", dest, dest); } else { /* * Don't have the CUPS drivers, so just use the standard Windows * drivers... */ snprintf(subcmd, sizeof(subcmd), "adddriver \"Windows NT x86\" \"%s:" "pscript5.dll:%s.ppd:ps5ui.dll:pscript.hlp:NULL:RAW:" "pscript.ntf\"", dest, dest); } snprintf(command, sizeof(command), "rpcclient %s", SAMBAServer); if ((status = do_samba_command(command, subcmd)) != 0) { fprintf(stderr, "cupsaddsmb: Unable to install Windows 2000 printer driver files (%d)!\n", status); unlink(ppdfile); return (5); } } snprintf(file, sizeof(file), "%s/drivers/ADOBEPS4.DRV", datadir); if (!access(file, 0)) { /* * Do the smbclient commands needed for the Adobe Win9x drivers... */ snprintf(command, sizeof(command), "smbclient //%s/print\\$", SAMBAServer); snprintf(subcmd, sizeof(subcmd), "mkdir WIN40;" "put %s WIN40/%s.PPD;" "put %s/drivers/ADFONTS.MFM WIN40/ADFONTS.MFM;" "put %s/drivers/ADOBEPS4.DRV WIN40/ADOBEPS4.DRV;" "put %s/drivers/ADOBEPS4.HLP WIN40/ADOBEPS4.HLP;" "put %s/drivers/ICONLIB.DLL WIN40/ICONLIB.DLL;" "put %s/drivers/PSMON.DLL WIN40/PSMON.DLL;", ppdfile, dest, datadir, datadir, datadir, datadir, datadir); if ((status = do_samba_command(command, subcmd)) != 0) { fprintf(stderr, "cupsaddsmb: Unable to copy Windows 9x printer driver files (%d)!\n", status); unlink(ppdfile); return (6); } /* * Do the rpcclient commands needed for the Adobe Win9x drivers... */ snprintf(command, sizeof(command), "rpcclient %s", SAMBAServer); snprintf(subcmd, sizeof(subcmd), "adddriver \"Windows 4.0\" \"%s:ADOBEPS4.DRV:%s.PPD:NULL:" "ADOBEPS4.HLP:PSMON.DLL:RAW:" "ADOBEPS4.DRV,%s.PPD,ADOBEPS4.HLP,PSMON.DLL,ADFONTS.MFM," "ICONLIB.DLL\"", dest, dest, dest); if ((status = do_samba_command(command, subcmd)) != 0) { fprintf(stderr, "cupsaddsmb: Unable to install Windows 9x printer driver files (%d)!\n", status); unlink(ppdfile); return (7); } } unlink(ppdfile); /* * Finally, associate the drivers we just added with the queue... */ snprintf(command, sizeof(command), "rpcclient %s", SAMBAServer); snprintf(subcmd, sizeof(subcmd), "setdriver %s %s", dest, dest); if ((status = do_samba_command(command, subcmd)) != 0) { fprintf(stderr, "cupsaddsmb: Unable to set Windows printer driver (%d)!\n", status); return (8); } return (0); } /* * 'ppd_gets()' - Get a CR and/or LF-terminated line. */ char * /* O - Line read or NULL on eof/error */ ppd_gets(FILE *fp, /* I - File to read from*/ char *buf, /* O - String buffer */ int buflen) /* I - Size of string buffer */ { int ch; /* Character from file */ char *ptr, /* Current position in line buffer */ *end; /* End of line buffer */ /* * Range check input... */ if (!fp || !buf || buflen < 2 || feof(fp)) return (NULL); /* * Now loop until we have a valid line... */ for (ptr = buf, end = buf + buflen - 1; ptr < end ;) { if ((ch = getc(fp)) == EOF) { if (ptr == buf) return (NULL); else break; } *ptr++ = ch; if (ch == '\r') { /* * Check for CR LF... */ if ((ch = getc(fp)) != '\n') ungetc(ch, fp); else if (ptr < end) *ptr++ = ch; break; } else if (ch == '\n') { /* * Line feed ends a line... */ break; } } *ptr = '\0'; return (buf); } /* * 'usage()' - Show program usage and exit... */ void usage(void) { puts("Usage: cupsaddsmb [options] printer1 ... printerN"); puts(" cupsaddsmb [options] -a"); puts(""); puts("Options:"); puts(" -H samba-server Use the named SAMBA server"); puts(" -U samba-user Authenticate using the named SAMBA user"); puts(" -a Export all printers"); puts(" -h cups-server Use the named CUPS server"); puts(" -v Be verbose (show commands)"); exit(1); } /* * 'write_option()' - Write a CUPS option to a PPD file. */ int /* O - 0 on success, 1 on failure */ write_option(FILE *dstfp, /* I - PPD file */ int order, /* I - Order dependency */ const char *name, /* I - Option name */ const char *text, /* I - Option text */ const char *attrname, /* I - Attribute name */ ipp_attribute_t *suppattr, /* I - IPP -supported attribute */ ipp_attribute_t *defattr, /* I - IPP -default attribute */ int defval, /* I - Default value number */ int valcount) /* I - Number of values */ { int i; /* Looping var */ if (!dstfp || !name || !text || !suppattr || !defattr) return (1); fprintf(dstfp, "*JCLOpenUI *%s/%s: PickOne\n" "*OrderDependency: %d JCLSetup *%s\n", name, text, order, name); if (defattr->value_tag == IPP_TAG_INTEGER) { /* * Do numeric options with a range or list... */ fprintf(dstfp, "*Default%s: %d\n", name, defattr->values[defval].integer); if (suppattr->value_tag == IPP_TAG_RANGE) { /* * List each number in the range... */ for (i = suppattr->values[0].range.lower; i <= suppattr->values[0].range.upper; i ++) { fprintf(dstfp, "*%s %d: \"", name, i); if (valcount == 1) fprintf(dstfp, "%%cupsJobTicket: %s=%d\n\"\n*End\n", attrname, i); else if (defval == 0) fprintf(dstfp, "%%cupsJobTicket: %s=%d\"\n", attrname, i); else if (defval < (valcount - 1)) fprintf(dstfp, ",%d\"\n", i); else fprintf(dstfp, ",%d\n\"\n*End\n", i); } } else { /* * List explicit numbers... */ for (i = 0; i < suppattr->num_values; i ++) { fprintf(dstfp, "*%s %d: \"", name, suppattr->values[i].integer); if (valcount == 1) fprintf(dstfp, "%%cupsJobTicket: %s=%d\n\"\n*End\n", attrname, suppattr->values[i].integer); else if (defval == 0) fprintf(dstfp, "%%cupsJobTicket: %s=%d\"\n", attrname, suppattr->values[i].integer); else if (defval < (valcount - 1)) fprintf(dstfp, ",%d\"\n", suppattr->values[i].integer); else fprintf(dstfp, ",%d\n\"\n*End\n", suppattr->values[i].integer); } } } else { /* * Do text options with a list... */ fprintf(dstfp, "*Default%s: %s\n", name, defattr->values[defval].string.text); for (i = 0; i < suppattr->num_values; i ++) { fprintf(dstfp, "*%s %s: \"", name, suppattr->values[i].string.text); if (valcount == 1) fprintf(dstfp, "%%cupsJobTicket: %s=%s\n\"\n*End\n", attrname, suppattr->values[i].string.text); else if (defval == 0) fprintf(dstfp, "%%cupsJobTicket: %s=%s\"\n", attrname, suppattr->values[i].string.text); else if (defval < (valcount - 1)) fprintf(dstfp, ",%s\"\n", suppattr->values[i].string.text); else fprintf(dstfp, ",%s\n\"\n*End\n", suppattr->values[i].string.text); } } fprintf(dstfp, "*JCLCloseUI: *%s\n\n", name); return (0); } /* * End of "$Id: cupsaddsmb.c,v 1.1.1.17 2005/01/04 19:16:39 jlovell Exp $". */