DPP: Clean up configuration parsing
Share a single parsing implementation for both hostapd and wpa_supplicant to avoid code duplication. In addition, clean up the implementation to be more easily extensible. Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
parent
3e1cfead0b
commit
9305c2332b
4 changed files with 195 additions and 299 deletions
|
@ -519,171 +519,31 @@ static int hostapd_dpp_set_configurator(struct hostapd_data *hapd,
|
|||
struct dpp_authentication *auth,
|
||||
const char *cmd)
|
||||
{
|
||||
const char *pos, *end;
|
||||
struct dpp_configuration *conf_sta = NULL, *conf_ap = NULL;
|
||||
struct dpp_configurator *conf = NULL;
|
||||
u8 ssid[32] = { "test" };
|
||||
size_t ssid_len = 4;
|
||||
char pass[64] = { };
|
||||
size_t pass_len = 0;
|
||||
u8 psk[PMK_LEN];
|
||||
int psk_set = 0;
|
||||
char *group_id = NULL;
|
||||
const char *pos;
|
||||
|
||||
if (!cmd)
|
||||
return 0;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "DPP: Set configurator parameters: %s", cmd);
|
||||
pos = os_strstr(cmd, " ssid=");
|
||||
if (pos) {
|
||||
pos += 6;
|
||||
end = os_strchr(pos, ' ');
|
||||
ssid_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
ssid_len /= 2;
|
||||
if (ssid_len > sizeof(ssid) ||
|
||||
hexstr2bin(pos, ssid, ssid_len) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " pass=");
|
||||
if (pos) {
|
||||
pos += 6;
|
||||
end = os_strchr(pos, ' ');
|
||||
pass_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
pass_len /= 2;
|
||||
if (pass_len > sizeof(pass) - 1 || pass_len < 8 ||
|
||||
hexstr2bin(pos, (u8 *) pass, pass_len) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " psk=");
|
||||
if (pos) {
|
||||
pos += 5;
|
||||
if (hexstr2bin(pos, psk, PMK_LEN) < 0)
|
||||
goto fail;
|
||||
psk_set = 1;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " group_id=");
|
||||
if (pos) {
|
||||
size_t group_id_len;
|
||||
|
||||
pos += 10;
|
||||
end = os_strchr(pos, ' ');
|
||||
group_id_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
group_id = os_malloc(group_id_len + 1);
|
||||
if (!group_id)
|
||||
goto fail;
|
||||
os_memcpy(group_id, pos, group_id_len);
|
||||
group_id[group_id_len] = '\0';
|
||||
}
|
||||
|
||||
if (os_strstr(cmd, " conf=sta-")) {
|
||||
conf_sta = os_zalloc(sizeof(struct dpp_configuration));
|
||||
if (!conf_sta)
|
||||
goto fail;
|
||||
os_memcpy(conf_sta->ssid, ssid, ssid_len);
|
||||
conf_sta->ssid_len = ssid_len;
|
||||
if (os_strstr(cmd, " conf=sta-psk") ||
|
||||
os_strstr(cmd, " conf=sta-sae") ||
|
||||
os_strstr(cmd, " conf=sta-psk-sae")) {
|
||||
if (os_strstr(cmd, " conf=sta-psk-sae"))
|
||||
conf_sta->akm = DPP_AKM_PSK_SAE;
|
||||
else if (os_strstr(cmd, " conf=sta-sae"))
|
||||
conf_sta->akm = DPP_AKM_SAE;
|
||||
else
|
||||
conf_sta->akm = DPP_AKM_PSK;
|
||||
if (psk_set) {
|
||||
os_memcpy(conf_sta->psk, psk, PMK_LEN);
|
||||
} else {
|
||||
conf_sta->passphrase = os_strdup(pass);
|
||||
if (!conf_sta->passphrase)
|
||||
goto fail;
|
||||
}
|
||||
} else if (os_strstr(cmd, " conf=sta-dpp")) {
|
||||
conf_sta->akm = DPP_AKM_DPP;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
if (os_strstr(cmd, " group_id=")) {
|
||||
conf_sta->group_id = group_id;
|
||||
group_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (os_strstr(cmd, " conf=ap-")) {
|
||||
conf_ap = os_zalloc(sizeof(struct dpp_configuration));
|
||||
if (!conf_ap)
|
||||
goto fail;
|
||||
os_memcpy(conf_ap->ssid, ssid, ssid_len);
|
||||
conf_ap->ssid_len = ssid_len;
|
||||
if (os_strstr(cmd, " conf=ap-psk") ||
|
||||
os_strstr(cmd, " conf=ap-sae") ||
|
||||
os_strstr(cmd, " conf=ap-psk-sae")) {
|
||||
if (os_strstr(cmd, " conf=ap-psk-sae"))
|
||||
conf_ap->akm = DPP_AKM_PSK_SAE;
|
||||
else if (os_strstr(cmd, " conf=ap-sae"))
|
||||
conf_ap->akm = DPP_AKM_SAE;
|
||||
else
|
||||
conf_ap->akm = DPP_AKM_PSK;
|
||||
if (psk_set) {
|
||||
os_memcpy(conf_ap->psk, psk, PMK_LEN);
|
||||
} else if (pass_len > 0) {
|
||||
conf_ap->passphrase = os_strdup(pass);
|
||||
if (!conf_ap->passphrase)
|
||||
goto fail;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
} else if (os_strstr(cmd, " conf=ap-dpp")) {
|
||||
conf_ap->akm = DPP_AKM_DPP;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
if (os_strstr(cmd, " group_id=")) {
|
||||
conf_ap->group_id = group_id;
|
||||
group_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " expiry=");
|
||||
if (pos) {
|
||||
long int val;
|
||||
|
||||
pos += 8;
|
||||
val = strtol(pos, NULL, 0);
|
||||
if (val <= 0)
|
||||
goto fail;
|
||||
if (conf_sta)
|
||||
conf_sta->netaccesskey_expiry = val;
|
||||
if (conf_ap)
|
||||
conf_ap->netaccesskey_expiry = val;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " configurator=");
|
||||
if (pos) {
|
||||
auth->configurator = 1;
|
||||
pos += 14;
|
||||
conf = hostapd_dpp_configurator_get_id(hapd, atoi(pos));
|
||||
if (!conf) {
|
||||
auth->conf = hostapd_dpp_configurator_get_id(hapd, atoi(pos));
|
||||
if (!auth->conf) {
|
||||
wpa_printf(MSG_INFO,
|
||||
"DPP: Could not find the specified configurator");
|
||||
goto fail;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
auth->conf_sta = conf_sta;
|
||||
auth->conf_ap = conf_ap;
|
||||
auth->conf = conf;
|
||||
os_free(group_id);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
wpa_msg(hapd->msg_ctx, MSG_INFO,
|
||||
"DPP: Failed to set configurator parameters");
|
||||
dpp_configuration_free(conf_sta);
|
||||
dpp_configuration_free(conf_ap);
|
||||
os_free(group_id);
|
||||
return -1;
|
||||
if (dpp_configuration_parse(auth, cmd) < 0) {
|
||||
wpa_msg(hapd->msg_ctx, MSG_INFO,
|
||||
"DPP: Failed to set configurator parameters");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
171
src/common/dpp.c
171
src/common/dpp.c
|
@ -4048,6 +4048,70 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
static int bin_str_eq(const char *val, size_t len, const char *cmp)
|
||||
{
|
||||
return os_strlen(cmp) == len && os_memcmp(val, cmp, len) == 0;
|
||||
}
|
||||
|
||||
|
||||
struct dpp_configuration * dpp_configuration_alloc(const char *type)
|
||||
{
|
||||
struct dpp_configuration *conf;
|
||||
const char *end;
|
||||
size_t len;
|
||||
|
||||
conf = os_zalloc(sizeof(*conf));
|
||||
if (!conf)
|
||||
goto fail;
|
||||
|
||||
end = os_strchr(type, ' ');
|
||||
if (end)
|
||||
len = end - type;
|
||||
else
|
||||
len = os_strlen(type);
|
||||
|
||||
if (bin_str_eq(type, len, "psk"))
|
||||
conf->akm = DPP_AKM_PSK;
|
||||
else if (bin_str_eq(type, len, "sae"))
|
||||
conf->akm = DPP_AKM_SAE;
|
||||
else if (bin_str_eq(type, len, "psk-sae"))
|
||||
conf->akm = DPP_AKM_PSK_SAE;
|
||||
else if (bin_str_eq(type, len, "dpp"))
|
||||
conf->akm = DPP_AKM_DPP;
|
||||
else
|
||||
goto fail;
|
||||
|
||||
return conf;
|
||||
fail:
|
||||
dpp_configuration_free(conf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static int dpp_akm_psk(enum dpp_akm akm)
|
||||
{
|
||||
return akm == DPP_AKM_PSK || akm == DPP_AKM_PSK_SAE;
|
||||
}
|
||||
|
||||
|
||||
static int dpp_akm_sae(enum dpp_akm akm)
|
||||
{
|
||||
return akm == DPP_AKM_SAE || akm == DPP_AKM_PSK_SAE;
|
||||
}
|
||||
|
||||
|
||||
int dpp_configuration_valid(const struct dpp_configuration *conf)
|
||||
{
|
||||
if (conf->ssid_len == 0)
|
||||
return 0;
|
||||
if (dpp_akm_psk(conf->akm) && !conf->passphrase && !conf->psk_set)
|
||||
return 0;
|
||||
if (dpp_akm_sae(conf->akm) && !conf->passphrase)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void dpp_configuration_free(struct dpp_configuration *conf)
|
||||
{
|
||||
if (!conf)
|
||||
|
@ -4058,6 +4122,113 @@ void dpp_configuration_free(struct dpp_configuration *conf)
|
|||
}
|
||||
|
||||
|
||||
int dpp_configuration_parse(struct dpp_authentication *auth, const char *cmd)
|
||||
{
|
||||
const char *pos, *end;
|
||||
struct dpp_configuration *conf_sta = NULL, *conf_ap = NULL;
|
||||
struct dpp_configuration *conf = NULL;
|
||||
|
||||
pos = os_strstr(cmd, " conf=sta-");
|
||||
if (pos) {
|
||||
conf_sta = dpp_configuration_alloc(pos + 10);
|
||||
if (!conf_sta)
|
||||
goto fail;
|
||||
conf = conf_sta;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " conf=ap-");
|
||||
if (pos) {
|
||||
conf_ap = dpp_configuration_alloc(pos + 9);
|
||||
if (!conf_ap)
|
||||
goto fail;
|
||||
conf = conf_ap;
|
||||
}
|
||||
|
||||
if (!conf)
|
||||
return 0;
|
||||
|
||||
pos = os_strstr(cmd, " ssid=");
|
||||
if (pos) {
|
||||
pos += 6;
|
||||
end = os_strchr(pos, ' ');
|
||||
conf->ssid_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
conf->ssid_len /= 2;
|
||||
if (conf->ssid_len > sizeof(conf->ssid) ||
|
||||
hexstr2bin(pos, conf->ssid, conf->ssid_len) < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
#ifdef CONFIG_TESTING_OPTIONS
|
||||
/* use a default SSID for legacy testing reasons */
|
||||
os_memcpy(conf->ssid, "test", 4);
|
||||
conf->ssid_len = 4;
|
||||
#else /* CONFIG_TESTING_OPTIONS */
|
||||
goto fail;
|
||||
#endif /* CONFIG_TESTING_OPTIONS */
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " pass=");
|
||||
if (pos) {
|
||||
size_t pass_len;
|
||||
|
||||
pos += 6;
|
||||
end = os_strchr(pos, ' ');
|
||||
pass_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
pass_len /= 2;
|
||||
if (pass_len > 63 || pass_len < 8)
|
||||
goto fail;
|
||||
conf->passphrase = os_zalloc(pass_len + 1);
|
||||
if (!conf->passphrase ||
|
||||
hexstr2bin(pos, (u8 *) conf->passphrase, pass_len) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " psk=");
|
||||
if (pos) {
|
||||
pos += 5;
|
||||
if (hexstr2bin(pos, conf->psk, PMK_LEN) < 0)
|
||||
goto fail;
|
||||
conf->psk_set = 1;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " group_id=");
|
||||
if (pos) {
|
||||
size_t group_id_len;
|
||||
|
||||
pos += 10;
|
||||
end = os_strchr(pos, ' ');
|
||||
group_id_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
conf->group_id = os_malloc(group_id_len + 1);
|
||||
if (!conf->group_id)
|
||||
goto fail;
|
||||
os_memcpy(conf->group_id, pos, group_id_len);
|
||||
conf->group_id[group_id_len] = '\0';
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " expiry=");
|
||||
if (pos) {
|
||||
long int val;
|
||||
|
||||
pos += 8;
|
||||
val = strtol(pos, NULL, 0);
|
||||
if (val <= 0)
|
||||
goto fail;
|
||||
conf->netaccesskey_expiry = val;
|
||||
}
|
||||
|
||||
if (!dpp_configuration_valid(conf))
|
||||
goto fail;
|
||||
|
||||
auth->conf_sta = conf_sta;
|
||||
auth->conf_ap = conf_ap;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
dpp_configuration_free(conf_sta);
|
||||
dpp_configuration_free(conf_ap);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void dpp_auth_deinit(struct dpp_authentication *auth)
|
||||
{
|
||||
if (!auth)
|
||||
|
|
|
@ -163,6 +163,7 @@ struct dpp_configuration {
|
|||
/* For legacy configuration */
|
||||
char *passphrase;
|
||||
u8 psk[32];
|
||||
int psk_set;
|
||||
};
|
||||
|
||||
struct dpp_authentication {
|
||||
|
@ -392,7 +393,10 @@ int dpp_auth_conf_rx(struct dpp_authentication *auth, const u8 *hdr,
|
|||
const u8 *attr_start, size_t attr_len);
|
||||
int dpp_notify_new_qr_code(struct dpp_authentication *auth,
|
||||
struct dpp_bootstrap_info *peer_bi);
|
||||
struct dpp_configuration * dpp_configuration_alloc(const char *type);
|
||||
int dpp_configuration_valid(const struct dpp_configuration *conf);
|
||||
void dpp_configuration_free(struct dpp_configuration *conf);
|
||||
int dpp_configuration_parse(struct dpp_authentication *auth, const char *cmd);
|
||||
void dpp_auth_deinit(struct dpp_authentication *auth);
|
||||
struct wpabuf *
|
||||
dpp_conf_req_rx(struct dpp_authentication *auth, const u8 *attr_start,
|
||||
|
|
|
@ -554,169 +554,30 @@ static int wpas_dpp_set_configurator(struct wpa_supplicant *wpa_s,
|
|||
struct dpp_authentication *auth,
|
||||
const char *cmd)
|
||||
{
|
||||
const char *pos, *end;
|
||||
struct dpp_configuration *conf_sta = NULL, *conf_ap = NULL;
|
||||
struct dpp_configurator *conf = NULL;
|
||||
u8 ssid[32] = { "test" };
|
||||
size_t ssid_len = 4;
|
||||
char pass[64] = { };
|
||||
size_t pass_len = 0;
|
||||
u8 psk[PMK_LEN];
|
||||
int psk_set = 0;
|
||||
char *group_id = NULL;
|
||||
const char *pos;
|
||||
|
||||
if (!cmd)
|
||||
return 0;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "DPP: Set configurator parameters: %s", cmd);
|
||||
pos = os_strstr(cmd, " ssid=");
|
||||
if (pos) {
|
||||
pos += 6;
|
||||
end = os_strchr(pos, ' ');
|
||||
ssid_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
ssid_len /= 2;
|
||||
if (ssid_len > sizeof(ssid) ||
|
||||
hexstr2bin(pos, ssid, ssid_len) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " pass=");
|
||||
if (pos) {
|
||||
pos += 6;
|
||||
end = os_strchr(pos, ' ');
|
||||
pass_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
pass_len /= 2;
|
||||
if (pass_len > sizeof(pass) - 1 || pass_len < 8 ||
|
||||
hexstr2bin(pos, (u8 *) pass, pass_len) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " psk=");
|
||||
if (pos) {
|
||||
pos += 5;
|
||||
if (hexstr2bin(pos, psk, PMK_LEN) < 0)
|
||||
goto fail;
|
||||
psk_set = 1;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " group_id=");
|
||||
if (pos) {
|
||||
size_t group_id_len;
|
||||
|
||||
pos += 10;
|
||||
end = os_strchr(pos, ' ');
|
||||
group_id_len = end ? (size_t) (end - pos) : os_strlen(pos);
|
||||
group_id = os_malloc(group_id_len + 1);
|
||||
if (!group_id)
|
||||
goto fail;
|
||||
os_memcpy(group_id, pos, group_id_len);
|
||||
group_id[group_id_len] = '\0';
|
||||
}
|
||||
|
||||
if (os_strstr(cmd, " conf=sta-")) {
|
||||
conf_sta = os_zalloc(sizeof(struct dpp_configuration));
|
||||
if (!conf_sta)
|
||||
goto fail;
|
||||
os_memcpy(conf_sta->ssid, ssid, ssid_len);
|
||||
conf_sta->ssid_len = ssid_len;
|
||||
if (os_strstr(cmd, " conf=sta-psk") ||
|
||||
os_strstr(cmd, " conf=sta-sae") ||
|
||||
os_strstr(cmd, " conf=sta-psk-sae")) {
|
||||
if (os_strstr(cmd, " conf=sta-psk-sae"))
|
||||
conf_sta->akm = DPP_AKM_PSK_SAE;
|
||||
else if (os_strstr(cmd, " conf=sta-sae"))
|
||||
conf_sta->akm = DPP_AKM_SAE;
|
||||
else
|
||||
conf_sta->akm = DPP_AKM_PSK;
|
||||
if (psk_set) {
|
||||
os_memcpy(conf_sta->psk, psk, PMK_LEN);
|
||||
} else if (pass_len > 0) {
|
||||
conf_sta->passphrase = os_strdup(pass);
|
||||
if (!conf_sta->passphrase)
|
||||
goto fail;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
} else if (os_strstr(cmd, " conf=sta-dpp")) {
|
||||
conf_sta->akm = DPP_AKM_DPP;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
if (os_strstr(cmd, " group_id=")) {
|
||||
conf_sta->group_id = group_id;
|
||||
group_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (os_strstr(cmd, " conf=ap-")) {
|
||||
conf_ap = os_zalloc(sizeof(struct dpp_configuration));
|
||||
if (!conf_ap)
|
||||
goto fail;
|
||||
os_memcpy(conf_ap->ssid, ssid, ssid_len);
|
||||
conf_ap->ssid_len = ssid_len;
|
||||
if (os_strstr(cmd, " conf=ap-psk") ||
|
||||
os_strstr(cmd, " conf=ap-sae") ||
|
||||
os_strstr(cmd, " conf=ap-psk-sae")) {
|
||||
if (os_strstr(cmd, " conf=ap-psk-sae"))
|
||||
conf_ap->akm = DPP_AKM_PSK_SAE;
|
||||
else if (os_strstr(cmd, " conf=ap-sae"))
|
||||
conf_ap->akm = DPP_AKM_SAE;
|
||||
else
|
||||
conf_ap->akm = DPP_AKM_PSK;
|
||||
if (psk_set) {
|
||||
os_memcpy(conf_ap->psk, psk, PMK_LEN);
|
||||
} else {
|
||||
conf_ap->passphrase = os_strdup(pass);
|
||||
if (!conf_ap->passphrase)
|
||||
goto fail;
|
||||
}
|
||||
} else if (os_strstr(cmd, " conf=ap-dpp")) {
|
||||
conf_ap->akm = DPP_AKM_DPP;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
if (os_strstr(cmd, " group_id=")) {
|
||||
conf_ap->group_id = group_id;
|
||||
group_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " expiry=");
|
||||
if (pos) {
|
||||
long int val;
|
||||
|
||||
pos += 8;
|
||||
val = strtol(pos, NULL, 0);
|
||||
if (val <= 0)
|
||||
goto fail;
|
||||
if (conf_sta)
|
||||
conf_sta->netaccesskey_expiry = val;
|
||||
if (conf_ap)
|
||||
conf_ap->netaccesskey_expiry = val;
|
||||
}
|
||||
|
||||
pos = os_strstr(cmd, " configurator=");
|
||||
if (pos) {
|
||||
pos += 14;
|
||||
conf = dpp_configurator_get_id(wpa_s, atoi(pos));
|
||||
if (!conf) {
|
||||
auth->conf = dpp_configurator_get_id(wpa_s, atoi(pos));
|
||||
if (!auth->conf) {
|
||||
wpa_printf(MSG_INFO,
|
||||
"DPP: Could not find the specified configurator");
|
||||
goto fail;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
auth->conf_sta = conf_sta;
|
||||
auth->conf_ap = conf_ap;
|
||||
auth->conf = conf;
|
||||
os_free(group_id);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
wpa_msg(wpa_s, MSG_INFO, "DPP: Failed to set configurator parameters");
|
||||
dpp_configuration_free(conf_sta);
|
||||
dpp_configuration_free(conf_ap);
|
||||
os_free(group_id);
|
||||
return -1;
|
||||
if (dpp_configuration_parse(auth, cmd) < 0) {
|
||||
wpa_msg(wpa_s, MSG_INFO,
|
||||
"DPP: Failed to set configurator parameters");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue