Skip to content

Commit 03235ce

Browse files
committed
clients/authconf.c: parse bracketed IPv6 addresses correctly [#3329, #3503]
Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
1 parent 39a5a5c commit 03235ce

1 file changed

Lines changed: 83 additions & 4 deletions

File tree

clients/authconf.c

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -588,26 +588,101 @@ int upscli_split_authconf_section(const char *sect_name,
588588
/* Take raw sect_name as input (e.g. a user-written string from config files).
589589
* Normalize it by splitting into user, host, and port components (populating absent values).
590590
* Return normalized components and reconstructed section name in output parameters (if not NULL).
591+
* This looks similar to upscli_splitname() but reserves the option to evolve
592+
* the supported section name syntax differently for the different purpose.
593+
* TOTHINK: Combine the `host:port` logic with upscli_splitaddr() to
594+
* de-duplicate IPv6 nuance handling etc.?
591595
*/
592-
const char *at = NULL, *colon = NULL;
596+
const char *at = NULL, *colon = NULL, *bracket_open = NULL, *bracket_close = NULL;
593597
char *sect_user = NULL, *sect_host = NULL, *sect_port = NULL;
594598
int fixed_sect_user = 0;
595599

596600
if (!sect_name) {
597601
upsdebugx(1, "%s: sect_name is NULL", __func__);
602+
errno = EINVAL;
598603
return -1;
599604
}
600605

601606
if (!(*sect_name)) {
602607
/* TOTHINK: Should this mean `localhost@NUT_PORT`? Or global? Probably neither. */
603608
upsdebugx(1, "%s: sect_name is empty", __func__);
609+
errno = EINVAL;
604610
return -1;
605611
}
606612

607613
at = strchr(sect_name, '@');
608614
colon = strchr(sect_name, ':');
615+
bracket_open = strchr(sect_name, '[');
616+
bracket_close = strchr(sect_name, ']');
617+
618+
/* Sanity checks */
609619
if (at && colon && colon < at) {
610620
upsdebugx(1, "%s: Invalid section header: colon ':' before at '@': '%s'", __func__, sect_name);
621+
errno = EINVAL;
622+
return -1;
623+
}
624+
625+
/* IPv6 numeric addresses are a series of colon-separated hex digits wrapped in square brackets */
626+
if (bracket_open && colon && colon < bracket_open) {
627+
upsdebugx(1, "%s: Invalid section header: colon ':' before opening bracket '[': '%s'", __func__, sect_name);
628+
errno = EINVAL;
629+
return -1;
630+
}
631+
632+
if ( (bracket_open && !bracket_close)
633+
|| (!bracket_open && bracket_close)
634+
) {
635+
upsdebugx(1, "%s: Invalid section header: single bracket '[' or ']' in text: '%s'", __func__, sect_name);
636+
errno = EINVAL;
637+
return -1;
638+
}
639+
640+
if (bracket_close && colon && colon > bracket_close) {
641+
upsdebugx(1, "%s: Invalid section header: first colon ':' is after closing bracket ']': '%s'", __func__, sect_name);
642+
errno = EINVAL;
643+
return -1;
644+
}
645+
646+
if (bracket_close && !colon) {
647+
upsdebugx(1, "%s: Invalid section header: brackets '[...]' present but no colon ':' inside: '%s'", __func__, sect_name);
648+
errno = EINVAL;
649+
return -1;
650+
}
651+
652+
/* For valid IPv6 spelling, consider there should be several colons inside brackets */
653+
if (bracket_open && bracket_close && colon && colon > bracket_open && colon < bracket_close) {
654+
/* There should be at most one more colon inside brackets
655+
* Technically: up to 8 hex sections split by 7 colons,
656+
* but long stretches of zeroes may collapse into "::" ONCE.
657+
*/
658+
colon = strchr(colon + 1, ':');
659+
if (!colon || colon > bracket_close) {
660+
upsdebugx(1, "%s: Invalid section header: withh numeric IPv6 there must be multiple colons ':' inside brackets: '%s'", __func__, sect_name);
661+
errno = EINVAL;
662+
return -1;
663+
}
664+
}
665+
666+
/* For valid IPv6 spelling, consider the colon after brackets as the port separator (if any, NULL otherwise) */
667+
if (bracket_close) {
668+
/* There should be at most one colon after brackets (may also be none) */
669+
colon = strchr(bracket_close + 1, ':');
670+
}
671+
672+
if (colon && strchr(colon + 1, ':')) {
673+
upsdebugx(1, "%s: Invalid section header: multiple colons ':' in text: '%s'", __func__, sect_name);
674+
return -1;
675+
}
676+
677+
if (bracket_open && strchr(bracket_open + 1, '[')) {
678+
upsdebugx(1, "%s: Invalid section header: multiple opening brackets '[' in text: '%s'", __func__, sect_name);
679+
errno = EINVAL;
680+
return -1;
681+
}
682+
683+
if (bracket_close && strchr(bracket_close + 1, ']')) {
684+
upsdebugx(1, "%s: Invalid section header: multiple closing brackets ']' in text: '%s'", __func__, sect_name);
685+
errno = EINVAL;
611686
return -1;
612687
}
613688

@@ -722,8 +797,10 @@ static void handle_authconf_args(size_t numargs, char **arg, int global_scope)
722797

723798
current_section_ignored = 0;
724799

725-
sect_name = xstrdup(&arg[0][1]); /* forget leading '[' */
726-
end_bracket = strchr(sect_name, ']');
800+
/* forget leading '[' */
801+
sect_name = xstrdup(&arg[0][1]);
802+
/* we would remove the LAST seen bracket (there may be some in text, in case of IPv6 addresses) */
803+
end_bracket = strrchr(sect_name, ']');
727804
if (!end_bracket || !strcmp(sect_name, "_global_defaults")) {
728805
free(sect_name);
729806

@@ -740,7 +817,9 @@ static void handle_authconf_args(size_t numargs, char **arg, int global_scope)
740817
return;
741818
}
742819

743-
*(char *)(end_bracket) = '\0'; /* forget trailing ']' and any characters after it (comments etc.) */
820+
/* forget trailing ']' and any characters after it (comments etc.)...
821+
* although those should be separate parseconf arguments */
822+
*(char *)(end_bracket) = '\0';
744823

745824
if (upscli_split_authconf_section(sect_name, &normalized_sect_name,
746825
&sect_user, &current_section_with_fixed_username,

0 commit comments

Comments
 (0)