diff options
| author | nbd <nbd@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2007-02-26 20:04:04 +0000 | 
|---|---|---|
| committer | nbd <nbd@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2007-02-26 20:04:04 +0000 | 
| commit | 79386b5db780fee3d1469941e640c2cbe2598a5c (patch) | |
| tree | acecda110ad48b648a8f3a3666786581034f00e2 | |
| parent | ddc24aa87ea2f11859e9e56a64210d5a268dbee8 (diff) | |
Integrate basic UCI config file validation support
Needs more testing and validation is not enforced yet
Code contributed by Fraunhofer Fokus
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@6391 3c298f89-4303-0410-b956-a3cf2f4a3e73
| -rwxr-xr-x | package/base-files/files/bin/uci | 23 | ||||
| -rwxr-xr-x | package/base-files/files/etc/functions.sh | 37 | ||||
| -rw-r--r-- | package/base-files/files/lib/config/parse_spec.awk | 255 | ||||
| -rw-r--r-- | package/base-files/files/lib/config/specs/network.spec | 7 | ||||
| -rw-r--r--[-rwxr-xr-x] | package/base-files/files/lib/config/uci.sh | 15 | ||||
| -rw-r--r-- | package/base-files/files/lib/config/validate.sh | 74 | ||||
| -rw-r--r-- | package/base-files/files/lib/config/validate_config.awk | 105 | ||||
| -rw-r--r-- | package/base-files/files/lib/config/validate_spec.awk | 171 | 
8 files changed, 656 insertions, 31 deletions
| diff --git a/package/base-files/files/bin/uci b/package/base-files/files/bin/uci index 9b50380df..f8e08f874 100755 --- a/package/base-files/files/bin/uci +++ b/package/base-files/files/bin/uci @@ -1,8 +1,8 @@  #!/bin/sh  # Shell script for interacting with config files  # -# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> -# Copyright (C) 2006 by Felix Fietkau <nbd@openwrt.org> +# Copyright (C) 2006        Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> +# Copyright (C) 2006,2007	Felix Fietkau <nbd@openwrt.org>  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License as published by @@ -18,8 +18,8 @@  # along with this program; if not, write to the Free Software  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -. /etc/functions.sh -include /lib/config +. $UCI_ROOT/etc/functions.sh +include $UCI_ROOT/lib/config  SEP="[^0-9A-Za-z_]" @@ -101,7 +101,7 @@ do_show() {  		exit 1  	} -	for package in ${PACKAGE:-$(cd /etc/config; ls)}; do +	for package in ${PACKAGE:-$(cd $UCI_ROOT/etc/config; ls)}; do  		SECTION=""  		config_cb() { @@ -136,6 +136,14 @@ do_show() {  	done  } +do_validate() { +	[ "$#" -ne 1 ] && { +		uci_usage validate +		exit 1 +	} +	uci_validate "$1" || exit "$?" +} +  uci_usage() {  	case "$1" in  		show) echo "$0 show [<package>[.<config>]]";; @@ -144,6 +152,7 @@ uci_usage() {  		del) echo "$0 del <package>.<config>[.<option>]";;  		rename) echo "$0 rename <package> <config> <name>";;  		commit) echo "$0 commit [<package> ... ]";; +		validate) echo "$0 validate <package>";;  		*)   			echo "Syntax: $0 <command> <arguments...>"  			echo @@ -153,6 +162,7 @@ uci_usage() {  			uci_usage del  			uci_usage rename  			uci_usage commit +			uci_usage validate   			echo  			exit 1  		;; @@ -164,7 +174,7 @@ if [ $# -eq 0 ] ; then  	exit 0  fi -local CMD="$1" +CMD="$1"  shift  case "$CMD" in  	set) do_set "$@";; @@ -173,6 +183,7 @@ case "$CMD" in  	get) do_get "$@";;  	show) do_show "$@";;  	commit) do_commit "$@";; +	validate) do_validate "$@";;  	*) uci_usage;;  esac  exit 0 diff --git a/package/base-files/files/etc/functions.sh b/package/base-files/files/etc/functions.sh index 5c353a3d0..84121c545 100755 --- a/package/base-files/files/etc/functions.sh +++ b/package/base-files/files/etc/functions.sh @@ -9,6 +9,7 @@ N="  "  _C=0 +NO_EXPORT=1  hotplug_dev() {  	env -i ACTION=$1 INTERFACE=$2 /sbin/hotplug net @@ -19,7 +20,7 @@ append() {  	local value="$2"  	local sep="${3:- }" -	eval "export -n -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\"" +	eval "export ${NO_EXPORT:+-n} -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""  }  reset_cb() { @@ -32,19 +33,19 @@ config () {  	local cfgtype="$1"  	local name="$2" -	CONFIG_NUM_SECTIONS=$(($CONFIG_NUM_SECTIONS + 1)) +	export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=$(($CONFIG_NUM_SECTIONS + 1))  	name="${name:-cfg$CONFIG_NUM_SECTIONS}"  	append CONFIG_SECTIONS "$name"  	config_cb "$cfgtype" "$name" -	CONFIG_SECTION="$name" -	export -n "CONFIG_${CONFIG_SECTION}_TYPE=$cfgtype" +	export ${NO_EXPORT:+-n} CONFIG_SECTION="$name" +	export ${NO_EXPORT:+-n} "CONFIG_${CONFIG_SECTION}_TYPE=$cfgtype"  }  option () {  	local varname="$1"; shift  	local value="$*" -	export -n "CONFIG_${CONFIG_SECTION}_${varname}=$value" +	export ${NO_EXPORT:+-n} "CONFIG_${CONFIG_SECTION}_${varname}=$value"  	option_cb "$varname" "$*"  } @@ -58,12 +59,12 @@ config_rename() {  	for oldvar in `set | grep ^CONFIG_${OLD}_ | \  		sed -e 's/\(.*\)=.*$/\1/'` ; do  		newvar="CONFIG_${NEW}_${oldvar##CONFIG_${OLD}_}" -		eval "export -n \"$newvar=\${$oldvar}\"" +		eval "export ${NO_EXPORT:+-n} \"$newvar=\${$oldvar}\""  		unset "$oldvar"  	done -	CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , $NEW ,")" +	export ${NO_EXPORT:+-n} CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , $NEW ,")" -	[ "$CONFIG_SECTION" = "$OLD" ] && CONFIG_SECTION="$NEW" +	[ "$CONFIG_SECTION" = "$OLD" ] && export ${NO_EXPORT:+-n} CONFIG_SECTION="$NEW"  }  config_unset() { @@ -74,8 +75,8 @@ config_clear() {  	local SECTION="$1"  	local oldvar -	CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , ,")" -	CONFIG_SECTIONS="${SECTION:+$CONFIG_SECTIONS}" +	export ${NO_EXPORT:+-n} CONFIG_SECTIONS="$(echo " $CONFIG_SECTIONS " | sed -e "s, $OLD , ,")" +	export ${NO_EXPORT:+-n} CONFIG_SECTIONS="${SECTION:+$CONFIG_SECTIONS}"  	for oldvar in `set | grep ^CONFIG_${SECTION:+${SECTION}_} | \  		sed -e 's/\(.*\)=.*$/\1/'` ; do  @@ -84,11 +85,11 @@ config_clear() {  }  config_load() { -	local file="/etc/config/$1" +	local file="$UCI_ROOT/etc/config/$1"  	_C=0 -	CONFIG_SECTIONS= -	CONFIG_NUM_SECTIONS=0 -	CONFIG_SECTION= +	export ${NO_EXPORT:+-n} CONFIG_SECTIONS= +	export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=0 +	export ${NO_EXPORT:+-n} CONFIG_SECTION=  	[ -e "$file" ] && {  		. $file @@ -100,7 +101,7 @@ config_load() {  config_get() {  	case "$3" in  		"") eval "echo \"\${CONFIG_${1}_${2}}\"";; -		*)  eval "export -n -- \"$1=\${CONFIG_${2}_${3}}\"";; +		*)  eval "export ${NO_EXPORT:+-n} -- \"$1=\${CONFIG_${2}_${3}}\"";;  	esac  } @@ -108,7 +109,7 @@ config_set() {  	local section="$1"  	local option="$2"  	local value="$3" -	export -n "CONFIG_${section}_${option}=$value" +	export ${NO_EXPORT:+-n} "CONFIG_${section}_${option}=$value"  }  config_foreach() { @@ -155,12 +156,12 @@ strtok() { # <string> { <variable> [<separator>] ... }  		val="${val#$tmp$2}" -		export -n "$1=$tmp"; count=$((count+1)) +		export ${NO_EXPORT:+-n} "$1=$tmp"; count=$((count+1))  		shift 2  	done  	if [ $# -gt 0 -a "$val" ]; then -		export -n "$1=$val"; count=$((count+1)) +		export ${NO_EXPORT:+-n} "$1=$val"; count=$((count+1))  	fi  	return $count diff --git a/package/base-files/files/lib/config/parse_spec.awk b/package/base-files/files/lib/config/parse_spec.awk new file mode 100644 index 000000000..5eabc5ac9 --- /dev/null +++ b/package/base-files/files/lib/config/parse_spec.awk @@ -0,0 +1,255 @@ +# AWK file for parsing uci specification files +# +# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# general: unfortunately, the development was done using gawk providing +#  a different match() functions than e.g. mawk on debian systems +#  - therefore, the script was changed to run on most awk's  +#  - even things like [:space:] are not used +# +# - script  parses the config section definition contained in one  +#   specification file +# global variables: +# * section  - contains the current config section name +# * var      - contains the name of the current config option +# * type     - contains the type of the current config option +# * required - contains the requirements of the current config option +# * optional - contains the optional scope of the current config option +# * vars[]  - array, contains the name of all config options valid within +#	      a certain config section, format: csv +# +# XXX todo: more than one config option with the same in different section +# will clash for the following tables +# * types[] - contains the type of a config option +# * reqs[]  - contains the requirements of a config option +# * opts[]  - contains the optional scope of a config option +# +BEGIN { +	section_count=1 +	section = "" +	simple_types = "int|ip|netmask|string|wep|hostname|mac|port|ports|wpapsk" +} + +# function print_specification +# - prints all information about the created tables containing the +#   specification  +function print_specification() { +	for (section in vars) { +		printf("%s\n",section); +		split(vars[section],arr,",") +		for (idx in arr) { +			printf("\t%s[%s]",arr[idx],types[section "_" arr[idx]]);  +			if (length(reqs[section "_" arr[idx]])) { +				if (reqs[section "_" arr[idx]]==1) { +					printf(",req"); +				}else{ +					printf(", req(%s)", reqs[section "_" arr[idx]]); +				} +			} +			if (length(opts[section "_" arr[idx]])) { +				printf(", opt(%s)", opts[section "_" arr[idx]]); +			} +			printf("\n"); +		} +	} +} + + +function reset_option() { +	# just set global variables parsed on one line back to defaults +	var = "" +	type = "" +	required = "" +	optional = "" +	found = 0 +} + +function store_option() { +	# save all information about a config option parsed from the spec file +	# to the relevant tables for future use + +	# first check minimum requirements for storing information +	if (!length(section)) { +		print STDERR "line " NR ": section definition missing" +		exit 1 +	} +	if (!length(var)) { +		print STDERR "line " NR ": invalid config option name" +		exit 1 +	} +	if (!length(type)) { +		print STDERR "line " NR ": invalid config option type" +		exit 1 +	} + +	# add config options to the names of options available for this +	# section +	if (exists[section]!=1) { +		section_names[section_count] = section +		section_count++ +		exists[section] = 1 +		vars[section] = var +	} else { +		vars[section] = vars[section] "," var +	} +	 +	# save the type, the requirements and the optional scope of the  +	# config option +	types[section "_" var] = type +	reqs[section "_" var] = required +	opts[section "_" var] = optional +} + +/^declare -x|^export/ {  +	sub(/^declare -x /,"") +	sub(/^export /,"") +	split($0,arr,"=") +	val=substr(arr[2],2,length(arr[2])-2) +	ENVIRON[arr[1]] = val +	next +} + +# main parsing function +# this is done in one function block to allow multiple semicolon separated +# definitions on one line +{ +	# replace leading/trailing white space +	gsub("^[ \t\n]+",""); +	gsub("[ \t\n]+$",""); + +	# comments are removed +	# XXX todo: check for quoted comments?? +	if (match($0,/[^#]*/)) { +		rest=substr($0,RSTART,RLENGTH) +	} else { +		rest=$0 +	} + +	# match the config section "<section> {" +	if (match(rest,/^[^ \t\n{]+[ \t\n]*\{/)) { +		match(rest,/^[^ \t\n{]+/) +		section = substr(rest,RSTART,RLENGTH) +		rest=substr($0,RSTART+RLENGTH); +		match(rest,/[ \t\n]*\{/) +		rest=substr(rest,RSTART+RLENGTH) +		# check for array indication +		if (match(section,/\[[ \t\n]*\]/)) { +			section=substr(section,1,RSTART-1) +			multiple[section] = 1 +		} else { +			multiple[section] = 0 +		} +	} + +	reset_option() + +	# parse the remaing line as long as there is something to parse +	while (rest ~ "[^ \t\n}]+") { +		found = 0 + +		# get option name and option type +		# first, check for "simple" datatype definitions +		if (match(rest,"[^: \t\n]+[ \t\n]*:[ \t\n]*(" \ +		                        simple_types ")")){ +			match(rest,"[^: \t\n]+") +			var=substr(rest,RSTART,RLENGTH) +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,"[ \t\n]*:[ \t\n]*") +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,"(" simple_types ")") +			type=substr(rest,RSTART,RLENGTH) +			rest = substr(rest,RSTART+RLENGTH) +			found = 1 +		# next, check for enum definitions +		} else if (match(rest,/[^: \t\n]+[ \t\n]*:[ \t\n]*enum\([^\)]+\)/ )) { +			match(rest,"[^: \t\n]+") +			var=substr(rest,RSTART,RLENGTH) +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,/[ \t\n]*:[ \t\n]*enum\(/) +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,/[^\)]+/) +			type="enum," substr(rest,RSTART,RLENGTH) +			rest = substr(rest,RSTART+RLENGTH+1) +			found=1 +		}			 + +		# after the name and the type,  +		# get the option requirements/scope +		if (match(rest,/[^,]*,[ \t\n]*required\[[^]]+\]/)) { +			match(rest,"[^,]*") +			save=substr(rest,RSTART,RLENGTH) +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,/,[ \t\n]*required\[/); +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,/[^]]+\]/) +			required=substr(rest,RSTART,RLENGTH-1) +			save=save substr(rest,RSTART+RLENGTH) +			rest=save +			found=1 +		} else if (match(rest,/[^,]*,[ \t\n]*required/)) { +			match(rest,"[^,]*") +			save=substr(rest,RSTART,RLENGTH) +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,",[ \t\n]*required"); +			rest=substr(rest,RSTART+RLENGTH) +			required=1 +			save=save substr(rest,RSTART+RLENGTH) +			rest=save +			found=1 +		} +		if (match(rest,/[^,]*,[ \t\n]*optional\[[^]]+\]/)) { +			match(rest,"[^,]*") +			save=substr(rest,RSTART,RLENGTH) +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,/,[ \t\n]*optional\[/); +			rest=substr(rest,RSTART+RLENGTH) +			match(rest,/[^]]+\]/) +			optional=substr(rest,RSTART,RLENGTH-1) +			save=save substr(rest,RSTART+RLENGTH) +			rest=save +			found=1 +		} +	 +		# if the remaining line contains a semicolon, complete the +		# specification of the config options +		if (match(rest, "^[ \t\n]*;(.*)")) { +			match(rest,"^[ \t\n]*;") +			rest=substr(rest,RSTART+RLENGTH) +			if (found==1) { +				store_option() +			} +			reset_option() + +		# if nothing matched on this line, clear the rest +		} else if (!found) { +			rest = "" +		} +	} + +	# after the line is pared, store the configuration option in the +	# table if any has been defined +	if (length(var)) { +		store_option() +		reset_option() +	} +	# close the section if the line contained a closing section bracket,  +	# XXX todo: check if this has to be done more intelligent +	if ($0 ~ /\}/) { +		section="" +	} +} diff --git a/package/base-files/files/lib/config/specs/network.spec b/package/base-files/files/lib/config/specs/network.spec new file mode 100644 index 000000000..3d19b5cf4 --- /dev/null +++ b/package/base-files/files/lib/config/specs/network.spec @@ -0,0 +1,7 @@ +interface[] { +	proto: string, required; +	ipaddr: ip, required[proto=static]; +	netmask: ip, required[proto=static]; +	gateway: ip; +	dns: ip; +} diff --git a/package/base-files/files/lib/config/uci.sh b/package/base-files/files/lib/config/uci.sh index 12795d813..43bb981cc 100755..100644 --- a/package/base-files/files/lib/config/uci.sh +++ b/package/base-files/files/lib/config/uci.sh @@ -1,8 +1,8 @@  #!/bin/sh  # Shell script defining macros for manipulating config files  # -# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> -# Copyright (C) 2006 by Felix Fietkau <nbd@openwrt.org> +# Copyright (C) 2006        Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> +# Copyright (C) 2006,2007   Felix Fietkau <nbd@openwrt.org>  #  # This program is free software; you can redistribute it and/or modify  # it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ uci_load() {  uci_do_update() {  	local FILENAME="$1"  	local UPDATE="$2" -	awk -f /lib/config/uci-update.awk -f - <<EOF +	awk -f $UCI_ROOT/lib/config/uci-update.awk -f - <<EOF  BEGIN {  	config = read_file("$FILENAME")  	$UPDATE @@ -94,9 +94,10 @@ uci_remove() {  uci_commit() {  	local PACKAGE="$1"  	local PACKAGE_BASE="$(basename "$PACKAGE")" -	 +  	mkdir -p /tmp/.uci -	lock "/tmp/.uci/$PACKAGE_BASE.lock" +	LOCK=`which lock` || LOCK=: +	$LOCK "/tmp/.uci/$PACKAGE_BASE.lock"  	[ -f "/tmp/.uci/$PACKAGE_BASE" ] && (  		updatestr="" @@ -128,13 +129,13 @@ uci_commit() {  		}  		config_load "$PACKAGE" -		CONFIG_FILENAME="${CONFIG_FILENAME:-$ROOT/etc/config/$PACKAGE_BASE}" +		CONFIG_FILENAME="${CONFIG_FILENAME:-$UCI_ROOT/etc/config/$PACKAGE_BASE}"  		uci_do_update "$CONFIG_FILENAME" "$updatestr" > "/tmp/.uci/$PACKAGE_BASE.new" && {  			mv -f "/tmp/.uci/$PACKAGE_BASE.new" "$CONFIG_FILENAME" && \  			rm -f "/tmp/.uci/$PACKAGE_BASE"  		}   	) -	lock -u "/tmp/.uci/$PACKAGE_BASE.lock" +	$LOCK -u "/tmp/.uci/$PACKAGE_BASE.lock"  } diff --git a/package/base-files/files/lib/config/validate.sh b/package/base-files/files/lib/config/validate.sh new file mode 100644 index 000000000..e16319dad --- /dev/null +++ b/package/base-files/files/lib/config/validate.sh @@ -0,0 +1,74 @@ +# Shell script defining validating configuration macros +# +# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> +# Copyright (C) 2007 by Felix Fietkau <nbd@openwrt.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +validate_spec() { +	export | grep 'CONFIG_' | cat - "$@" | awk \ +		-f $UCI_ROOT/lib/config/validate_config.awk \ +		-f $UCI_ROOT/lib/config/parse_spec.awk \ +		-f $UCI_ROOT/lib/config/validate_spec.awk +} + +validate_config_cb () { +	local TYPE +	local res= +	 +	[ -n "${CONFIG_SECTION}" ] || return 0 +	 +	config_get TYPE ${CONFIG_SECTION} TYPE +	[ -n "$TYPE" ] || return 0 +	 +	if type validate_${PACKAGE}_${TYPE} >/dev/null 2>&1; then +		validate_${PACKAGE}_${TYPE} +		res="$?" +	else  +		if [ -f $UCI_ROOT/lib/config/specs/${PACKAGE}.spec ]; then +			# no special defined, use default one +			validate_spec $UCI_ROOT/lib/config/specs/${PACKAGE}.spec +			res="$?" +		fi +	fi +	 +	VALIDATE_RES="${VALIDATE_RES:-$res}" +} + +uci_validate() {( +	PACKAGE="$1" +	FILE="$2" +	VALIDATE_RES= + +	[ -z "${PACKAGE}" ] && { +		echo "Error: no package defined" +		return 1 +	} + +	reset_cb +	config_cb() { +		validate_config_cb "$@" +	} +	unset NO_EXPORT +	if [ -n "$FILE" ]; then +		. "$FILE" +		config +	else +		config_load "$1" +	fi + +	return ${VALIDATE_RES:-0} +)} diff --git a/package/base-files/files/lib/config/validate_config.awk b/package/base-files/files/lib/config/validate_config.awk new file mode 100644 index 000000000..053694efd --- /dev/null +++ b/package/base-files/files/lib/config/validate_config.awk @@ -0,0 +1,105 @@ +# AWK file for validating uci specification files +# +# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +function is_int(value) { +	valid = 1 +	if (value !~ /^[0-9]*$/) { valid = 0 } +	return valid +} + +function is_netmask(value) { +	return is_ip(value) +} + +function is_ip(value) { +	valid = 1 +	if ((value != "") && (value !~ /^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$/)) valid = 0 +	else { +		split(value, ipaddr, "\\.") +		for (i = 1; i <= 4; i++) { +			if ((ipaddr[i] < 0) || (ipaddr[i] > 255)) valid = 0 +		} +	} +	return valid +} + +function is_wep(value) { +	valid = 1 +	if (value !~ /^[0-9A-Fa-f]*$/) { +		valid = 0 +	} else if ((length(value) != 0) && (length(value) != 10) && (length(value) != 26)) { +		valid = 0 +	} else if (value ~ /0$/) { +		valid = 0 +	} +	return valid +} + +function is_hostname(value) { +	valid = 1 +	if (value !~ /^[0-9a-zA-z\.\-]*$/) { +		valid = 0 +	} +	return valid; +} + +function is_string(value) { +	return 1; +} + +function is_mac(value) { +	valid = 1 +	if ((value != "") && (value !~ /^[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]$/)) { +		valid = 0 +	} +	return valid +} + +function is_port(value) { +	valid = 1 +	if (value !~ /^[0-9]*$/) { +		valid = 0 +	} +	return valid +} + +function is_ports(value) { +	valid = 1 +	n = split(value ",", ports, ",") +	for (i = 1; i <= n; i++) { +		if ((ports[i] !~ /^[0-9]*$/) && (ports[i] !~ /^[0-9][0-9]*-[0-9][0-9]*$/)) { +			valid = 0 +		} +	} +	return valid +} + +function is_wpapsk(value) { +	valid = 1 +	if (length(value) > 64) { +		valid = 0 +	} +	if ((length(value) != 0) && (length(value) < 8)) { +		valid = 0 +	} +	if ((length(value) == 64) && (value ~ /[^0-9a-fA-F]/)) { +		valid = 0 +	} +	return valid +} + diff --git a/package/base-files/files/lib/config/validate_spec.awk b/package/base-files/files/lib/config/validate_spec.awk new file mode 100644 index 000000000..0816c0829 --- /dev/null +++ b/package/base-files/files/lib/config/validate_spec.awk @@ -0,0 +1,171 @@ +# AWK file for validating uci specification files +# +# Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de> +# Copyright (C) 2007 by Felix Fietkau <nbd@openwrt.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# general: unfortunately, the development was done using gawk providing +#  a different match() functions than e.g. mawk on debian systems +#  - therefore, the script was changed to run on most awk's  +#  - even things like [:space:] are not used +# +# - script  parses the config section definition contained in one  +#   specification file +# global variables: +# * section  - contains the current config section name +# * var      - contains the name of the current config option +# * type     - contains the type of the current config option +# * required - contains the requirements of the current config option +# * optional - contains the optional scope of the current config option +# * vars[]  - array, contains the name of all config options valid within +#	      a certain config section, format: csv +# +# XXX todo: more than one config option with the same in different section +# will clash for the following tables +# * types[] - contains the type of a config option +# * reqs[]  - contains the requirements of a config option +# * opts[]  - contains the optional scope of a config option +# + +# - check requirement validates, if the config option is required in +#   the config section type and if so, if it is defined +# - the functions exits with error in case of non-conforming  +#   behaviour +# XXX todo: use return instead of exit +# +function check_requirements(vsec,var) { +	# check, if config option is required in all cases +	if (reqs[vsec "_" var] == 1) { +		# option is always required, is it defined? +		if (!length(ENVIRON["CONFIG_" vsec "_" var])) { +			print STDERR "Error: missing config option " var " in " vsec +			exit 1 +		} + +	# check, if config option is required only when other options +	# have certain values +	} else if (length(reqs[vsec "_" var])) { +		# - check all requirements, e.g. proto=static,proto=pptp +		# - note, that the required flag is tiggered if at least one +		#   of the conditions is met +		split(reqs[vsec "_" var],arr,","); +		for (idx in arr) { +			# parse the condition space tolerant +			if (!match(arr[idx],"^[ \t\n]*[^ \t\n=]+"\ +				"[ \t\n]*=.+")) { +				print STDERR "Error: invalid requirement "\ +					"in spec file for " var " : " arr[idx] +				exit 1 +			} +			# get the name of the variable +			match(arr[idx],"[^ \t\n=]+"); +			name=substr(arr[idx],RSTART,RLENGTH) +			mrest=substr(arr[idx],RSTART+RLENGTH) +			# get the spaces +			match(mrest,"[ \t\n]*=[ \t\n]*") +			val=substr(mrest,RSTART+RLENGTH) +			# check the condition +			if (ENVIRON["CONFIG_" vsec "_" name] == val) { +				# condition is met, check requirement +				if (!length(ENVIRON["CONFIG_" vsec "_" var])) { +					print STDERR "Error: missing config " \ +						"option " var " in " vsec  +					exit 1 +				} +			} +		} +	} +} + +# is_valid just returns true(1)/false(0) if the +# given value is conform with the type definition  +# NOTE: this function needs the type validating function from +# validate_config.awk +# +function is_valid(type,value) { + +	# the enum type contains a definition of all allowed values as csv +	# e.g. enum,alpha,beta,gamma +	if (type ~ "enum" ) { +		split(type,tarr,",") +		for (num in tarr) { +			if (num > 0) { +				gsub("^[ \t\n]*","",tarr[num]); +				gsub("[ \t\n]*$","",tarr[num]); +				if (tarr[num] == value) { +					return 1 +				}	 +			} +		} +		return 0; +	} + +	# all other types are checked as defined in the former validate.awk +	if (type ~ "int") return is_int(value) +	if (type ~ "ip" ) return is_ip(value) +	if (type ~ "netmask" ) return is_netmask(value) +	if (type ~ "string" ) return is_string(value) +	if (type ~ "wep" ) return is_wep(value) +	if (type ~ "hostname" ) return is_hostname(value) +	if (type ~ "mac" ) return is_mac(value) +	if (type ~ "port" ) return is_port(value) +	if (type ~ "ports" ) return is_ports(value) +	if (type ~ "wpapsk" ) return is_wpapsk(value) +} + +# validate_config compares the specification as parsed from the spec file +# with the environment variables +# CONFIG_SECTION contains the relevant config section name, e.g. wan +# CONFIG_<section>_TYPE contains the type of the config, e.g. interface +# CONFIG_<section>_<var> contains the value of the config option <var> +# +function validate_config() { +	# get the config section name +	vname=ENVIRON["CONFIG_SECTION"] +	if (!length(vname)) { +		print STDERR "Error: no current configuration" +		exit 1 +	} +	# get the config section type +	vsec=ENVIRON["CONFIG_" vname "_TYPE"] +	if (!length(vsec)) { +		print STDERR "Error: section " vsec " not found" +		exit 1 +	} + +	# loop through all config options specified for this section type +	split(vars[vsec],options,",") +	for (oidx in options) { +		# first, look for all required attributes +		var=options[oidx] +		check_requirements(vname,var) + +		# next look at each option and validate it +		val=ENVIRON["CONFIG_" vname "_" var] +		if (length(val)) { +			if (!is_valid(types[vsec "_" var],val)) { +				print "Error: type validation error for '" var "' in section '" vname "'" +				exit 1 +			} +		} +	} +} + + +END { +	validate_config() +} | 
