From ae68e5b11573b1b7dc0eba9d95c7ccbba1f1b23d Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 6 May 2020 18:50:59 +0300 Subject: Initial import. --- src/numeric.ml | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/validate_value.ml | 42 ++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/numeric.ml create mode 100644 src/validate_value.ml (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml new file mode 100644 index 000000000..12ea04ca4 --- /dev/null +++ b/src/numeric.ml @@ -0,0 +1,71 @@ +type options = { + positive: bool; + nonnegative: bool; + ranges: string list; +} + +let default_opts = { + positive = false; + nonnegative = false; + ranges = [] +} + +let int_of_string = Big_int.big_int_of_string +let int_of_string_opt = Big_int.big_int_of_string_opt +let big = Big_int.big_int_of_int +let (>=) = Big_int.ge_big_int +let (<=) = Big_int.le_big_int + +let opts = ref default_opts + +let number_arg = ref "" + +let args = [ + ("--non-negative", Arg.Unit (fun () -> opts := {!opts with nonnegative=true}), "Check if the number is non-negative (>= 0)"); + ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); + ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number is within a range (inclusive)"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) +let () = Arg.parse args (fun s -> number_arg := s) usage + +let check_nonnegative opts n = + if opts.nonnegative && (n < (big 0)) then + failwith "Number should be non-negative." + +let check_positive opts n = + if opts.positive && (n <= (big 0)) then + failwith "Number should be positive" + +let number_of_string s = + let n = int_of_string_opt s in + match n with + | Some n -> n + | None -> + Printf.ksprintf failwith "'%s' is not a valid integer number" s + +let range_of_string s = + let rs = String.split_on_char '-' s |> List.map String.trim |> List.map int_of_string_opt in + match rs with + | [Some l; Some r] -> (l, r) + | _ -> Printf.ksprintf failwith "'%s' is not a valid number range" s + +let check_ranges ranges n = + let in_range (l, r) n = (n >= l) && (n <= r) in + let res = List.fold_left (fun acc r -> acc || (in_range r n)) false ranges in + if not res then + Printf.ksprintf failwith "Number is not in any of allowed ranges" + +let () = try + let opts = !opts in + let n = number_of_string !number_arg in + check_nonnegative opts n; + check_positive opts n; + if opts.ranges <> [] then + let ranges = List.map range_of_string opts.ranges in + check_ranges ranges n +with (Failure err) -> + print_endline err; + exit 1 + diff --git a/src/validate_value.ml b/src/validate_value.ml new file mode 100644 index 000000000..3af58e0a4 --- /dev/null +++ b/src/validate_value.ml @@ -0,0 +1,42 @@ +type check = Regex of string | Exec of string + +let validate_value value_constraint value = + match value_constraint with + | Regex s -> + (try + let _ = Pcre.exec ~pat:s value in true + with Not_found -> false) + | Exec c -> + (* XXX: Using Unix.system is a bad idea on multiple levels, + especially when the input comes directly from the user... + We should do something about it. + *) + let result = Unix.system (Printf.sprintf "%s %s" c value) in + match result with + | Unix.WEXITED 0 -> true + | Unix.WEXITED 127 -> + let () = Printf.printf "Could not execute validator %s" c in + false + | _ -> false + +let value = ref "" + +let checks = ref [] + +let args = [ + ("--regex", Arg.String (fun s -> checks := (Regex s) :: !checks), "Check the value against a regex"); + ("--exec", Arg.String (fun s -> checks := (Exec s) :: !checks), "Check the value against an external command"); + ("--value", Arg.String (fun s -> value := s), "Value to check"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = Arg.parse args (fun _ -> ()) usage + +let _ = + let value = !value in + let checks = !checks in + match checks with + | [] -> exit 0 + | _ -> + List.iter (fun c -> if (validate_value c value) then exit 0 else ()) checks; + exit 1 -- cgit v1.2.3 From 20b538f9d7c8212cffc84e524f4c954708ff3de7 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 20 May 2020 22:04:26 +0300 Subject: T2434: use correct bigint comparison. --- src/numeric.ml | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index 12ea04ca4..17fcac63f 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -15,6 +15,8 @@ let int_of_string_opt = Big_int.big_int_of_string_opt let big = Big_int.big_int_of_int let (>=) = Big_int.ge_big_int let (<=) = Big_int.le_big_int +let (<) = Big_int.lt_big_int +let (>) = Big_int.gt_big_int let opts = ref default_opts -- cgit v1.2.3 From 3f84c582f966f83d86cf066328eeced4704d63a4 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 19 Jun 2020 02:11:21 +0300 Subject: T1901: quote the value to avoid having the shell interpret anything as a part of the command. --- debian/changelog | 6 ++++++ src/validate_value.ml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/debian/changelog b/debian/changelog index 298b33d3c..e0ac2fa3f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vyos-utils (0.0.3) unstable; urgency=medium + + * Quote values properly (T1901). + + -- Daniil Baturin Fri, 19 Jun 2020 02:12:22 +0300 + vyos-utils (0.0.2) unstable; urgency=medium * Fixed an invalid argument bug in the --non-negative option. diff --git a/src/validate_value.ml b/src/validate_value.ml index 3af58e0a4..250f9f180 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -11,7 +11,7 @@ let validate_value value_constraint value = especially when the input comes directly from the user... We should do something about it. *) - let result = Unix.system (Printf.sprintf "%s %s" c value) in + let result = Unix.system (Printf.sprintf "%s \'%s\'" c value) in match result with | Unix.WEXITED 0 -> true | Unix.WEXITED 127 -> -- cgit v1.2.3 From 68d1b283f7669909474f6b8159f3abc426e0fd3d Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 18 Feb 2021 13:27:38 +0000 Subject: T2759: collect output from validators in a buffer and print it only if validation fails. --- debian/rules | 2 +- src/validate_value.ml | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/debian/rules b/debian/rules index b3ff1c6af..e3462afba 100755 --- a/debian/rules +++ b/debian/rules @@ -9,7 +9,7 @@ override_dh_auto_build: eval `opam env` mkdir -p _build ocamlfind ocamlopt -o _build/numeric -package num -linkpkg src/numeric.ml - ocamlfind ocamlopt -o _build/validate-value -package pcre,unix -linkpkg src/validate_value.ml + ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml override_dh_auto_install: mkdir -p $(DIR)/usr/libexec/vyos/validators diff --git a/src/validate_value.ml b/src/validate_value.ml index 250f9f180..02cd15a40 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -1,23 +1,27 @@ type check = Regex of string | Exec of string -let validate_value value_constraint value = +let validate_value buf value_constraint value = match value_constraint with | Regex s -> (try let _ = Pcre.exec ~pat:s value in true with Not_found -> false) | Exec c -> - (* XXX: Using Unix.system is a bad idea on multiple levels, + (* XXX: Unix.open_process_in is "shelling out", which is a bad idea on multiple levels, especially when the input comes directly from the user... We should do something about it. *) - let result = Unix.system (Printf.sprintf "%s \'%s\'" c value) in + let chan = Unix.open_process_in (Printf.sprintf "%s \'%s\' 2>&1" c value) in + let out = try CCIO.read_all chan with _ -> "" in + let result = Unix.close_process_in chan in match result with | Unix.WEXITED 0 -> true | Unix.WEXITED 127 -> let () = Printf.printf "Could not execute validator %s" c in false - | _ -> false + | _ -> + let () = Buffer.add_string buf out; Buffer.add_string buf "\n" in + false let value = ref "" @@ -33,10 +37,15 @@ let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) let () = Arg.parse args (fun _ -> ()) usage let _ = + let buf = Buffer.create 4096 in let value = !value in let checks = !checks in match checks with | [] -> exit 0 | _ -> - List.iter (fun c -> if (validate_value c value) then exit 0 else ()) checks; + List.iter (fun c -> if (validate_value buf c value) then exit 0 else ()) checks; + (* If we got this far, value validation failed. + Show the user output from the validators. + *) + Buffer.contents buf |> print_endline; exit 1 -- cgit v1.2.3 From ed9a41190997d4a46c1e8efd21ac06aeb0965965 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 18 Apr 2021 12:19:08 +0300 Subject: Consistent formatting in validate-value.ml --- src/validate_value.ml | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/validate_value.ml b/src/validate_value.ml index 02cd15a40..0b4148fea 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -1,36 +1,35 @@ type check = Regex of string | Exec of string let validate_value buf value_constraint value = - match value_constraint with - | Regex s -> - (try - let _ = Pcre.exec ~pat:s value in true - with Not_found -> false) - | Exec c -> - (* XXX: Unix.open_process_in is "shelling out", which is a bad idea on multiple levels, - especially when the input comes directly from the user... - We should do something about it. - *) - let chan = Unix.open_process_in (Printf.sprintf "%s \'%s\' 2>&1" c value) in - let out = try CCIO.read_all chan with _ -> "" in - let result = Unix.close_process_in chan in - match result with - | Unix.WEXITED 0 -> true - | Unix.WEXITED 127 -> - let () = Printf.printf "Could not execute validator %s" c in - false - | _ -> - let () = Buffer.add_string buf out; Buffer.add_string buf "\n" in - false + match value_constraint with + | Regex s -> + (try let _ = Pcre.exec ~pat:s value in true + with Not_found -> false) + | Exec c -> + (* XXX: Unix.open_process_in is "shelling out", which is a bad idea on multiple levels, + especially when the input comes directly from the user... + We should do something about it. + *) + let chan = Unix.open_process_in (Printf.sprintf "%s \'%s\' 2>&1" c value) in + let out = try CCIO.read_all chan with _ -> "" in + let result = Unix.close_process_in chan in + match result with + | Unix.WEXITED 0 -> true + | Unix.WEXITED 127 -> + let () = Printf.printf "Could not execute validator %s" c in + false + | _ -> + let () = Buffer.add_string buf out; Buffer.add_string buf "\n" in + false let value = ref "" let checks = ref [] let args = [ - ("--regex", Arg.String (fun s -> checks := (Regex s) :: !checks), "Check the value against a regex"); - ("--exec", Arg.String (fun s -> checks := (Exec s) :: !checks), "Check the value against an external command"); - ("--value", Arg.String (fun s -> value := s), "Value to check"); + ("--regex", Arg.String (fun s -> checks := (Regex s) :: !checks), "Check the value against a regex"); + ("--exec", Arg.String (fun s -> checks := (Exec s) :: !checks), "Check the value against an external command"); + ("--value", Arg.String (fun s -> value := s), "Value to check"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) -- cgit v1.2.3 From cf462028d5921efd53996cee0c340e8d7f402784 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 21 May 2021 15:22:46 -0500 Subject: T3574: add support for --grp argument Add support for --grp argument, which joins the arguments following (up until the next --grp) with logical and; arguments preceding a group, and other groups following, are joined by logical or. --- src/validate_value.ml | 55 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/validate_value.ml b/src/validate_value.ml index 0b4148fea..6d5261862 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -1,7 +1,30 @@ -type check = Regex of string | Exec of string +type check = Regex of string | Exec of string | Grp | Faux -let validate_value buf value_constraint value = +let checks = ref [] + +let find_next_grp n l = + let rec aux i = function + | [] -> List.length l + | h::t -> + if i > n && h = Grp then i + else aux (i+1) t + in aux 0 l + +let get_next_range = + let n = ref (-1) in + let f () = + let i = !n and j = (n := find_next_grp !n !checks; !n) in + (i, j) in + f + +let rec validate_value buf value_constraint value = match value_constraint with + | Faux -> false + | Grp -> + let (k, j) = get_next_range () in + let in_grp i = if i > k && i < j then true else false in + let conj_checks = List.filteri (fun i _ -> in_grp i) !checks in + List.fold_left (&&) true (List.map (fun e -> validate_value buf e value) conj_checks) | Regex s -> (try let _ = Pcre.exec ~pat:s value in true with Not_found -> false) @@ -24,25 +47,37 @@ let validate_value buf value_constraint value = let value = ref "" -let checks = ref [] - let args = [ ("--regex", Arg.String (fun s -> checks := (Regex s) :: !checks), "Check the value against a regex"); ("--exec", Arg.String (fun s -> checks := (Exec s) :: !checks), "Check the value against an external command"); + ("--grp", Arg.Unit (fun () -> checks := (Grp) :: !checks), "Group following arguments, joining results with logical and"); ("--value", Arg.String (fun s -> value := s), "Value to check"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) let () = Arg.parse args (fun _ -> ()) usage -let _ = - let buf = Buffer.create 4096 in +let buf = Buffer.create 4096 + +let mask l = + let (k, j) = get_next_range () in + let imask i e = + match e with + | Grp -> Grp + | _ -> if i >= k && i <= j then e else Faux in + List.mapi (fun i e -> imask i e) l + +let validate = + checks := List.rev(!checks); let value = !value in - let checks = !checks in - match checks with - | [] -> exit 0 + let disj_checks = mask !checks in + match disj_checks with + | [] -> false | _ -> - List.iter (fun c -> if (validate_value buf c value) then exit 0 else ()) checks; + List.fold_left (||) false (List.map (fun c -> validate_value buf c value) disj_checks) + +let _ = + if validate then exit 0 else (* If we got this far, value validation failed. Show the user output from the validators. *) -- cgit v1.2.3 From 9b593c24700a3e0e0b37fa3cf4161f305a9c7ccb Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 13 Jun 2021 11:52:35 -0500 Subject: T3574: refactor for clarity --- src/validate_value.ml | 99 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/validate_value.ml b/src/validate_value.ml index 6d5261862..32d34ded2 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -1,30 +1,16 @@ -type check = Regex of string | Exec of string | Grp | Faux +type argopt = RegexOpt of string | ExecOpt of string | GroupSeparator +type check = Regex of string | Exec of string | Group of check list +let options = ref [] let checks = ref [] +let value = ref "" -let find_next_grp n l = - let rec aux i = function - | [] -> List.length l - | h::t -> - if i > n && h = Grp then i - else aux (i+1) t - in aux 0 l - -let get_next_range = - let n = ref (-1) in - let f () = - let i = !n and j = (n := find_next_grp !n !checks; !n) in - (i, j) in - f +let buf = Buffer.create 4096 let rec validate_value buf value_constraint value = match value_constraint with - | Faux -> false - | Grp -> - let (k, j) = get_next_range () in - let in_grp i = if i > k && i < j then true else false in - let conj_checks = List.filteri (fun i _ -> in_grp i) !checks in - List.fold_left (&&) true (List.map (fun e -> validate_value buf e value) conj_checks) + | Group l -> + List.for_all (fun c -> validate_value buf c value) l | Regex s -> (try let _ = Pcre.exec ~pat:s value in true with Not_found -> false) @@ -45,36 +31,73 @@ let rec validate_value buf value_constraint value = let () = Buffer.add_string buf out; Buffer.add_string buf "\n" in false -let value = ref "" - let args = [ - ("--regex", Arg.String (fun s -> checks := (Regex s) :: !checks), "Check the value against a regex"); - ("--exec", Arg.String (fun s -> checks := (Exec s) :: !checks), "Check the value against an external command"); - ("--grp", Arg.Unit (fun () -> checks := (Grp) :: !checks), "Group following arguments, joining results with logical and"); + ("--regex", Arg.String (fun s -> options := (RegexOpt s) :: !options), "Check the value against a regex"); + ("--exec", Arg.String (fun s -> options := (ExecOpt s) :: !options), "Check the value against an external command"); + ("--grp", Arg.Unit (fun () -> options := (GroupSeparator) :: !options), "Group following arguments, combining results with logical and"); ("--value", Arg.String (fun s -> value := s), "Value to check"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) let () = Arg.parse args (fun _ -> ()) usage -let buf = Buffer.create 4096 +let find_next_group n l = + let rec aux i = function + | [] -> List.length l + | h::t -> + if i > n && h = GroupSeparator then i + else aux (i+1) t + in aux 0 l + +let get_next_range = + let n = ref (-1) in + let f () = + let i = !n and j = (n := find_next_group !n !options; !n) in + (i, j) in + f + +let option_to_check opt = + match opt with + | RegexOpt s -> Regex s + | ExecOpt s -> Exec s + | GroupSeparator -> raise (Invalid_argument "GroupSeparator in isolation has no corresponding check") + +let read_initial_options j = + if j > 0 then + let initial_options = List.filteri (fun i _ -> i < j) !options in + ignore (List.map (fun c -> checks := (option_to_check c) :: !checks) initial_options); () + else () + +let read_group_options i j = + if i < j then + let group_options = List.filteri (fun k _ -> i < k && k < j) !options in + let l = List.map (fun c -> option_to_check c) group_options in + checks := (Group l) :: !checks; () + else () + +let read_options () = + options := List.rev(!options); + + let (_, j) = get_next_range () in + read_initial_options j; -let mask l = - let (k, j) = get_next_range () in - let imask i e = - match e with - | Grp -> Grp - | _ -> if i >= k && i <= j then e else Faux in - List.mapi (fun i e -> imask i e) l + let quit_loop = ref false in + while not !quit_loop do + let i, j = get_next_range () in + if i < j then + read_group_options i j + else + quit_loop := true + done let validate = - checks := List.rev(!checks); + read_options (); let value = !value in - let disj_checks = mask !checks in - match disj_checks with + let checks = !checks in + match checks with | [] -> false | _ -> - List.fold_left (||) false (List.map (fun c -> validate_value buf c value) disj_checks) + List.exists (fun c -> validate_value buf c value) checks let _ = if validate then exit 0 else -- cgit v1.2.3 From 964ced5a0ffe146115194dc50bf755a241fa40a1 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sat, 4 Dec 2021 21:27:49 +0700 Subject: T4047: use full string match in the regex validator --- src/validate_value.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/validate_value.ml b/src/validate_value.ml index 32d34ded2..05d62b189 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -12,7 +12,7 @@ let rec validate_value buf value_constraint value = | Group l -> List.for_all (fun c -> validate_value buf c value) l | Regex s -> - (try let _ = Pcre.exec ~pat:s value in true + (try let _ = Pcre.exec ~pat:(Printf.sprintf "^%s$" s) value in true with Not_found -> false) | Exec c -> (* XXX: Unix.open_process_in is "shelling out", which is a bad idea on multiple levels, -- cgit v1.2.3 From 5bbda46493d0c11c8a90e50a68c9788a7488345e Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 10 May 2022 14:21:48 +0300 Subject: T4421: add support for floating point number in the numeric validator --- debian/rules | 2 +- src/numeric.ml | 49 +++++++++++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/debian/rules b/debian/rules index e3462afba..4d3db357c 100755 --- a/debian/rules +++ b/debian/rules @@ -8,7 +8,7 @@ DIR := debian/tmp override_dh_auto_build: eval `opam env` mkdir -p _build - ocamlfind ocamlopt -o _build/numeric -package num -linkpkg src/numeric.ml + ocamlfind ocamlopt -o _build/numeric src/numeric.ml ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml override_dh_auto_install: diff --git a/src/numeric.ml b/src/numeric.ml index 17fcac63f..ab59d6c11 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -1,23 +1,17 @@ type options = { positive: bool; nonnegative: bool; + allow_float: bool; ranges: string list; } let default_opts = { positive = false; nonnegative = false; + allow_float = false; ranges = [] } -let int_of_string = Big_int.big_int_of_string -let int_of_string_opt = Big_int.big_int_of_string_opt -let big = Big_int.big_int_of_int -let (>=) = Big_int.ge_big_int -let (<=) = Big_int.le_big_int -let (<) = Big_int.lt_big_int -let (>) = Big_int.gt_big_int - let opts = ref default_opts let number_arg = ref "" @@ -26,6 +20,7 @@ let args = [ ("--non-negative", Arg.Unit (fun () -> opts := {!opts with nonnegative=true}), "Check if the number is non-negative (>= 0)"); ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number is within a range (inclusive)"); + ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) @@ -33,25 +28,39 @@ let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) let () = Arg.parse args (fun s -> number_arg := s) usage let check_nonnegative opts n = - if opts.nonnegative && (n < (big 0)) then + if opts.nonnegative && (n < 0.0) then failwith "Number should be non-negative." let check_positive opts n = - if opts.positive && (n <= (big 0)) then + if opts.positive && (n <= 0.0) then failwith "Number should be positive" -let number_of_string s = - let n = int_of_string_opt s in +let number_of_string opts s = + let n = float_of_string_opt s in match n with - | Some n -> n + | Some n -> + (* If floats are explicitly allowed, just return the number. *) + if opts.allow_float then n + (* If floats are not explicitly allowed, check if the argument has a decimal separator in it. + If the argument string contains a dot but float_of_string didn't dislike it, + it's a valid number but not an integer. + *) + else if not (String.contains s '.') then n + (* If float_of_string returned None, the argument string is just garbage rather than a number. *) + else Printf.ksprintf failwith "'%s' is not a valid integer number" s | None -> - Printf.ksprintf failwith "'%s' is not a valid integer number" s + Printf.ksprintf failwith "'%s' is not a valid number" s -let range_of_string s = - let rs = String.split_on_char '-' s |> List.map String.trim |> List.map int_of_string_opt in +let range_of_string opts s = + let rs = String.split_on_char '-' s |> List.map String.trim |> List.map (number_of_string opts) in match rs with - | [Some l; Some r] -> (l, r) - | _ -> Printf.ksprintf failwith "'%s' is not a valid number range" s + | [l; r] -> (l, r) + | exception (Failure msg) -> + (* Some of the numbers in the range are bad. *) + Printf.ksprintf failwith "'%s' is not a valid number range: %s" s msg + | _ -> + (* The range itself if malformed, like 1-10-20. *) + Printf.ksprintf failwith "'%s' is not a valid number range" s let check_ranges ranges n = let in_range (l, r) n = (n >= l) && (n <= r) in @@ -61,11 +70,11 @@ let check_ranges ranges n = let () = try let opts = !opts in - let n = number_of_string !number_arg in + let n = number_of_string opts !number_arg in check_nonnegative opts n; check_positive opts n; if opts.ranges <> [] then - let ranges = List.map range_of_string opts.ranges in + let ranges = List.map (range_of_string opts) opts.ranges in check_ranges ranges n with (Failure err) -> print_endline err; -- cgit v1.2.3 From 4aa302a05fad95f3d13eb2ff20bbfee88c32e7ff Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 10 May 2022 15:04:28 +0300 Subject: T4421: only allow numbers that look like an integer part with an optional fractional part --- debian/rules | 2 +- src/numeric.ml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/debian/rules b/debian/rules index 4d3db357c..c6e892053 100755 --- a/debian/rules +++ b/debian/rules @@ -8,7 +8,7 @@ DIR := debian/tmp override_dh_auto_build: eval `opam env` mkdir -p _build - ocamlfind ocamlopt -o _build/numeric src/numeric.ml + ocamlfind ocamlopt -o _build/numeric -package pcre -linkpkg src/numeric.ml ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml override_dh_auto_install: diff --git a/src/numeric.ml b/src/numeric.ml index ab59d6c11..b296cec69 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -35,7 +35,12 @@ let check_positive opts n = if opts.positive && (n <= 0.0) then failwith "Number should be positive" +let looks_like_number value = + try let _ = Pcre.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true + with Not_found -> false + let number_of_string opts s = + if not (looks_like_number s) then Printf.ksprintf failwith "'%s' is not a valid number" s else let n = float_of_string_opt s in match n with | Some n -> -- cgit v1.2.3 From dbf73551a7f770f763d6286f5080c37cdb15f20c Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 17 Jun 2022 20:30:52 -0500 Subject: numeric: T4467: add support for relative values: +/-N Add option '--relative' to check that value is of the form '+/-N' for some numeric N (e.g. +10' or '-30'), interpreted as a relative increment or decrement, rather than a signed integer/float. This may be combined with any other option, which will be applied to the value stripped of the leading modifier. Testing this extension revealed that negative values are being interpreted as options, whether quoted or not; to address that, an Arg.Rest keyword '--' is introduced, which will need to be passed along with the option in interface definitions using the above. For example: --- src/numeric.ml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index b296cec69..ad3a06651 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -3,13 +3,15 @@ type options = { nonnegative: bool; allow_float: bool; ranges: string list; + relative: bool; } let default_opts = { positive = false; nonnegative = false; allow_float = false; - ranges = [] + ranges = []; + relative = false } let opts = ref default_opts @@ -21,6 +23,8 @@ let args = [ ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number is within a range (inclusive)"); ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); + ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); + ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) @@ -39,6 +43,20 @@ let looks_like_number value = try let _ = Pcre.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true with Not_found -> false +let is_relative value = + try let _ = Pcre.exec ~pat:"^[+-][0-9]+$" value in true + with Not_found -> false + +let number_string_from_relative value = + String.sub value 1 (String.length value - 1) + +let get_relative opts s = + if opts.relative then + if not (is_relative s) then + failwith "Value is not a relative increment/decrement" + else number_string_from_relative s + else s + let number_of_string opts s = if not (looks_like_number s) then Printf.ksprintf failwith "'%s' is not a valid number" s else let n = float_of_string_opt s in @@ -75,7 +93,8 @@ let check_ranges ranges n = let () = try let opts = !opts in - let n = number_of_string opts !number_arg in + let s = get_relative opts !number_arg in + let n = number_of_string opts s in check_nonnegative opts n; check_positive opts n; if opts.ranges <> [] then -- cgit v1.2.3 From 4fac67b10c0cc53aebf7acd1aaa788796239659f Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 6 Sep 2022 08:54:27 -0500 Subject: numeric: T4669: extend numeric.ml for inverted and range values --- src/numeric.ml | 111 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index ad3a06651..0ac59f5db 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -1,8 +1,12 @@ +type numeric_str = Number_string of string | Range_string of string +type numeric_val = Number_float of float | Range_float of float * float + type options = { positive: bool; nonnegative: bool; allow_float: bool; ranges: string list; + not_ranges: string list; relative: bool; } @@ -11,7 +15,8 @@ let default_opts = { nonnegative = false; allow_float = false; ranges = []; - relative = false + not_ranges = []; + relative = false; } let opts = ref default_opts @@ -21,23 +26,34 @@ let number_arg = ref "" let args = [ ("--non-negative", Arg.Unit (fun () -> opts := {!opts with nonnegative=true}), "Check if the number is non-negative (>= 0)"); ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); - ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number is within a range (inclusive)"); + ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number or range is within a range (inclusive)"); + ("--not-range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_ranges=(s :: optsv.not_ranges)}), "Check if the number or range is not within a range (inclusive)"); ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); ] -let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) +let usage = Printf.sprintf "Usage: %s [OPTIONS] |" Sys.argv.(0) let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) let () = Arg.parse args (fun s -> number_arg := s) usage -let check_nonnegative opts n = - if opts.nonnegative && (n < 0.0) then - failwith "Number should be non-negative." - -let check_positive opts n = - if opts.positive && (n <= 0.0) then - failwith "Number should be positive" +let check_nonnegative opts m = + if opts.nonnegative then + match m with + | Number_float n -> + if (n < 0.0) then + failwith "Number should be non-negative." + | Range_float _ -> + failwith "option '--non-negative' does not apply to a range value" + +let check_positive opts m = + if opts.positive then + match m with + | Number_float n -> + if (n <= 0.0) then + failwith "Number should be positive" + | Range_float _ -> + failwith "option '--positive does' not apply to a range value" let looks_like_number value = try let _ = Pcre.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true @@ -47,15 +63,19 @@ let is_relative value = try let _ = Pcre.exec ~pat:"^[+-][0-9]+$" value in true with Not_found -> false -let number_string_from_relative value = +let number_string_drop_modifier value = String.sub value 1 (String.length value - 1) -let get_relative opts s = +let get_relative opts t = if opts.relative then - if not (is_relative s) then - failwith "Value is not a relative increment/decrement" - else number_string_from_relative s - else s + match t with + | Number_string s -> + if not (is_relative s) then + failwith "Value is not a relative increment/decrement" + else Number_string (number_string_drop_modifier s) + | Range_string _ -> + failwith "increment/decrement does not apply to a range value" + else t let number_of_string opts s = if not (looks_like_number s) then Printf.ksprintf failwith "'%s' is not a valid number" s else @@ -85,21 +105,62 @@ let range_of_string opts s = (* The range itself if malformed, like 1-10-20. *) Printf.ksprintf failwith "'%s' is not a valid number range" s -let check_ranges ranges n = +let value_in_ranges ranges n = + let in_range (l, r) n = (n >= l) && (n <= r) in + List.fold_left (fun acc r -> acc || (in_range r n)) false ranges + +let value_not_in_ranges ranges n = let in_range (l, r) n = (n >= l) && (n <= r) in - let res = List.fold_left (fun acc r -> acc || (in_range r n)) false ranges in - if not res then - Printf.ksprintf failwith "Number is not in any of allowed ranges" + List.fold_left (fun acc r -> acc && (not (in_range r n))) true ranges + +let check_ranges opts m = + if opts.ranges <> [] then + let ranges = List.map (range_of_string opts) opts.ranges in + match m with + | Number_float n -> + if not (value_in_ranges ranges n) then + Printf.ksprintf failwith "Number is not in any of allowed ranges" + | Range_float (i, j) -> + if (not (value_in_ranges ranges i) || + not (value_in_ranges ranges j)) then + Printf.ksprintf failwith "Range is not in any of allowed ranges" + + +let check_not_ranges opts m = + if opts.not_ranges <> [] then + let ranges = List.map (range_of_string opts) opts.not_ranges in + match m with + | Number_float n -> + if not (value_not_in_ranges ranges n) then + Printf.ksprintf failwith "Number is in one of excluded ranges" + | Range_float (i, j) -> + if (not (value_not_in_ranges ranges i) || + not (value_not_in_ranges ranges j)) then + Printf.ksprintf failwith "Range is in one of excluded ranges" + +let is_range_val s = + try let _ = Pcre.exec ~pat:"^[0-9]+-[0-9]+$" s in true + with Not_found -> false + +let var_numeric_str s = + match is_range_val s with + | true -> Range_string s + | false -> Number_string s let () = try + let s = var_numeric_str !number_arg in let opts = !opts in - let s = get_relative opts !number_arg in - let n = number_of_string opts s in + let s = get_relative opts s in + let n = + match s with + | Number_string r -> Number_float (number_of_string opts r) + | Range_string r -> let i, j = range_of_string opts r in + Range_float (i, j) + in check_nonnegative opts n; check_positive opts n; - if opts.ranges <> [] then - let ranges = List.map (range_of_string opts) opts.ranges in - check_ranges ranges n + check_ranges opts n; + check_not_ranges opts n with (Failure err) -> print_endline err; exit 1 -- cgit v1.2.3 From bb970f95f67ec649d864858293990fc09079bda6 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 22 Sep 2022 16:55:07 +0100 Subject: T4669: disallow range arguments to numeric unless explicitly allowed with --allow-range option --- src/numeric.ml | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index 0ac59f5db..eb4ac6d4b 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -8,6 +8,7 @@ type options = { ranges: string list; not_ranges: string list; relative: bool; + allow_range: bool; } let default_opts = { @@ -17,6 +18,7 @@ let default_opts = { ranges = []; not_ranges = []; relative = false; + allow_range = false; } let opts = ref default_opts @@ -30,6 +32,7 @@ let args = [ ("--not-range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_ranges=(s :: optsv.not_ranges)}), "Check if the number or range is not within a range (inclusive)"); ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); + ("--allow-range", Arg.Unit (fun () -> opts := {!opts with allow_range=true}), "Allow the argument to be a range rather than a single number"); ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] |" Sys.argv.(0) @@ -138,6 +141,13 @@ let check_not_ranges opts m = not (value_not_in_ranges ranges j)) then Printf.ksprintf failwith "Range is in one of excluded ranges" +let check_argument_type opts m = + match m with + | Number_float _ -> () + | Range_float _ -> + if opts.allow_range then () + else Printf.ksprintf failwith "Value must be a number, not a range" + let is_range_val s = try let _ = Pcre.exec ~pat:"^[0-9]+-[0-9]+$" s in true with Not_found -> false @@ -157,6 +167,7 @@ let () = try | Range_string r -> let i, j = range_of_string opts r in Range_float (i, j) in + check_argument_type opts n; check_nonnegative opts n; check_positive opts n; check_ranges opts n; -- cgit v1.2.3 From 986c556c28976bcd7add93f3b08908a79dd21a5c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 19 Oct 2022 17:48:09 +0100 Subject: T4761: add a URL value validator --- debian/rules | 1 + src/url.ml | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/url.ml (limited to 'src') diff --git a/debian/rules b/debian/rules index c6e892053..22202e90b 100755 --- a/debian/rules +++ b/debian/rules @@ -9,6 +9,7 @@ override_dh_auto_build: eval `opam env` mkdir -p _build ocamlfind ocamlopt -o _build/numeric -package pcre -linkpkg src/numeric.ml + ocamlfind ocamlopt -o _build/url -package pcre -linkpkg src/url.ml ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml override_dh_auto_install: diff --git a/src/url.ml b/src/url.ml new file mode 100644 index 000000000..1985ff070 --- /dev/null +++ b/src/url.ml @@ -0,0 +1,73 @@ +(* Extract and validate the scheme part. + As per the RFC: + + Scheme names consist of a sequence of characters. The lower case + letters "a"--"z", digits, and the characters plus ("+"), period + ("."), and hyphen ("-") are allowed. For resiliency, programs + interpreting URLs should treat upper case letters as equivalent to + lower case in scheme names (e.g., allow "HTTP" as well as "http"). + *) +let split_scheme url = + let aux url = + let res = Pcre.exec ~pat:{|^([a-zA-Z0-9\.\-]+):(.*)$|} url in + let scheme = Pcre.get_substring res 1 in + let uri = Pcre.get_substring res 2 in + (String.lowercase_ascii scheme, uri) + in + try Ok (aux url) + with Not_found -> Error (Printf.sprintf {|"%s" is not a valid URL|} url) + +let is_scheme_allowed allowed_schemes scheme = + match List.find_opt ((=) scheme) allowed_schemes with + | Some _ -> Ok () + | None -> Error (Printf.sprintf {|URL scheme "%s:" is not allowed|} scheme) + +let regex_matches regex s = + try + let _ = Pcre.exec ~pat:regex s in + true + with Not_found -> false + +let validate_uri scheme uri = + match scheme with + | "http" | "https" -> + if regex_matches {|//[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(:[0-9]+)*(/.* )?|} uri then Ok () + else Error (Printf.sprintf {|"%s" is not a valid URI for the %s URL scheme|} uri scheme) + | _ -> Ok () + +let validate_url allowed_schemes url = + let (let*) = Result.bind in + let* scheme, uri = split_scheme url in + let* () = is_scheme_allowed allowed_schemes scheme in + let* () = validate_uri scheme uri in + Ok () + +let file_transport_schemes = ["http"; "https"; "ftp"; "sftp"; "scp"; "tftp"] + +let message_schemes = ["mailto"; "tel"; "sms"] + +let allowed_schemes = ref [] +let url = ref "" + +let args = [ + ("--scheme", + Arg.String (fun s -> allowed_schemes := s :: !allowed_schemes), + "Allow only specified schemes"); + ("--file-transport", + Arg.Unit (fun () -> allowed_schemes := (List.append !allowed_schemes file_transport_schemes)), + "Allow only file transport protocols (HTTP/S, FTP, SCP/SFTP, TFTP)"); + ("--", Arg.Rest (fun s -> url := s), "Interpret next item as an argument"); +] + +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = + let () = Arg.parse args (fun s -> url := s) usage in + (* Force all allowed scheme named to lowercase for ease of comparison. *) + let allowed_schemes = List.map String.lowercase_ascii !allowed_schemes in + let res = validate_url allowed_schemes !url in + match res with + | Ok () -> () + | Error msg -> + let () = Printf.fprintf stdout "%s" msg in + exit 1 -- cgit v1.2.3 From 96bb119393d8606d9408d72aa5cb468702bef625 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 4 Nov 2022 15:29:03 +0000 Subject: T4798: add a file path validator --- debian/rules | 2 ++ debian/vyos-utils.install | 3 ++- src/file_path.ml | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/file_path.ml (limited to 'src') diff --git a/debian/rules b/debian/rules index c6e892053..09cdd65fb 100755 --- a/debian/rules +++ b/debian/rules @@ -9,12 +9,14 @@ override_dh_auto_build: eval `opam env` mkdir -p _build ocamlfind ocamlopt -o _build/numeric -package pcre -linkpkg src/numeric.ml + ocamlfind ocamlopt -o _build/file-path -package fileutils -linkpkg src/file_path.ml ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml override_dh_auto_install: mkdir -p $(DIR)/usr/libexec/vyos/validators cp _build/numeric $(DIR)/usr/libexec/vyos/validators cp _build/validate-value $(DIR)/usr/libexec/vyos/ + cp _build/file-path $(DIR)/usr/libexec/vyos/validators override_dh_auto_test: echo "No tests yet" diff --git a/debian/vyos-utils.install b/debian/vyos-utils.install index 77ea559be..a67b3f86b 100644 --- a/debian/vyos-utils.install +++ b/debian/vyos-utils.install @@ -1,2 +1,3 @@ -usr/libexec/vyos/validators/numeric usr/libexec/vyos/validate-value +usr/libexec/vyos/validators/numeric +usr/libexec/vyos/validators/file-path diff --git a/src/file_path.ml b/src/file_path.ml new file mode 100644 index 000000000..8c05ffdc0 --- /dev/null +++ b/src/file_path.ml @@ -0,0 +1,48 @@ +type opts = { + must_be_file : bool; + parent : string option; + strict : bool; +} + +let default_opts = { + must_be_file = true; + parent = None; + strict = false +} + +let opts = ref default_opts + +let path_arg = ref "" + +let args = [ + ("--file", Arg.Unit (fun () -> opts := {!opts with must_be_file=true}), "Path must point to a file and not a directory (default)"); + ("--directory", Arg.Unit (fun () -> opts := {!opts with must_be_file=false}), "Path must point to a directory"); + ("--parent-dir", Arg.String (fun s -> opts := {!opts with parent=(Some s)}), "Path must be inside specific parent directory"); + ("--strict", Arg.Unit (fun () -> opts := {!opts with strict=true}), "Treat warnings as errors"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) +let () = Arg.parse args (fun s -> path_arg := s) usage + +let fail msg = + let () = print_endline msg in + exit 1 + +let () = + let path = !path_arg in + let opts = !opts in + (* First, check if the file/dir path exists at all. *) + let exists = FileUtil.test FileUtil.Exists path in + if not exists then Printf.ksprintf fail {|Incorrect path %s: no such file or directory|} path else + (* If yes, check if it's of the correct type: file or directory. *) + let is_file = FileUtil.test FileUtil.Is_file path in + if ((not is_file) && opts.must_be_file) then Printf.ksprintf fail {|%s is a directory, not a file|} path else + if (is_file && (not opts.must_be_file)) then Printf.ksprintf fail {|%s is a file, not a directory|} path else + match opts.parent with + | None -> + exit 0 + | Some parent -> + if not (FilePath.is_subdir (FilePath.reduce path) (FilePath.reduce parent)) then + let msg = Printf.sprintf {|Path %s is not under %s directory|} path parent in + if opts.strict then fail msg else Printf.printf "Warning: %s\n" msg -- cgit v1.2.3 From 3883bb64a45794904f042e2f1e4458eb4700de8b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 12 Dec 2022 10:58:55 -0600 Subject: validators: T4875: add option lookup-path to cat with file/dir arg --- src/file_path.ml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/file_path.ml b/src/file_path.ml index 8c05ffdc0..ea3068c1f 100644 --- a/src/file_path.ml +++ b/src/file_path.ml @@ -1,12 +1,14 @@ type opts = { must_be_file : bool; parent : string option; + lookup_path : string option; strict : bool; } let default_opts = { must_be_file = true; parent = None; + lookup_path = None; strict = false } @@ -18,6 +20,7 @@ let args = [ ("--file", Arg.Unit (fun () -> opts := {!opts with must_be_file=true}), "Path must point to a file and not a directory (default)"); ("--directory", Arg.Unit (fun () -> opts := {!opts with must_be_file=false}), "Path must point to a directory"); ("--parent-dir", Arg.String (fun s -> opts := {!opts with parent=(Some s)}), "Path must be inside specific parent directory"); + ("--lookup-path", Arg.String (fun s -> opts := {!opts with lookup_path=(Some s)}), "Prefix path argument with lookup path"); ("--strict", Arg.Unit (fun () -> opts := {!opts with strict=true}), "Treat warnings as errors"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) @@ -30,8 +33,12 @@ let fail msg = exit 1 let () = - let path = !path_arg in let opts = !opts in + let path = + match opts.lookup_path with + | None -> !path_arg + | Some lookup_path -> FilePath.concat lookup_path !path_arg + in (* First, check if the file/dir path exists at all. *) let exists = FileUtil.test FileUtil.Exists path in if not exists then Printf.ksprintf fail {|Incorrect path %s: no such file or directory|} path else -- cgit v1.2.3 From 541df959cfc92206e15f29d2d1745e627a22a707 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 23 Dec 2022 11:23:50 -0800 Subject: T4761: add support for literal IP addresses in URL's Signed-off-by: Daniil Baturin --- debian/rules | 1 + src/url.ml | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/debian/rules b/debian/rules index 22202e90b..7f137b3fb 100755 --- a/debian/rules +++ b/debian/rules @@ -9,6 +9,7 @@ override_dh_auto_build: eval `opam env` mkdir -p _build ocamlfind ocamlopt -o _build/numeric -package pcre -linkpkg src/numeric.ml + ocamlfind ocamlopt -o _build/file-path -package fileutils -linkpkg src/file_path.ml ocamlfind ocamlopt -o _build/url -package pcre -linkpkg src/url.ml ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml diff --git a/src/url.ml b/src/url.ml index 1985ff070..5d36daeb2 100644 --- a/src/url.ml +++ b/src/url.ml @@ -24,16 +24,92 @@ let is_scheme_allowed allowed_schemes scheme = let regex_matches regex s = try - let _ = Pcre.exec ~pat:regex s in + let _ = Pcre.exec ~rex:regex s in true with Not_found -> false +let host_path_format = + Pcre.regexp + {|^//(?:[a-zA-Z0-9\-\._~]+(?::[a-zA-Z0-9\-\._~]*)?@)?([a-zA-Z0-9\-\._~]+|\[[a-zA-Z0-9:\.]+\])(?::([0-9]+))?(/.*)?$|} + +let host_name_format = Pcre.regexp {|^[a-zA-Z0-9]+([\-\._~]{1}[a-zA-Z0-9]+)*$|} +let ipv4_addr_format = Pcre.regexp {|^(([1-9]\d{0,2}|0)\.){3}([1-9]\d{0,2}|0)$|} +let ipv6_addr_format = Pcre.regexp {|^\[([a-z0-9:\.]+|[A-Z0-9:\.]+)\]$|} + +let is_port s = + try + let n = int_of_string s in + if n > 0 && n < 65536 then true + else false + with Failure _ -> false + +let is_ipv4_octet s = + try + let n = int_of_string s in + if n >= 0 && n < 256 then true + else false + with Failure _ -> false + +let is_ipv6_segment s = + try + let n = int_of_string ("0x" ^ s) in + if n >= 0 && n < 65536 then true + else false + with Failure _ -> false + +let is_ipv4_addr s = + let res = Pcre.exec ~rex:ipv4_addr_format s in + let ipv4_addr_str = Pcre.get_substring res 0 in + let ipv4_addr_l = String.split_on_char '.' ipv4_addr_str in + List.for_all is_ipv4_octet ipv4_addr_l + +let is_ipv6_pure_addr s = + let ipv6_addr_l = String.split_on_char ':' s in + if List.length ipv6_addr_l > 8 || List.length ipv6_addr_l < 3 then false + else + let seg_str_l = List.filter (fun s -> String.length s > 0) ipv6_addr_l in + List.for_all is_ipv6_segment seg_str_l + +let is_ipv6_dual_addr s = + let ipv6_addr_l = List.rev (String.split_on_char ':' s) in + match ipv6_addr_l with + | [] -> false + | h::t -> + if not (is_ipv4_addr h) then false + else + if List.length t > 6 || List.length t < 2 then false + else + let seg_str_l = List.filter (fun s -> String.length s > 0) t in + List.for_all is_ipv6_segment seg_str_l + +let is_ipv6_addr s = + let res = Pcre.exec ~rex:ipv6_addr_format s in + let ipv6_addr_str = Pcre.get_substring res 1 in + try + let typo = Pcre.exec ~pat:{|:::|} ipv6_addr_str in + match typo with + | _ -> false + with Not_found -> + is_ipv6_pure_addr ipv6_addr_str || is_ipv6_dual_addr ipv6_addr_str + +let host_path_matches s = + try + let res = Pcre.exec ~rex:host_path_format s in + let substr = Pcre.get_substrings ~full_match:false res in + let port_str = Array.get substr 1 in + if String.length port_str > 0 && not (is_port port_str) then false + else + let host = Array.get substr 0 in + match host with + | host when regex_matches ipv6_addr_format host -> is_ipv6_addr host + | host when regex_matches ipv4_addr_format host -> is_ipv4_addr host + | host when regex_matches host_name_format host -> true + | _ -> false + with Not_found -> false; + let validate_uri scheme uri = - match scheme with - | "http" | "https" -> - if regex_matches {|//[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(:[0-9]+)*(/.* )?|} uri then Ok () - else Error (Printf.sprintf {|"%s" is not a valid URI for the %s URL scheme|} uri scheme) - | _ -> Ok () + if host_path_matches uri then Ok () + else Error (Printf.sprintf {|"%s" is not a valid URI for the %s URL scheme|} uri scheme) let validate_url allowed_schemes url = let (let*) = Result.bind in -- cgit v1.2.3 From 5a4c8468835b75bc7d8e6c9f9c5a6c2a6a624def Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 29 Dec 2022 08:46:48 -0600 Subject: T4761: fix typo --- src/url.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/url.ml b/src/url.ml index 5d36daeb2..2f3367b73 100644 --- a/src/url.ml +++ b/src/url.ml @@ -105,7 +105,7 @@ let host_path_matches s = | host when regex_matches ipv4_addr_format host -> is_ipv4_addr host | host when regex_matches host_name_format host -> true | _ -> false - with Not_found -> false; + with Not_found -> false let validate_uri scheme uri = if host_path_matches uri then Ok () -- cgit v1.2.3 From 04d43b4e9b4b469c281bc3638d6979dd1b43dd18 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sat, 4 Feb 2023 08:16:22 -0600 Subject: T4952: add wrapper to getifaddrs for completion helper list_interfaces --- debian/rules | 7 +++ src/iface/func.ml | 1 + src/iface/func.mli | 1 + src/iface/iface.c | 38 ++++++++++++++ src/iface/list_interfaces.ml | 121 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+) mode change 100644 => 100755 debian/rules create mode 100644 src/iface/func.ml create mode 100644 src/iface/func.mli create mode 100644 src/iface/iface.c create mode 100644 src/iface/list_interfaces.ml (limited to 'src') diff --git a/debian/rules b/debian/rules old mode 100644 new mode 100755 index d222fe89f..6640e33b6 --- a/debian/rules +++ b/debian/rules @@ -12,6 +12,12 @@ override_dh_auto_build: ocamlfind ocamlopt -o _build/file-path -package fileutils -linkpkg src/file_path.ml ocamlfind ocamlopt -o _build/url -package pcre -linkpkg src/url.ml ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml +# list_interfaces + ocamlfind ocamlopt -c src/iface/func.mli + ocamlfind ocamlopt -I src/iface -c src/iface/func.ml + ocamlfind ocamlopt -I src/iface -c src/iface/iface.c + ocamlfind ocamlopt -I src/iface -package pcre -c src/iface/list_interfaces.ml + ocamlfind ocamlopt -I src/iface func.cmx iface.o list_interfaces.cmx -o _build/list_interfaces -package pcre -linkpkg override_dh_auto_install: mkdir -p $(DIR)/usr/libexec/vyos/validators @@ -19,6 +25,7 @@ override_dh_auto_install: cp _build/validate-value $(DIR)/usr/libexec/vyos/ cp _build/file-path $(DIR)/usr/libexec/vyos/validators cp _build/url $(DIR)/usr/libexec/vyos/validators + cp _build/list_interfaces $(DIR)/usr/libexec/vyos/completion override_dh_auto_test: echo "No tests yet" diff --git a/src/iface/func.ml b/src/iface/func.ml new file mode 100644 index 000000000..13e1860d9 --- /dev/null +++ b/src/iface/func.ml @@ -0,0 +1 @@ +external list_interfaces: unit -> string list = "interface_list" diff --git a/src/iface/func.mli b/src/iface/func.mli new file mode 100644 index 000000000..b16d37354 --- /dev/null +++ b/src/iface/func.mli @@ -0,0 +1 @@ +external list_interfaces : unit -> string list = "interface_list" diff --git a/src/iface/iface.c b/src/iface/iface.c new file mode 100644 index 000000000..bf2f0250e --- /dev/null +++ b/src/iface/iface.c @@ -0,0 +1,38 @@ +/* + * Simple wrapper of getifaddrs for OCaml list of interfaces + */ +#include +#include +#include +#include + +CAMLprim value interface_list(value unit) { + struct ifaddrs *ifaddr; + struct ifaddrs *ifa; + + CAMLparam1( unit ); + CAMLlocal2( cli, cons ); + + cli = Val_emptylist; + + if (getifaddrs(&ifaddr) == -1) { + CAMLreturn(cli); + } + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_name == NULL) + continue; + + CAMLlocal1( ml_s ); + cons = caml_alloc(2, 0); + + ml_s = caml_copy_string(ifa->ifa_name); + Store_field( cons, 0, ml_s ); + Store_field( cons, 1, cli ); + + cli = cons; + } + + freeifaddrs(ifaddr); + + CAMLreturn(cli); +} diff --git a/src/iface/list_interfaces.ml b/src/iface/list_interfaces.ml new file mode 100644 index 000000000..2daa5099f --- /dev/null +++ b/src/iface/list_interfaces.ml @@ -0,0 +1,121 @@ +(* + *) +let intf_type = ref "" +let broadcast = ref false +let bridgeable = ref false +let bondable = ref false + +let args = [ + ("--type", Arg.String (fun s -> intf_type := s), "List interfaces of specified type"); + ("--broadcast", Arg.Unit (fun () -> broadcast := true), "List broadcast interfaces"); + ("--bridgeable", Arg.Unit (fun () -> bridgeable := true), "List bridgeable interfaces"); + ("--bondable", Arg.Unit (fun () -> bondable := true), "List bondable interfaces"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = Arg.parse args (fun _ -> ()) usage + +let type_to_prefix it = + match it with + | "" -> "" + | "bonding" -> "bond" + | "bridge" -> "br" + | "dummy" -> "dum" + | "ethernet" -> "eth" + | "geneve" -> "gnv" + | "input" -> "ifb" + | "l2tpeth" -> "l2tpeth" + | "loopback" -> "lo" + | "macsec" -> "macsec" + | "openvpn" -> "vtun" + | "pppoe" -> "pppoe" + | "pseudo-ethernet" -> "peth" + | "sstpc" -> "sstpc" + | "tunnel" -> "tun" + | "virtual-ethernet" -> "veth" + | "vti" -> "vti" + | "vxlan" -> "vxlan" + | "wireguard" -> "wg" + | "wireless" -> "wlan" + | "wwan" -> "wwan" + | _ -> "" + +(* filter_section to match the constraint of python.vyos.ifconfig.section + *) +let rx = Pcre.regexp {|\d(\d|v|\.)*$|} + +let filter_section s = + let r = Pcre.qreplace_first ~rex:rx ~templ:"" s in + match r with + |"bond"|"br"|"dum"|"eth"|"gnv"|"ifb"|"l2tpeth"|"lo"|"macsec" -> true + |"peth"|"pppoe"|"sstpc"|"tun"|"veth"|"vti"|"vtun"|"vxlan"|"wg"|"wlan"|"wwan" -> true + | _ -> false + +let filter_from_prefix p s = + let pattern = Printf.sprintf "^%s(.*)$" p + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_from_type it = + let pre = type_to_prefix it in + match pre with + | "" -> None + | _ -> Some (filter_from_prefix pre) + +let filter_broadcast s = + let pattern = {|^(bond|br|eth)(.*)$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_bridgeable s = + let pattern = {|^(bond|eth|gnv|l2tpeth|lo|tun|veth|vtun|wlan)(.*)$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_bondable s = + let pattern = {|^(eth)(.*)$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let get_interfaces = + let intf_type = !intf_type in + let fltr = + if String.length(intf_type) > 0 then + filter_from_type intf_type + else None + in + let l = Func.list_interfaces () in + let res = List.sort_uniq compare l in + let res = + if !broadcast then List.filter filter_broadcast res + else res + in + let res = + if !bridgeable then List.filter filter_bridgeable res + else res + in + let res = + if !bondable then List.filter filter_bondable res + else res + in + let res = List.filter filter_section res in + match fltr with + | Some f -> List.filter f res + | None -> res + +let () = + let res = get_interfaces in + List.iter (Printf.printf "%s ") res; + Printf.printf "\n" -- cgit v1.2.3 From 675ea7481aeef903a8583458f43c12def75da222 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 31 Jul 2023 15:31:23 -0500 Subject: T5421: add arg to filter out subinterfaces --- src/iface/list_interfaces.ml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src') diff --git a/src/iface/list_interfaces.ml b/src/iface/list_interfaces.ml index 2daa5099f..063e34444 100644 --- a/src/iface/list_interfaces.ml +++ b/src/iface/list_interfaces.ml @@ -4,12 +4,14 @@ let intf_type = ref "" let broadcast = ref false let bridgeable = ref false let bondable = ref false +let no_vlan = ref false let args = [ ("--type", Arg.String (fun s -> intf_type := s), "List interfaces of specified type"); ("--broadcast", Arg.Unit (fun () -> broadcast := true), "List broadcast interfaces"); ("--bridgeable", Arg.Unit (fun () -> bridgeable := true), "List bridgeable interfaces"); ("--bondable", Arg.Unit (fun () -> bondable := true), "List bondable interfaces"); + ("--no-vlan-subinterfaces", Arg.Unit (fun () -> no_vlan := true), "List only parent interfaces"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) @@ -89,6 +91,14 @@ let filter_bondable s = true with Not_found -> false +let filter_no_vlan s = + let pattern = {|^([^.]+)(\.\d+)+$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + false + with Not_found -> true + let get_interfaces = let intf_type = !intf_type in let fltr = @@ -110,6 +120,10 @@ let get_interfaces = if !bondable then List.filter filter_bondable res else res in + let res = + if !no_vlan then List.filter filter_no_vlan res + else res + in let res = List.filter filter_section res in match fltr with | Some f -> List.filter f res -- cgit v1.2.3 From 198ed142b65818a2d18e5961dac380f3edc25660 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Wed, 2 Aug 2023 22:52:55 +0200 Subject: T5430: vxlan mus tbe listed as bridgable in completion helper --- src/iface/list_interfaces.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/iface/list_interfaces.ml b/src/iface/list_interfaces.ml index 063e34444..fec5c9ccc 100644 --- a/src/iface/list_interfaces.ml +++ b/src/iface/list_interfaces.ml @@ -76,7 +76,7 @@ let filter_broadcast s = with Not_found -> false let filter_bridgeable s = - let pattern = {|^(bond|eth|gnv|l2tpeth|lo|tun|veth|vtun|wlan)(.*)$|} + let pattern = {|^(bond|eth|gnv|l2tpeth|lo|tun|veth|vtun|vxlan|wlan)(.*)$|} in try let _ = Pcre.exec ~pat:pattern s in -- cgit v1.2.3 From dea4f2b277d882362c2265bedae05bb3746a4136 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 26 Sep 2023 19:04:50 +0100 Subject: numeric: T5617: add --not-value to the numeric validator --- src/numeric.ml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index eb4ac6d4b..a3c5d11a8 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -7,6 +7,7 @@ type options = { allow_float: bool; ranges: string list; not_ranges: string list; + not_values: string list; relative: bool; allow_range: bool; } @@ -17,6 +18,7 @@ let default_opts = { allow_float = false; ranges = []; not_ranges = []; + not_values = []; relative = false; allow_range = false; } @@ -30,6 +32,7 @@ let args = [ ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number or range is within a range (inclusive)"); ("--not-range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_ranges=(s :: optsv.not_ranges)}), "Check if the number or range is not within a range (inclusive)"); + ("--not-value", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_values=(s :: optsv.not_values)}), "Check if the number does not equal a specific value"); ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); ("--allow-range", Arg.Unit (fun () -> opts := {!opts with allow_range=true}), "Allow the argument to be a range rather than a single number"); @@ -141,6 +144,18 @@ let check_not_ranges opts m = not (value_not_in_ranges ranges j)) then Printf.ksprintf failwith "Range is in one of excluded ranges" +let check_not_values opts m = + let excluded_values = List.map (number_of_string opts) opts.not_values in + match m with + | Range_float _ -> Printf.ksprintf failwith "--not-value cannot be used with ranges" + | Number_float num -> + begin + let res = List.find_opt ((=) num) excluded_values in + match res with + | None -> () + | Some _ -> Printf.ksprintf failwith "Value is excluded by --not-value" + end + let check_argument_type opts m = match m with | Number_float _ -> () @@ -170,6 +185,7 @@ let () = try check_argument_type opts n; check_nonnegative opts n; check_positive opts n; + check_not_values opts n; check_ranges opts n; check_not_ranges opts n with (Failure err) -> -- cgit v1.2.3 From ba5e5638c15914c0b1bb3d61942f64046b38efd6 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 6 Oct 2023 16:59:16 +0100 Subject: numeric: T5638: Do not complain about range arguments in check_not_value when it's not necessary (i.e., when no actual --not-value options are given) --- src/numeric.ml | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index a3c5d11a8..f27c1ee33 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -146,6 +146,7 @@ let check_not_ranges opts m = let check_not_values opts m = let excluded_values = List.map (number_of_string opts) opts.not_values in + if excluded_values = [] then () else match m with | Range_float _ -> Printf.ksprintf failwith "--not-value cannot be used with ranges" | Number_float num -> -- cgit v1.2.3 From 4a18740024573d6c6f459053a8902ad00fcfaa60 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 6 Oct 2023 17:01:28 +0100 Subject: numeric: T5638: add --require-range --- src/numeric.ml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/numeric.ml b/src/numeric.ml index f27c1ee33..0c75f83f5 100644 --- a/src/numeric.ml +++ b/src/numeric.ml @@ -10,6 +10,7 @@ type options = { not_values: string list; relative: bool; allow_range: bool; + require_range: bool; } let default_opts = { @@ -21,6 +22,7 @@ let default_opts = { not_values = []; relative = false; allow_range = false; + require_range = false; } let opts = ref default_opts @@ -36,6 +38,7 @@ let args = [ ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); ("--allow-range", Arg.Unit (fun () -> opts := {!opts with allow_range=true}), "Allow the argument to be a range rather than a single number"); + ("--require-range", Arg.Unit (fun () -> opts := {!opts with require_range=true; allow_range=true}), "Require the argument to be a range rather than a single number"); ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] |" Sys.argv.(0) @@ -159,7 +162,9 @@ let check_not_values opts m = let check_argument_type opts m = match m with - | Number_float _ -> () + | Number_float _ -> + if opts.require_range then Printf.ksprintf failwith "Value must be a range, not a number" + else () | Range_float _ -> if opts.allow_range then () else Printf.ksprintf failwith "Value must be a number, not a range" -- cgit v1.2.3 From acec28c250895c849228e3a138706c15ef7394a9 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 24 Dec 2023 16:08:22 -0800 Subject: T5882: use Dune as build system --- debian/rules | 23 +++++++---------------- dune-project | 2 ++ src/dune | 34 ++++++++++++++++++++++++++++++++++ vyos-utils.opam | 21 +++++++++++++++++++++ 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 dune-project create mode 100644 src/dune create mode 100644 vyos-utils.opam (limited to 'src') diff --git a/debian/rules b/debian/rules index 700081401..451eef1ba 100755 --- a/debian/rules +++ b/debian/rules @@ -7,26 +7,17 @@ DIR := debian/tmp override_dh_auto_build: eval `opam env` - mkdir -p _build - ocamlfind ocamlopt -o _build/numeric -package pcre -linkpkg src/numeric.ml - ocamlfind ocamlopt -o _build/file-path -package fileutils -linkpkg src/file_path.ml - ocamlfind ocamlopt -o _build/url -package pcre -linkpkg src/url.ml - ocamlfind ocamlopt -o _build/validate-value -package pcre,unix,containers -linkpkg src/validate_value.ml -# list_interfaces - ocamlfind ocamlopt -c src/iface/func.mli - ocamlfind ocamlopt -I src/iface -c src/iface/func.ml - ocamlfind ocamlopt -I src/iface -c src/iface/iface.c - ocamlfind ocamlopt -I src/iface -package pcre -c src/iface/list_interfaces.ml - ocamlfind ocamlopt -I src/iface func.cmx iface.o list_interfaces.cmx -o _build/list_interfaces -package pcre -linkpkg + dune clean + dune build override_dh_auto_install: mkdir -p $(DIR)/usr/libexec/vyos/validators mkdir -p $(DIR)/usr/libexec/vyos/completion - cp _build/numeric $(DIR)/usr/libexec/vyos/validators - cp _build/validate-value $(DIR)/usr/libexec/vyos/ - cp _build/file-path $(DIR)/usr/libexec/vyos/validators - cp _build/url $(DIR)/usr/libexec/vyos/validators - cp _build/list_interfaces $(DIR)/usr/libexec/vyos/completion + cp _build/install/default/bin/numeric $(DIR)/usr/libexec/vyos/validators + cp _build/install/default/bin/validate-value $(DIR)/usr/libexec/vyos/ + cp _build/install/default/bin/file-path $(DIR)/usr/libexec/vyos/validators + cp _build/install/default/bin/url $(DIR)/usr/libexec/vyos/validators + cp _build/install/default/bin/list_interfaces $(DIR)/usr/libexec/vyos/completion override_dh_auto_test: echo "No tests yet" diff --git a/dune-project b/dune-project new file mode 100644 index 000000000..8a076e452 --- /dev/null +++ b/dune-project @@ -0,0 +1,2 @@ +(lang dune 2.0) +(name vyos-utils) diff --git a/src/dune b/src/dune new file mode 100644 index 000000000..8da009459 --- /dev/null +++ b/src/dune @@ -0,0 +1,34 @@ +(include_subdirs unqualified) + +(executable + (name numeric) + (public_name numeric) + (modules numeric) + (libraries pcre)) + +(executable + (name url) + (public_name url) + (modules url) + (libraries pcre)) + +(executable + (name file_path) + (public_name file-path) + (modules file_path) + (libraries fileutils)) + +(executable + (name validate_value) + (public_name validate-value) + (modules validate_value) + (libraries pcre unix containers)) + +(executable + (name list_interfaces) + (public_name list_interfaces) + (modules func list_interfaces) + (libraries pcre) + (foreign_stubs + (language c) + (names iface))) diff --git a/vyos-utils.opam b/vyos-utils.opam new file mode 100644 index 000000000..fda994424 --- /dev/null +++ b/vyos-utils.opam @@ -0,0 +1,21 @@ +opam-version: "2.0" +name: "vyos-utils" +version: "0.0.3" +synopsis: "VyOS utils" +description: """ +A collection of validators and completion helpers. +""" +maintainer: "Daniil Baturin " +authors: "VyOS maintainers and contributors " +license: "MIT" +homepage: "https://github.com/vyos/vyos-utils" +bug-reports: "https://phabricator.vyos.net" +dev-repo: "git+https://github.com/vyos/vyos-utils/" +build: [ + ["dune" "subst"] {pinned} + ["dune" "build" "-p" name] +] +depends: [ + "ocamlfind" {build} + "dune" {build & >= "2.0"} +] -- cgit v1.2.3 From 3d22aa091bb962c68810f10a6027ef452da84a90 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 21 May 2024 11:47:35 -0500 Subject: T5940: relax regex of uri user information for 1.3.x compatibility --- src/url.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/url.ml b/src/url.ml index 2f3367b73..3d7754437 100644 --- a/src/url.ml +++ b/src/url.ml @@ -30,7 +30,7 @@ let regex_matches regex s = let host_path_format = Pcre.regexp - {|^//(?:[a-zA-Z0-9\-\._~]+(?::[a-zA-Z0-9\-\._~]*)?@)?([a-zA-Z0-9\-\._~]+|\[[a-zA-Z0-9:\.]+\])(?::([0-9]+))?(/.*)?$|} + {|^//(?:[^/?#]+(?::[^/?#]*)?@)?([a-zA-Z0-9\-\._~]+|\[[a-zA-Z0-9:\.]+\])(?::([0-9]+))?(/.*)?$|} let host_name_format = Pcre.regexp {|^[a-zA-Z0-9]+([\-\._~]{1}[a-zA-Z0-9]+)*$|} let ipv4_addr_format = Pcre.regexp {|^(([1-9]\d{0,2}|0)\.){3}([1-9]\d{0,2}|0)$|} -- cgit v1.2.3 From f22292be24334ac8767aa288221091955f93ebd6 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 22 May 2024 13:34:05 +0100 Subject: T6380: Move validators to a dedicated subdirectory --- src/file_path.ml | 55 ------------ src/numeric.ml | 200 -------------------------------------------- src/url.ml | 149 --------------------------------- src/validators/file_path.ml | 55 ++++++++++++ src/validators/numeric.ml | 200 ++++++++++++++++++++++++++++++++++++++++++++ src/validators/url.ml | 149 +++++++++++++++++++++++++++++++++ 6 files changed, 404 insertions(+), 404 deletions(-) delete mode 100644 src/file_path.ml delete mode 100644 src/numeric.ml delete mode 100644 src/url.ml create mode 100644 src/validators/file_path.ml create mode 100644 src/validators/numeric.ml create mode 100644 src/validators/url.ml (limited to 'src') diff --git a/src/file_path.ml b/src/file_path.ml deleted file mode 100644 index ea3068c1f..000000000 --- a/src/file_path.ml +++ /dev/null @@ -1,55 +0,0 @@ -type opts = { - must_be_file : bool; - parent : string option; - lookup_path : string option; - strict : bool; -} - -let default_opts = { - must_be_file = true; - parent = None; - lookup_path = None; - strict = false -} - -let opts = ref default_opts - -let path_arg = ref "" - -let args = [ - ("--file", Arg.Unit (fun () -> opts := {!opts with must_be_file=true}), "Path must point to a file and not a directory (default)"); - ("--directory", Arg.Unit (fun () -> opts := {!opts with must_be_file=false}), "Path must point to a directory"); - ("--parent-dir", Arg.String (fun s -> opts := {!opts with parent=(Some s)}), "Path must be inside specific parent directory"); - ("--lookup-path", Arg.String (fun s -> opts := {!opts with lookup_path=(Some s)}), "Prefix path argument with lookup path"); - ("--strict", Arg.Unit (fun () -> opts := {!opts with strict=true}), "Treat warnings as errors"); -] -let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) - -let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) -let () = Arg.parse args (fun s -> path_arg := s) usage - -let fail msg = - let () = print_endline msg in - exit 1 - -let () = - let opts = !opts in - let path = - match opts.lookup_path with - | None -> !path_arg - | Some lookup_path -> FilePath.concat lookup_path !path_arg - in - (* First, check if the file/dir path exists at all. *) - let exists = FileUtil.test FileUtil.Exists path in - if not exists then Printf.ksprintf fail {|Incorrect path %s: no such file or directory|} path else - (* If yes, check if it's of the correct type: file or directory. *) - let is_file = FileUtil.test FileUtil.Is_file path in - if ((not is_file) && opts.must_be_file) then Printf.ksprintf fail {|%s is a directory, not a file|} path else - if (is_file && (not opts.must_be_file)) then Printf.ksprintf fail {|%s is a file, not a directory|} path else - match opts.parent with - | None -> - exit 0 - | Some parent -> - if not (FilePath.is_subdir (FilePath.reduce path) (FilePath.reduce parent)) then - let msg = Printf.sprintf {|Path %s is not under %s directory|} path parent in - if opts.strict then fail msg else Printf.printf "Warning: %s\n" msg diff --git a/src/numeric.ml b/src/numeric.ml deleted file mode 100644 index 0c75f83f5..000000000 --- a/src/numeric.ml +++ /dev/null @@ -1,200 +0,0 @@ -type numeric_str = Number_string of string | Range_string of string -type numeric_val = Number_float of float | Range_float of float * float - -type options = { - positive: bool; - nonnegative: bool; - allow_float: bool; - ranges: string list; - not_ranges: string list; - not_values: string list; - relative: bool; - allow_range: bool; - require_range: bool; -} - -let default_opts = { - positive = false; - nonnegative = false; - allow_float = false; - ranges = []; - not_ranges = []; - not_values = []; - relative = false; - allow_range = false; - require_range = false; -} - -let opts = ref default_opts - -let number_arg = ref "" - -let args = [ - ("--non-negative", Arg.Unit (fun () -> opts := {!opts with nonnegative=true}), "Check if the number is non-negative (>= 0)"); - ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); - ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number or range is within a range (inclusive)"); - ("--not-range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_ranges=(s :: optsv.not_ranges)}), "Check if the number or range is not within a range (inclusive)"); - ("--not-value", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_values=(s :: optsv.not_values)}), "Check if the number does not equal a specific value"); - ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); - ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); - ("--allow-range", Arg.Unit (fun () -> opts := {!opts with allow_range=true}), "Allow the argument to be a range rather than a single number"); - ("--require-range", Arg.Unit (fun () -> opts := {!opts with require_range=true; allow_range=true}), "Require the argument to be a range rather than a single number"); - ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); -] -let usage = Printf.sprintf "Usage: %s [OPTIONS] |" Sys.argv.(0) - -let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) -let () = Arg.parse args (fun s -> number_arg := s) usage - -let check_nonnegative opts m = - if opts.nonnegative then - match m with - | Number_float n -> - if (n < 0.0) then - failwith "Number should be non-negative." - | Range_float _ -> - failwith "option '--non-negative' does not apply to a range value" - -let check_positive opts m = - if opts.positive then - match m with - | Number_float n -> - if (n <= 0.0) then - failwith "Number should be positive" - | Range_float _ -> - failwith "option '--positive does' not apply to a range value" - -let looks_like_number value = - try let _ = Pcre.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true - with Not_found -> false - -let is_relative value = - try let _ = Pcre.exec ~pat:"^[+-][0-9]+$" value in true - with Not_found -> false - -let number_string_drop_modifier value = - String.sub value 1 (String.length value - 1) - -let get_relative opts t = - if opts.relative then - match t with - | Number_string s -> - if not (is_relative s) then - failwith "Value is not a relative increment/decrement" - else Number_string (number_string_drop_modifier s) - | Range_string _ -> - failwith "increment/decrement does not apply to a range value" - else t - -let number_of_string opts s = - if not (looks_like_number s) then Printf.ksprintf failwith "'%s' is not a valid number" s else - let n = float_of_string_opt s in - match n with - | Some n -> - (* If floats are explicitly allowed, just return the number. *) - if opts.allow_float then n - (* If floats are not explicitly allowed, check if the argument has a decimal separator in it. - If the argument string contains a dot but float_of_string didn't dislike it, - it's a valid number but not an integer. - *) - else if not (String.contains s '.') then n - (* If float_of_string returned None, the argument string is just garbage rather than a number. *) - else Printf.ksprintf failwith "'%s' is not a valid integer number" s - | None -> - Printf.ksprintf failwith "'%s' is not a valid number" s - -let range_of_string opts s = - let rs = String.split_on_char '-' s |> List.map String.trim |> List.map (number_of_string opts) in - match rs with - | [l; r] -> (l, r) - | exception (Failure msg) -> - (* Some of the numbers in the range are bad. *) - Printf.ksprintf failwith "'%s' is not a valid number range: %s" s msg - | _ -> - (* The range itself if malformed, like 1-10-20. *) - Printf.ksprintf failwith "'%s' is not a valid number range" s - -let value_in_ranges ranges n = - let in_range (l, r) n = (n >= l) && (n <= r) in - List.fold_left (fun acc r -> acc || (in_range r n)) false ranges - -let value_not_in_ranges ranges n = - let in_range (l, r) n = (n >= l) && (n <= r) in - List.fold_left (fun acc r -> acc && (not (in_range r n))) true ranges - -let check_ranges opts m = - if opts.ranges <> [] then - let ranges = List.map (range_of_string opts) opts.ranges in - match m with - | Number_float n -> - if not (value_in_ranges ranges n) then - Printf.ksprintf failwith "Number is not in any of allowed ranges" - | Range_float (i, j) -> - if (not (value_in_ranges ranges i) || - not (value_in_ranges ranges j)) then - Printf.ksprintf failwith "Range is not in any of allowed ranges" - - -let check_not_ranges opts m = - if opts.not_ranges <> [] then - let ranges = List.map (range_of_string opts) opts.not_ranges in - match m with - | Number_float n -> - if not (value_not_in_ranges ranges n) then - Printf.ksprintf failwith "Number is in one of excluded ranges" - | Range_float (i, j) -> - if (not (value_not_in_ranges ranges i) || - not (value_not_in_ranges ranges j)) then - Printf.ksprintf failwith "Range is in one of excluded ranges" - -let check_not_values opts m = - let excluded_values = List.map (number_of_string opts) opts.not_values in - if excluded_values = [] then () else - match m with - | Range_float _ -> Printf.ksprintf failwith "--not-value cannot be used with ranges" - | Number_float num -> - begin - let res = List.find_opt ((=) num) excluded_values in - match res with - | None -> () - | Some _ -> Printf.ksprintf failwith "Value is excluded by --not-value" - end - -let check_argument_type opts m = - match m with - | Number_float _ -> - if opts.require_range then Printf.ksprintf failwith "Value must be a range, not a number" - else () - | Range_float _ -> - if opts.allow_range then () - else Printf.ksprintf failwith "Value must be a number, not a range" - -let is_range_val s = - try let _ = Pcre.exec ~pat:"^[0-9]+-[0-9]+$" s in true - with Not_found -> false - -let var_numeric_str s = - match is_range_val s with - | true -> Range_string s - | false -> Number_string s - -let () = try - let s = var_numeric_str !number_arg in - let opts = !opts in - let s = get_relative opts s in - let n = - match s with - | Number_string r -> Number_float (number_of_string opts r) - | Range_string r -> let i, j = range_of_string opts r in - Range_float (i, j) - in - check_argument_type opts n; - check_nonnegative opts n; - check_positive opts n; - check_not_values opts n; - check_ranges opts n; - check_not_ranges opts n -with (Failure err) -> - print_endline err; - exit 1 - diff --git a/src/url.ml b/src/url.ml deleted file mode 100644 index 3d7754437..000000000 --- a/src/url.ml +++ /dev/null @@ -1,149 +0,0 @@ -(* Extract and validate the scheme part. - As per the RFC: - - Scheme names consist of a sequence of characters. The lower case - letters "a"--"z", digits, and the characters plus ("+"), period - ("."), and hyphen ("-") are allowed. For resiliency, programs - interpreting URLs should treat upper case letters as equivalent to - lower case in scheme names (e.g., allow "HTTP" as well as "http"). - *) -let split_scheme url = - let aux url = - let res = Pcre.exec ~pat:{|^([a-zA-Z0-9\.\-]+):(.*)$|} url in - let scheme = Pcre.get_substring res 1 in - let uri = Pcre.get_substring res 2 in - (String.lowercase_ascii scheme, uri) - in - try Ok (aux url) - with Not_found -> Error (Printf.sprintf {|"%s" is not a valid URL|} url) - -let is_scheme_allowed allowed_schemes scheme = - match List.find_opt ((=) scheme) allowed_schemes with - | Some _ -> Ok () - | None -> Error (Printf.sprintf {|URL scheme "%s:" is not allowed|} scheme) - -let regex_matches regex s = - try - let _ = Pcre.exec ~rex:regex s in - true - with Not_found -> false - -let host_path_format = - Pcre.regexp - {|^//(?:[^/?#]+(?::[^/?#]*)?@)?([a-zA-Z0-9\-\._~]+|\[[a-zA-Z0-9:\.]+\])(?::([0-9]+))?(/.*)?$|} - -let host_name_format = Pcre.regexp {|^[a-zA-Z0-9]+([\-\._~]{1}[a-zA-Z0-9]+)*$|} -let ipv4_addr_format = Pcre.regexp {|^(([1-9]\d{0,2}|0)\.){3}([1-9]\d{0,2}|0)$|} -let ipv6_addr_format = Pcre.regexp {|^\[([a-z0-9:\.]+|[A-Z0-9:\.]+)\]$|} - -let is_port s = - try - let n = int_of_string s in - if n > 0 && n < 65536 then true - else false - with Failure _ -> false - -let is_ipv4_octet s = - try - let n = int_of_string s in - if n >= 0 && n < 256 then true - else false - with Failure _ -> false - -let is_ipv6_segment s = - try - let n = int_of_string ("0x" ^ s) in - if n >= 0 && n < 65536 then true - else false - with Failure _ -> false - -let is_ipv4_addr s = - let res = Pcre.exec ~rex:ipv4_addr_format s in - let ipv4_addr_str = Pcre.get_substring res 0 in - let ipv4_addr_l = String.split_on_char '.' ipv4_addr_str in - List.for_all is_ipv4_octet ipv4_addr_l - -let is_ipv6_pure_addr s = - let ipv6_addr_l = String.split_on_char ':' s in - if List.length ipv6_addr_l > 8 || List.length ipv6_addr_l < 3 then false - else - let seg_str_l = List.filter (fun s -> String.length s > 0) ipv6_addr_l in - List.for_all is_ipv6_segment seg_str_l - -let is_ipv6_dual_addr s = - let ipv6_addr_l = List.rev (String.split_on_char ':' s) in - match ipv6_addr_l with - | [] -> false - | h::t -> - if not (is_ipv4_addr h) then false - else - if List.length t > 6 || List.length t < 2 then false - else - let seg_str_l = List.filter (fun s -> String.length s > 0) t in - List.for_all is_ipv6_segment seg_str_l - -let is_ipv6_addr s = - let res = Pcre.exec ~rex:ipv6_addr_format s in - let ipv6_addr_str = Pcre.get_substring res 1 in - try - let typo = Pcre.exec ~pat:{|:::|} ipv6_addr_str in - match typo with - | _ -> false - with Not_found -> - is_ipv6_pure_addr ipv6_addr_str || is_ipv6_dual_addr ipv6_addr_str - -let host_path_matches s = - try - let res = Pcre.exec ~rex:host_path_format s in - let substr = Pcre.get_substrings ~full_match:false res in - let port_str = Array.get substr 1 in - if String.length port_str > 0 && not (is_port port_str) then false - else - let host = Array.get substr 0 in - match host with - | host when regex_matches ipv6_addr_format host -> is_ipv6_addr host - | host when regex_matches ipv4_addr_format host -> is_ipv4_addr host - | host when regex_matches host_name_format host -> true - | _ -> false - with Not_found -> false - -let validate_uri scheme uri = - if host_path_matches uri then Ok () - else Error (Printf.sprintf {|"%s" is not a valid URI for the %s URL scheme|} uri scheme) - -let validate_url allowed_schemes url = - let (let*) = Result.bind in - let* scheme, uri = split_scheme url in - let* () = is_scheme_allowed allowed_schemes scheme in - let* () = validate_uri scheme uri in - Ok () - -let file_transport_schemes = ["http"; "https"; "ftp"; "sftp"; "scp"; "tftp"] - -let message_schemes = ["mailto"; "tel"; "sms"] - -let allowed_schemes = ref [] -let url = ref "" - -let args = [ - ("--scheme", - Arg.String (fun s -> allowed_schemes := s :: !allowed_schemes), - "Allow only specified schemes"); - ("--file-transport", - Arg.Unit (fun () -> allowed_schemes := (List.append !allowed_schemes file_transport_schemes)), - "Allow only file transport protocols (HTTP/S, FTP, SCP/SFTP, TFTP)"); - ("--", Arg.Rest (fun s -> url := s), "Interpret next item as an argument"); -] - -let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) - -let () = - let () = Arg.parse args (fun s -> url := s) usage in - (* Force all allowed scheme named to lowercase for ease of comparison. *) - let allowed_schemes = List.map String.lowercase_ascii !allowed_schemes in - let res = validate_url allowed_schemes !url in - match res with - | Ok () -> () - | Error msg -> - let () = Printf.fprintf stdout "%s" msg in - exit 1 diff --git a/src/validators/file_path.ml b/src/validators/file_path.ml new file mode 100644 index 000000000..ea3068c1f --- /dev/null +++ b/src/validators/file_path.ml @@ -0,0 +1,55 @@ +type opts = { + must_be_file : bool; + parent : string option; + lookup_path : string option; + strict : bool; +} + +let default_opts = { + must_be_file = true; + parent = None; + lookup_path = None; + strict = false +} + +let opts = ref default_opts + +let path_arg = ref "" + +let args = [ + ("--file", Arg.Unit (fun () -> opts := {!opts with must_be_file=true}), "Path must point to a file and not a directory (default)"); + ("--directory", Arg.Unit (fun () -> opts := {!opts with must_be_file=false}), "Path must point to a directory"); + ("--parent-dir", Arg.String (fun s -> opts := {!opts with parent=(Some s)}), "Path must be inside specific parent directory"); + ("--lookup-path", Arg.String (fun s -> opts := {!opts with lookup_path=(Some s)}), "Prefix path argument with lookup path"); + ("--strict", Arg.Unit (fun () -> opts := {!opts with strict=true}), "Treat warnings as errors"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) +let () = Arg.parse args (fun s -> path_arg := s) usage + +let fail msg = + let () = print_endline msg in + exit 1 + +let () = + let opts = !opts in + let path = + match opts.lookup_path with + | None -> !path_arg + | Some lookup_path -> FilePath.concat lookup_path !path_arg + in + (* First, check if the file/dir path exists at all. *) + let exists = FileUtil.test FileUtil.Exists path in + if not exists then Printf.ksprintf fail {|Incorrect path %s: no such file or directory|} path else + (* If yes, check if it's of the correct type: file or directory. *) + let is_file = FileUtil.test FileUtil.Is_file path in + if ((not is_file) && opts.must_be_file) then Printf.ksprintf fail {|%s is a directory, not a file|} path else + if (is_file && (not opts.must_be_file)) then Printf.ksprintf fail {|%s is a file, not a directory|} path else + match opts.parent with + | None -> + exit 0 + | Some parent -> + if not (FilePath.is_subdir (FilePath.reduce path) (FilePath.reduce parent)) then + let msg = Printf.sprintf {|Path %s is not under %s directory|} path parent in + if opts.strict then fail msg else Printf.printf "Warning: %s\n" msg diff --git a/src/validators/numeric.ml b/src/validators/numeric.ml new file mode 100644 index 000000000..0c75f83f5 --- /dev/null +++ b/src/validators/numeric.ml @@ -0,0 +1,200 @@ +type numeric_str = Number_string of string | Range_string of string +type numeric_val = Number_float of float | Range_float of float * float + +type options = { + positive: bool; + nonnegative: bool; + allow_float: bool; + ranges: string list; + not_ranges: string list; + not_values: string list; + relative: bool; + allow_range: bool; + require_range: bool; +} + +let default_opts = { + positive = false; + nonnegative = false; + allow_float = false; + ranges = []; + not_ranges = []; + not_values = []; + relative = false; + allow_range = false; + require_range = false; +} + +let opts = ref default_opts + +let number_arg = ref "" + +let args = [ + ("--non-negative", Arg.Unit (fun () -> opts := {!opts with nonnegative=true}), "Check if the number is non-negative (>= 0)"); + ("--positive", Arg.Unit (fun () -> opts := {!opts with positive=true}), "Check if the number is positive (> 0)"); + ("--range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with ranges=(s :: optsv.ranges)}), "Check if the number or range is within a range (inclusive)"); + ("--not-range", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_ranges=(s :: optsv.not_ranges)}), "Check if the number or range is not within a range (inclusive)"); + ("--not-value", Arg.String (fun s -> let optsv = !opts in opts := {optsv with not_values=(s :: optsv.not_values)}), "Check if the number does not equal a specific value"); + ("--float", Arg.Unit (fun () -> opts := {!opts with allow_float=true}), "Allow floating-point numbers"); + ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); + ("--allow-range", Arg.Unit (fun () -> opts := {!opts with allow_range=true}), "Allow the argument to be a range rather than a single number"); + ("--require-range", Arg.Unit (fun () -> opts := {!opts with require_range=true; allow_range=true}), "Require the argument to be a range rather than a single number"); + ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] |" Sys.argv.(0) + +let () = if Array.length Sys.argv = 1 then (Arg.usage args usage; exit 1) +let () = Arg.parse args (fun s -> number_arg := s) usage + +let check_nonnegative opts m = + if opts.nonnegative then + match m with + | Number_float n -> + if (n < 0.0) then + failwith "Number should be non-negative." + | Range_float _ -> + failwith "option '--non-negative' does not apply to a range value" + +let check_positive opts m = + if opts.positive then + match m with + | Number_float n -> + if (n <= 0.0) then + failwith "Number should be positive" + | Range_float _ -> + failwith "option '--positive does' not apply to a range value" + +let looks_like_number value = + try let _ = Pcre.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true + with Not_found -> false + +let is_relative value = + try let _ = Pcre.exec ~pat:"^[+-][0-9]+$" value in true + with Not_found -> false + +let number_string_drop_modifier value = + String.sub value 1 (String.length value - 1) + +let get_relative opts t = + if opts.relative then + match t with + | Number_string s -> + if not (is_relative s) then + failwith "Value is not a relative increment/decrement" + else Number_string (number_string_drop_modifier s) + | Range_string _ -> + failwith "increment/decrement does not apply to a range value" + else t + +let number_of_string opts s = + if not (looks_like_number s) then Printf.ksprintf failwith "'%s' is not a valid number" s else + let n = float_of_string_opt s in + match n with + | Some n -> + (* If floats are explicitly allowed, just return the number. *) + if opts.allow_float then n + (* If floats are not explicitly allowed, check if the argument has a decimal separator in it. + If the argument string contains a dot but float_of_string didn't dislike it, + it's a valid number but not an integer. + *) + else if not (String.contains s '.') then n + (* If float_of_string returned None, the argument string is just garbage rather than a number. *) + else Printf.ksprintf failwith "'%s' is not a valid integer number" s + | None -> + Printf.ksprintf failwith "'%s' is not a valid number" s + +let range_of_string opts s = + let rs = String.split_on_char '-' s |> List.map String.trim |> List.map (number_of_string opts) in + match rs with + | [l; r] -> (l, r) + | exception (Failure msg) -> + (* Some of the numbers in the range are bad. *) + Printf.ksprintf failwith "'%s' is not a valid number range: %s" s msg + | _ -> + (* The range itself if malformed, like 1-10-20. *) + Printf.ksprintf failwith "'%s' is not a valid number range" s + +let value_in_ranges ranges n = + let in_range (l, r) n = (n >= l) && (n <= r) in + List.fold_left (fun acc r -> acc || (in_range r n)) false ranges + +let value_not_in_ranges ranges n = + let in_range (l, r) n = (n >= l) && (n <= r) in + List.fold_left (fun acc r -> acc && (not (in_range r n))) true ranges + +let check_ranges opts m = + if opts.ranges <> [] then + let ranges = List.map (range_of_string opts) opts.ranges in + match m with + | Number_float n -> + if not (value_in_ranges ranges n) then + Printf.ksprintf failwith "Number is not in any of allowed ranges" + | Range_float (i, j) -> + if (not (value_in_ranges ranges i) || + not (value_in_ranges ranges j)) then + Printf.ksprintf failwith "Range is not in any of allowed ranges" + + +let check_not_ranges opts m = + if opts.not_ranges <> [] then + let ranges = List.map (range_of_string opts) opts.not_ranges in + match m with + | Number_float n -> + if not (value_not_in_ranges ranges n) then + Printf.ksprintf failwith "Number is in one of excluded ranges" + | Range_float (i, j) -> + if (not (value_not_in_ranges ranges i) || + not (value_not_in_ranges ranges j)) then + Printf.ksprintf failwith "Range is in one of excluded ranges" + +let check_not_values opts m = + let excluded_values = List.map (number_of_string opts) opts.not_values in + if excluded_values = [] then () else + match m with + | Range_float _ -> Printf.ksprintf failwith "--not-value cannot be used with ranges" + | Number_float num -> + begin + let res = List.find_opt ((=) num) excluded_values in + match res with + | None -> () + | Some _ -> Printf.ksprintf failwith "Value is excluded by --not-value" + end + +let check_argument_type opts m = + match m with + | Number_float _ -> + if opts.require_range then Printf.ksprintf failwith "Value must be a range, not a number" + else () + | Range_float _ -> + if opts.allow_range then () + else Printf.ksprintf failwith "Value must be a number, not a range" + +let is_range_val s = + try let _ = Pcre.exec ~pat:"^[0-9]+-[0-9]+$" s in true + with Not_found -> false + +let var_numeric_str s = + match is_range_val s with + | true -> Range_string s + | false -> Number_string s + +let () = try + let s = var_numeric_str !number_arg in + let opts = !opts in + let s = get_relative opts s in + let n = + match s with + | Number_string r -> Number_float (number_of_string opts r) + | Range_string r -> let i, j = range_of_string opts r in + Range_float (i, j) + in + check_argument_type opts n; + check_nonnegative opts n; + check_positive opts n; + check_not_values opts n; + check_ranges opts n; + check_not_ranges opts n +with (Failure err) -> + print_endline err; + exit 1 + diff --git a/src/validators/url.ml b/src/validators/url.ml new file mode 100644 index 000000000..3d7754437 --- /dev/null +++ b/src/validators/url.ml @@ -0,0 +1,149 @@ +(* Extract and validate the scheme part. + As per the RFC: + + Scheme names consist of a sequence of characters. The lower case + letters "a"--"z", digits, and the characters plus ("+"), period + ("."), and hyphen ("-") are allowed. For resiliency, programs + interpreting URLs should treat upper case letters as equivalent to + lower case in scheme names (e.g., allow "HTTP" as well as "http"). + *) +let split_scheme url = + let aux url = + let res = Pcre.exec ~pat:{|^([a-zA-Z0-9\.\-]+):(.*)$|} url in + let scheme = Pcre.get_substring res 1 in + let uri = Pcre.get_substring res 2 in + (String.lowercase_ascii scheme, uri) + in + try Ok (aux url) + with Not_found -> Error (Printf.sprintf {|"%s" is not a valid URL|} url) + +let is_scheme_allowed allowed_schemes scheme = + match List.find_opt ((=) scheme) allowed_schemes with + | Some _ -> Ok () + | None -> Error (Printf.sprintf {|URL scheme "%s:" is not allowed|} scheme) + +let regex_matches regex s = + try + let _ = Pcre.exec ~rex:regex s in + true + with Not_found -> false + +let host_path_format = + Pcre.regexp + {|^//(?:[^/?#]+(?::[^/?#]*)?@)?([a-zA-Z0-9\-\._~]+|\[[a-zA-Z0-9:\.]+\])(?::([0-9]+))?(/.*)?$|} + +let host_name_format = Pcre.regexp {|^[a-zA-Z0-9]+([\-\._~]{1}[a-zA-Z0-9]+)*$|} +let ipv4_addr_format = Pcre.regexp {|^(([1-9]\d{0,2}|0)\.){3}([1-9]\d{0,2}|0)$|} +let ipv6_addr_format = Pcre.regexp {|^\[([a-z0-9:\.]+|[A-Z0-9:\.]+)\]$|} + +let is_port s = + try + let n = int_of_string s in + if n > 0 && n < 65536 then true + else false + with Failure _ -> false + +let is_ipv4_octet s = + try + let n = int_of_string s in + if n >= 0 && n < 256 then true + else false + with Failure _ -> false + +let is_ipv6_segment s = + try + let n = int_of_string ("0x" ^ s) in + if n >= 0 && n < 65536 then true + else false + with Failure _ -> false + +let is_ipv4_addr s = + let res = Pcre.exec ~rex:ipv4_addr_format s in + let ipv4_addr_str = Pcre.get_substring res 0 in + let ipv4_addr_l = String.split_on_char '.' ipv4_addr_str in + List.for_all is_ipv4_octet ipv4_addr_l + +let is_ipv6_pure_addr s = + let ipv6_addr_l = String.split_on_char ':' s in + if List.length ipv6_addr_l > 8 || List.length ipv6_addr_l < 3 then false + else + let seg_str_l = List.filter (fun s -> String.length s > 0) ipv6_addr_l in + List.for_all is_ipv6_segment seg_str_l + +let is_ipv6_dual_addr s = + let ipv6_addr_l = List.rev (String.split_on_char ':' s) in + match ipv6_addr_l with + | [] -> false + | h::t -> + if not (is_ipv4_addr h) then false + else + if List.length t > 6 || List.length t < 2 then false + else + let seg_str_l = List.filter (fun s -> String.length s > 0) t in + List.for_all is_ipv6_segment seg_str_l + +let is_ipv6_addr s = + let res = Pcre.exec ~rex:ipv6_addr_format s in + let ipv6_addr_str = Pcre.get_substring res 1 in + try + let typo = Pcre.exec ~pat:{|:::|} ipv6_addr_str in + match typo with + | _ -> false + with Not_found -> + is_ipv6_pure_addr ipv6_addr_str || is_ipv6_dual_addr ipv6_addr_str + +let host_path_matches s = + try + let res = Pcre.exec ~rex:host_path_format s in + let substr = Pcre.get_substrings ~full_match:false res in + let port_str = Array.get substr 1 in + if String.length port_str > 0 && not (is_port port_str) then false + else + let host = Array.get substr 0 in + match host with + | host when regex_matches ipv6_addr_format host -> is_ipv6_addr host + | host when regex_matches ipv4_addr_format host -> is_ipv4_addr host + | host when regex_matches host_name_format host -> true + | _ -> false + with Not_found -> false + +let validate_uri scheme uri = + if host_path_matches uri then Ok () + else Error (Printf.sprintf {|"%s" is not a valid URI for the %s URL scheme|} uri scheme) + +let validate_url allowed_schemes url = + let (let*) = Result.bind in + let* scheme, uri = split_scheme url in + let* () = is_scheme_allowed allowed_schemes scheme in + let* () = validate_uri scheme uri in + Ok () + +let file_transport_schemes = ["http"; "https"; "ftp"; "sftp"; "scp"; "tftp"] + +let message_schemes = ["mailto"; "tel"; "sms"] + +let allowed_schemes = ref [] +let url = ref "" + +let args = [ + ("--scheme", + Arg.String (fun s -> allowed_schemes := s :: !allowed_schemes), + "Allow only specified schemes"); + ("--file-transport", + Arg.Unit (fun () -> allowed_schemes := (List.append !allowed_schemes file_transport_schemes)), + "Allow only file transport protocols (HTTP/S, FTP, SCP/SFTP, TFTP)"); + ("--", Arg.Rest (fun s -> url := s), "Interpret next item as an argument"); +] + +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = + let () = Arg.parse args (fun s -> url := s) usage in + (* Force all allowed scheme named to lowercase for ease of comparison. *) + let allowed_schemes = List.map String.lowercase_ascii !allowed_schemes in + let res = validate_url allowed_schemes !url in + match res with + | Ok () -> () + | Error msg -> + let () = Printf.fprintf stdout "%s" msg in + exit 1 -- cgit v1.2.3 From a89b0e79b00aa656cede3c6527cfda23b5ea19b1 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 22 May 2024 13:37:52 +0100 Subject: T6380: Move list_interfaces code to a dedicated dir for completion helpers --- src/completion/list_interfaces/func.ml | 1 + src/completion/list_interfaces/func.mli | 1 + src/completion/list_interfaces/iface.c | 38 ++++++ src/completion/list_interfaces/list_interfaces.ml | 135 ++++++++++++++++++++++ src/iface/func.ml | 1 - src/iface/func.mli | 1 - src/iface/iface.c | 38 ------ src/iface/list_interfaces.ml | 135 ---------------------- 8 files changed, 175 insertions(+), 175 deletions(-) create mode 100644 src/completion/list_interfaces/func.ml create mode 100644 src/completion/list_interfaces/func.mli create mode 100644 src/completion/list_interfaces/iface.c create mode 100644 src/completion/list_interfaces/list_interfaces.ml delete mode 100644 src/iface/func.ml delete mode 100644 src/iface/func.mli delete mode 100644 src/iface/iface.c delete mode 100644 src/iface/list_interfaces.ml (limited to 'src') diff --git a/src/completion/list_interfaces/func.ml b/src/completion/list_interfaces/func.ml new file mode 100644 index 000000000..13e1860d9 --- /dev/null +++ b/src/completion/list_interfaces/func.ml @@ -0,0 +1 @@ +external list_interfaces: unit -> string list = "interface_list" diff --git a/src/completion/list_interfaces/func.mli b/src/completion/list_interfaces/func.mli new file mode 100644 index 000000000..b16d37354 --- /dev/null +++ b/src/completion/list_interfaces/func.mli @@ -0,0 +1 @@ +external list_interfaces : unit -> string list = "interface_list" diff --git a/src/completion/list_interfaces/iface.c b/src/completion/list_interfaces/iface.c new file mode 100644 index 000000000..bf2f0250e --- /dev/null +++ b/src/completion/list_interfaces/iface.c @@ -0,0 +1,38 @@ +/* + * Simple wrapper of getifaddrs for OCaml list of interfaces + */ +#include +#include +#include +#include + +CAMLprim value interface_list(value unit) { + struct ifaddrs *ifaddr; + struct ifaddrs *ifa; + + CAMLparam1( unit ); + CAMLlocal2( cli, cons ); + + cli = Val_emptylist; + + if (getifaddrs(&ifaddr) == -1) { + CAMLreturn(cli); + } + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_name == NULL) + continue; + + CAMLlocal1( ml_s ); + cons = caml_alloc(2, 0); + + ml_s = caml_copy_string(ifa->ifa_name); + Store_field( cons, 0, ml_s ); + Store_field( cons, 1, cli ); + + cli = cons; + } + + freeifaddrs(ifaddr); + + CAMLreturn(cli); +} diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml new file mode 100644 index 000000000..fec5c9ccc --- /dev/null +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -0,0 +1,135 @@ +(* + *) +let intf_type = ref "" +let broadcast = ref false +let bridgeable = ref false +let bondable = ref false +let no_vlan = ref false + +let args = [ + ("--type", Arg.String (fun s -> intf_type := s), "List interfaces of specified type"); + ("--broadcast", Arg.Unit (fun () -> broadcast := true), "List broadcast interfaces"); + ("--bridgeable", Arg.Unit (fun () -> bridgeable := true), "List bridgeable interfaces"); + ("--bondable", Arg.Unit (fun () -> bondable := true), "List bondable interfaces"); + ("--no-vlan-subinterfaces", Arg.Unit (fun () -> no_vlan := true), "List only parent interfaces"); +] +let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) + +let () = Arg.parse args (fun _ -> ()) usage + +let type_to_prefix it = + match it with + | "" -> "" + | "bonding" -> "bond" + | "bridge" -> "br" + | "dummy" -> "dum" + | "ethernet" -> "eth" + | "geneve" -> "gnv" + | "input" -> "ifb" + | "l2tpeth" -> "l2tpeth" + | "loopback" -> "lo" + | "macsec" -> "macsec" + | "openvpn" -> "vtun" + | "pppoe" -> "pppoe" + | "pseudo-ethernet" -> "peth" + | "sstpc" -> "sstpc" + | "tunnel" -> "tun" + | "virtual-ethernet" -> "veth" + | "vti" -> "vti" + | "vxlan" -> "vxlan" + | "wireguard" -> "wg" + | "wireless" -> "wlan" + | "wwan" -> "wwan" + | _ -> "" + +(* filter_section to match the constraint of python.vyos.ifconfig.section + *) +let rx = Pcre.regexp {|\d(\d|v|\.)*$|} + +let filter_section s = + let r = Pcre.qreplace_first ~rex:rx ~templ:"" s in + match r with + |"bond"|"br"|"dum"|"eth"|"gnv"|"ifb"|"l2tpeth"|"lo"|"macsec" -> true + |"peth"|"pppoe"|"sstpc"|"tun"|"veth"|"vti"|"vtun"|"vxlan"|"wg"|"wlan"|"wwan" -> true + | _ -> false + +let filter_from_prefix p s = + let pattern = Printf.sprintf "^%s(.*)$" p + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_from_type it = + let pre = type_to_prefix it in + match pre with + | "" -> None + | _ -> Some (filter_from_prefix pre) + +let filter_broadcast s = + let pattern = {|^(bond|br|eth)(.*)$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_bridgeable s = + let pattern = {|^(bond|eth|gnv|l2tpeth|lo|tun|veth|vtun|vxlan|wlan)(.*)$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_bondable s = + let pattern = {|^(eth)(.*)$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + true + with Not_found -> false + +let filter_no_vlan s = + let pattern = {|^([^.]+)(\.\d+)+$|} + in + try + let _ = Pcre.exec ~pat:pattern s in + false + with Not_found -> true + +let get_interfaces = + let intf_type = !intf_type in + let fltr = + if String.length(intf_type) > 0 then + filter_from_type intf_type + else None + in + let l = Func.list_interfaces () in + let res = List.sort_uniq compare l in + let res = + if !broadcast then List.filter filter_broadcast res + else res + in + let res = + if !bridgeable then List.filter filter_bridgeable res + else res + in + let res = + if !bondable then List.filter filter_bondable res + else res + in + let res = + if !no_vlan then List.filter filter_no_vlan res + else res + in + let res = List.filter filter_section res in + match fltr with + | Some f -> List.filter f res + | None -> res + +let () = + let res = get_interfaces in + List.iter (Printf.printf "%s ") res; + Printf.printf "\n" diff --git a/src/iface/func.ml b/src/iface/func.ml deleted file mode 100644 index 13e1860d9..000000000 --- a/src/iface/func.ml +++ /dev/null @@ -1 +0,0 @@ -external list_interfaces: unit -> string list = "interface_list" diff --git a/src/iface/func.mli b/src/iface/func.mli deleted file mode 100644 index b16d37354..000000000 --- a/src/iface/func.mli +++ /dev/null @@ -1 +0,0 @@ -external list_interfaces : unit -> string list = "interface_list" diff --git a/src/iface/iface.c b/src/iface/iface.c deleted file mode 100644 index bf2f0250e..000000000 --- a/src/iface/iface.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Simple wrapper of getifaddrs for OCaml list of interfaces - */ -#include -#include -#include -#include - -CAMLprim value interface_list(value unit) { - struct ifaddrs *ifaddr; - struct ifaddrs *ifa; - - CAMLparam1( unit ); - CAMLlocal2( cli, cons ); - - cli = Val_emptylist; - - if (getifaddrs(&ifaddr) == -1) { - CAMLreturn(cli); - } - for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_name == NULL) - continue; - - CAMLlocal1( ml_s ); - cons = caml_alloc(2, 0); - - ml_s = caml_copy_string(ifa->ifa_name); - Store_field( cons, 0, ml_s ); - Store_field( cons, 1, cli ); - - cli = cons; - } - - freeifaddrs(ifaddr); - - CAMLreturn(cli); -} diff --git a/src/iface/list_interfaces.ml b/src/iface/list_interfaces.ml deleted file mode 100644 index fec5c9ccc..000000000 --- a/src/iface/list_interfaces.ml +++ /dev/null @@ -1,135 +0,0 @@ -(* - *) -let intf_type = ref "" -let broadcast = ref false -let bridgeable = ref false -let bondable = ref false -let no_vlan = ref false - -let args = [ - ("--type", Arg.String (fun s -> intf_type := s), "List interfaces of specified type"); - ("--broadcast", Arg.Unit (fun () -> broadcast := true), "List broadcast interfaces"); - ("--bridgeable", Arg.Unit (fun () -> bridgeable := true), "List bridgeable interfaces"); - ("--bondable", Arg.Unit (fun () -> bondable := true), "List bondable interfaces"); - ("--no-vlan-subinterfaces", Arg.Unit (fun () -> no_vlan := true), "List only parent interfaces"); -] -let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) - -let () = Arg.parse args (fun _ -> ()) usage - -let type_to_prefix it = - match it with - | "" -> "" - | "bonding" -> "bond" - | "bridge" -> "br" - | "dummy" -> "dum" - | "ethernet" -> "eth" - | "geneve" -> "gnv" - | "input" -> "ifb" - | "l2tpeth" -> "l2tpeth" - | "loopback" -> "lo" - | "macsec" -> "macsec" - | "openvpn" -> "vtun" - | "pppoe" -> "pppoe" - | "pseudo-ethernet" -> "peth" - | "sstpc" -> "sstpc" - | "tunnel" -> "tun" - | "virtual-ethernet" -> "veth" - | "vti" -> "vti" - | "vxlan" -> "vxlan" - | "wireguard" -> "wg" - | "wireless" -> "wlan" - | "wwan" -> "wwan" - | _ -> "" - -(* filter_section to match the constraint of python.vyos.ifconfig.section - *) -let rx = Pcre.regexp {|\d(\d|v|\.)*$|} - -let filter_section s = - let r = Pcre.qreplace_first ~rex:rx ~templ:"" s in - match r with - |"bond"|"br"|"dum"|"eth"|"gnv"|"ifb"|"l2tpeth"|"lo"|"macsec" -> true - |"peth"|"pppoe"|"sstpc"|"tun"|"veth"|"vti"|"vtun"|"vxlan"|"wg"|"wlan"|"wwan" -> true - | _ -> false - -let filter_from_prefix p s = - let pattern = Printf.sprintf "^%s(.*)$" p - in - try - let _ = Pcre.exec ~pat:pattern s in - true - with Not_found -> false - -let filter_from_type it = - let pre = type_to_prefix it in - match pre with - | "" -> None - | _ -> Some (filter_from_prefix pre) - -let filter_broadcast s = - let pattern = {|^(bond|br|eth)(.*)$|} - in - try - let _ = Pcre.exec ~pat:pattern s in - true - with Not_found -> false - -let filter_bridgeable s = - let pattern = {|^(bond|eth|gnv|l2tpeth|lo|tun|veth|vtun|vxlan|wlan)(.*)$|} - in - try - let _ = Pcre.exec ~pat:pattern s in - true - with Not_found -> false - -let filter_bondable s = - let pattern = {|^(eth)(.*)$|} - in - try - let _ = Pcre.exec ~pat:pattern s in - true - with Not_found -> false - -let filter_no_vlan s = - let pattern = {|^([^.]+)(\.\d+)+$|} - in - try - let _ = Pcre.exec ~pat:pattern s in - false - with Not_found -> true - -let get_interfaces = - let intf_type = !intf_type in - let fltr = - if String.length(intf_type) > 0 then - filter_from_type intf_type - else None - in - let l = Func.list_interfaces () in - let res = List.sort_uniq compare l in - let res = - if !broadcast then List.filter filter_broadcast res - else res - in - let res = - if !bridgeable then List.filter filter_bridgeable res - else res - in - let res = - if !bondable then List.filter filter_bondable res - else res - in - let res = - if !no_vlan then List.filter filter_no_vlan res - else res - in - let res = List.filter filter_section res in - match fltr with - | Some f -> List.filter f res - | None -> res - -let () = - let res = get_interfaces in - List.iter (Printf.printf "%s ") res; - Printf.printf "\n" -- cgit v1.2.3 From 067e167ad5f4f85c4233a0634b90fb1cd1237720 Mon Sep 17 00:00:00 2001 From: khramshinr Date: Thu, 12 Dec 2024 11:29:08 +0800 Subject: T6792: Autocomplete for "show arp interface" interfaces extended filter for possible broadcast-capable interfaces --- src/completion/list_interfaces/list_interfaces.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml index fec5c9ccc..58d0693d3 100644 --- a/src/completion/list_interfaces/list_interfaces.ml +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -68,7 +68,7 @@ let filter_from_type it = | _ -> Some (filter_from_prefix pre) let filter_broadcast s = - let pattern = {|^(bond|br|eth)(.*)$|} + let pattern = {|^(bond|br|tun|vtun|eth|gnv|peth|macsec|vxlan|wwan|wlan)(.*)$|} in try let _ = Pcre.exec ~pat:pattern s in -- cgit v1.2.3 From c6cb5ce2724a52b3b7b3741323c4466c87953c6a Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 13 May 2025 13:04:44 +0100 Subject: validators: T7450: use PCRE2 instead of the outdated original PCRE --- debian/control | 3 +-- src/completion/list_interfaces/list_interfaces.ml | 14 +++++------ src/dune | 8 +++--- src/validate_value.ml | 6 +++-- src/validators/numeric.ml | 6 ++--- src/validators/url.ml | 30 +++++++++++------------ 6 files changed, 34 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/debian/control b/debian/control index 4ae87cad2..4aa2d7367 100644 --- a/debian/control +++ b/debian/control @@ -3,8 +3,7 @@ Section: contrib/net Priority: extra Maintainer: VyOS Package Maintainers Build-Depends: debhelper (>= 9), - quilt, - libpcre3-dev, + quilt Standards-Version: 3.9.6 Package: vyos-utils diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml index 58d0693d3..f3db6bdfe 100644 --- a/src/completion/list_interfaces/list_interfaces.ml +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -44,10 +44,10 @@ let type_to_prefix it = (* filter_section to match the constraint of python.vyos.ifconfig.section *) -let rx = Pcre.regexp {|\d(\d|v|\.)*$|} +let rx = Pcre2.regexp {|\d(\d|v|\.)*$|} let filter_section s = - let r = Pcre.qreplace_first ~rex:rx ~templ:"" s in + let r = Pcre2.qreplace_first ~rex:rx ~templ:"" s in match r with |"bond"|"br"|"dum"|"eth"|"gnv"|"ifb"|"l2tpeth"|"lo"|"macsec" -> true |"peth"|"pppoe"|"sstpc"|"tun"|"veth"|"vti"|"vtun"|"vxlan"|"wg"|"wlan"|"wwan" -> true @@ -57,7 +57,7 @@ let filter_from_prefix p s = let pattern = Printf.sprintf "^%s(.*)$" p in try - let _ = Pcre.exec ~pat:pattern s in + let _ = Pcre2.exec ~pat:pattern s in true with Not_found -> false @@ -71,7 +71,7 @@ let filter_broadcast s = let pattern = {|^(bond|br|tun|vtun|eth|gnv|peth|macsec|vxlan|wwan|wlan)(.*)$|} in try - let _ = Pcre.exec ~pat:pattern s in + let _ = Pcre2.exec ~pat:pattern s in true with Not_found -> false @@ -79,7 +79,7 @@ let filter_bridgeable s = let pattern = {|^(bond|eth|gnv|l2tpeth|lo|tun|veth|vtun|vxlan|wlan)(.*)$|} in try - let _ = Pcre.exec ~pat:pattern s in + let _ = Pcre2.exec ~pat:pattern s in true with Not_found -> false @@ -87,7 +87,7 @@ let filter_bondable s = let pattern = {|^(eth)(.*)$|} in try - let _ = Pcre.exec ~pat:pattern s in + let _ = Pcre2.exec ~pat:pattern s in true with Not_found -> false @@ -95,7 +95,7 @@ let filter_no_vlan s = let pattern = {|^([^.]+)(\.\d+)+$|} in try - let _ = Pcre.exec ~pat:pattern s in + let _ = Pcre2.exec ~pat:pattern s in false with Not_found -> true diff --git a/src/dune b/src/dune index 8da009459..7b6bcbd4f 100644 --- a/src/dune +++ b/src/dune @@ -4,13 +4,13 @@ (name numeric) (public_name numeric) (modules numeric) - (libraries pcre)) + (libraries pcre2)) (executable (name url) (public_name url) (modules url) - (libraries pcre)) + (libraries pcre2)) (executable (name file_path) @@ -22,13 +22,13 @@ (name validate_value) (public_name validate-value) (modules validate_value) - (libraries pcre unix containers)) + (libraries pcre2 unix containers)) (executable (name list_interfaces) (public_name list_interfaces) (modules func list_interfaces) - (libraries pcre) + (libraries pcre2) (foreign_stubs (language c) (names iface))) diff --git a/src/validate_value.ml b/src/validate_value.ml index 05d62b189..fd9f3d9d7 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -12,14 +12,16 @@ let rec validate_value buf value_constraint value = | Group l -> List.for_all (fun c -> validate_value buf c value) l | Regex s -> - (try let _ = Pcre.exec ~pat:(Printf.sprintf "^%s$" s) value in true + (try let _ = Pcre2.exec ~pat:(Printf.sprintf "^%s$" s) value in true with Not_found -> false) | Exec c -> (* XXX: Unix.open_process_in is "shelling out", which is a bad idea on multiple levels, especially when the input comes directly from the user... We should do something about it. *) - let chan = Unix.open_process_in (Printf.sprintf "%s \'%s\' 2>&1" c value) in + let cmd = Printf.sprintf "%s \'%s\' 2>&1" c value in + let () = Printf.printf "COMMAND: %s" cmd in + let chan = Unix.open_process_in cmd in let out = try CCIO.read_all chan with _ -> "" in let result = Unix.close_process_in chan in match result with diff --git a/src/validators/numeric.ml b/src/validators/numeric.ml index 0c75f83f5..e6caddc05 100644 --- a/src/validators/numeric.ml +++ b/src/validators/numeric.ml @@ -65,11 +65,11 @@ let check_positive opts m = failwith "option '--positive does' not apply to a range value" let looks_like_number value = - try let _ = Pcre.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true + try let _ = Pcre2.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true with Not_found -> false let is_relative value = - try let _ = Pcre.exec ~pat:"^[+-][0-9]+$" value in true + try let _ = Pcre2.exec ~pat:"^[+-][0-9]+$" value in true with Not_found -> false let number_string_drop_modifier value = @@ -170,7 +170,7 @@ let check_argument_type opts m = else Printf.ksprintf failwith "Value must be a number, not a range" let is_range_val s = - try let _ = Pcre.exec ~pat:"^[0-9]+-[0-9]+$" s in true + try let _ = Pcre2.exec ~pat:"^[0-9]+-[0-9]+$" s in true with Not_found -> false let var_numeric_str s = diff --git a/src/validators/url.ml b/src/validators/url.ml index 3d7754437..7ba5dcf6c 100644 --- a/src/validators/url.ml +++ b/src/validators/url.ml @@ -9,9 +9,9 @@ *) let split_scheme url = let aux url = - let res = Pcre.exec ~pat:{|^([a-zA-Z0-9\.\-]+):(.*)$|} url in - let scheme = Pcre.get_substring res 1 in - let uri = Pcre.get_substring res 2 in + let res = Pcre2.exec ~pat:{|^([a-zA-Z0-9\.\-]+):(.*)$|} url in + let scheme = Pcre2.get_substring res 1 in + let uri = Pcre2.get_substring res 2 in (String.lowercase_ascii scheme, uri) in try Ok (aux url) @@ -24,17 +24,17 @@ let is_scheme_allowed allowed_schemes scheme = let regex_matches regex s = try - let _ = Pcre.exec ~rex:regex s in + let _ = Pcre2.exec ~rex:regex s in true with Not_found -> false let host_path_format = - Pcre.regexp + Pcre2.regexp {|^//(?:[^/?#]+(?::[^/?#]*)?@)?([a-zA-Z0-9\-\._~]+|\[[a-zA-Z0-9:\.]+\])(?::([0-9]+))?(/.*)?$|} -let host_name_format = Pcre.regexp {|^[a-zA-Z0-9]+([\-\._~]{1}[a-zA-Z0-9]+)*$|} -let ipv4_addr_format = Pcre.regexp {|^(([1-9]\d{0,2}|0)\.){3}([1-9]\d{0,2}|0)$|} -let ipv6_addr_format = Pcre.regexp {|^\[([a-z0-9:\.]+|[A-Z0-9:\.]+)\]$|} +let host_name_format = Pcre2.regexp {|^[a-zA-Z0-9]+([\-\._~]{1}[a-zA-Z0-9]+)*$|} +let ipv4_addr_format = Pcre2.regexp {|^(([1-9]\d{0,2}|0)\.){3}([1-9]\d{0,2}|0)$|} +let ipv6_addr_format = Pcre2.regexp {|^\[([a-z0-9:\.]+|[A-Z0-9:\.]+)\]$|} let is_port s = try @@ -58,8 +58,8 @@ let is_ipv6_segment s = with Failure _ -> false let is_ipv4_addr s = - let res = Pcre.exec ~rex:ipv4_addr_format s in - let ipv4_addr_str = Pcre.get_substring res 0 in + let res = Pcre2.exec ~rex:ipv4_addr_format s in + let ipv4_addr_str = Pcre2.get_substring res 0 in let ipv4_addr_l = String.split_on_char '.' ipv4_addr_str in List.for_all is_ipv4_octet ipv4_addr_l @@ -83,10 +83,10 @@ let is_ipv6_dual_addr s = List.for_all is_ipv6_segment seg_str_l let is_ipv6_addr s = - let res = Pcre.exec ~rex:ipv6_addr_format s in - let ipv6_addr_str = Pcre.get_substring res 1 in + let res = Pcre2.exec ~rex:ipv6_addr_format s in + let ipv6_addr_str = Pcre2.get_substring res 1 in try - let typo = Pcre.exec ~pat:{|:::|} ipv6_addr_str in + let typo = Pcre2.exec ~pat:{|:::|} ipv6_addr_str in match typo with | _ -> false with Not_found -> @@ -94,8 +94,8 @@ let is_ipv6_addr s = let host_path_matches s = try - let res = Pcre.exec ~rex:host_path_format s in - let substr = Pcre.get_substrings ~full_match:false res in + let res = Pcre2.exec ~rex:host_path_format s in + let substr = Pcre2.get_substrings ~full_match:false res in let port_str = Array.get substr 1 in if String.length port_str > 0 && not (is_port port_str) then false else -- cgit v1.2.3 From 184b7023b4cf00be4e891e7038d7abe2941cd38a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 15 May 2025 13:05:18 -0500 Subject: validators: T7450: remove debug line --- src/validate_value.ml | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/validate_value.ml b/src/validate_value.ml index fd9f3d9d7..1d3450fd9 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -20,7 +20,6 @@ let rec validate_value buf value_constraint value = We should do something about it. *) let cmd = Printf.sprintf "%s \'%s\' 2>&1" c value in - let () = Printf.printf "COMMAND: %s" cmd in let chan = Unix.open_process_in cmd in let out = try CCIO.read_all chan with _ -> "" in let result = Unix.close_process_in chan in -- cgit v1.2.3 From 8881e886be2bfe26896c420dd85c150442e95acb Mon Sep 17 00:00:00 2001 From: Andrew Topp Date: Sun, 29 Jun 2025 16:37:03 +1000 Subject: validators: T6622: Radix-aware numeric validation * Adding support for hex, octal and binary integers (decimal-only floats) * Defaults retain current parameter and parsing behaviour * Any radix option overrides default assumption of "--decimal" * Radix options are inclusive together * As number_of_string is used for parameters, param parsing is overridden to always allow un-prefixed decimals, for convenience. --- src/validators/numeric.ml | 88 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/validators/numeric.ml b/src/validators/numeric.ml index e6caddc05..8e4becb56 100644 --- a/src/validators/numeric.ml +++ b/src/validators/numeric.ml @@ -11,6 +11,10 @@ type options = { relative: bool; allow_range: bool; require_range: bool; + parse_hex: bool; + parse_oct: bool; + parse_bin: bool; + parse_dec: bool; } let default_opts = { @@ -23,6 +27,10 @@ let default_opts = { relative = false; allow_range = false; require_range = false; + parse_hex = false; + parse_oct = false; + parse_bin = false; + parse_dec = false; } let opts = ref default_opts @@ -39,6 +47,10 @@ let args = [ ("--relative", Arg.Unit (fun () -> opts := {!opts with relative=true}), "Allow relative increment/decrement (+/-N)"); ("--allow-range", Arg.Unit (fun () -> opts := {!opts with allow_range=true}), "Allow the argument to be a range rather than a single number"); ("--require-range", Arg.Unit (fun () -> opts := {!opts with require_range=true; allow_range=true}), "Require the argument to be a range rather than a single number"); + ("--hex", Arg.Unit (fun () -> opts := {!opts with parse_hex=true}), "Parse hexadecimal integers as valid numbers, complete with 0x-prefix"); + ("--octal", Arg.Unit (fun () -> opts := {!opts with parse_oct=true}), "Parse octal integers as valid numbers, complete with 0o-prefix"); + ("--binary", Arg.Unit (fun () -> opts := {!opts with parse_bin=true}), "Parse binary integers as valid numbers, complete with 0b-prefix"); + ("--decimal", Arg.Unit (fun () -> opts := {!opts with parse_dec=true}), "Continue to parse decimal numbers even when other radixes are requested, no prefix required"); ("--", Arg.Rest (fun s -> number_arg := s), "Interpret next item as an argument"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] |" Sys.argv.(0) @@ -64,12 +76,24 @@ let check_positive opts m = | Range_float _ -> failwith "option '--positive does' not apply to a range value" -let looks_like_number value = +let looks_like_decimal value = try let _ = Pcre2.exec ~pat:"^(\\-?)[0-9]+(\\.[0-9]+)?$" value in true with Not_found -> false +let looks_like_hex value = + try let _ = Pcre2.exec ~pat:"^(\\-?)0[xX][0-9a-fA-F]+$" value in true + with Not_found -> false + +let looks_like_octal value = + try let _ = Pcre2.exec ~pat:"^(\\-?)0[oO][0-7]+$" value in true + with Not_found -> false + +let looks_like_binary value = + try let _ = Pcre2.exec ~pat:"^(\\-?)0[bB][0-1]+$" value in true + with Not_found -> false + let is_relative value = - try let _ = Pcre2.exec ~pat:"^[+-][0-9]+$" value in true + try let _ = Pcre2.exec ~pat:"^[+-](0[xboXBO])?[0-9a-fA-F]+$" value in true with Not_found -> false let number_string_drop_modifier value = @@ -87,24 +111,40 @@ let get_relative opts t = else t let number_of_string opts s = - if not (looks_like_number s) then Printf.ksprintf failwith "'%s' is not a valid number" s else - let n = float_of_string_opt s in - match n with - | Some n -> - (* If floats are explicitly allowed, just return the number. *) - if opts.allow_float then n - (* If floats are not explicitly allowed, check if the argument has a decimal separator in it. - If the argument string contains a dot but float_of_string didn't dislike it, - it's a valid number but not an integer. - *) - else if not (String.contains s '.') then n - (* If float_of_string returned None, the argument string is just garbage rather than a number. *) - else Printf.ksprintf failwith "'%s' is not a valid integer number" s - | None -> - Printf.ksprintf failwith "'%s' is not a valid number" s + if (opts.allow_float && not opts.parse_dec) then + failwith "Only decimal numbers may be floating point" + else if (opts.parse_hex && (looks_like_hex s)) || + (opts.parse_oct && (looks_like_octal s)) || + (opts.parse_bin && (looks_like_binary s)) then + (* float_of_string won't deal with octal or binary and hex-floats are just weird. + Easier to separate non-decimal parsing this way. + *) + let n = int_of_string_opt s in + match n with + | Some n -> + float_of_int n + | None -> + Printf.ksprintf failwith "'%s' is not a valid non-decimal number" s + else if (opts.parse_dec && (looks_like_decimal s)) then + let n = float_of_string_opt s in + match n with + | Some n -> + (* If floats are explicitly allowed, just return the number. *) + if opts.allow_float then n + (* If floats are not explicitly allowed, check if the argument has a decimal separator in it. + If the argument string contains a dot but float_of_string didn't dislike it, + it's a valid number but not an integer. + *) + else if not (String.contains s '.') then n + (* If float_of_string returned None, the argument string is just garbage rather than a number. *) + else Printf.ksprintf failwith "'%s' is not a valid integer number" s + | None -> + Printf.ksprintf failwith "'%s' is not a valid number" s + else Printf.ksprintf failwith "'%s' is not a valid number" s let range_of_string opts s = - let rs = String.split_on_char '-' s |> List.map String.trim |> List.map (number_of_string opts) in + let param_opts = { opts with parse_dec = true } in + let rs = String.split_on_char '-' s |> List.map String.trim |> List.map (number_of_string param_opts) in match rs with | [l; r] -> (l, r) | exception (Failure msg) -> @@ -148,7 +188,8 @@ let check_not_ranges opts m = Printf.ksprintf failwith "Range is in one of excluded ranges" let check_not_values opts m = - let excluded_values = List.map (number_of_string opts) opts.not_values in + let param_opts = { opts with parse_dec = true } in + let excluded_values = List.map (number_of_string param_opts) opts.not_values in if excluded_values = [] then () else match m with | Range_float _ -> Printf.ksprintf failwith "--not-value cannot be used with ranges" @@ -170,7 +211,7 @@ let check_argument_type opts m = else Printf.ksprintf failwith "Value must be a number, not a range" let is_range_val s = - try let _ = Pcre2.exec ~pat:"^[0-9]+-[0-9]+$" s in true + try let _ = Pcre2.exec ~pat:"^(0[xboXBO])?[0-9a-fA-F]+-(0[xboXBO])?[0-9a-fA-F]+$" s in true with Not_found -> false let var_numeric_str s = @@ -178,9 +219,14 @@ let var_numeric_str s = | true -> Range_string s | false -> Number_string s +let check_default_radix opts = + if (not opts.parse_hex && not opts.parse_oct && not opts.parse_bin) then + {opts with parse_dec=true} + else opts + let () = try let s = var_numeric_str !number_arg in - let opts = !opts in + let opts = check_default_radix !opts in let s = get_relative opts s in let n = match s with -- cgit v1.2.3 From ad5b369a63ed07c6d92b453f4f73bfe7993ff053 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 7 Aug 2025 13:15:59 +0100 Subject: op-mode: T7583: add a new operational command runner --- debian/changelog | 6 + debian/rules | 2 + debian/vyos-utils.install | 1 + debian/vyos-utils.postinst | 6 + src/dune | 13 ++ src/vyos_op_run.ml | 294 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 322 insertions(+) create mode 100644 debian/vyos-utils.postinst create mode 100644 src/vyos_op_run.ml (limited to 'src') diff --git a/debian/changelog b/debian/changelog index e0ac2fa3f..0db92790a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vyos-utils (0.0.4) unstable; urgency=medium + + * Add an operational command runner. + + -- Daniil Baturin Thu, 07 Aug 2025 12:39:14 +0000 + vyos-utils (0.0.3) unstable; urgency=medium * Quote values properly (T1901). diff --git a/debian/rules b/debian/rules index 451eef1ba..ea313abfc 100755 --- a/debian/rules +++ b/debian/rules @@ -13,11 +13,13 @@ override_dh_auto_build: override_dh_auto_install: mkdir -p $(DIR)/usr/libexec/vyos/validators mkdir -p $(DIR)/usr/libexec/vyos/completion + mkdir -p $(DIR)/usr/bin cp _build/install/default/bin/numeric $(DIR)/usr/libexec/vyos/validators cp _build/install/default/bin/validate-value $(DIR)/usr/libexec/vyos/ cp _build/install/default/bin/file-path $(DIR)/usr/libexec/vyos/validators cp _build/install/default/bin/url $(DIR)/usr/libexec/vyos/validators cp _build/install/default/bin/list_interfaces $(DIR)/usr/libexec/vyos/completion + cp _build/install/default/bin/vyos-op-run $(DIR)/usr/bin/ override_dh_auto_test: echo "No tests yet" diff --git a/debian/vyos-utils.install b/debian/vyos-utils.install index 9cc90a1b3..55eefd4e7 100644 --- a/debian/vyos-utils.install +++ b/debian/vyos-utils.install @@ -3,3 +3,4 @@ usr/libexec/vyos/validators/numeric usr/libexec/vyos/validators/file-path usr/libexec/vyos/validators/url usr/libexec/vyos/completion/list_interfaces +usr/bin/vyos-op-run diff --git a/debian/vyos-utils.postinst b/debian/vyos-utils.postinst new file mode 100644 index 000000000..decc38f60 --- /dev/null +++ b/debian/vyos-utils.postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +# Set the SUID bit on the runner +# so that it can have the prigilege to run commands as root. +# Permissions will be enforced at the VyOS command level. +chmod u+s /usr/bin/vyos-op-run diff --git a/src/dune b/src/dune index 7b6bcbd4f..6dc9c8e7e 100644 --- a/src/dune +++ b/src/dune @@ -32,3 +32,16 @@ (foreign_stubs (language c) (names iface))) + +(executable + (name vyos_op_run) + (public_name vyos-op-run) + (modules vyos_op_run) + (libraries + logs + logs.fmt + fmt.tty + yojson + mustache + pcre2 + unix)) diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml new file mode 100644 index 000000000..0a8f35134 --- /dev/null +++ b/src/vyos_op_run.ml @@ -0,0 +1,294 @@ +(* + * vyos-op-run: the wrapper for executing operational mode commands. + * + * Copyright VyOS maintainers and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or later as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) + +(* Global constants *) +let op_def_file = "/usr/share/vyos/op_cache.json" + +(* Execution options *) +type options = { + (* The option not to actually run the command, just print it *) + dry_run: bool; + + (* Enable debug output *) + debug: bool; + + (* The original VyOS command, + like "show interfaces ethernet", + for debugging and for substitutions of $@/$* + *) + vyos_command: string; +} + +let default_options = { + dry_run = false; + debug = false; + vyos_command = ""; +} + +(* Exceptions and helpers *) +exception Invalid_command of string +let invalid_command msg = raise (Invalid_command msg) + +exception Internal_error of string +let internal_error msg = raise (Internal_error msg) + +exception Command_error of string +let command_error msg = raise (Command_error msg) + +exception Incomplete_command + +(* Logging setup routines *) +let get_color_style () = + let no_color = Sys.getenv_opt "NO_COLOR" |> Option.is_some in + (* Logs always go to stderr, so we don't check if stdout is a TTY. *) + let interactive = Unix.isatty (Unix.descr_of_out_channel stderr) in + if interactive && (not no_color) then `Ansi_tty else `None + +let setup_logging debug = + let level = + if debug then Logs.Debug + else Logs.Warning + in + let style = get_color_style () in + Logs.set_level (Some level); + Fmt_tty.setup_std_outputs ~style_renderer:style (); + Logs.set_reporter @@ Logs.format_reporter (); + (* Enable exception tracing if debug=true, + by default it's disabled in the OCaml runtime *) + if debug then Printexc.record_backtrace true + +(* JSON data helpers *) +let get_string_field name obj = + let open Yojson.Safe.Util in + member name obj |> to_string + +let read_command_definitions () = + let ic = open_in op_def_file in + let data = Yojson.Safe.from_channel ic in + let () = close_in ic in + data + +let find_child_node op_node word = + let open Yojson.Safe.Util in + let res = member word op_node in + match res with + | (`Assoc _) as d -> Some d + | `Null -> None + | _ -> + Printf.ksprintf internal_error {|Child node "%s" is not an object!|} word + +let get_node_data op_node = + let open Yojson.Safe.Util in + let res = member "__node_data" op_node in + match res with + | (`Assoc _) as d -> d + | `Null -> + Printf.ksprintf internal_error "Op node has no data!\n" + | _ -> + Printf.ksprintf internal_error "Op node data is not an object!" + +let get_path node_data = + let open Yojson.Safe.Util in + member "path" node_data |> convert_each to_string + +let get_node_type node_data = + let open Yojson.Safe.Util in + let res = member "node_type" node_data in + match res with + | `String _type -> _type + | `Null -> + Printf.ksprintf internal_error "Op node has no type!" + | _ -> + Printf.ksprintf internal_error "Op node data is not a string!" + +let get_command_opt ?(field_name="command") node_data = + let open Yojson.Safe.Util in + let res = member field_name node_data in + match res with + | `String cmd -> Some cmd + | `Null -> None + | _ -> Printf.ksprintf internal_error "command must be a string" + +let get_command ?(field_name="command") node_data = + let res = get_command_opt ~field_name:field_name node_data in + match res with + | Some cmd -> cmd + | None -> Printf.ksprintf internal_error "node is expected to have a command" + +let get_virtual_tag_node node = + let open Yojson.Safe.Util in + let res = member "__virtual_tag" node in + match res with + | `Null -> None + | _ -> Some res + +(* Command rendering and execution *) +let render_command opts env command_tmpl = + let () = Logs.debug @@ fun m -> m "Command template: %s" command_tmpl in + let command_tmpl = (Mustache.of_string command_tmpl) in + let command = Mustache.render command_tmpl (`O env) in + let vyos_command = opts.vyos_command in + Pcre2.replace ~pat:{|\$[@*]|} ~templ:vyos_command command + +let run_command opts env command_tmpl = + let cmd = render_command opts env command_tmpl in + if opts.dry_run then Printf.printf "%s\n%!" cmd else + let () = Logs.debug @@ fun m -> m "Command to be executed %s" cmd in + let res = Unix.system cmd in + match res with + | Unix.WEXITED 0 -> () + | _ -> Printf.ksprintf command_error "Execution of command '%s' failed" cmd + +(* Command lookup *) +let rec find_command opts ?(env=[]) ?(parent="") node cmd_words = + match cmd_words with + | w :: ws -> + let () = Logs.debug @@ fun m -> m "Looking up node '%s'" w in + let res = find_child_node node w in + begin match res with + | Some child_node -> + (* It's a normal, fixed command word *) + find_command opts ~env:env ~parent:w child_node ws + | None -> + (* It's either an argument of a tag node + or an incorrect command word *) + let node_data = get_node_data node in + let node_type = get_string_field "node_type" node_data in + let virtual_tag_node = get_virtual_tag_node node in + match node_type, virtual_tag_node with + | "tagNode", None -> + (* It's a simple tag node *) + let env = (Printf.sprintf "%s-tag_value" parent, `String w) :: env in + begin match ws with + | [] -> + let command = get_command node_data in + run_command opts env command + | _ as ws -> + find_command opts ~env:env ~parent:w node ws + end + | "node", Some vtn -> + (* It's a command that can be used either by itself or with an argument. *) + let env = (Printf.sprintf "%s-tag_value" parent, `String w) :: env in + begin match ws with + | [] -> + let vtn_data = get_node_data vtn in + let command = get_command vtn_data in + run_command opts env command + | _ -> + (* In the case of a virtual tag node, we take the parent (for variable substitution purposes) + from the upper level. + *) + find_command opts ~env:env ~parent:parent vtn ws + end + | "node", None | "leafNode", None -> + let path = get_path node_data in + Printf.ksprintf invalid_command {|"%s" is not a valid argument for command [%s]|} + w (String.concat " " path) + | _, _ -> + Printf.ksprintf internal_error + {|Node with type "%s" must not have a child|} + node_type + end + | _ -> + let node_data = get_node_data node in + let node_type = get_node_type node_data in + let command = + begin match node_type with + | "node" | "leafNode" -> + get_command_opt node_data + | "tagNode" -> + (* If it's a tag node but there's no argument, + we need to check if that tag node has standalone behavior attached to it. + *) + get_command_opt ~field_name:"standalone_command" node_data + | "virtualTagNode" -> + None + | _ -> Printf.ksprintf internal_error {|Invalid node type "%s"|} node_type + end + in + begin match command with + | Some command -> + run_command opts env command + | None -> + raise Incomplete_command + end + +(* Command line argument parsing *) +let usage_msg = Printf.sprintf {|Usage: %s [OPTIONS] + +%s is the VyOS operational command wrapper. +It is used by the CLI and can be used +for running operational commands from scripts. + +Options: +|} Sys.argv.(0) Sys.argv.(0) + +let get_args () = + let opts = ref default_options in + let args = ref [] in + let add_positional_arg arg = + args := arg :: !args + in + let arg_spec = Arg.align [ + ("--dry-run", + Arg.Unit (fun () -> opts := {!opts with dry_run=true}), + "Show the command instead of executing it"); + ("--debug", + Arg.Unit (fun () -> opts := {!opts with debug=true}), + "Enable debug output"); + ] + in + let () = Arg.parse arg_spec add_positional_arg usage_msg in + let args = List.rev !args in + ({!opts with vyos_command=(String.concat " " args)}, args) + +let () = + let debug = + (* For simplicity, we check for the existence + of the VYOS_DEBUG environment variable, + rather than for specific values. + *) + match Unix.getenv "VYOS_DEBUG" with + | _ -> true + | exception Not_found -> false + in + let options, args = get_args () in + (* If debug is not enabled by the environment variable, + take it from command line options -- + it may be enabled there. + *) + let () = if debug then print_endline "Debug is enabled by the env var" in + let debug = if debug then true else options.debug in + let () = setup_logging debug in + let op_defs = read_command_definitions () in + let () = Unix.setuid 0 in + try + find_command options ~env:[] ~parent:"" op_defs args + with + | Invalid_command msg -> + Printf.fprintf stderr "Invalid command [%s]: %s" options.vyos_command msg; + exit 1 + | Command_error msg -> + Printf.fprintf stderr "%s" msg; + | Incomplete_command -> + Printf.fprintf stderr "Incomplete command: %s" options.vyos_command; + exit 2 + | Internal_error msg -> + Printf.fprintf stderr "Internal error: %s" msg; + exit 255 + -- cgit v1.2.3 From dd7ca05d33174c5fe2e35c5d0b7610973a02039c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 21 Aug 2025 13:46:01 +0100 Subject: vyos-op-run: T7745: add a command permission checking mechanism --- src/vyos_op_run.ml | 212 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 197 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml index 0a8f35134..0767ba298 100644 --- a/src/vyos_op_run.ml +++ b/src/vyos_op_run.ml @@ -18,6 +18,25 @@ (* Global constants *) let op_def_file = "/usr/share/vyos/op_cache.json" +let permissions_file = "/etc/vyos/operators.json" +let vyos_admin_group_name = "vyattacfg" + +(* List of commands that operators are unconditionally denied to execute. *) +let admin_only_commands = [ + (* Configuration mode operations *) + ["configure"]; + ["commit"]; + ["commit-confirm"]; + ["confirm"]; + (* XXX: executing a shell through a wrapper that does setuid 0 + provides a ready shell escape and defeats the purpose. + We cannot allow operator-level users to execute shells + in VRFs and network namespaces + at least until we find a way to drop privileges + after attaching to the VRF/netns but before executing the commands. + *) + ["execute"; "shell"]; +] (* Execution options *) type options = { @@ -50,6 +69,9 @@ let internal_error msg = raise (Internal_error msg) exception Command_error of string let command_error msg = raise (Command_error msg) +exception Permission_error +let permission_error () = raise Permission_error + exception Incomplete_command (* Logging setup routines *) @@ -83,6 +105,12 @@ let read_command_definitions () = let () = close_in ic in data +let read_permissions () = + let ic = open_in permissions_file in + let data = Yojson.Safe.from_channel ic in + let () = close_in ic in + data + let find_child_node op_node word = let open Yojson.Safe.Util in let res = member word op_node in @@ -137,6 +165,148 @@ let get_virtual_tag_node node = | `Null -> None | _ -> Some res +(* Command permission checks *) + +let rec permission_matches perm cmd = + match perm, cmd with + | [], _ -> + (* If all terms of the permission matched + all words of the command, the command is allowed -- + we follow the implicit approach + "every permission includes all sub-commands" + *) + true + | _, [] -> + (* If the command is shorter than the permission spec, + it means the permission is more specific. + E.g., 'show interfaces ethernet' permission + should reject attempts to run 'show interfaces', + since its intent is to allow access only to Ethernet. *) + false + | (p :: ps), (c :: cs) -> + (* Permission term can be either a command word + or a special token '*' that matches any command. *) + if (p = c) || (p = "*") then permission_matches ps cs + else false + +let group_perms_match perms group cmd = + let get_group_perms perms g = + let perms = Yojson.Safe.Util.path + ["groups"; g; "command_policy"; "allow"] perms + in + match perms with + | Some v -> + (try + v |> + Yojson.Safe.Util.to_list |> + List.map (fun j -> Yojson.Safe.Util.to_list j |> List.map Yojson.Safe.Util.to_string) + with _ -> + Printf.ksprintf internal_error + "Command policy for group %s is not a list of string lists" g) + | None -> Printf.ksprintf internal_error + "Configuration does not define command policy for group %s" g + in + let rec perm_list_matches ps cmd = + match ps with + | [] -> false + | p :: ps -> + if permission_matches p cmd then true + else perm_list_matches ps cmd + in + let group_perms = get_group_perms perms group in + perm_list_matches group_perms cmd + +let is_admin () = + let admin_group = Unix.getgrnam vyos_admin_group_name in + let user_groups = Unix.getgroups () in + match (Array.find_opt ((=) admin_group.gr_gid) user_groups) with + | Some _ -> true + | None -> false + +let has_unsafe_characters cmd = + (* XXX: this function is highly restrictive now, + until we are completely certain that shell escape + cannot happen down the line inside VyOS op mode scripts. + Alphanumeric characters, hyphens, dots, and whitespace + should allow operator users to use most commands + that take interface names, FQDNs, and config entities + like IPsec peer names. + Notable exceptions are: + - 'show bgp regexp': regexes naturally require '$' and other + patently shell-unsafe characters. + - 'monitor traffic interface eth0 filter': + PCAP filters use '!', '&&' and '||', + although people can use 'and', 'or', 'not' + to get around the restriction. + - 'add system image': requires non-alphanumeric characters + for URLs. + *) + try + let _ = Pcre2.exec ~pat:{|[^a-zA-Z0-9_\-\.\s]|} cmd in + let () = + Printf.fprintf stderr "Command [%s] contains special characters \ + that operator-level users are not allowed to use\n" cmd + in + true + with Not_found -> false + +let is_admin_only_command cmd = + let rec prefix_matches prefix target = + match prefix, target with + | [], _ -> + (* The target matched every word of the prefix, + so it's a match. + *) + true + | _, [] -> + (* The target is shorter than the prefix, + so it's not a match. + *) + false + | (p :: ps), (t :: ts) -> + if p = t then prefix_matches ps ts + else false + in + let res = List.find_opt (fun p -> prefix_matches p cmd) admin_only_commands in + match res with + | None -> false + | Some _ -> true + +let check_command_permissions perms cmd = + let rec aux perms groups cmd = + match groups with + | [] -> permission_error () + | g :: gs -> + if group_perms_match perms g cmd then () + else aux perms gs cmd + in + (* VyOS admins can execute any commands without restrictions *) + if is_admin () then () else + (* Operators are not allowed to execute commands + with potentially unsafe characters in them *) + if has_unsafe_characters (String.concat " " cmd) then permission_error () else + (* Some commands are unconditionally denied to operators *) + if is_admin_only_command cmd then permission_error () else + (* Operator level users must always be in groups + with defined command policies + *) + let username = Unix.getlogin () in + let groups = Yojson.Safe.Util.path ["users"; username] perms in + match groups with + | None | Some (`List []) -> + Printf.ksprintf internal_error "User %s is not assigned to any operator group" username + | Some gs -> + let group_list = + (try + gs |> + Yojson.Safe.Util.to_list |> + List.map Yojson.Safe.Util.to_string + with _ -> + Printf.ksprintf internal_error "The groups field for user %s is not a list of strings" + username) + in + aux perms group_list cmd + (* Command rendering and execution *) let render_command opts env command_tmpl = let () = Logs.debug @@ fun m -> m "Command template: %s" command_tmpl in @@ -145,17 +315,22 @@ let render_command opts env command_tmpl = let vyos_command = opts.vyos_command in Pcre2.replace ~pat:{|\$[@*]|} ~templ:vyos_command command -let run_command opts env command_tmpl = +let run_external_command opts env command_tmpl = let cmd = render_command opts env command_tmpl in if opts.dry_run then Printf.printf "%s\n%!" cmd else let () = Logs.debug @@ fun m -> m "Command to be executed %s" cmd in let res = Unix.system cmd in match res with | Unix.WEXITED 0 -> () - | _ -> Printf.ksprintf command_error "Execution of command '%s' failed" cmd + | _ -> + (* Many op mode commands return non-zero exit codes on benign errors + such as an unconfigured subsystem, + so we shouldn't show this to the user by default. + *) + Logs.debug @@ fun m -> m "Execution of command '%s' failed" cmd (* Command lookup *) -let rec find_command opts ?(env=[]) ?(parent="") node cmd_words = +let rec run_vyos_command opts ?(env=[]) ?(parent="") node cmd_words = match cmd_words with | w :: ws -> let () = Logs.debug @@ fun m -> m "Looking up node '%s'" w in @@ -163,7 +338,7 @@ let rec find_command opts ?(env=[]) ?(parent="") node cmd_words = begin match res with | Some child_node -> (* It's a normal, fixed command word *) - find_command opts ~env:env ~parent:w child_node ws + run_vyos_command opts ~env:env ~parent:w child_node ws | None -> (* It's either an argument of a tag node or an incorrect command word *) @@ -177,9 +352,9 @@ let rec find_command opts ?(env=[]) ?(parent="") node cmd_words = begin match ws with | [] -> let command = get_command node_data in - run_command opts env command + run_external_command opts env command | _ as ws -> - find_command opts ~env:env ~parent:w node ws + run_vyos_command opts ~env:env ~parent:w node ws end | "node", Some vtn -> (* It's a command that can be used either by itself or with an argument. *) @@ -188,12 +363,12 @@ let rec find_command opts ?(env=[]) ?(parent="") node cmd_words = | [] -> let vtn_data = get_node_data vtn in let command = get_command vtn_data in - run_command opts env command + run_external_command opts env command | _ -> (* In the case of a virtual tag node, we take the parent (for variable substitution purposes) from the upper level. *) - find_command opts ~env:env ~parent:parent vtn ws + run_vyos_command opts ~env:env ~parent:parent vtn ws end | "node", None | "leafNode", None -> let path = get_path node_data in @@ -223,7 +398,7 @@ let rec find_command opts ?(env=[]) ?(parent="") node cmd_words = in begin match command with | Some command -> - run_command opts env command + run_external_command opts env command | None -> raise Incomplete_command end @@ -276,19 +451,26 @@ let () = let debug = if debug then true else options.debug in let () = setup_logging debug in let op_defs = read_command_definitions () in + let permissions = read_permissions () in let () = Unix.setuid 0 in + let () = Logs.debug @@ fun m -> m "Executing VyOS command [%s]" (String.concat " " args) in try - find_command options ~env:[] ~parent:"" op_defs args - with + check_command_permissions permissions args; + run_vyos_command options ~env:[] ~parent:"" op_defs args + with + | Permission_error -> + Printf.fprintf stderr "You do not have a permission to execute VyOS command [%s]\n" + options.vyos_command; + exit 1 | Invalid_command msg -> - Printf.fprintf stderr "Invalid command [%s]: %s" options.vyos_command msg; + Printf.fprintf stderr "Invalid command [%s]: %s\n" options.vyos_command msg; exit 1 | Command_error msg -> - Printf.fprintf stderr "%s" msg; + Printf.fprintf stderr "%s\n" msg; | Incomplete_command -> - Printf.fprintf stderr "Incomplete command: %s" options.vyos_command; + Printf.fprintf stderr "Incomplete command: %s\n" options.vyos_command; exit 2 | Internal_error msg -> - Printf.fprintf stderr "Internal error: %s" msg; + Printf.fprintf stderr "Internal error: %s\n" msg; exit 255 -- cgit v1.2.3 From ac2ee231810b3d8718bc6dde76c21fff581ae1c2 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 27 Aug 2025 08:56:39 -0500 Subject: T7758: allow repeated option --type in list_interfaces --- src/completion/list_interfaces/list_interfaces.ml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml index f3db6bdfe..a8d5e7bed 100644 --- a/src/completion/list_interfaces/list_interfaces.ml +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -1,13 +1,13 @@ (* *) -let intf_type = ref "" +let intf_types = ref [] let broadcast = ref false let bridgeable = ref false let bondable = ref false let no_vlan = ref false let args = [ - ("--type", Arg.String (fun s -> intf_type := s), "List interfaces of specified type"); + ("--type", Arg.String (fun s -> intf_types := (s :: !intf_types)), "List interfaces of specified type"); ("--broadcast", Arg.Unit (fun () -> broadcast := true), "List broadcast interfaces"); ("--bridgeable", Arg.Unit (fun () -> bridgeable := true), "List bridgeable interfaces"); ("--bondable", Arg.Unit (fun () -> bondable := true), "List bondable interfaces"); @@ -99,8 +99,7 @@ let filter_no_vlan s = false with Not_found -> true -let get_interfaces = - let intf_type = !intf_type in +let get_interfaces_by_type out intf_type = let fltr = if String.length(intf_type) > 0 then filter_from_type intf_type @@ -124,10 +123,16 @@ let get_interfaces = if !no_vlan then List.filter filter_no_vlan res else res in - let res = List.filter filter_section res in - match fltr with - | Some f -> List.filter f res - | None -> res + let add_out = + let res = List.filter filter_section res in + match fltr with + | Some f -> List.filter f res + | None -> res + in List.append out add_out + +let get_interfaces = + let types = List.rev !intf_types in + List.fold_left get_interfaces_by_type [] types let () = let res = get_interfaces in -- cgit v1.2.3 From f85763356b2fbd27e71ef957750046195c2fdabd Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 28 Aug 2025 11:25:39 -0500 Subject: T7758: add missing case to allow repeated option --type --- src/completion/list_interfaces/list_interfaces.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml index a8d5e7bed..739a831a2 100644 --- a/src/completion/list_interfaces/list_interfaces.ml +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -132,7 +132,10 @@ let get_interfaces_by_type out intf_type = let get_interfaces = let types = List.rev !intf_types in - List.fold_left get_interfaces_by_type [] types + if types <> [] then + List.fold_left get_interfaces_by_type [] types + else + get_interfaces_by_type [] "" let () = let res = get_interfaces in -- cgit v1.2.3 From 59dfd78905b548bb22692c45e7aee7453fb00354 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Mon, 6 Oct 2025 21:14:58 +0200 Subject: T7899: virtual-ethernet interfaces support broadcasts The Linux Kernel treats virtual-ethernet interfaces like regular ethernet interfaces. As ethernet interfaces support broadcasts and we have a completion helper for broadcast interfaces, this must be extended for virtual-ethernet interfaces. vyos@vyos# ${vyos_completion_dir}/list_interfaces --broadcast eth0 eth0.10 eth0.201 eth1 eth2 eth3 veth101 veth102 veth201 veth202 --- src/completion/list_interfaces/list_interfaces.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml index 739a831a2..cf6301ed5 100644 --- a/src/completion/list_interfaces/list_interfaces.ml +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -68,7 +68,7 @@ let filter_from_type it = | _ -> Some (filter_from_prefix pre) let filter_broadcast s = - let pattern = {|^(bond|br|tun|vtun|eth|gnv|peth|macsec|vxlan|wwan|wlan)(.*)$|} + let pattern = {|^(bond|br|tun|vtun|eth|gnv|peth|macsec|veth|vxlan|wwan|wlan)(.*)$|} in try let _ = Pcre2.exec ~pat:pattern s in -- cgit v1.2.3 From 4138a081bdfddd09bac348ec74c3ed8b162ba037 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 11 Nov 2025 13:46:47 +0000 Subject: vyos-op-run: T8005: sanitize environment variables to prevent malicious variable injection --- debian/changelog | 6 ++++++ src/vyos_op_run.ml | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/debian/changelog b/debian/changelog index 0db92790a..08a5f8ce2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vyos-utils (0.0.5) unstable; urgency=medium + + * Sanitize the environment in vyos-op-run to prevent malicious variable injection + + -- Daniil Baturin Tue, 11 Nov 2025 13:46:05 +0000 + vyos-utils (0.0.4) unstable; urgency=medium * Add an operational command runner. diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml index 0767ba298..c7e38a753 100644 --- a/src/vyos_op_run.ml +++ b/src/vyos_op_run.ml @@ -319,7 +319,39 @@ let run_external_command opts env command_tmpl = let cmd = render_command opts env command_tmpl in if opts.dry_run then Printf.printf "%s\n%!" cmd else let () = Logs.debug @@ fun m -> m "Command to be executed %s" cmd in - let res = Unix.system cmd in + (* Get the user database entry to populate the basic environment from: + we cannot trust an unprivileged user to supply $SHELL + or allow them to impersonate someone else by setting custom $LOGNAME, etc. + *) + let user_pw_entry = Unix.getpwuid @@ Unix.getuid () in + let make_var name value = Printf.sprintf "%s=%s" name value in + let env = [| + (* A knowingly safe executable lookup path. + Since we do not use /usr/local, we do not need to include that. + Executables in VyOS-specific directories are referred to by absolute paths + in the operational command JSON cache, + so we don't need to include those, either. + *) + make_var "PATH" "/usr/sbin:/usr/bin:/sbin:/bin"; + (* Standard UNIX variables *) + make_var "HOME" user_pw_entry.pw_dir; + make_var "USER" user_pw_entry.pw_name; + make_var "LOGNAME" user_pw_entry.pw_name; + make_var "SHELL" user_pw_entry.pw_shell; + (* VyOS-specific variables *) + make_var "vyos_data_dir" "/usr/share/vyos"; + make_var "vyos_validators_dir" "/usr/libexec/vyos/validators"; + make_var "vyos_completion_dir" "/usr/libexec/vyos/completion"; + make_var "vyos_libexec_dir" "/usr/libexec/vyos"; + make_var "vyos_op_scripts_dir" "/usr/libexec/vyos/op_mode"; + |] + in + let shell = "/bin/sh" in + (* We use execve with an absolute path to Bourne shell rather than execvpe + so that a user trying to do PATH=/bad/place vyos-op-run + cannot achieve anything with that trick. + *) + let res = Unix.execve shell [|shell; "-c"; cmd|] env in match res with | Unix.WEXITED 0 -> () | _ -> -- cgit v1.2.3 From 2b5b541b9682f6e61f85a3ec121e249a098c5a35 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 4 Dec 2025 14:25:00 +0000 Subject: vyos-op-run: T7901: skip permission checks if the user is root --- src/vyos_op_run.ml | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml index c7e38a753..6299a1d9c 100644 --- a/src/vyos_op_run.ml +++ b/src/vyos_op_run.ml @@ -217,6 +217,9 @@ let group_perms_match perms group cmd = perm_list_matches group_perms cmd let is_admin () = + (* If executed by root, skip all permission checks *) + if Unix.geteuid () = 0 then true else + (* Otherwise, check if the user is a VyOS admin *) let admin_group = Unix.getgrnam vyos_admin_group_name in let user_groups = Unix.getgroups () in match (Array.find_opt ((=) admin_group.gr_gid) user_groups) with -- cgit v1.2.3 From 9ccd34149d6369c5daf902e5b9cb824cdbeb6dd4 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Fri, 19 Dec 2025 21:39:10 +0100 Subject: T8110: "list_interfaces --bondable" must not return VLAN subinterfaces Inspired by the regex used for "set interfaces ethernet" adjust the regex to match for interfaces acting as a bond member interface. This has been changed to also include (in addition to eth) lan, eno, ens prefixed physical interfaces. --- src/completion/list_interfaces/list_interfaces.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/completion/list_interfaces/list_interfaces.ml b/src/completion/list_interfaces/list_interfaces.ml index cf6301ed5..b76afd4c1 100644 --- a/src/completion/list_interfaces/list_interfaces.ml +++ b/src/completion/list_interfaces/list_interfaces.ml @@ -84,7 +84,7 @@ let filter_bridgeable s = with Not_found -> false let filter_bondable s = - let pattern = {|^(eth)(.*)$|} + let pattern = {|^(eth|lan|eno|ens)[A-Za-z0-9_-]*$|} in try let _ = Pcre2.exec ~pat:pattern s in -- cgit v1.2.3 From ffde10a863f4da905618139ad1ab03dcf613cfa2 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 23 Feb 2026 19:48:12 +0000 Subject: vyos-op-run: T8301: improve the security of UID check and setuid logic --- src/vyos_op_run.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml index 6299a1d9c..7afa07111 100644 --- a/src/vyos_op_run.ml +++ b/src/vyos_op_run.ml @@ -218,7 +218,7 @@ let group_perms_match perms group cmd = let is_admin () = (* If executed by root, skip all permission checks *) - if Unix.geteuid () = 0 then true else + if Unix.getuid () = 0 then true else (* Otherwise, check if the user is a VyOS admin *) let admin_group = Unix.getgrnam vyos_admin_group_name in let user_groups = Unix.getgroups () in @@ -487,10 +487,10 @@ let () = let () = setup_logging debug in let op_defs = read_command_definitions () in let permissions = read_permissions () in - let () = Unix.setuid 0 in let () = Logs.debug @@ fun m -> m "Executing VyOS command [%s]" (String.concat " " args) in try check_command_permissions permissions args; + Unix.setuid 0; run_vyos_command options ~env:[] ~parent:"" op_defs args with | Permission_error -> -- cgit v1.2.3 From 3aac2bec0dea9a94c9e9848431400b77f906332e Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 24 Feb 2026 14:28:11 +0000 Subject: vyos-op-run: T8306: improve execution logic logging --- src/vyos_op_run.ml | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml index 7afa07111..43e9211b9 100644 --- a/src/vyos_op_run.ml +++ b/src/vyos_op_run.ml @@ -100,12 +100,14 @@ let get_string_field name obj = member name obj |> to_string let read_command_definitions () = + let () = Logs.debug @@ fun m -> m "Reading command definitions from %s" op_def_file in let ic = open_in op_def_file in let data = Yojson.Safe.from_channel ic in let () = close_in ic in data let read_permissions () = + let () = Logs.debug @@ fun m -> m "Reading user permissions from %s" permissions_file in let ic = open_in permissions_file in let data = Yojson.Safe.from_channel ic in let () = close_in ic in @@ -218,13 +220,21 @@ let group_perms_match perms group cmd = let is_admin () = (* If executed by root, skip all permission checks *) - if Unix.getuid () = 0 then true else - (* Otherwise, check if the user is a VyOS admin *) - let admin_group = Unix.getgrnam vyos_admin_group_name in - let user_groups = Unix.getgroups () in - match (Array.find_opt ((=) admin_group.gr_gid) user_groups) with - | Some _ -> true - | None -> false + if Unix.getuid () = 0 then + let () = Logs.debug @@ fun m -> m "The user is root, permission checks will be skipped" in + true + else begin + (* Otherwise, check if the user is a VyOS admin *) + let admin_group = Unix.getgrnam vyos_admin_group_name in + let user_groups = Unix.getgroups () in + match (Array.find_opt ((=) admin_group.gr_gid) user_groups) with + | Some _ -> + let () = Logs.debug @@ fun m -> m "The user is a VyOS admin, permission checks will be skipped" in + true + | None -> + let () = Logs.debug @@ fun m -> m "The user does not have VyOS admin permissions" in + false + end let has_unsafe_characters cmd = (* XXX: this function is highly restrictive now, @@ -244,6 +254,7 @@ let has_unsafe_characters cmd = - 'add system image': requires non-alphanumeric characters for URLs. *) + let () = Logs.debug @@ fun m -> m "Checking the command for unsafe characters" in try let _ = Pcre2.exec ~pat:{|[^a-zA-Z0-9_\-\.\s]|} cmd in let () = @@ -270,10 +281,13 @@ let is_admin_only_command cmd = if p = t then prefix_matches ps ts else false in + let () = Logs.debug @@ fun m -> m "Checking if the command is admin-only" in let res = List.find_opt (fun p -> prefix_matches p cmd) admin_only_commands in match res with | None -> false - | Some _ -> true + | Some _ -> + let () = Logs.debug @@ fun m -> m "Commandis reserved for admins" in + true let check_command_permissions perms cmd = let rec aux perms groups cmd = @@ -283,6 +297,7 @@ let check_command_permissions perms cmd = if group_perms_match perms g cmd then () else aux perms gs cmd in + let () = Logs.debug @@ fun m -> m "Checking if the user is allowed to execute the command" in (* VyOS admins can execute any commands without restrictions *) if is_admin () then () else (* Operators are not allowed to execute commands @@ -321,7 +336,6 @@ let render_command opts env command_tmpl = let run_external_command opts env command_tmpl = let cmd = render_command opts env command_tmpl in if opts.dry_run then Printf.printf "%s\n%!" cmd else - let () = Logs.debug @@ fun m -> m "Command to be executed %s" cmd in (* Get the user database entry to populate the basic environment from: we cannot trust an unprivileged user to supply $SHELL or allow them to impersonate someone else by setting custom $LOGNAME, etc. @@ -354,6 +368,7 @@ let run_external_command opts env command_tmpl = so that a user trying to do PATH=/bad/place vyos-op-run cannot achieve anything with that trick. *) + let () = Logs.debug @@ fun m -> m "Executing Unix command: %s" cmd in let res = Unix.execve shell [|shell; "-c"; cmd|] env in match res with | Unix.WEXITED 0 -> () @@ -487,7 +502,7 @@ let () = let () = setup_logging debug in let op_defs = read_command_definitions () in let permissions = read_permissions () in - let () = Logs.debug @@ fun m -> m "Executing VyOS command [%s]" (String.concat " " args) in + let () = Logs.debug @@ fun m -> m "Executing VyOS command [%s]" options.vyos_command in try check_command_permissions permissions args; Unix.setuid 0; -- cgit v1.2.3 From 6f09f720e45a6bac9a8459b55977fd58050a4e49 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 24 Feb 2026 14:38:17 +0000 Subject: vyos-op-run: T8306: add error messages for system exceptions --- src/vyos_op_run.ml | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/vyos_op_run.ml b/src/vyos_op_run.ml index 43e9211b9..aa88d42fd 100644 --- a/src/vyos_op_run.ml +++ b/src/vyos_op_run.ml @@ -520,6 +520,12 @@ let () = | Incomplete_command -> Printf.fprintf stderr "Incomplete command: %s\n" options.vyos_command; exit 2 + | Sys_error msg -> + Printf.fprintf stderr "System error: %s" msg; + exit 255 + | Unix.Unix_error (err, func, _) -> + Printf.fprintf stderr "Failed to execute Unix call %s: %s" func (Unix.error_message err); + exit 255 | Internal_error msg -> Printf.fprintf stderr "Internal error: %s\n" msg; exit 255 -- cgit v1.2.3 From b67eaf59e5207473d62fc7dd6739ffc7602f8938 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 10 Mar 2026 13:21:01 -0500 Subject: T8269: add option to silence output of individual validators --- src/validate_value.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/validate_value.ml b/src/validate_value.ml index 1d3450fd9..ce4755f2c 100644 --- a/src/validate_value.ml +++ b/src/validate_value.ml @@ -4,6 +4,7 @@ type check = Regex of string | Exec of string | Group of check list let options = ref [] let checks = ref [] let value = ref "" +let silent = ref false let buf = Buffer.create 4096 @@ -36,6 +37,7 @@ let args = [ ("--regex", Arg.String (fun s -> options := (RegexOpt s) :: !options), "Check the value against a regex"); ("--exec", Arg.String (fun s -> options := (ExecOpt s) :: !options), "Check the value against an external command"); ("--grp", Arg.Unit (fun () -> options := (GroupSeparator) :: !options), "Group following arguments, combining results with logical and"); + ("--silent", Arg.Unit (fun () -> silent := true), "Suppress individual validator output"); ("--value", Arg.String (fun s -> value := s), "Value to check"); ] let usage = Printf.sprintf "Usage: %s [OPTIONS] " Sys.argv.(0) @@ -105,5 +107,6 @@ let _ = (* If we got this far, value validation failed. Show the user output from the validators. *) - Buffer.contents buf |> print_endline; + if not !silent then Buffer.contents buf |> print_endline + else (); exit 1 -- cgit v1.2.3 From 040847f155c7701256017c6d307e7758a7d0f320 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Sun, 19 Apr 2026 20:55:09 +0200 Subject: T8532: update URL and bump version of OCaml tools --- src/ocaml/dune-project | 2 +- src/ocaml/vyos-1x.opam | 21 +++++++++++++++++++++ src/ocaml/vyos-utils.opam | 21 --------------------- 3 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 src/ocaml/vyos-1x.opam delete mode 100644 src/ocaml/vyos-utils.opam (limited to 'src') diff --git a/src/ocaml/dune-project b/src/ocaml/dune-project index 8a076e452..621644ea3 100644 --- a/src/ocaml/dune-project +++ b/src/ocaml/dune-project @@ -1,2 +1,2 @@ (lang dune 2.0) -(name vyos-utils) +(name vyos-1x) diff --git a/src/ocaml/vyos-1x.opam b/src/ocaml/vyos-1x.opam new file mode 100644 index 000000000..9da287652 --- /dev/null +++ b/src/ocaml/vyos-1x.opam @@ -0,0 +1,21 @@ +opam-version: "2.0" +name: "vyos-1x" +version: "0.0.4" +synopsis: "VyOS utils" +description: """ +A collection of validators and completion helpers. +""" +maintainer: "Daniil Baturin " +authors: "VyOS maintainers and contributors " +license: "MIT" +homepage: "https://github.com/vyos/vyos-1x" +bug-reports: "https://vyos.dev" +dev-repo: "git+https://github.com/vyos/vyos-1x/" +build: [ + ["dune" "subst"] {pinned} + ["dune" "build" "-p" name] +] +depends: [ + "ocamlfind" {build} + "dune" {build & >= "2.0"} +] diff --git a/src/ocaml/vyos-utils.opam b/src/ocaml/vyos-utils.opam deleted file mode 100644 index fda994424..000000000 --- a/src/ocaml/vyos-utils.opam +++ /dev/null @@ -1,21 +0,0 @@ -opam-version: "2.0" -name: "vyos-utils" -version: "0.0.3" -synopsis: "VyOS utils" -description: """ -A collection of validators and completion helpers. -""" -maintainer: "Daniil Baturin " -authors: "VyOS maintainers and contributors " -license: "MIT" -homepage: "https://github.com/vyos/vyos-utils" -bug-reports: "https://phabricator.vyos.net" -dev-repo: "git+https://github.com/vyos/vyos-utils/" -build: [ - ["dune" "subst"] {pinned} - ["dune" "build" "-p" name] -] -depends: [ - "ocamlfind" {build} - "dune" {build & >= "2.0"} -] -- cgit v1.2.3