/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2003-2010 Marcel Holtmann <marcel@holtmann.org> * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/socket.h> #include <glib.h> #include <bluetooth/bluetooth.h> #include <bluetooth/sdp.h> #include <bluetooth/sdp_lib.h> #include <gdbus.h> #include "cups.h" struct cups_device { char *bdaddr; char *name; char *id; }; static GSList *device_list = NULL; static GMainLoop *loop = NULL; static DBusConnection *conn = NULL; static gboolean doing_disco = FALSE; #define ATTRID_1284ID 0x0300 struct context_data { gboolean found; char *id; }; static void element_start(GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **err) { struct context_data *ctx_data = user_data; if (!strcmp(element_name, "record")) return; if (!strcmp(element_name, "attribute")) { int i; for (i = 0; attribute_names[i]; i++) { if (!strcmp(attribute_names[i], "id")) { if (strtol(attribute_values[i], 0, 0) == ATTRID_1284ID) ctx_data->found = TRUE; break; } } return; } if (ctx_data->found && !strcmp(element_name, "text")) { int i; for (i = 0; attribute_names[i]; i++) { if (!strcmp(attribute_names[i], "value")) { ctx_data->id = g_strdup(attribute_values[i] + 2); ctx_data->found = FALSE; } } } } static GMarkupParser parser = { element_start, NULL, NULL, NULL, NULL }; static char *sdp_xml_parse_record(const char *data) { GMarkupParseContext *ctx; struct context_data ctx_data; int size; size = strlen(data); ctx_data.found = FALSE; ctx_data.id = NULL; ctx = g_markup_parse_context_new(&parser, 0, &ctx_data, NULL); if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) { g_markup_parse_context_free(ctx); g_free(ctx_data.id); return NULL; } g_markup_parse_context_free(ctx); return ctx_data.id; } static char *device_get_ieee1284_id(const char *adapter, const char *device) { DBusMessage *message, *reply; DBusMessageIter iter, reply_iter; DBusMessageIter reply_iter_entry; const char *hcr_print = "00001126-0000-1000-8000-00805f9b34fb"; const char *xml; char *id = NULL; /* Look for the service handle of the HCRP service */ message = dbus_message_new_method_call("org.bluez", device, "org.bluez.Device", "DiscoverServices"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &hcr_print); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return NULL; dbus_message_iter_init(reply, &reply_iter); if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { dbus_message_unref(reply); return NULL; } dbus_message_iter_recurse(&reply_iter, &reply_iter_entry); /* Hopefully we only get one handle, or take a punt */ while (dbus_message_iter_get_arg_type(&reply_iter_entry) == DBUS_TYPE_DICT_ENTRY) { guint32 key; DBusMessageIter dict_entry; dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); /* Key ? */ dbus_message_iter_get_basic(&dict_entry, &key); if (!key) { dbus_message_iter_next(&reply_iter_entry); continue; } /* Try to get the value */ if (!dbus_message_iter_next(&dict_entry)) { dbus_message_iter_next(&reply_iter_entry); continue; } dbus_message_iter_get_basic(&dict_entry, &xml); id = sdp_xml_parse_record(xml); if (id != NULL) break; dbus_message_iter_next(&reply_iter_entry); } dbus_message_unref(reply); return id; } static void print_printer_details(const char *name, const char *bdaddr, const char *id) { char *uri, *escaped; escaped = g_strdelimit(g_strdup(name), "\"", '\''); uri = g_strdup_printf("bluetooth://%c%c%c%c%c%c%c%c%c%c%c%c", bdaddr[0], bdaddr[1], bdaddr[3], bdaddr[4], bdaddr[6], bdaddr[7], bdaddr[9], bdaddr[10], bdaddr[12], bdaddr[13], bdaddr[15], bdaddr[16]); printf("direct %s \"%s\" \"%s (Bluetooth)\"", uri, escaped, escaped); if (id != NULL) printf(" \"%s\"\n", id); else printf("\n"); g_free(escaped); g_free(uri); } static void add_device_to_list(const char *name, const char *bdaddr, const char *id) { struct cups_device *device; GSList *l; /* Look for the device in the list */ for (l = device_list; l != NULL; l = l->next) { device = (struct cups_device *) l->data; if (strcmp(device->bdaddr, bdaddr) == 0) { if (device->name != name) { g_free(device->name); device->name = g_strdup(name); } g_free(device->id); device->id = g_strdup(id); return; } } /* Or add it to the list if it's not there */ device = g_new0(struct cups_device, 1); device->bdaddr = g_strdup(bdaddr); device->name = g_strdup(name); device->id = g_strdup(id); device_list = g_slist_prepend(device_list, device); print_printer_details(device->name, device->bdaddr, device->id); } static gboolean parse_device_properties(DBusMessageIter *reply_iter, char **name, char **bdaddr) { guint32 class = 0; DBusMessageIter reply_iter_entry; if (dbus_message_iter_get_arg_type(reply_iter) != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(reply_iter, &reply_iter_entry); while (dbus_message_iter_get_arg_type(&reply_iter_entry) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter dict_entry, iter_dict_val; dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); /* Key == Class ? */ dbus_message_iter_get_basic(&dict_entry, &key); if (!key) { dbus_message_iter_next(&reply_iter_entry); continue; } if (strcmp(key, "Class") != 0 && strcmp(key, "Alias") != 0 && strcmp(key, "Address") != 0) { dbus_message_iter_next(&reply_iter_entry); continue; } /* Try to get the value */ if (!dbus_message_iter_next(&dict_entry)) { dbus_message_iter_next(&reply_iter_entry); continue; } dbus_message_iter_recurse(&dict_entry, &iter_dict_val); if (strcmp(key, "Class") == 0) { dbus_message_iter_get_basic(&iter_dict_val, &class); } else { const char *value; dbus_message_iter_get_basic(&iter_dict_val, &value); if (strcmp(key, "Alias") == 0) { *name = g_strdup(value); } else if (bdaddr) { *bdaddr = g_strdup(value); } } dbus_message_iter_next(&reply_iter_entry); } if (class == 0) return FALSE; if (((class & 0x1f00) >> 8) == 0x06 && (class & 0x80)) return TRUE; return FALSE; } static gboolean device_is_printer(const char *adapter, const char *device_path, char **name, char **bdaddr) { DBusMessage *message, *reply; DBusMessageIter reply_iter; gboolean retval; message = dbus_message_new_method_call("org.bluez", device_path, "org.bluez.Device", "GetProperties"); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return FALSE; dbus_message_iter_init(reply, &reply_iter); retval = parse_device_properties(&reply_iter, name, bdaddr); dbus_message_unref(reply); return retval; } static void remote_device_found(const char *adapter, const char *bdaddr, const char *name) { DBusMessage *message, *reply, *adapter_reply; DBusMessageIter iter; char *object_path = NULL; char *id; adapter_reply = NULL; if (adapter == NULL) { message = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "DefaultAdapter"); adapter_reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (dbus_message_get_args(adapter_reply, NULL, DBUS_TYPE_OBJECT_PATH, &adapter, DBUS_TYPE_INVALID) == FALSE) return; } message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter", "FindDevice"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); if (adapter_reply != NULL) dbus_message_unref(adapter_reply); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) { message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter", "CreateDevice"); dbus_message_iter_init_append(message, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, NULL); dbus_message_unref(message); if (!reply) return; } if (dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID) == FALSE) { return; } id = device_get_ieee1284_id(adapter, object_path); add_device_to_list(name, bdaddr, id); g_free(id); } static void discovery_completed(void) { g_slist_free(device_list); device_list = NULL; g_main_loop_quit(loop); } static void remote_device_disappeared(const char *bdaddr) { GSList *l; for (l = device_list; l != NULL; l = l->next) { struct cups_device *device = (struct cups_device *) l->data; if (strcmp(device->bdaddr, bdaddr) == 0) { g_free(device->name); g_free(device->bdaddr); g_free(device); device_list = g_slist_delete_link(device_list, l); return; } } } static gboolean list_known_printers(const char *adapter) { DBusMessageIter reply_iter, iter_array; DBusError error; DBusMessage *message, *reply; message = dbus_message_new_method_call ("org.bluez", adapter, "org.bluez.Adapter", "ListDevices"); if (message == NULL) return FALSE; dbus_error_init(&error); reply = dbus_connection_send_with_reply_and_block(conn, message, -1, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) return FALSE; dbus_message_iter_init(reply, &reply_iter); if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { dbus_message_unref(reply); return FALSE; } dbus_message_iter_recurse(&reply_iter, &iter_array); while (dbus_message_iter_get_arg_type(&iter_array) == DBUS_TYPE_OBJECT_PATH) { const char *object_path; char *name = NULL; char *bdaddr = NULL; dbus_message_iter_get_basic(&iter_array, &object_path); if (device_is_printer(adapter, object_path, &name, &bdaddr)) { char *id; id = device_get_ieee1284_id(adapter, object_path); add_device_to_list(name, bdaddr, id); g_free(id); } g_free(name); g_free(bdaddr); dbus_message_iter_next(&iter_array); } dbus_message_unref(reply); return FALSE; } static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *message, void *user_data) { if (dbus_message_is_signal(message, "org.bluez.Adapter", "DeviceFound")) { const char *adapter, *bdaddr; char *name; DBusMessageIter iter; dbus_message_iter_init(message, &iter); dbus_message_iter_get_basic(&iter, &bdaddr); dbus_message_iter_next(&iter); adapter = dbus_message_get_path(message); if (parse_device_properties(&iter, &name, NULL)) remote_device_found(adapter, bdaddr, name); g_free (name); } else if (dbus_message_is_signal(message, "org.bluez.Adapter", "DeviceDisappeared")) { char *bdaddr; dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &bdaddr, DBUS_TYPE_INVALID); remote_device_disappeared(bdaddr); } else if (dbus_message_is_signal(message, "org.bluez.Adapter", "PropertyChanged")) { DBusMessageIter iter, value_iter; const char *name; gboolean discovering; dbus_message_iter_init(message, &iter); dbus_message_iter_get_basic(&iter, &name); if (name == NULL || strcmp(name, "Discovering") != 0) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_message_iter_next(&iter); dbus_message_iter_recurse(&iter, &value_iter); dbus_message_iter_get_basic(&value_iter, &discovering); if (discovering == FALSE && doing_disco) { doing_disco = FALSE; discovery_completed(); } } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static gboolean list_printers(void) { /* 1. Connect to the bus * 2. Get the manager * 3. Get the default adapter * 4. Get a list of devices * 5. Get the class of each device * 6. Print the details from each printer device */ DBusError error; dbus_bool_t hcid_exists; DBusMessage *reply, *message; DBusMessageIter reply_iter; char *adapter, *match; conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); if (conn == NULL) return TRUE; dbus_error_init(&error); hcid_exists = dbus_bus_name_has_owner(conn, "org.bluez", &error); if (dbus_error_is_set(&error)) return TRUE; if (!hcid_exists) return TRUE; /* Get the default adapter */ message = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "DefaultAdapter"); if (message == NULL) { dbus_connection_unref(conn); return FALSE; } reply = dbus_connection_send_with_reply_and_block(conn, message, -1, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { dbus_connection_unref(conn); /* No adapter */ return TRUE; } dbus_message_iter_init(reply, &reply_iter); if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_OBJECT_PATH) { dbus_message_unref(reply); dbus_connection_unref(conn); return FALSE; } dbus_message_iter_get_basic(&reply_iter, &adapter); adapter = g_strdup(adapter); dbus_message_unref(reply); if (!dbus_connection_add_filter(conn, filter_func, adapter, g_free)) { g_free(adapter); dbus_connection_unref(conn); return FALSE; } #define MATCH_FORMAT \ "type='signal'," \ "interface='org.bluez.Adapter'," \ "sender='org.bluez'," \ "path='%s'" match = g_strdup_printf(MATCH_FORMAT, adapter); dbus_bus_add_match(conn, match, &error); g_free(match); /* Add the the recent devices */ list_known_printers(adapter); doing_disco = TRUE; message = dbus_message_new_method_call("org.bluez", adapter, "org.bluez.Adapter", "StartDiscovery"); if (!dbus_connection_send_with_reply(conn, message, NULL, -1)) { dbus_message_unref(message); dbus_connection_unref(conn); g_free(adapter); return FALSE; } dbus_message_unref(message); loop = g_main_loop_new(NULL, TRUE); g_main_loop_run(loop); g_free(adapter); dbus_connection_unref(conn); return TRUE; } /* * Usage: printer-uri job-id user title copies options [file] * */ int main(int argc, char *argv[]) { sdp_session_t *sdp; bdaddr_t bdaddr; unsigned short ctrl_psm, data_psm; uint8_t channel, b[6]; char *ptr, str[3], device[18], service[12]; const char *uri, *cups_class; int i, err, fd, copies, proto; /* Make sure status messages are not buffered */ setbuf(stderr, NULL); /* Make sure output is not buffered */ setbuf(stdout, NULL); /* Ignore SIGPIPE signals */ #ifdef HAVE_SIGSET sigset(SIGPIPE, SIG_IGN); #elif defined(HAVE_SIGACTION) memset(&action, 0, sizeof(action)); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_IGN); #endif /* HAVE_SIGSET */ if (argc == 1) { if (list_printers() == TRUE) return CUPS_BACKEND_OK; else return CUPS_BACKEND_FAILED; } if (argc < 6 || argc > 7) { fprintf(stderr, "Usage: bluetooth job-id user title copies options [file]\n"); return CUPS_BACKEND_FAILED; } if (argc == 6) { fd = 0; copies = 1; } else { if ((fd = open(argv[6], O_RDONLY)) < 0) { perror("ERROR: Unable to open print file"); return CUPS_BACKEND_FAILED; } copies = atoi(argv[4]); } uri = getenv("DEVICE_URI"); if (!uri) uri = argv[0]; if (strncasecmp(uri, "bluetooth://", 12)) { fprintf(stderr, "ERROR: No device URI found\n"); return CUPS_BACKEND_FAILED; } ptr = argv[0] + 12; for (i = 0; i < 6; i++) { strncpy(str, ptr, 2); b[i] = (uint8_t) strtol(str, NULL, 16); ptr += 2; } sprintf(device, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", b[0], b[1], b[2], b[3], b[4], b[5]); str2ba(device, &bdaddr); ptr = strchr(ptr, '/'); if (ptr) { strncpy(service, ptr + 1, 12); if (!strncasecmp(ptr + 1, "spp", 3)) proto = 1; else if (!strncasecmp(ptr + 1, "hcrp", 4)) proto = 2; else proto = 0; } else { strcpy(service, "auto"); proto = 0; } cups_class = getenv("CLASS"); fprintf(stderr, "DEBUG: %s device %s service %s fd %d copies %d class %s\n", argv[0], device, service, fd, copies, cups_class ? cups_class : "(none)"); fputs("STATE: +connecting-to-device\n", stderr); service_search: sdp = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY); if (!sdp) { fprintf(stderr, "ERROR: Can't open Bluetooth connection\n"); return CUPS_BACKEND_FAILED; } switch (proto) { case 1: err = sdp_search_spp(sdp, &channel); break; case 2: err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm); break; default: proto = 2; err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm); if (err) { proto = 1; err = sdp_search_spp(sdp, &channel); } break; } sdp_close(sdp); if (err) { if (cups_class) { fputs("INFO: Unable to contact printer, queuing on " "next printer in class...\n", stderr); sleep(5); return CUPS_BACKEND_FAILED; } sleep(20); fprintf(stderr, "ERROR: Can't get service information\n"); goto service_search; } connect: switch (proto) { case 1: err = spp_print(BDADDR_ANY, &bdaddr, channel, fd, copies, cups_class); break; case 2: err = hcrp_print(BDADDR_ANY, &bdaddr, ctrl_psm, data_psm, fd, copies, cups_class); break; default: err = CUPS_BACKEND_FAILED; fprintf(stderr, "ERROR: Unsupported protocol\n"); break; } if (err == CUPS_BACKEND_FAILED && cups_class) { fputs("INFO: Unable to contact printer, queuing on " "next printer in class...\n", stderr); sleep(5); return CUPS_BACKEND_FAILED; } else if (err == CUPS_BACKEND_RETRY) { sleep(20); goto connect; } if (fd != 0) close(fd); if (!err) fprintf(stderr, "INFO: Ready to print\n"); return err; }