diff options
-rw-r--r-- | CHANGES | 6 | ||||
-rw-r--r-- | channels/chan_iax2.c | 107 | ||||
-rw-r--r-- | channels/chan_sip.c | 161 | ||||
-rw-r--r-- | channels/sip/include/sip.h | 10 | ||||
-rw-r--r-- | configs/acl.conf.sample | 86 | ||||
-rw-r--r-- | configs/extconfig.conf.sample | 6 | ||||
-rw-r--r-- | configs/iax.conf.sample | 11 | ||||
-rw-r--r-- | configs/manager.conf.sample | 1 | ||||
-rw-r--r-- | configs/sip.conf.sample | 10 | ||||
-rw-r--r-- | include/asterisk/acl.h | 145 | ||||
-rw-r--r-- | include/asterisk/channel.h | 1 | ||||
-rw-r--r-- | include/asterisk/config.h | 22 | ||||
-rw-r--r-- | include/asterisk/event_defs.h | 4 | ||||
-rw-r--r-- | main/acl.c | 244 | ||||
-rw-r--r-- | main/asterisk.c | 6 | ||||
-rw-r--r-- | main/config.c | 135 | ||||
-rw-r--r-- | main/loader.c | 2 | ||||
-rw-r--r-- | main/manager.c | 76 | ||||
-rw-r--r-- | main/named_acl.c | 558 |
19 files changed, 1455 insertions, 136 deletions
@@ -46,6 +46,12 @@ Core have their own verbosity level. The command 'core set verbose' will now set a separate level for each remote console without affecting any other console. + * Named ACLs can now be specified in acl.conf and used in configurations that + use ACLs. As a general rule, if some derivative of 'permit' or 'deny' is + used to specify an ACL, a similar form of 'acl' will add a named ACL to the + working ACL. In addition, some CLI commands have + been added to provide informational and configuration reload capabilities to + this feature ('acl show [named acl]' and 'reload acl'). * Hangup handlers can be attached to channels using the CHANNEL(hangup_handler_xxx) options. Hangup handlers will run when the channel is hung up similar to the h extension. diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index ad6cdd15c..cc51624f2 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -275,6 +275,7 @@ static char language[MAX_LANGUAGE] = ""; static char regcontext[AST_MAX_CONTEXT] = ""; static struct ast_event_sub *network_change_event_subscription; /*!< subscription id for network change events */ +static struct ast_event_sub *acl_change_event_subscription; /*!< subscription id for ACL change events */ static int network_change_event_sched_id = -1; static int maxauthreq = 3; @@ -438,7 +439,7 @@ struct iax2_context { #define IAX_SHRINKCALLERID (uint64_t)(1 << 31) /*!< Turn on and off caller id shrinking */ static int global_rtautoclear = 120; -static int reload_config(void); +static int reload_config(int forced_reload); /*! * \brief Call token validation settings. @@ -479,7 +480,7 @@ struct iax2_user { int maxauthreq; /*!< Maximum allowed outstanding AUTHREQs */ int curauthreq; /*!< Current number of outstanding AUTHREQs */ struct ast_codec_pref prefs; - struct ast_ha *ha; + struct ast_acl_list *acl; struct iax2_context *contexts; struct ast_variable *vars; enum calltoken_peer_enum calltoken_required; /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */ @@ -539,7 +540,7 @@ struct iax2_peer { struct ast_event_sub *mwi_event_sub; - struct ast_ha *ha; + struct ast_acl_list *acl; enum calltoken_peer_enum calltoken_required; /*!< Is calltoken validation required or not, can be YES, NO, or AUTO */ }; @@ -1242,6 +1243,7 @@ static struct callno_entry *get_unused_callno(int trunk, int validated); static int replace_callno(const void *obj); static void sched_delay_remove(struct sockaddr_in *sin, struct callno_entry *callno_entry); static void network_change_event_cb(const struct ast_event *, void *); +static void acl_change_event_cb(const struct ast_event *, void *); static struct ast_channel_tech iax2_tech = { .type = "IAX2", @@ -1324,6 +1326,21 @@ static void network_change_event_unsubscribe(void) } } +static void acl_change_event_subscribe(void) +{ + if (!acl_change_event_subscription) { + acl_change_event_subscription = ast_event_subscribe(AST_EVENT_ACL_CHANGE, + acl_change_event_cb, "IAX2 ACL Change", NULL, AST_EVENT_IE_END); + } +} + +static void acl_change_event_unsubscribe(void) +{ + if (acl_change_event_subscription) { + acl_change_event_subscription = ast_event_unsubscribe(acl_change_event_subscription); + } +} + static int network_change_event_sched_cb(const void *data) { struct iax2_registry *reg; @@ -1346,6 +1363,12 @@ static void network_change_event_cb(const struct ast_event *event, void *userdat } +static void acl_change_event_cb(const struct ast_event *event, void *userdata) +{ + ast_log(LOG_NOTICE, "Reloading chan_iax2 in response to ACL change event.\n"); + reload_config(1); +} + /*! \brief Send manager event at call setup to link between Asterisk channel name and IAX2 call identifiers */ @@ -3818,7 +3841,7 @@ static char *handle_cli_iax2_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli(a->fd, " Encryption : %s\n", peer->encmethods ? ast_str_buffer(encmethods) : "No"); ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), peer->cid_name, peer->cid_num, "<unspecified>")); ast_cli(a->fd, " Expire : %d\n", peer->expire); - ast_cli(a->fd, " ACL : %s\n", (peer->ha ? "Yes" : "No")); + ast_cli(a->fd, " ACL : %s\n", (ast_acl_list_is_empty(peer->acl) ? "No" : "Yes")); ast_cli(a->fd, " Addr->IP : %s Port %d\n", peer_addr.sin_addr.s_addr ? ast_inet_ntoa(peer_addr.sin_addr) : "(Unspecified)", ntohs(peer_addr.sin_port)); ast_cli(a->fd, " Defaddr->IP : %s Port %d\n", ast_inet_ntoa(peer->defaddr.sin_addr), ntohs(peer->defaddr.sin_port)); ast_cli(a->fd, " Username : %s\n", peer->username); @@ -6701,7 +6724,7 @@ static char *handle_cli_iax2_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli(a->fd, FORMAT2, user->name, auth, user->authmethods, user->contexts ? user->contexts->context : DEFAULT_CONTEXT, - user->ha ? "Yes" : "No", pstr); + ast_acl_list_is_empty(user->acl) ? "No" : "Yes", pstr); } ao2_iterator_destroy(&i); @@ -7681,7 +7704,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies while ((user = ao2_iterator_next(&i))) { if ((ast_strlen_zero(iaxs[callno]->username) || /* No username specified */ !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */ - && ast_apply_ha(user->ha, &addr) /* Access is permitted from this IP */ + && ast_apply_acl(user->acl, &addr, "IAX2 user ACL: ") /* Access is permitted from this IP */ && (ast_strlen_zero(iaxs[callno]->context) || /* No context specified */ apply_context(user->contexts, iaxs[callno]->context))) { /* Context is permitted */ if (!ast_strlen_zero(iaxs[callno]->username)) { @@ -7692,7 +7715,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies break; } else if (ast_strlen_zero(user->secret) && ast_strlen_zero(user->dbsecret) && ast_strlen_zero(user->inkeys)) { /* No required authentication */ - if (user->ha) { + if (user->acl) { /* There was host authentication and we passed, bonus! */ if (bestscore < 4) { bestscore = 4; @@ -7712,7 +7735,7 @@ static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies } } } else { - if (user->ha) { + if (user->acl) { /* Authentication, but host access too, eh, it's something.. */ if (bestscore < 2) { bestscore = 2; @@ -8072,7 +8095,7 @@ static int register_verify(int callno, struct sockaddr_in *sin, struct iax_ies * } ast_sockaddr_from_sin(&addr, sin); - if (!ast_apply_ha(p->ha, &addr)) { + if (!ast_apply_acl(p->acl, &addr, "IAX2 Peer ACL: ")) { if (authdebug) ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", ast_inet_ntoa(sin->sin_addr), p->name); goto return_unref; @@ -12514,7 +12537,7 @@ static void peer_destructor(void *obj) struct iax2_peer *peer = obj; int callno = peer->callno; - ast_free_ha(peer->ha); + ast_free_acl_list(peer->acl); if (callno > 0) { ast_mutex_lock(&iaxsl[callno]); @@ -12537,10 +12560,11 @@ static void peer_destructor(void *obj) static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int temponly) { struct iax2_peer *peer = NULL; - struct ast_ha *oldha = NULL; + struct ast_acl_list *oldacl = NULL; int maskfound = 0; int found = 0; int firstpass = 1; + int subscribe_acl_change = 0; if (!temponly) { peer = ao2_find(peers, name, OBJ_KEY); @@ -12551,8 +12575,8 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st if (peer) { found++; if (firstpass) { - oldha = peer->ha; - peer->ha = NULL; + oldacl = peer->acl; + peer->acl = NULL; } unlink_peer(peer); } else if ((peer = ao2_alloc(sizeof(*peer), peer_destructor))) { @@ -12683,8 +12707,9 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st } else if (!strcasecmp(v->name, "sourceaddress")) { peer_set_srcaddr(peer, v->value); } else if (!strcasecmp(v->name, "permit") || - !strcasecmp(v->name, "deny")) { - peer->ha = ast_append_ha(v->name, v->value, peer->ha, NULL); + !strcasecmp(v->name, "deny") || + !strcasecmp(v->name, "acl")) { + ast_append_acl(v->name, v->value, &peer->acl, NULL, &subscribe_acl_change); } else if (!strcasecmp(v->name, "mask")) { maskfound++; inet_aton(v->value, &peer->mask); @@ -12795,8 +12820,8 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st ast_clear_flag64(peer, IAX_DELME); } - if (oldha) - ast_free_ha(oldha); + if (oldacl) + ast_free_acl_list(oldacl); if (!ast_strlen_zero(peer->mailbox)) { char *mailbox, *context; @@ -12810,6 +12835,10 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st AST_EVENT_IE_END); } + if (subscribe_acl_change) { + acl_change_event_subscribe(); + } + return peer; } @@ -12817,7 +12846,7 @@ static void user_destructor(void *obj) { struct iax2_user *user = obj; - ast_free_ha(user->ha); + ast_free_acl_list(user->acl); free_context(user->contexts); if(user->vars) { ast_variables_destroy(user->vars); @@ -12831,11 +12860,12 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st { struct iax2_user *user = NULL; struct iax2_context *con, *conl = NULL; - struct ast_ha *oldha = NULL; + struct ast_acl_list *oldacl = NULL; struct iax2_context *oldcon = NULL; int format; int firstpass=1; int oldcurauthreq = 0; + int subscribe_acl_change = 0; char *varname = NULL, *varval = NULL; struct ast_variable *tmpvar = NULL; @@ -12848,9 +12878,9 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st if (user) { if (firstpass) { oldcurauthreq = user->curauthreq; - oldha = user->ha; + oldacl = user->acl; oldcon = user->contexts; - user->ha = NULL; + user->acl = NULL; user->contexts = NULL; } /* Already in the list, remove it and it will be added back (or FREE'd) */ @@ -12899,8 +12929,9 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st conl = con; } } else if (!strcasecmp(v->name, "permit") || - !strcasecmp(v->name, "deny")) { - user->ha = ast_append_ha(v->name, v->value, user->ha, NULL); + !strcasecmp(v->name, "deny") || + !strcasecmp(v->name, "acl")) { + ast_append_acl(v->name, v->value, &user->acl, NULL, &subscribe_acl_change); } else if (!strcasecmp(v->name, "setvar")) { varname = ast_strdupa(v->value); if (varname && (varval = strchr(varname,'='))) { @@ -13069,10 +13100,17 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st ast_clear_flag64(user, IAX_DELME); } cleanup: - if (oldha) - ast_free_ha(oldha); - if (oldcon) + if (oldacl) { + ast_free_acl_list(oldacl); + } + if (oldcon) { free_context(oldcon); + } + + if (subscribe_acl_change) { + acl_change_event_subscribe(); + } + return user; } @@ -13171,7 +13209,7 @@ static void set_config_destroy(void) } /*! \brief Load configuration */ -static int set_config(const char *config_file, int reload) +static int set_config(const char *config_file, int reload, int forced) { struct ast_config *cfg, *ucfg; iax2_format capability = iax2_capability; @@ -13187,7 +13225,7 @@ static int set_config(const char *config_file, int reload) struct iax2_user *user; struct iax2_peer *peer; struct ast_netsock *ns; - struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + struct ast_flags config_flags = { (reload && !forced) ? CONFIG_FLAG_FILEUNCHANGED : 0 }; #if 0 static unsigned short int last_port=0; #endif @@ -13667,12 +13705,12 @@ static void poke_all_peers(void) } ao2_iterator_destroy(&i); } -static int reload_config(void) +static int reload_config(int forced_reload) { static const char config[] = "iax.conf"; struct iax2_registry *reg; - if (set_config(config, 1) > 0) { + if (set_config(config, 1, forced_reload) > 0) { prune_peers(); prune_users(); ao2_callback(callno_limits, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, prune_addr_range_cb, NULL); @@ -13711,14 +13749,14 @@ static char *handle_cli_iax2_reload(struct ast_cli_entry *e, int cmd, struct ast return NULL; } - reload_config(); + reload_config(0); return CLI_SUCCESS; } static int reload(void) { - return reload_config(); + return reload_config(0); } static int cache_get_callno_locked(const char *data) @@ -14483,6 +14521,7 @@ static int __unload_module(void) int x; network_change_event_unsubscribe(); + acl_change_event_unsubscribe(); ast_manager_unregister("IAXpeers"); ast_manager_unregister("IAXpeerlist"); @@ -14818,7 +14857,7 @@ static int users_data_provider_get(const struct ast_data_search *search, ast_data_add_int(data_enum_node, "value", user->amaflags); ast_data_add_str(data_enum_node, "text", ast_cdr_flags2str(user->amaflags)); - ast_data_add_bool(data_user, "access-control", user->ha ? 1 : 0); + ast_data_add_bool(data_user, "access-control", ast_acl_list_is_empty(user->acl) ? 0 : 1); if (ast_test_flag64(user, IAX_CODEC_NOCAP)) { pstr = "REQ only"; @@ -14922,7 +14961,7 @@ static int load_module(void) ast_timer_set_rate(timer, 1000 / trunkfreq); } - if (set_config(config, 0) == -1) { + if (set_config(config, 0, 0) == -1) { if (timer) { ast_timer_close(timer); } diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 70d645e15..0fee3a56b 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -793,6 +793,7 @@ static struct ast_flags global_flags[3] = {{0}}; /*!< global SIP_ flags */ static int global_t38_maxdatagram; /*!< global T.38 FaxMaxDatagram override */ static struct ast_event_sub *network_change_event_subscription; /*!< subscription id for network change events */ +static struct ast_event_sub *acl_change_event_subscription; /*!< subscription id for named ACL system change events */ static int network_change_event_sched_id = -1; static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */ @@ -1407,6 +1408,7 @@ static void sip_poke_all_peers(void); static void sip_peer_hold(struct sip_pvt *p, int hold); static void mwi_event_cb(const struct ast_event *, void *); static void network_change_event_cb(const struct ast_event *, void *); +static void acl_change_event_cb(const struct ast_event *event, void *userdata); static void sip_keepalive_all_peers(void); /*--- Applications, functions, CLI and manager command helpers */ @@ -4711,8 +4713,8 @@ static void sip_destroy_peer(struct sip_peer *peer) } register_peer_exten(peer, FALSE); - ast_free_ha(peer->ha); - ast_free_ha(peer->directmediaha); + ast_free_acl_list(peer->acl); + ast_free_acl_list(peer->directmediaacl); if (peer->selfdestruct) ast_atomic_fetchadd_int(&apeerobjs, -1); else if (!ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && peer->is_realtime) { @@ -5499,7 +5501,9 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer) dialog->noncodeccapability |= AST_RTP_DTMF; else dialog->noncodeccapability &= ~AST_RTP_DTMF; - dialog->directmediaha = ast_duplicate_ha_list(peer->directmediaha); + + dialog->directmediaacl = ast_duplicate_acl_list(peer->directmediaacl); + if (peer->call_limit) ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT); if (!dialog->portinuri) @@ -5992,9 +5996,8 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist) p->tsrtp = NULL; } - if (p->directmediaha) { - ast_free_ha(p->directmediaha); - p->directmediaha = NULL; + if (p->directmediaacl) { + p->directmediaacl = ast_free_acl_list(p->directmediaacl); } ast_string_field_free_memory(p); @@ -15034,8 +15037,8 @@ static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, st } /* Check that they're allowed to register at this IP */ - if (ast_apply_ha(sip_cfg.contact_ha, &peer->addr) != AST_SENSE_ALLOW || - ast_apply_ha(peer->contactha, &peer->addr) != AST_SENSE_ALLOW) { + if (ast_apply_acl(sip_cfg.contact_acl, &peer->addr, "SIP contact ACL: ") != AST_SENSE_ALLOW || + ast_apply_acl(peer->contactacl, &peer->addr, "SIP contact ACL: ") != AST_SENSE_ALLOW) { ast_log(LOG_WARNING, "Domain '%s' disallowed by contact ACL (violating IP %s)\n", hostport, ast_sockaddr_stringify_addr(&testsa)); ast_string_field_set(peer, fullcontact, ""); @@ -15485,6 +15488,22 @@ static void network_change_event_unsubscribe(void) } } +static void acl_change_event_subscribe(void) +{ + if (!acl_change_event_subscription) { + acl_change_event_subscription = ast_event_subscribe(AST_EVENT_ACL_CHANGE, + acl_change_event_cb, "Manager must react to Named ACL changes", NULL, AST_EVENT_IE_END); + } + +} + +static void acl_change_event_unsubscribe(void) +{ + if (acl_change_event_subscription) { + acl_change_event_subscription = ast_event_unsubscribe(acl_change_event_subscription); + } +} + static int network_change_event_sched_cb(const void *data) { network_change_event_sched_id = -1; @@ -15796,7 +15815,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock } peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0); - if (!(peer && ast_apply_ha(peer->ha, addr))) { + if (!(peer && ast_apply_acl(peer->acl, addr, "SIP Peer ACL: "))) { /* Peer fails ACL check */ if (peer) { sip_unref_peer(peer, "register_verify: sip_unref_peer: from sip_find_peer operation"); @@ -16950,7 +16969,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, return AUTH_DONT_KNOW; } - if (!ast_apply_ha(peer->ha, addr)) { + if (!ast_apply_acl(peer->acl, addr, "SIP Peer ACL: ")) { ast_debug(2, "Found peer '%s' for '%s', but fails host access\n", peer->name, of); sip_unref_peer(peer, "sip_unref_peer: check_peer_ok: from sip_find_peer call, early return of AUTH_ACL_FAILED"); return AUTH_ACL_FAILED; @@ -17754,7 +17773,7 @@ static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_arg user->secret, user->accountcode, user->context, - AST_CLI_YESNO(user->ha != NULL), + AST_CLI_YESNO(ast_acl_list_is_empty(user->acl) == 0), AST_CLI_YESNO(ast_test_flag(&user->flags[0], SIP_NAT_FORCE_RPORT))); ao2_unlock(user); sip_unref_peer(user, "sip show users"); @@ -18013,7 +18032,7 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT) ? ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? " A " : " a " : ast_test_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT) ? " N " : " ", /* NAT=yes? */ - peer->ha ? " A " : " ", /* permit/deny */ + (!ast_acl_list_is_empty(peer->acl)) ? " A " : " ", /* permit/deny */ tmp_port, status, peer->description ? peer->description : "", realtimepeers ? (peer->is_realtime ? "Cached RT" : "") : ""); @@ -18048,7 +18067,7 @@ static char *_sip_show_peers(int fd, int *total, struct mansession *s, const str ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "yes" : "no", ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT) ? "yes" : "no", /* VIDEOSUPPORT=yes? */ ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT) ? "yes" : "no", /* TEXTSUPPORT=yes? */ - peer->ha ? "yes" : "no", /* permit/deny */ + ast_acl_list_is_empty(peer->acl) ? "no" : "yes", /* permit/deny/acl */ status, realtimepeers ? (peer->is_realtime ? "yes" : "no") : "no", peer->description); @@ -18719,8 +18738,8 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct ast_cli(fd, " Insecure : %s\n", insecure2str(ast_test_flag(&peer->flags[0], SIP_INSECURE))); ast_cli(fd, " Force rport : %s\n", force_rport_string(peer->flags)); ast_cli(fd, " Symmetric RTP: %s\n", comedia_string(peer->flags)); - ast_cli(fd, " ACL : %s\n", AST_CLI_YESNO(peer->ha != NULL)); - ast_cli(fd, " DirectMedACL : %s\n", AST_CLI_YESNO(peer->directmediaha != NULL)); + ast_cli(fd, " ACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->acl) == 0)); + ast_cli(fd, " DirectMedACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(peer->directmediaacl) == 0)); ast_cli(fd, " T.38 support : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(fd, " T.38 EC mode : %s\n", faxec2str(ast_test_flag(&peer->flags[1], SIP_PAGE2_T38SUPPORT))); ast_cli(fd, " T.38 MaxDtgrm: %d\n", peer->t38_maxdatagram); @@ -18838,7 +18857,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct astman_append(s, "SIP-Comedia: %s\r\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA) ? (ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "A" : "a") : (ast_test_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP) ? "Y" : "N")); - astman_append(s, "ACL: %s\r\n", (peer->ha?"Y":"N")); + astman_append(s, "ACL: %s\r\n", (ast_acl_list_is_empty(peer->acl) ? "N" : "Y")); astman_append(s, "SIP-CanReinvite: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA)?"Y":"N")); astman_append(s, "SIP-DirectMedia: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_DIRECT_MEDIA)?"Y":"N")); astman_append(s, "SIP-PromiscRedir: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_PROMISCREDIR)?"Y":"N")); @@ -18989,7 +19008,7 @@ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args ast_cli(a->fd, " Pickupgroup : "); print_group(a->fd, user->pickupgroup, 0); ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "<unspecified>")); - ast_cli(a->fd, " ACL : %s\n", AST_CLI_YESNO(user->ha != NULL)); + ast_cli(a->fd, " ACL : %s\n", AST_CLI_YESNO(ast_acl_list_is_empty(user->acl) == 0)); ast_cli(a->fd, " Sess-Timers : %s\n", stmode2str(user->stimer.st_mode_oper)); ast_cli(a->fd, " Sess-Refresh : %s\n", strefresher2str(user->stimer.st_ref)); ast_cli(a->fd, " Sess-Expires : %d secs\n", user->stimer.st_max_se); @@ -27643,6 +27662,23 @@ static int restart_monitor(void) return 0; } +static void acl_change_event_cb(const struct ast_event *event, void *userdata) +{ + ast_log(LOG_NOTICE, "Reloading chan_sip in response to ACL change event.\n"); + + ast_mutex_lock(&sip_reload_lock); + + if (sip_reloading) { + ast_verbose("Previous SIP reload not yet done\n"); + } else { + sip_reloading = TRUE; + sip_reloadreason = CHANNEL_ACL_RELOAD; + } + + ast_mutex_unlock(&sip_reload_lock); + + restart_monitor(); +} /*! \brief Session-Timers: Restart session timer */ static void restart_session_timer(struct sip_pvt *p) @@ -29041,8 +29077,8 @@ static void add_peer_mailboxes(struct sip_peer *peer, const char *value) static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only) { struct sip_peer *peer = NULL; - struct ast_ha *oldha = NULL; - struct ast_ha *olddirectmediaha = NULL; + struct ast_acl_list *oldacl = NULL; + struct ast_acl_list *olddirectmediaacl = NULL; int found = 0; int firstpass = 1; uint16_t port = 0; @@ -29056,6 +29092,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str static int deprecation_warning = 1; int alt_fullcontact = alt ? 1 : 0, headercount = 0; struct ast_str *fullcontact = ast_str_alloca(512); + int acl_change_subscription_needed = 0; if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { /* Note we do NOT use sip_find_peer here, to avoid realtime recursion */ @@ -29104,10 +29141,10 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str /* Note that our peer HAS had its reference count increased */ if (firstpass) { - oldha = peer->ha; - peer->ha = NULL; - olddirectmediaha = peer->directmediaha; - peer->directmediaha = NULL; + oldacl = peer->acl; + peer->acl = NULL; + olddirectmediaacl = peer->directmediaacl; + peer->directmediaacl = NULL; set_peer_defaults(peer); /* Set peer defaults */ peer->type = 0; } @@ -29308,25 +29345,25 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str sip_unref_peer(peer, "sip_unref_peer: from build_peer defaultip"); return NULL; } - } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { + } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny") || !strcasecmp(v->name, "acl")) { int ha_error = 0; if (!ast_strlen_zero(v->value)) { - peer->ha = ast_append_ha(v->name, v->value, peer->ha, &ha_error); + ast_append_acl(v->name, v->value, &peer->acl, &ha_error, &acl_change_subscription_needed); } if (ha_error) { ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value); } - } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny")) { + } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny") || !strcasecmp(v->name, "contactacl")) { int ha_error = 0; if (!ast_strlen_zero(v->value)) { - peer->contactha = ast_append_ha(v->name + 7, v->value, peer->contactha, &ha_error); + ast_append_acl(v->name + 7, v->value, &peer->contactacl, &ha_error, &acl_change_subscription_needed); } if (ha_error) { ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value); } - } else if (!strcasecmp(v->name, "directmediapermit") || !strcasecmp(v->name, "directmediadeny")) { + } else if (!strcasecmp(v->name, "directmediapermit") || !strcasecmp(v->name, "directmediadeny") || !strcasecmp(v->name, "directmediaacl")) { int ha_error = 0; - peer->directmediaha = ast_append_ha(v->name + 11, v->value, peer->directmediaha, &ha_error); + ast_append_acl(v->name + 11, v->value, &peer->directmediaacl, &ha_error, &acl_change_subscription_needed); if (ha_error) { ast_log(LOG_ERROR, "Bad directmedia ACL entry in configuration line %d : %s\n", v->lineno, v->value); } @@ -29674,8 +29711,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str if (global_dynamic_exclude_static && !ast_sockaddr_isnull(&peer->addr)) { int ha_error = 0; - sip_cfg.contact_ha = ast_append_ha("deny", ast_sockaddr_stringify_addr(&peer->addr), - sip_cfg.contact_ha, &ha_error); + + ast_append_acl("deny", ast_sockaddr_stringify_addr(&peer->addr), &sip_cfg.contact_acl, &ha_error, NULL); if (ha_error) { ast_log(LOG_ERROR, "Bad or unresolved host/IP entry in configuration for peer %s, cannot add to contact ACL\n", peer->name); } @@ -29746,8 +29783,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str peer->the_mark = 0; - ast_free_ha(oldha); - ast_free_ha(olddirectmediaha); + oldacl = ast_free_acl_list(oldacl); + olddirectmediaacl = ast_free_acl_list(olddirectmediaacl); if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */ char *reg_string; if (asprintf(®_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, peer->callback) < 0) { @@ -29757,6 +29794,12 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str ast_free(reg_string); } } + + /* If an ACL change subscription is needed and doesn't exist, we need one. */ + if (acl_change_subscription_needed) { + acl_change_event_subscribe(); + } + return peer; } @@ -29838,13 +29881,14 @@ static int reload_config(enum channelreloadreason reason) char *cat, *stringp, *context, *oldregcontext; char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT]; struct ast_flags dummy[3]; - struct ast_flags config_flags = { reason == CHANNEL_MODULE_LOAD ? 0 : ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) ? 0 : CONFIG_FLAG_FILEUNCHANGED }; + struct ast_flags config_flags = { (reason == CHANNEL_MODULE_LOAD || reason == CHANNEL_ACL_RELOAD) ? 0 : ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) ? 0 : CONFIG_FLAG_FILEUNCHANGED }; int auto_sip_domains = FALSE; struct ast_sockaddr old_bindaddr = bindaddr; int registry_count = 0, peer_count = 0, timerb_set = 0, timert1_set = 0; int subscribe_network_change = 1; time_t run_start, run_end; int bindport = 0; + int acl_change_subscription_needed = 0; run_start = time(0); ast_unload_realtime("sipregs"); @@ -29887,8 +29931,7 @@ static int reload_config(enum channelreloadreason reason) } } - ast_free_ha(sip_cfg.contact_ha); - sip_cfg.contact_ha = NULL; + sip_cfg.contact_acl = ast_free_acl_list(sip_cfg.contact_acl); default_tls_cfg.enabled = FALSE; /* Default: Disable TLS */ @@ -29981,7 +30024,7 @@ static int reload_config(enum channelreloadreason reason) sip_cfg.accept_outofcall_message = DEFAULT_ACCEPT_OUTOFCALL_MESSAGE; sip_cfg.allowsubscribe = FALSE; sip_cfg.disallowed_methods = SIP_UNKNOWN; - sip_cfg.contact_ha = NULL; /* Reset the contact ACL */ + sip_cfg.contact_acl = NULL; /* Reset the contact ACL */ snprintf(global_useragent, sizeof(global_useragent), "%s %s", DEFAULT_USERAGENT, ast_get_version()); snprintf(global_sdpsession, sizeof(global_sdpsession), "%s %s", DEFAULT_SDPSESSION, ast_get_version()); snprintf(global_sdpowner, sizeof(global_sdpowner), "%s", DEFAULT_SDPOWNER); @@ -30179,9 +30222,9 @@ static int reload_config(enum channelreloadreason reason) ast_sockaddr_stringify(&sip_tcp_desc.local_address)); } else if (!strcasecmp(v->name, "dynamic_exclude_static") || !strcasecmp(v->name, "dynamic_excludes_static")) { global_dynamic_exclude_static = ast_true(v->value); - } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny")) { + } else if (!strcasecmp(v->name, "contactpermit") || !strcasecmp(v->name, "contactdeny") || !strcasecmp(v->name, "contactacl")) { int ha_error = 0; - sip_cfg.contact_ha = ast_append_ha(v->name + 7, v->value, sip_cfg.contact_ha, &ha_error); + ast_append_acl(v->name + 7, v->value, &sip_cfg.contact_acl, &ha_error, &acl_change_subscription_needed); if (ha_error) { ast_log(LOG_ERROR, "Bad ACL entry in configuration line %d : %s\n", v->lineno, v->value); } @@ -30959,10 +31002,15 @@ static int reload_config(enum channelreloadreason reason) run_end = time(0); ast_debug(4, "SIP reload_config done...Runtime= %d sec\n", (int)(run_end-run_start)); + /* If an ACL change subscription is needed and doesn't exist, we need one. */ + if (acl_change_subscription_needed) { + acl_change_event_subscribe(); + } + return 0; } -static int apply_directmedia_ha(struct sip_pvt *p, struct ast_ha *directmediaha, const char *op) +static int apply_directmedia_acl(struct sip_pvt *p, struct ast_acl_list *directmediaacl, const char *op) { struct ast_sockaddr us = { { 0, }, }, them = { { 0, }, }; int res = AST_SENSE_ALLOW; @@ -30970,7 +31018,7 @@ static int apply_directmedia_ha(struct sip_pvt *p, struct ast_ha *directmediaha, ast_rtp_instance_get_remote_address(p->rtp, &them); ast_rtp_instance_get_local_address(p->rtp, &us); - if ((res = ast_apply_ha(directmediaha, &them)) == AST_SENSE_DENY) { + if ((res = ast_apply_acl(directmediaacl, &them, "SIP Direct Media ACL: ")) == AST_SENSE_DENY) { const char *us_addr = ast_strdupa(ast_sockaddr_stringify(&us)); const char *them_addr = ast_strdupa(ast_sockaddr_stringify(&them)); @@ -31045,8 +31093,8 @@ static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl) static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel *chan2, char *rtptype) { struct sip_pvt *p1 = NULL, *p2 = NULL; - struct ast_ha *p2_directmediaha = NULL; /* opposed directmediaha for comparing against first channel host address */ - struct ast_ha *p1_directmediaha = NULL; /* opposed directmediaha for comparing against second channel host address */ + struct ast_acl_list *p2_directmediaacl = NULL; /* opposed directmediaha for comparing against first channel host address */ + struct ast_acl_list *p1_directmediaacl = NULL; /* opposed directmediaha for comparing against second channel host address */ int res = 1; if (!(p1 = ast_channel_tech_pvt(chan1))) { @@ -31058,18 +31106,18 @@ static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel } sip_pvt_lock(p2); - if (p2->relatedpeer && p2->relatedpeer->directmediaha) { - p2_directmediaha = ast_duplicate_ha_list(p2->relatedpeer->directmediaha); + if (p2->relatedpeer && p2->relatedpeer->directmediaacl) { + p2_directmediaacl = ast_duplicate_acl_list(p2->relatedpeer->directmediaacl); } sip_pvt_unlock(p2); sip_pvt_lock(p1); - if (p1->relatedpeer && p1->relatedpeer->directmediaha) { - p1_directmediaha = ast_duplicate_ha_list(p1->relatedpeer->directmediaha); + if (p1->relatedpeer && p1->relatedpeer->directmediaacl) { + p1_directmediaacl = ast_duplicate_acl_list(p1->relatedpeer->directmediaacl); } - if (p2_directmediaha && ast_test_flag(&p1->flags[0], SIP_DIRECT_MEDIA)) { - if (!apply_directmedia_ha(p1, p2_directmediaha, rtptype)) { + if (p2_directmediaacl && ast_test_flag(&p1->flags[0], SIP_DIRECT_MEDIA)) { + if (!apply_directmedia_acl(p1, p2_directmediaacl, rtptype)) { res = 0; } } @@ -31080,8 +31128,8 @@ static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel } sip_pvt_lock(p2); - if (p1_directmediaha && ast_test_flag(&p2->flags[0], SIP_DIRECT_MEDIA)) { - if (!apply_directmedia_ha(p2, p1_directmediaha, rtptype)) { + if (p1_directmediaacl && ast_test_flag(&p2->flags[0], SIP_DIRECT_MEDIA)) { + if (!apply_directmedia_acl(p2, p1_directmediaacl, rtptype)) { res = 0; } } @@ -31089,12 +31137,12 @@ static int sip_allow_anyrtp_remote(struct ast_channel *chan1, struct ast_channel allow_anyrtp_remote_end: - if (p2_directmediaha) { - ast_free_ha(p2_directmediaha); + if (p2_directmediaacl) { + p2_directmediaacl = ast_free_acl_list(p2_directmediaacl); } - if (p1_directmediaha) { - ast_free_ha(p1_directmediaha); + if (p1_directmediaacl) { + p1_directmediaacl = ast_free_acl_list(p1_directmediaacl); } return res; @@ -32574,6 +32622,7 @@ static int unload_module(void) int wait_count; network_change_event_unsubscribe(); + acl_change_event_unsubscribe(); ast_sched_dump(sched); @@ -32723,7 +32772,7 @@ static int unload_module(void) ao2_t_ref(sip_monitor_instances, -1, "unref the sip_monitor_instances table"); clear_sip_domains(); - ast_free_ha(sip_cfg.contact_ha); + sip_cfg.contact_acl = ast_free_acl_list(sip_cfg.contact_acl); close(sipsock); ast_sched_context_destroy(sched); con = ast_context_find(used_context); diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 72f9d2b6f..1e659a749 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -757,7 +757,7 @@ struct sip_settings { char default_subscribecontext[AST_MAX_CONTEXT]; char default_record_on_feature[FEATURE_MAX_LEN]; char default_record_off_feature[FEATURE_MAX_LEN]; - struct ast_ha *contact_ha; /*! \brief Global list of addresses dynamic peers are not allowed to use */ + struct ast_acl_list *contact_acl; /*! \brief Global list of addresses dynamic peers are not allowed to use */ struct ast_format_cap *caps; /*!< Supported codecs */ int tcp_enabled; int default_max_forwards; /*!< Default max forwards (SIP Anti-loop) */ @@ -1117,7 +1117,7 @@ struct sip_pvt { int rtptimeout; /*!< RTP timeout time */ int rtpholdtimeout; /*!< RTP timeout time on hold*/ int rtpkeepalive; /*!< RTP send packets for keepalive */ - struct ast_ha *directmediaha; /*!< Which IPs are allowed to interchange direct media with this peer - copied from sip_peer */ + struct ast_acl_list *directmediaacl; /*!< Which IPs are allowed to interchange direct media with this peer - copied from sip_peer */ struct ast_sockaddr recv; /*!< Received as */ struct ast_sockaddr ourip; /*!< Our IP (as seen from the outside) */ enum transfermodes allowtransfer; /*!< REFER: restriction scheme */ @@ -1335,9 +1335,9 @@ struct sip_peer { int keepalive; /*!< Keepalive: How often to send keep alive packet */ int keepalivesend; /*!< Keepalive: Scheduled item for sending keep alive packet */ struct ast_sockaddr defaddr; /*!< Default IP address, used until registration */ - struct ast_ha *ha; /*!< Access control list */ - struct ast_ha *contactha; /*!< Restrict what IPs are allowed in the Contact header (for registration) */ - struct ast_ha *directmediaha; /*!< Restrict what IPs are allowed to interchange direct media with */ + struct ast_acl_list *acl; /*!< Access control list */ + struct ast_acl_list *contactacl; /*!< Restrict what IPs are allowed in the Contact header (for registration) */ + struct ast_acl_list *directmediaacl; /*!< Restrict what IPs are allowed to interchange direct media with */ struct ast_variable *chanvars; /*!< Variables to set for channel created by user */ struct sip_pvt *mwipvt; /*!< Subscription for MWI */ struct sip_st_cfg stimer; /*!< SIP Session-Timers */ diff --git a/configs/acl.conf.sample b/configs/acl.conf.sample new file mode 100644 index 000000000..ca6906d4b --- /dev/null +++ b/configs/acl.conf.sample @@ -0,0 +1,86 @@ +; +; Named Access Control Lists (ACLs) +; +; A convenient way to share acl definitions +; +; This configuration file is read on startup +; +; CLI Commands +; ----------------------------------------------------------- +; acl show Show all named ACLs configured +; acl show <name> Show contents of a particular named ACL +; reload acl Reload configuration file +; +;[general] +;systemname=asterisksystem1 ; If a system name is specified, realtime +; ; ACLs will only be retrieved if they have +; ; a systemname field that matches this value. +; ; If it's less blank, the field is ignored. +; +; Any configuration that uses ACLs which has been made to be able to use named +; ACLs will specify a named ACL with the 'acl' option in its configuration in +; a similar fashion to the usual 'permit' and 'deny' options. Example: +; acl=my_named_acl +; +; Multiple named ACLs can be applied by either comma separating the arguments or +; just by adding additional ACL lines. Example: +; acl=my_named_acl +; acl=my_named_acl2 +; +; or +; +; acl=my_named_acl,my_named_acl2 +; +; ACLs specified by name are evaluated independently from the ACL specified via +; permit/deny. In order for an address to pass a given ACL, it must pass both +; the ACL specified by permit/deny for a given item as well as any named ACLs +; that were specified. +; +;[example_named_acl1] +;deny=0.0.0.0/0.0.0.0 +;permit=209.16.236.0 +;permit=209.16.236.1 +; +;[example_named_acl2] +;permit=0.0.0.0/0.0.0.0 +;deny=10.24.20.171 +;deny=10.24.20.103 +;deny=209.16.236.1 +; +; example_named_acl1 above shows an example of whitelisting. When whitelisting, the +; named ACLs should follow a deny that blocks everything (like deny=0.0.0.0/0.0.0.0) +; The following example explains how combining the ACLs works: +; <in another configuration> +; [example_item_with_acl] +; acl=example_named_acl1 +; acl=example_named_acl2 +; +; Suppose 209.16.236.0 tries to communicate and the ACL for that example is applied to it... +; First, example_named_acl1 is evaluated. The address is allowed by that ACL. +; Next, example_named_acl2 is evaluated. The address isn't blocked by example_named_acl2 +; either, so it passes. +; +; Suppose instead 209.16.236.1 tries to communicate and the same ACL is applied. +; First, example_named_acl1 is evaluated and the address is allowed. +; However, it is blocked by example_named_acl2, so the address is blocked from the combined +; ACL. +; +; Similarly, the permits/denies in specific configurations that make up an ACL definition +; are also treated as a separate ACL for evaluation. So if we change the example above to: +; <in another configuration> +; [example_item_with_acl] +; acl=example_named_acl1 +; acl=example_named_acl2 +; deny=209.16.236.0 +; +; Then 209.16.236.0 will be rejected by the non-named component of the combined ACL even +; though it passes the two named components. +; +; +; Named ACLs can use ipv6 addresses just like normal ACLs. +;[ipv6_example_1] +;deny = :: +;permit = ::1/128 +; +;[ipv6_example_2] +;permit = fe80::21d:bad:fad:2323 diff --git a/configs/extconfig.conf.sample b/configs/extconfig.conf.sample index 1d67fa536..a83fa2c12 100644 --- a/configs/extconfig.conf.sample +++ b/configs/extconfig.conf.sample @@ -32,6 +32,11 @@ ; cdr.conf ; rtp.conf ; +; Named ACLs specified in realtime also can not be used +; from manager.conf unless the storage driver is preloaded. +; Attempting to use a realtime stored named ACL before the +; driver is loaded will result in an invalid ACL which +; rejects all addresses. ; ; Realtime configuration engine ; @@ -74,6 +79,7 @@ ;meetme => mysql,general ;queues => odbc,asterisk ;queue_members => odbc,asterisk +;acls => odbc,asterisk ;musiconhold => mysql,general ;queue_log => mysql,general ; diff --git a/configs/iax.conf.sample b/configs/iax.conf.sample index 19f2a9b17..9b5d4bc78 100644 --- a/configs/iax.conf.sample +++ b/configs/iax.conf.sample @@ -518,11 +518,11 @@ inkeys=freeworlddialup ; ; Further user sections may be added, specifying a context and a secret used ; for connections with that given authentication name. Limited IP based -; access control is allowed by use of "permit" and "deny" keywords. Multiple -; rules are permitted. Multiple permitted contexts may be specified, in -; which case the first will be the default. You can also override Caller*ID -; so that when you receive a call you set the Caller*ID to be what you want -; instead of trusting what the remote user provides +; access control is allowed by use of "permit", "deny", and "acl" keywords. +; Multiple rules are permitted. Multiple permitted contexts may be specified, +; in which case the first will be the default. You can also override +; Caller*ID so that when you receive a call you set the Caller*ID to be what +; you want instead of trusting what the remote user provides ; ; There are three authentication methods that are supported: md5, plaintext, ; and rsa. The least secure is "plaintext", which sends passwords cleartext @@ -639,6 +639,7 @@ description=Demo System At Digium ; Description of this peer, as listed by ;secret=shazbot ; only the last specified secret will be used. ;context=default ;permit=0.0.0.0/0.0.0.0 +;acl=example_named_acl ; ; With immediate=yes, an IAX2 phone or a phone on an IAXy acts as a hot-line diff --git a/configs/manager.conf.sample b/configs/manager.conf.sample index 5e99cf80d..31c182007 100644 --- a/configs/manager.conf.sample +++ b/configs/manager.conf.sample @@ -85,6 +85,7 @@ bindaddr = 0.0.0.0 ;secret = mysecret ;deny=0.0.0.0/0.0.0.0 ;permit=209.16.236.73/255.255.255.0 +;acl=named_acl_example ; use a named ACL from acl.conf ; ;eventfilter=Event: Newchannel ;eventfilter=!Channel: DAHDI* diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index e92bba9fe..dcf88aa73 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -10,8 +10,8 @@ ; ; Especially note the following settings: ; - allowguest (default enabled) -; - permit/deny - IP address filters -; - contactpermit/contactdeny - IP address filters for registrations +; - permit/deny/acl - IP address filters +; - contactpermit/contactdeny/contactacl - IP address filters for registrations ; - context - Which set of services you offer various users ; ; SIP dial strings @@ -454,6 +454,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ;contactdeny=0.0.0.0/0.0.0.0 ; Use contactpermit and contactdeny to ;contactpermit=172.16.0.0/255.255.0.0 ; restrict at what IPs your users may ; register their phones. +;contactacl=named_acl_example ; Use named ACLs defined in acl.conf ;engine=asterisk ; RTP engine to use when communicating with the device @@ -956,6 +957,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; Use this if some of your phones are on IP addresses that ; can not reach each other directly. This way you can force ; RTP to always flow through asterisk in such cases. +;directmediaacl=acl_example ; Use named ACLs defined in acl.conf ;ignoresdpversion=yes ; By default, Asterisk will honor the session version ; number in SDP packets and will only modify the SDP @@ -1211,10 +1213,11 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ; t38pt_usertpsource ; contactpermit ; Limit what a host may register as (a neat trick ; contactdeny ; is to register at the same IP as a SIP provider, -; ; then call oneself, and get redirected to that +; contactacl ; then call oneself, and get redirected to that ; ; same location). ; directmediapermit ; directmediadeny +; directmediaacl ; unsolicited_mailbox ; use_q850_reason ; maxforwards @@ -1419,6 +1422,7 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ;permit=2001:db8::/32 ; IPv6 ACLs can be specified if desired. IPv6 ACLs ; apply only to IPv6 addresses, and IPv4 ACLs apply ; only to IPv4 addresses. +;acl=named_acl_example ; Use named ACLs defined in acl.conf ;[cisco1] ;type=friend diff --git a/include/asterisk/acl.h b/include/asterisk/acl.h index 1576aef11..0dfb47f08 100644 --- a/include/asterisk/acl.h +++ b/include/asterisk/acl.h @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 1999 - 2006, Digium, Inc. + * Copyright (C) 1999 - 2012, Digium, Inc. * * Mark Spencer <markster@digium.com> * @@ -29,15 +29,18 @@ extern "C" { #endif #include "asterisk/network.h" +#include "asterisk/linkedlists.h" #include "asterisk/netsock2.h" #include "asterisk/io.h" -#define AST_SENSE_DENY 0 -#define AST_SENSE_ALLOW 1 +enum ast_acl_sense { + AST_SENSE_DENY, + AST_SENSE_ALLOW +}; /* Host based access control */ -/*! \brief internal representation of acl entries +/*! \brief internal representation of ACL entries * In principle user applications would have no need for this, * but there is sometimes a need to extract individual items, * e.g. to print them, and rather than defining iterators to @@ -49,10 +52,29 @@ struct ast_ha { /* Host access rule */ struct ast_sockaddr addr; struct ast_sockaddr netmask; - int sense; + enum ast_acl_sense sense; struct ast_ha *next; }; +#define ACL_NAME_LENGTH 80 + +/*! + * \brief an ast_acl is a linked list node of ast_ha structs which may have names. + * + * \note These shouldn't be used directly by ACL consumers. Consumers should handle + * ACLs via ast_acl_list structs. + */ +struct ast_acl { + struct ast_ha *acl; /*!< Rules contained by the ACL */ + int is_realtime; /*!< If raised, this named ACL was retrieved from realtime storage */ + int is_invalid; /*!< If raised, this is an invalid ACL which will automatically reject everything. */ + char name[ACL_NAME_LENGTH]; /*!< If this was retrieved from the named ACL subsystem, this is the name of the ACL. */ + AST_LIST_ENTRY(ast_acl) list; +}; + +/*! \brief Wrapper for an ast_acl linked list. */ +AST_LIST_HEAD(ast_acl_list, ast_acl); + /*! * \brief Free a list of HAs * @@ -66,6 +88,18 @@ struct ast_ha { void ast_free_ha(struct ast_ha *ha); /*! + * \brief Free a list of ACLs + * + * \details + * Given the head of a list of ast_acl structs, it and all appended + * acl structs will be freed. This includes the ast_ha structs within + * the individual nodes. + * \param acl The list of ACLs to free + * \retval NULL + */ +struct ast_acl_list *ast_free_acl_list(struct ast_acl_list *acl); + +/*! * \brief Copy the contents of one HA to another * * \details @@ -101,6 +135,35 @@ void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to); struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error); /*! + * \brief Add a rule to an ACL struct + * + * \details + * This adds a named ACL or an ACL rule to an ast_acl container. + * It works in a similar way to ast_append_ha. + * + * \param sense Can be any among "permit", "deny", or "acl" + * this controls whether the rule being added will simply modify the unnamed ACL at the head of the list + * or if a new named ACL will be added to that ast_acl. + * \param stuff If sense is 'permit'/'deny', this is the ip address and subnet mask separated with a '/' like in ast_append ha. + * If it sense is 'acl', then this will be the name of the ACL being appended to the container. + * \param path Address of the ACL list being appended + * \param[out] error The int that error points to will be set to 1 if an error occurs. + * \param[out] named_acl_flag This will raise a flag under certain conditions to indicate that a named ACL has been added by this + * operation. This may be used to indicate that an event subscription should be made against the named ACL subsystem. + * Note: This flag may be raised by this function, but it will never be lowered by it. + */ +void ast_append_acl(const char *sense, const char *stuff, struct ast_acl_list **path, int *error, int *named_acl_flag); + +/*! + * \brief Determines if an ACL is empty or if it contains entries + * + * \param acl_list The ACL list being checked + * \retval 0 - the list is not empty + * \retval 1 - the list is empty + */ +int ast_acl_list_is_empty(struct ast_acl_list *acl_list); + +/*! * \brief Apply a set of rules to a given IP address * * \details @@ -115,7 +178,25 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha * \retval AST_SENSE_ALLOW The IP address passes our ACL * \retval AST_SENSE_DENY The IP address fails our ACL */ -int ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr); +enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr); + +/*! + * \brief Apply a set of rules to a given IP address + * + * \details + * Similar to the above, only uses an acl container, which is a whole slew + * of ast_ha lists. It runs ast_apply_ha on each of the ast_ha structs + * contained in the acl container. It will deny if any of the ast_ha lists + * fail, and it will pass only if all of the rules pass. + * + * \param acl The head of the list of ACLs to evaluate + * \param addr An ast_sockaddr whose address is considered when matching rules + * \param purpose Context for which the ACL is being applied - Establishes purpose of a notice when rejected + * + * \retval AST_SENSE_ALLOW The IP address passes our ACLs + * \retval AST_SENSE_DENY The IP address fails our ACLs + */ +enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose); /*! * \brief Get the IP address given a hostname @@ -199,15 +280,26 @@ int ast_lookup_iface(char *iface, struct ast_sockaddr *address); * value is allocated on the heap and must be freed independently * of the input parameter when finished. * - * \note - * This function is not actually used anywhere. - * * \param original The ast_ha to copy * \retval The head of the list of duplicated ast_has */ struct ast_ha *ast_duplicate_ha_list(struct ast_ha *original); /*! + * \brief Duplicates the contests of a list of lists of host access rules. + * + * \details + * A deep copy of an ast_acl list is made (which in turn means a deep copy of + * each of the ast_ha structs contained within). The returned value is allocated + * on the heap and must be freed independently of the input paramater when + * finished. + * + * \param original The ast_acl_list to copy + * \retval The new duplicated ast_acl_list + */ +struct ast_acl_list *ast_duplicate_acl_list(struct ast_acl_list *original); + +/*! * \brief Find our IP address * * \details @@ -258,6 +350,41 @@ int ast_str2tos(const char *value, unsigned int *tos); */ const char *ast_tos2str(unsigned int tos); +/*! + * \brief Retrieve a named ACL + * + * \details + * This function attempts to find a named ACL. If found, a copy + * of the requested ACL will be made which must be freed by + * the caller. + * + * \param name Name of the ACL sought + * \param[out] is_realtime will be true if the ACL being returned is from realtime + * \param[out] is_undefined will be true if no ACL profile can be found for the requested name + * + * \retval A copy of the named ACL as an ast_ha + * \retval NULL if no ACL could be found. + */ +struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined); + +/*! + * \brief Initialize and configure the named ACL system. + * + * \details + * This function will prepare the named ACL system for use. + * For this reason, it needs to be called before other things that use ACLs are initialized. + */ +int ast_named_acl_init(void); + +/*! + * \brief reload/reconfigure the named ACL system. + * + * \details + * This function is designed to trigger an event upon a successful reload that may update + * ACL consumers. + */ +int ast_named_acl_reload(void); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 4f2cc8849..85a527442 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -970,6 +970,7 @@ enum channelreloadreason { CHANNEL_MODULE_RELOAD, CHANNEL_CLI_RELOAD, CHANNEL_MANAGER_RELOAD, + CHANNEL_ACL_RELOAD, }; /*! diff --git a/include/asterisk/config.h b/include/asterisk/config.h index b2ffcf5c4..1f4ce2970 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -190,6 +190,19 @@ void ast_config_destroy(struct ast_config *config); struct ast_variable *ast_category_root(struct ast_config *config, char *cat); /*! + * \brief Sorts categories in a config in the order of a numerical value contained within them. + * + * \param config The config structure you wish to sort + * \param variable Which numerical value you wish to sort by + * \param descending If true, we sort highest to lowest instead of lowest to highest + * + * \details + * This function will assume a value of 0 for any non-numerical strings and NULL fields. + */ +void ast_config_sort_categories(struct ast_config *config, int descending, + int (*comparator)(struct ast_category *p, struct ast_category *q)); + +/*! * \brief Goes through categories * * \param config Which config structure you wish to "browse" @@ -482,6 +495,15 @@ int ast_config_engine_register(struct ast_config_engine *newconfig); int ast_config_engine_deregister(struct ast_config_engine *del); /*! + * \brief Determine if a mapping exists for a given family + * + * \param family which family you are looking to see if a mapping exists for + * \retval 1 if it is mapped + * \retval 0 if it is not + */ +int ast_realtime_is_mapping_defined(const char *family); + +/*! * \brief Exposed initialization method for core process * * \details diff --git a/include/asterisk/event_defs.h b/include/asterisk/event_defs.h index b2bf0e4ca..d1e88360c 100644 --- a/include/asterisk/event_defs.h +++ b/include/asterisk/event_defs.h @@ -56,8 +56,10 @@ enum ast_event_type { AST_EVENT_NETWORK_CHANGE = 0x09, /*! The presence state for a presence provider */ AST_EVENT_PRESENCE_STATE = 0x0a, + /*! Used to alert listeners when a named ACL has changed. */ + AST_EVENT_ACL_CHANGE = 0x0b, /*! Number of event types. This should be the last event type + 1 */ - AST_EVENT_TOTAL = 0x0b, + AST_EVENT_TOTAL = 0x0c, }; /*! \brief Event Information Element types */ diff --git a/main/acl.c b/main/acl.c index e27ed0cc0..4b14ac575 100644 --- a/main/acl.c +++ b/main/acl.c @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 1999 - 2006, Digium, Inc. + * Copyright (C) 1999 - 2012, Digium, Inc. * * Mark Spencer <markster@digium.com> * @@ -230,6 +230,28 @@ void ast_free_ha(struct ast_ha *ha) } } +/* Free ACL list structure */ +struct ast_acl_list *ast_free_acl_list(struct ast_acl_list *acl_list) +{ + struct ast_acl *current; + + if (!acl_list) { + return NULL; + } + + AST_LIST_LOCK(acl_list); + while ((current = AST_LIST_REMOVE_HEAD(acl_list, list))) { + ast_free_ha(current->acl); + ast_free(current); + } + AST_LIST_UNLOCK(acl_list); + + AST_LIST_HEAD_DESTROY(acl_list); + ast_free(acl_list); + + return NULL; +} + /* Copy HA structure */ void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to) { @@ -275,6 +297,56 @@ struct ast_ha *ast_duplicate_ha_list(struct ast_ha *original) return ret; /* Return start of list */ } +static int acl_new(struct ast_acl **pointer, const char *name) { + struct ast_acl *acl; + if (!(acl = ast_calloc(1, sizeof(*acl)))) { + return 1; + } + + *pointer = acl; + ast_copy_string(acl->name, name, ACL_NAME_LENGTH); + return 0; +} + +struct ast_acl_list *ast_duplicate_acl_list(struct ast_acl_list *original) +{ + struct ast_acl_list *clone; + struct ast_acl *current_cursor; + struct ast_acl *current_clone; + + /* Early return if we receive a duplication request for a NULL original. */ + if (!original) { + return NULL; + } + + if (!(clone = ast_calloc(1, sizeof(*clone)))) { + ast_log(LOG_WARNING, "Failed to allocate ast_acl_list struct while cloning an ACL\n"); + return NULL; + } + AST_LIST_HEAD_INIT(clone); + + AST_LIST_LOCK(original); + + AST_LIST_TRAVERSE(original, current_cursor, list) { + if ((acl_new(¤t_clone, current_cursor->name))) { + ast_log(LOG_WARNING, "Failed to allocate ast_acl struct while cloning an ACL."); + continue; + } + + /* Copy data from original ACL to clone ACL */ + current_clone->acl = ast_duplicate_ha_list(current_cursor->acl); + + current_clone->is_invalid = current_cursor->is_invalid; + current_clone->is_realtime = current_cursor->is_realtime; + + AST_LIST_INSERT_TAIL(clone, current_clone, list); + } + + AST_LIST_UNLOCK(original); + + return clone; +} + /*! * \brief * Isolate a 32-bit section of an IPv6 address @@ -396,6 +468,135 @@ static int parse_cidr_mask(struct ast_sockaddr *addr, int is_v4, const char *mas return 0; } + + +void ast_append_acl(const char *sense, const char *stuff, struct ast_acl_list **path, int *error, int *named_acl_flag) +{ + struct ast_acl *acl = NULL; + struct ast_acl *current; + struct ast_acl_list *working_list; + + char *tmp, *list; + + /* If the ACL list is currently uninitialized, it must be initialized. */ + if (*path == NULL) { + struct ast_acl_list *list; + list = ast_calloc(1, sizeof(*list)); + if (!list) { + /* Allocation Error */ + if (error) { + *error = 1; + } + return; + } + + AST_LIST_HEAD_INIT(list); + *path = list; + } + + working_list = *path; + + AST_LIST_LOCK(working_list); + + /* First we need to determine if we will need to add a new ACL node or if we can use an existing one. */ + if (strncasecmp(sense, "a", 1)) { + /* The first element in the path should be the unnamed, base ACL. If that's the case, we use it. If not, + * we have to make one and link it up appropriately. */ + current = AST_LIST_FIRST(working_list); + + if (!current || !ast_strlen_zero(current->name)) { + if (acl_new(&acl, "")) { + if (error) { + *error = 1; + } + } + // Need to INSERT the ACL at the head here. + AST_LIST_INSERT_HEAD(working_list, acl, list); + } else { + /* If the first element was already the unnamed base ACL, we just use that one. */ + acl = current; + } + + /* With the proper ACL set for modification, we can just pass this off to the ast_ha append function. */ + acl->acl = ast_append_ha(sense, stuff, acl->acl, error); + + AST_LIST_UNLOCK(working_list); + return; + } + + /* We are in ACL append mode, so we know we'll be adding one or more named ACLs. */ + list = ast_strdupa(stuff); + + while ((tmp = strsep(&list, ","))) { + struct ast_ha *named_ha; + int already_included = 0; + + /* Remove leading whitespace from the string in case the user put spaces between items */ + tmp = ast_skip_blanks(tmp); + + /* The first step is to check for a duplicate */ + AST_LIST_TRAVERSE(working_list, current, list) { + if (!strcasecmp(current->name, tmp)) { /* ACL= */ + /* Inclusion of the same ACL multiple times isn't a catastrophic error, but it will raise the error flag and skip the entry. */ + ast_log(LOG_ERROR, "Named ACL '%s' is already included in the ast_acl container.", tmp); + if (error) { + *error = 1; + } + already_included = 1; + break; + } + } + + if (already_included) { + continue; + } + + if (acl_new(&acl, tmp)) { + /* This is a catastrophic allocation error and we'll return immediately if this happens. */ + if (error) { + *error = 1; + } + AST_LIST_UNLOCK(working_list); + return; + } + + /* Attempt to grab the Named ACL we are looking for. */ + named_ha = ast_named_acl_find(tmp, &acl->is_realtime, &acl->is_invalid); + + /* Set the ACL's ast_ha to the duplicated named ACL retrieved above. */ + acl->acl = named_ha; + + /* Raise the named_acl_flag since we are adding a named ACL to the ACL container. */ + if (named_acl_flag) { + *named_acl_flag = 1; + } + + /* Now insert the new ACL at the end of the list. */ + AST_LIST_INSERT_TAIL(working_list, acl, list); + } + + AST_LIST_UNLOCK(working_list); +} + +int ast_acl_list_is_empty(struct ast_acl_list *acl_list) +{ + struct ast_acl *head; + + if (!acl_list) { + return 1; + } + + AST_LIST_LOCK(acl_list); + head = AST_LIST_FIRST(acl_list); + AST_LIST_UNLOCK(acl_list); + + if (head) { + return 0; + } + + return 1; +} + struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error) { struct ast_ha *ha; @@ -509,16 +710,51 @@ struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha const char *addr = ast_strdupa(ast_sockaddr_stringify(&ha->addr)); const char *mask = ast_strdupa(ast_sockaddr_stringify(&ha->netmask)); - ast_debug(1, "%s/%s sense %d appended to acl for peer\n", addr, mask, ha->sense); + ast_debug(3, "%s/%s sense %d appended to acl\n", addr, mask, ha->sense); } return ret; } -int ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr) +enum ast_acl_sense ast_apply_acl(struct ast_acl_list *acl_list, const struct ast_sockaddr *addr, const char *purpose) +{ + struct ast_acl *acl; + + /* If the list is NULL, there are no rules, so we'll allow automatically. */ + if (!acl_list) { + return AST_SENSE_ALLOW; + } + + AST_LIST_LOCK(acl_list); + + AST_LIST_TRAVERSE(acl_list, acl, list) { + if (acl->is_invalid) { + /* In this case, the baseline ACL shouldn't ever trigger this, but if that somehow happens, it'll still be shown. */ + ast_log(LOG_WARNING, "%sRejecting '%s' due to use of an invalid ACL '%s'.\n", purpose ? purpose : "", ast_sockaddr_stringify_addr(addr), + ast_strlen_zero(acl->name) ? "(BASELINE)" : acl->name); + AST_LIST_UNLOCK(acl_list); + return AST_SENSE_DENY; + } + + if (acl->acl) { + if (ast_apply_ha(acl->acl, addr) == AST_SENSE_DENY) { + ast_log(LOG_NOTICE, "%sRejecting '%s' due to a failure to pass ACL '%s'\n", purpose ? purpose : "", ast_sockaddr_stringify_addr(addr), + ast_strlen_zero(acl->name) ? "(BASELINE)" : acl->name); + AST_LIST_UNLOCK(acl_list); + return AST_SENSE_DENY; + } + } + } + + AST_LIST_UNLOCK(acl_list); + + return AST_SENSE_ALLOW; +} + +enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockaddr *addr) { /* Start optimistic */ - int res = AST_SENSE_ALLOW; + enum ast_acl_sense res = AST_SENSE_ALLOW; const struct ast_ha *current_ha; for (current_ha = ha; current_ha; current_ha = current_ha->next) { diff --git a/main/asterisk.c b/main/asterisk.c index 08aaa5764..23d6c2320 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -117,6 +117,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/channel.h" #include "asterisk/translate.h" #include "asterisk/features.h" +#include "asterisk/acl.h" #include "asterisk/ulaw.h" #include "asterisk/alaw.h" #include "asterisk/callerid.h" @@ -4033,6 +4034,11 @@ int main(int argc, char *argv[]) exit(1); } + if (ast_named_acl_init()) { /* Initialize the Named ACL system */ + printf("%s", term_quit()); + exit(1); + } + ast_http_init(); /* Start the HTTP server, if needed */ if (init_manager()) { diff --git a/main/config.c b/main/config.c index 9e2df6c43..fee6d9eb7 100644 --- a/main/config.c +++ b/main/config.c @@ -773,6 +773,124 @@ struct ast_variable *ast_category_root(struct ast_config *config, char *cat) return NULL; } +void ast_config_sort_categories(struct ast_config *config, int descending, + int (*comparator)(struct ast_category *p, struct ast_category *q)) +{ + /* + * The contents of this function are adapted from + * an example of linked list merge sorting + * copyright 2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + int insize = 1; + struct ast_category *p, *q, *e, *tail; + int nmerges, psize, qsize, i; + + /* If the descending flag was sent, we'll apply inversion to the comparison function's return. */ + if (descending) { + descending = -1; + } else { + descending = 1; + } + + if (!config->root) { + return; + } + + while (1) { + p = config->root; + config->root = NULL; + tail = NULL; + + nmerges = 0; /* count number of merges we do in this pass */ + + while (p) { + nmerges++; /* there exists a merge to be done */ + + /* step `insize' places along from p */ + q = p; + psize = 0; + for (i = 0; i < insize; i++) { + psize++; + q = q->next; + if (!q) { + break; + } + } + + /* if q hasn't fallen off end, we have two lists to merge */ + qsize = insize; + + /* now we have two lists; merge them */ + while (psize > 0 || (qsize > 0 && q)) { + /* decide whether next element of merge comes from p or q */ + if (psize == 0) { + /* p is empty; e must come from q. */ + e = q; + q = q->next; + qsize--; + } else if (qsize == 0 || !q) { + /* q is empty; e must come from p. */ + e = p; p = p->next; psize--; + } else if ((comparator(p,q) * descending) <= 0) { + /* First element of p is lower (or same) e must come from p. */ + e = p; + p = p->next; + psize--; + } else { + /* First element of q is lower; e must come from q. */ + e = q; + q = q->next; + qsize--; + } + + /* add the next element to the merged list */ + if (tail) { + tail->next = e; + } else { + config->root = e; + } + tail = e; + } + + /* now p has stepped `insize' places along, and q has too */ + p = q; + } + + tail->next = NULL; + + /* If we have done only one merge, we're finished. */ + if (nmerges <= 1) { /* allow for nmerges==0, the empty list case */ + return; + } + + /* Otherwise repeat, merging lists twice the size */ + insize *= 2; + } + +} + char *ast_category_browse(struct ast_config *config, const char *prev) { struct ast_category *cat; @@ -2244,6 +2362,23 @@ int ast_config_engine_deregister(struct ast_config_engine *del) return 0; } +int ast_realtime_is_mapping_defined(const char *family) +{ + struct ast_config_map *map; + ast_mutex_lock(&config_lock); + + for (map = config_maps; map; map = map->next) { + if (!strcasecmp(family, map->name)) { + ast_mutex_unlock(&config_lock); + return 1; + } + } + + ast_mutex_unlock(&config_lock); + + return 0; +} + /*! \brief Find realtime engine for realtime family */ static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz) { diff --git a/main/loader.c b/main/loader.c index d71509219..5dab43b32 100644 --- a/main/loader.c +++ b/main/loader.c @@ -44,6 +44,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/config.h" #include "asterisk/channel.h" #include "asterisk/term.h" +#include "asterisk/acl.h" #include "asterisk/manager.h" #include "asterisk/cdr.h" #include "asterisk/enum.h" @@ -270,6 +271,7 @@ static struct reload_classes { { "dnsmgr", dnsmgr_reload }, { "extconfig", read_config_maps }, { "enum", ast_enum_reload }, + { "acl", ast_named_acl_reload }, { "manager", reload_manager }, { "http", ast_http_reload }, { "logger", logger_reload }, diff --git a/main/manager.c b/main/manager.c index d70ac6d15..c94e94ca2 100644 --- a/main/manager.c +++ b/main/manager.c @@ -80,6 +80,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/astobj2.h" #include "asterisk/features.h" #include "asterisk/security_events.h" +#include "asterisk/event.h" #include "asterisk/aoc.h" #include "asterisk/stringfields.h" #include "asterisk/presencestate.h" @@ -990,7 +991,7 @@ static char global_realm[MAXHOSTNAMELEN]; /*!< Default realm */ static int block_sockets; static int unauth_sessions = 0; - +static struct ast_event_sub *acl_change_event_subscription; /*! \brief * Descriptor for a manager session, either on the AMI socket or over HTTP. @@ -1011,6 +1012,23 @@ static const struct { {{ "restart", "gracefully", NULL }}, }; +static void acl_change_event_cb(const struct ast_event *event, void *userdata); + +static void acl_change_event_subscribe(void) +{ + if (!acl_change_event_subscription) { + acl_change_event_subscription = ast_event_subscribe(AST_EVENT_ACL_CHANGE, + acl_change_event_cb, "Manager must react to Named ACL changes", NULL, AST_EVENT_IE_END); + } +} + +static void acl_change_event_unsubscribe(void) +{ + if (acl_change_event_subscription) { + acl_change_event_subscription = ast_event_unsubscribe(acl_change_event_subscription); + } +} + /* In order to understand what the heck is going on with the * mansession_session and mansession structs, we need to have a bit of a history * lesson. @@ -1110,7 +1128,6 @@ static AST_RWLIST_HEAD_STATIC(channelvars, manager_channel_variable); struct ast_manager_user { char username[80]; char *secret; /*!< Secret for logging in */ - struct ast_ha *ha; /*!< ACL setting */ int readperm; /*!< Authorization for reading */ int writeperm; /*!< Authorization for writing */ int writetimeout; /*!< Per user Timeout for ast_carefulwrite() */ @@ -1118,6 +1135,7 @@ struct ast_manager_user { int keep; /*!< mark entries created on a reload */ struct ao2_container *whitefilters; /*!< Manager event filters - white list */ struct ao2_container *blackfilters; /*!< Manager event filters - black list */ + struct ast_acl_list *acl; /*!< ACL setting */ char *a1_hash; /*!< precalculated A1 for Digest auth */ AST_RWLIST_ENTRY(ast_manager_user) list; }; @@ -1666,13 +1684,13 @@ static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli ast_cli(a->fd, " username: %s\n" " secret: %s\n" - " acl: %s\n" + " ACL: %s\n" " read perm: %s\n" " write perm: %s\n" "displayconnects: %s\n", (user->username ? user->username : "(N/A)"), (user->secret ? "<Set>" : "(N/A)"), - (user->ha ? "yes" : "no"), + ((user->acl && !ast_acl_list_is_empty(user->acl)) ? "yes" : "no"), authority_to_str(user->readperm, &rauthority), authority_to_str(user->writeperm, &wauthority), (user->displayconnects ? "yes" : "no")); @@ -2478,7 +2496,7 @@ static int authenticate(struct mansession *s, const struct message *m) if (!(user = get_manager_by_name_locked(username))) { report_invalid_user(s, 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->ha && !ast_apply_ha(user->ha, &s->session->addr)) { + } else if (user->acl && (ast_apply_acl(user->acl, &s->session->addr, "Manager User ACL: ") == AST_SENSE_DENY)) { report_failed_acl(s, username); 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")) { @@ -6402,7 +6420,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser, } /* --- We have User for this auth, now check ACL */ - if (user->ha && !ast_apply_ha(user->ha, remote_address)) { + if (user->acl && !ast_apply_acl(user->acl, remote_address, "Manager User ACL:")) { AST_RWLIST_UNLOCK(&users); ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_sockaddr_stringify_addr(&session->addr), d.username); ast_http_error(ser, 403, "Permission denied", "Permission denied\n"); @@ -7070,7 +7088,7 @@ static void load_channelvars(struct ast_variable *var) AST_RWLIST_UNLOCK(&channelvars); } -static int __init_manager(int reload) +static int __init_manager(int reload, int by_external_config) { struct ast_config *ucfg = NULL, *cfg = NULL; #ifdef AST_XML_DOCS @@ -7081,12 +7099,13 @@ static int __init_manager(int reload) int newhttptimeout = 60; struct ast_manager_user *user = NULL; struct ast_variable *var; - struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + struct ast_flags config_flags = { (reload && !by_external_config) ? CONFIG_FLAG_FILEUNCHANGED : 0 }; char a1[256]; char a1_hash[256]; struct ast_sockaddr ami_desc_local_address_tmp; struct ast_sockaddr amis_desc_local_address_tmp; int tls_was_enabled = 0; + int acl_subscription_flag = 0; manager_enabled = 0; @@ -7160,6 +7179,11 @@ static int __init_manager(int reload) return 0; } + /* If this wasn't performed due to a forced reload (because those can be created by ACL change events, we need to unsubscribe to ACL change events. */ + if (!by_external_config) { + acl_change_event_unsubscribe(); + } + /* default values */ ast_copy_string(global_realm, S_OR(ast_config_AST_SYSTEM_NAME, DEFAULT_REALM), sizeof(global_realm)); ast_sockaddr_setnull(&ami_desc.local_address); @@ -7310,7 +7334,7 @@ static int __init_manager(int reload) ast_copy_string(user->username, cat, sizeof(user->username)); /* Insert into list */ AST_LIST_INSERT_TAIL(&users, user, list); - user->ha = NULL; + user->acl = NULL; user->keep = 1; user->readperm = -1; user->writeperm = -1; @@ -7367,7 +7391,7 @@ static int __init_manager(int reload) /* cat is NULL here in any case */ while ((cat = ast_category_browse(cfg, cat))) { - struct ast_ha *oldha; + struct ast_acl_list *oldacl; if (!strcasecmp(cat, "general")) { continue; @@ -7381,7 +7405,7 @@ static int __init_manager(int reload) /* Copy name over */ ast_copy_string(user->username, cat, sizeof(user->username)); - user->ha = NULL; + user->acl = NULL; user->readperm = 0; user->writeperm = 0; /* Default displayconnect from [general] */ @@ -7399,8 +7423,8 @@ static int __init_manager(int reload) /* Make sure we keep this user and don't destroy it during cleanup */ user->keep = 1; - oldha = user->ha; - user->ha = NULL; + oldacl = user->acl; + user->acl = NULL; var = ast_variable_browse(cfg, cat); for (; var; var = var->next) { @@ -7410,8 +7434,9 @@ static int __init_manager(int reload) } user->secret = ast_strdup(var->value); } else if (!strcasecmp(var->name, "deny") || - !strcasecmp(var->name, "permit")) { - user->ha = ast_append_ha(var->name, var->value, user->ha, NULL); + !strcasecmp(var->name, "permit") || + !strcasecmp(var->name, "acl")) { + ast_append_acl(var->name, var->value, &user->acl, NULL, &acl_subscription_flag); } else if (!strcasecmp(var->name, "read") ) { user->readperm = get_perm(var->value); } else if (!strcasecmp(var->name, "write") ) { @@ -7432,10 +7457,16 @@ static int __init_manager(int reload) ast_debug(1, "%s is an unknown option.\n", var->name); } } - ast_free_ha(oldha); + + oldacl = ast_free_acl_list(oldacl); } ast_config_destroy(cfg); + /* Check the flag for named ACL event subscription and if we need to, register a subscription. */ + if (acl_subscription_flag && !by_external_config) { + acl_change_event_subscribe(); + } + /* Perform cleanup - essentially prune out old users that no longer exist */ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&users, user, list) { if (user->keep) { /* valid record. clear flag for the next round */ @@ -7464,7 +7495,7 @@ static int __init_manager(int reload) ao2_t_callback(user->blackfilters, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "unlink all black filters"); ao2_t_ref(user->whitefilters, -1, "decrement ref for white container, should be last one"); ao2_t_ref(user->blackfilters, -1, "decrement ref for black container, should be last one"); - ast_free_ha(user->ha); + user->acl = ast_free_acl_list(user->acl); ast_free(user); } AST_RWLIST_TRAVERSE_SAFE_END; @@ -7516,6 +7547,13 @@ static int __init_manager(int reload) return 0; } +static void acl_change_event_cb(const struct ast_event *event, void *userdata) +{ + /* For now, this is going to be performed simply and just execute a forced reload. */ + ast_log(LOG_NOTICE, "Reloading manager in response to ACL change event.\n"); + __init_manager(1, 1); +} + /* clear out every entry in the channelvar list */ static void free_channelvars(void) { @@ -7529,12 +7567,12 @@ static void free_channelvars(void) int init_manager(void) { - return __init_manager(0); + return __init_manager(0, 0); } int reload_manager(void) { - return __init_manager(1); + return __init_manager(1, 0); } int astman_datastore_add(struct mansession *s, struct ast_datastore *datastore) diff --git a/main/named_acl.c b/main/named_acl.c new file mode 100644 index 000000000..52487975e --- /dev/null +++ b/main/named_acl.c @@ -0,0 +1,558 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Copyright (C) 2012, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * Jonathan Rose <jrose@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Named Access Control Lists + * + * \author Jonathan Rose <jrose@digium.com> + * + * \note Based on a feature proposed by + * Olle E. Johansson <oej@edvina.net> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/config.h" +#include "asterisk/config_options.h" +#include "asterisk/event.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/acl.h" +#include "asterisk/astobj2.h" + +#define NACL_CONFIG "acl.conf" +#define ACL_FAMILY "acls" + +struct named_acl_global_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(systemname); + ); +}; + +/* + * Configuration structure - holds pointers to ao2 containers used for configuration + * Since there isn't a general level or any other special levels for acl.conf at this + * time, it's really a config options friendly wrapper for the named ACL container + */ +struct named_acl_config { + struct named_acl_global_config *global; + struct ao2_container *named_acl_list; +}; + +static AO2_GLOBAL_OBJ_STATIC(globals); + +/*! \note These functions are used for placing/retrieving named ACLs in their ao2_container. */ +static void *named_acl_config_alloc(void); +static void *named_acl_alloc(const char *cat); +static void *named_acl_find(struct ao2_container *container, const char *cat); + +/* Config type for named ACL profiles (must not be named general) */ +static struct aco_type named_acl_type = { + .type = ACO_ITEM, /*!< named_acls are items stored in containers, not individual global objects */ + .category_match = ACO_BLACKLIST, + .category = "^general$", /*!< Match everything but "general" */ + .item_alloc = named_acl_alloc, /*!< A callback to allocate a new named_acl based on category */ + .item_find = named_acl_find, /*!< A callback to find a named_acl in some container of named_acls */ + .item_offset = offsetof(struct named_acl_config, named_acl_list), /*!< Could leave this out since 0 */ +}; + +/* Config type for the general part of the ACL profile (must be named general) */ +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .item_offset = offsetof(struct named_acl_config, global), + .category_match = ACO_WHITELIST, + .category = "^general$", +}; + +/* This array of aco_type structs is necessary to use aco_option_register */ +struct aco_type *named_acl_types[] = ACO_TYPES(&named_acl_type); + +struct aco_type *global_options[] = ACO_TYPES(&global_option); + +struct aco_file named_acl_conf = { + .filename = "acl.conf", + .types = ACO_TYPES(&named_acl_type, &global_option), +}; + +/* Create a config info struct that describes the config processing for named ACLs. */ +CONFIG_INFO_STANDARD(cfg_info, globals, named_acl_config_alloc, + .files = ACO_FILES(&named_acl_conf), +); + +struct named_acl { + struct ast_ha *ha; + char name[ACL_NAME_LENGTH]; /* Same max length as a configuration category */ +}; + +static int named_acl_hash_fn(const void *obj, const int flags) +{ + const struct named_acl *entry = obj; + return ast_str_hash(entry->name); +} + +static int named_acl_cmp_fn(void *obj, void *arg, const int flags) +{ + struct named_acl *entry1 = obj; + struct named_acl *entry2 = arg; + + return (!strcmp(entry1->name, entry2->name)) ? (CMP_MATCH | CMP_STOP) : 0; +} + +/*! \brief destructor for named_acl_config */ +static void named_acl_config_destructor(void *obj) +{ + struct named_acl_config *cfg = obj; + ao2_cleanup(cfg->named_acl_list); + ao2_cleanup(cfg->global); +} + +static void named_acl_global_config_destructor(void *obj) +{ + struct named_acl_global_config *global = obj; + ast_string_field_free_memory(global); +} + +/*! \brief allocator callback for named_acl_config. Notice it returns void * since it is used by + * the backend config code + */ +static void *named_acl_config_alloc(void) +{ + struct named_acl_config *cfg; + + if (!(cfg = ao2_alloc(sizeof(*cfg), named_acl_config_destructor))) { + return NULL; + } + + if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), named_acl_global_config_destructor))) { + goto error; + } + + if (ast_string_field_init(cfg->global, 128)) { + goto error; + } + + if (!(cfg->named_acl_list = ao2_container_alloc(37, named_acl_hash_fn, named_acl_cmp_fn))) { + goto error; + } + + return cfg; + +error: + ao2_ref(cfg, -1); + return NULL; +} + +/*! \brief Destroy a named ACL object */ +static void destroy_named_acl(void *obj) +{ + struct named_acl *named_acl = obj; + ast_free_ha(named_acl->ha); +} + +/*! + * \brief Create a named ACL structure + * + * \param cat name given to the ACL + * \retval NULL failure + *\retval non-NULL successfully allocated named ACL + */ +void *named_acl_alloc(const char *cat) +{ + struct named_acl *named_acl; + + named_acl = ao2_alloc(sizeof(*named_acl), destroy_named_acl); + if (!named_acl) { + return NULL; + } + + ast_copy_string(named_acl->name, cat, sizeof(named_acl->name)); + + return named_acl; +} + +/*! + * \brief Find a named ACL in a container by its name + * + * \param container ao2container holding the named ACLs + * \param name of the ACL wanted to be found + * \retval pointer to the named ACL if available. Null if not found. + */ +void *named_acl_find(struct ao2_container *container, const char *cat) +{ + struct named_acl tmp; + ast_copy_string(tmp.name, cat, sizeof(tmp.name)); + return ao2_find(container, &tmp, OBJ_POINTER); +} + +/*! + * \internal + * \brief Callback function to compare the ACL order of two given categories. + * This function is used to sort lists of ACLs received from realtime. + * + * \param p first category being compared + * \param q second category being compared + * + * \retval -1 (p < q) + * \retval 0 (p == q) + * \retval 1 (p > q) + */ +static int acl_order_comparator(struct ast_category *p, struct ast_category *q) +{ + int p_value = 0, q_value = 0; + struct ast_variable *p_var = ast_category_first(p); + struct ast_variable *q_var = ast_category_first(q); + + while (p_var) { + if (!strcasecmp(p_var->name, "rule_order")) { + p_value = atoi(p_var->value); + break; + } + p_var = p_var->next; + } + + while (q_var) { + if (!strcasecmp(q_var->name, "rule_order")) { + q_value = atoi(q_var->value); + break; + } + q_var = q_var->next; + } + + if (p_value < q_value) { + return -1; + } else if (q_value < p_value) { + return 1; + } + + return 0; +} + +/*! + * \internal + * \brief Search for a named ACL via realtime Database and build the named_acl + * if it is valid. + * + * \param name of the ACL wanted to be found + * \retval pointer to the named ACL if available. Null if the ACL subsystem is unconfigured. + */ +static struct named_acl *named_acl_find_realtime(const char *name) +{ + struct ast_config *cfg; + char *item = NULL; + const char *systemname = NULL; + struct ast_ha *built_ha = NULL; + struct named_acl *acl; + + RAII_VAR(struct named_acl_config *, acl_options, ao2_global_obj_ref(globals), ao2_cleanup); + + /* If we have a systemname set in the global options, we only want to retrieve entries with a matching systemname field. */ + if (acl_options) { + systemname = acl_options->global->systemname; + } + + if (ast_strlen_zero(systemname)) { + cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, SENTINEL); + } else { + cfg = ast_load_realtime_multientry(ACL_FAMILY, "name", name, "systemname", systemname, SENTINEL); + } + + if (!cfg) { + return NULL; + } + + /* At this point, the configuration must be sorted by the order field. */ + ast_config_sort_categories(cfg, 0, acl_order_comparator); + + while ((item = ast_category_browse(cfg, item))) { + int append_ha_error = 0; + const char *order = ast_variable_retrieve(cfg, item, "rule_order"); + const char *sense = ast_variable_retrieve(cfg, item, "sense"); + const char *rule = ast_variable_retrieve(cfg, item, "rule"); + + built_ha = ast_append_ha(sense, rule, built_ha, &append_ha_error); + if (append_ha_error) { + /* We need to completely reject an ACL that contains any bad rules. */ + ast_log(LOG_ERROR, "Rejecting realtime ACL due to bad ACL definition '%s': %s - %s - %s\n", name, order, sense, rule); + ast_free_ha(built_ha); + return NULL; + } + } + + ast_config_destroy(cfg); + + acl = named_acl_alloc(name); + if (!acl) { + ast_log(LOG_ERROR, "allocation error\n"); + ast_free_ha(built_ha); + return NULL; + } + + acl->ha = built_ha; + + return acl; +} + +struct ast_ha *ast_named_acl_find(const char *name, int *is_realtime, int *is_undefined) { + struct ast_ha *ha = NULL; + + RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup); + + if (is_realtime) { + *is_realtime = 0; + } + + if (is_undefined) { + *is_undefined = 0; + } + + /* If the config or its named_acl_list hasn't been initialized, abort immediately. */ + if ((!cfg) || (!(cfg->named_acl_list))) { + ast_log(LOG_ERROR, "Attempted to find named ACL '%s', but the ACL configuration isn't available.\n", name); + return NULL; + } + + named_acl = named_acl_find(cfg->named_acl_list, name); + + /* If a named ACL couldn't be retrieved locally, we need to try realtime storage. */ + if (!named_acl) { + RAII_VAR(struct named_acl *, realtime_acl, NULL, ao2_cleanup); + + /* Attempt to create from realtime */ + if ((realtime_acl = named_acl_find_realtime(name))) { + if (is_realtime) { + *is_realtime = 1; + } + ha = ast_duplicate_ha_list(realtime_acl->ha); + return ha; + } + + /* Couldn't create from realtime. Raise relevant flags and print relevant warnings. */ + if (ast_realtime_is_mapping_defined(ACL_FAMILY) && !ast_check_realtime(ACL_FAMILY)) { + ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n" + "This ACL may exist in the configured realtime backend, but that backend hasn't been registered yet. " + "Fix this establishing preload for the backend in 'modules.conf'.\n", name); + } else { + ast_log(LOG_WARNING, "ACL '%s' does not exist. The ACL will be marked as undefined and will automatically fail if applied.\n", name); + } + + if (is_undefined) { + *is_undefined = 1; + } + + return NULL; + } + + ha = ast_duplicate_ha_list(named_acl->ha); + + if (!ha) { + ast_log(LOG_NOTICE, "ACL '%s' contains no rules. It is valid, but it will accept addresses unconditionally.\n", name); + } + + return ha; +} + +/*! + * \internal + * \brief Sends an update event corresponding to a given named ACL that has changed. + * + * \param name Name of the ACL that has changed. May be an empty string (but not NULL) + * If name is an empty string, then all ACLs must be refreshed. + * + * \retval 0 success + * \retval 1 failure + */ +static int push_acl_change_event(char *name) +{ + struct ast_event *event = ast_event_new(AST_EVENT_ACL_CHANGE, + AST_EVENT_IE_DESCRIPTION, AST_EVENT_IE_PLTYPE_STR, name, + AST_EVENT_IE_END); + if (!event) { + ast_log(LOG_ERROR, "Failed to allocate acl.conf reload event. Some modules will have out of date ACLs.\n"); + return -1; + } + + if (ast_event_queue(event)) { + ast_event_destroy(event); + ast_log(LOG_ERROR, "Failed to queue acl.conf reload event. Some modules will have out of date ACLs.\n"); + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief reload configuration for named ACLs + * + * \param fd file descriptor for CLI client + */ +int ast_named_acl_reload(void) +{ + enum aco_process_status status; + + status = aco_process_config(&cfg_info, 1); + + if (status == ACO_PROCESS_ERROR) { + ast_log(LOG_WARNING, "Could not reload ACL config\n"); + return 0; + } + + if (status == ACO_PROCESS_UNCHANGED) { + /* We don't actually log anything if the config was unchanged, + * but we don't need to send a config change event either. + */ + return 0; + } + + /* We need to push an ACL change event with no ACL name so that all subscribers update with all ACLs */ + push_acl_change_event(""); + + return 0; +} + +/*! + * \internal + * \brief secondary handler for the 'acl show <name>' command (with arg) + * + * \param fd file descriptor of the cli + * \name name of the ACL requested for display + */ +static void cli_display_named_acl(int fd, const char *name) +{ + struct ast_ha *ha; + int ha_index = 0; + int is_realtime = 0; + + RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct named_acl *, named_acl, NULL, ao2_cleanup); + + /* If the configuration or the configuration's named_acl_list is unavailable, abort. */ + if ((!cfg) || (!cfg->named_acl_list)) { + ast_log(LOG_ERROR, "Attempted to show named ACL '%s', but the acl configuration isn't available.\n", name); + return; + } + + named_acl = named_acl_find(cfg->named_acl_list, name); + + /* If the named_acl couldn't be found with the search, also abort. */ + if (!named_acl) { + if (!(named_acl = named_acl_find_realtime(name))) { + ast_cli(fd, "\nCould not find ACL named '%s'\n", name); + return; + } + + is_realtime = 1; + } + + ast_cli(fd, "\nACL: %s%s\n---------------------------------------------\n", name, is_realtime ? " (realtime)" : ""); + for (ha = named_acl->ha; ha; ha = ha->next) { + char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr)); + char *mask = ast_sockaddr_stringify_addr(&ha->netmask); + ast_cli(fd, "%3d: %s - %s/%s\n", ha_index, ha->sense == AST_SENSE_ALLOW ? "allow" : " deny", addr, mask); + ha_index++; + } +} + +/*! + * \internal + * \brief secondary handler for the 'acl show' command (no args) + * + * \param fd file descriptor of the cli + */ +static void cli_display_named_acl_list(int fd) +{ + struct ao2_iterator i; + void *o; + RAII_VAR(struct named_acl_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + + ast_cli(fd, "\nacl\n---\n"); + + if (!cfg || !cfg->named_acl_list) { + ast_cli(fd, "ACL configuration isn't available.\n"); + return; + } + i = ao2_iterator_init(cfg->named_acl_list, 0); + + while ((o = ao2_iterator_next(&i))) { + struct named_acl *named_acl = o; + ast_cli(fd, "%s\n", named_acl->name); + ao2_ref(o, -1); + } + + ao2_iterator_destroy(&i); +} + +/* \brief ACL command show <name> */ +static char *handle_show_named_acl_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "acl show"; + e->usage = + "Usage: acl show [name]\n" + " Shows a list of named ACLs or lists all entries in a given named ACL.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc == 2) { + cli_display_named_acl_list(a->fd); + return CLI_SUCCESS; + } + + if (a->argc == 3) { + cli_display_named_acl(a->fd, a->argv[2]); + return CLI_SUCCESS; + } + + + return CLI_SHOWUSAGE; +} + +static struct ast_cli_entry cli_named_acl[] = { + AST_CLI_DEFINE(handle_show_named_acl_cmd, "Show a named ACL or list all named ACLs"), +}; + +int ast_named_acl_init() +{ + ast_cli_register_multiple(cli_named_acl, ARRAY_LEN(cli_named_acl)); + + if (aco_info_init(&cfg_info)) { + return 0; + } + + /* Register the global options */ + aco_option_register(&cfg_info, "systemname", ACO_EXACT, global_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct named_acl_global_config, systemname)); + + /* Register the per level options. */ + aco_option_register(&cfg_info, "permit", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 1, FLDSET(struct named_acl, ha)); + aco_option_register(&cfg_info, "deny", ACO_EXACT, named_acl_types, NULL, OPT_ACL_T, 0, FLDSET(struct named_acl, ha)); + + if (aco_process_config(&cfg_info, 0)) { + return 0; + } + + return 0; +} |