+#if defined(HAVE_GSSAPI) && defined(HAVE_XPC)
+/*
+ * 'run_as_user()' - Run the IPP backend as the printing user.
+ *
+ * This function uses an XPC-based user agent to run the backend as the printing
+ * user. We need to do this in order to have access to the user's Kerberos
+ * credentials.
+ */
+
+static int /* O - Exit status */
+run_as_user(int argc, /* I - Number of command-line args */
+ char *argv[], /* I - Command-line arguments */
+ uid_t uid, /* I - User ID */
+ const char *device_uri, /* I - Device URI */
+ int fd) /* I - File to print */
+{
+ xpc_connection_t conn; /* Connection to XPC service */
+ xpc_object_t request; /* Request message dictionary */
+ __block xpc_object_t response; /* Response message dictionary */
+ dispatch_semaphore_t sem; /* Semaphore for waiting for response */
+ int status = CUPS_BACKEND_FAILED;
+ /* Status of request */
+
+
+ fprintf(stderr, "DEBUG: Running IPP backend as UID %d.\n", (int)uid);
+
+ /*
+ * Connect to the user agent for the specified UID...
+ */
+
+ conn = xpc_connection_create_mach_service(kPMPrintUIToolAgent,
+ dispatch_get_global_queue(0, 0), 0);
+ if (!conn)
+ {
+ _cupsLangPrintFilter(stderr, "ERROR",
+ _("Unable to start backend process."));
+ fputs("DEBUG: Unable to create connection to agent.\n", stderr);
+ goto cleanup;
+ }
+
+ xpc_connection_set_event_handler(conn,
+ ^(xpc_object_t event)
+ {
+ xpc_type_t messageType = xpc_get_type(event);
+
+ if (messageType == XPC_TYPE_ERROR)
+ {
+ if (event == XPC_ERROR_CONNECTION_INTERRUPTED)
+ fprintf(stderr, "DEBUG: Interrupted connection to service %s.\n",
+ xpc_connection_get_name(conn));
+ else if (event == XPC_ERROR_CONNECTION_INVALID)
+ fprintf(stderr, "DEBUG: Connection invalid for service %s.\n",
+ xpc_connection_get_name(conn));
+ else
+ fprintf(stderr, "DEBUG: Unxpected error for service %s: %s\n",
+ xpc_connection_get_name(conn),
+ xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
+ }
+ });
+ xpc_connection_set_target_uid(conn, uid);
+ xpc_connection_resume(conn);
+
+ /*
+ * Try starting the backend...
+ */
+
+ request = xpc_dictionary_create(NULL, NULL, 0);
+ xpc_dictionary_set_int64(request, "command", kPMStartJob);
+ xpc_dictionary_set_string(request, "device-uri", device_uri);
+ xpc_dictionary_set_string(request, "job-id", argv[1]);
+ xpc_dictionary_set_string(request, "user", argv[2]);
+ xpc_dictionary_set_string(request, "title", argv[3]);
+ xpc_dictionary_set_string(request, "copies", argv[4]);
+ xpc_dictionary_set_string(request, "options", argv[5]);
+ xpc_dictionary_set_string(request, "auth-info-required",
+ getenv("AUTH_INFO_REQUIRED"));
+ xpc_dictionary_set_fd(request, "stdin", fd);
+ xpc_dictionary_set_fd(request, "stderr", 2);
+ xpc_dictionary_set_fd(request, "side-channel", CUPS_SC_FD);
+
+ sem = dispatch_semaphore_create(0);
+ response = NULL;
+
+ xpc_connection_send_message_with_reply(conn, request,
+ dispatch_get_global_queue(0,0),
+ ^(xpc_object_t reply)
+ {
+ /* Save the response and wake up */
+ if (xpc_get_type(reply)
+ == XPC_TYPE_DICTIONARY)
+ response = xpc_retain(reply);
+
+ dispatch_semaphore_signal(sem);
+ });
+
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ xpc_release(request);
+ dispatch_release(sem);
+
+ if (response)
+ {
+ child_pid = xpc_dictionary_get_int64(response, "child-pid");
+
+ xpc_release(response);
+
+ if (child_pid)
+ fprintf(stderr, "DEBUG: Child PID=%d.\n", child_pid);
+ else
+ {
+ _cupsLangPrintFilter(stderr, "ERROR",
+ _("Unable to start backend process."));
+ fputs("DEBUG: No child PID.\n", stderr);
+ goto cleanup;
+ }
+ }
+ else
+ {
+ _cupsLangPrintFilter(stderr, "ERROR",
+ _("Unable to start backend process."));
+ fputs("DEBUG: No reply from agent.\n", stderr);
+ goto cleanup;
+ }
+
+ /*
+ * Then wait for the backend to finish...
+ */
+
+ request = xpc_dictionary_create(NULL, NULL, 0);
+ xpc_dictionary_set_int64(request, "command", kPMWaitForJob);
+ xpc_dictionary_set_fd(request, "stderr", 2);
+
+ sem = dispatch_semaphore_create(0);
+ response = NULL;
+
+ xpc_connection_send_message_with_reply(conn, request,
+ dispatch_get_global_queue(0,0),
+ ^(xpc_object_t reply)
+ {
+ /* Save the response and wake up */
+ if (xpc_get_type(reply)
+ == XPC_TYPE_DICTIONARY)
+ response = xpc_retain(reply);
+
+ dispatch_semaphore_signal(sem);
+ });
+
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ xpc_release(request);
+ dispatch_release(sem);
+
+ if (response)
+ {
+ status = xpc_dictionary_get_int64(response, "status");
+
+ if (status == SIGTERM || status == SIGKILL || status == SIGPIPE)
+ {
+ fprintf(stderr, "DEBUG: Child terminated on signal %d.\n", status);
+ status = CUPS_BACKEND_FAILED;
+ }
+ else if (WIFSIGNALED(status))
+ {
+ fprintf(stderr, "DEBUG: Child crashed on signal %d.\n", status);
+ status = CUPS_BACKEND_STOP;
+ }
+ else if (WIFEXITED(status))
+ {
+ status = WEXITSTATUS(status);
+ fprintf(stderr, "DEBUG: Child exited with status %d.\n", status);
+ }
+
+ xpc_release(response);
+ }
+ else
+ _cupsLangPrintFilter(stderr, "ERROR",
+ _("Unable to get backend exit status."));
+
+ cleanup:
+
+ if (conn)
+ {
+ xpc_connection_suspend(conn);
+ xpc_connection_cancel(conn);
+ xpc_release(conn);
+ }
+
+ return (status);
+}
+#endif /* HAVE_GSSAPI && HAVE_XPC */
+
+