1 /*
2 * Scheduler notification tester for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright 2007-2014 by Apple Inc.
6 * Copyright 2006-2007 by Easy Software Products.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
9 */
10
11 /*
12 * Include necessary headers...
13 */
14
15 #include <cups/cups.h>
16 #include <cups/debug-private.h>
17 #include <cups/string-private.h>
18 #include <signal.h>
19 #include <cups/ipp-private.h> /* TODO: Update so we don't need this */
20
21
22 /*
23 * Local globals...
24 */
25
26 static int terminate = 0;
27
28
29 /*
30 * Local functions...
31 */
32
33 static void print_attributes(ipp_t *ipp, int indent);
34 static void sigterm_handler(int sig);
35 static void usage(void) _CUPS_NORETURN;
36
37
38 /*
39 * 'main()' - Subscribe to the .
40 */
41
42 int
main(int argc,char * argv[])43 main(int argc, /* I - Number of command-line arguments */
44 char *argv[]) /* I - Command-line arguments */
45 {
46 int i; /* Looping var */
47 const char *uri; /* URI to use */
48 int num_events; /* Number of events */
49 const char *events[100]; /* Events */
50 int subscription_id, /* notify-subscription-id */
51 sequence_number, /* notify-sequence-number */
52 interval; /* Interval between polls */
53 http_t *http; /* HTTP connection */
54 ipp_t *request, /* IPP request */
55 *response; /* IPP response */
56 ipp_attribute_t *attr; /* Current attribute */
57 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
58 struct sigaction action; /* Actions for POSIX signals */
59 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
60
61
62 /*
63 * Parse command-line...
64 */
65
66 num_events = 0;
67 uri = NULL;
68
69 for (i = 1; i < argc; i ++)
70 if (!strcmp(argv[i], "-E"))
71 cupsSetEncryption(HTTP_ENCRYPT_REQUIRED);
72 else if (!strcmp(argv[i], "-e"))
73 {
74 i ++;
75 if (i >= argc || num_events >= 100)
76 usage();
77
78 events[num_events] = argv[i];
79 num_events ++;
80 }
81 else if (!strcmp(argv[i], "-h"))
82 {
83 i ++;
84 if (i >= argc)
85 usage();
86
87 cupsSetServer(argv[i]);
88 }
89 else if (uri || strncmp(argv[i], "ipp://", 6))
90 usage();
91 else
92 uri = argv[i];
93
94 if (!uri)
95 usage();
96
97 if (num_events == 0)
98 {
99 events[0] = "all";
100 num_events = 1;
101 }
102
103 /*
104 * Connect to the server...
105 */
106
107 if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
108 cupsEncryption())) == NULL)
109 {
110 perror(cupsServer());
111 return (1);
112 }
113
114 /*
115 * Catch CTRL-C and SIGTERM...
116 */
117
118 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
119 sigset(SIGINT, sigterm_handler);
120 sigset(SIGTERM, sigterm_handler);
121 #elif defined(HAVE_SIGACTION)
122 memset(&action, 0, sizeof(action));
123
124 sigemptyset(&action.sa_mask);
125 action.sa_handler = sigterm_handler;
126 sigaction(SIGINT, &action, NULL);
127 sigaction(SIGTERM, &action, NULL);
128 #else
129 signal(SIGINT, sigterm_handler);
130 signal(SIGTERM, sigterm_handler);
131 #endif /* HAVE_SIGSET */
132
133 /*
134 * Create the subscription...
135 */
136
137 if (strstr(uri, "/jobs/"))
138 {
139 request = ippNewRequest(IPP_CREATE_JOB_SUBSCRIPTION);
140 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
141 }
142 else
143 {
144 request = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION);
145 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
146 uri);
147 }
148
149 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
150 NULL, cupsUser());
151
152 ippAddStrings(request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, "notify-events",
153 num_events, NULL, events);
154 ippAddString(request, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD,
155 "notify-pull-method", NULL, "ippget");
156
157 response = cupsDoRequest(http, request, uri);
158 if (cupsLastError() >= IPP_BAD_REQUEST)
159 {
160 fprintf(stderr, "Create-%s-Subscription: %s\n",
161 strstr(uri, "/jobs") ? "Job" : "Printer", cupsLastErrorString());
162 ippDelete(response);
163 httpClose(http);
164 return (1);
165 }
166
167 if ((attr = ippFindAttribute(response, "notify-subscription-id",
168 IPP_TAG_INTEGER)) == NULL)
169 {
170 fputs("ERROR: No notify-subscription-id in response!\n", stderr);
171 ippDelete(response);
172 httpClose(http);
173 return (1);
174 }
175
176 subscription_id = attr->values[0].integer;
177
178 printf("Create-%s-Subscription: notify-subscription-id=%d\n",
179 strstr(uri, "/jobs/") ? "Job" : "Printer", subscription_id);
180
181 ippDelete(response);
182
183 /*
184 * Monitor for events...
185 */
186
187 sequence_number = 0;
188
189 while (!terminate)
190 {
191 /*
192 * Get the current events...
193 */
194
195 printf("\nGet-Notifications(%d,%d):", subscription_id, sequence_number);
196 fflush(stdout);
197
198 request = ippNewRequest(IPP_GET_NOTIFICATIONS);
199
200 if (strstr(uri, "/jobs/"))
201 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
202 else
203 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
204 uri);
205
206 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
207 "requesting-user-name", NULL, cupsUser());
208
209 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
210 "notify-subscription-ids", subscription_id);
211 if (sequence_number)
212 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
213 "notify-sequence-numbers", sequence_number + 1);
214
215 response = cupsDoRequest(http, request, uri);
216
217 printf(" %s\n", ippErrorString(cupsLastError()));
218
219 if (cupsLastError() >= IPP_BAD_REQUEST)
220 fprintf(stderr, "Get-Notifications: %s\n", cupsLastErrorString());
221 else if (response)
222 {
223 print_attributes(response, 0);
224
225 for (attr = ippFindAttribute(response, "notify-sequence-number",
226 IPP_TAG_INTEGER);
227 attr;
228 attr = ippFindNextAttribute(response, "notify-sequence-number",
229 IPP_TAG_INTEGER))
230 if (attr->values[0].integer > sequence_number)
231 sequence_number = attr->values[0].integer;
232 }
233
234 if ((attr = ippFindAttribute(response, "notify-get-interval",
235 IPP_TAG_INTEGER)) != NULL &&
236 attr->values[0].integer > 0)
237 interval = attr->values[0].integer;
238 else
239 interval = 5;
240
241 ippDelete(response);
242 sleep((unsigned)interval);
243 }
244
245 /*
246 * Cancel the subscription...
247 */
248
249 printf("\nCancel-Subscription:");
250 fflush(stdout);
251
252 request = ippNewRequest(IPP_CANCEL_SUBSCRIPTION);
253
254 if (strstr(uri, "/jobs/"))
255 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
256 else
257 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
258 uri);
259
260 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
261 NULL, cupsUser());
262
263 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER,
264 "notify-subscription-id", subscription_id);
265
266 ippDelete(cupsDoRequest(http, request, uri));
267
268 printf(" %s\n", ippErrorString(cupsLastError()));
269
270 if (cupsLastError() >= IPP_BAD_REQUEST)
271 fprintf(stderr, "Cancel-Subscription: %s\n", cupsLastErrorString());
272
273 /*
274 * Close the connection and return...
275 */
276
277 httpClose(http);
278
279 return (0);
280 }
281
282
283 /*
284 * 'print_attributes()' - Print the attributes in a request...
285 */
286
287 static void
print_attributes(ipp_t * ipp,int indent)288 print_attributes(ipp_t *ipp, /* I - IPP request */
289 int indent) /* I - Indentation */
290 {
291 int i; /* Looping var */
292 ipp_tag_t group; /* Current group */
293 ipp_attribute_t *attr; /* Current attribute */
294 _ipp_value_t *val; /* Current value */
295 static const char * const tags[] = /* Value/group tag strings */
296 {
297 "reserved-00",
298 "operation-attributes-tag",
299 "job-attributes-tag",
300 "end-of-attributes-tag",
301 "printer-attributes-tag",
302 "unsupported-attributes-tag",
303 "subscription-attributes-tag",
304 "event-attributes-tag",
305 "reserved-08",
306 "reserved-09",
307 "reserved-0A",
308 "reserved-0B",
309 "reserved-0C",
310 "reserved-0D",
311 "reserved-0E",
312 "reserved-0F",
313 "unsupported",
314 "default",
315 "unknown",
316 "no-value",
317 "reserved-14",
318 "not-settable",
319 "delete-attr",
320 "admin-define",
321 "reserved-18",
322 "reserved-19",
323 "reserved-1A",
324 "reserved-1B",
325 "reserved-1C",
326 "reserved-1D",
327 "reserved-1E",
328 "reserved-1F",
329 "reserved-20",
330 "integer",
331 "boolean",
332 "enum",
333 "reserved-24",
334 "reserved-25",
335 "reserved-26",
336 "reserved-27",
337 "reserved-28",
338 "reserved-29",
339 "reserved-2a",
340 "reserved-2b",
341 "reserved-2c",
342 "reserved-2d",
343 "reserved-2e",
344 "reserved-2f",
345 "octetString",
346 "dateTime",
347 "resolution",
348 "rangeOfInteger",
349 "begCollection",
350 "textWithLanguage",
351 "nameWithLanguage",
352 "endCollection",
353 "reserved-38",
354 "reserved-39",
355 "reserved-3a",
356 "reserved-3b",
357 "reserved-3c",
358 "reserved-3d",
359 "reserved-3e",
360 "reserved-3f",
361 "reserved-40",
362 "textWithoutLanguage",
363 "nameWithoutLanguage",
364 "reserved-43",
365 "keyword",
366 "uri",
367 "uriScheme",
368 "charset",
369 "naturalLanguage",
370 "mimeMediaType",
371 "memberName"
372 };
373
374
375 for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next)
376 {
377 if ((attr->group_tag == IPP_TAG_ZERO && indent <= 8) || !attr->name)
378 {
379 group = IPP_TAG_ZERO;
380 putchar('\n');
381 continue;
382 }
383
384 if (group != attr->group_tag)
385 {
386 group = attr->group_tag;
387
388 putchar('\n');
389 for (i = 4; i < indent; i ++)
390 putchar(' ');
391
392 printf("%s:\n\n", tags[group]);
393 }
394
395 for (i = 0; i < indent; i ++)
396 putchar(' ');
397
398 printf("%s (", attr->name);
399 if (attr->num_values > 1)
400 printf("1setOf ");
401 printf("%s):", tags[attr->value_tag]);
402
403 switch (attr->value_tag)
404 {
405 case IPP_TAG_ENUM :
406 case IPP_TAG_INTEGER :
407 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
408 printf(" %d", val->integer);
409 putchar('\n');
410 break;
411
412 case IPP_TAG_BOOLEAN :
413 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
414 printf(" %s", val->boolean ? "true" : "false");
415 putchar('\n');
416 break;
417
418 case IPP_TAG_RANGE :
419 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
420 printf(" %d-%d", val->range.lower, val->range.upper);
421 putchar('\n');
422 break;
423
424 case IPP_TAG_DATE :
425 {
426 char vstring[256]; /* Formatted time */
427
428 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
429 printf(" (%s)", _cupsStrDate(vstring, sizeof(vstring), ippDateToTime(val->date)));
430 }
431 putchar('\n');
432 break;
433
434 case IPP_TAG_RESOLUTION :
435 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
436 printf(" %dx%d%s", val->resolution.xres, val->resolution.yres,
437 val->resolution.units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
438 putchar('\n');
439 break;
440
441 case IPP_TAG_STRING :
442 case IPP_TAG_TEXTLANG :
443 case IPP_TAG_NAMELANG :
444 case IPP_TAG_TEXT :
445 case IPP_TAG_NAME :
446 case IPP_TAG_KEYWORD :
447 case IPP_TAG_URI :
448 case IPP_TAG_URISCHEME :
449 case IPP_TAG_CHARSET :
450 case IPP_TAG_LANGUAGE :
451 case IPP_TAG_MIMETYPE :
452 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
453 printf(" \"%s\"", val->string.text);
454 putchar('\n');
455 break;
456
457 case IPP_TAG_BEGIN_COLLECTION :
458 putchar('\n');
459
460 for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
461 {
462 if (i)
463 putchar('\n');
464 print_attributes(val->collection, indent + 4);
465 }
466 break;
467
468 default :
469 printf("UNKNOWN (%d values)\n", attr->num_values);
470 break;
471 }
472 }
473 }
474
475
476 /*
477 * 'sigterm_handler()' - Flag when the user hits CTRL-C...
478 */
479
480 static void
sigterm_handler(int sig)481 sigterm_handler(int sig) /* I - Signal number (unused) */
482 {
483 (void)sig;
484
485 terminate = 1;
486 }
487
488
489 /*
490 * 'usage()' - Show program usage...
491 */
492
493 static void
usage(void)494 usage(void)
495 {
496 puts("Usage: testsub [-E] [-e event ... -e eventN] [-h hostname] URI");
497 exit(0);
498 }
499