char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup); RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup); RAII_VAR(void *, object, NULL, ao2_cleanup); int is_container = 0; const char *cmd1; const char *cmd2; const char *object_id; char formatter_type[64]; struct ast_sip_cli_context context = { .indent_level = 0, .show_details = 0, .show_details_only_level_0 = 0, .recurse = 0, }; if (cmd == CLI_INIT) { return NULL; } cmd1 = e->cmda[1]; cmd2 = e->cmda[2]; object_id = a->argv[3]; if (!ast_ends_with(cmd2, "s")) { ast_copy_string(formatter_type, cmd2, sizeof(formatter_type)); is_container = 0; } else if (ast_ends_with(cmd2, "ies")) { /* Take the plural "ies" off of the object name and re[place with "y". */ int l = strlen(cmd2); snprintf(formatter_type, 64, "%*.*sy", l - 3, l - 3, cmd2); is_container = 1; } else { /* Take the plural "s" off of the object name. */ ast_copy_string(formatter_type, cmd2, strlen(cmd2)); is_container = 1; } if (!strcmp(cmd1, "show")) { context.show_details_only_level_0 = !is_container; context.recurse = 1; } else { is_container = 1; } if (cmd == CLI_GENERATE && (is_container || a->argc > 4 || (a->argc == 4 && ast_strlen_zero(a->word)))) { return CLI_SUCCESS; } context.output_buffer = ast_str_create(256); if (!context.output_buffer) { return CLI_FAILURE; } formatter_entry = ast_sip_lookup_cli_formatter(formatter_type); if (!formatter_entry) { ast_log(LOG_ERROR, "No formatter registered for object type %s.\n", formatter_type); ast_free(context.output_buffer); return CLI_FAILURE; } ast_str_append(&context.output_buffer, 0, "\n"); formatter_entry->print_header(NULL, &context, 0); ast_str_append(&context.output_buffer, 0, " =========================================================================================\n\n"); if (is_container || cmd == CLI_GENERATE) { container = formatter_entry->get_container(); if (!container) { ast_cli(a->fd, "No container returned for object type %s.\n", formatter_type); ast_free(context.output_buffer); return CLI_FAILURE; } } if (cmd == CLI_GENERATE) { ast_free(context.output_buffer); return complete_show_sorcery_object(container, formatter_entry, a->word, a->n); } if (is_container) { if (!ao2_container_count(container)) { ast_free(context.output_buffer); ast_cli(a->fd, "No objects found.\n\n"); return CLI_SUCCESS; } ao2_callback(container, OBJ_NODATA, formatter_entry->print_body, &context); } else { if (ast_strlen_zero(object_id)) { ast_free(context.output_buffer); ast_cli(a->fd, "No object specified.\n"); return CLI_FAILURE; } object = formatter_entry->retrieve_by_id(object_id); if (!object) { ast_free(context.output_buffer); ast_cli(a->fd, "Unable to find object %s.\n\n", object_id); return CLI_SUCCESS; } formatter_entry->print_body(object, &context, 0); } ast_str_append(&context.output_buffer, 0, "\n"); dump_str_and_free(a->fd, context.output_buffer); return CLI_SUCCESS; } static int formatter_sort(const void *obj, const void *arg, int flags) { const struct ast_sip_cli_formatter_entry *left_obj = obj; const struct ast_sip_cli_formatter_entry *right_obj = arg; const char *right_key = arg; int cmp = 0; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_OBJECT: right_key = right_obj->name; /* Fall through */ case OBJ_SEARCH_KEY: cmp = strcmp(left_obj->name, right_key); break; case OBJ_SEARCH_PARTIAL_KEY: cmp = strncmp(left_obj->name, right_key, strlen(right_key)); break; default: cmp = 0; break; } return cmp; } static int formatter_compare(void *obj, void *arg, int flags) { const struct ast_sip_cli_formatter_entry *left_obj = obj; const struct ast_sip_cli_formatter_entry *right_obj = arg; const char *right_key = arg; int cmp = 0; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_OBJECT: right_key = right_obj->name; /* Fall through */ case OBJ_SEARCH_KEY: if (strcmp(left_obj->name, right_key) == 0) {; cmp = CMP_MATCH | CMP_STOP; } break; case OBJ_SEARCH_PARTIAL_KEY: if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) { cmp = CMP_MATCH; } break; default: cmp = 0; break; } return cmp; } static int formatter_hash(const void *obj, int flags) { const struct ast_sip_cli_formatter_entry *left_obj = obj; if (flags & OBJ_SEARCH_OBJECT) { return ast_str_hash(left_obj->name); } else if (flags & OBJ_SEARCH_KEY) { return ast_str_hash(obj); } return -1; } struct ast_sip_cli_formatter_entry *ast_sip_lookup_cli_formatter(const char *name) { return ao2_find(formatter_registry, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); } int ast_sip_register_cli_formatter(struct ast_sip_cli_formatter_entry *formatter) { ast_assert(formatter != NULL); ast_assert(formatter->name != NULL); ast_assert(formatter->print_body != NULL); ast_assert(formatter->print_header != NULL); ast_assert(formatter->get_container != NULL); ast_assert(formatter->iterate != NULL); ast_assert(formatter->get_id != NULL); ast_assert(formatter->retrieve_by_id != NULL); ao2_link(formatter_registry, formatter); return 0; }
/*! * \internal * \brief ARI HTTP handler. * * This handler takes the HTTP request and turns it into the appropriate * RESTful request (conversion to JSON, routing, etc.) * * \param ser TCP session. * \param urih URI handler. * \param uri URI requested. * \param method HTTP method. * \param get_params HTTP \c GET params. * \param headers HTTP headers. */ static int ast_ari_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers) { RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup); RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free); RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup); struct ast_ari_response response = {}; RAII_VAR(struct ast_variable *, post_vars, NULL, ast_variables_destroy); if (!response_body) { ast_http_request_close_on_completion(ser); ast_http_error(ser, 500, "Server Error", "Out of memory"); return 0; } response.headers = ast_str_create(40); if (!response.headers) { ast_http_request_close_on_completion(ser); ast_http_error(ser, 500, "Server Error", "Out of memory"); return 0; } conf = ast_ari_config_get(); if (!conf || !conf->general) { ast_free(response.headers); ast_http_request_close_on_completion(ser); ast_http_error(ser, 500, "Server Error", "URI handler config missing"); return 0; } process_cors_request(headers, &response); /* Process form data from a POST. It could be mixed with query * parameters, which seems a bit odd. But it's allowed, so that's okay * with us. */ post_vars = ast_http_get_post_vars(ser, headers); if (!post_vars) { switch (errno) { case EFBIG: ast_ari_response_error(&response, 413, "Request Entity Too Large", "Request body too large"); goto request_failed; case ENOMEM: ast_http_request_close_on_completion(ser); ast_ari_response_error(&response, 500, "Internal Server Error", "Out of memory"); goto request_failed; case EIO: ast_ari_response_error(&response, 400, "Bad Request", "Error parsing request body"); goto request_failed; } } if (get_params == NULL) { get_params = post_vars; } else if (get_params && post_vars) { /* Has both post_vars and get_params */ struct ast_variable *last_var = post_vars; while (last_var->next) { last_var = last_var->next; } /* The duped get_params will get freed when post_vars gets * ast_variables_destroyed. */ last_var->next = ast_variables_dup(get_params); get_params = post_vars; } user = authenticate_user(get_params, headers); if (response.response_code > 0) { /* POST parameter processing error. Do nothing. */ } else if (!user) { /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response * message is used by an origin server to challenge the * authorization of a user agent. This response MUST include a * WWW-Authenticate header field containing at least one * challenge applicable to the requested resource. */ ast_ari_response_error(&response, 401, "Unauthorized", "Authentication required"); /* Section 1.2: * realm = "realm" "=" realm-value * realm-value = quoted-string * Section 2: * challenge = "Basic" realm */ ast_str_append(&response.headers, 0, "WWW-Authenticate: Basic realm=\"%s\"\r\n", conf->general->auth_realm); } else if (!ast_fully_booted) { ast_http_request_close_on_completion(ser); ast_ari_response_error(&response, 503, "Service Unavailable", "Asterisk not booted"); } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) { ast_ari_response_error(&response, 403, "Forbidden", "Write access denied"); } else if (ast_ends_with(uri, "/")) { remove_trailing_slash(uri, &response); } else if (ast_begins_with(uri, "api-docs/")) { /* Serving up API docs */ if (method != AST_HTTP_GET) { ast_ari_response_error(&response, 405, "Method Not Allowed", "Unsupported method"); } else { /* Skip the api-docs prefix */ ast_ari_get_docs(strchr(uri, '/') + 1, headers, &response); } } else { /* Other RESTful resources */ ast_ari_invoke(ser, uri, method, get_params, headers, &response); } if (response.no_response) { /* The handler indicates no further response is necessary. * Probably because it already handled it */ ast_free(response.headers); return 0; } request_failed: /* If you explicitly want to have no content, set message to * ast_json_null(). */ ast_assert(response.message != NULL); ast_assert(response.response_code > 0); /* response.message could be NULL, in which case the empty response_body * is correct */ if (response.message && !ast_json_is_null(response.message)) { ast_str_append(&response.headers, 0, "Content-type: application/json\r\n"); if (ast_json_dump_str_format(response.message, &response_body, conf->general->format) != 0) { /* Error encoding response */ response.response_code = 500; response.response_text = "Internal Server Error"; ast_str_set(&response_body, 0, "%s", ""); ast_str_set(&response.headers, 0, "%s", ""); } } ast_debug(3, "Examining ARI response:\n%d %s\n%s\n%s\n", response.response_code, response.response_text, ast_str_buffer(response.headers), ast_str_buffer(response_body)); ast_http_send(ser, method, response.response_code, response.response_text, response.headers, response_body, 0, 0); /* ast_http_send takes ownership, so we don't have to free them */ response_body = NULL; ast_json_unref(response.message); return 0; }
/*! * \internal * \brief ARI HTTP handler. * * This handler takes the HTTP request and turns it into the appropriate * RESTful request (conversion to JSON, routing, etc.) * * \param ser TCP session. * \param urih URI handler. * \param uri URI requested. * \param method HTTP method. * \param get_params HTTP \c GET params. * \param headers HTTP headers. */ static int ast_ari_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers) { RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup); RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free); RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free); RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup); struct ast_ari_response response = {}; int ret = 0; if (!response_headers || !response_body) { return -1; } response.headers = ast_str_create(40); if (!response.headers) { return -1; } conf = ast_ari_config_get(); if (!conf || !conf->general) { return -1; } process_cors_request(headers, &response); user = authenticate_user(get_params, headers); if (!user) { /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response * message is used by an origin server to challenge the * authorization of a user agent. This response MUST include a * WWW-Authenticate header field containing at least one * challenge applicable to the requested resource. */ response.response_code = 401; response.response_text = "Unauthorized"; /* Section 1.2: * realm = "realm" "=" realm-value * realm-value = quoted-string * Section 2: * challenge = "Basic" realm */ ast_str_append(&response.headers, 0, "WWW-Authenticate: Basic realm=\"%s\"\r\n", conf->general->auth_realm); response.message = ast_json_pack("{s: s}", "error", "Authentication required"); } else if (!ast_fully_booted) { response.response_code = 503; response.response_text = "Service Unavailable"; response.message = ast_json_pack("{s: s}", "error", "Asterisk not booted"); } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) { response.message = ast_json_pack("{s: s}", "error", "Write access denied"); response.response_code = 403; response.response_text = "Forbidden"; } else if (ast_ends_with(uri, "/")) { remove_trailing_slash(uri, &response); } else if (ast_begins_with(uri, "api-docs/")) { /* Serving up API docs */ if (method != AST_HTTP_GET) { response.message = ast_json_pack("{s: s}", "message", "Unsupported method"); response.response_code = 405; response.response_text = "Method Not Allowed"; } else { /* Skip the api-docs prefix */ ast_ari_get_docs(strchr(uri, '/') + 1, headers, &response); } } else { /* Other RESTful resources */ ast_ari_invoke(ser, uri, method, get_params, headers, &response); } if (response.no_response) { /* The handler indicates no further response is necessary. * Probably because it already handled it */ return 0; } /* If you explicitly want to have no content, set message to * ast_json_null(). */ ast_assert(response.message != NULL); ast_assert(response.response_code > 0); ast_str_append(&response_headers, 0, "%s", ast_str_buffer(response.headers)); /* response.message could be NULL, in which case the empty response_body * is correct */ if (response.message && !ast_json_is_null(response.message)) { ast_str_append(&response_headers, 0, "Content-type: application/json\r\n"); if (ast_json_dump_str_format(response.message, &response_body, conf->general->format) != 0) { /* Error encoding response */ response.response_code = 500; response.response_text = "Internal Server Error"; ast_str_set(&response_body, 0, "%s", ""); ast_str_set(&response_headers, 0, "%s", ""); ret = -1; } } ast_http_send(ser, method, response.response_code, response.response_text, response_headers, response_body, 0, 0); /* ast_http_send takes ownership, so we don't have to free them */ response_headers = NULL; response_body = NULL; ast_json_unref(response.message); return ret; }