1/* 2 * "$Id: rss.c 12131 2014-08-28 23:38:16Z msweet $" 3 * 4 * RSS notifier for CUPS. 5 * 6 * Copyright 2007-2014 by Apple Inc. 7 * Copyright 2007 by Easy Software Products. 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 <cups/cups.h> 21#include <sys/stat.h> 22#include <cups/language.h> 23#include <cups/string-private.h> 24#include <cups/array.h> 25#include <sys/select.h> 26#include <cups/ipp-private.h> /* TODO: Update so we don't need this */ 27 28 29/* 30 * Structures... 31 */ 32 33typedef struct _cups_rss_s /**** RSS message data ****/ 34{ 35 int sequence_number; /* notify-sequence-number */ 36 char *subject, /* Message subject/summary */ 37 *text, /* Message text */ 38 *link_url; /* Link to printer */ 39 time_t event_time; /* When the event occurred */ 40} _cups_rss_t; 41 42 43/* 44 * Local globals... 45 */ 46 47static char *rss_password; /* Password for remote RSS */ 48 49 50/* 51 * Local functions... 52 */ 53 54static int compare_rss(_cups_rss_t *a, _cups_rss_t *b); 55static void delete_message(_cups_rss_t *rss); 56static void load_rss(cups_array_t *rss, const char *filename); 57static _cups_rss_t *new_message(int sequence_number, char *subject, 58 char *text, char *link_url, 59 time_t event_time); 60static const char *password_cb(const char *prompt); 61static int save_rss(cups_array_t *rss, const char *filename, 62 const char *baseurl); 63static char *xml_escape(const char *s); 64 65 66/* 67 * 'main()' - Main entry for the test notifier. 68 */ 69 70int /* O - Exit status */ 71main(int argc, /* I - Number of command-line arguments */ 72 char *argv[]) /* I - Command-line arguments */ 73{ 74 int i; /* Looping var */ 75 ipp_t *event; /* Event from scheduler */ 76 ipp_state_t state; /* IPP event state */ 77 char scheme[32], /* URI scheme ("rss") */ 78 username[256], /* Username for remote RSS */ 79 host[1024], /* Hostname for remote RSS */ 80 resource[1024], /* RSS file */ 81 *options; /* Options */ 82 int port, /* Port number for remote RSS */ 83 max_events; /* Maximum number of events */ 84 http_t *http; /* Connection to remote server */ 85 http_status_t status; /* HTTP GET/PUT status code */ 86 char filename[1024], /* Local filename */ 87 newname[1024]; /* filename.N */ 88 cups_lang_t *language; /* Language information */ 89 ipp_attribute_t *printer_up_time, /* Timestamp on event */ 90 *notify_sequence_number,/* Sequence number */ 91 *notify_printer_uri; /* Printer URI */ 92 char *subject, /* Subject for notification message */ 93 *text, /* Text for notification message */ 94 link_url[1024], /* Link to printer */ 95 link_scheme[32], /* Scheme for link */ 96 link_username[256], /* Username for link */ 97 link_host[1024], /* Host for link */ 98 link_resource[1024]; /* Resource for link */ 99 int link_port; /* Link port */ 100 cups_array_t *rss; /* RSS message array */ 101 _cups_rss_t *msg; /* RSS message */ 102 char baseurl[1024]; /* Base URL */ 103 fd_set input; /* Input set for select() */ 104 struct timeval timeout; /* Timeout for select() */ 105 int changed; /* Has the RSS data changed? */ 106 int exit_status; /* Exit status */ 107 108 109 fprintf(stderr, "DEBUG: argc=%d\n", argc); 110 for (i = 0; i < argc; i ++) 111 fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]); 112 113 /* 114 * See whether we are publishing this RSS feed locally or remotely... 115 */ 116 117 if (httpSeparateURI(HTTP_URI_CODING_ALL, argv[1], scheme, sizeof(scheme), 118 username, sizeof(username), host, sizeof(host), &port, 119 resource, sizeof(resource)) < HTTP_URI_OK) 120 { 121 fprintf(stderr, "ERROR: Bad RSS URI \"%s\"!\n", argv[1]); 122 return (1); 123 } 124 125 max_events = 20; 126 127 if ((options = strchr(resource, '?')) != NULL) 128 { 129 *options++ = '\0'; 130 131 if (!strncmp(options, "max_events=", 11)) 132 { 133 max_events = atoi(options + 11); 134 135 if (max_events <= 0) 136 max_events = 20; 137 } 138 } 139 140 rss = cupsArrayNew((cups_array_func_t)compare_rss, NULL); 141 142 if (host[0]) 143 { 144 /* 145 * Remote feed, see if we can get the current file... 146 */ 147 148 int fd; /* Temporary file */ 149 150 151 if ((rss_password = strchr(username, ':')) != NULL) 152 *rss_password++ = '\0'; 153 154 cupsSetPasswordCB(password_cb); 155 cupsSetUser(username); 156 157 if ((fd = cupsTempFd(filename, sizeof(filename))) < 0) 158 { 159 fprintf(stderr, "ERROR: Unable to create temporary file: %s\n", 160 strerror(errno)); 161 162 return (1); 163 } 164 165 if ((http = httpConnect(host, port)) == NULL) 166 { 167 fprintf(stderr, "ERROR: Unable to connect to %s on port %d: %s\n", 168 host, port, strerror(errno)); 169 170 close(fd); 171 unlink(filename); 172 173 return (1); 174 } 175 176 status = cupsGetFd(http, resource, fd); 177 178 close(fd); 179 180 if (status != HTTP_OK && status != HTTP_NOT_FOUND) 181 { 182 fprintf(stderr, "ERROR: Unable to GET %s from %s on port %d: %d %s\n", 183 resource, host, port, status, httpStatus(status)); 184 185 httpClose(http); 186 unlink(filename); 187 188 return (1); 189 } 190 191 strlcpy(newname, filename, sizeof(newname)); 192 193 httpAssembleURI(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http", 194 NULL, host, port, resource); 195 } 196 else 197 { 198 const char *cachedir, /* CUPS_CACHEDIR */ 199 *server_name, /* SERVER_NAME */ 200 *server_port; /* SERVER_PORT */ 201 202 203 http = NULL; 204 205 if ((cachedir = getenv("CUPS_CACHEDIR")) == NULL) 206 cachedir = CUPS_CACHEDIR; 207 208 if ((server_name = getenv("SERVER_NAME")) == NULL) 209 server_name = "localhost"; 210 211 if ((server_port = getenv("SERVER_PORT")) == NULL) 212 server_port = "631"; 213 214 snprintf(filename, sizeof(filename), "%s/rss%s", cachedir, resource); 215 snprintf(newname, sizeof(newname), "%s.N", filename); 216 217 httpAssembleURIf(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http", 218 NULL, server_name, atoi(server_port), "/rss%s", resource); 219 } 220 221 /* 222 * Load the previous RSS file, if any... 223 */ 224 225 load_rss(rss, filename); 226 227 changed = cupsArrayCount(rss) == 0; 228 229 /* 230 * Localize for the user's chosen language... 231 */ 232 233 language = cupsLangDefault(); 234 235 /* 236 * Read events and update the RSS file until we are out of events. 237 */ 238 239 for (exit_status = 0, event = NULL;;) 240 { 241 if (changed) 242 { 243 /* 244 * Save the messages to the file again, uploading as needed... 245 */ 246 247 if (save_rss(rss, newname, baseurl)) 248 { 249 if (http) 250 { 251 /* 252 * Upload the RSS file... 253 */ 254 255 if ((status = cupsPutFile(http, resource, filename)) != HTTP_CREATED) 256 fprintf(stderr, "ERROR: Unable to PUT %s from %s on port %d: %d %s\n", 257 resource, host, port, status, httpStatus(status)); 258 } 259 else 260 { 261 /* 262 * Move the new RSS file over top the old one... 263 */ 264 265 if (rename(newname, filename)) 266 fprintf(stderr, "ERROR: Unable to rename %s to %s: %s\n", 267 newname, filename, strerror(errno)); 268 } 269 270 changed = 0; 271 } 272 } 273 274 /* 275 * Wait up to 30 seconds for an event... 276 */ 277 278 timeout.tv_sec = 30; 279 timeout.tv_usec = 0; 280 281 FD_ZERO(&input); 282 FD_SET(0, &input); 283 284 if (select(1, &input, NULL, NULL, &timeout) < 0) 285 continue; 286 else if (!FD_ISSET(0, &input)) 287 { 288 fprintf(stderr, "DEBUG: %s is bored, exiting...\n", argv[1]); 289 break; 290 } 291 292 /* 293 * Read the next event... 294 */ 295 296 event = ippNew(); 297 while ((state = ippReadFile(0, event)) != IPP_DATA) 298 { 299 if (state <= IPP_IDLE) 300 break; 301 } 302 303 if (state == IPP_ERROR) 304 fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr); 305 306 if (state <= IPP_IDLE) 307 break; 308 309 /* 310 * Collect the info from the event... 311 */ 312 313 printer_up_time = ippFindAttribute(event, "printer-up-time", 314 IPP_TAG_INTEGER); 315 notify_sequence_number = ippFindAttribute(event, "notify-sequence-number", 316 IPP_TAG_INTEGER); 317 notify_printer_uri = ippFindAttribute(event, "notify-printer-uri", 318 IPP_TAG_URI); 319 subject = cupsNotifySubject(language, event); 320 text = cupsNotifyText(language, event); 321 322 if (printer_up_time && notify_sequence_number && subject && text) 323 { 324 /* 325 * Create a new RSS message... 326 */ 327 328 if (notify_printer_uri) 329 { 330 httpSeparateURI(HTTP_URI_CODING_ALL, 331 notify_printer_uri->values[0].string.text, 332 link_scheme, sizeof(link_scheme), 333 link_username, sizeof(link_username), 334 link_host, sizeof(link_host), &link_port, 335 link_resource, sizeof(link_resource)); 336 httpAssembleURI(HTTP_URI_CODING_ALL, link_url, sizeof(link_url), 337 "http", link_username, link_host, link_port, 338 link_resource); 339 } 340 341 msg = new_message(notify_sequence_number->values[0].integer, 342 xml_escape(subject), xml_escape(text), 343 notify_printer_uri ? xml_escape(link_url) : NULL, 344 printer_up_time->values[0].integer); 345 346 if (!msg) 347 { 348 fprintf(stderr, "ERROR: Unable to create message: %s\n", 349 strerror(errno)); 350 exit_status = 1; 351 break; 352 } 353 354 /* 355 * Add it to the array... 356 */ 357 358 cupsArrayAdd(rss, msg); 359 360 changed = 1; 361 362 /* 363 * Trim the array as needed... 364 */ 365 366 while (cupsArrayCount(rss) > max_events) 367 { 368 msg = cupsArrayFirst(rss); 369 370 cupsArrayRemove(rss, msg); 371 372 delete_message(msg); 373 } 374 } 375 376 if (subject) 377 free(subject); 378 379 if (text) 380 free(text); 381 382 ippDelete(event); 383 event = NULL; 384 } 385 386 /* 387 * We only get here when idle or error... 388 */ 389 390 ippDelete(event); 391 392 if (http) 393 { 394 unlink(filename); 395 httpClose(http); 396 } 397 398 return (exit_status); 399} 400 401 402/* 403 * 'compare_rss()' - Compare two messages. 404 */ 405 406static int /* O - Result of comparison */ 407compare_rss(_cups_rss_t *a, /* I - First message */ 408 _cups_rss_t *b) /* I - Second message */ 409{ 410 return (a->sequence_number - b->sequence_number); 411} 412 413 414/* 415 * 'delete_message()' - Free all memory used by a message. 416 */ 417 418static void 419delete_message(_cups_rss_t *msg) /* I - RSS message */ 420{ 421 if (msg->subject) 422 free(msg->subject); 423 424 if (msg->text) 425 free(msg->text); 426 427 if (msg->link_url) 428 free(msg->link_url); 429 430 free(msg); 431} 432 433 434/* 435 * 'load_rss()' - Load an existing RSS feed file. 436 */ 437 438static void 439load_rss(cups_array_t *rss, /* I - RSS messages */ 440 const char *filename) /* I - File to load */ 441{ 442 FILE *fp; /* File pointer */ 443 char line[4096], /* Line from file */ 444 *subject, /* Subject */ 445 *text, /* Text */ 446 *link_url, /* Link URL */ 447 *start, /* Start of element */ 448 *end; /* End of element */ 449 time_t event_time; /* Event time */ 450 int sequence_number; /* Sequence number */ 451 int in_item; /* In an item */ 452 _cups_rss_t *msg; /* New message */ 453 454 455 if ((fp = fopen(filename, "r")) == NULL) 456 { 457 if (errno != ENOENT) 458 fprintf(stderr, "ERROR: Unable to open %s: %s\n", filename, 459 strerror(errno)); 460 461 return; 462 } 463 464 subject = NULL; 465 text = NULL; 466 link_url = NULL; 467 event_time = 0; 468 sequence_number = 0; 469 in_item = 0; 470 471 while (fgets(line, sizeof(line), fp)) 472 { 473 if (strstr(line, "<item>")) 474 in_item = 1; 475 else if (strstr(line, "</item>") && in_item) 476 { 477 if (subject && text) 478 { 479 msg = new_message(sequence_number, subject, text, link_url, 480 event_time); 481 482 if (msg) 483 cupsArrayAdd(rss, msg); 484 485 } 486 else 487 { 488 if (subject) 489 free(subject); 490 491 if (text) 492 free(text); 493 494 if (link_url) 495 free(link_url); 496 } 497 498 subject = NULL; 499 text = NULL; 500 link_url = NULL; 501 event_time = 0; 502 sequence_number = 0; 503 in_item = 0; 504 } 505 else if (!in_item) 506 continue; 507 else if ((start = strstr(line, "<title>")) != NULL) 508 { 509 start += 7; 510 if ((end = strstr(start, "</title>")) != NULL) 511 { 512 *end = '\0'; 513 subject = strdup(start); 514 } 515 } 516 else if ((start = strstr(line, "<description>")) != NULL) 517 { 518 start += 13; 519 if ((end = strstr(start, "</description>")) != NULL) 520 { 521 *end = '\0'; 522 text = strdup(start); 523 } 524 } 525 else if ((start = strstr(line, "<link>")) != NULL) 526 { 527 start += 6; 528 if ((end = strstr(start, "</link>")) != NULL) 529 { 530 *end = '\0'; 531 link_url = strdup(start); 532 } 533 } 534 else if ((start = strstr(line, "<pubDate>")) != NULL) 535 { 536 start += 9; 537 if ((end = strstr(start, "</pubDate>")) != NULL) 538 { 539 *end = '\0'; 540 event_time = httpGetDateTime(start); 541 } 542 } 543 else if ((start = strstr(line, "<guid>")) != NULL) 544 sequence_number = atoi(start + 6); 545 } 546 547 if (subject) 548 free(subject); 549 550 if (text) 551 free(text); 552 553 if (link_url) 554 free(link_url); 555 556 fclose(fp); 557} 558 559 560/* 561 * 'new_message()' - Create a new RSS message. 562 */ 563 564static _cups_rss_t * /* O - New message */ 565new_message(int sequence_number, /* I - notify-sequence-number */ 566 char *subject, /* I - Subject/summary */ 567 char *text, /* I - Text */ 568 char *link_url, /* I - Link to printer */ 569 time_t event_time) /* I - Date/time of event */ 570{ 571 _cups_rss_t *msg; /* New message */ 572 573 574 if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL) 575 return (NULL); 576 577 msg->sequence_number = sequence_number; 578 msg->subject = subject; 579 msg->text = text; 580 msg->link_url = link_url; 581 msg->event_time = event_time; 582 583 return (msg); 584} 585 586 587/* 588 * 'password_cb()' - Return the cached password. 589 */ 590 591static const char * /* O - Cached password */ 592password_cb(const char *prompt) /* I - Prompt string, unused */ 593{ 594 (void)prompt; 595 596 return (rss_password); 597} 598 599 600/* 601 * 'save_rss()' - Save messages to a RSS file. 602 */ 603 604static int /* O - 1 on success, 0 on failure */ 605save_rss(cups_array_t *rss, /* I - RSS messages */ 606 const char *filename, /* I - File to save to */ 607 const char *baseurl) /* I - Base URL */ 608{ 609 FILE *fp; /* File pointer */ 610 _cups_rss_t *msg; /* Current message */ 611 char date[1024]; /* Current date */ 612 char *href; /* Escaped base URL */ 613 614 615 if ((fp = fopen(filename, "w")) == NULL) 616 { 617 fprintf(stderr, "ERROR: Unable to create %s: %s\n", filename, 618 strerror(errno)); 619 return (0); 620 } 621 622 fchmod(fileno(fp), 0644); 623 624 fputs("<?xml version=\"1.0\"?>\n", fp); 625 fputs("<rss version=\"2.0\">\n", fp); 626 fputs(" <channel>\n", fp); 627 fputs(" <title>CUPS RSS Feed</title>\n", fp); 628 629 href = xml_escape(baseurl); 630 fprintf(fp, " <link>%s</link>\n", href); 631 free(href); 632 633 fputs(" <description>CUPS RSS Feed</description>\n", fp); 634 fputs(" <generator>" CUPS_SVERSION "</generator>\n", fp); 635 fputs(" <ttl>1</ttl>\n", fp); 636 637 fprintf(fp, " <pubDate>%s</pubDate>\n", 638 httpGetDateString2(time(NULL), date, sizeof(date))); 639 640 for (msg = (_cups_rss_t *)cupsArrayLast(rss); 641 msg; 642 msg = (_cups_rss_t *)cupsArrayPrev(rss)) 643 { 644 fputs(" <item>\n", fp); 645 fprintf(fp, " <title>%s</title>\n", msg->subject); 646 fprintf(fp, " <description>%s</description>\n", msg->text); 647 if (msg->link_url) 648 fprintf(fp, " <link>%s</link>\n", msg->link_url); 649 fprintf(fp, " <pubDate>%s</pubDate>\n", 650 httpGetDateString2(msg->event_time, date, sizeof(date))); 651 fprintf(fp, " <guid>%d</guid>\n", msg->sequence_number); 652 fputs(" </item>\n", fp); 653 } 654 655 fputs(" </channel>\n", fp); 656 fputs("</rss>\n", fp); 657 658 return (!fclose(fp)); 659} 660 661 662/* 663 * 'xml_escape()' - Copy a string, escaping &, <, and > as needed. 664 */ 665 666static char * /* O - Escaped string */ 667xml_escape(const char *s) /* I - String to escape */ 668{ 669 char *e, /* Escaped string */ 670 *eptr; /* Pointer into escaped string */ 671 const char *sptr; /* Pointer into string */ 672 size_t bytes; /* Bytes needed for string */ 673 674 675 /* 676 * First figure out how many extra bytes we need... 677 */ 678 679 for (bytes = 0, sptr = s; *sptr; sptr ++) 680 if (*sptr == '&') 681 bytes += 4; /* & */ 682 else if (*sptr == '<' || *sptr == '>') 683 bytes += 3; /* < and > */ 684 685 /* 686 * If there is nothing to escape, just strdup() it... 687 */ 688 689 if (bytes == 0) 690 return (strdup(s)); 691 692 /* 693 * Otherwise allocate memory and copy... 694 */ 695 696 if ((e = malloc(bytes + 1 + strlen(s))) == NULL) 697 return (NULL); 698 699 for (eptr = e, sptr = s; *sptr; sptr ++) 700 if (*sptr == '&') 701 { 702 *eptr++ = '&'; 703 *eptr++ = 'a'; 704 *eptr++ = 'm'; 705 *eptr++ = 'p'; 706 *eptr++ = ';'; 707 } 708 else if (*sptr == '<') 709 { 710 *eptr++ = '&'; 711 *eptr++ = 'l'; 712 *eptr++ = 't'; 713 *eptr++ = ';'; 714 } 715 else if (*sptr == '>') 716 { 717 *eptr++ = '&'; 718 *eptr++ = 'g'; 719 *eptr++ = 't'; 720 *eptr++ = ';'; 721 } 722 else 723 *eptr++ = *sptr; 724 725 *eptr = '\0'; 726 727 return (e); 728} 729 730 731/* 732 * End of "$Id: rss.c 12131 2014-08-28 23:38:16Z msweet $". 733 */ 734