/* SSSD InfoPipe Copyright (C) Stephen Gallagher 2009 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 3 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, see . */ #define _GNU_SOURCE #include #include #include "popt.h" #include "util/util.h" #include "util/btreemap.h" #include "sbus/sssd_dbus.h" #include "sbus/sbus_client.h" #include "db/sysdb.h" #include "confdb/confdb.h" #include "monitor/monitor_sbus.h" #include "monitor/monitor_interfaces.h" #include "infopipe/sysbus.h" #include "infopipe/infopipe.h" #include "infopipe/infopipe_private.h" #define INFP_CONF_ENTRY "config/services/info" static int service_identity(DBusMessage *message, struct sbus_conn_ctx *sconn) { dbus_uint16_t version = INFOPIPE_VERSION; const char *name = INFOPIPE_SERVICE_NAME; DBusMessage *reply; dbus_bool_t ret; DEBUG(4, ("Sending identity data [%s,%d]\n", name, version)); reply = dbus_message_new_method_return(message); if (!reply) return ENOMEM; ret = dbus_message_append_args(reply, DBUS_TYPE_STRING, &name, DBUS_TYPE_UINT16, &version, DBUS_TYPE_INVALID); if (!ret) { dbus_message_unref(reply); return EIO; } /* send reply back */ sbus_conn_send_reply(sconn, reply); dbus_message_unref(reply); return EOK; } static int service_pong(DBusMessage *message, struct sbus_conn_ctx *sconn) { DBusMessage *reply; dbus_bool_t ret; reply = dbus_message_new_method_return(message); if (!reply) return ENOMEM; ret = dbus_message_append_args(reply, DBUS_TYPE_INVALID); if (!ret) { dbus_message_unref(reply); return EIO; } /* send reply back */ sbus_conn_send_reply(sconn, reply); dbus_message_unref(reply); return EOK; } static int service_reload(DBusMessage *message, struct sbus_conn_ctx *sconn) { /* Monitor calls this function when we need to reload * our configuration information. Perform whatever steps * are needed to update the configuration objects. */ /* Send an empty reply to acknowledge receipt */ return service_pong(message, sconn); } struct sbus_method mon_sbus_methods[] = { { SERVICE_METHOD_IDENTITY, service_identity }, { SERVICE_METHOD_PING, service_pong }, { SERVICE_METHOD_RELOAD, service_reload }, { NULL, NULL } }; static int infp_monitor_init(struct infp_ctx *infp_ctx) { int ret; char *sbus_address; struct service_sbus_ctx *ss_ctx; struct sbus_method_ctx *sm_ctx; /* Set up SBUS connection to the monitor */ ret = monitor_get_sbus_address(infp_ctx, infp_ctx->cdb, &sbus_address); if (ret != EOK) { DEBUG(0, ("Could not locate monitor address.\n")); return ret; } ret = monitor_init_sbus_methods(infp_ctx, mon_sbus_methods, &sm_ctx); if (ret != EOK) { DEBUG(0, ("Could not initialize SBUS methods.\n")); return ret; } ret = sbus_client_init(infp_ctx, infp_ctx->ev, sbus_address, sm_ctx, NULL /* Private Data */, NULL /* Destructor */, &ss_ctx); if (ret != EOK) { DEBUG(0, ("Failed to connect to monitor services.\n")); return ret; } /* Set up InfoPipe-specific listeners */ /* None currently used */ infp_ctx->ss_ctx = ss_ctx; return EOK; } /* Helper function to return an immediate error message in the event * of internal error in the InfoPipe to avoid forcing the clients to * time out waiting for a reply. * This function will make a best effort to send a reply, but if it * fails, clients will simply have to handle the timeout. */ void infp_return_failure(struct infp_req_ctx *infp_req, const char *message) { DBusMessage *reply; if(infp_req == NULL) return; reply = dbus_message_new_error(infp_req->req_message, DBUS_ERROR_FAILED, message); /* If the reply was NULL, we ran out of memory, so we won't * bother trying to queue the message to send. In this case, * our safest move is to allow the client to time out waiting * for a reply. */ if(reply) { sbus_conn_send_reply(infp_req->sconn, reply); dbus_message_unref(reply); } } /* Helper function to return an ack to the caller to indicate * that the internal process completed succesfully. An ack in * InfoPipe is simply an empty D-BUS method return (as opposed * to a D-BUS error or signal) */ void infp_return_success(struct infp_req_ctx *infp_req) { DBusMessage *reply; if (infp_req == NULL) return; reply = dbus_message_new_method_return(infp_req->req_message); /* If the reply was NULL, we ran out of memory, so we won't * bother trying to queue the message to send. In this case, * our safest move is to allow the client to time out waiting * for a reply. */ if(reply) { sbus_conn_send_reply(infp_req->sconn, reply); dbus_message_unref(reply); } } struct sbus_method infp_methods[] = { INFP_PERMISSION_METHODS INFP_USER_METHODS INFP_GROUP_METHODS { NULL, NULL } }; int infp_introspect(DBusMessage *message, struct sbus_conn_ctx *sconn) { DBusMessage *reply; FILE *xml_stream = NULL; struct infp_ctx *infp; long xml_size, read_size; int ret; dbus_bool_t dbret; infp = talloc_get_type(sbus_conn_get_private_data(sconn), struct infp_ctx); if (infp->introspect_xml == NULL) { /* Read in the Introspection XML the first time */ xml_stream = fopen(SSSD_INTROSPECT_PATH"/"INFP_INTROSPECT_XML, "r"); if(xml_stream == NULL) { ret = errno; DEBUG(0, ("Could not open [%s] for reading. [%d:%s]\n", SSSD_INTROSPECT_PATH"/"INFP_INTROSPECT_XML, ret, strerror(ret))); return ret; } if (fseek(xml_stream, 0L, SEEK_END) != 0) { ret = errno; DEBUG(0, ("Could not seek into [%s]. [%d:%s]\n", SSSD_INTROSPECT_PATH"/"INFP_INTROSPECT_XML, ret, strerror(ret))); goto done; } errno = 0; xml_size = ftell(xml_stream); if (xml_size <= 0) { ret = errno; DEBUG(0, ("Could not get [%s] length (or file is empty). [%d:%s]\n", SSSD_INTROSPECT_PATH"/"INFP_INTROSPECT_XML, ret, strerror(ret))); goto done; } if (fseek(xml_stream, 0L, SEEK_SET) != 0) { ret = errno; DEBUG(0, ("Could not seek into [%s]. [%d:%s]\n", SSSD_INTROSPECT_PATH"/"INFP_INTROSPECT_XML, ret, strerror(ret))); goto done; } infp->introspect_xml = talloc_size(infp, xml_size+1); if (!(infp->introspect_xml)) { ret = ENOMEM; goto done; } read_size = fread(infp->introspect_xml, 1, xml_size, xml_stream); if (read_size < xml_size) { if (!feof(xml_stream)) { ret = ferror(xml_stream); DEBUG(0, ("Error occurred while reading [%s]. [%d:%s]\n", SSSD_INTROSPECT_PATH"/"INFP_INTROSPECT_XML, ret, strerror(ret))); talloc_free(infp->introspect_xml); infp->introspect_xml = NULL; goto done; } } /* Copy the introspection XML to the infp_ctx */ infp->introspect_xml[xml_size+1] = '\0'; } /* Return the Introspection XML */ reply = dbus_message_new_method_return(message); if (reply == NULL) { ret = ENOMEM; goto done; } dbret = dbus_message_append_args(reply, DBUS_TYPE_STRING, &infp->introspect_xml, DBUS_TYPE_INVALID); if (!dbret) { ret = ENOMEM; goto done; } /* send reply back */ sbus_conn_send_reply(sconn, reply); dbus_message_unref(reply); DEBUG(9, ("%s\n", infp->introspect_xml)); ret = EOK; done: if (xml_stream) fclose(xml_stream); return ret; } struct infp_req_ctx *infp_req_init(TALLOC_CTX *mem_ctx, DBusMessage *message, struct sbus_conn_ctx *sconn) { struct infp_req_ctx *infp_req; /* Create an infp_req_ctx */ infp_req = talloc_zero(mem_ctx, struct infp_req_ctx); if (infp_req == NULL) { return NULL; } infp_req->infp = talloc_get_type(sbus_conn_get_private_data(sconn), struct infp_ctx); infp_req->sconn = sconn; infp_req->req_message = message; infp_req->caller = sysbus_get_caller(infp_req, infp_req->req_message, infp_req->sconn); if (infp_req->caller == NULL) { talloc_free(infp_req); return NULL; } return infp_req; } static int infp_process_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct confdb_ctx *cdb) { struct infp_ctx *infp_ctx; int ret; infp_ctx = talloc_zero(mem_ctx, struct infp_ctx); if (infp_ctx == NULL) { DEBUG(0, ("Fatal error initializing infp_ctx\n")); return ENOMEM; } infp_ctx->ev = ev; infp_ctx->cdb = cdb; /* Connect to the monitor */ ret = infp_monitor_init(infp_ctx); if (ret != EOK) { DEBUG(0, ("Fatal error setting up monitor bus\n")); talloc_free(infp_ctx); return EIO; } /* Connect to the D-BUS system bus and set up methods */ ret = sysbus_init(infp_ctx, &infp_ctx->sysbus, infp_ctx->ev, INFOPIPE_DBUS_NAME, INFOPIPE_INTERFACE, INFOPIPE_PATH, infp_methods, infp_introspect); if (ret != EOK) { DEBUG(0, ("Failed to connect to the system message bus\n")); talloc_free(infp_ctx); return EIO; } /* Connect to the sysdb */ ret = sysdb_init(infp_ctx, infp_ctx->ev, infp_ctx->cdb, NULL, &infp_ctx->sysdb); if (ret != EOK) { DEBUG(0, ("Failed to connect to the cache database\n")); talloc_free(infp_ctx); return EIO; } /* Read in the domain map */ ret = confdb_get_domains(cdb, infp_ctx, &infp_ctx->domains); if (ret != EOK) { DEBUG(0, ("Failed to populate the domain map\n")); talloc_free(infp_ctx); return EIO; } infp_ctx->cache_timeout = 600; /* FIXME: read from confdb */ /* Add the infp_ctx to the sbus_conn_ctx private data * so we can pass it into message handler functions */ sbus_conn_set_private_data(sysbus_get_sbus_conn(infp_ctx->sysbus), infp_ctx); return EOK; } int infp_get_object_type(const char *obj) { int object_type = INFP_OBJ_TYPE_INVALID; if (strcasecmp(obj, "user") == 0) object_type = INFP_OBJ_TYPE_USER; else if (strcasecmp(obj, "group") == 0) object_type = INFP_OBJ_TYPE_GROUP; return object_type; } int infp_get_action_type(const char *action) { int action_type = INFP_ACTION_TYPE_INVALID; if (strcasecmp(action, "read") == 0) action_type = INFP_ACTION_TYPE_READ; else if (strcasecmp(action, "create") == 0) action_type = INFP_ACTION_TYPE_CREATE; else if ((strcasecmp(action, "delete") == 0)) action_type = INFP_ACTION_TYPE_DELETE; else if ((strcasecmp(action, "modify") == 0)) action_type = INFP_ACTION_TYPE_MODIFY; else if ((strcasecmp(action, "addmember") == 0)) action_type = INFP_ACTION_TYPE_ADDMEMBER; else if ((strcasecmp(action, "removemember") == 0)) action_type = INFP_ACTION_TYPE_REMOVEMEMBER; return action_type; } int infp_get_attribute_type(const char *attribute) { int attribute_type = INFP_ATTR_TYPE_INVALID; if(attribute == NULL) return INFP_ATTR_TYPE_INVALID; if(strcasecmp(attribute, "defaultgroup") == 0) attribute_type = INFP_ATTR_TYPE_DEFAULTGROUP; else if (strcasecmp(attribute, "gecos") == 0) { attribute_type = INFP_ATTR_TYPE_GECOS; } else if (strcasecmp(attribute, "homedir") == 0) { attribute_type = INFP_ATTR_TYPE_HOMEDIR; } else if (strcasecmp(attribute, "shell") == 0) { attribute_type = INFP_ATTR_TYPE_SHELL; } else if (strcasecmp(attribute, "fullname") == 0) { attribute_type = INFP_ATTR_TYPE_FULLNAME; } else if (strcasecmp(attribute, "locale") == 0) { attribute_type = INFP_ATTR_TYPE_LOCALE; } else if (strcasecmp(attribute, "keyboard") == 0) { attribute_type = INFP_ATTR_TYPE_KEYBOARD; } else if (strcasecmp(attribute, "session") == 0) { attribute_type = INFP_ATTR_TYPE_SESSION; } else if (strcasecmp(attribute, "last_login") == 0) { attribute_type = INFP_ATTR_TYPE_LAST_LOGIN; } else if (strcasecmp(attribute, "userpic") == 0) { attribute_type = INFP_ATTR_TYPE_USERPIC; } return attribute_type; } bool infp_get_permissions(const char *caller, struct sss_domain_info *domain, int object_type, const char *instance, int action_type, int action_attribute) { /* TODO: have a real ACL mechanism. * For right now, root is God and no one else can do anything. * Note: this is buggy. It will return true for ALL requests, * even the nonsensical ones. */ if (strcmp(caller, "root") == 0) return true; return false; } struct sss_domain_info *infp_get_domain_obj(struct infp_ctx *infp, const char *domain_name) { struct sss_domain_info *dom; for (dom = infp->domains; dom; dom = dom->next) { if (strcasecmp(dom->name, domain_name) == 0) break; } return dom; } /* CheckPermissions(STRING domain, STRING object, STRING instance * ARRAY(STRING action_type, STRING attribute) actions) */ int infp_check_permissions(DBusMessage *message, struct sbus_conn_ctx *sconn) { DBusMessage *reply; TALLOC_CTX *tmp_ctx; struct infp_ctx *infp; int current_type; char *caller; DBusMessageIter iter; DBusMessageIter action_array_iter; DBusMessageIter action_struct_iter; int object_type; const char *einval_msg; const char *domain_name; struct sss_domain_info *domain; const char *object; const char *instance; const char *action; const char *attribute; int action_type, attribute_type; dbus_bool_t *permissions; size_t count; tmp_ctx = talloc_new(NULL); if(tmp_ctx == NULL) { return ENOMEM; } infp = talloc_get_type(sbus_conn_get_private_data(sconn), struct infp_ctx); /* Get the caller */ caller = sysbus_get_caller(tmp_ctx, message, sconn); if (caller == NULL) { return EIO; } if (!dbus_message_iter_init(message, &iter)) { einval_msg = talloc_strdup(tmp_ctx, "No arguments received."); goto einval; } /* domain */ current_type = dbus_message_iter_get_arg_type (&iter); if (current_type != DBUS_TYPE_STRING) { einval_msg = talloc_strdup(tmp_ctx, "Expected domain"); goto einval; } dbus_message_iter_get_basic(&iter, &domain_name); DEBUG(9, ("Domain: %s\n", domain_name)); domain = infp_get_domain_obj(infp, domain_name); /* Object */ dbus_message_iter_next(&iter); current_type = dbus_message_iter_get_arg_type (&iter); if (current_type != DBUS_TYPE_STRING) { einval_msg = talloc_strdup(tmp_ctx, "Expected object"); goto einval; } dbus_message_iter_get_basic(&iter, &object); DEBUG(9, ("Object: %s\n", object)); object_type = infp_get_object_type(object); if (object_type == INFP_OBJ_TYPE_INVALID) { einval_msg = talloc_strdup(tmp_ctx, "Invalid object type"); goto einval; } /* Instance */ dbus_message_iter_next(&iter); current_type = dbus_message_iter_get_arg_type (&iter); if (current_type != DBUS_TYPE_STRING) { einval_msg = talloc_strdup(tmp_ctx, "Expected instance"); goto einval; } dbus_message_iter_get_basic(&iter, &instance); DEBUG(9, ("Instance: %s\n", instance)); if(strcmp(instance,"")==0) { instance = NULL; } /* Actions */ dbus_message_iter_next(&iter); current_type = dbus_message_iter_get_arg_type (&iter); if (current_type != DBUS_TYPE_ARRAY) { einval_msg = talloc_strdup(tmp_ctx, "Expected array of actions"); goto einval; } dbus_message_iter_recurse(&iter, &action_array_iter); count = 0; permissions = NULL; while((current_type=dbus_message_iter_get_arg_type(&action_array_iter)) != DBUS_TYPE_INVALID) { if (current_type != DBUS_TYPE_STRUCT) { einval_msg = talloc_strdup(tmp_ctx, "Action array entry was not a struct"); goto einval; } dbus_message_iter_recurse(&action_array_iter, &action_struct_iter); /* action_type */ if (dbus_message_iter_get_arg_type(&action_struct_iter) != DBUS_TYPE_STRING) { einval_msg = talloc_strdup(tmp_ctx, "Missing action_type"); goto einval; } dbus_message_iter_get_basic(&action_struct_iter, &action); DEBUG(9, ("Action type: %s\n", action)); action_type = infp_get_action_type(action); if(action_type == INFP_ACTION_TYPE_INVALID) { einval_msg = talloc_asprintf(tmp_ctx, "Action type [%s] is not valid", action); goto einval; } /* attribute */ dbus_message_iter_next(&action_struct_iter); if (dbus_message_iter_get_arg_type(&action_struct_iter) != DBUS_TYPE_STRING) { einval_msg = talloc_strdup(tmp_ctx, "Missing attribute"); goto einval; } dbus_message_iter_get_basic(&action_struct_iter, &attribute); DEBUG(9, ("Action attribute: %s\n", attribute)); attribute_type = infp_get_attribute_type(attribute); if(attribute_type == INFP_ATTR_TYPE_INVALID) { einval_msg = talloc_asprintf(tmp_ctx, "Attribute [%s] is not valid", attribute); goto einval; } if (dbus_message_iter_has_next(&action_struct_iter)) { einval_msg = talloc_strdup(tmp_ctx, "Unexpected value in action struct"); goto einval; } /* Process the actions */ count++; permissions=talloc_realloc(tmp_ctx, permissions, dbus_bool_t, count); permissions[count-1] = infp_get_permissions(caller, domain, object_type, instance, action_type, attribute_type); dbus_message_iter_next(&action_array_iter); } /* Create response message */ reply = dbus_message_new_method_return(message); if (reply == NULL) { talloc_free(tmp_ctx); return ENOMEM; } dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_BOOLEAN, &permissions, count, DBUS_TYPE_INVALID); /* send reply back */ sbus_conn_send_reply(sconn, reply); dbus_message_unref(reply); talloc_free(tmp_ctx); return EOK; einval: reply = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, einval_msg); sbus_conn_send_reply(sconn, reply); dbus_message_unref(reply); talloc_free(tmp_ctx); return EOK; } int infp_get_ldb_val_from_dbus(TALLOC_CTX *mem_ctx, DBusMessageIter *iter, struct ldb_val **value, int dbus_type, int subtype) { struct ldb_val *val = NULL; void *tmp; size_t element_size; int num_elements; int ret; val = talloc_zero(mem_ctx, struct ldb_val); if (val == NULL) { ret = ENOMEM; goto done; } /* Fixed-size types */ if (sbus_is_dbus_fixed_type(dbus_type)) { dbus_message_iter_get_basic(iter, &tmp); val->length = sbus_get_dbus_type_size(dbus_type); } else if (sbus_is_dbus_string_type(dbus_type)) { dbus_message_iter_get_basic(iter, &tmp); val->length = strlen((const char *)tmp); } else if (dbus_type == DBUS_TYPE_ARRAY) { if (!sbus_is_dbus_fixed_type(subtype)) { ret = EINVAL; goto done; } element_size = sbus_get_dbus_type_size(subtype); dbus_message_iter_get_fixed_array(iter, &tmp, &num_elements); val->length = num_elements * element_size; } else { /* Unsupported type */ ret = EINVAL; goto done; } val->data = talloc_memdup(val, tmp, val->length); if (val->data == NULL) { ret = ENOMEM; goto done; } *value = val; ret = EOK; done: if (ret != EOK) { talloc_free(val); *value = NULL; } return ret; } int main(int argc, const char *argv[]) { int opt; poptContext pc; struct main_context *main_ctx; int ret; struct poptOption long_options[] = { POPT_AUTOHELP SSSD_MAIN_OPTS { NULL } }; pc = poptGetContext(argv[0], argc, argv, long_options, 0); while((opt = poptGetNextOpt(pc)) != -1) { switch(opt) { default: fprintf(stderr, "\nInvalid option %s: %s\n\n", poptBadOption(pc, 0), poptStrerror(opt)); poptPrintUsage(pc, stderr, 0); return 1; } } poptFreeContext(pc); /* set up things like debug , signals, daemonization, etc... */ ret = server_setup("sssd[info]", 0, INFP_CONF_ENTRY, &main_ctx); if (ret != EOK) return 2; ret = infp_process_init(main_ctx, main_ctx->event_ctx, main_ctx->confdb_ctx); if (ret != EOK) return 3; /* loop on main */ server_loop(main_ctx); return 0; }