/* * Printer class routines for the CUPS scheduler. * * Copyright 2007-2017 by Apple Inc. * Copyright 1997-2007 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more information. */ /* * Include necessary headers... */ #include "cupsd.h" /* * 'cupsdAddClass()' - Add a class to the system. */ cupsd_printer_t * /* O - New class */ cupsdAddClass(const char *name) /* I - Name of class */ { cupsd_printer_t *c; /* New class */ char uri[1024]; /* Class URI */ /* * Add the printer and set the type to "class"... */ if ((c = cupsdAddPrinter(name)) != NULL) { /* * Change from a printer to a class... */ c->type = CUPS_PRINTER_CLASS; httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, ServerName, RemotePort, "/classes/%s", name); cupsdSetString(&c->uri, uri); cupsdSetString(&c->error_policy, "retry-current-job"); } return (c); } /* * 'cupsdAddPrinterToClass()' - Add a printer to a class... */ void cupsdAddPrinterToClass( cupsd_printer_t *c, /* I - Class to add to */ cupsd_printer_t *p) /* I - Printer to add */ { int i; /* Looping var */ cupsd_printer_t **temp; /* Pointer to printer array */ /* * See if this printer is already a member of the class... */ for (i = 0; i < c->num_printers; i ++) if (c->printers[i] == p) return; /* * Allocate memory as needed... */ if (c->num_printers == 0) temp = malloc(sizeof(cupsd_printer_t *)); else temp = realloc(c->printers, sizeof(cupsd_printer_t *) * (size_t)(c->num_printers + 1)); if (temp == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to add printer %s to class %s!", p->name, c->name); return; } /* * Add the printer to the end of the array and update the number of printers. */ c->printers = temp; temp += c->num_printers; c->num_printers ++; *temp = p; } /* * 'cupsdDeletePrinterFromClass()' - Delete a printer from a class. */ int /* O - 1 if class changed, 0 otherwise */ cupsdDeletePrinterFromClass( cupsd_printer_t *c, /* I - Class to delete from */ cupsd_printer_t *p) /* I - Printer to delete */ { int i; /* Looping var */ /* * See if the printer is in the class... */ for (i = 0; i < c->num_printers; i ++) if (p == c->printers[i]) break; /* * If it is, remove it from the list... */ if (i < c->num_printers) { /* * Yes, remove the printer... */ c->num_printers --; if (i < c->num_printers) memmove(c->printers + i, c->printers + i + 1, (size_t)(c->num_printers - i) * sizeof(cupsd_printer_t *)); } else return (0); /* * Update the IPP attributes (have to do this for member-names)... */ cupsdSetPrinterAttrs(c); return (1); } /* * 'cupsdDeletePrinterFromClasses()' - Delete a printer from all classes. */ int /* O - 1 if class changed, 0 otherwise */ cupsdDeletePrinterFromClasses( cupsd_printer_t *p) /* I - Printer to delete */ { int changed = 0; /* Any class changed? */ cupsd_printer_t *c; /* Pointer to current class */ /* * Loop through the printer/class list and remove the printer * from each class listed... */ for (c = (cupsd_printer_t *)cupsArrayFirst(Printers); c; c = (cupsd_printer_t *)cupsArrayNext(Printers)) if (c->type & CUPS_PRINTER_CLASS) changed |= cupsdDeletePrinterFromClass(c, p); return (changed); } /* * 'cupsdFindAvailablePrinter()' - Find an available printer in a class. */ cupsd_printer_t * /* O - Available printer or NULL */ cupsdFindAvailablePrinter( const char *name) /* I - Class to check */ { int i; /* Looping var */ cupsd_printer_t *c; /* Printer class */ /* * Find the class... */ if ((c = cupsdFindClass(name)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to find class \"%s\"!", name); return (NULL); } if (c->num_printers == 0) return (NULL); /* * Make sure that the last printer is also a valid index into the printer * array. If not, reset the last printer to 0... */ if (c->last_printer >= c->num_printers) c->last_printer = 0; /* * Loop through the printers in the class and return the first idle * printer... We keep track of the last printer that we used so that * a "round robin" type of scheduling is realized (otherwise the first * server might be saturated with print jobs...) * * Thanks to Joel Fredrikson for helping us get this right! */ for (i = c->last_printer + 1; ; i ++) { if (i >= c->num_printers) i = 0; if (c->printers[i]->accepting && (c->printers[i]->state == IPP_PRINTER_IDLE || ((c->printers[i]->type & CUPS_PRINTER_REMOTE) && !c->printers[i]->job))) { c->last_printer = i; return (c->printers[i]); } if (i == c->last_printer) break; } return (NULL); } /* * 'cupsdFindClass()' - Find the named class. */ cupsd_printer_t * /* O - Matching class or NULL */ cupsdFindClass(const char *name) /* I - Name of class */ { cupsd_printer_t *c; /* Current class/printer */ if ((c = cupsdFindDest(name)) != NULL && (c->type & CUPS_PRINTER_CLASS)) return (c); else return (NULL); } /* * 'cupsdLoadAllClasses()' - Load classes from the classes.conf file. */ void cupsdLoadAllClasses(void) { int i; /* Looping var */ cups_file_t *fp; /* classes.conf file */ int linenum; /* Current line number */ char line[4096], /* Line from file */ *value, /* Pointer to value */ *valueptr; /* Pointer into value */ cupsd_printer_t *p, /* Current printer class */ *temp; /* Temporary pointer to printer */ /* * Open the classes.conf file... */ snprintf(line, sizeof(line), "%s/classes.conf", ServerRoot); if ((fp = cupsdOpenConfFile(line)) == NULL) return; /* * Read class configurations until we hit EOF... */ linenum = 0; p = NULL; while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { /* * Decode the directive... */ if (!_cups_strcasecmp(line, " or */ if (p == NULL && value) { cupsdLogMessage(CUPSD_LOG_DEBUG, "Loading class %s...", value); /* * Since prior classes may have implicitly defined this class, * see if it already exists... */ if ((p = cupsdFindDest(value)) != NULL) { p->type = CUPS_PRINTER_CLASS; cupsdSetStringf(&p->uri, "ipp://%s:%d/classes/%s", ServerName, LocalPort, value); cupsdSetString(&p->error_policy, "retry-job"); } else p = cupsdAddClass(value); p->accepting = 1; p->state = IPP_PRINTER_IDLE; if (!_cups_strcasecmp(line, "") || !_cups_strcasecmp(line, "")) { if (p != NULL) { cupsdSetPrinterAttrs(p); p = NULL; } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!p) { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "PrinterId")) { if (value && (i = atoi(value)) > 0) p->printer_id = i; else cupsdLogMessage(CUPSD_LOG_ERROR, "Bad PrinterId on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "UUID")) { if (value && !strncmp(value, "urn:uuid:", 9)) cupsdSetString(&(p->uuid), value); else cupsdLogMessage(CUPSD_LOG_ERROR, "Bad UUID on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "AuthInfoRequired")) { if (!cupsdSetAuthInfoRequired(p, value, NULL)) cupsdLogMessage(CUPSD_LOG_ERROR, "Bad AuthInfoRequired on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "Info")) { if (value) cupsdSetString(&p->info, value); } else if (!_cups_strcasecmp(line, "Location")) { if (value) cupsdSetString(&p->location, value); } else if (!_cups_strcasecmp(line, "Option") && value) { /* * Option name value */ for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++); if (!*valueptr) cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); else { for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0'); p->num_options = cupsAddOption(value, valueptr, p->num_options, &(p->options)); } } else if (!_cups_strcasecmp(line, "Printer")) { if (!value) { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); continue; } else if ((temp = cupsdFindPrinter(value)) == NULL) { cupsdLogMessage(CUPSD_LOG_WARN, "Unknown printer %s on line %d of classes.conf.", value, linenum); /* * Add the missing remote printer... */ if ((temp = cupsdAddPrinter(value)) != NULL) { cupsdSetString(&temp->make_model, "Remote Printer on unknown"); temp->state = IPP_PRINTER_STOPPED; temp->type |= CUPS_PRINTER_REMOTE; cupsdSetString(&temp->location, "Location Unknown"); cupsdSetString(&temp->info, "No Information Available"); temp->hostname[0] = '\0'; cupsdSetPrinterAttrs(temp); } } if (temp) cupsdAddPrinterToClass(p, temp); } else if (!_cups_strcasecmp(line, "State")) { /* * Set the initial queue state... */ if (!_cups_strcasecmp(value, "idle")) p->state = IPP_PRINTER_IDLE; else if (!_cups_strcasecmp(value, "stopped")) { p->state = IPP_PRINTER_STOPPED; for (i = 0 ; i < p->num_reasons; i ++) if (!strcmp("paused", p->reasons[i])) break; if (i >= p->num_reasons && p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0]))) { p->reasons[p->num_reasons] = _cupsStrAlloc("paused"); p->num_reasons ++; } } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "StateMessage")) { /* * Set the initial queue state message... */ if (value) strlcpy(p->state_message, value, sizeof(p->state_message)); } else if (!_cups_strcasecmp(line, "StateTime")) { /* * Set the state time... */ if (value) p->state_time = atoi(value); } else if (!_cups_strcasecmp(line, "Accepting")) { /* * Set the initial accepting state... */ if (value && (!_cups_strcasecmp(value, "yes") || !_cups_strcasecmp(value, "on") || !_cups_strcasecmp(value, "true"))) p->accepting = 1; else if (value && (!_cups_strcasecmp(value, "no") || !_cups_strcasecmp(value, "off") || !_cups_strcasecmp(value, "false"))) p->accepting = 0; else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "Shared")) { /* * Set the initial shared state... */ if (value && (!_cups_strcasecmp(value, "yes") || !_cups_strcasecmp(value, "on") || !_cups_strcasecmp(value, "true"))) p->shared = 1; else if (value && (!_cups_strcasecmp(value, "no") || !_cups_strcasecmp(value, "off") || !_cups_strcasecmp(value, "false"))) p->shared = 0; else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "JobSheets")) { /* * Set the initial job sheets... */ if (value) { for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++); if (*valueptr) *valueptr++ = '\0'; cupsdSetString(&p->job_sheets[0], value); while (isspace(*valueptr & 255)) valueptr ++; if (*valueptr) { for (value = valueptr; *valueptr && !isspace(*valueptr & 255); valueptr ++); if (*valueptr) *valueptr = '\0'; cupsdSetString(&p->job_sheets[1], value); } } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "AllowUser")) { if (value) { p->deny_users = 0; cupsdAddString(&(p->users), value); } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "DenyUser")) { if (value) { p->deny_users = 1; cupsdAddString(&(p->users), value); } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "QuotaPeriod")) { if (value) p->quota_period = atoi(value); else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "PageLimit")) { if (value) p->page_limit = atoi(value); else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "KLimit")) { if (value) p->k_limit = atoi(value); else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "OpPolicy")) { if (value) { cupsd_policy_t *pol; /* Policy */ if ((pol = cupsdFindPolicy(value)) != NULL) { cupsdSetString(&p->op_policy, value); p->op_policy_ptr = pol; } else cupsdLogMessage(CUPSD_LOG_ERROR, "Bad policy \"%s\" on line %d of classes.conf", value, linenum); } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else if (!_cups_strcasecmp(line, "ErrorPolicy")) { if (value) { if (strcmp(value, "retry-current-job") && strcmp(value, "retry-job")) cupsdLogMessage(CUPSD_LOG_WARN, "ErrorPolicy %s ignored on line %d of classes.conf", value, linenum); } else cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of classes.conf.", linenum); } else { /* * Something else we don't understand... */ cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown configuration directive %s on line %d of classes.conf.", line, linenum); } } cupsFileClose(fp); } /* * 'cupsdSaveAllClasses()' - Save classes to the classes.conf file. */ void cupsdSaveAllClasses(void) { cups_file_t *fp; /* classes.conf file */ char filename[1024], /* classes.conf filename */ temp[1024], /* Temporary string */ value[2048], /* Value string */ *name; /* Current user name */ cupsd_printer_t *pclass; /* Current printer class */ int i; /* Looping var */ time_t curtime; /* Current time */ struct tm curdate; /* Current date */ cups_option_t *option; /* Current option */ /* * Create the classes.conf file... */ snprintf(filename, sizeof(filename), "%s/classes.conf", ServerRoot); if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL) return; cupsdLogMessage(CUPSD_LOG_INFO, "Saving classes.conf..."); /* * Write a small header to the file... */ time(&curtime); localtime_r(&curtime, &curdate); strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", &curdate); cupsFilePuts(fp, "# Class configuration file for " CUPS_SVERSION "\n"); cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp); cupsFilePuts(fp, "# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING\n"); /* * Write each local class known to the system... */ for (pclass = (cupsd_printer_t *)cupsArrayFirst(Printers); pclass; pclass = (cupsd_printer_t *)cupsArrayNext(Printers)) { /* * Skip remote destinations and regular printers... */ if ((pclass->type & CUPS_PRINTER_REMOTE) || !(pclass->type & CUPS_PRINTER_CLASS)) continue; /* * Write printers as needed... */ if (pclass == DefaultPrinter) cupsFilePrintf(fp, "\n", pclass->name); else cupsFilePrintf(fp, "\n", pclass->name); if (pclass->printer_id) cupsFilePrintf(fp, "PrinterId %d\n", pclass->printer_id); cupsFilePrintf(fp, "UUID %s\n", pclass->uuid); if (pclass->num_auth_info_required > 0) { switch (pclass->num_auth_info_required) { case 1 : strlcpy(value, pclass->auth_info_required[0], sizeof(value)); break; case 2 : snprintf(value, sizeof(value), "%s,%s", pclass->auth_info_required[0], pclass->auth_info_required[1]); break; case 3 : default : snprintf(value, sizeof(value), "%s,%s,%s", pclass->auth_info_required[0], pclass->auth_info_required[1], pclass->auth_info_required[2]); break; } cupsFilePutConf(fp, "AuthInfoRequired", value); } if (pclass->info) cupsFilePutConf(fp, "Info", pclass->info); if (pclass->location) cupsFilePutConf(fp, "Location", pclass->location); if (pclass->state == IPP_PRINTER_STOPPED) cupsFilePuts(fp, "State Stopped\n"); else cupsFilePuts(fp, "State Idle\n"); cupsFilePrintf(fp, "StateTime %d\n", (int)pclass->state_time); if (pclass->accepting) cupsFilePuts(fp, "Accepting Yes\n"); else cupsFilePuts(fp, "Accepting No\n"); if (pclass->shared) cupsFilePuts(fp, "Shared Yes\n"); else cupsFilePuts(fp, "Shared No\n"); snprintf(value, sizeof(value), "%s %s", pclass->job_sheets[0], pclass->job_sheets[1]); cupsFilePutConf(fp, "JobSheets", value); for (i = 0; i < pclass->num_printers; i ++) cupsFilePrintf(fp, "Printer %s\n", pclass->printers[i]->name); cupsFilePrintf(fp, "QuotaPeriod %d\n", pclass->quota_period); cupsFilePrintf(fp, "PageLimit %d\n", pclass->page_limit); cupsFilePrintf(fp, "KLimit %d\n", pclass->k_limit); for (name = (char *)cupsArrayFirst(pclass->users); name; name = (char *)cupsArrayNext(pclass->users)) cupsFilePutConf(fp, pclass->deny_users ? "DenyUser" : "AllowUser", name); if (pclass->op_policy) cupsFilePutConf(fp, "OpPolicy", pclass->op_policy); if (pclass->error_policy) cupsFilePutConf(fp, "ErrorPolicy", pclass->error_policy); for (i = pclass->num_options, option = pclass->options; i > 0; i --, option ++) { snprintf(value, sizeof(value), "%s %s", option->name, option->value); cupsFilePutConf(fp, "Option", value); } if (pclass == DefaultPrinter) cupsFilePuts(fp, "\n"); else cupsFilePuts(fp, "\n"); } cupsdCloseCreatedConfFile(fp, filename); }