1/*
2 * "$Id: classes.c 11791 2014-04-02 16:56:54Z msweet $"
3 *
4 * Printer class routines for the CUPS scheduler.
5 *
6 * Copyright 2007-2014 by Apple Inc.
7 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 *
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file.  If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
14 */
15
16/*
17 * Include necessary headers...
18 */
19
20#include "cupsd.h"
21
22
23/*
24 * 'cupsdAddClass()' - Add a class to the system.
25 */
26
27cupsd_printer_t *			/* O - New class */
28cupsdAddClass(const char *name)		/* I - Name of class */
29{
30  cupsd_printer_t	*c;		/* New class */
31  char			uri[1024];	/* Class URI */
32
33
34 /*
35  * Add the printer and set the type to "class"...
36  */
37
38  if ((c = cupsdAddPrinter(name)) != NULL)
39  {
40   /*
41    * Change from a printer to a class...
42    */
43
44    c->type = CUPS_PRINTER_CLASS;
45
46    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
47		     ServerName, RemotePort, "/classes/%s", name);
48    cupsdSetString(&c->uri, uri);
49
50    cupsdSetString(&c->error_policy, "retry-current-job");
51  }
52
53  return (c);
54}
55
56
57/*
58 * 'cupsdAddPrinterToClass()' - Add a printer to a class...
59 */
60
61void
62cupsdAddPrinterToClass(
63    cupsd_printer_t *c,			/* I - Class to add to */
64    cupsd_printer_t *p)			/* I - Printer to add */
65{
66  int			i;		/* Looping var */
67  cupsd_printer_t	**temp;		/* Pointer to printer array */
68
69
70 /*
71  * See if this printer is already a member of the class...
72  */
73
74  for (i = 0; i < c->num_printers; i ++)
75    if (c->printers[i] == p)
76      return;
77
78 /*
79  * Allocate memory as needed...
80  */
81
82  if (c->num_printers == 0)
83    temp = malloc(sizeof(cupsd_printer_t *));
84  else
85    temp = realloc(c->printers, sizeof(cupsd_printer_t *) * (size_t)(c->num_printers + 1));
86
87  if (temp == NULL)
88  {
89    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to add printer %s to class %s!",
90                    p->name, c->name);
91    return;
92  }
93
94 /*
95  * Add the printer to the end of the array and update the number of printers.
96  */
97
98  c->printers = temp;
99  temp        += c->num_printers;
100  c->num_printers ++;
101
102  *temp = p;
103}
104
105
106/*
107 * 'cupsdDeletePrinterFromClass()' - Delete a printer from a class.
108 */
109
110int					/* O - 1 if class changed, 0 otherwise */
111cupsdDeletePrinterFromClass(
112    cupsd_printer_t *c,			/* I - Class to delete from */
113    cupsd_printer_t *p)			/* I - Printer to delete */
114{
115  int	i;				/* Looping var */
116
117
118 /*
119  * See if the printer is in the class...
120  */
121
122  for (i = 0; i < c->num_printers; i ++)
123    if (p == c->printers[i])
124      break;
125
126 /*
127  * If it is, remove it from the list...
128  */
129
130  if (i < c->num_printers)
131  {
132   /*
133    * Yes, remove the printer...
134    */
135
136    c->num_printers --;
137    if (i < c->num_printers)
138      memmove(c->printers + i, c->printers + i + 1,
139              (size_t)(c->num_printers - i) * sizeof(cupsd_printer_t *));
140  }
141  else
142    return (0);
143
144 /*
145  * Update the IPP attributes (have to do this for member-names)...
146  */
147
148  cupsdSetPrinterAttrs(c);
149
150  return (1);
151}
152
153
154/*
155 * 'cupsdDeletePrinterFromClasses()' - Delete a printer from all classes.
156 */
157
158int					/* O - 1 if class changed, 0 otherwise */
159cupsdDeletePrinterFromClasses(
160    cupsd_printer_t *p)			/* I - Printer to delete */
161{
162  int			changed = 0;	/* Any class changed? */
163  cupsd_printer_t	*c;		/* Pointer to current class */
164
165
166 /*
167  * Loop through the printer/class list and remove the printer
168  * from each class listed...
169  */
170
171  for (c = (cupsd_printer_t *)cupsArrayFirst(Printers);
172       c;
173       c = (cupsd_printer_t *)cupsArrayNext(Printers))
174    if (c->type & CUPS_PRINTER_CLASS)
175      changed |= cupsdDeletePrinterFromClass(c, p);
176
177  return (changed);
178}
179
180
181/*
182 * 'cupsdFindAvailablePrinter()' - Find an available printer in a class.
183 */
184
185cupsd_printer_t *			/* O - Available printer or NULL */
186cupsdFindAvailablePrinter(
187    const char *name)			/* I - Class to check */
188{
189  int			i;		/* Looping var */
190  cupsd_printer_t	*c;		/* Printer class */
191
192
193 /*
194  * Find the class...
195  */
196
197  if ((c = cupsdFindClass(name)) == NULL)
198  {
199    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to find class \"%s\"!", name);
200    return (NULL);
201  }
202
203  if (c->num_printers == 0)
204    return (NULL);
205
206 /*
207  * Make sure that the last printer is also a valid index into the printer
208  * array.  If not, reset the last printer to 0...
209  */
210
211  if (c->last_printer >= c->num_printers)
212    c->last_printer = 0;
213
214 /*
215  * Loop through the printers in the class and return the first idle
216  * printer...  We keep track of the last printer that we used so that
217  * a "round robin" type of scheduling is realized (otherwise the first
218  * server might be saturated with print jobs...)
219  *
220  * Thanks to Joel Fredrikson for helping us get this right!
221  */
222
223  for (i = c->last_printer + 1; ; i ++)
224  {
225    if (i >= c->num_printers)
226      i = 0;
227
228    if (c->printers[i]->accepting &&
229        (c->printers[i]->state == IPP_PRINTER_IDLE ||
230         ((c->printers[i]->type & CUPS_PRINTER_REMOTE) && !c->printers[i]->job)))
231    {
232      c->last_printer = i;
233      return (c->printers[i]);
234    }
235
236    if (i == c->last_printer)
237      break;
238  }
239
240  return (NULL);
241}
242
243
244/*
245 * 'cupsdFindClass()' - Find the named class.
246 */
247
248cupsd_printer_t *			/* O - Matching class or NULL */
249cupsdFindClass(const char *name)	/* I - Name of class */
250{
251  cupsd_printer_t	*c;		/* Current class/printer */
252
253
254  if ((c = cupsdFindDest(name)) != NULL && (c->type & CUPS_PRINTER_CLASS))
255    return (c);
256  else
257    return (NULL);
258}
259
260
261/*
262 * 'cupsdLoadAllClasses()' - Load classes from the classes.conf file.
263 */
264
265void
266cupsdLoadAllClasses(void)
267{
268  int			i;		/* Looping var */
269  cups_file_t		*fp;		/* classes.conf file */
270  int			linenum;	/* Current line number */
271  char			line[4096],	/* Line from file */
272			*value,		/* Pointer to value */
273			*valueptr;	/* Pointer into value */
274  cupsd_printer_t	*p,		/* Current printer class */
275			*temp;		/* Temporary pointer to printer */
276
277
278 /*
279  * Open the classes.conf file...
280  */
281
282  snprintf(line, sizeof(line), "%s/classes.conf", ServerRoot);
283  if ((fp = cupsdOpenConfFile(line)) == NULL)
284    return;
285
286 /*
287  * Read class configurations until we hit EOF...
288  */
289
290  linenum = 0;
291  p       = NULL;
292
293  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
294  {
295   /*
296    * Decode the directive...
297    */
298
299    if (!_cups_strcasecmp(line, "<Class") ||
300        !_cups_strcasecmp(line, "<DefaultClass"))
301    {
302     /*
303      * <Class name> or <DefaultClass name>
304      */
305
306      if (p == NULL && value)
307      {
308        cupsdLogMessage(CUPSD_LOG_DEBUG, "Loading class %s...", value);
309
310       /*
311        * Since prior classes may have implicitly defined this class,
312	* see if it already exists...
313	*/
314
315        if ((p = cupsdFindDest(value)) != NULL)
316	{
317	  p->type = CUPS_PRINTER_CLASS;
318	  cupsdSetStringf(&p->uri, "ipp://%s:%d/classes/%s", ServerName,
319	                  LocalPort, value);
320	  cupsdSetString(&p->error_policy, "retry-job");
321	}
322	else
323          p = cupsdAddClass(value);
324
325	p->accepting = 1;
326	p->state     = IPP_PRINTER_IDLE;
327
328        if (!_cups_strcasecmp(line, "<DefaultClass"))
329	  DefaultPrinter = p;
330      }
331      else
332        cupsdLogMessage(CUPSD_LOG_ERROR,
333	                "Syntax error on line %d of classes.conf.", linenum);
334    }
335    else if (!_cups_strcasecmp(line, "</Class>") || !_cups_strcasecmp(line, "</DefaultClass>"))
336    {
337      if (p != NULL)
338      {
339        cupsdSetPrinterAttrs(p);
340        p = NULL;
341      }
342      else
343        cupsdLogMessage(CUPSD_LOG_ERROR,
344	                "Syntax error on line %d of classes.conf.", linenum);
345    }
346    else if (!p)
347    {
348      cupsdLogMessage(CUPSD_LOG_ERROR,
349                      "Syntax error on line %d of classes.conf.", linenum);
350    }
351    else if (!_cups_strcasecmp(line, "UUID"))
352    {
353      if (value && !strncmp(value, "urn:uuid:", 9))
354        cupsdSetString(&(p->uuid), value);
355      else
356        cupsdLogMessage(CUPSD_LOG_ERROR,
357	                "Bad UUID on line %d of classes.conf.", linenum);
358    }
359    else if (!_cups_strcasecmp(line, "AuthInfoRequired"))
360    {
361      if (!cupsdSetAuthInfoRequired(p, value, NULL))
362	cupsdLogMessage(CUPSD_LOG_ERROR,
363			"Bad AuthInfoRequired on line %d of classes.conf.",
364			linenum);
365    }
366    else if (!_cups_strcasecmp(line, "Info"))
367    {
368      if (value)
369        cupsdSetString(&p->info, value);
370    }
371    else if (!_cups_strcasecmp(line, "Location"))
372    {
373      if (value)
374        cupsdSetString(&p->location, value);
375    }
376    else if (!_cups_strcasecmp(line, "Option") && value)
377    {
378     /*
379      * Option name value
380      */
381
382      for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++);
383
384      if (!*valueptr)
385        cupsdLogMessage(CUPSD_LOG_ERROR,
386	                "Syntax error on line %d of classes.conf.", linenum);
387      else
388      {
389        for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0');
390
391        p->num_options = cupsAddOption(value, valueptr, p->num_options,
392	                               &(p->options));
393      }
394    }
395    else if (!_cups_strcasecmp(line, "Printer"))
396    {
397      if (!value)
398      {
399	cupsdLogMessage(CUPSD_LOG_ERROR,
400	                "Syntax error on line %d of classes.conf.", linenum);
401        continue;
402      }
403      else if ((temp = cupsdFindPrinter(value)) == NULL)
404      {
405	cupsdLogMessage(CUPSD_LOG_WARN,
406	                "Unknown printer %s on line %d of classes.conf.",
407	                value, linenum);
408
409       /*
410	* Add the missing remote printer...
411	*/
412
413	if ((temp = cupsdAddPrinter(value)) != NULL)
414	{
415	  cupsdSetString(&temp->make_model, "Remote Printer on unknown");
416
417          temp->state = IPP_PRINTER_STOPPED;
418	  temp->type  |= CUPS_PRINTER_REMOTE;
419
420	  cupsdSetString(&temp->location, "Location Unknown");
421	  cupsdSetString(&temp->info, "No Information Available");
422	  temp->hostname[0] = '\0';
423
424	  cupsdSetPrinterAttrs(temp);
425	}
426      }
427
428      if (temp)
429        cupsdAddPrinterToClass(p, temp);
430    }
431    else if (!_cups_strcasecmp(line, "State"))
432    {
433     /*
434      * Set the initial queue state...
435      */
436
437      if (!_cups_strcasecmp(value, "idle"))
438        p->state = IPP_PRINTER_IDLE;
439      else if (!_cups_strcasecmp(value, "stopped"))
440      {
441        p->state = IPP_PRINTER_STOPPED;
442
443        for (i = 0 ; i < p->num_reasons; i ++)
444	  if (!strcmp("paused", p->reasons[i]))
445	    break;
446
447        if (i >= p->num_reasons &&
448	    p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
449	{
450	  p->reasons[p->num_reasons] = _cupsStrAlloc("paused");
451	  p->num_reasons ++;
452	}
453      }
454      else
455	cupsdLogMessage(CUPSD_LOG_ERROR,
456	                "Syntax error on line %d of classes.conf.",
457	                linenum);
458    }
459    else if (!_cups_strcasecmp(line, "StateMessage"))
460    {
461     /*
462      * Set the initial queue state message...
463      */
464
465      if (value)
466	strlcpy(p->state_message, value, sizeof(p->state_message));
467    }
468    else if (!_cups_strcasecmp(line, "StateTime"))
469    {
470     /*
471      * Set the state time...
472      */
473
474      if (value)
475        p->state_time = atoi(value);
476    }
477    else if (!_cups_strcasecmp(line, "Accepting"))
478    {
479     /*
480      * Set the initial accepting state...
481      */
482
483      if (value &&
484          (!_cups_strcasecmp(value, "yes") ||
485           !_cups_strcasecmp(value, "on") ||
486           !_cups_strcasecmp(value, "true")))
487        p->accepting = 1;
488      else if (value &&
489               (!_cups_strcasecmp(value, "no") ||
490        	!_cups_strcasecmp(value, "off") ||
491        	!_cups_strcasecmp(value, "false")))
492        p->accepting = 0;
493      else
494	cupsdLogMessage(CUPSD_LOG_ERROR,
495	                "Syntax error on line %d of classes.conf.",
496	                linenum);
497    }
498    else if (!_cups_strcasecmp(line, "Shared"))
499    {
500     /*
501      * Set the initial shared state...
502      */
503
504      if (value &&
505          (!_cups_strcasecmp(value, "yes") ||
506           !_cups_strcasecmp(value, "on") ||
507           !_cups_strcasecmp(value, "true")))
508        p->shared = 1;
509      else if (value &&
510               (!_cups_strcasecmp(value, "no") ||
511        	!_cups_strcasecmp(value, "off") ||
512        	!_cups_strcasecmp(value, "false")))
513        p->shared = 0;
514      else
515	cupsdLogMessage(CUPSD_LOG_ERROR,
516	                "Syntax error on line %d of classes.conf.",
517	                linenum);
518    }
519    else if (!_cups_strcasecmp(line, "JobSheets"))
520    {
521     /*
522      * Set the initial job sheets...
523      */
524
525      if (value)
526      {
527	for (valueptr = value;
528	     *valueptr && !isspace(*valueptr & 255);
529	     valueptr ++);
530
531	if (*valueptr)
532          *valueptr++ = '\0';
533
534	cupsdSetString(&p->job_sheets[0], value);
535
536	while (isspace(*valueptr & 255))
537          valueptr ++;
538
539	if (*valueptr)
540	{
541          for (value = valueptr;
542	       *valueptr && !isspace(*valueptr & 255);
543	       valueptr ++);
544
545	  if (*valueptr)
546            *valueptr = '\0';
547
548	  cupsdSetString(&p->job_sheets[1], value);
549	}
550      }
551      else
552	cupsdLogMessage(CUPSD_LOG_ERROR,
553	                "Syntax error on line %d of classes.conf.", linenum);
554    }
555    else if (!_cups_strcasecmp(line, "AllowUser"))
556    {
557      if (value)
558      {
559        p->deny_users = 0;
560        cupsdAddString(&(p->users), value);
561      }
562      else
563	cupsdLogMessage(CUPSD_LOG_ERROR,
564	                "Syntax error on line %d of classes.conf.", linenum);
565    }
566    else if (!_cups_strcasecmp(line, "DenyUser"))
567    {
568      if (value)
569      {
570        p->deny_users = 1;
571        cupsdAddString(&(p->users), value);
572      }
573      else
574	cupsdLogMessage(CUPSD_LOG_ERROR,
575	                "Syntax error on line %d of classes.conf.", linenum);
576    }
577    else if (!_cups_strcasecmp(line, "QuotaPeriod"))
578    {
579      if (value)
580        p->quota_period = atoi(value);
581      else
582	cupsdLogMessage(CUPSD_LOG_ERROR,
583	                "Syntax error on line %d of classes.conf.", linenum);
584    }
585    else if (!_cups_strcasecmp(line, "PageLimit"))
586    {
587      if (value)
588        p->page_limit = atoi(value);
589      else
590	cupsdLogMessage(CUPSD_LOG_ERROR,
591	                "Syntax error on line %d of classes.conf.", linenum);
592    }
593    else if (!_cups_strcasecmp(line, "KLimit"))
594    {
595      if (value)
596        p->k_limit = atoi(value);
597      else
598	cupsdLogMessage(CUPSD_LOG_ERROR,
599	                "Syntax error on line %d of classes.conf.", linenum);
600    }
601    else if (!_cups_strcasecmp(line, "OpPolicy"))
602    {
603      if (value)
604      {
605        cupsd_policy_t *pol;		/* Policy */
606
607
608        if ((pol = cupsdFindPolicy(value)) != NULL)
609	{
610          cupsdSetString(&p->op_policy, value);
611	  p->op_policy_ptr = pol;
612	}
613	else
614	  cupsdLogMessage(CUPSD_LOG_ERROR,
615	                  "Bad policy \"%s\" on line %d of classes.conf",
616			  value, linenum);
617      }
618      else
619	cupsdLogMessage(CUPSD_LOG_ERROR,
620	                "Syntax error on line %d of classes.conf.", linenum);
621    }
622    else if (!_cups_strcasecmp(line, "ErrorPolicy"))
623    {
624      if (value)
625      {
626        if (strcmp(value, "retry-current-job") && strcmp(value, "retry-job"))
627	  cupsdLogMessage(CUPSD_LOG_WARN,
628	                  "ErrorPolicy %s ignored on line %d of classes.conf",
629			  value, linenum);
630      }
631      else
632	cupsdLogMessage(CUPSD_LOG_ERROR,
633	                "Syntax error on line %d of classes.conf.", linenum);
634    }
635    else
636    {
637     /*
638      * Something else we don't understand...
639      */
640
641      cupsdLogMessage(CUPSD_LOG_ERROR,
642                      "Unknown configuration directive %s on line %d of classes.conf.",
643	              line, linenum);
644    }
645  }
646
647  cupsFileClose(fp);
648}
649
650
651/*
652 * 'cupsdSaveAllClasses()' - Save classes to the classes.conf file.
653 */
654
655void
656cupsdSaveAllClasses(void)
657{
658  cups_file_t		*fp;		/* classes.conf file */
659  char			filename[1024],	/* classes.conf filename */
660			temp[1024],	/* Temporary string */
661			value[2048],	/* Value string */
662			*name;		/* Current user name */
663  cupsd_printer_t	*pclass;	/* Current printer class */
664  int			i;		/* Looping var */
665  time_t		curtime;	/* Current time */
666  struct tm		*curdate;	/* Current date */
667  cups_option_t		*option;	/* Current option */
668
669
670 /*
671  * Create the classes.conf file...
672  */
673
674  snprintf(filename, sizeof(filename), "%s/classes.conf", ServerRoot);
675
676  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
677    return;
678
679  cupsdLogMessage(CUPSD_LOG_INFO, "Saving classes.conf...");
680
681 /*
682  * Write a small header to the file...
683  */
684
685  curtime = time(NULL);
686  curdate = localtime(&curtime);
687  strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);
688
689  cupsFilePuts(fp, "# Class configuration file for " CUPS_SVERSION "\n");
690  cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
691  cupsFilePuts(fp, "# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING\n");
692
693 /*
694  * Write each local class known to the system...
695  */
696
697  for (pclass = (cupsd_printer_t *)cupsArrayFirst(Printers);
698       pclass;
699       pclass = (cupsd_printer_t *)cupsArrayNext(Printers))
700  {
701   /*
702    * Skip remote destinations and regular printers...
703    */
704
705    if ((pclass->type & CUPS_PRINTER_REMOTE) ||
706        !(pclass->type & CUPS_PRINTER_CLASS))
707      continue;
708
709   /*
710    * Write printers as needed...
711    */
712
713    if (pclass == DefaultPrinter)
714      cupsFilePrintf(fp, "<DefaultClass %s>\n", pclass->name);
715    else
716      cupsFilePrintf(fp, "<Class %s>\n", pclass->name);
717
718    cupsFilePrintf(fp, "UUID %s\n", pclass->uuid);
719
720    if (pclass->num_auth_info_required > 0)
721    {
722      switch (pclass->num_auth_info_required)
723      {
724        case 1 :
725            strlcpy(value, pclass->auth_info_required[0], sizeof(value));
726	    break;
727
728        case 2 :
729            snprintf(value, sizeof(value), "%s,%s",
730	             pclass->auth_info_required[0],
731		     pclass->auth_info_required[1]);
732	    break;
733
734        case 3 :
735	default :
736            snprintf(value, sizeof(value), "%s,%s,%s",
737	             pclass->auth_info_required[0],
738		     pclass->auth_info_required[1],
739		     pclass->auth_info_required[2]);
740	    break;
741      }
742
743      cupsFilePutConf(fp, "AuthInfoRequired", value);
744    }
745
746    if (pclass->info)
747      cupsFilePutConf(fp, "Info", pclass->info);
748
749    if (pclass->location)
750      cupsFilePutConf(fp, "Location", pclass->location);
751
752    if (pclass->state == IPP_PRINTER_STOPPED)
753      cupsFilePuts(fp, "State Stopped\n");
754    else
755      cupsFilePuts(fp, "State Idle\n");
756
757    cupsFilePrintf(fp, "StateTime %d\n", (int)pclass->state_time);
758
759    if (pclass->accepting)
760      cupsFilePuts(fp, "Accepting Yes\n");
761    else
762      cupsFilePuts(fp, "Accepting No\n");
763
764    if (pclass->shared)
765      cupsFilePuts(fp, "Shared Yes\n");
766    else
767      cupsFilePuts(fp, "Shared No\n");
768
769    snprintf(value, sizeof(value), "%s %s", pclass->job_sheets[0],
770             pclass->job_sheets[1]);
771    cupsFilePutConf(fp, "JobSheets", value);
772
773    for (i = 0; i < pclass->num_printers; i ++)
774      cupsFilePrintf(fp, "Printer %s\n", pclass->printers[i]->name);
775
776    cupsFilePrintf(fp, "QuotaPeriod %d\n", pclass->quota_period);
777    cupsFilePrintf(fp, "PageLimit %d\n", pclass->page_limit);
778    cupsFilePrintf(fp, "KLimit %d\n", pclass->k_limit);
779
780    for (name = (char *)cupsArrayFirst(pclass->users);
781         name;
782	 name = (char *)cupsArrayNext(pclass->users))
783      cupsFilePutConf(fp, pclass->deny_users ? "DenyUser" : "AllowUser", name);
784
785     if (pclass->op_policy)
786      cupsFilePutConf(fp, "OpPolicy", pclass->op_policy);
787    if (pclass->error_policy)
788      cupsFilePutConf(fp, "ErrorPolicy", pclass->error_policy);
789
790    for (i = pclass->num_options, option = pclass->options;
791         i > 0;
792	 i --, option ++)
793    {
794      snprintf(value, sizeof(value), "%s %s", option->name, option->value);
795      cupsFilePutConf(fp, "Option", value);
796    }
797
798    if (pclass == DefaultPrinter)
799      cupsFilePuts(fp, "</DefaultClass>\n");
800    else
801      cupsFilePuts(fp, "</Class>\n");
802  }
803
804  cupsdCloseCreatedConfFile(fp, filename);
805}
806
807
808/*
809 * End of "$Id: classes.c 11791 2014-04-02 16:56:54Z msweet $".
810 */
811