+ char *buf, *tok, *saveptr;
+ bool result;
+ int type;
+
+ /* Use strdup directly since we can't easily reach a talloc parent */
+ buf = strdup(strip_string(field_buffer(field, 0)));
+ if (!buf)
+ /* We tried */
+ return true;
+
+ result = false;
+ tok = strtok_r(buf, " ", &saveptr);
+ if (!tok || *tok == '\0')
+ goto err;
+
+ while (tok) {
+ type = addr_scheme(tok);
+ if (!(type == AF_INET || type == AF_INET6))
+ goto err;
+
+ tok = strtok_r(NULL, " ", &saveptr);
+ }
+ result = true;
+
+err:
+ free(buf);
+ return result;
+}
+
+static bool check_ip_multi_char(int c, const void *arg __attribute__((unused)))
+{
+ return (check_ipv4_multi_char(c) || check_ipv6_multi_char(c));
+}
+
+void widget_textbox_set_validator_ip(struct nc_widget_textbox *textbox)
+{
+ if (!textbox->set->ip_type) {
+ textbox->set->ip_type = new_fieldtype(check_ip_field, NULL);
+ }
+ set_field_type(textbox->widget.field, textbox->set->ip_type);
+}
+
+/*
+ * In a perfect world we would use link_fieldtype() but segfaults do not
+ * enhance the user experience.
+ */
+void widget_textbox_set_validator_ip_multi(struct nc_widget_textbox *textbox)
+{
+ if (!textbox->set->ip_multi_type) {
+ textbox->set->ip_multi_type = new_fieldtype(
+ check_ip_multi_field,
+ check_ip_multi_char);
+ }
+ set_field_type(textbox->widget.field, textbox->set->ip_multi_type);
+}
+
+static void subset_update_order(struct nc_widget_subset *subset)
+{
+ char *str;
+ int i, val;
+
+ for (i = 0; i < subset->n_active; i++) {
+ val = subset->active[i];
+ str = talloc_asprintf(subset, "(%d) %s",
+ i, subset->options[val].str);
+ set_field_buffer(subset->options[val].field, 0,
+ str);
+ talloc_free(str);
+ }
+}
+
+static void widget_focus_change(struct nc_widget *widget, FIELD *field,
+ bool focussed);
+
+static void subset_delete_active(struct nc_widget_subset *subset, int idx)
+{
+ bool last = idx == (subset->n_active - 1);
+ struct nc_widgetset *set = subset->set;
+ struct nc_widget *widget;
+ size_t rem;
+ int i, val;
+ uint32_t opts;
+
+ /* Shift field focus to nearest active option or next visible field */
+ if (subset->n_active > 1) {
+ if (last)
+ val = subset->active[idx - 1];
+ else
+ val = subset->active[idx + 1];
+ set->cur_field = subset->options[val].field;
+ } else {
+ for (i = 0; i < set->n_fields; i++)
+ if (field_visible(set->fields[i])) {
+ opts = field_opts(set->fields[i]);
+ if ((opts & O_ACTIVE) == O_ACTIVE) {
+ set->cur_field = set->fields[i];
+ break;
+ }
+ }
+ }
+
+ set_current_field(set->form, set->cur_field);
+ widget = field_userptr(set->cur_field);
+ widget_focus_change(widget, set->cur_field, true);
+ if (set->widget_focus)
+ set->widget_focus(widget, set->widget_focus_arg);
+
+ /* Update active array */
+ rem = sizeof(int) * (subset->n_active - idx - 1);
+ val = subset->active[idx];
+ field_set_visible(subset->options[val].field, false);
+ if (rem)
+ memmove(&subset->active[idx], &subset->active[idx + 1], rem);
+ subset->n_active--;
+ subset->active = talloc_realloc(subset, subset->active,
+ int, subset->n_active);
+
+ subset->widget.height = subset->n_active;
+}
+
+static bool subset_process_key(struct nc_widget *w, FORM *form, int key)
+{
+ struct nc_widget_subset *subset = to_subset(w);
+ int i, val, opt_idx = -1;
+ FIELD *field;
+
+ if (key != '-' && key != '+' && key != KEY_DC && key != KEY_BACKSPACE)
+ return false;
+
+ field = current_field(form);
+
+ for (i = 0; i < subset->n_active; i++) {
+ val = subset->active[i];
+ if (subset->options[val].field == field) {
+ opt_idx = i;
+ break;