]>
Commit | Line | Data |
---|---|---|
ef416fc2 | 1 | /* |
f2d18633 | 2 | * "$Id$" |
ef416fc2 | 3 | * |
71e16022 | 4 | * "mailto" notifier for CUPS. |
ef416fc2 | 5 | * |
e60ec91f | 6 | * Copyright 2007-2011 by Apple Inc. |
ef416fc2 | 7 | * Copyright 1997-2005 by Easy Software Products. |
8 | * | |
9 | * These coded instructions, statements, and computer programs are the | |
bc44d920 | 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/". | |
ef416fc2 | 14 | * |
15 | * Contents: | |
16 | * | |
fa73b229 | 17 | * main() - Main entry for the mailto notifier. |
18 | * email_message() - Email a notification message. | |
19 | * load_configuration() - Load the mailto.conf file. | |
20 | * pipe_sendmail() - Open a pipe to sendmail... | |
21 | * print_attributes() - Print the attributes in a request... | |
ef416fc2 | 22 | */ |
23 | ||
24 | /* | |
25 | * Include necessary headers... | |
26 | */ | |
27 | ||
e60ec91f | 28 | #include <cups/cups-private.h> |
fa73b229 | 29 | #include <sys/wait.h> |
30 | #include <signal.h> | |
31 | ||
32 | ||
33 | /* | |
34 | * Globals... | |
35 | */ | |
36 | ||
37 | char mailtoCc[1024]; /* Cc email address */ | |
38 | char mailtoFrom[1024]; /* From email address */ | |
39 | char mailtoReplyTo[1024]; /* Reply-To email address */ | |
40 | char mailtoSubject[1024]; /* Subject prefix */ | |
41 | char mailtoSMTPServer[1024]; /* SMTP server to use */ | |
42 | char mailtoSendmail[1024]; /* Sendmail program to use */ | |
43 | ||
44 | ||
45 | /* | |
46 | * Local functions... | |
47 | */ | |
48 | ||
49 | void email_message(const char *to, const char *subject, | |
50 | const char *text); | |
51 | int load_configuration(void); | |
52 | cups_file_t *pipe_sendmail(const char *to); | |
53 | void print_attributes(ipp_t *ipp, int indent); | |
ef416fc2 | 54 | |
55 | ||
56 | /* | |
57 | * 'main()' - Main entry for the mailto notifier. | |
58 | */ | |
59 | ||
60 | int /* O - Exit status */ | |
61 | main(int argc, /* I - Number of command-line arguments */ | |
62 | char *argv[]) /* I - Command-line arguments */ | |
63 | { | |
fa73b229 | 64 | int i; /* Looping var */ |
65 | ipp_t *msg; /* Event message from scheduler */ | |
66 | ipp_state_t state; /* IPP event state */ | |
67 | char *subject, /* Subject for notification message */ | |
68 | *text; /* Text for notification message */ | |
69 | cups_lang_t *lang; /* Language info */ | |
70 | char temp[1024]; /* Temporary string */ | |
71 | int templen; /* Length of temporary string */ | |
72 | #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET) | |
73 | struct sigaction action; /* POSIX sigaction data */ | |
74 | #endif /* HAVE_SIGACTION && !HAVE_SIGSET */ | |
75 | ||
76 | ||
77 | /* | |
78 | * Don't buffer stderr... | |
79 | */ | |
80 | ||
81 | setbuf(stderr, NULL); | |
82 | ||
83 | /* | |
84 | * Ignore SIGPIPE signals... | |
85 | */ | |
86 | ||
87 | #ifdef HAVE_SIGSET | |
88 | sigset(SIGPIPE, SIG_IGN); | |
89 | #elif defined(HAVE_SIGACTION) | |
90 | memset(&action, 0, sizeof(action)); | |
91 | action.sa_handler = SIG_IGN; | |
92 | sigaction(SIGPIPE, &action, NULL); | |
93 | #else | |
94 | signal(SIGPIPE, SIG_IGN); | |
95 | #endif /* HAVE_SIGSET */ | |
96 | ||
97 | /* | |
98 | * Validate command-line options... | |
99 | */ | |
100 | ||
101 | if (argc != 3) | |
102 | { | |
103 | fputs("Usage: mailto mailto:user@domain.com notify-user-data\n", stderr); | |
104 | return (1); | |
105 | } | |
106 | ||
107 | if (strncmp(argv[1], "mailto:", 7)) | |
108 | { | |
109 | fprintf(stderr, "ERROR: Bad recipient \"%s\"!\n", argv[1]); | |
110 | return (1); | |
111 | } | |
112 | ||
113 | fprintf(stderr, "DEBUG: argc=%d\n", argc); | |
114 | for (i = 0; i < argc; i ++) | |
115 | fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]); | |
116 | ||
117 | /* | |
118 | * Load configuration data... | |
119 | */ | |
120 | ||
121 | if ((lang = cupsLangDefault()) == NULL) | |
122 | return (1); | |
123 | ||
124 | if (!load_configuration()) | |
125 | return (1); | |
126 | ||
127 | /* | |
128 | * Get the reply-to address... | |
129 | */ | |
130 | ||
131 | templen = sizeof(temp); | |
132 | httpDecode64_2(temp, &templen, argv[2]); | |
133 | ||
134 | if (!strncmp(temp, "mailto:", 7)) | |
135 | strlcpy(mailtoReplyTo, temp + 7, sizeof(mailtoReplyTo)); | |
136 | else if (temp[0]) | |
137 | fprintf(stderr, "WARNING: Bad notify-user-data value (%d bytes) ignored!\n", | |
138 | templen); | |
139 | ||
140 | /* | |
141 | * Loop forever until we run out of events... | |
142 | */ | |
143 | ||
144 | for (;;) | |
145 | { | |
146 | /* | |
147 | * Get the next event... | |
148 | */ | |
149 | ||
150 | msg = ippNew(); | |
151 | while ((state = ippReadFile(0, msg)) != IPP_DATA) | |
152 | { | |
153 | if (state <= IPP_IDLE) | |
154 | break; | |
155 | } | |
156 | ||
157 | fprintf(stderr, "DEBUG: state=%d\n", state); | |
158 | ||
159 | if (state == IPP_ERROR) | |
160 | fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr); | |
161 | ||
162 | if (state <= IPP_IDLE) | |
163 | { | |
164 | /* | |
165 | * Out of messages, free memory and then exit... | |
166 | */ | |
167 | ||
168 | ippDelete(msg); | |
169 | return (0); | |
170 | } | |
171 | ||
172 | /* | |
173 | * Get the subject and text for the message, then email it... | |
174 | */ | |
175 | ||
176 | subject = cupsNotifySubject(lang, msg); | |
177 | text = cupsNotifyText(lang, msg); | |
178 | ||
179 | fprintf(stderr, "DEBUG: subject=\"%s\"\n", subject); | |
180 | fprintf(stderr, "DEBUG: text=\"%s\"\n", text); | |
181 | ||
182 | if (subject && text) | |
183 | email_message(argv[1] + 7, subject, text); | |
184 | else | |
185 | { | |
186 | fputs("ERROR: Missing attributes in event notification!\n", stderr); | |
187 | print_attributes(msg, 4); | |
188 | } | |
189 | ||
190 | /* | |
191 | * Free the memory used for this event... | |
192 | */ | |
193 | ||
194 | if (subject) | |
195 | free(subject); | |
196 | ||
197 | if (text) | |
198 | free(text); | |
199 | ||
200 | ippDelete(msg); | |
201 | } | |
202 | } | |
203 | ||
204 | ||
205 | /* | |
206 | * 'email_message()' - Email a notification message. | |
207 | */ | |
208 | ||
209 | void | |
210 | email_message(const char *to, /* I - Recipient of message */ | |
211 | const char *subject, /* I - Subject of message */ | |
212 | const char *text) /* I - Text of message */ | |
213 | { | |
214 | cups_file_t *fp; /* Pipe/socket to mail server */ | |
215 | const char *nl; /* Newline to use */ | |
216 | char response[1024]; /* SMTP response buffer */ | |
217 | ||
218 | ||
219 | /* | |
220 | * Connect to the mail server... | |
221 | */ | |
222 | ||
223 | if (mailtoSendmail[0]) | |
224 | { | |
225 | /* | |
226 | * Use the sendmail command... | |
227 | */ | |
228 | ||
229 | fp = pipe_sendmail(to); | |
230 | ||
231 | if (!fp) | |
232 | return; | |
233 | ||
234 | nl = "\n"; | |
235 | } | |
236 | else | |
237 | { | |
238 | /* | |
239 | * Use an SMTP server... | |
240 | */ | |
241 | ||
242 | char hostbuf[1024]; /* Local hostname */ | |
243 | ||
244 | ||
245 | if (strchr(mailtoSMTPServer, ':')) | |
246 | fp = cupsFileOpen(mailtoSMTPServer, "s"); | |
247 | else | |
248 | { | |
249 | char spec[1024]; /* Host:service spec */ | |
250 | ||
251 | ||
252 | snprintf(spec, sizeof(spec), "%s:smtp", mailtoSMTPServer); | |
253 | fp = cupsFileOpen(spec, "s"); | |
254 | } | |
255 | ||
256 | if (!fp) | |
257 | { | |
258 | fprintf(stderr, "ERROR: Unable to connect to SMTP server \"%s\"!\n", | |
259 | mailtoSMTPServer); | |
260 | return; | |
261 | } | |
262 | ||
263 | fprintf(stderr, "DEBUG: Connected to \"%s\"...\n", mailtoSMTPServer); | |
264 | ||
757d2cad | 265 | cupsFilePrintf(fp, "HELO %s\r\n", |
266 | httpGetHostname(NULL, hostbuf, sizeof(hostbuf))); | |
fa73b229 | 267 | fprintf(stderr, "DEBUG: >>> HELO %s\n", hostbuf); |
268 | ||
269 | if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) | |
270 | goto smtp_error; | |
271 | fprintf(stderr, "DEBUG: <<< %s\n", response); | |
272 | ||
273 | cupsFilePrintf(fp, "MAIL FROM:%s\r\n", mailtoFrom); | |
274 | fprintf(stderr, "DEBUG: >>> MAIL FROM:%s\n", mailtoFrom); | |
275 | ||
276 | if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) | |
277 | goto smtp_error; | |
278 | fprintf(stderr, "DEBUG: <<< %s\n", response); | |
279 | ||
280 | cupsFilePrintf(fp, "RCPT TO:%s\r\n", to); | |
281 | fprintf(stderr, "DEBUG: >>> RCPT TO:%s\n", to); | |
282 | ||
283 | if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) | |
284 | goto smtp_error; | |
285 | fprintf(stderr, "DEBUG: <<< %s\n", response); | |
286 | ||
287 | cupsFilePuts(fp, "DATA\r\n"); | |
288 | fputs("DEBUG: DATA\n", stderr); | |
289 | ||
290 | if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) | |
291 | goto smtp_error; | |
292 | fprintf(stderr, "DEBUG: <<< %s\n", response); | |
293 | ||
294 | nl = "\r\n"; | |
295 | } | |
296 | ||
297 | /* | |
298 | * Send the message... | |
299 | */ | |
300 | ||
301 | cupsFilePrintf(fp, "Date: %s%s", httpGetDateString(time(NULL)), nl); | |
302 | cupsFilePrintf(fp, "From: %s%s", mailtoFrom, nl); | |
303 | cupsFilePrintf(fp, "Subject: %s %s%s", mailtoSubject, subject, nl); | |
304 | if (mailtoReplyTo[0]) | |
305 | { | |
306 | cupsFilePrintf(fp, "Sender: %s%s", mailtoReplyTo, nl); | |
307 | cupsFilePrintf(fp, "Reply-To: %s%s", mailtoReplyTo, nl); | |
308 | } | |
309 | cupsFilePrintf(fp, "To: %s%s", to, nl); | |
310 | if (mailtoCc[0]) | |
311 | cupsFilePrintf(fp, "Cc: %s%s", mailtoCc, nl); | |
312 | cupsFilePrintf(fp, "Content-Type: text/plain%s", nl); | |
313 | cupsFilePuts(fp, nl); | |
314 | cupsFilePrintf(fp, "%s%s", text, nl); | |
ae71f5de | 315 | cupsFilePrintf(fp, ".%s", nl); |
fa73b229 | 316 | |
317 | /* | |
318 | * Close the connection to the mail server... | |
319 | */ | |
320 | ||
321 | if (mailtoSendmail[0]) | |
322 | { | |
323 | /* | |
324 | * Close the pipe and wait for the sendmail command to finish... | |
325 | */ | |
326 | ||
327 | int status; /* Exit status */ | |
328 | ||
329 | ||
330 | cupsFileClose(fp); | |
331 | ||
e60ec91f MS |
332 | while (wait(&status)) |
333 | { | |
334 | if (errno != EINTR) | |
335 | { | |
336 | fprintf(stderr, "DEBUG: Unable to get child status: %s\n", | |
337 | strerror(errno)); | |
338 | status = 0; | |
339 | break; | |
340 | } | |
341 | } | |
fa73b229 | 342 | |
343 | /* | |
344 | * Report any non-zero status... | |
345 | */ | |
346 | ||
347 | if (status) | |
348 | { | |
349 | if (WIFEXITED(status)) | |
350 | fprintf(stderr, "ERROR: Sendmail command returned status %d!\n", | |
351 | WEXITSTATUS(status)); | |
352 | else | |
353 | fprintf(stderr, "ERROR: Sendmail command crashed on signal %d!\n", | |
354 | WTERMSIG(status)); | |
355 | } | |
356 | } | |
357 | else | |
358 | { | |
359 | /* | |
360 | * Finish up the SMTP submission and close the connection... | |
361 | */ | |
362 | ||
363 | if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) | |
364 | goto smtp_error; | |
365 | fprintf(stderr, "DEBUG: <<< %s\n", response); | |
366 | ||
367 | /* | |
368 | * Process SMTP errors here... | |
369 | */ | |
370 | ||
371 | smtp_error: | |
372 | ||
373 | cupsFilePuts(fp, "QUIT\r\n"); | |
374 | fputs("DEBUG: QUIT\n", stderr); | |
375 | ||
376 | if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500) | |
c7017ecc MS |
377 | fprintf(stderr, "ERROR: Got \"%s\" trying to QUIT connection.\n", |
378 | response); | |
379 | else | |
380 | fprintf(stderr, "DEBUG: <<< %s\n", response); | |
fa73b229 | 381 | |
382 | cupsFileClose(fp); | |
383 | ||
384 | fprintf(stderr, "DEBUG: Closed connection to \"%s\"...\n", | |
385 | mailtoSMTPServer); | |
386 | } | |
387 | } | |
388 | ||
389 | ||
390 | /* | |
391 | * 'load_configuration()' - Load the mailto.conf file. | |
392 | */ | |
393 | ||
394 | int /* I - 1 on success, 0 on failure */ | |
395 | load_configuration(void) | |
396 | { | |
397 | cups_file_t *fp; /* mailto.conf file */ | |
398 | const char *server_root, /* CUPS_SERVERROOT environment variable */ | |
399 | *server_admin; /* SERVER_ADMIN environment variable */ | |
400 | char line[1024], /* Line from file */ | |
401 | *value; /* Value for directive */ | |
402 | int linenum; /* Line number in file */ | |
403 | ||
404 | ||
405 | /* | |
406 | * Initialize defaults... | |
407 | */ | |
408 | ||
409 | mailtoCc[0] = '\0'; | |
410 | ||
411 | if ((server_admin = getenv("SERVER_ADMIN")) != NULL) | |
412 | strlcpy(mailtoFrom, server_admin, sizeof(mailtoFrom)); | |
413 | else | |
414 | snprintf(mailtoFrom, sizeof(mailtoFrom), "root@%s", | |
757d2cad | 415 | httpGetHostname(NULL, line, sizeof(line))); |
fa73b229 | 416 | |
417 | strlcpy(mailtoSendmail, "/usr/sbin/sendmail", sizeof(mailtoSendmail)); | |
418 | ||
419 | mailtoSMTPServer[0] = '\0'; | |
420 | ||
421 | mailtoSubject[0] = '\0'; | |
422 | ||
423 | /* | |
424 | * Try loading the config file... | |
425 | */ | |
426 | ||
427 | if ((server_root = getenv("CUPS_SERVERROOT")) == NULL) | |
428 | server_root = CUPS_SERVERROOT; | |
429 | ||
430 | snprintf(line, sizeof(line), "%s/mailto.conf", server_root); | |
431 | ||
432 | if ((fp = cupsFileOpen(line, "r")) == NULL) | |
433 | { | |
e60ec91f MS |
434 | if (errno != ENOENT) |
435 | { | |
436 | fprintf(stderr, "ERROR: Unable to open \"%s\" - %s\n", line, | |
437 | strerror(errno)); | |
438 | return (1); | |
439 | } | |
440 | else | |
441 | return (0); | |
fa73b229 | 442 | } |
443 | ||
444 | linenum = 0; | |
445 | ||
446 | while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) | |
447 | { | |
448 | if (!value) | |
449 | { | |
450 | fprintf(stderr, "ERROR: No value found for %s directive on line %d!\n", | |
451 | line, linenum); | |
452 | cupsFileClose(fp); | |
453 | return (0); | |
454 | } | |
455 | ||
88f9aafc | 456 | if (!_cups_strcasecmp(line, "Cc")) |
fa73b229 | 457 | strlcpy(mailtoCc, value, sizeof(mailtoCc)); |
88f9aafc | 458 | else if (!_cups_strcasecmp(line, "From")) |
fa73b229 | 459 | strlcpy(mailtoFrom, value, sizeof(mailtoFrom)); |
88f9aafc | 460 | else if (!_cups_strcasecmp(line, "Sendmail")) |
fa73b229 | 461 | { |
462 | strlcpy(mailtoSendmail, value, sizeof(mailtoSendmail)); | |
463 | mailtoSMTPServer[0] = '\0'; | |
464 | } | |
88f9aafc | 465 | else if (!_cups_strcasecmp(line, "SMTPServer")) |
fa73b229 | 466 | { |
467 | mailtoSendmail[0] = '\0'; | |
468 | strlcpy(mailtoSMTPServer, value, sizeof(mailtoSMTPServer)); | |
469 | } | |
88f9aafc | 470 | else if (!_cups_strcasecmp(line, "Subject")) |
fa73b229 | 471 | strlcpy(mailtoSubject, value, sizeof(mailtoSubject)); |
472 | else | |
473 | { | |
474 | fprintf(stderr, | |
475 | "ERROR: Unknown configuration directive \"%s\" on line %d!\n", | |
476 | line, linenum); | |
477 | } | |
478 | } | |
479 | ||
480 | /* | |
481 | * Close file and return... | |
482 | */ | |
483 | ||
484 | cupsFileClose(fp); | |
485 | ||
486 | return (1); | |
487 | } | |
488 | ||
489 | ||
490 | /* | |
491 | * 'pipe_sendmail()' - Open a pipe to sendmail... | |
492 | */ | |
493 | ||
494 | cups_file_t * /* O - CUPS file */ | |
495 | pipe_sendmail(const char *to) /* I - To: address */ | |
496 | { | |
497 | cups_file_t *fp; /* CUPS file */ | |
498 | int pid; /* Process ID */ | |
499 | int pipefds[2]; /* Pipe file descriptors */ | |
500 | int argc; /* Number of arguments */ | |
501 | char *argv[100], /* Argument array */ | |
502 | line[1024], /* Sendmail command + args */ | |
503 | *lineptr; /* Pointer into line */ | |
504 | ||
505 | ||
506 | /* | |
507 | * First break the mailtoSendmail string into arguments... | |
508 | */ | |
509 | ||
510 | strlcpy(line, mailtoSendmail, sizeof(line)); | |
511 | argv[0] = line; | |
512 | argc = 1; | |
513 | ||
514 | for (lineptr = strchr(line, ' '); lineptr; lineptr = strchr(lineptr, ' ')) | |
515 | { | |
516 | while (*lineptr == ' ') | |
517 | *lineptr++ = '\0'; | |
518 | ||
519 | if (*lineptr) | |
520 | { | |
521 | /* | |
522 | * Point to the next argument... | |
523 | */ | |
524 | ||
525 | argv[argc ++] = lineptr; | |
526 | ||
527 | /* | |
528 | * Stop if we have too many... | |
529 | */ | |
530 | ||
531 | if (argc >= (int)(sizeof(argv) / sizeof(argv[0]) - 2)) | |
532 | break; | |
533 | } | |
534 | } | |
535 | ||
536 | argv[argc ++] = (char *)to; | |
537 | argv[argc] = NULL; | |
538 | ||
539 | /* | |
540 | * Create the pipe... | |
541 | */ | |
542 | ||
543 | if (pipe(pipefds)) | |
544 | { | |
545 | perror("ERROR: Unable to create pipe"); | |
546 | return (NULL); | |
547 | } | |
548 | ||
549 | /* | |
550 | * Then run the command... | |
551 | */ | |
552 | ||
553 | if ((pid = fork()) == 0) | |
554 | { | |
555 | /* | |
556 | * Child goes here - redirect stdin to the input side of the pipe, | |
557 | * redirect stdout to stderr, and exec... | |
558 | */ | |
559 | ||
560 | close(0); | |
561 | dup(pipefds[0]); | |
562 | ||
563 | close(1); | |
564 | dup(2); | |
565 | ||
566 | close(pipefds[0]); | |
567 | close(pipefds[1]); | |
568 | ||
569 | execvp(argv[0], argv); | |
570 | exit(errno); | |
571 | } | |
572 | else if (pid < 0) | |
573 | { | |
574 | /* | |
575 | * Unable to fork - error out... | |
576 | */ | |
577 | ||
578 | perror("ERROR: Unable to fork command"); | |
579 | ||
580 | close(pipefds[0]); | |
581 | close(pipefds[1]); | |
582 | ||
583 | return (NULL); | |
584 | } | |
585 | ||
586 | /* | |
587 | * Create a CUPS file using the output side of the pipe and close the | |
588 | * input side... | |
589 | */ | |
590 | ||
591 | close(pipefds[0]); | |
592 | ||
593 | if ((fp = cupsFileOpenFd(pipefds[1], "w")) == NULL) | |
594 | { | |
595 | int status; /* Status of command */ | |
596 | ||
597 | ||
598 | close(pipefds[1]); | |
599 | wait(&status); | |
600 | } | |
601 | ||
602 | return (fp); | |
603 | } | |
604 | ||
605 | ||
606 | /* | |
607 | * 'print_attributes()' - Print the attributes in a request... | |
608 | */ | |
609 | ||
610 | void | |
611 | print_attributes(ipp_t *ipp, /* I - IPP request */ | |
612 | int indent) /* I - Indentation */ | |
613 | { | |
fa73b229 | 614 | ipp_tag_t group; /* Current group */ |
615 | ipp_attribute_t *attr; /* Current attribute */ | |
e60ec91f | 616 | char buffer[1024]; /* Value buffer */ |
fa73b229 | 617 | |
618 | ||
619 | for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next) | |
620 | { | |
621 | if ((attr->group_tag == IPP_TAG_ZERO && indent <= 8) || !attr->name) | |
622 | { | |
623 | group = IPP_TAG_ZERO; | |
624 | fputc('\n', stderr); | |
625 | continue; | |
626 | } | |
627 | ||
628 | if (group != attr->group_tag) | |
629 | { | |
630 | group = attr->group_tag; | |
631 | ||
e60ec91f | 632 | fprintf(stderr, "DEBUG: %*s%s:\n\n", indent - 4, "", ippTagString(group)); |
fa73b229 | 633 | } |
634 | ||
a2326b5b | 635 | ippAttributeString(attr, buffer, sizeof(buffer)); |
fa73b229 | 636 | |
e60ec91f MS |
637 | fprintf(stderr, "DEBUG: %*s%s (%s%s) %s", indent, "", attr->name, |
638 | attr->num_values > 1 ? "1setOf " : "", | |
639 | ippTagString(attr->value_tag), buffer); | |
fa73b229 | 640 | } |
ef416fc2 | 641 | } |
642 | ||
643 | ||
644 | /* | |
f2d18633 | 645 | * End of "$Id$". |
ef416fc2 | 646 | */ |