Skip to content
Snippets Groups Projects
manager.c 282 KiB
Newer Older
  • Learn to ignore specific revisions
  • Luigi Rizzo's avatar
    Luigi Rizzo committed
    /*! \note NOTE: XXX this comment is unclear and possibly wrong.
    
       Callers of astman_send_error(), astman_send_response() or astman_send_ack() must EITHER
    
       hold the session lock _or_ be running in an action callback (in which case s->session->busy will
    
       be non-zero). In either of these cases, there is no need to lock-protect the session's
       fd, since no other output will be sent (events will be queued), and no input will
       be read until either the current action finishes or get_input() obtains the session
       lock.
     */
    
    /*! \todo XXX MSG_MOREDATA should go to a header file. */
    #define MSG_MOREDATA	((char *)astman_send_response)
    
    
    /*! \brief send a response with an optional message,
     * and terminate it with an empty line.
     * m is used only to grab the 'ActionID' field.
     *
     * Use the explicit constant MSG_MOREDATA to remove the empty line.
     * XXX MSG_MOREDATA should go to a header file.
     */
    
    static void astman_send_response_full(struct mansession *s, const struct message *m, char *resp, char *msg, char *listflag)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	const char *id = astman_get_header(m, "ActionID");
    
    	astman_append(s, "Response: %s\r\n", resp);
    
    		astman_append(s, "ActionID: %s\r\n", id);
    
    		astman_append(s, "EventList: %s\r\n", listflag);	/* Start, complete, cancelled */
    
    		astman_append(s, "Message: %s\r\n\r\n", msg);
    
    		astman_append(s, "\r\n");
    
    void astman_send_response(struct mansession *s, const struct message *m, char *resp, char *msg)
    
    void astman_send_error(struct mansession *s, const struct message *m, char *error)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	astman_send_response_full(s, m, "Error", error, NULL);
    
    void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...)
    {
    
    	va_list ap;
    	struct ast_str *buf;
    
    
    	if (!(buf = ast_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE))) {
    		return;
    	}
    
    	va_start(ap, fmt);
    
    	res = ast_str_set_va(&buf, 0, fmt, ap);
    
    	if (res == AST_DYNSTR_BUILD_FAILED) {
    		return;
    	}
    
    	/* astman_append will use the same underlying buffer, so copy the message out
    	 * before sending the response */
    	msg = ast_str_buffer(buf);
    	if (msg) {
    		msg = ast_strdupa(msg);
    	}
    	astman_send_response_full(s, m, "Error", msg, NULL);
    
    void astman_send_ack(struct mansession *s, const struct message *m, char *msg)
    
    	astman_send_response_full(s, m, "Success", msg, NULL);
    
    static void astman_start_ack(struct mansession *s, const struct message *m)
    
    	astman_send_response_full(s, m, "Success", MSG_MOREDATA, NULL);
    }
    
    
    void astman_send_listack(struct mansession *s, const struct message *m, char *msg, char *listflag)
    
    	astman_send_response_full(s, m, "Success", msg, listflag);
    
    }
    
    void astman_send_list_complete_start(struct mansession *s, const struct message *m, const char *event_name, int count)
    {
    	const char *id = astman_get_header(m, "ActionID");
    
    	astman_append(s, "Event: %s\r\n", event_name);
    	if (!ast_strlen_zero(id)) {
    		astman_append(s, "ActionID: %s\r\n", id);
    	}
    	astman_append(s,
    		"EventList: Complete\r\n"
    		"ListItems: %d\r\n",
    		count);
    }
    
    void astman_send_list_complete_end(struct mansession *s)
    {
    	astman_append(s, "\r\n");
    
    /*! \brief Lock the 'mansession' structure. */
    static void mansession_lock(struct mansession *s)
    {
    	ast_mutex_lock(&s->lock);
    }
    
    /*! \brief Unlock the 'mansession' structure. */
    static void mansession_unlock(struct mansession *s)
    {
    	ast_mutex_unlock(&s->lock);
    }
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
       Rather than braindead on,off this now can also accept a specific int mask value
    
       or a ',' delim list of mask strings (the same as manager.conf) -anthm
    */
    
    static int set_eventmask(struct mansession *s, const char *eventmask)
    
    	int maskint = strings_to_mask(eventmask);
    
    	ao2_lock(s->session);
    
    		s->session->send_events = maskint;
    
    	ao2_unlock(s->session);
    
    static enum ast_transport mansession_get_transport(const struct mansession *s)
    
    	return s->tcptls_session->parent->tls_cfg ? AST_TRANSPORT_TLS :
    			AST_TRANSPORT_TCP;
    
    }
    
    static void report_invalid_user(const struct mansession *s, const char *username)
    {
    	char session_id[32];
    	struct ast_security_event_inval_acct_id inval_acct_id = {
    		.common.event_type = AST_SECURITY_EVENT_INVAL_ACCT_ID,
    		.common.version    = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s);
    
    	ast_security_event_report(AST_SEC_EVT(&inval_acct_id));
    }
    
    static void report_failed_acl(const struct mansession *s, const char *username)
    {
    	char session_id[32];
    	struct ast_security_event_failed_acl failed_acl_event = {
    		.common.event_type = AST_SECURITY_EVENT_FAILED_ACL,
    		.common.version    = AST_SECURITY_EVENT_FAILED_ACL_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    
    	ast_security_event_report(AST_SEC_EVT(&failed_acl_event));
    }
    
    static void report_inval_password(const struct mansession *s, const char *username)
    {
    	char session_id[32];
    	struct ast_security_event_inval_password inval_password = {
    		.common.event_type = AST_SECURITY_EVENT_INVAL_PASSWORD,
    		.common.version    = AST_SECURITY_EVENT_INVAL_PASSWORD_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    
    	ast_security_event_report(AST_SEC_EVT(&inval_password));
    }
    
    static void report_auth_success(const struct mansession *s)
    {
    	char session_id[32];
    	struct ast_security_event_successful_auth successful_auth = {
    		.common.event_type = AST_SECURITY_EVENT_SUCCESSFUL_AUTH,
    		.common.version    = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = s->session->username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    
    	ast_security_event_report(AST_SEC_EVT(&successful_auth));
    }
    
    static void report_req_not_allowed(const struct mansession *s, const char *action)
    {
    	char session_id[32];
    	char request_type[64];
    	struct ast_security_event_req_not_allowed req_not_allowed = {
    		.common.event_type = AST_SECURITY_EVENT_REQ_NOT_ALLOWED,
    		.common.version    = AST_SECURITY_EVENT_REQ_NOT_ALLOWED_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = s->session->username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    
    		.request_type      = request_type,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    	snprintf(request_type, sizeof(request_type), "Action: %s", action);
    
    	ast_security_event_report(AST_SEC_EVT(&req_not_allowed));
    }
    
    static void report_req_bad_format(const struct mansession *s, const char *action)
    {
    	char session_id[32];
    	char request_type[64];
    	struct ast_security_event_req_bad_format req_bad_format = {
    		.common.event_type = AST_SECURITY_EVENT_REQ_BAD_FORMAT,
    		.common.version    = AST_SECURITY_EVENT_REQ_BAD_FORMAT_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = s->session->username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    
    		.request_type      = request_type,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    	snprintf(request_type, sizeof(request_type), "Action: %s", action);
    
    	ast_security_event_report(AST_SEC_EVT(&req_bad_format));
    }
    
    static void report_failed_challenge_response(const struct mansession *s,
    		const char *response, const char *expected_response)
    {
    	char session_id[32];
    	struct ast_security_event_chal_resp_failed chal_resp_failed = {
    		.common.event_type = AST_SECURITY_EVENT_CHAL_RESP_FAILED,
    		.common.version    = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = s->session->username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    
    		.challenge         = s->session->challenge,
    		.response          = response,
    		.expected_response = expected_response,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    
    	ast_security_event_report(AST_SEC_EVT(&chal_resp_failed));
    }
    
    static void report_session_limit(const struct mansession *s)
    {
    	char session_id[32];
    	struct ast_security_event_session_limit session_limit = {
    		.common.event_type = AST_SECURITY_EVENT_SESSION_LIMIT,
    		.common.version    = AST_SECURITY_EVENT_SESSION_LIMIT_VERSION,
    		.common.service    = "AMI",
    		.common.account_id = s->session->username,
    		.common.session_tv = &s->session->sessionstart_tv,
    		.common.local_addr = {
    
    			.addr      = &s->tcptls_session->parent->local_address,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.remote_addr = {
    
    			.addr      = &s->session->addr,
    
    			.transport = mansession_get_transport(s),
    		},
    		.common.session_id = session_id,
    	};
    
    	snprintf(session_id, sizeof(session_id), "%p", s->session);
    
    	ast_security_event_report(AST_SEC_EVT(&session_limit));
    }
    
    
    /*
     * Here we start with action_ handlers for AMI actions,
     * and the internal functions used by them.
     * Generally, the handlers are called action_foo()
     */
    
    /* helper function for action_login() */
    
    static int authenticate(struct mansession *s, const struct message *m)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	const char *username = astman_get_header(m, "Username");
    	const char *password = astman_get_header(m, "Secret");
    
    	regex_t *regex_filter;
    	struct ao2_iterator filter_iter;
    
    	if (ast_strlen_zero(username)) {	/* missing username */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    
    	/* locate user in locked state */
    	AST_RWLIST_WRLOCK(&users);
    
    	if (!(user = get_manager_by_name_locked(username))) {
    
    		ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_sockaddr_stringify_addr(&s->session->addr), username);
    
    	} else if (user->acl && (ast_apply_acl(user->acl, &s->session->addr, "Manager User ACL: ") == AST_SENSE_DENY)) {
    
    		ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_sockaddr_stringify_addr(&s->session->addr), username);
    
    	} else if (!strcasecmp(astman_get_header(m, "AuthType"), "MD5")) {
    
    		const char *key = astman_get_header(m, "Key");
    
    		if (!ast_strlen_zero(key) && !ast_strlen_zero(s->session->challenge) && user->secret) {
    
    			int x;
    			int len = 0;
    			char md5key[256] = "";
    			struct MD5Context md5;
    			unsigned char digest[16];
    
    			MD5Init(&md5);
    
    			MD5Update(&md5, (unsigned char *) s->session->challenge, strlen(s->session->challenge));
    
    			MD5Update(&md5, (unsigned char *) user->secret, strlen(user->secret));
    
    			MD5Final(digest, &md5);
    
    			for (x = 0; x < 16; x++)
    
    				len += sprintf(md5key + len, "%02hhx", digest[x]);
    
    			} else {
    				report_failed_challenge_response(s, key, md5key);
    
    			ast_debug(1, "MD5 authentication is not possible.  challenge: '%s'\n",
    
    				S_OR(s->session->challenge, ""));
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    	} else if (user->secret) {
    		if (!strcmp(password, user->secret)) {
    			error = 0;
    		} else {
    			report_inval_password(s, username);
    		}
    
    		ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_sockaddr_stringify_addr(&s->session->addr), username);
    
    	/* All of the user parameters are copied to the session so that in the event
    
    	* of a reload and a configuration change, the session parameters are not
    	* changed. */
    
    	ast_copy_string(s->session->username, username, sizeof(s->session->username));
    	s->session->readperm = user->readperm;
    	s->session->writeperm = user->writeperm;
    	s->session->writetimeout = user->writetimeout;
    
    	if (user->chanvars) {
    		s->session->chanvars = ast_variables_dup(user->chanvars);
    	}
    
    	filter_iter = ao2_iterator_init(user->whitefilters, 0);
    	while ((regex_filter = ao2_iterator_next(&filter_iter))) {
    		ao2_t_link(s->session->whitefilters, regex_filter, "add white user filter to session");
    		ao2_t_ref(regex_filter, -1, "remove iterator ref");
    	}
    	ao2_iterator_destroy(&filter_iter);
    
    	filter_iter = ao2_iterator_init(user->blackfilters, 0);
    	while ((regex_filter = ao2_iterator_next(&filter_iter))) {
    		ao2_t_link(s->session->blackfilters, regex_filter, "add black user filter to session");
    		ao2_t_ref(regex_filter, -1, "remove iterator ref");
    
    	ao2_iterator_destroy(&filter_iter);
    
    
    	s->session->sessionstart = time(NULL);
    
    	s->session->sessionstart_tv = ast_tvnow();
    
    	set_eventmask(s, astman_get_header(m, "Events"));
    
    static int action_ping(struct mansession *s, const struct message *m)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Jeff Peeler's avatar
    Jeff Peeler committed
    	const char *actionid = astman_get_header(m, "ActionID");
    
    	struct timeval now = ast_tvnow();
    
    Jeff Peeler's avatar
    Jeff Peeler committed
    
    	astman_append(s, "Response: Success\r\n");
    	if (!ast_strlen_zero(actionid)){
    		astman_append(s, "ActionID: %s\r\n", actionid);
    	}
    
    	astman_append(
    		s,
    		"Ping: Pong\r\n"
    		"Timestamp: %ld.%06lu\r\n"
    		"\r\n",
    
    		(long) now.tv_sec, (unsigned long) now.tv_usec);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static int action_getconfig(struct mansession *s, const struct message *m)
    
    	const char *fn = astman_get_header(m, "Filename");
    
    	const char *category = astman_get_header(m, "Category");
    
    	const char *filter = astman_get_header(m, "Filter");
    	const char *category_name;
    
    	int catcount = 0;
    	int lineno = 0;
    
    	struct ast_category *cur_category = NULL;
    
    	struct ast_variable *v;
    
    	struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
    
    
    	if (ast_strlen_zero(fn)) {
    		astman_send_error(s, m, "Filename not specified");
    		return 0;
    	}
    
    	cfg = ast_config_load2(fn, "manager", config_flags);
    
    	if (cfg == CONFIG_STATUS_FILEMISSING) {
    
    		astman_send_error(s, m, "Config file not found");
    		return 0;
    
    	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
    		astman_send_error(s, m, "Config file has invalid format");
    		return 0;
    
    	while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) {
    		struct ast_str *templates;
    
    		category_name = ast_category_get_name(cur_category);
    		lineno = 0;
    		astman_append(s, "Category-%06d: %s\r\n", catcount, category_name);
    
    		if (ast_category_is_template(cur_category)) {
    			astman_append(s, "IsTemplate-%06d: %d\r\n", catcount, 1);
    		}
    
    		if ((templates = ast_category_get_templates(cur_category))
    			&& ast_str_strlen(templates) > 0) {
    			astman_append(s, "Templates-%06d: %s\r\n", catcount, ast_str_buffer(templates));
    			ast_free(templates);
    
    
    		for (v = ast_category_first(cur_category); v; v = v->next) {
    			astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
    		}
    
    		catcount++;
    
    	if (!ast_strlen_zero(category) && catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */
    
    		astman_append(s, "No categories found\r\n");
    
    	ast_config_destroy(cfg);
    	astman_append(s, "\r\n");
    
    	return 0;
    }
    
    static int action_listcategories(struct mansession *s, const struct message *m)
    {
    	struct ast_config *cfg;
    	const char *fn = astman_get_header(m, "Filename");
    
    	const char *match = astman_get_header(m, "Match");
    	struct ast_category *category = NULL;
    
    	struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
    	int catcount = 0;
    
    	if (ast_strlen_zero(fn)) {
    		astman_send_error(s, m, "Filename not specified");
    		return 0;
    	}
    
    	if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
    
    		astman_send_error(s, m, "Config file not found");
    		return 0;
    	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
    		astman_send_error(s, m, "Config file has invalid format");
    
    	while ((category = ast_category_browse_filtered(cfg, NULL, category, match))) {
    		astman_append(s, "Category-%06d: %s\r\n", catcount, ast_category_get_name(category));
    
    	if (catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */
    
    		astman_append(s, "Error: no categories found\r\n");
    
    	ast_config_destroy(cfg);
    	astman_append(s, "\r\n");
    
    /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
    static void json_escape(char *out, const char *in)
    {
    	for (; *in; in++) {
    
    		if (*in == '\\' || *in == '\"') {
    
    /*!
     * \internal
     * \brief Append a JSON escaped string to the manager stream.
     *
     * \param s AMI stream to append a string.
     * \param str String to append to the stream after JSON escaping it.
     *
     * \return Nothing
     */
    static void astman_append_json(struct mansession *s, const char *str)
    {
    	char *buf;
    
    
    	buf = ast_alloca(2 * strlen(str) + 1);
    
    	json_escape(buf, str);
    	astman_append(s, "%s", buf);
    }
    
    
    static int action_getconfigjson(struct mansession *s, const struct message *m)
    {
    	struct ast_config *cfg;
    	const char *fn = astman_get_header(m, "Filename");
    
    	const char *filter = astman_get_header(m, "Filter");
    	const char *category = astman_get_header(m, "Category");
    	struct ast_category *cur_category = NULL;
    	const char *category_name;
    
    	struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
    
    
    	if (ast_strlen_zero(fn)) {
    		astman_send_error(s, m, "Filename not specified");
    		return 0;
    	}
    
    
    	if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
    
    		astman_send_error(s, m, "Config file not found");
    		return 0;
    
    	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
    		astman_send_error(s, m, "Config file has invalid format");
    		return 0;
    
    	}
    
    	astman_start_ack(s, m);
    	astman_append(s, "JSON: {");
    
    	while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) {
    
    		category_name = ast_category_get_name(cur_category);
    
    		astman_append(s, "%s\"", comma1 ? "," : "");
    
    		astman_append_json(s, category_name);
    
    		astman_append(s, "\":{");
    
    
    		if (ast_category_is_template(cur_category)) {
    
    			astman_append(s, "\"istemplate\":1");
    
    			comma2 = 1;
    		}
    
    		if ((templates = ast_category_get_templates(cur_category))
    			&& ast_str_strlen(templates) > 0) {
    			astman_append(s, "%s", comma2 ? "," : "");
    
    			astman_append(s, "\"templates\":\"%s\"", ast_str_buffer(templates));
    
    			ast_free(templates);
    			comma2 = 1;
    		}
    
    		for (v = ast_category_first(cur_category); v; v = v->next) {
    
    			astman_append(s, "%s\"", comma2 ? "," : "");
    			astman_append_json(s, v->name);
    			astman_append(s, "\":\"");
    			astman_append_json(s, v->value);
    			astman_append(s, "\"");
    			comma2 = 1;
    
    		astman_append(s, "}");
    
    /*! \brief helper function for action_updateconfig */
    
    static enum error_type handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
    
    	const char *action, *cat, *var, *value, *match, *line, *options;
    
    	struct ast_variable *v;
    
    	struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
    	enum error_type result = 0;
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    	for (x = 0; x < 100000; x++) {	/* 100000 = the max number of allowed updates + 1 */
    
    		unsigned int object = 0;
    
    		char *dupoptions;
    		int allowdups = 0;
    		int istemplate = 0;
    		int ignoreerror = 0;
    		char *inherit = NULL;
    		char *catfilter = NULL;
    		char *token;
    		int foundvar = 0;
    		int foundcat = 0;
    		struct ast_category *category = NULL;
    
    		snprintf(hdr, sizeof(hdr), "Action-%06d", x);
    		action = astman_get_header(m, hdr);
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    		if (ast_strlen_zero(action))		/* breaks the for loop if no action header */
    
    			break;							/* this could cause problems if actions come in misnumbered */
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    
    
    		snprintf(hdr, sizeof(hdr), "Cat-%06d", x);
    		cat = astman_get_header(m, hdr);
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    		if (ast_strlen_zero(cat)) {		/* every action needs a category */
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    			result =  UNSPECIFIED_CATEGORY;
    			break;
    		}
    
    
    		snprintf(hdr, sizeof(hdr), "Var-%06d", x);
    
    		var = astman_get_header(m, hdr);
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    
    
    		snprintf(hdr, sizeof(hdr), "Value-%06d", x);
    
    		value = astman_get_header(m, hdr);
    
    
    		if (!ast_strlen_zero(value) && *value == '>') {
    			object = 1;
    			value++;
    		}
    
    		snprintf(hdr, sizeof(hdr), "Match-%06d", x);
    		match = astman_get_header(m, hdr);
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    
    
    		snprintf(hdr, sizeof(hdr), "Line-%06d", x);
    		line = astman_get_header(m, hdr);
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    
    
    		snprintf(hdr, sizeof(hdr), "Options-%06d", x);
    		options = astman_get_header(m, hdr);
    		if (!ast_strlen_zero(options)) {
    			dupoptions = ast_strdupa(options);
    			while ((token = ast_strsep(&dupoptions, ',', AST_STRSEP_STRIP))) {
    				if (!strcasecmp("allowdups", token)) {
    					allowdups = 1;
    					continue;
    				}
    				if (!strcasecmp("template", token)) {
    					istemplate = 1;
    					continue;
    				}
    				if (!strcasecmp("ignoreerror", token)) {
    					ignoreerror = 1;
    					continue;
    				}
    				if (ast_begins_with(token, "inherit")) {
    					char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
    					c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
    					if (c) {
    						inherit = ast_strdupa(c);
    					}
    					continue;
    				}
    				if (ast_begins_with(token, "catfilter")) {
    					char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
    					c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
    					if (c) {
    						catfilter = ast_strdupa(c);
    					}
    					continue;
    				}
    			}
    		}
    
    
    		if (!strcasecmp(action, "newcat")) {
    
    			struct ast_category *template;
    			char *tmpl_name = NULL;
    
    			if (!allowdups) {
    				if (ast_category_get(cfg, cat, "TEMPLATES=include")) {
    					if (ignoreerror) {
    						continue;
    					} else {
    						result = FAILURE_NEWCAT;	/* already exist */
    						break;
    					}
    				}
    			}
    
    			if (istemplate) {
    				category = ast_category_new_template(cat, dfn, -1);
    			} else {
    				category = ast_category_new(cat, dfn, -1);
    
    
    			if (inherit) {
    				while ((tmpl_name = ast_strsep(&inherit, ',', AST_STRSEP_STRIP))) {
    					if ((template = ast_category_get(cfg, tmpl_name, "TEMPLATES=restrict"))) {
    
    						if (ast_category_inherit(category, template)) {
    							result = FAILURE_ALLOCATION;
    							break;
    						}
    
    					} else {
    						ast_category_destroy(category);
    						category = NULL;
    						result = FAILURE_TEMPLATE;	/* template not found */
    						break;
    					}
    				}
    			}
    
    			if (category != NULL) {
    				if (ast_strlen_zero(match)) {
    					ast_category_append(cfg, category);
    				} else {
    					if (ast_category_insert(cfg, category, match)) {
    						ast_category_destroy(category);
    						result = FAILURE_NEWCAT;
    						break;
    					}
    
    		} else if (!strcasecmp(action, "renamecat")) {
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    			if (ast_strlen_zero(value)) {
    
    
    			foundcat = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				ast_category_rename(category, value);
    				foundcat = 1;
    			}
    
    			if (!foundcat) {
    
    		} else if (!strcasecmp(action, "delcat")) {
    
    			foundcat = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				category = ast_category_delete(cfg, category);
    				foundcat = 1;
    			}
    
    			if (!foundcat && !ignoreerror) {
    				result = UNKNOWN_CATEGORY;
    
    		} else if (!strcasecmp(action, "emptycat")) {
    
    			foundcat = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				ast_category_empty(category);
    				foundcat = 1;
    			}
    
    			if (!foundcat) {
    				result = UNKNOWN_CATEGORY;
    
    		} else if (!strcasecmp(action, "update")) {
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    			if (ast_strlen_zero(var)) {
    
    
    			foundcat = 0;
    			foundvar = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				if (!ast_variable_update(category, var, value, match, object)) {
    					foundvar = 1;
    				}
    				foundcat = 1;
    			}
    
    			if (!foundcat) {
    
    		} else if (!strcasecmp(action, "delete")) {
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    			if ((ast_strlen_zero(var) && ast_strlen_zero(line))) {
    
    
    			foundcat = 0;
    			foundvar = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				if (!ast_variable_delete(category, var, match, line)) {
    					foundvar = 1;
    				}
    				foundcat = 1;
    			}
    
    			if (!foundcat) {
    
    
    			if (!foundvar && !ignoreerror) {
    				result = FAILURE_UPDATE;
    
    		} else if (!strcasecmp(action, "append")) {
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    			if (ast_strlen_zero(var)) {
    
    
    			foundcat = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				if (!(v = ast_variable_new(var, value, dfn))) {
    					result = FAILURE_ALLOCATION;
    					break;
    				}
    				if (object || (match && !strcasecmp(match, "object"))) {
    					v->object = 1;
    				}
    				ast_variable_append(category, v);
    				foundcat = 1;
    
    		} else if (!strcasecmp(action, "insert")) {
    
    Pari Nannapaneni's avatar
     
    Pari Nannapaneni committed
    			if (ast_strlen_zero(var) || ast_strlen_zero(line)) {
    
    
    			foundcat = 0;
    			while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
    				if (!(v = ast_variable_new(var, value, dfn))) {
    					result = FAILURE_ALLOCATION;
    					break;
    				}
    				ast_variable_insert(category, v, line);
    				foundcat = 1;
    
    		}
    		else {
    			ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action);
    
    	ast_free(str1);
    	ast_free(str2);
    	return result;
    
    static int action_updateconfig(struct mansession *s, const struct message *m)
    
    	const char *sfn = astman_get_header(m, "SrcFilename");
    	const char *dfn = astman_get_header(m, "DstFilename");
    
    	const char *rld = astman_get_header(m, "Reload");
    
    	int preserve_effective_context = CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT;
    	const char *preserve_effective_context_string = astman_get_header(m, "PreserveEffectiveContext");
    
    	struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
    
    	enum error_type result;
    
    
    	if (ast_strlen_zero(sfn) || ast_strlen_zero(dfn)) {
    		astman_send_error(s, m, "Filename not specified");
    		return 0;
    	}
    
    	if (!(cfg = ast_config_load2(sfn, "manager", config_flags))) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		astman_send_error(s, m, "Config file not found");
    		return 0;
    
    	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
    		astman_send_error(s, m, "Config file has invalid format");
    		return 0;
    
    	result = handle_updates(s, m, cfg, dfn);
    	if (!result) {
    		ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
    
    		if (!ast_strlen_zero(preserve_effective_context_string) && !ast_true(preserve_effective_context_string)) {
    			preserve_effective_context = CONFIG_SAVE_FLAG_NONE;
    		}
    		res = ast_config_text_file_save2(dfn, cfg, "Manager", preserve_effective_context);
    
    		ast_config_destroy(cfg);
    		if (res) {
    			astman_send_error(s, m, "Save of config failed");
    			return 0;
    		}
    		astman_send_ack(s, m, NULL);
    		if (!ast_strlen_zero(rld)) {