diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml new file mode 100644 index 0000000000..e25107416b --- /dev/null +++ b/.github/workflows/auto-update.yml @@ -0,0 +1,42 @@ +name: Push new tag update to stable branch + +on: + schedule: + - cron: '9 7 * * *' + workflow_dispatch: + inputs: + workflow_choice: + description: "Choose YAML to update" + required: true + default: "both" + type: choice + options: + - snapcraft + - rockcraft + - both + +jobs: + update-yamls: + runs-on: ubuntu-latest + steps: + - name: Checkout this repo + uses: actions/checkout@v3 + + - name: Run desktop-snaps action (Snapcraft) + if: ${{ github.event_name == 'schedule' || github.event.inputs.workflow_choice == 'snapcraft' || github.event.inputs.workflow_choice == 'both' }} + uses: ubuntu/desktop-snaps@stable + with: + token: ${{ secrets.GITHUB_TOKEN }} + repo: ${{ github.repository }} + version-schema: 'v(\d+\.\d+\.\d+)' + yaml-path: 'packaging/snapcraft.yaml' + + - name: Run desktop-snaps action (Rockcraft) + if: ${{ github.event_name == 'schedule' || github.event.inputs.workflow_choice == 'rockcraft' || github.event.inputs.workflow_choice == 'both' }} + uses: ubuntu/desktop-snaps@stable + with: + token: ${{ secrets.GITHUB_TOKEN }} + repo: ${{ github.repository }} + rock-version-schema: 'v(\d+\.\d+\.\d+)' + yaml-path: 'packaging/rockcraft.yaml' + readme-path: packaging/README.md \ No newline at end of file diff --git a/.github/workflows/packages-ci.yml b/.github/workflows/packages-ci.yml new file mode 100644 index 0000000000..5fde0f0b10 --- /dev/null +++ b/.github/workflows/packages-ci.yml @@ -0,0 +1,146 @@ +name: CI Pipeline for Containerized CUPS + +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master + workflow_dispatch: + +jobs: + build-rock: + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Pack with Rockcraft + uses: canonical/craft-actions/rockcraft-pack@main + id: rockcraft + with: + path: packaging + + - name: Upload Rock Artifact + uses: actions/upload-artifact@v4 + with: + name: cups-rock + path: ${{ steps.rockcraft.outputs.rock }} + + build-snap: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build Snap Package + uses: snapcore/action-build@v1 + id: snapcraft + with: + path: packaging + + test: + needs: build-rock + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download Rock Artifact + uses: actions/download-artifact@v4 + with: + name: cups-rock + + - name: Install Dependencies for Testing + run: | + sudo snap install rockcraft --classic + sudo snap install docker + sudo apt-get update + sudo apt-get install -y avahi-utils + sudo apt-get install -y curl cups-ipp-utils cups-client screen + + - name: Ensure Docker Daemon is Running + run: | + # Start and enable Docker daemon if not already active + sudo systemctl start docker + sudo systemctl enable docker + sudo systemctl is-active --quiet docker || sudo systemctl start docker + + - name: Build Docker Image + run: | + # Build Docker image from the latest .rock artifact + ROCK="$(ls *.rock | tail -n 1)" + sudo rockcraft.skopeo --insecure-policy copy oci-archive:"${ROCK}" docker-daemon:cups:latest + + - name: Run CUPS container + env: + CUPS_ADMIN: "print" + CUPS_PASSWORD: "print" + run: | + # Run CUPS container with environment variables for admin credentials + sudo docker run --rm -d --name cups -p 8080:631 \ + -e CUPS_ADMIN="${CUPS_ADMIN}" -e CUPS_PASSWORD="${CUPS_PASSWORD}" \ + cups:latest + + - name: Wait for CUPS to be ready + run: | + # Wait until CUPS server is ready to accept connections + until curl -s http://localhost:8080 | grep -q 'CUPS'; do + echo "Waiting for CUPS..." + sleep 5 + done + + - name: Start ippeveprinter + run: | + # Start the ippeveprinter service for printing + screen -S ippeveprinter-session -d -m /usr/sbin/ippeveprinter -f application/pdf myprinter + sleep 10 + + - name: Add CUPS printer + env: + CUPS_ADMIN: "print" + run: | + # Add a test printer to the CUPS server + sudo docker exec -u "${CUPS_ADMIN}" cups lpadmin -p testprinter \ + -v ipps://myprinter._ipps._tcp.local/ -E -m everywhere + + - name: Test printing files + env: + CUPS_ADMIN: "print" + run: | + # Test printing a file from the host to the CUPS server + echo "Test Print Job" | sudo tee host-testfile.txt > /dev/null + CUPS_SERVER=localhost:8080 lp -d testprinter host-testfile.txt + + # Wait for the final print job to complete + until CUPS_SERVER=localhost:8080 lpstat -W completed | grep -q 'testprinter-1'; do + echo "Waiting for print job 1 to complete..." + sleep 5 + done + CUPS_SERVER=localhost:8080 lpstat -W completed | grep 'testprinter-1' + + # Test printing multiple file types from inside container and wait for completion + testfiles=( + "/share/cups/ipptool/testfile.txt" + "/share/cups/ipptool/testfile.pdf" + "/share/cups/ipptool/testfile.jpg" + "/share/cups/ipptool/testfile.ps" + "/share/cups/ipptool/testfile.pcl" + ) + + for i in "${!testfiles[@]}"; do + sudo docker exec -u "${CUPS_ADMIN}" cups lp -d testprinter "${testfiles[i]}" + until sudo docker exec -u "${CUPS_ADMIN}" cups lpstat -W completed | grep -q "testprinter-$((i+2))"; do + echo "Waiting for print job $((i+2)) to complete..." + sleep 5 + done + sudo docker exec -u "${CUPS_ADMIN}" cups lpstat -W completed | grep "testprinter-$((i+2))" + done + + - name: Stop CUPS container + run: | + sudo docker stop cups \ No newline at end of file diff --git a/.github/workflows/registry-actions.yml b/.github/workflows/registry-actions.yml new file mode 100644 index 0000000000..a77b8d0f9f --- /dev/null +++ b/.github/workflows/registry-actions.yml @@ -0,0 +1,121 @@ +name: Pack and Publish OCI Image to Docker Registry and GitHub packaging + +on: + push: + branches: + - main + - master + workflow_dispatch: + inputs: + workflow_choice: + description: "Choose Release Channel" + required: true + default: "edge" + type: choice + options: + - edge + - stable + - both + workflow_run: + workflows: ["Push new tag update to stable branch"] + types: + - completed + +jobs: + build-rock: + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Pack with Rockcraft + uses: canonical/craft-actions/rockcraft-pack@main + id: rockcraft + with: + path: packaging + + - name: Upload Rock Artifact + uses: actions/upload-artifact@v4 + with: + name: cups-rock + path: ${{ steps.rockcraft.outputs.rock }} + + publish-rock: + needs: build-rock + if: github.ref_name == 'main' || github.ref_name == 'master' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download Rock Artifact + uses: actions/download-artifact@v4 + with: + name: cups-rock + + - name: Install Dependencies + run: | + sudo snap install rockcraft --classic + sudo snap install docker + sudo snap install yq + + - name: Ensure Docker Daemon is Running + run: | + sudo systemctl start docker + sudo systemctl enable docker + sudo systemctl is-active --quiet docker || sudo systemctl start docker + + # - name: Log in to Docker Hub + # uses: docker/login-action@v3.2.0 + # with: + # username: ${{ secrets.DOCKER_USERNAME }} + # password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to GitHub Packages + uses: docker/login-action@v3.2.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and Push Docker Image (Edge & Latest Channel) + if: github.event.inputs.workflow_choice == 'edge' || github.event.inputs.workflow_choice == 'both' || github.event_name == 'push' || github.event_name == 'workflow_run' + env: + USERNAME: ${{ secrets.DOCKER_USERNAME }} + ORG: ${{ github.repository_owner }} + run: | + IMAGE="$(yq '.name' packaging/rockcraft.yaml)" + VERSION="$(yq '.version' packaging/rockcraft.yaml)" + ROCK="$(ls *.rock | tail -n 1)" + ORG_NAME=$(echo "${ORG}" | tr '[:upper:]' '[:lower:]') + sudo rockcraft.skopeo --insecure-policy copy oci-archive:"${ROCK}" docker-daemon:"${ORG_NAME}/${IMAGE}:${VERSION}-edge" + # Push to Docker Hub + # docker tag ${ORG_NAME}/${IMAGE}:${VERSION}-edge ${USERNAME}:${VERSION}-edge + # docker push ${USERNAME}/${IMAGE}:${VERSION}-edge + # docker tag ${USERNAME}/${IMAGE}:${VERSION}-edge ${USERNAME}/${IMAGE}:latest + # docker push ${USERNAME}/${IMAGE}:latest + # Push to GitHub Packages + GITHUB_IMAGE="ghcr.io/${ORG_NAME}/${IMAGE}" + docker tag ${ORG_NAME}/${IMAGE}:${VERSION}-edge ${GITHUB_IMAGE}:${VERSION}-edge + docker push ${GITHUB_IMAGE}:${VERSION}-edge + docker tag ${GITHUB_IMAGE}:${VERSION}-edge ${GITHUB_IMAGE}:latest + docker push ${GITHUB_IMAGE}:latest + + - name: Build and Push Docker Image (Stable Channel) + if: github.event.inputs.workflow_choice == 'stable' || github.event.inputs.workflow_choice == 'both' + env: + USERNAME: ${{ secrets.DOCKER_USERNAME }} + ORG: ${{ github.repository_owner }} + run: | + IMAGE="$(yq '.name' packaging/rockcraft.yaml)" + VERSION="$(yq '.version' packaging/rockcraft.yaml)" + ROCK="$(ls *.rock | tail -n 1)" + ORG_NAME=$(echo "${ORG}" | tr '[:upper:]' '[:lower:]') + sudo rockcraft.skopeo --insecure-policy copy oci-archive:"${ROCK}" docker-daemon:"${ORG_NAME}/${IMAGE}:${VERSION}-stable" + # Push to Docker Hub + # docker tag ${ORG_NAME}/${IMAGE}:${VERSION}-stable ${USERNAME}:${VERSION}-stable + # docker push ${USERNAME}/${IMAGE}:${VERSION}-stable + # Push to GitHub Packages + GITHUB_IMAGE="ghcr.io/${ORG_NAME}/${IMAGE}" + docker tag ${ORG_NAME}/${IMAGE}:${VERSION}-stable ${GITHUB_IMAGE}:${VERSION}-stable + docker push ${GITHUB_IMAGE}:${VERSION}-stable \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a0215bd9e..3c7fad4b89 100644 --- a/.gitignore +++ b/.gitignore @@ -165,4 +165,11 @@ /xcode/CUPS.xcodeproj/project.xcworkspace/ /xcode/CUPS.xcodeproj/xcuserdata/ .DS_store -container-config +/container-config +/packaging/parts +/packaging/stage +/packaging/prime +*.snap +*.tar* +/packaging/snap/.snapcraft +*.rock \ No newline at end of file diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000000..82613bea34 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,516 @@ +# OpenPrinting CUPS: Snap, OCI Image, EPM, and RPM Packages + +## Overview +This repository provides a complete CUPS printing stack in multiple formats: +- **Snap Package:** For snap-based or classic Linux systems. +- **OCI Image (Docker/Rock):** For immutable Linux distributions and Docker-based environments. +- **EPM and RPM Packages**: For binary distributions suitable for various Linux distributions. + +## Links +- [CUPS in the Snap Store](https://snapcraft.io/cups) +- [CUPS in Docker Hub]() (Link to be updated) +- [All OpenPrinting Snaps](https://snapcraft.io/search?q=OpenPrinting) + +--- + +## CUPS Snap Package + +### Introduction + +This is a complete printing stack in a Snap. It contains not only CUPS but also cups-filters, Ghostscript, and Poppler (the two latter as PostScript and PDF interpreters). This is everything (except printer-model-specific drivers) which is needed for printing. + +This Snap is designed for the following three use cases: + +1. Providing a printing stack for a purely Snap-based operating system, like Ubuntu Core. +2. Providing a printing stack for a classic Linux system, installing the Snap instead of the system's usual printing packages. It is planned that Ubuntu Desktop switches over to use CUPS from this Snap. +3. Being a proxy/firewall between application Snaps which print and the system's classically installed (DEB, RPM, ...) CUPS, to block administrative CUPS requests. + +Note that this Snap is still under development and therefore there are probably still many bugs. + + +### Installation and Usage + +The CUPS Snap works on both classic systems (standard Linux distributions like Ubuntu Desktop) and purely snap-based systems (like Ubuntu Core). + +If on your classic system there is already CUPS running, the CUPS Snap's cups-daemon works as a proxy to protect the system's CUPS daemon against adminstrative requests from applications from the Snap Store (proxy mode). For this the CUPS Snap will get automatically installed as soon as a Snap which prints is installed from the Snap Store. Then it does its job fully automatically without any configuration (like creating queues) needed by the user. + +For developemnt purposes the CUPS Snap's CUPS daemon can also be run as an independent second daemon in parallel to the system's CUPS, on an alternative port and domain socket (parallel mode). Note that running two independent CUPS instances on one system is not recommended on production systems. It is error-prone and confusing for users. This mode is also not well tested. Disable or remove your system's CUPS if you want to use the Snap's CUPS as your standard CUPS. + +Usually if you decide to manually install this Snap, you want to use it as the standard CUPS for your system. Therefore we tell here how to disable the system's original, classically installed CUPS and run the CUPS Snap as standard CUPS (stand-alone mode). + + +#### Stand-alone mode + +**NOTE: The CUPS Snap does not support classic printer drivers (consisting of filters and PPD files, usually installed as DEB or RPM packages), but only Printer Applications (see below, links to Printer Applications: "Printer Applications" under "Discussion and Links")** + +If the Snap's CUPS runs alone, the standard resources, port 631 and `(/var)/run/cups/cups.sock` are used to assure maximum compatibility with both snapped and classically installed client applications. + +To assure that the CUPS Snap will be the only CUPS running on your system and you have a systemd-based system (like Ubuntu), run +``` +sudo systemctl mask cups-browsed +sudo systemctl stop cups-browsed +sudo systemctl mask cups +sudo systemctl mask cups.socket +sudo systemctl stop cups +``` +and either +``` +sudo mv /etc/cups /etc/cups.old +``` +or +``` +sudo touch /var/snap/cups/common/no-proxy +``` +before downloading and installing the CUPS Snap. + +The `stop` commands stop the daemons immediately, the `mask` commands exclude them from being started during boot (use `unmask` to restore). The `mv` command makes the system's CUPS completely invisible to the CUPS Snap, to prevent the Snap from entering proxy mode. The alternative `touch` command creates a file to make the CUPS Snap suppress its proxy mode altogether (remove the file to re-enable proxy mode), meaning that, as before, we get stand-alone mode if no classic cupsd is running and parallel mode otherwise. + +The Snap is available in the Edge channel of the Snap Store and from there one can install it via + +``` +snap install --edge cups +``` + +If you want to install from source, you have to build it via + +``` +snapcraft snap +``` + +or if this does not work on your system with + +``` +snapcraft cleanbuild +``` + +and install it with the command + +``` +sudo snap install --dangerous .snap +``` + +with `.snap` being the name of the snap file. + +For maximum compatibility with most snapped and unsnapped applications this Snap's CUPS will be accessible through the usual socket /run/cups/cups.sock and port 631. + +To use use the snap's command line utilities acting on the snap's CUPS, preceed the commands with `cups.`: +``` +cups.lpstat -H +cups.lpstat -v +cups.cupsctl +cups.lpadmin -p printer -E -v file:/dev/null +``` +You can run administrative commands without `sudo` and without getting asked for a password if you are member of the "lpadmin" group if it exists on your system, otherwise if you are member of the "adm" group (this is the case for the first user created on a classic Ubuntu system). This works on classic systems (you can also add a user to the "lpadmin" or "adm" group) but not on snap-based systems (the standard user is not in the "adm" group and you cannot add users to the "adm" group). You can always run administrative programs as root (for example running them with `sudo`). + +The snap's command line utilities can only access files in the calling user's home directory if they are not hidden (name begins with a dot '`.`'). So you can usually print with a command like +``` +cups.lp -d +``` +For hidden files you have to pipe the file into the command, like with +``` +cat | cups.lp -d +``` +or copy or rename the file into a standard file. + +The web interface can be accessed under +``` +http://localhost:631/ +``` +To make administrative tasks working, you have to enter user name and password of a user in the "lpadmin" or "adm" group, or "root" and the root password if your system is configured appropriately. + +You can also use the standard CUPS utilities installed on your system when the CUPS Snap is in stand-alone mode as then its CUPS daemon listens on the standard domain and port. + + +#### Proxy mode + +Only if there is already a CUPS instance running on your system (like one installed via classic Debian or RPM packages), the Snap's CUPS will run in a proxy mode which makes the CUPS Snap's CUPS daemon run in parallel to the system's classic CUPS daemon to serve as a proxy for snapped applications to prevent those applications from performing administrative tasks (like modifying print queues) on the system's CUPS. + +If you have an application which prints in a Snap and such a Snap is installed from the Snap Store, the Snap's "cups" plug is auto-connected to the appropriate slot of the CUPS Snap, and if the CUPS Snap is not installed, it gets installed automatically, going into stand-alone mode if the system has no CUPS installed and into proxy mode otherwise, fully automatically without need of user intervention. The snapped application can only print to the snapped CUPS, never to a classically installed system CUPS, and the snapped CUPS only allows it to list queues, jobs, and printer options, and to print, not to do admninistrative tasks as creating or modifying queues. + +If there is a classic CUPS installed and therefore the snapped CUPS is in proxy mode, the snapped CUPS mirrors all print queues of the classic CUPS and so the snapped application sees the same printers as an unsnapped application which directly accesses the system's CUPS. The mirrored queues have exactly the same printer options and jobs are passed unfiltered to the system's CUPS where the user's drivers, especially classic and proprietary drivers which are not available as Printer Applications, convert the data to the printer's language and send the jobs off to the physical printers. + +Even discovered IPP printers for which the system's CUPS creates temporary queues on-demand are mirrored, but as permanent queues on the snapped CUPS. + +The configuration of the system's CUPS does not need to be changed by the user for that, nor is it changed by the CUPS Snap (the CUPS Snap does no administrative action on the system's classic CUPS at all). The printers of the system's CUPS do not even need to get shared for the proxy to work. If unsnapped applications on the local machine can print, the proxy works. + +All this goes fully automatically. The user does not need to do anything with the CUPS Snap in proxy mode. All printing administration he performs on the system's CUPS. + + +#### Parallel mode + +**NOTE: Using this mode is not reconmmended on production systems. It is not well tested and error-prone. Two independent CUPS daemons on one system can also easily confuse users.** + +You can also run the CUPS daemon of the CUPS Snap as an independent second CUPS daemon in parallel to the system's classically installed CUPS daemon. This is for development purposes to test the interaction of the two CUPS daemons and their attached cups-browsed instances. + +This mode does not get invoked automatically. To activate it, you have to create a file to tell the Snap to use this mode instead of proxy mode if there is already a classically installed CUPS. Run + +``` +sudo touch /var/snap/cups/common/no-proxy +``` +and then restart the Snap with +``` +sudo snap stop cups +sudo snap start cups +``` +to switch into parallel mode. Remove the file and restart the CUPS Snap again to get back to proxy mode. Note that proxy mode removes all print queues you have created in parallel mode. + +In this mode the Snap's CUPS will run on port 10631 (instead of port 631) and it will use the domain socket `/var/snap/cups/common/run/cups.sock` (instead of the standard `(/var)/run/cups/cups.sock`). + +The Snap's utilities (names prefixed with `cups.`) will access the Snap's CUPS, the system's utilities (no prefixes) will access the system's CUPS. + +The web interface is available under +``` +http://localhost:10631/ +``` + +You can also access the snap's CUPS with the system's utilities by specifying the server (example if the snap's CUPS runs in parallel with a system's one, on port 10631 and /var/snap/cups/common/run/cups.sock): +``` +lpstat -h localhost:10631 -v +lpstat -h /var/snap/cups/common/run/cups.sock -v +``` + +For the **very rare** case that you want to do administrative tasks on your +system's classically installed CUPS using the utilities which come with the +CUPS Snap, you need to manually connect the utilities' "cups-internal" +interface: +``` +sudo snap connect cups:cups-internal cups:cups-control +``` + + +#### cups-browsed + +cups-browsed is automatically started together with CUPS in both stand-alone and parallel mode. So queues to discovered IPP printers and shared queues on remote CUPS servers get automatically created. + + +#### Configuration files and logs + +Independent in which mode the CUPS Snap is running, the configuration files are in the +``` +/var/snap/cups/common/etc/cups +``` +directory. They can be edited. To make sure you chnges do not get overwritten by CUPS and to get your changes activated, stop the CUPS Snap before editing +``` +sudo snap stop cups +``` +and after editing start it again: +``` +sudo snap start cups +``` + +Note that some items in the configuration files cannot be changed and get overwritten by the CUPS Snap when it is started. These settings are required for CUPS being able to run in the confinements of a Snap. + +Log files you find in +``` +/var/snap/cups/current/var/log +``` +The CUPS Snap is set to debug mode by default, so you have verbose logs for CUPS (`error_log`), cups-browsed (`cups-browsed_log`, stand-alone and parallel mode), and cups-proxyd (`cups-proxyd_log`, proxy mode). + + +### What is planned/still missing? + +- Spin out cups-browsed into a separate Snap +- Add a script for easy migration from a classically installed CUPS to the + CUPS Snap + + +### Printer drivers deprecated -> Printer Applications + +Printer drivers (printer-model-specific software and/or data) in the form of filters and PPD files added to CUPS are deprecated. They get replaced by Printer Applications, simple daemons which emulate a driverless IPP printer on localhost and do the filtering of the incoming jobs and connection to the printer. These daemons will also be packaged in Snaps, to make the driver packages distribution-independent. + +Therefore we will not add a printer driver interface as it is not needed any more. + +Note that this Snap DOES NOT support classic printer drivers! + +Nearly all free software printer drivers (at least the ones for which there are packages in the Debian Linux distribution) are available in Printer Application Snaps now. See the section "Printer Applications" under "Discussion and Links" below. + +There is a Printer Application for [PostScript](https://snapcraft.io/ps-printer-app), [HPLIP](https://snapcraft.io/hplip-printer-app), and [Gutenprint](https://snapcraft.io/gutenprint-printer-app). All the other drivers are joined together in the [GhostScript Printer Application](https://snapcraft.io/ghostscript-printer-app). + +See also the [overview in the Snap Store](https://snapcraft.io/search?q=OpenPrinting). + +In addition, there is also the [Legacy Printer Application](https://github.com/OpenPrinting/pappl-retrofit#legacy-printer-application) contained in [pappl-retrofit](https://github.com/OpenPrinting/pappl-retrofit) which makes all drivers classically installed for the system's classically installed CUPS available in a Printer Application and this way for the CUPS Snap. It is especially helpful for drivers which are not (yet) available as Printer Application. Note that this Printer Application cannot be provided as a Snap as it otherwise had no access to the drivers. + +If your printer is a driverless IPP printer (AirPrint, Mopria, IPP Everywhere, Wi-Fi Direct Print) you do not need a Printer Application. If you connect such a printer via USB, you need [IPP-over-USB support](https://github.com/OpenPrinting/ipp-usb), also available as [Snap](https://snapcraft.io/ipp-usb). + + +### Default paper size + +The CUPS in this Snap uses libpaper which allows to configure a system-wide default paper size by simply dropping the name of the desired size into the configuration file named `papersize`, usually "a4" or "letter". CUPS is then supposed to use this paper size as the default for newly created print queues. + +This actually works for creating queues via the CUPS web interface but not with the `lpadmin` command line utility or with some printer setup tools, so it is of restricted use. + +But if you are user of the web interface and get notorically the wrong paper size as default, create a file named `/var/snap/cups/common/etc/papersize` and drop the name of your desired default page size in it, in a single line, without spaces, commands, ..., simply the page size name, usually "a4" or "letter". Then restart the CUPS Snap for the change to take effect. + + +### Discussion and Links + +Call for testing: + +* [Call for testing on Snapcraft forum](https://forum.snapcraft.io/t/call-for-testing-openprintings-cups-snap/) +* [Call for testing on Discourse](https://discourse.ubuntu.com/t/cups-snap-call-for-testing/) + +Printing with Snaps: + +* [New interface: “cups” for all Snaps which print (How to use, how it exactly works)](https://forum.snapcraft.io/t/new-interface-cups-for-all-snaps-which-print/) +* [Printing and managing printers from your Snap](https://forum.snapcraft.io/t/printing-and-managing-printers-from-your-snap/) + +Documentation requests: + +* [The "cups" interface, by Graham Morrison](https://forum.snapcraft.io/t/the-cups-interface/) +* [My original documentation request for the "cups" interface](https://forum.snapcraft.io/t/the-cups-interface/) + +The development of this Snap is discussed on the Snapcraft forum: + +* [“cups” interface merged into snapd - Additional steps to complete (solved)](https://forum.snapcraft.io/t/cups-interface-merged-into-snapd-additional-steps-to-complete/) +* [Handling of the “cups” plug by snapd, especially auto-connection (How we came to the proxy mode of the CUPS Snap, solved)](https://forum.snapcraft.io/t/handling-of-the-cups-plug-by-snapd-especially-auto-connection/) +* [CUPS Snap: Needs fontconfig for text filter, get “Fontconfig error: Cannot load default config file”](https://forum.snapcraft.io/t/cups-snap-needs-fontconfig-for-text-filter-get-fontconfig-error-cannot-load-default-config-file/) +* [General development](https://forum.snapcraft.io/t/snapping-cups-printing-stack-avahi-support-system-users-groups/) +* [Developer sprint Sep 17th, 2018](https://forum.snapcraft.io/t/developer-sprint-sep-17th-2018/) +* [Interface request: “cups-control” on CUPS snap and including D-Bus](https://forum.snapcraft.io/t/interface-request-cups-control-on-cups-snap-and-including-d-bus/) +* [Snapping CUPS Printing Stack: Providing cups-control interface](https://forum.snapcraft.io/t/snapping-cups-printing-stack-providing-cups-control-interface) +* [Snapping CUPS drivers as plugins (DEPRECATED)](https://forum.snapcraft.io/t/snapping-cups-drivers-as-plugins) + +Related topics on the forum: + +* [How to add or workaround a udev rule](https://forum.snapcraft.io/t/how-to-add-or-workaround-a-udev-rule/) +* [No mdns support in snaps (should core have a modified nsswitch.conf ?)](https://forum.snapcraft.io/t/no-mdns-support-in-snaps-should-core-have-a-modified-nsswitch-conf/) +* [Multiple users and groups in snaps](https://forum.snapcraft.io/t/multiple-users-and-groups-in-snaps/) +* [Snapped daemon running as root cannot create file in directory with odd ownerships/permissions](https://forum.snapcraft.io/t/snapped-daemon-running-as-root-cannot-create-file-in-directory-with-odd-ownerships-permissions) +* [Hardware-associated snaps - Snap Store search by hardware signature](https://forum.snapcraft.io/t/hardware-associated-snaps-snap-store-search-by-hardware-signature) +* [User authentication in snapd (pam mediation)](https://forum.snapcraft.io/t/user-authentication-in-snapd-pam-mediation) + +Getting the Snap into the store: + +* [Call for testing: OpenPrinting’s printing-stack-snap (Printing in a Snap) (DEPRECATED)](https://forum.snapcraft.io/t/call-for-testing-openprintings-printing-stack-snap-printing-in-a-snap/) +* [Post a snap on behalf of OpenPrinting](https://forum.snapcraft.io/t/post-a-snap-on-behalf-of-openprinting/) + +Printer Applications + +* [PostScript Printer Application](https://github.com/OpenPrinting/ps-printer-app) ([Snap Store](https://snapcraft.io/ps-printer-app)): Printer Application Snap for PostScript printers which are supported by the manufacturer's PPD files. User can add PPD files if the needed one is not included or outdated. +* [Ghostscript Printer Application](https://github.com/OpenPrinting/ghostscript-printer-app) ([Snap Store](https://snapcraft.io/ghostscript-printer-app)): Printer Application with Ghostscript and many other drivers, for practically all Linux-supported printers which are not PostScript and not supported by HPLIP or Gutenprint. +* [HPLIP Printer Application](https://github.com/OpenPrinting/hplip-printer-app) ([Snap Store](https://snapcraft.io/hplip-printer-app)): HPLIP in a Printer Application Snap. Supports nearly every HP printer ever made. Installing HP's proprietary plugin (needed for a few printers) into the Snap is supported and easily done with the web interface. +* [Gutenprint Printer Application](https://github.com/OpenPrinting/gutenprint-printer-app) ([Snap Store](https://snapcraft.io/gutenprint-printer-app)): High quality output and a lot of knobs to adjust, especially for Epson and Canon inkjets but also for many other printers, in a Printer Application Snap. +* [Legacy Printer Application](https://github.com/OpenPrinting/pappl-retrofit#legacy-printer-application) (not available as Snap): It is a part of the [pappl-retrofit](https://github.com/OpenPrinting/pappl-retrofit) package and it makes drivers classically installed for the system's classically installed CUPS available in a Printer Application and this way for the CUPS Snap. It is especially helpful for drivers which are not (yet) available as Printer Application. +* [PAPPL](https://github.com/michaelrsweet/pappl/): Base infrastructure for all the Printer Applications linked above. +* [PAPPL CUPS driver retro-fit library](https://github.com/OpenPrinting/pappl-retrofit): Retro-fit layer to integrate CUPS drivers consisting of PPD files, CUPS filters, and CUPS backends into Printer Applications. +* [Printer Applications 2020 (PDF)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/printer-applications-may-2020.pdf) +* [Printer Applications 2021 (PDF)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/printer-applications-may-2021.pdf) +* [CUPS 2018 (PDF, pages 28-29)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/cups-plenary-may-18.pdf) +* [CUPS 2019 (PDF, pages 30-35)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/cups-plenary-april-19.pdf) +* [cups-filters 2018 (PDF, page 11)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/cups-filters-ippusbxd-2018.pdf) +* [cups-filters 2019 (PDF, pages 16-17)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/cups-filters-ippusbxd-2019.pdf) +* [cups-filters 2020 (PDF)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/cups-filters-ippusbxd-2020.pdf) +* [cups-filters 2021 (PDF)](https://ftp.pwg.org/pub/pwg/liaison/openprinting/presentations/cups-filters-cups-snap-ipp-usb-and-more-2021.pdf) + +Snapping of [ipp-usb](https://github.com/OpenPrinting/ipp-usb) + +* [DONE] ipp-usb Snap in the [Snap Store](https://snapcraft.io/ipp-usb) +* ippusbxd got discontinued and replaced by ipp-usb, so it will not get snapped +* [Feature request: Support for systemd templates](https://forum.snapcraft.io/t/feature-request-support-for-systemd-templates/) (Not needed for the ipp-usb Snap) + +Requests for auto-connection to interfaces + +* [Request: CUPS Snap auto connection to "cups-control" interface (accepted)](https://forum.snapcraft.io/t/request-cups-snap-auto-connection-to-cups-control-interface/) +* [Request: CUPS Snap to use the `cups-socket-directory` attribute and auto-connection of any Snap’s “cups” plug to the “cups” slot of the CUPS Snap (accepted)](https://forum.snapcraft.io/t/request-cups-snap-to-use-the-cups-socket-directory-attribute-and-auto-connection-of-any-snaps-cups-plug-to-the-cups-slot-of-the-cups-snap/) +* [Request: CUPS Snap (“cups”) auto connection to of cups:cups-control to cups:cups-control and also of the network-manager-observe interface (accepted)](https://forum.snapcraft.io/t/request-cups-snap-cups-auto-connection-to-of-cups-cups-control-to-cups-admin-and-also-of-the-network-manager-observe-interface/) +* [Request: CUPS Snap (“cups”) auto connection to avahi-control, raw-usb, cups-control, and system-files interfaces (accepted)](https://forum.snapcraft.io/t/request-cups-snap-cups-auto-connection-to-avahi-control-raw-usb-cups-control-and-system-files-interfaces/) +* [Request: Printing Stack Snap auto connection to avahi-control, raw-usb, and home interfaces (DEPRECATED)](https://forum.snapcraft.io/t/request-printing-stack-snap-auto-connection-to-avahi-control-raw-usb-and-home-interfaces) + +Links on other platforms: + +* [Pull request on cupsd to fix CUPS D-Bus service access via "cups-control" interface](https://github.com/snapcore/snapd/pull/11843) +* [Pull request on snapd to fix detection if `cups` interface is connected (merged)](https://github.com/snapcore/snapd/pull/11616) +* [Pull request on snapd for adding the `cups` interface printing always through CUPS Snap (merged)](https://github.com/snapcore/snapd/pull/10427) +* [Pull request on snapd for adding a new exit code for `snapctl --is-connected` for cases when the peer is from the same snap (solved differently)](https://github.com/snapcore/snapd/pull/10024) +* [Pull request on snapd for making the `cups` interface implicit on classic (dropped)](https://github.com/snapcore/snapd/pull/10023) +* [Pull Request on snapd for adding the client Snap checking API (merged)](https://github.com/snapcore/snapd/pull/9132) +* [Pull request on snapd-glib for adding support for exit codes returned by snapctl commands issued via library function (merged)](https://github.com/snapcore/snapd-glib/pull/97) +* [Pull request on CUPS to add Snap mediation (merged)](https://github.com/OpenPrinting/cups/pull/269) +* [Trello card about adding API to check client Snaps whether they plug a certain interface](https://trello.com/c/9IJToylf/1215-snapd-api-for-checking-client-snaps-whether-they-plug-a-given-interface) + +--- + +## CUPS OCI Image + +### Install from Docker Hub +#### Prerequisites + +- **Docker Installed**: Ensure Docker is installed on your system. You can download it from the [official Docker website](https://www.docker.com/get-started). + +- `cups-ipp-utils` +- `cups-client` + + Install the required packages on docker-host using the following commands: + + ```sh + sudo apt-get update + sudo apt-get install cups-ipp-utils cups-client -y + ``` + +#### Step-by-Step Guide + +You can pull the `cups` docker image from either the GitHub Container Registry or Docker Hub. + +**From GitHub Container Registry**
+To pull the image from the GitHub Container Registry, run the following command: +```sh + sudo docker pull ghcr.io/openprinting/cups:latest +``` + +To run the container after pulling the image from the GitHub Container Registry, use: +```sh + sudo docker run -d \ + --name cups \ + --network host \ + -e CUPS_PORT= \ + -e CUPS_ADMIN= \ + -e CUPS_PASSWORD= \ + ghcr.io/openprinting/cups:latest +``` + +**From Docker Hub**
+Alternatively, you can pull the image from Docker Hub, by running: +```sh + sudo docker pull openprinting/cups +``` + +To run the container after pulling the image from Docker Hub, use: +```sh + sudo docker run -d \ + --name cups \ + --network host \ + -e CUPS_PORT= \ + -e CUPS_ADMIN= \ + -e CUPS_PASSWORD= \ + openprinting/cups:latest +``` + +- `CUPS_ADMIN` and `CUPS_PASSWORD` are administrative credentials for accessing CUPS. +- If credentials are not provided, you can view the randomly generated administrative credentials for your container using the following command: + ```sh + sudo docker exec cups cat /etc/cups/cups-credentials + ``` +- `CUPS_PORT` is an optional flag used to start CUPS on a specified port. If not provided, it will start on the default port 631. +- **The container must be started in `--network host` mode** to allow the cups instance inside the container to access and discover printers available in the local network where the host system is in. +- Alternatively using the internal network of the Docker instance (`-p :631` instead of `--network host -e CUPS_PORT=`) only gives access to local printers running on the host system itself. + +### Setting Up and Running CUPS locally + +#### Prerequisites + +1. **Docker Installed**: Ensure Docker is installed on your system. You can download it from the [official Docker website](https://www.docker.com/get-started). + +2. **Rockcraft**: Rockcraft should be installed. You can install Rockcraft using the following command: +```sh + sudo snap install rockcraft --classic +``` + +3. **Skopeo**: Skopeo should be installed to compile `.rock` files into Docker images.
+**Note**: It comes bundled with Rockcraft. + +#### Step-by-Step Guide + +**Build the CUPS Rock** + +The first step is to build the Rock from the `rockcraft.yaml`. This image will contain all the configurations and dependencies required to run CUPS. + +Open your terminal and navigate to the directory containing your `rockcraft.yaml`, then run the following command: + +```sh + rockcraft pack -v +``` + +**Compile to Docker image** + +Once the rock is built, you need to compile docker image from it: + +```sh + sudo rockcraft.skopeo --insecure-policy copy oci-archive: docker-daemon:cups:latest +``` + +**Run the CUPS Docker Container** + +```sh + sudo docker run -d \ + --name cups \ + --network host \ + -e CUPS_PORT= \ + -e CUPS_ADMIN= \ + -e CUPS_PASSWORD= \ + cups:latest +``` + +- `CUPS_ADMIN` and `CUPS_PASSWORD` are administrative credentials for accessing CUPS. +- If credentials are not provided, you can view the randomly generated administrative credentials for your container using the following command: + ```sh + sudo docker exec cups cat /etc/cups/cups-credentials + ``` +- `CUPS_PORT` is an optional flag used to start CUPS on a specified port. If not provided, it will start on the default port 631. +- **The container must be started in `--network host` mode** to allow the cups instance inside the container to access and discover printers available in the local network where the host system is in. +- Alternatively using the internal network of the Docker instance (`-p :631` instead of `--network host -e CUPS_PORT=`) only gives access to local printers running on the host system itself. + +#### Accessing the CUPS Web Interface +- The CUPS web interface can be accessed at `http://localhost:CUPS_PORT` to manage printers and check job statuses. + +### CUPS Commands +To use use the cups's command line utilities acting on the CUPS image, proceed the commands with following format: + +1. Use Docker's -u flag to specify the CUPS_ADMIN user +```sh + sudo docker exec -u "${CUPS_ADMIN}" cups +``` +Example: +To add a printer from inside the container: +```sh + sudo docker exec -u "${CUPS_ADMIN}" cups lpadmin -p +``` + +2. Use `cups-client` on Docker-host to execute CUPS commands:s +```sh + CUPS_SERVER=localhost: +``` + +Example: +To check the print status: +```sh + CUPS_SERVER=localhost:CUPS_PORT lpstat -W completed +``` +**Note to use cups administrative task pass -U flag with lpadmin command** + + + + +--- +## Creating Binary Distributions with EPM and RPM + +### Overview + +Binary packages can be built using: +- **RPM Spec File**: Located at `packaging/cups.spec.in`. +- **EPM List File**: Located at `packaging/cups.list.in`. + +### Building RPM Packages +- Install RPM tools: [RPM Official Site](). +- Build RPMs using the provided spec file. + +### Creating Binary Distributions with EPM +1. Install EPM tools: [EPM Official Site](). + +2. The top level makefile supports generation of many types of binary distributions +using EPM. To build a binary distribution type: + ```sh + make FORMAT + ``` + or + ```sh + gmake FORMAT + ``` + for FreeBSD, NetBSD, and OpenBSD. The "FORMAT" target is one of the following: + +Supported formats: +- "epm": Builds a script + tarfile package +- "bsd": Builds a *BSD package +- "deb": Builds a Debian package +- "pkg": Builds a Solaris package +- "rpm": Builds a RPM package +- "slackware": Build a Slackware package \ No newline at end of file diff --git a/packaging/cups-proxyd/Makefile b/packaging/cups-proxyd/Makefile new file mode 100644 index 0000000000..a481de6a82 --- /dev/null +++ b/packaging/cups-proxyd/Makefile @@ -0,0 +1,69 @@ +# +# Makefile for the cups-proxyd +# +# Copyright © 2020 by Till Kamppeter +# +# Licensed under Apache License v2.0. See the file "LICENSE" for more +# information. +# + +# Version and +VERSION = 1.0 +prefix = $(DESTDIR)/usr +includedir = $(prefix)/include +sbindir = $(prefix)/sbin +libdir = $(prefix)/lib +cupsbackenddir = $(prefix)/lib/cups/backend + + +# Compiler/linker options... +OPTIM = -Os -g +CFLAGS += `cups-config --cflags` `pkg-config --cflags "avahi-client" 2>/dev/null` `pkg-config --cflags "glib-2.0 >= 2.30.2" 2>/dev/null` `pkg-config --cflags "avahi-glib" 2>/dev/null` `pkg-config --cflags "gio-unix-2.0" 2>/dev/null` $(OPTIM) -DVERSION='"$(VERSION)"' +LDFLAGS += $(OPTIM) `cups-config --ldflags` +LIBS += `cups-config --libs` `pkg-config --libs "avahi-client" 2>/dev/null` `pkg-config --libs "glib-2.0 >= 2.30.2" 2>/dev/null` `pkg-config --libs "avahi-glib" 2>/dev/null` `pkg-config --libs "gio-unix-2.0" 2>/dev/null` + +# Targets... +CUPS_PROXYD_OBJS = cups-proxyd.o cups-notifier.o +PROXY_OBJS = proxy.o +OBJS = $(CUPS_PROXYD_OBJS) $(PROXY_OBJS) +TARGETS = cups-proxyd proxy + +# General build rules... +.SUFFIXES: .c .o +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< + +# Targets... +all: $(TARGETS) + +clean: + rm -f $(TARGETS) $(OBJS) $(cups_notifier_sources) + +install: $(TARGETS) + mkdir -p $(sbindir) + cp cups-proxyd $(sbindir) + mkdir -p $(cupsbackenddir) + cp proxy $(cupsbackenddir) + +cups-proxyd.o: cups-proxyd.c cups-notifier.h + $(CC) $(CFLAGS) -c -o cups-proxyd.o cups-proxyd.c + +cups-proxyd: $(CUPS_PROXYD_OBJS) + $(CC) $(LDFLAGS) -o $@ $(CUPS_PROXYD_OBJS) $(LIBS) + +proxy: $(PROXY_OBJS) + $(CC) $(LDFLAGS) -o $@ $(PROXY_OBJS) $(LIBS) + +$(OBJS): Makefile + +# Generate sources for CUPS notifier client... +cups_notifier_sources = \ + cups-notifier.c \ + cups-notifier.h + +$(cups_notifier_sources): org.cups.cupsd.Notifier.xml + gdbus-codegen \ + --interface-prefix org.cups.cupsd \ + --c-namespace Cups \ + --generate-c-code cups-notifier \ + org.cups.cupsd.Notifier.xml diff --git a/packaging/cups-proxyd/cups-proxyd.c b/packaging/cups-proxyd/cups-proxyd.c new file mode 100644 index 0000000000..d7f85d6f92 --- /dev/null +++ b/packaging/cups-proxyd/cups-proxyd.c @@ -0,0 +1,1849 @@ + /*** + This file is part of cups-filters. + + This file is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA. +***/ + +#define _GNU_SOURCE + +#include +#if defined(__OpenBSD__) +#include +#endif /* __OpenBSD__ */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "cups-notifier.h" + +/* Attribute to mark a CUPS queue as created by us */ +#define CUPS_PROXYD_MARK "cups-proxyd" + +/* Update delay and interval in msec */ +#define UPDATE_DELAY 500 +#define UPDATE_INTERVAL 2000 + +#define NOTIFY_LEASE_DURATION (24 * 60 * 60) +#define CUPS_DBUS_PATH "/org/cups/cupsd/Notifier" + +#define DEFAULT_LOGDIR "/var/log/cups" +#define DEBUG_LOG_FILE "/cups-proxyd_log" +#define DEBUG_LOG_FILE_2 "/cups-proxyd_previous_logs" + +/* Data structure for destination list obtained with cupsEnumDests() */ +typedef struct dest_list_s { + int num_dests; + cups_dest_t *dests; + cups_dest_t *default_dest; + int temporary_dests; + int current_dest; +} dest_list_t; + +static char *proxy_cups_server = NULL; +static char *system_cups_server = NULL; +static http_t *proxy_conn = NULL; +static http_t *system_conn = NULL; +static cups_array_t *proxy_printers = NULL; + +static long update_delay = UPDATE_DELAY; +static long update_interval = UPDATE_INTERVAL; +static struct timeval last_update; +static guint update_timer_id = 0; +static guint queues_timer_id = 0; + +static GMainLoop *gmainloop = NULL; + +static CupsNotifier *cups_notifier = NULL; + +static AvahiGLibPoll *glib_poll = NULL; +static AvahiClient *client = NULL; +static AvahiServiceBrowser *sb1 = NULL, *sb2 = NULL; +static int avahi_present = 0; + +static unsigned int DebugLogFileSize = 1024; +static unsigned int HttpLocalTimeout = 2; + +static int debug_stderr = 0; +static int debug_logfile = 0; +static FILE *lfp = NULL; + +static char logdir[1024]; +static char debug_log_file[2048]; +static char debug_log_file_bckp[2048]; + +static int terminating = 0; + + +/* Open the debug log file if we use one */ +void +start_debug_logging() +{ + if (debug_log_file[0] == '\0') + return; + if (lfp == NULL) + lfp = fopen(debug_log_file, "a+"); + if (lfp == NULL) { + fprintf(stderr, "cups-proxyd: ERROR: Failed creating debug log file %s\n", + debug_log_file); + exit(1); + } +} + + +/* Close the debug log file if we use one */ +void +stop_debug_logging() +{ + debug_logfile = 0; + if (lfp) + fclose(lfp); + lfp = NULL; +} + + +/* returns the size of the debug log file */ +long int +findLogFileSize() +{ + FILE* fp = fopen(debug_log_file, "r"); + if (fp == NULL) { + return (-1); + } + fseek(fp, 0L, SEEK_END); + long int res = ftell(fp); + fclose(fp); + return (res); +} + + +/* Copy a file */ +void +copyToFile(FILE **fp1, FILE **fp2) +{ + int buffer_size = 2048; + char *buf = (char*) malloc(sizeof(char)*buffer_size); + if(!buf){ + fprintf(stderr,"Error creating buffer for debug logging\n"); + return; + } + fseek(*fp1, 0, SEEK_SET); + size_t r; + do { + r = fread(buf, sizeof(char), buffer_size, *fp1); + fwrite(buf, sizeof(char), r, *fp2); + } while(r==buffer_size); +} + + +/* Output a log line, to stderr and/or into the log file, also check + the log file size limit and rotate the log */ +void +debug_printf(const char *format, ...) +{ + if (debug_stderr || debug_logfile) { + time_t curtime = time(NULL); + char buf[64]; + ctime_r(&curtime, buf); + while(isspace(buf[strlen(buf)-1])) buf[strlen(buf)-1] = '\0'; + va_list arglist; + if (debug_stderr) { + va_start(arglist, format); + fprintf(stderr, "%s ", buf); + vfprintf(stderr, format, arglist); + fflush(stderr); + va_end(arglist); + } + if (debug_logfile && lfp) { + va_start(arglist, format); + fprintf(lfp, "%s ", buf); + vfprintf(lfp, format, arglist); + fflush(lfp); + va_end(arglist); + } + + long int log_file_size = findLogFileSize(); + if(DebugLogFileSize>0 && log_file_size>(long int)DebugLogFileSize*1024){ + fclose(lfp); + FILE *fp1 = fopen(debug_log_file, "r"); + FILE *fp2 = fopen(debug_log_file_bckp, "w"); + copyToFile(&fp1,&fp2); + fclose(fp1); + fclose(fp2); + lfp = fopen(debug_log_file, "w"); + } + } +} + + +/* Output a multi-line log produced by a library function into the + debug log, each line in the correct format */ +void +debug_log_out(char *log) { + if (debug_stderr || debug_logfile) { + time_t curtime = time(NULL); + char buf[64]; + char *ptr1, *ptr2; + ctime_r(&curtime, buf); + while(isspace(buf[strlen(buf)-1])) buf[strlen(buf)-1] = '\0'; + ptr1 = log; + while(ptr1) { + ptr2 = strchr(ptr1, '\n'); + if (ptr2) *ptr2 = '\0'; + if (debug_stderr) + fprintf(stderr, "%s %s\n", buf, ptr1); + if (debug_logfile && lfp) + fprintf(lfp, "%s %s\n", buf, ptr1); + if (ptr2) *ptr2 = '\n'; + ptr1 = ptr2 ? (ptr2 + 1) : NULL; + } + } +} + + +/* CUPS Password callback to avoid that the daemon gets stuck in password + prompts of libcups functions */ +static const char * +password_callback (const char *prompt, + http_t *http, + const char *method, + const char *resource, + void *user_data) +{ + return NULL; +} + + +/* Connect to CUPS with encryption and shortened timeout */ +http_t * +httpConnectEncryptShortTimeout(const char *host, int port, + http_encryption_t encryption) +{ + return (httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 3000, + NULL)); +} + + +/* Log timeout events on HTTP connections to CUPS */ +int +http_timeout_cb(http_t *http, void *user_data) +{ + debug_printf("HTTP timeout!\n"); + return 0; +} + + +/* HTTP connection to CUPS, CUPS instanced to connec to is defined by a string + with either the domain socket path or hostname and port */ +static http_t * +http_connect(http_t **conn, const char *server) +{ + if (!conn) + return (NULL); + + const char *server_str = strdup(server); + int port = 631; + char *p = strrchr(server_str, ':'); + + if (p) { + *p = '\0'; + port = atoi(p + 1); + } + + if (!*conn) { + if (server_str[0] == '/') + debug_printf("cups-proxyd: Creating http connection to CUPS daemon via domain socket: %s\n", + server_str); + else + debug_printf("cups-proxyd: Creating http connection to CUPS daemon: %s:%d\n", + server_str, port); + *conn = httpConnectEncryptShortTimeout(server_str, port, + cupsEncryption()); + } + if (*conn) + httpSetTimeout(*conn, HttpLocalTimeout, http_timeout_cb, NULL); + else { + if (server_str[0] == '/') + debug_printf("cups-proxyd: Failed creating http connection to CUPS daemon via domain socket: %s\n", + server_str); + else + debug_printf("cups-proxyd: Failed creating http connection to CUPS daemon: %s:%d\n", + server_str, port); + } + + return *conn; +} + + +/* Connect to the proxy CUPS daemon and also tell the libcups functions to + use the proxy CUPS */ +static http_t * +http_connect_proxy(void) +{ + if (!proxy_cups_server) + return NULL; + cupsSetServer(proxy_cups_server); + return http_connect(&proxy_conn, proxy_cups_server); +} + + +/* Connect to the system's CUPS daemon and also tell the libcups functions to + use the system's CUPS */ +static http_t * +http_connect_system(void) +{ + cupsSetServer(system_cups_server); + return http_connect(&system_conn, system_cups_server); +} + + +/* Close the specified HTTP connection */ +static void +http_close(http_t **conn) +{ + if (!conn) + return; + + if (*conn) { + httpClose (*conn); + *conn = NULL; + } +} + + +/* Close connection to proxy CUPS */ +static void +http_close_proxy(void) +{ + if (!proxy_cups_server) + return; + debug_printf("cups-proxyd: Closing connection to proxy CUPS daemon.\n"); + http_close(&proxy_conn); +} + + +/* Close connection to system's CUPS */ +static void +http_close_system(void) +{ + debug_printf("cups-proxyd: Closing connection to system's CUPS daemon.\n"); + http_close(&system_conn); +} + + +/* Callback for cupsEnumDests() to add the name of each found printer to a + CUPS array, we use it to list the queues on the proxy CUPS daemon */ +int +add_printer_name_cb(cups_array_t *user_data, unsigned flags, cups_dest_t *dest) +{ + char *p; + + if (flags & CUPS_DEST_FLAGS_REMOVED) { + /* Remove printer name from array */ + if ((p = cupsArrayFind(user_data, dest->name)) != NULL) { + cupsArrayRemove(user_data, p); + free(p); + } + } else + /* Add printer name to array... */ + cupsArrayAdd(user_data, strdup(dest->name)); + return (1); +} + + +/* Callback for cupsEnumDests() to add the CUPS dest record of each found + printer to a list, we use it to get all print queue info from the + system's CUPS */ +int +add_dest_cb(dest_list_t *user_data, unsigned flags, cups_dest_t *dest) +{ + if (flags & CUPS_DEST_FLAGS_REMOVED) + /* Remove destination from array */ + user_data->num_dests = + cupsRemoveDest(dest->name, dest->instance, user_data->num_dests, + &(user_data->dests)); + else { + /* Add destination to array... */ + user_data->num_dests = + cupsCopyDest(dest, user_data->num_dests, + &(user_data->dests)); + if (dest->is_default) + user_data->default_dest = + cupsGetDest(dest->name, dest->instance, user_data->num_dests, + user_data->dests); + } + return (1); +} + + +/* Get a list of the names of all permanent queues on the proxy CUPS + daemon. We do not expect temporary queues for discovered printers + on the proxy CUPS daemon, as we clone the system's queues including + the temporary one and the proxy CUPS "sees" the same network + printers as the system's CUPS, meaning that they cannot be any + further temporary queues. + We use this list when starting to find whether there leftover queue + to be removed on the proxy */ +static cups_array_t* +get_proxy_printers (void) +{ + cups_array_t *printer_list; + + debug_printf("cups-proxyd (%s): cupsEnumDests\n", proxy_cups_server); + + /* Create array for destination list */ + printer_list = cupsArrayNew((cups_array_func_t)strcasecmp, NULL); + + /* Proxy's CUPS daemon */ + cupsSetServer(proxy_cups_server); + + /* Get a list of only permanent queues. As we copy also the not + actually existing queues of the system's CUPS which would be + created on-demand for a discovered printer, the proxy's CUPS + would not create temporary queues anyway (it discovers the same + printers as the system's CUPS) */ + cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, NULL, CUPS_PRINTER_LOCAL, + CUPS_PRINTER_DISCOVERED, (cups_dest_cb_t)add_printer_name_cb, + printer_list); + return printer_list; +} + + +/* Get all printers of the system's CUPS, including discovered printers + for which the system's CUPS would create a temporary queue. All these + will be cloned to the proxy CUPS */ +static dest_list_t* +get_system_printers (void) +{ + dest_list_t *dest_list; + char *default_printer = NULL; + + debug_printf("cups-proxyd (%s): cupsEnumDests\n", system_cups_server); + + /* Memory for destination list header */ + if ((dest_list = (dest_list_t *)calloc(1, sizeof(dest_list_t))) == + NULL) { + debug_printf("ERROR: Unable to allocate memory for system's print queue list.\n"); + return NULL; + } + + /* System's CUPS daemon */ + cupsSetServer(system_cups_server); + + /* Get a complete queue list, including the discovered printers for + which a temporary queue gets created on demand, from the system's + CUPS daemon */ + cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, NULL, 0, 0, + (cups_dest_cb_t)add_dest_cb, dest_list); + return dest_list; +} + + +/* Free the list of destination when completing an update run */ +void +free_dest_list (dest_list_t *dest_list) +{ + if (dest_list) { + if (dest_list->num_dests > 0 && dest_list->dests) + cupsFreeDests (dest_list->num_dests, dest_list->dests); + free(dest_list); + } +} + + +/* This function replaces cupsGetPPD2(), but is much simplified, + and it works with non-standard (!= 631) ports */ +char* +loadPPD(http_t *http, + const char *name) +{ + char uri[HTTP_MAX_URI]; + char *resource; + int fd, status; + char tempfile[1024] = ""; + + /* Download URI and resource for the PPD file (this works also for + classes, returning the first member's PPD file) */ + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "http", NULL, + "localhost", 0, "/printers/%s.ppd", name); + resource = strstr(uri, "/printers/"); + + /* Download the file */ + fd = cupsTempFd(tempfile, sizeof(tempfile)); + status = cupsGetFd(http, resource, fd); + close(fd); + + /* Check for errors */ + if (status == HTTP_STATUS_OK) + { + if (tempfile[0]) + return(strdup(tempfile)); + } + else if (tempfile[0]) + unlink(tempfile); + return NULL; +} + + +/* Set the default on the proxy CUPS daemon, this way we can also sync + the system's default printer with the proxy */ +int +set_default_printer_on_proxy(const char *printer) { + ipp_t *request; + char uri[HTTP_MAX_URI]; + http_t *http = NULL; + + if (terminating) + return (1); + + if (printer == NULL) + return (1); + + debug_printf("Setting printer %s as default on proxy CUPS daemon.\n", + printer); + + if (!proxy_cups_server) + return (1); + + /* Proxy CUPS daemon */ + cupsSetServer(proxy_cups_server); + + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + "localhost", 0, "/printers/%s", printer); + request = ippNewRequest(IPP_OP_CUPS_SET_DEFAULT); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, uri); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", + NULL, cupsUser()); + + /* Set the printer as default on the proxy CUPS daemon */ + if ((http = http_connect_proxy()) == NULL) { + debug_printf("Could not connect to proxy CUPS daemon.\n"); + return (0); + } + + ippDelete(cupsDoRequest(http, request, "/admin/")); + + if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { + debug_printf("ERROR: Failed setting proxy CUPS default printer to %': %s\n", + printer, cupsLastErrorString()); + return (0); + } + debug_printf("Successfully set proxy CUPS default printer to %s\n", + printer); + return (1); +} + + +/* Remove a queue from the proxy, so we can reflect queue removal on + the system's CUPS */ +static int +remove_queue_from_proxy (const char *name) +{ + http_t *http; + char uri[HTTP_MAX_URI]; + ipp_t *request; + char *p; + + + if (terminating) + return (1); + + debug_printf("Removing proxy CUPS queue %s.\n", name); + + if (!proxy_cups_server) + return (1); + + /* Proxy CUPS daemon */ + cupsSetServer(proxy_cups_server); + + /* Remove the CUPS queue */ + request = ippNewRequest(CUPS_DELETE_PRINTER); + + /* Printer URI: ipp://localhost/printers/ */ + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + "localhost", 0, "/printers/%s", name); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, uri); + + /* Default user */ + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, cupsUser()); + + /* Remove the queue from the proxy CUPS daemon */ + if ((http = http_connect_proxy()) == NULL) { + debug_printf("Could not connect to proxy CUPS daemon.\n"); + return (0); + } + + /* Do it */ + ippDelete(cupsDoRequest(http, request, "/admin/")); + + if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && + cupsLastError() != IPP_STATUS_ERROR_NOT_FOUND) { + debug_printf("Unable to remove CUPS queue! (%s)\n", + cupsLastErrorString()); + return (0); + } + + /* Remove entry from list */ + if ((p = cupsArrayFind(proxy_printers, (char *)name)) != NULL) { + cupsArrayRemove(proxy_printers, p); + free(p); + } + + return (1); +} + + +/* Create a queue on the proxy CUPS daemon which resembles the given + queue from the system's CUPS. Having the same PPD (but without + filter definitions) and basic properties of the system's queue + print dialogs offer the same options. Jobs are passed through to + the system's CUPS daemon without filtering. */ +static int +clone_system_queue_to_proxy (cups_dest_t *dest) +{ + http_t *http; + char *loadedppd = NULL; + char uri[HTTP_MAX_URI], device_uri[HTTP_MAX_URI], + ppdfile[1024], line[1024]; + int num_options; + cups_option_t *options; + ipp_t *request; + int i, is_temporary = 0, ap_remote_queue_id_line_inserted; + cups_file_t *in, *out; + const char *ptype; + const char *val; + char buf[HTTP_MAX_HOST]; + int p; + char host[HTTP_MAX_URI], /* Printer: Hostname */ + resource[HTTP_MAX_URI], /* Printer: Resource path */ + scheme[32], /* Printer: URI's scheme */ + username[64]; /* Printer: URI's username */ + int port = 0; /* Printer: URI's port number */ + + + if (terminating) + return (1); + + debug_printf("Cloning printer %s from system's CUPS to proxy CUPS.\n", + dest->name); + + /* First we need to copy the PPD file from the original (system's) + queue if it has one. It will be modified to suppress filtering on + the proxy but provides options and default seetings of the + original PPD, so that clients show the same in their print + dialogs as with the original PPD */ + + if ((http = http_connect_system()) == NULL) { + debug_printf("Could not connect to system's CUPS daemon.\n"); + return (0); + } + + /* Is this a permanent queue or a temporary queue for a discovered + IPP printer? */ + ptype = cupsGetOption("printer-type", dest->num_options, + dest->options); + if (ptype && (atoi(ptype) & CUPS_PRINTER_DISCOVERED)) + is_temporary = 1; + + /* If the original queue is only created on-demand this call makes + the system's CUPS actually create the queue so that we can grab + the PPD. We discard the result of the call. */ + if (is_temporary) { + debug_printf("Establishing dummy connection to make the system's CUPS create the temporary queue.\n"); + cups_dinfo_t *dinfo = cupsCopyDestInfo(http, dest); + if (dinfo == NULL) { + debug_printf("Unable to connect to destination and/or to create the temporary queue, not able to clone this printer\n"); + return(0); + } else { + debug_printf("Temporary queue created.\n"); + cupsFreeDestInfo(dinfo); + /* Check whether this queue, if temporary, is of CUPS echoing its own + shared printer as discovered printer and skip such useless queues */ + p = 0; + val = cupsGetOption("device-uri", dest->num_options, dest->options); + if (val && !strncasecmp(val, "ipp", 3) && + (strcasestr(val, "/printers/") || strcasestr(val, "/classes/"))) { + if (gethostname(buf, sizeof(buf) - 1)) + buf[0] = '\0'; + p = ippPort(); + httpSeparateURI(HTTP_URI_CODING_ALL, val, + scheme, sizeof(scheme) - 1, + username, sizeof(username) - 1, + host, sizeof(host) - 1, + &port, + resource, sizeof(resource) - 1); + } + } + if (p && + (!strcasecmp(scheme, "ipp") || !strcasecmp(scheme, "ipps")) && + (port == p || port == 0) && + (!strncasecmp(resource, "/printers/", 10) || + !strncasecmp(resource, "/classes/", 9)) && + ((buf[0] && + strncasecmp(host, buf, strlen(buf)) == 0 && + (strlen(host) == strlen(buf) || + (strlen(host) > strlen(buf) && + (strcasecmp(host + strlen(buf), ".local") == 0 || + strcasecmp(host + strlen(buf), ".local.") == 0)))) || + (strncasecmp(host, "localhost", 9) == 0 && + (strlen(host) == 9 || + (strlen(host) > 9 && + (strcasecmp(host + 9, ".local") == 0 || + strcasecmp(host + 9, ".local.") == 0)))))) { + debug_printf("The queue %s is a shared printer of the system's CUPS echoed as a printer discovered by the system's CUPS, skipping/removing!\n", + dest->name); + remove_queue_from_proxy(dest->name); + return (1); + } + } + + /* Load the queue's PPD file from the system's CUPS */ + if ((loadedppd = loadPPD(http, dest->name)) == NULL) { + debug_printf("Unable to load PPD from queue %s on the system!\n", + dest->name); + if (is_temporary) { + debug_printf("Discovered printers/Temporary queues have always a PPD, skipping.\n"); + return (0); + } + } else { + debug_printf("Loaded PPD file %s from queue %s on the system.\n", + loadedppd, dest->name); + } + + /* Non-raw queue with PPD, the usual thing */ + ppdfile[0] = '\0'; + if (loadedppd) { + if ((out = cupsTempFile2(ppdfile, sizeof(ppdfile))) == NULL) { + debug_printf("Unable to create temporary file!\n"); + unlink(loadedppd); + free(loadedppd); + } else if ((in = cupsFileOpen(loadedppd, "r")) == NULL) { + debug_printf("Unable to open the downloaded PPD file!\n"); + cupsFileClose(out); + unlink(loadedppd); + free(loadedppd); + unlink(ppdfile); + ppdfile[0] = '\0'; + } else { + debug_printf("Editing PPD file for printer %s, to mark it as remote CUPS printer and to do not do the conversion from PDF to the printer's native format, saving the resulting PPD in %s.\n", + dest->name, ppdfile); + ap_remote_queue_id_line_inserted = 0; + while (cupsFileGets(in, line, sizeof(line))) { + if (strncmp(line, "*cupsFilter:", 12) && + strncmp(line, "*cupsFilter2:", 13)) { + /* Write an "APRemoteQueueID" line to make this queue marked + as remote printer by CUPS */ + if (strncmp(line, "*%", 2) && + strncmp(line, "*PPD-Adobe:", 11) && + ap_remote_queue_id_line_inserted == 0) { + ap_remote_queue_id_line_inserted = 1; + cupsFilePrintf(out, "*APRemoteQueueID: \"\"\n"); + } + /* Simply write out the line as we read it */ + cupsFilePrintf(out, "%s\n", line); + } + } + cupsFilePrintf(out, "*cupsFilter2: \"application/vnd.cups-pdf application/pdf 0 -\"\n"); + cupsFileClose(in); + cupsFileClose(out); + unlink(loadedppd); + free(loadedppd); + } + } + + if (!proxy_cups_server) + return (1); + + /* Proxy CUPS daemon */ + cupsSetServer(proxy_cups_server); + + /* Create a new CUPS queue or modify the existing queue */ + request = ippNewRequest(CUPS_ADD_MODIFY_PRINTER); + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, + "localhost", 0, "/printers/%s", dest->name); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, uri); + /* Default user */ + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, + "requesting-user-name", NULL, cupsUser()); + /* Queue should be enabled ... */ + ippAddInteger(request, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", + IPP_PRINTER_IDLE); + /* ... and accepting jobs */ + ippAddBoolean(request, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1); + + num_options = 0; + options = NULL; + + /* Device URI: proxy:/// */ + httpAssembleURIf(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri), + "proxy", NULL, system_cups_server, 0, "/%s", + dest->name); + num_options = cupsAddOption("device-uri", device_uri, + num_options, &options); + /* Option cups-proxyd=true, marking that we have created this queue */ + num_options = cupsAddOption(CUPS_PROXYD_MARK "-default", "true", + num_options, &options); + /* Default option settings from printer entry */ + for (i = 0; i < dest->num_options; i ++) { + debug_printf(" %s=%s\n", dest->options[i].name, dest->options[i].value); + if (strcasecmp(dest->options[i].name, "printer-is-shared") && + strcasecmp(dest->options[i].name, "device-uri")) + num_options = cupsAddOption(dest->options[i].name, + dest->options[i].value, + num_options, &options); + } + /* Make sure that the PPD file on the proxy CUPS gets removed if the + queue of the system's CUPS gets turned into a raw queue */ + if (!ppdfile[0]) + num_options = cupsAddOption("ppd-name", "raw", + num_options, &options); + + /* Encode option list into IPP attributes */ + cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION); + cupsEncodeOptions2(request, num_options, options, IPP_TAG_PRINTER); + + /* Create the queue on the proxy CUPS daemon */ + if ((http = http_connect_proxy()) == NULL) { + debug_printf("Could not connect to proxy CUPS daemon.\n"); + return (0); + } + + /* Do it */ + if (ppdfile[0]) { + debug_printf("Non-raw queue %s with PPD file: %s\n", dest->name, ppdfile); + ippDelete(cupsDoFileRequest(http, request, "/admin/", ppdfile)); + unlink(ppdfile); + } else { + debug_printf("Raw queue %s\n", dest->name); + ippDelete(cupsDoRequest(http, request, "/admin/")); + } + cupsFreeOptions(num_options, options); + + if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) { + debug_printf("Unable to create/modify CUPS queue (%s)!\n", + cupsLastErrorString()); + return (0); + } + + if (cupsArrayFind(proxy_printers, dest->name) == NULL) + cupsArrayAdd(proxy_printers, strdup(dest->name)); + + if (dest->is_default) { + debug_printf("%s is the system's default printer.\n", dest->name); + if (set_default_printer_on_proxy(dest->name)) + debug_printf("Set %s as default printer on proxy.\n", dest->name); + else + debug_printf("Could not set %s as default printer on proxy!\n", dest->name); + } + + return (1); +} + + +/* For updating each of the queues of the system's CUPS daemon on the + proxy during an update run, we do not go through all of them in a + loop but instead, we call this function again and again, once for + each queue, via g_timeout_add() with a zero-second timeout. This + way other events in the main loop can stop the process after any + queue, especially a new update run can start when the previous one + did not finish yet. This assures quick updating of the most + important (the permanent queues, see below) queues which are done + in the beginning of the update run. + + We also do not go through all printers as they appear in the list, but + instead, run through the list twice, once doing the permanent queues + and once the discovered printers which let CUPS create a temporary + queue on-demand. + + We do this as the manually created queues are usually the most + important and cloning them to the proxy is much faster than cloning + the temporary queues as CUPS has to actually create those so that we + can grab their PPD files. This way the the important queues get updated + right away quickly in the beginning of an update run. */ +static gboolean +update_next_proxy_printer (gpointer user_data) +{ + dest_list_t *system_printers = (dest_list_t *)user_data; + int cloned = 0; + const char *val; + + if (terminating) + goto finish; + + next: + val = cupsGetOption("printer-type", + system_printers->dests[system_printers->current_dest].num_options, + system_printers->dests[system_printers->current_dest].options); + + if ((!system_printers->temporary_dests && + (val && !(atoi(val) & CUPS_PRINTER_DISCOVERED))) || + (system_printers->temporary_dests && + (!val || (atoi(val) & CUPS_PRINTER_DISCOVERED)))) { + debug_printf("Cloning %s queue %s from the system's CUPS to the proxy CUPS.\n", + system_printers->temporary_dests ? "temporary" : "permanent", + system_printers->dests[system_printers->current_dest].name); + if (!clone_system_queue_to_proxy + (&(system_printers->dests[system_printers->current_dest]))) + debug_printf("Unable to clone queue %s!\n", + system_printers->dests[system_printers->current_dest].name); + cloned = 1; + } + + if (system_printers->current_dest < system_printers->num_dests - 1) { + system_printers->current_dest ++; + } else if (!system_printers->temporary_dests) { + system_printers->temporary_dests ++; + system_printers->current_dest = 0; + } else + goto finish; + + if (!cloned) + goto next; + + /* Repeat this function for the next printer */ + return (TRUE); + + finish: + /* Close the connections to the CUPS daemons to not try to keep them + open internally and CUPS closing them without notice */ + http_close_proxy(); + http_close_system(); + + /* Free the memory occupied by the system's destination list */ + free_dest_list(system_printers); + + /* Mark that we are done */ + queues_timer_id = 0; + + /* We are done, do not repeat again */ + return (FALSE); +} + + +/* Start a new update run, killing any previous update run still running, + marking its start time (to prevent the next update run to start earlier + than update_interval msecs from now), removing queues from the proxy + which have disappeared on the system, and finally kicking off the + updating of each system's queue on the proxy, each queue as a separate + timeout event, to allow easy stopping of the chain. */ +static gboolean +update_proxy_printers (gpointer user_data) +{ + dest_list_t *system_printers; + const char *pname; + + (void)user_data; + + if (terminating) + return (FALSE); + + /* Kill previous update if it is still running */ + if (queues_timer_id) { + debug_printf("Killing previous update.\n"); + g_source_remove (queues_timer_id); + queues_timer_id = 0; + } + + /* Mark the start of this update */ + gettimeofday(&last_update, NULL); + update_timer_id = 0; + + /* Get list of printers on the system's CUPS */ + system_printers = get_system_printers(); + system_printers->temporary_dests = 0; + system_printers->current_dest = 0; + + /* Check whether one of the printers on the system's CUPS has disappeared + compared to the current state of the proxy */ + if (proxy_cups_server) + for (pname = (const char *)cupsArrayFirst(proxy_printers); + pname; + pname = (const char *)cupsArrayNext(proxy_printers)) { + if (terminating) + return (FALSE); + if (!cupsGetDest(pname, NULL, + system_printers->num_dests, system_printers->dests)) { + debug_printf("Queue %s disappeared on the system, removing it from proxy.\n", pname); + if (!remove_queue_from_proxy(pname)) + debug_printf("Could not remove queue %s from proxy!\n", pname); + } + } + + /* Schedule the update for the system's queues */ + debug_printf("Cloning queues from the system to the proxy.\n"); + queues_timer_id = + g_timeout_add(0, update_next_proxy_printer, system_printers); + + /* Do not repeat this part */ + return (FALSE); +} + + +/* When an event (DNS-SD or CUPS D-Bus notification) requests an + update of the queues on the proxy CUPS, it calls this function and + here the next update run gets scheduled. To not cause a flooding + with update runs due to the fact that often the same change on the + system's CUPS creates lots of events (often one DNS-SD event for + each network interface, multiplied by 2 for IPP/IPPS and multiplied + by 2 again for IPv4/IPv6). + + So we do not start more than one update run every 2 seconds + (update_interval, adjustable) so that at least the most important + (permanent) queues get updated before the update run gets killed by + another update run. + + We also delay the start of the update run 0.5 seconds + (update_delay, adjustable) after the first event, so that if + several changes occur shortly after each otherb that they get + treated by a single update run instead of causing several + additional ones. To assure this we do not schedule any further + update run if one is scheduled and waiting for its time to take + off. */ +static void +schedule_proxy_update (void) +{ + struct timeval now; + long delay; + const char *pname; + + if (terminating) + return; + + /* No scheduling without main loop */ + if (!gmainloop) + return; + + /* We already have scheduled an update, do not scedule another one */ + if (update_timer_id) { + debug_printf("Update of queues on proxy CUPS already scheduled!\n"); + return; + } + + /* Calculate the delay when we call update_proxy_printers(), so that the + function does not get called more often than once in UPDATE_INTERVAL + msecs + Delay the cupdate at least UPDATE_DELAY msecs from now, as often one + change on the system's CUPS causes several Avahi event, and then + we had one update triggered immediately and another after + UPDATE_INTERVAL msecs */ + gettimeofday(&now, NULL); + delay = update_interval - (now.tv_sec - last_update.tv_sec) * 1000 - + (now.tv_usec - last_update.tv_usec) / 1000; + if (delay < update_delay) + delay = update_delay; + + /* Schedule the update */ + debug_printf("Updating queues on proxy CUPS in %d msecs\n", delay); + update_timer_id = + g_timeout_add(delay, update_proxy_printers, NULL); +} + + +/* Create a subscription for D-Bus notifications on the system's + CUPS. This makes the CUPS daemon fire up a D-Bus notifier + process. */ +static int +create_subscription () +{ + ipp_t *req; + ipp_t *resp; + ipp_attribute_t *attr; + int id = 0; + http_t *http = NULL; + + debug_printf("Creating subscription to D-Bus notifications on the system's CUPS daemon.\n"); + + http = http_connect_system(); + if (http == NULL) { + debug_printf("Cannot connect to the system's CUPS daemon to subscribe to notifications!\n"); + return 0; + } + + req = ippNewRequest (IPP_CREATE_PRINTER_SUBSCRIPTION); + ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, "/"); + ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, + "notify-events", NULL, "all"); + ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, + "notify-recipient-uri", NULL, "dbus://"); + ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-lease-duration", NOTIFY_LEASE_DURATION); + + resp = cupsDoRequest (http, req, "/"); + if (!resp || cupsLastError() != IPP_STATUS_OK) { + debug_printf("Error subscribing to CUPS notifications: %s\n", + cupsLastErrorString ()); + http_close_system(); + return 0; + } + + attr = ippFindAttribute (resp, "notify-subscription-id", IPP_TAG_INTEGER); + if (attr) + id = ippGetInteger (attr, 0); + else + debug_printf("ipp-create-printer-subscription response doesn't contain " + "subscription id!\n"); + + ippDelete (resp); + http_close_system(); + return id; +} + + +/* Renew the D-Bus notification subscription, telling to CUPS that we + are still there and it should not let the notifier time out. */ +static gboolean +renew_subscription (int id) +{ + ipp_t *req; + ipp_t *resp; + http_t *http = NULL; + + http = http_connect_system(); + if (http == NULL) { + debug_printf("Cannot connect to system's CUPS to renew subscriptions!\n"); + return FALSE; + } + + req = ippNewRequest (IPP_RENEW_SUBSCRIPTION); + ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, + "notify-subscription-id", id); + ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, "/"); + ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, + "notify-recipient-uri", NULL, "dbus://"); + ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-lease-duration", NOTIFY_LEASE_DURATION); + + resp = cupsDoRequest(http, req, "/"); + if (!resp || cupsLastError() != IPP_STATUS_OK) { + debug_printf("Error renewing CUPS subscription %d: %s\n", + id, cupsLastErrorString ()); + http_close_system(); + return FALSE; + } + + ippDelete (resp); + http_close_system(); + return TRUE; +} + + +/* Function which is called as a timeout event handler to let the + renewal of the D-Bus subscription be done to the right time. */ +static gboolean +renew_subscription_timeout (gpointer userdata) +{ + int *subscription_id = userdata; + + debug_printf("renew_subscription_timeout() in THREAD %ld\n", pthread_self()); + + if (*subscription_id <= 0 || !renew_subscription (*subscription_id)) + *subscription_id = create_subscription (); + + return TRUE; +} + + +/* Cancel the D-Bus notifier subscription, so that CUPS can terminate its + notifier when we shut down. */ +void +cancel_subscription (int id) +{ + ipp_t *req; + ipp_t *resp; + http_t *http = NULL; + + if (id <= 0) + return; + + http = http_connect_system(); + if (http == NULL) { + debug_printf("Cannot connect to system's CUPS to cancel subscriptions.\n"); + return; + } + + req = ippNewRequest (IPP_CANCEL_SUBSCRIPTION); + ippAddString (req, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, "/"); + ippAddInteger (req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, + "notify-subscription-id", id); + + resp = cupsDoRequest (http, req, "/"); + if (!resp || cupsLastError() != IPP_STATUS_OK) { + debug_printf ("Error canceling subscription to CUPS notifications: %s\n", + cupsLastErrorString ()); + http_close_system(); + return; + } + + ippDelete (resp); + http_close_system(); +} + + +/* D-Bus notification event handler for a printer status change on the + system's CUPS, for example when a printer is enabled or disabled or + when the default printer changes. We trigger an update run to + forward the changes to the proxy. */ +static void +on_printer_state_changed (CupsNotifier *object, + const gchar *text, + const gchar *printer_uri, + const gchar *printer, + guint printer_state, + const gchar *printer_state_reasons, + gboolean printer_is_accepting_jobs, + gpointer user_data) +{ + debug_printf("on_printer_state_changed() in THREAD %ld\n", pthread_self()); + debug_printf("[System CUPS Notification] Printer state change on printer %s: %s\n", + printer, text); + debug_printf("[System CUPS Notification] Printer state reasons: %s\n", + printer_state_reasons); + debug_printf("[System CUPS Notification] Updating printers on proxy CUPS daemon.\n"); + + /* Update printers on proxy CUPS */ + schedule_proxy_update(); +} + + +/* D-Bus notification event handler for a printer removal on the + system's CUPS. As this does not only mean the manual removal of a + permanent queue or the physical removal of a discoverable network + printer, but also the simple removal of a temporary queue, we + cannot simply remove the corresponding queue on the + proxy. Therefore we trigger an update run to forward the actual set + of queues to the proxy. */ +static void +on_printer_deleted (CupsNotifier *object, + const gchar *text, + const gchar *printer_uri, + const gchar *printer, + guint printer_state, + const gchar *printer_state_reasons, + gboolean printer_is_accepting_jobs, + gpointer user_data) +{ + debug_printf("on_printer_deleted() in THREAD %ld\n", pthread_self()); + debug_printf("[System CUPS Notification] Printer deleted: %s\n", text); + debug_printf("[System CUPS Notification] Updating printers on proxy CUPS daemon.\n"); + + /* Update printers on proxy CUPS */ + schedule_proxy_update(); +} + + +/* D-Bus notification event handler for a printer + creation/modification event on the system's CUPS. Here we also + trigger an update run to forward the actual set of queues to the + proxy. */ +static void +on_printer_modified (CupsNotifier *object, + const gchar *text, + const gchar *printer_uri, + const gchar *printer, + guint printer_state, + const gchar *printer_state_reasons, + gboolean printer_is_accepting_jobs, + gpointer user_data) +{ + debug_printf("on_printer_modified() in THREAD %ld\n", pthread_self()); + debug_printf("[System CUPS Notification] Printer modified: %s\n", text); + debug_printf("[System CUPS Notification] Updating printers on proxy CUPS daemon.\n"); + + /* Update printers on proxy CUPS */ + schedule_proxy_update(); +} + + +/* Callback to handle the appearing and disappearing of DNS-SD + services. As we only browse IPP and IPPS services this means that + this callback is only triggered on IPP printers (there are a few + false positives as stand-alone IPP scanners). As IPP printers we + discover here are also discovered by the system's CUPS they cause + potential temporary queues there and therefore we need to trigger + update runs on the proxy here, for both appearing and disappearing + of IPP printers. We also log the events in the debug log. */ +static void browse_callback(AvahiServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiClient *c = userdata; + char ifname[IF_NAMESIZE]; + + debug_printf("browse_callback() in THREAD %ld\n", pthread_self()); + + if (b == NULL) + return; + + /* Called whenever a new services becomes available on the LAN or + is removed from the LAN */ + + switch (event) { + + /* Avahi browser error */ + case AVAHI_BROWSER_FAILURE: + + debug_printf("[Avahi Browser] ERROR: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + g_main_loop_quit(gmainloop); + g_main_context_wakeup(NULL); + return; + + /* New service (remote printer) */ + case AVAHI_BROWSER_NEW: + + if (c == NULL || name == NULL || type == NULL || domain == NULL) + return; + + /* Get the interface name */ + if (!if_indextoname(interface, ifname)) { + debug_printf("[Avahi Browser] Unable to find interface name for interface %d: %s\n", + interface, strerror(errno)); + strncpy(ifname, "Unknown", sizeof(ifname) - 1); + } + + debug_printf("[Avahi Browser] NEW: service '%s' of type '%s' in domain '%s' on interface '%s' (%s)\n", + name, type, domain, ifname, + protocol != AVAHI_PROTO_UNSPEC ? + avahi_proto_to_string(protocol) : "Unknown"); + debug_printf("[Avahi Browser] Updating printers on proxy CUPS daemon.\n"); + + /* Update printers on proxy CUPS */ + schedule_proxy_update(); + + break; + + /* A service (remote printer) has disappeared */ + case AVAHI_BROWSER_REMOVE: + + if (name == NULL || type == NULL || domain == NULL) + return; + + /* Get the interface name */ + if (!if_indextoname(interface, ifname)) { + debug_printf("[Avahi Browser] Unable to find interface name for interface %d: %s\n", + interface, strerror(errno)); + strncpy(ifname, "Unknown", sizeof(ifname) - 1); + } + + debug_printf("[Avahi Browser] REMOVE: service '%s' of type '%s' in domain '%s' on interface '%s' (%s)\n", + name, type, domain, ifname, + protocol != AVAHI_PROTO_UNSPEC ? + avahi_proto_to_string(protocol) : "Unknown"); + debug_printf("[Avahi Browser] Updating printers on proxy CUPS daemon.\n"); + + /* Update printers on proxy CUPS */ + schedule_proxy_update(); + + break; + + /* All cached Avahi events are treated now */ + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + debug_printf("[Avahi Browser] %s\n", + event == AVAHI_BROWSER_CACHE_EXHAUSTED ? + "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + break; + } +} + + +/* Shutdown of the two (IPP and IPPS) Avahi browsers. */ +void avahi_browser_shutdown() { + avahi_present = 0; + + /* Free the data structures for DNS-SD browsing */ + if (sb1) { + avahi_service_browser_free(sb1); + sb1 = NULL; + } + if (sb2) { + avahi_service_browser_free(sb2); + sb2 = NULL; + } +} + + +/* Shutdown of the client connection to avahi-daemon */ +void avahi_shutdown() { + avahi_browser_shutdown(); + if (client) { + avahi_client_free(client); + client = NULL; + } + if (glib_poll) { + avahi_glib_poll_free(glib_poll); + glib_poll = NULL; + } +} + + +/* Callback to handle Avahi events */ +static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) { + int error; + + if (c == NULL) + return; + + /* Called whenever the client or server state changes */ + switch (state) { + + /* avahi-daemon available */ + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_RUNNING: + case AVAHI_CLIENT_S_COLLISION: + + debug_printf("[Avahi Browser] Avahi server connection got available, setting up service browsers.\n"); + + /* Create the service browsers */ + if (!sb1) + if (!(sb1 = + avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + "_ipp._tcp", NULL, 0, browse_callback, + c))) { + debug_printf("[Avahi Browser] ERROR: Failed to create service browser for IPP: %s\n", + avahi_strerror(avahi_client_errno(c))); + } + if (!sb2) + if (!(sb2 = + avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + "_ipps._tcp", NULL, 0, browse_callback, + c))) { + debug_printf("[Avahi Browser] ERROR: Failed to create service browser for IPPS: %s\n", + avahi_strerror(avahi_client_errno(c))); + } + + avahi_present = 1; + + break; + + /* Avahi client error */ + case AVAHI_CLIENT_FAILURE: + + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + debug_printf("[Avahi Browser] Avahi server disappeared, shutting down service browsers.\n"); + avahi_browser_shutdown(); + /* Renewing client */ + avahi_client_free(client); + client = avahi_client_new(avahi_glib_poll_get(glib_poll), + AVAHI_CLIENT_NO_FAIL, + client_callback, NULL, &error); + if (!client) { + debug_printf("[Avahi Browser] ERROR: Failed to create client: %s\n", + avahi_strerror(error)); + avahi_shutdown(); + } + } else { + debug_printf("[Avahi Browser] ERROR: Avahi server connection failure: %s\n", + avahi_strerror(avahi_client_errno(c))); + g_main_loop_quit(gmainloop); + g_main_context_wakeup(NULL); + } + break; + + default: + break; + } +} + + +/* Initialization of the connection to the avahi-daemon */ +void avahi_init() { + int error; + + /* Allocate main loop object */ + if (!glib_poll) + if (!(glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT))) { + debug_printf("[Avahi Browser] ERROR: Failed to create glib poll object.\n"); + goto avahi_init_fail; + } + + /* Allocate a new client */ + if (!client) + client = avahi_client_new(avahi_glib_poll_get(glib_poll), + AVAHI_CLIENT_NO_FAIL, + client_callback, NULL, &error); + + /* Check wether creating the client object succeeded */ + if (!client) { + debug_printf("[Avahi Browser] ERROR: Failed to create client: %s\n", + avahi_strerror(error)); + goto avahi_init_fail; + } + + return; + + avahi_init_fail: + avahi_shutdown(); +} + + +/* Handler for SIGTERM, for a controlled shutdown of the daemon without + long delays caused by an update run still in progress. */ +static void +sigterm_handler(int sig) { + (void)sig; /* remove compiler warnings... */ + + if (terminating) { + debug_printf("Caught signal %d while already terminating.\n", sig); + return; + } + terminating = 1; /* ignore any further callbacks and break loops */ + /* Flag that we should stop and return... */ + g_main_loop_quit(gmainloop); + g_main_context_wakeup(NULL); + debug_printf("Caught signal %d, shutting down ...\n", sig); +} + + +/* Main function, to read the command line, initiate logging, + listening to D-Bus and DNS-SD events, and cleaning up when + receiving a termination signal. */ +int main(int argc, char *argv[]) { + int ret = 1; + int i; + char *val; + char *p; + GDBusProxy *proxy = NULL; + GError *error = NULL; + int subscription_id = 0; + + /* Read command line options */ + if (argc >= 2) { + for (i = 1; i < argc; i++) + if (!strcasecmp(argv[i], "--debug") || !strcasecmp(argv[i], "-d") || + !strncasecmp(argv[i], "-v", 2)) { + /* Turn on debug output mode if requested */ + debug_stderr = 1; + debug_printf("Reading command line option %s, turning on debug mode (Log on standard error).\n", + argv[i]); + } else if (!strcasecmp(argv[i], "--logfile") || + !strcasecmp(argv[i], "-l")) { + /* Turn on debug log file mode if requested */ + if (debug_logfile == 0) { + debug_logfile = 1; + start_debug_logging(); + debug_printf("Reading command line option %s, turning on debug mode (Log into log file %s).\n", + argv[i], debug_log_file); + } + } else if (!strncasecmp(argv[i], "--logdir", 8)) { + debug_printf("Reading command line: %s\n", argv[i]); + if (argv[i][8] == '=' && argv[i][9]) + val = argv[i] + 9; + else if (!argv[i][8] && i < argc - 1) { + i++; + debug_printf("Reading command line: %s\n", argv[i]); + val = argv[i]; + } else { + fprintf(stderr, "Expected log directory after \"--logdir\" option.\n\n"); + goto help; + } + strncpy(logdir, val, sizeof(logdir) - 1); + debug_printf("Set log directory to %s.\n", logdir); + } else if (!strncasecmp(argv[i], "--update-delay", 14)) { + debug_printf("Reading command line: %s\n", argv[i]); + if (argv[i][14] == '=' && argv[i][15]) + val = argv[i] + 15; + else if (!argv[i][14] && i < argc - 1) { + i++; + debug_printf("Reading command line: %s\n", argv[i]); + val = argv[i]; + } else { + fprintf(stderr, "Expected update delay setting after \"--update-delay\" option.\n\n"); + goto help; + } + int t = atoi(val); + if (t >= 0) { + update_delay = t; + debug_printf("Set update delay to %d msec.\n", + t); + } else { + fprintf(stderr, "Invalid update delay value: %d\n\n", + t); + goto help; + } + } else if (!strncasecmp(argv[i], "--update-interval", 17)) { + debug_printf("Reading command line: %s\n", argv[i]); + if (argv[i][17] == '=' && argv[i][18]) + val = argv[i] + 18; + else if (!argv[i][17] && i < argc - 1) { + i++; + debug_printf("Reading command line: %s\n", argv[i]); + val = argv[i]; + } else { + fprintf(stderr, "Expected update interval setting after \"--update-interval\" option.\n\n"); + goto help; + } + int t = atoi(val); + if (t >= 0) { + update_interval = t; + debug_printf("Set update interval to %d msec.\n", + t); + } else { + fprintf(stderr, "Invalid update interval value: %d\n\n", + t); + goto help; + } + } else if (!strcasecmp(argv[i], "--version") || + !strcasecmp(argv[i], "--help") || !strcasecmp(argv[i], "-h")) { + /* Help!! */ + goto help; + } else if (argv[i][0] == '-') { + /* Unknown option */ + fprintf(stderr, + "Reading command line option %s, unknown command line option.\n\n", + argv[i]); + goto help; + } else { + if (proxy_cups_server == NULL) { + debug_printf("Reading command line: %s -> Proxy cupsd hostname:port or socket\n", argv[i]); + proxy_cups_server = strdup(argv[i]); + } else if (system_cups_server == NULL) { + debug_printf("Reading command line: %s -> System cupsd hostname:port or socket\n", argv[i]); + system_cups_server = strdup(argv[i]); + } else { + /* Extra argument */ + fprintf(stderr, + "Reading command line option %s, too many arguments.\n\n", + argv[i]); + goto help; + } + } + } + if (system_cups_server == NULL) { + /* Less than 2 CUPS daemons specified */ + if (proxy_cups_server == NULL) { + debug_printf("Both a proxy cupsd and a system cupsd need to be specified (or at least a system cupsd for a dry run).\n\n"); + goto help; + } else { + system_cups_server = proxy_cups_server; + proxy_cups_server = NULL; + } + } + + /* Set the paths of the log files */ + if (logdir[0] == '\0') + strncpy(logdir, DEFAULT_LOGDIR, sizeof(logdir) - 1); + + strncpy(debug_log_file, logdir, + sizeof(debug_log_file) - 1); + strncpy(debug_log_file + strlen(logdir), + DEBUG_LOG_FILE, + sizeof(debug_log_file) - strlen(logdir) - 1); + + strncpy(debug_log_file_bckp, logdir, + sizeof(debug_log_file_bckp) - 1); + strncpy(debug_log_file_bckp + strlen(logdir), + DEBUG_LOG_FILE_2, + sizeof(debug_log_file_bckp) - strlen(logdir) - 1); + + if (debug_logfile == 1) + start_debug_logging(); + + debug_printf("main() in THREAD %ld\n", pthread_self()); + + debug_printf("System CUPS: %s\n", system_cups_server); + debug_printf("Proxy CUPS: %s\n", proxy_cups_server); + + debug_printf("cups-proxyd version " VERSION " starting.\n"); + + /* Wait for both CUPS daemons to start */ + debug_printf("Check whether both CUPS daemons are running.\n"); + if (proxy_cups_server) + while (http_connect_proxy() == NULL) + sleep(1); + while (http_connect_system() == NULL) + sleep(1); + if (proxy_cups_server) + http_close_proxy(); + http_close_system(); + + /* Create list of print queues on proxy CUPS daemon */ + if (proxy_cups_server) + proxy_printers = get_proxy_printers(); + + /* Redirect SIGINT and SIGTERM so that we do a proper shutdown */ +#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ + sigset(SIGTERM, sigterm_handler); + sigset(SIGINT, sigterm_handler); + debug_printf("Using signal handler SIGSET\n"); +#elif defined(HAVE_SIGACTION) + struct sigaction action; /* Actions for POSIX signals */ + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGTERM); + action.sa_handler = sigterm_handler; + sigaction(SIGTERM, &action, NULL); + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + action.sa_handler = sigterm_handler; + sigaction(SIGINT, &action, NULL); + debug_printf("Using signal handler SIGACTION\n"); +#else + signal(SIGTERM, sigterm_handler); + signal(SIGINT, sigterm_handler); + debug_printf("Using signal handler SIGNAL\n"); +#endif /* HAVE_SIGSET */ + + /* Initialize last_update */ + memset(&last_update, 0, sizeof(last_update)); + + /* Start Avahi browsers to trigger updates when network printers appear or + disappear (which create potential temporary queues on the system's CUPS */ + avahi_init(); + + /* Override the default password callback so we don't end up + * prompting for it. */ + cupsSetPasswordCB2 (password_callback, NULL); + + /* Create the main loop */ + gmainloop = g_main_loop_new (NULL, FALSE); + + /* Subscribe to the system's CUPS' D-Bus notifications and create a proxy + to receive the notifications */ + subscription_id = create_subscription (); + g_timeout_add_seconds (NOTIFY_LEASE_DURATION - 60, + renew_subscription_timeout, + &subscription_id); + cups_notifier = cups_notifier_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + 0, + NULL, + CUPS_DBUS_PATH, + NULL, + &error); + if (error) { + fprintf (stderr, "Error creating cups notify handler: %s", error->message); + g_error_free (error); + cups_notifier = NULL; + } + if (cups_notifier != NULL) { + g_signal_connect (cups_notifier, "printer-state-changed", + G_CALLBACK (on_printer_state_changed), NULL); + g_signal_connect (cups_notifier, "printer-deleted", + G_CALLBACK (on_printer_deleted), NULL); + g_signal_connect (cups_notifier, "printer-modified", + G_CALLBACK (on_printer_modified), NULL); + } + + /* Schedule first update to sync with current state */ + schedule_proxy_update(); + + /* Run the main loop */ + g_main_loop_run (gmainloop); + + /* Main loop exited */ + debug_printf("Main loop exited\n"); + g_main_loop_unref (gmainloop); + gmainloop = NULL; + ret = 0; + +fail: + + /* Clean up things */ + + /* Remove all queue list entries */ + for (p = (char *)cupsArrayFirst(proxy_printers); + p; p = (char *)cupsArrayNext(proxy_printers)) + free(p); + cupsArrayDelete(proxy_printers); + + /* Cancel subscription to CUPS notifications */ + cancel_subscription (subscription_id); + if (cups_notifier) + g_object_unref (cups_notifier); + + /* Stop Avahi browsers */ + avahi_shutdown(); + + /* Disconnect from CUPS daemons */ + if (proxy_cups_server) + http_close_proxy(); + http_close_system(); + + if (proxy_cups_server) + free(proxy_cups_server); + free(system_cups_server); + + /* Close log file if we have one */ + if (debug_logfile == 1) + stop_debug_logging(); + + return ret; + + help: + + fprintf(stderr, + "cups-proxyd version " VERSION "\n\n" + "Usage: cups-proxyd [] [options]\n" + "\n" + ": The CUPS daemon being the proxy, which receives\n" + " the print jobs of the clients. If left out, we get\n" + " into dry-run mode. All appearing and disappearing\n" + " printers for the system's CUPS get logged.\n" + ": The system's CUPS daemon, which is protected by\n" + " the proxy.\n" + "\n" + "Both proxy and system cupsd have to be specified either by their socket file\n" + "or by :\n" + "\n" + "Options:\n" + " -d\n" + " -v\n" + " --debug Run in debug mode (logging to stderr).\n" + " -l\n" + " --logfile Run in debug mode (logging into file).\n" + " --logdir= Directory to put the log files in. Only used\n" + " together with -l or --logfile\n" + " -h\n" + " --help\n" + " --version Show this usage message.\n" + " --update-delay=