diff --git a/contrib/byacc/test/btyacc/err_syntax22.error b/contrib/byacc/test/btyacc/err_syntax22.error new file mode 100644 index 000000000000..8622aa73f598 --- /dev/null +++ b/contrib/byacc/test/btyacc/err_syntax22.error @@ -0,0 +1 @@ +YACC: e - line 17 of "./err_syntax22.y", $2 (recur) is untyped diff --git a/contrib/byacc/test/yacc/err_syntax22.error b/contrib/byacc/test/yacc/err_syntax22.error new file mode 100644 index 000000000000..8622aa73f598 --- /dev/null +++ b/contrib/byacc/test/yacc/err_syntax22.error @@ -0,0 +1 @@ +YACC: e - line 17 of "./err_syntax22.y", $2 (recur) is untyped diff --git a/contrib/unbound/doc/unbound.doxygen b/contrib/unbound/doc/unbound.doxygen new file mode 100644 index 000000000000..7222dbc274e9 --- /dev/null +++ b/contrib/unbound/doc/unbound.doxygen @@ -0,0 +1,1656 @@ +# Doxyfile 1.7.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = unbound + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.1 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +#SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +#SHOW_DIRECTORIES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = NO + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = ./build \ + ./compat \ + ./contrib \ + util/configparser.c \ + util/configparser.h \ + util/configlexer.c \ + util/locks.h \ + pythonmod/doc \ + pythonmod/examples \ + pythonmod/unboundmodule.py \ + pythonmod/interface.h \ + pythonmod/ubmodule-msg.py \ + pythonmod/ubmodule-tst.py \ + unboundmodule.py \ + libunbound/python/unbound.py \ + libunbound/python/libunbound_wrap.c \ + libunbound/python/doc \ + libunbound/python/examples \ + ./ldns-src \ + README.md \ + doc/control_proto_spec.txt \ + doc/requirements.txt + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +#COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# If the HTML_TIMESTAMP tag is set to YES then the generated HTML +# documentation will contain the timesstamp. + +HTML_TIMESTAMP = NO + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +#HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +#HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +#HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +#HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +#DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +#DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +#USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +#EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +#FORMULA_TRANSPARENT = YES + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvances is that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +#PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = YES + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +#XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +#XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = *.h + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = DOXYGEN + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = ATTR_UNUSED + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +#PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +#MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +#DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +#DOT_FONTNAME = FreeSans.ttf + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/contrib/wpa/CONTRIBUTIONS b/contrib/wpa/CONTRIBUTIONS index b2064dc83443..1b4caf7ac811 100644 --- a/contrib/wpa/CONTRIBUTIONS +++ b/contrib/wpa/CONTRIBUTIONS @@ -143,7 +143,7 @@ The license terms used for hostap.git files Modified BSD license (no advertisement clause): -Copyright (c) 2002-2022, Jouni Malinen and contributors +Copyright (c) 2002-2021, Jouni Malinen and contributors All Rights Reserved. Redistribution and use in source and binary forms, with or without diff --git a/contrib/wpa/COPYING b/contrib/wpa/COPYING index 7ca30301e28b..5d0115c9ca6f 100644 --- a/contrib/wpa/COPYING +++ b/contrib/wpa/COPYING @@ -1,7 +1,7 @@ wpa_supplicant and hostapd -------------------------- -Copyright (c) 2002-2022, Jouni Malinen and contributors +Copyright (c) 2002-2019, Jouni Malinen and contributors All Rights Reserved. diff --git a/contrib/wpa/README b/contrib/wpa/README index 1470c4f23582..a9f806967bf9 100644 --- a/contrib/wpa/README +++ b/contrib/wpa/README @@ -1,7 +1,7 @@ wpa_supplicant and hostapd -------------------------- -Copyright (c) 2002-2022, Jouni Malinen and contributors +Copyright (c) 2002-2019, Jouni Malinen and contributors All Rights Reserved. These programs are licensed under the BSD license (the one with diff --git a/contrib/wpa/hostapd/ChangeLog b/contrib/wpa/hostapd/ChangeLog index 279298e4d4d4..34a8a081879d 100644 --- a/contrib/wpa/hostapd/ChangeLog +++ b/contrib/wpa/hostapd/ChangeLog @@ -1,48 +1,5 @@ ChangeLog for hostapd -2022-01-16 - v2.10 - * SAE changes - - improved protection against side channel attacks - [https://w1.fi/security/2022-1/] - - added option send SAE Confirm immediately (sae_config_immediate=1) - after SAE Commit - - added support for the hash-to-element mechanism (sae_pwe=1 or - sae_pwe=2) - - fixed PMKSA caching with OKC - - added support for SAE-PK - * EAP-pwd changes - - improved protection against side channel attacks - [https://w1.fi/security/2022-1/] - * fixed WPS UPnP SUBSCRIBE handling of invalid operations - [https://w1.fi/security/2020-1/] - * fixed PMF disconnection protection bypass - [https://w1.fi/security/2019-7/] - * added support for using OpenSSL 3.0 - * fixed various issues in experimental support for EAP-TEAP server - * added configuration (max_auth_rounds, max_auth_rounds_short) to - increase the maximum number of EAP message exchanges (mainly to - support cases with very large certificates) for the EAP server - * added support for DPP release 2 (Wi-Fi Device Provisioning Protocol) - * extended HE (IEEE 802.11ax) support, including 6 GHz support - * removed obsolete IAPP functionality - * fixed EAP-FAST server with TLS GCM/CCM ciphers - * dropped support for libnl 1.1 - * added support for nl80211 control port for EAPOL frame TX/RX - * fixed OWE key derivation with groups 20 and 21; this breaks backwards - compatibility for these groups while the default group 19 remains - backwards compatible; owe_ptk_workaround=1 can be used to enabled a - a workaround for the group 20/21 backwards compatibility - * added support for Beacon protection - * added support for Extended Key ID for pairwise keys - * removed WEP support from the default build (CONFIG_WEP=y can be used - to enable it, if really needed) - * added a build option to remove TKIP support (CONFIG_NO_TKIP=y) - * added support for Transition Disable mechanism to allow the AP to - automatically disable transition mode to improve security - * added support for PASN - * added EAP-TLS server support for TLS 1.3 (disabled by default for now) - * a large number of other fixes, cleanup, and extensions - 2019-08-07 - v2.9 * SAE changes - disable use of groups using Brainpool curves diff --git a/contrib/wpa/hostapd/README b/contrib/wpa/hostapd/README index 739c964d44d8..1f30d7ea39fa 100644 --- a/contrib/wpa/hostapd/README +++ b/contrib/wpa/hostapd/README @@ -2,7 +2,7 @@ hostapd - user space IEEE 802.11 AP and IEEE 802.1X/WPA/WPA2/EAP Authenticator and RADIUS authentication server ================================================================ -Copyright (c) 2002-2022, Jouni Malinen and contributors +Copyright (c) 2002-2019, Jouni Malinen and contributors All Rights Reserved. This program is licensed under the BSD license (the one with diff --git a/contrib/wpa/hostapd/hostapd_cli.c b/contrib/wpa/hostapd/hostapd_cli.c index 2609121116b5..0e7fdd6bccfb 100644 --- a/contrib/wpa/hostapd/hostapd_cli.c +++ b/contrib/wpa/hostapd/hostapd_cli.c @@ -1,6 +1,6 @@ /* * hostapd - command line interface for hostapd daemon - * Copyright (c) 2004-2022, Jouni Malinen + * Copyright (c) 2004-2019, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -21,7 +21,7 @@ static const char *const hostapd_cli_version = "hostapd_cli v" VERSION_STR "\n" -"Copyright (c) 2004-2022, Jouni Malinen and contributors"; +"Copyright (c) 2004-2019, Jouni Malinen and contributors"; static struct wpa_ctrl *ctrl_conn; static int hostapd_cli_quit = 0; diff --git a/contrib/wpa/hostapd/main.c b/contrib/wpa/hostapd/main.c index c9ec38d19f88..4f2d1f21659e 100644 --- a/contrib/wpa/hostapd/main.c +++ b/contrib/wpa/hostapd/main.c @@ -1,6 +1,6 @@ /* * hostapd / main() - * Copyright (c) 2002-2022, Jouni Malinen + * Copyright (c) 2002-2019, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -454,7 +454,7 @@ static void show_version(void) "hostapd v%s\n" "User space daemon for IEEE 802.11 AP management,\n" "IEEE 802.1X/WPA/WPA2/EAP/RADIUS Authenticator\n" - "Copyright (c) 2002-2022, Jouni Malinen " + "Copyright (c) 2002-2019, Jouni Malinen " "and contributors\n", VERSION_STR); } diff --git a/contrib/wpa/hs20/server/Makefile b/contrib/wpa/hs20/server/Makefile new file mode 100644 index 000000000000..0cab6d6b010a --- /dev/null +++ b/contrib/wpa/hs20/server/Makefile @@ -0,0 +1,42 @@ +ALL=hs20_spp_server + +include ../../src/build.rules + +CFLAGS += -I../../src +CFLAGS += -I../../src/utils +CFLAGS += -I../../src/crypto + +LIBS += -lsqlite3 + +# Using glibc < 2.17 requires -lrt for clock_gettime() +LIBS += -lrt + +ifndef CONFIG_NO_GITVER +# Add VERSION_STR postfix for builds from a git repository +ifeq ($(wildcard ../../.git),../../.git) +GITVER := $(shell git describe --dirty=+) +ifneq ($(GITVER),) +CFLAGS += -DGIT_VERSION_STR_POSTFIX=\"-$(GITVER)\" +endif +endif +endif + +OBJS=spp_server.o +OBJS += hs20_spp_server.o +OBJS += ../../src/utils/xml-utils.o +OBJS += ../../src/utils/base64.o +OBJS += ../../src/utils/common.o +OBJS += ../../src/utils/os_unix.o +OBJS += ../../src/utils/wpa_debug.o +OBJS += ../../src/crypto/md5-internal.o +CFLAGS += $(shell xml2-config --cflags) +LIBS += $(shell xml2-config --libs) +OBJS += ../../src/utils/xml_libxml2.o + +_OBJS_VAR := OBJS +include ../../src/objs.mk +hs20_spp_server: $(OBJS) + $(LDO) $(LDFLAGS) -o hs20_spp_server $(OBJS) $(LIBS) + +clean: common-clean + rm -f core *~ diff --git a/contrib/wpa/hs20/server/ca/clean.sh b/contrib/wpa/hs20/server/ca/clean.sh new file mode 100755 index 000000000000..c72dcbda45e9 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/clean.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +for i in server-client server server-revoked user ocsp; do + rm -f $i.csr $i.key $i.pem +done + +rm -f openssl.cnf.tmp +if [ -d demoCA ]; then + rm -r demoCA +fi +rm -f ca.pem logo.asn1 logo.der server.der ocsp-server-cache.der +rm -f my-openssl.cnf my-openssl-root.cnf +#rm -r rootCA diff --git a/contrib/wpa/hs20/server/ca/est-csrattrs.cnf b/contrib/wpa/hs20/server/ca/est-csrattrs.cnf new file mode 100644 index 000000000000..b50ea00d0b77 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/est-csrattrs.cnf @@ -0,0 +1,17 @@ +asn1 = SEQUENCE:attrs + +[attrs] +#oid1 = OID:challengePassword +attr1 = SEQUENCE:extreq +oid2 = OID:sha256WithRSAEncryption + +[extreq] +oid = OID:extensionRequest +vals = SET:extreqvals + +[extreqvals] + +oid1 = OID:macAddress +#oid2 = OID:imei +#oid3 = OID:meid +#oid4 = OID:DevId diff --git a/contrib/wpa/hs20/server/ca/est-csrattrs.sh b/contrib/wpa/hs20/server/ca/est-csrattrs.sh new file mode 100755 index 000000000000..0b73a0408284 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/est-csrattrs.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +openssl asn1parse -genconf est-csrattrs.cnf -out est-csrattrs.der -oid hs20.oid +base64 est-csrattrs.der > est-attrs.b64 diff --git a/contrib/wpa/hs20/server/ca/hs20.oid b/contrib/wpa/hs20/server/ca/hs20.oid new file mode 100644 index 000000000000..a829ff29bf44 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/hs20.oid @@ -0,0 +1,7 @@ +1.3.6.1.1.1.1.22 macAddress +1.2.840.113549.1.9.14 extensionRequest +1.3.6.1.4.1.40808.1.1.1 id-wfa-hotspot-friendlyName +1.3.6.1.4.1.40808.1.1.2 id-kp-HS2.0Auth +1.3.6.1.4.1.40808.1.1.3 imei +1.3.6.1.4.1.40808.1.1.4 meid +1.3.6.1.4.1.40808.1.1.5 DevId diff --git a/contrib/wpa/hs20/server/ca/ocsp-req.sh b/contrib/wpa/hs20/server/ca/ocsp-req.sh new file mode 100755 index 000000000000..931a20696d02 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/ocsp-req.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +for i in *.pem; do + echo "===[ $i ]===================" + openssl ocsp -text -CAfile ca.pem -verify_other demoCA/cacert.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ + +# openssl ocsp -text -CAfile rootCA/cacert.pem -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ + +# openssl ocsp -text -CAfile rootCA/cacert.pem -verify_other demoCA/cacert.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ +# openssl ocsp -text -CAfile rootCA/cacert.pem -VAfile ca.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ +done diff --git a/contrib/wpa/hs20/server/ca/ocsp-responder-ica.sh b/contrib/wpa/hs20/server/ca/ocsp-responder-ica.sh new file mode 100755 index 000000000000..116c6e1c3d01 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/ocsp-responder-ica.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +openssl ocsp -index demoCA/index.txt -port 8888 -nmin 5 -rsigner demoCA/cacert.pem -rkey demoCA/private/cakey-plain.pem -CA demoCA/cacert.pem -resp_no_certs -text diff --git a/contrib/wpa/hs20/server/ca/ocsp-responder.sh b/contrib/wpa/hs20/server/ca/ocsp-responder.sh new file mode 100755 index 000000000000..620947d01af0 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/ocsp-responder.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +openssl ocsp -index demoCA/index.txt -port 8888 -nmin 5 -rsigner ocsp.pem -rkey ocsp.key -CA demoCA/cacert.pem -text -ignore_err diff --git a/contrib/wpa/hs20/server/ca/ocsp-update-cache.sh b/contrib/wpa/hs20/server/ca/ocsp-update-cache.sh new file mode 100755 index 000000000000..f2b23250cadd --- /dev/null +++ b/contrib/wpa/hs20/server/ca/ocsp-update-cache.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# NOTE: You may need to replace 'localhost' with your OCSP server hostname. +openssl ocsp \ + -no_nonce \ + -CAfile ca.pem \ + -verify_other demoCA/cacert.pem \ + -issuer demoCA/cacert.pem \ + -cert server.pem \ + -url http://localhost:8888/ \ + -respout ocsp-server-cache.der diff --git a/contrib/wpa/hs20/server/ca/openssl-root.cnf b/contrib/wpa/hs20/server/ca/openssl-root.cnf new file mode 100644 index 000000000000..5bc50be1dbc9 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/openssl-root.cnf @@ -0,0 +1,125 @@ +# OpenSSL configuration file for Hotspot 2.0 PKI (Root CA) + +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +#logotypeoid=1.3.6.1.5.5.7.1.12 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./rootCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +input_password = @PASSWORD@ +output_password = @PASSWORD@ + +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 + +localityName = Locality Name (eg, city) +localityName_default = Tuusula + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = WFA Hotspot 2.0 + +##organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = +#@OU@ + +commonName = Common Name (e.g. server FQDN or YOUR name) +#@CN@ +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] + +[ v3_req ] + +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName=DNS:example.com,DNS:another.example.com + +[ v3_ca ] + +# Hotspot 2.0 PKI requirements +subjectKeyIdentifier=hash +basicConstraints = critical,CA:true +keyUsage = critical, cRLSign, keyCertSign + +[ crl_ext ] + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ v3_OCSP ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning diff --git a/contrib/wpa/hs20/server/ca/openssl.cnf b/contrib/wpa/hs20/server/ca/openssl.cnf new file mode 100644 index 000000000000..61410138340f --- /dev/null +++ b/contrib/wpa/hs20/server/ca/openssl.cnf @@ -0,0 +1,200 @@ +# OpenSSL configuration file for Hotspot 2.0 PKI (Intermediate CA) + +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +#logotypeoid=1.3.6.1.5.5.7.1.12 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = ext_client # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +copy_extensions = copy + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = supplied +stateOrProvinceName = optional +organizationName = supplied +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_osu_server ] +countryName = match +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = supplied +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +input_password = @PASSWORD@ +output_password = @PASSWORD@ + +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = FI +countryName_min = 2 +countryName_max = 2 + +localityName = Locality Name (eg, city) +localityName_default = Tuusula + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = @DOMAIN@ + +##organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = +#@OU@ + +commonName = Common Name (e.g. server FQDN or YOUR name) +#@CN@ +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] + +[ v3_ca ] + +# Hotspot 2.0 PKI requirements +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, cRLSign, keyCertSign +authorityInfoAccess = OCSP;URI:@OCSP_URI@ +# For SP intermediate CA +#subjectAltName=critical,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:engExample OSU +#nameConstraints=permitted;DNS:.@DOMAIN@ +#1.3.6.1.5.5.7.1.12=ASN1:SEQUENCE:LogotypeExtn + +[ v3_osu_server ] + +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, keyEncipherment +#@ALTNAME@ + +#logotypeoid=ASN1:SEQUENCE:LogotypeExtn +1.3.6.1.5.5.7.1.12=ASN1:SEQUENCE:LogotypeExtn +[LogotypeExtn] +communityLogos=EXP:0,SEQUENCE:LogotypeInfo +[LogotypeInfo] +# note: implicit tag converted to explicit for CHOICE +direct=EXP:0,SEQUENCE:LogotypeData +[LogotypeData] +image=SEQUENCE:LogotypeImage +[LogotypeImage] +imageDetails=SEQUENCE:LogotypeDetails +imageInfo=SEQUENCE:LogotypeImageInfo +[LogotypeDetails] +mediaType=IA5STRING:image/png +logotypeHash=SEQUENCE:HashAlgAndValues +logotypeURI=SEQUENCE:URI +[HashAlgAndValues] +value1=SEQUENCE:HashAlgAndValueSHA256 +#value2=SEQUENCE:HashAlgAndValueSHA1 +[HashAlgAndValueSHA256] +hashAlg=SEQUENCE:sha256_alg +hashValue=FORMAT:HEX,OCTETSTRING:@LOGO_HASH256@ +[HashAlgAndValueSHA1] +hashAlg=SEQUENCE:sha1_alg +hashValue=FORMAT:HEX,OCTETSTRING:@LOGO_HASH1@ +[sha256_alg] +algorithm=OID:sha256 +[sha1_alg] +algorithm=OID:sha1 +[URI] +uri=IA5STRING:@LOGO_URI@ +[LogotypeImageInfo] +# default value color(1), component optional +#type=IMP:0,INTEGER:1 +fileSize=INTEGER:7549 +xSize=INTEGER:128 +ySize=INTEGER:80 +language=IMP:4,IA5STRING:zxx + +[ crl_ext ] + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ v3_OCSP ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning + +[ ext_client ] + +basicConstraints=CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +authorityInfoAccess = OCSP;URI:@OCSP_URI@ +#@ALTNAME@ +extendedKeyUsage = clientAuth + +[ ext_server ] + +# Hotspot 2.0 PKI requirements +basicConstraints=critical, CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +authorityInfoAccess = OCSP;URI:@OCSP_URI@ +#@ALTNAME@ +extendedKeyUsage = critical, serverAuth +keyUsage = critical, keyEncipherment diff --git a/contrib/wpa/hs20/server/ca/setup.sh b/contrib/wpa/hs20/server/ca/setup.sh new file mode 100755 index 000000000000..78abcccff455 --- /dev/null +++ b/contrib/wpa/hs20/server/ca/setup.sh @@ -0,0 +1,209 @@ +#!/bin/sh + +if [ -z "$OPENSSL" ]; then + OPENSSL=openssl +fi +export OPENSSL_CONF=$PWD/openssl.cnf +PASS=whatever +if [ -z "$DOMAIN" ]; then + DOMAIN=w1.fi +fi +COMPANY=w1.fi +OPER_ENG="engw1.fi TESTING USE" +OPER_FI="finw1.fi TESTIKÄYTTÖ" +CNR="Hotspot 2.0 Trust Root CA - 99" +CNO="ocsp.$DOMAIN" +CNV="osu-revoked.$DOMAIN" +CNOC="osu-client.$DOMAIN" +OSU_SERVER_HOSTNAME="osu.$DOMAIN" +DEBUG=0 +OCSP_URI="http://$CNO:8888/" +LOGO_URI="http://osu.w1.fi/w1fi_logo.png" +LOGO_HASH256="4532f7ec36424381617c03c6ce87b55a51d6e7177ffafda243cebf280a68954d" +LOGO_HASH1="5e1d5085676eede6b02da14d31c523ec20ffba0b" + +# Command line overrides +USAGE=$( cat < my-openssl-root.cnf + +cat openssl.cnf | sed "s/@PASSWORD@/$PASS/" | +sed "s,@OCSP_URI@,$OCSP_URI," | +sed "s,@LOGO_URI@,$LOGO_URI," | +sed "s,@LOGO_HASH1@,$LOGO_HASH1," | +sed "s,@LOGO_HASH256@,$LOGO_HASH256," | +sed "s/@DOMAIN@/$DOMAIN/" \ + > my-openssl.cnf + + +cat my-openssl-root.cnf | sed "s/#@CN@/commonName_default = $CNR/" > openssl.cnf.tmp +mkdir -p rootCA/certs rootCA/crl rootCA/newcerts rootCA/private +touch rootCA/index.txt +if [ -e rootCA/private/cakey.pem ]; then + echo " * Use existing Root CA" +else + echo " * Generate Root CA private key" + $OPENSSL req -config openssl.cnf.tmp -batch -new -newkey rsa:4096 -keyout rootCA/private/cakey.pem -out rootCA/careq.pem || fail "Failed to generate Root CA private key" + echo " * Sign Root CA certificate" + $OPENSSL ca -config openssl.cnf.tmp -md sha256 -create_serial -out rootCA/cacert.pem -days 10957 -batch -keyfile rootCA/private/cakey.pem -passin pass:$PASS -selfsign -extensions v3_ca -outdir rootCA/newcerts -infiles rootCA/careq.pem || fail "Failed to sign Root CA certificate" + $OPENSSL x509 -in rootCA/cacert.pem -out rootCA/cacert.der -outform DER || fail "Failed to create rootCA DER" + sha256sum rootCA/cacert.der > rootCA/cacert.fingerprint || fail "Failed to create rootCA fingerprint" +fi +if [ ! -e rootCA/crlnumber ]; then + echo 00 > rootCA/crlnumber +fi + +echo +echo "---[ Intermediate CA ]--------------------------------------------------" +echo + +cat my-openssl.cnf | sed "s/#@CN@/commonName_default = $COMPANY Hotspot 2.0 Intermediate CA/" > openssl.cnf.tmp +mkdir -p demoCA/certs demoCA/crl demoCA/newcerts demoCA/private +touch demoCA/index.txt +if [ -e demoCA/private/cakey.pem ]; then + echo " * Use existing Intermediate CA" +else + echo " * Generate Intermediate CA private key" + $OPENSSL req -config openssl.cnf.tmp -batch -new -newkey rsa:2048 -keyout demoCA/private/cakey.pem -out demoCA/careq.pem || fail "Failed to generate Intermediate CA private key" + echo " * Sign Intermediate CA certificate" + $OPENSSL ca -config openssl.cnf.tmp -md sha256 -create_serial -out demoCA/cacert.pem -days 3652 -batch -keyfile rootCA/private/cakey.pem -cert rootCA/cacert.pem -passin pass:$PASS -extensions v3_ca -infiles demoCA/careq.pem || fail "Failed to sign Intermediate CA certificate" + # horrible from security view point, but for testing purposes since OCSP responder does not seem to support -passin + openssl rsa -in demoCA/private/cakey.pem -out demoCA/private/cakey-plain.pem -passin pass:$PASS + $OPENSSL x509 -in demoCA/cacert.pem -out demoCA/cacert.der -outform DER || fail "Failed to create demoCA DER." + sha256sum demoCA/cacert.der > demoCA/cacert.fingerprint || fail "Failed to create demoCA fingerprint" +fi +if [ ! -e demoCA/crlnumber ]; then + echo 00 > demoCA/crlnumber +fi + +echo +echo "OCSP responder" +echo + +cat my-openssl.cnf | sed "s/#@CN@/commonName_default = $CNO/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out ocsp.csr -keyout ocsp.key -extensions v3_OCSP +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -keyfile demoCA/private/cakey.pem -passin pass:$PASS -in ocsp.csr -out ocsp.pem -days 730 -extensions v3_OCSP || fail "Could not generate ocsp.pem" + +echo +echo "---[ Server - to be revoked ] ------------------------------------------" +echo + +cat my-openssl.cnf | sed "s/#@CN@/commonName_default = $CNV/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out server-revoked.csr -keyout server-revoked.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server-revoked.csr -out server-revoked.pem -key $PASS -days 730 -extensions ext_server +$OPENSSL ca -revoke server-revoked.pem -key $PASS + +echo +echo "---[ Server - with client ext key use ] ---------------------------------" +echo "---[ Only used for negative-testing for OSU-client implementation ] -----" +echo + +cat my-openssl.cnf | sed "s/#@CN@/commonName_default = $CNOC/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out server-client.csr -keyout server-client.key || fail "Could not create server-client.key" +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server-client.csr -out server-client.pem -key $PASS -days 730 -extensions ext_client || fail "Could not create server-client.pem" + +echo +echo "---[ User ]-------------------------------------------------------------" +echo + +cat my-openssl.cnf | sed "s/#@CN@/commonName_default = User/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out user.csr -keyout user.key || fail "Could not create user.key" +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in user.csr -out user.pem -key $PASS -days 730 -extensions ext_client || fail "Could not create user.pem" + +echo +echo "---[ Server ]-----------------------------------------------------------" +echo + +ALT="DNS:$OSU_SERVER_HOSTNAME" +ALT="$ALT,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:$OPER_ENG" +ALT="$ALT,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:$OPER_FI" + +cat my-openssl.cnf | + sed "s/#@CN@/commonName_default = $OSU_SERVER_HOSTNAME/" | + sed "s/^##organizationalUnitName/organizationalUnitName/" | + sed "s/#@OU@/organizationalUnitName_default = Hotspot 2.0 Online Sign Up Server/" | + sed "s/#@ALTNAME@/subjectAltName=critical,$ALT/" \ + > openssl.cnf.tmp +echo $OPENSSL req -config $PWD/openssl.cnf.tmp -batch -sha256 -new -newkey rsa:2048 -nodes -out server.csr -keyout server.key -reqexts v3_osu_server +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -sha256 -new -newkey rsa:2048 -nodes -out server.csr -keyout server.key -reqexts v3_osu_server || fail "Failed to generate server request" +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server.csr -out server.pem -key $PASS -days 730 -extensions ext_server -policy policy_osu_server || fail "Failed to sign server certificate" + +#dump logotype details for debugging +$OPENSSL x509 -in server.pem -out server.der -outform DER +openssl asn1parse -in server.der -inform DER | grep HEX | tail -1 | sed 's/.*://' | xxd -r -p > logo.der +openssl asn1parse -in logo.der -inform DER > logo.asn1 + + +echo +echo "---[ CRL ]---------------------------------------------------------------" +echo + +$OPENSSL ca -config $PWD/my-openssl.cnf -gencrl -md sha256 -out demoCA/crl/crl.pem -passin pass:$PASS + +echo +echo "---[ Verify ]------------------------------------------------------------" +echo + +$OPENSSL verify -CAfile rootCA/cacert.pem demoCA/cacert.pem +$OPENSSL verify -CAfile rootCA/cacert.pem -untrusted demoCA/cacert.pem *.pem + +cat rootCA/cacert.pem demoCA/cacert.pem > ca.pem diff --git a/contrib/wpa/hs20/server/ca/w1fi_logo.png b/contrib/wpa/hs20/server/ca/w1fi_logo.png new file mode 100644 index 000000000000..ac7c259fff2e Binary files /dev/null and b/contrib/wpa/hs20/server/ca/w1fi_logo.png differ diff --git a/contrib/wpa/hs20/server/hs20-osu-server.txt b/contrib/wpa/hs20/server/hs20-osu-server.txt new file mode 100644 index 000000000000..22478ad9d2cb --- /dev/null +++ b/contrib/wpa/hs20/server/hs20-osu-server.txt @@ -0,0 +1,262 @@ +Hotspot 2.0 OSU server +====================== + +The information in this document is based on the assumption that Ubuntu +16.04 server (64-bit) distribution is used and the web server is +Apache2. Neither of these are requirements for the installation, but if +other combinations are used, the package names and configuration +parameters may need to be adjusted. + +NOTE: This implementation and the example configuration here is meant +only for testing purposes in a lab environment. This design is not +secure to be installed in a publicly available Internet server without +considerable amount of modification and review for security issues. + + +Build dependencies +------------------ + +Ubuntu 16.04 server +- default installation +- upgraded to latest package versions + sudo apt-get update + sudo apt-get upgrade + +Packages needed for running the service: + sudo apt-get install sqlite3 + sudo apt-get install apache2 + sudo apt-get install php-sqlite3 php-xml libapache2-mod-php + +Additional packages needed for building the components: + sudo apt-get install build-essential + sudo apt-get install libsqlite3-dev + sudo apt-get install libssl-dev + sudo apt-get install libxml2-dev + + +Installation location +--------------------- + +Select a location for the installation root directory. The example here +assumes /home/user/hs20-server to be used, but this can be changed by +editing couple of files as indicated below. + +sudo mkdir -p /home/user/hs20-server +sudo chown $USER /home/user/hs20-server +mkdir -p /home/user/hs20-server/spp +mkdir -p /home/user/hs20-server/AS + + +Build +----- + +# hostapd as RADIUS server +cd hostapd + +#example build configuration +cat > .config < /home/user/hs20-server/terms-and-conditions <Terms and conditions..

+EOF + +# Build local keys and certs +cd ca +# Display help options. +./setup.sh -h + +# Remove old keys, fill in appropriate values, and generate your keys. +# For instance: +./clean.sh +rm -fr rootCA" +old_hostname=myserver.local +./setup.sh -C "Hotspot 2.0 Trust Root CA - CT" \ + -o $old_hostname-osu-client \ + -O $old_hostname-oscp -p lanforge -S $old_hostname \ + -V $old_hostname-osu-revoked \ + -m local -u http://$old_hostname:8888/ + +# Configure subscription policies +mkdir -p /home/user/hs20-server/spp/policy +cat > /home/user/hs20-server/spp/policy/default.xml < + + 30 + ClientInitiated + Unrestricted + https://policy-server.osu.example.com/hs20/spp.php + + +EOF + + +# Install Hotspot 2.0 SPP and OMA DM XML schema/DTD files + +# XML schema for SPP +# Copy the latest XML schema into /home/user/hs20-server/spp/spp.xsd + +# OMA DM Device Description Framework DTD +# Copy into /home/user/hs20-server/spp/dm_ddf-v1_2.dtd +# http://www.openmobilealliance.org/tech/DTD/dm_ddf-v1_2.dtd + + +# Configure RADIUS authentication service +# Note: Change the URL to match the setup +# Note: Install AAA server key/certificate and root CA in Key directory + +cat > /home/user/hs20-server/AS/as-sql.conf < /home/user/hs20-server/AS/as.radius_clients < + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Require all granted + SSLOptions +StdEnvVars + + +Update SSL configuration to use the OSU server certificate/key. +They keys and certs are called 'server.key' and 'server.pem' from +ca/setup.sh. + +To support subscription remediation using client certificates, set +"SSLVerifyClient optional" and configure the trust root CA(s) for the +client certificates with SSLCACertificateFile. + +Enable default-ssl site and restart Apache2: + sudo a2ensite default-ssl + sudo a2enmod ssl + sudo service apache2 restart + + +Management UI +------------- + +The sample PHP scripts include a management UI for testing +purposes. That is available at https:///hs20/users.php + + +AP configuration +---------------- + +APs can now be configured to use the OSU server as the RADIUS +authentication server. In addition, the OSU Provider List ANQP element +should be configured to use the SPP (SOAP+XML) option and with the +following Server URL: +https:///hs20/spp.php/signup?realm=example.com diff --git a/contrib/wpa/hs20/server/hs20_spp_server.c b/contrib/wpa/hs20/server/hs20_spp_server.c new file mode 100644 index 000000000000..347c40a73d6a --- /dev/null +++ b/contrib/wpa/hs20/server/hs20_spp_server.c @@ -0,0 +1,207 @@ +/* + * Hotspot 2.0 SPP server - standalone version + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include +#include + +#include "common.h" +#include "common/version.h" +#include "xml-utils.h" +#include "spp_server.h" + + +static void write_timestamp(FILE *f) +{ + time_t t; + struct tm *tm; + + time(&t); + tm = localtime(&t); + + fprintf(f, "%04u-%02u-%02u %02u:%02u:%02u ", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + +void debug_print(struct hs20_svc *ctx, int print, const char *fmt, ...) +{ + va_list ap; + + if (ctx->debug_log == NULL) + return; + + write_timestamp(ctx->debug_log); + va_start(ap, fmt); + vfprintf(ctx->debug_log, fmt, ap); + va_end(ap); + + fprintf(ctx->debug_log, "\n"); +} + + +void debug_dump_node(struct hs20_svc *ctx, const char *title, xml_node_t *node) +{ + char *str; + + if (ctx->debug_log == NULL) + return; + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + + write_timestamp(ctx->debug_log); + fprintf(ctx->debug_log, "%s: '%s'\n", title, str); + os_free(str); +} + + +static int process(struct hs20_svc *ctx) +{ + int dmacc = 0; + xml_node_t *soap, *spp, *resp; + char *user, *realm, *post, *str; + + ctx->addr = getenv("HS20ADDR"); + if (ctx->addr) + debug_print(ctx, 1, "Connection from %s", ctx->addr); + ctx->test = getenv("HS20TEST"); + if (ctx->test) + debug_print(ctx, 1, "Requested test functionality: %s", + ctx->test); + + user = getenv("HS20USER"); + if (user && strlen(user) == 0) + user = NULL; + realm = getenv("HS20REALM"); + if (realm == NULL) { + debug_print(ctx, 1, "HS20REALM not set"); + return -1; + } + post = getenv("HS20POST"); + if (post == NULL) { + debug_print(ctx, 1, "HS20POST not set"); + return -1; + } + + ctx->imsi = getenv("HS20IMSI"); + if (ctx->imsi) + debug_print(ctx, 1, "IMSI %s", ctx->imsi); + + ctx->eap_method = getenv("HS20EAPMETHOD"); + if (ctx->eap_method) + debug_print(ctx, 1, "EAP method %s", ctx->eap_method); + + ctx->id_hash = getenv("HS20IDHASH"); + if (ctx->id_hash) + debug_print(ctx, 1, "ID-HASH %s", ctx->id_hash); + + soap = xml_node_from_buf(ctx->xml, post); + if (soap == NULL) { + debug_print(ctx, 1, "Could not parse SOAP data"); + return -1; + } + debug_dump_node(ctx, "Received SOAP message", soap); + spp = soap_get_body(ctx->xml, soap); + if (spp == NULL) { + debug_print(ctx, 1, "Could not get SPP message"); + xml_node_free(ctx->xml, soap); + return -1; + } + debug_dump_node(ctx, "Received SPP message", spp); + + resp = hs20_spp_server_process(ctx, spp, user, realm, dmacc); + xml_node_free(ctx->xml, soap); + if (resp == NULL && user == NULL) { + debug_print(ctx, 1, "Request HTTP authentication"); + return 2; /* Request authentication */ + } + if (resp == NULL) { + debug_print(ctx, 1, "No response"); + return -1; + } + + soap = soap_build_envelope(ctx->xml, resp); + if (soap == NULL) { + debug_print(ctx, 1, "SOAP envelope building failed"); + return -1; + } + str = xml_node_to_str(ctx->xml, soap); + xml_node_free(ctx->xml, soap); + if (str == NULL) { + debug_print(ctx, 1, "Could not get node string"); + return -1; + } + printf("%s", str); + free(str); + + return 0; +} + + +static void usage(void) +{ + printf("usage:\n" + "hs20_spp_server -r [-f]\n"); +} + + +int main(int argc, char *argv[]) +{ + struct hs20_svc ctx; + int ret; + + os_memset(&ctx, 0, sizeof(ctx)); + for (;;) { + int c = getopt(argc, argv, "f:r:v"); + if (c < 0) + break; + switch (c) { + case 'f': + if (ctx.debug_log) + break; + ctx.debug_log = fopen(optarg, "a"); + if (ctx.debug_log == NULL) { + printf("Could not write to %s\n", optarg); + return -1; + } + break; + case 'r': + ctx.root_dir = optarg; + break; + case 'v': + printf("hs20_spp_server v%s\n", VERSION_STR); + return 0; + default: + usage(); + return -1; + } + } + if (ctx.root_dir == NULL) { + usage(); + return -1; + } + ctx.xml = xml_node_init_ctx(&ctx, NULL); + if (ctx.xml == NULL) + return -1; + if (hs20_spp_server_init(&ctx) < 0) { + xml_node_deinit_ctx(ctx.xml); + return -1; + } + + ret = process(&ctx); + debug_print(&ctx, 1, "process() --> %d", ret); + + xml_node_deinit_ctx(ctx.xml); + hs20_spp_server_deinit(&ctx); + if (ctx.debug_log) + fclose(ctx.debug_log); + + return ret; +} diff --git a/contrib/wpa/hs20/server/spp_server.c b/contrib/wpa/hs20/server/spp_server.c new file mode 100644 index 000000000000..a50e9074f7b4 --- /dev/null +++ b/contrib/wpa/hs20/server/spp_server.c @@ -0,0 +1,2933 @@ +/* + * Hotspot 2.0 SPP server + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "base64.h" +#include "md5_i.h" +#include "xml-utils.h" +#include "spp_server.h" + + +#define SPP_NS_URI "http://www.wi-fi.org/specifications/hotspot2dot0/v1.0/spp" + +#define URN_OMA_DM_DEVINFO "urn:oma:mo:oma-dm-devinfo:1.0" +#define URN_OMA_DM_DEVDETAIL "urn:oma:mo:oma-dm-devdetail:1.0" +#define URN_OMA_DM_DMACC "urn:oma:mo:oma-dm-dmacc:1.0" +#define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0" + + +/* TODO: timeout to expire sessions */ + +enum hs20_session_operation { + NO_OPERATION, + UPDATE_PASSWORD, + CONTINUE_SUBSCRIPTION_REMEDIATION, + CONTINUE_POLICY_UPDATE, + USER_REMEDIATION, + SUBSCRIPTION_REGISTRATION, + POLICY_REMEDIATION, + POLICY_UPDATE, + FREE_REMEDIATION, + CLEAR_REMEDIATION, + CERT_REENROLL, +}; + + +static char * db_get_session_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *field); +static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, + const char *field); +static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, + const char *realm, int use_dmacc); +static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, + const char *session_id, + const char *user, + const char *realm, + int add_est_user); + + +static int db_add_session(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *pw, + const char *redirect_uri, + enum hs20_session_operation operation, + const u8 *mac_addr) +{ + char *sql; + int ret = 0; + char addr[20]; + + if (mac_addr) + snprintf(addr, sizeof(addr), MACSTR, MAC2STR(mac_addr)); + else + addr[0] = '\0'; + sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm," + "operation,password,redirect_uri,mac_addr,test) " + "VALUES " + "(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," + "%Q,%Q,%Q,%d,%Q,%Q,%Q,%Q)", + sessionid, user ? user : "", realm ? realm : "", + operation, pw ? pw : "", + redirect_uri ? redirect_uri : "", + addr, ctx->test); + if (sql == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session entry into sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + ret = -1; + } + sqlite3_free(sql); + return ret; +} + + +static void db_update_session_password(struct hs20_svc *ctx, const char *user, + const char *realm, const char *sessionid, + const char *pw) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET password=%Q WHERE id=%Q AND " + "user=%Q AND realm=%Q", + pw, sessionid, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update session password: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_update_session_machine_managed(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *sessionid, + const int pw_mm) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET machine_managed=%Q WHERE id=%Q AND user=%Q AND realm=%Q", + pw_mm ? "1" : "0", sessionid, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, + "Failed to update session machine_managed: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_pps(struct hs20_svc *ctx, const char *user, + const char *realm, const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET pps=%Q WHERE id=%Q AND " + "user=%Q AND realm=%Q", + str, sessionid, user, realm); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session pps: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_devinfo(struct hs20_svc *ctx, const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET devinfo=%Q WHERE id=%Q", + str, sessionid); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session devinfo: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_devdetail(struct hs20_svc *ctx, + const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET devdetail=%Q WHERE id=%Q", + str, sessionid); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session devdetail: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_dmacc(struct hs20_svc *ctx, const char *sessionid, + const char *username, const char *password) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET osu_user=%Q, osu_password=%Q WHERE id=%Q", + username, password, sessionid); + if (!sql) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session DMAcc: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_eap_method(struct hs20_svc *ctx, + const char *sessionid, + const char *method) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET eap_method=%Q WHERE id=%Q", + method, sessionid); + if (!sql) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session EAP method: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_id_hash(struct hs20_svc *ctx, const char *sessionid, + const char *id_hash) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET mobile_identifier_hash=%Q WHERE id=%Q", + id_hash, sessionid); + if (!sql) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session ID hash: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_remove_session(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid) +{ + char *sql; + + if (user == NULL || realm == NULL) { + sql = sqlite3_mprintf("DELETE FROM sessions WHERE " + "id=%Q", sessionid); + } else { + sql = sqlite3_mprintf("DELETE FROM sessions WHERE " + "user=%Q AND realm=%Q AND id=%Q", + user, realm, sessionid); + } + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to delete session entry from " + "sqlite database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void hs20_eventlog(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *notes, + const char *dump) +{ + char *sql; + char *user_buf = NULL, *realm_buf = NULL; + + debug_print(ctx, 1, "eventlog: %s", notes); + + if (user == NULL) { + user_buf = db_get_session_val(ctx, NULL, NULL, sessionid, + "user"); + user = user_buf; + realm_buf = db_get_session_val(ctx, NULL, NULL, sessionid, + "realm"); + realm = realm_buf; + } + + sql = sqlite3_mprintf("INSERT INTO eventlog" + "(user,realm,sessionid,timestamp,notes,dump,addr)" + " VALUES (%Q,%Q,%Q," + "strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," + "%Q,%Q,%Q)", + user, realm, sessionid, notes, + dump ? dump : "", ctx->addr ? ctx->addr : ""); + free(user_buf); + free(realm_buf); + if (sql == NULL) + return; + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add eventlog entry into sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void hs20_eventlog_node(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *notes, + xml_node_t *node) +{ + char *str; + + if (node) + str = xml_node_to_str(ctx->xml, node); + else + str = NULL; + hs20_eventlog(ctx, user, realm, sessionid, notes, str); + free(str); +} + + +static void db_update_mo_str(struct hs20_svc *ctx, const char *user, + const char *realm, const char *name, + const char *str) +{ + char *sql; + if (user == NULL || realm == NULL || name == NULL) + return; + sql = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE identity=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')", + name, str, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update user MO entry in sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_update_mo(struct hs20_svc *ctx, const char *user, + const char *realm, const char *name, xml_node_t *mo) +{ + char *str; + + str = xml_node_to_str(ctx->xml, mo); + if (str == NULL) + return; + + db_update_mo_str(ctx, user, realm, name, str); + free(str); +} + + +static void add_text_node(struct hs20_svc *ctx, xml_node_t *parent, + const char *name, const char *value) +{ + xml_node_create_text(ctx->xml, parent, NULL, name, value ? value : ""); +} + + +static void add_text_node_conf(struct hs20_svc *ctx, const char *realm, + xml_node_t *parent, const char *name, + const char *field) +{ + char *val; + val = db_get_osu_config_val(ctx, realm, field); + xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : ""); + os_free(val); +} + + +static void add_text_node_conf_corrupt(struct hs20_svc *ctx, const char *realm, + xml_node_t *parent, const char *name, + const char *field) +{ + char *val; + + val = db_get_osu_config_val(ctx, realm, field); + if (val) { + size_t len; + + len = os_strlen(val); + if (len > 0) { + if (val[len - 1] == '0') + val[len - 1] = '1'; + else + val[len - 1] = '0'; + } + } + xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : ""); + os_free(val); +} + + +static int new_password(char *buf, int buflen) +{ + int i; + + if (buflen < 1) + return -1; + buf[buflen - 1] = '\0'; + if (os_get_random((unsigned char *) buf, buflen - 1) < 0) + return -1; + + for (i = 0; i < buflen - 1; i++) { + unsigned char val = buf[i]; + val %= 2 * 26 + 10; + if (val < 26) + buf[i] = 'a' + val; + else if (val < 2 * 26) + buf[i] = 'A' + val - 26; + else + buf[i] = '0' + val - 2 * 26; + } + + return 0; +} + + +struct get_db_field_data { + const char *field; + char *value; +}; + + +static int get_db_field(void *ctx, int argc, char *argv[], char *col[]) +{ + struct get_db_field_data *data = ctx; + int i; + + for (i = 0; i < argc; i++) { + if (os_strcmp(col[i], data->field) == 0 && argv[i]) { + os_free(data->value); + data->value = os_strdup(argv[i]); + break; + } + } + + return 0; +} + + +static char * db_get_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *field, int dmacc) +{ + char *cmd; + struct get_db_field_data data; + + cmd = sqlite3_mprintf("SELECT %s FROM users WHERE %s=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')", + field, dmacc ? "osu_user" : "identity", + user, realm); + if (cmd == NULL) + return NULL; + memset(&data, 0, sizeof(data)); + data.field = field; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "Could not find user '%s'", user); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: user='%s' realm='%s' field='%s' dmacc=%d --> " + "value='%s'", user, realm, field, dmacc, data.value); + + return data.value; +} + + +static int db_update_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *field, + const char *val, int dmacc) +{ + char *cmd; + int ret; + + cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE %s=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')", + field, val, dmacc ? "osu_user" : "identity", user, + realm); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, + "Failed to update user in sqlite database: %s", + sqlite3_errmsg(ctx->db)); + ret = -1; + } else { + debug_print(ctx, 1, + "DB: user='%s' realm='%s' field='%s' set to '%s'", + user, realm, field, val); + ret = 0; + } + sqlite3_free(cmd); + + return ret; +} + + +static char * db_get_session_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *field) +{ + char *cmd; + struct get_db_field_data data; + + if (user == NULL || realm == NULL) { + cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " + "id=%Q", field, session_id); + } else { + cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " + "user=%Q AND realm=%Q AND id=%Q", + field, user, realm, session_id); + } + if (cmd == NULL) + return NULL; + debug_print(ctx, 1, "DB: %s", cmd); + memset(&data, 0, sizeof(data)); + data.field = field; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "DB: Could not find session %s: %s", + session_id, sqlite3_errmsg(ctx->db)); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: return '%s'", data.value); + return data.value; +} + + +static int update_password(struct hs20_svc *ctx, const char *user, + const char *realm, const char *pw, int dmacc) +{ + char *cmd; + + cmd = sqlite3_mprintf("UPDATE users SET password=%Q, " + "remediation='' " + "WHERE %s=%Q AND phase2=1", + pw, dmacc ? "osu_user" : "identity", + user); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update database for user '%s'", + user); + } + sqlite3_free(cmd); + + return 0; +} + + +static int clear_remediation(struct hs20_svc *ctx, const char *user, + const char *realm, int dmacc) +{ + char *cmd; + + cmd = sqlite3_mprintf("UPDATE users SET remediation='' WHERE %s=%Q", + dmacc ? "osu_user" : "identity", + user); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update database for user '%s'", + user); + } + sqlite3_free(cmd); + + return 0; +} + + +static int add_eap_ttls(struct hs20_svc *ctx, xml_node_t *parent) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "EAPMethod"); + if (node == NULL) + return -1; + + add_text_node(ctx, node, "EAPType", "21"); + add_text_node(ctx, node, "InnerMethod", "MS-CHAP-V2"); + + return 0; +} + + +static xml_node_t * build_username_password(struct hs20_svc *ctx, + xml_node_t *parent, + const char *user, const char *pw) +{ + xml_node_t *node; + char *b64; + size_t len; + + node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword"); + if (node == NULL) + return NULL; + + add_text_node(ctx, node, "Username", user); + + b64 = base64_encode(pw, strlen(pw), NULL); + if (b64 == NULL) + return NULL; + len = os_strlen(b64); + if (len > 0 && b64[len - 1] == '\n') + b64[len - 1] = '\0'; + add_text_node(ctx, node, "Password", b64); + free(b64); + + return node; +} + + +static int add_username_password(struct hs20_svc *ctx, xml_node_t *cred, + const char *user, const char *pw, + int machine_managed) +{ + xml_node_t *node; + + node = build_username_password(ctx, cred, user, pw); + if (node == NULL) + return -1; + + add_text_node(ctx, node, "MachineManaged", + machine_managed ? "TRUE" : "FALSE"); + add_text_node(ctx, node, "SoftTokenApp", ""); + add_eap_ttls(ctx, node); + + return 0; +} + + +static void add_creation_date(struct hs20_svc *ctx, xml_node_t *cred) +{ + char str[30]; + time_t now; + struct tm tm; + + time(&now); + gmtime_r(&now, &tm); + snprintf(str, sizeof(str), "%04u-%02u-%02uT%02u:%02u:%02uZ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + xml_node_create_text(ctx->xml, cred, NULL, "CreationDate", str); +} + + +static xml_node_t * build_credential_pw(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *pw, int machine_managed) +{ + xml_node_t *cred; + + cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); + if (cred == NULL) { + debug_print(ctx, 1, "Failed to create Credential node"); + return NULL; + } + add_creation_date(ctx, cred); + if (add_username_password(ctx, cred, user, pw, machine_managed) < 0) { + xml_node_free(ctx->xml, cred); + return NULL; + } + add_text_node(ctx, cred, "Realm", realm); + + return cred; +} + + +static xml_node_t * build_credential(struct hs20_svc *ctx, + const char *user, const char *realm, + char *new_pw, size_t new_pw_len) +{ + if (new_password(new_pw, new_pw_len) < 0) + return NULL; + debug_print(ctx, 1, "Update password to '%s'", new_pw); + return build_credential_pw(ctx, user, realm, new_pw, 1); +} + + +static xml_node_t * build_credential_cert(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *cert_fingerprint) +{ + xml_node_t *cred, *cert; + + cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); + if (cred == NULL) { + debug_print(ctx, 1, "Failed to create Credential node"); + return NULL; + } + add_creation_date(ctx, cred); + cert = xml_node_create(ctx->xml, cred, NULL, "DigitalCertificate"); + add_text_node(ctx, cert, "CertificateType", "x509v3"); + add_text_node(ctx, cert, "CertSHA256Fingerprint", cert_fingerprint); + add_text_node(ctx, cred, "Realm", realm); + + return cred; +} + + +static xml_node_t * build_post_dev_data_response(struct hs20_svc *ctx, + xml_namespace_t **ret_ns, + const char *session_id, + const char *status, + const char *error_code) +{ + xml_node_t *spp_node = NULL; + xml_namespace_t *ns; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppPostDevDataResponse"); + if (spp_node == NULL) + return NULL; + if (ret_ns) + *ret_ns = ns; + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); + + if (error_code) { + xml_node_t *node; + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + if (node) + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int add_update_node(struct hs20_svc *ctx, xml_node_t *spp_node, + xml_namespace_t *ns, const char *uri, + xml_node_t *upd_node) +{ + xml_node_t *node, *tnds; + char *str; + + tnds = mo_to_tnds(ctx->xml, upd_node, 0, NULL, NULL); + if (!tnds) + return -1; + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) + return -1; + node = xml_node_create_text(ctx->xml, spp_node, ns, "updateNode", str); + free(str); + + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", uri); + + return 0; +} + + +static xml_node_t * read_subrem_file(struct hs20_svc *ctx, + const char *subrem_id, + char *uri, size_t uri_size) +{ + char fname[200]; + char *buf, *buf2, *pos; + size_t len; + xml_node_t *node; + + os_snprintf(fname, sizeof(fname), "%s/spp/subrem/%s", + ctx->root_dir, subrem_id); + debug_print(ctx, 1, "Use subrem file %s", fname); + + buf = os_readfile(fname, &len); + if (!buf) + return NULL; + buf2 = os_realloc(buf, len + 1); + if (!buf2) { + os_free(buf); + return NULL; + } + buf = buf2; + buf[len] = '\0'; + + pos = os_strchr(buf, '\n'); + if (!pos) { + os_free(buf); + return NULL; + } + *pos++ = '\0'; + os_strlcpy(uri, buf, uri_size); + + node = xml_node_from_buf(ctx->xml, pos); + os_free(buf); + + return node; +} + + +static xml_node_t * build_sub_rem_resp(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, + int machine_rem, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *cred; + char buf[400]; + char new_pw[33]; + char *status; + char *cert; + + cert = db_get_val(ctx, user, realm, "cert", dmacc); + if (cert && cert[0] == '\0') { + os_free(cert); + cert = NULL; + } + if (cert) { + char *subrem; + + /* No change needed in PPS MO unless specifically asked to */ + cred = NULL; + buf[0] = '\0'; + + subrem = db_get_val(ctx, user, realm, "subrem", dmacc); + if (subrem && subrem[0]) { + cred = read_subrem_file(ctx, subrem, buf, sizeof(buf)); + if (!cred) { + debug_print(ctx, 1, + "Could not create updateNode from subrem file"); + os_free(subrem); + os_free(cert); + return NULL; + } + } + os_free(subrem); + } else { + char *real_user = NULL; + char *pw; + + if (dmacc) { + real_user = db_get_val(ctx, user, realm, "identity", + dmacc); + if (!real_user) { + debug_print(ctx, 1, + "Could not find user identity for dmacc user '%s'", + user); + return NULL; + } + } + + pw = db_get_session_val(ctx, user, realm, session_id, + "password"); + if (pw && pw[0]) { + debug_print(ctx, 1, "New password from the user: '%s'", + pw); + snprintf(new_pw, sizeof(new_pw), "%s", pw); + free(pw); + cred = build_credential_pw(ctx, + real_user ? real_user : user, + realm, new_pw, 0); + } else { + cred = build_credential(ctx, + real_user ? real_user : user, + realm, new_pw, sizeof(new_pw)); + } + + free(real_user); + if (!cred) { + debug_print(ctx, 1, "Could not build credential"); + os_free(cert); + return NULL; + } + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential", + realm); + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) { + debug_print(ctx, 1, "Could not build sppPostDevDataResponse"); + os_free(cert); + return NULL; + } + + if ((cred && add_update_node(ctx, spp_node, ns, buf, cred) < 0) || + (!cred && !xml_node_create(ctx->xml, spp_node, ns, "noMOUpdate"))) { + debug_print(ctx, 1, "Could not add update node"); + xml_node_free(ctx->xml, spp_node); + os_free(cert); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + machine_rem ? "machine remediation" : + "user remediation", cred); + xml_node_free(ctx->xml, cred); + + if (cert) { + debug_print(ctx, 1, "Request DB remediation clearing on success notification (certificate credential)"); + db_add_session(ctx, user, realm, session_id, NULL, NULL, + CLEAR_REMEDIATION, NULL); + } else { + debug_print(ctx, 1, "Request DB password update on success " + "notification"); + db_add_session(ctx, user, realm, session_id, new_pw, NULL, + UPDATE_PASSWORD, NULL); + } + os_free(cert); + + return spp_node; +} + + +static xml_node_t * machine_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id, int dmacc) +{ + return build_sub_rem_resp(ctx, user, realm, session_id, 1, dmacc); +} + + +static xml_node_t * cert_reenroll(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id) +{ + db_add_session(ctx, user, realm, session_id, NULL, NULL, + CERT_REENROLL, NULL); + return spp_exec_get_certificate(ctx, session_id, user, realm, 0); +} + + +static xml_node_t * policy_remediation(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *policy; + char buf[400]; + const char *status; + + hs20_eventlog(ctx, user, realm, session_id, + "requires policy remediation", NULL); + + db_add_session(ctx, user, realm, session_id, NULL, NULL, + POLICY_REMEDIATION, NULL); + + policy = build_policy(ctx, user, realm, dmacc); + if (!policy) { + return build_post_dev_data_response( + ctx, NULL, session_id, + "No update available at this time", NULL); + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Cred01/Policy", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { + xml_node_free(ctx->xml, spp_node); + xml_node_free(ctx->xml, policy); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "policy update (sub rem)", policy); + xml_node_free(ctx->xml, policy); + + return spp_node; +} + + +static xml_node_t * browser_remediation(struct hs20_svc *ctx, + const char *session_id, + const char *redirect_uri, + const char *uri) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *exec_node; + + if (redirect_uri == NULL) { + debug_print(ctx, 1, "Missing redirectURI attribute for user " + "remediation"); + return NULL; + } + debug_print(ctx, 1, "redirectURI %s", redirect_uri); + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", + uri); + return spp_node; +} + + +static xml_node_t * user_remediation(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *redirect_uri) +{ + char uri[300], *val; + + hs20_eventlog(ctx, user, realm, session_id, + "requires user remediation", NULL); + val = db_get_osu_config_val(ctx, realm, "remediation_url"); + if (val == NULL) + return NULL; + + db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, + USER_REMEDIATION, NULL); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + return browser_remediation(ctx, session_id, redirect_uri, uri); +} + + +static xml_node_t * free_remediation(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, + const char *redirect_uri) +{ + char uri[300], *val; + + hs20_eventlog(ctx, user, realm, session_id, + "requires free/public account remediation", NULL); + val = db_get_osu_config_val(ctx, realm, "free_remediation_url"); + if (val == NULL) + return NULL; + + db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, + FREE_REMEDIATION, NULL); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + return browser_remediation(ctx, session_id, redirect_uri, uri); +} + + +static xml_node_t * no_sub_rem(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id) +{ + const char *status; + + hs20_eventlog(ctx, user, realm, session_id, + "no subscription mediation available", NULL); + + status = "No update available at this time"; + return build_post_dev_data_response(ctx, NULL, session_id, status, + NULL); +} + + +static xml_node_t * hs20_subscription_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id, + int dmacc, + const char *redirect_uri) +{ + char *type, *identity; + xml_node_t *ret; + char *free_account; + + identity = db_get_val(ctx, user, realm, "identity", dmacc); + if (identity == NULL || strlen(identity) == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "user not found in database for remediation", + NULL); + os_free(identity); + return build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", + "Not found"); + } + os_free(identity); + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + if (free_account && strcmp(free_account, user) == 0) { + free(free_account); + return no_sub_rem(ctx, user, realm, session_id); + } + free(free_account); + + type = db_get_val(ctx, user, realm, "remediation", dmacc); + if (type && strcmp(type, "free") != 0) { + char *val; + int shared = 0; + val = db_get_val(ctx, user, realm, "shared", dmacc); + if (val) + shared = atoi(val); + free(val); + if (shared) { + free(type); + return no_sub_rem(ctx, user, realm, session_id); + } + } + if (type && strcmp(type, "user") == 0) + ret = user_remediation(ctx, user, realm, session_id, + redirect_uri); + else if (type && strcmp(type, "free") == 0) + ret = free_remediation(ctx, user, realm, session_id, + redirect_uri); + else if (type && strcmp(type, "policy") == 0) + ret = policy_remediation(ctx, user, realm, session_id, dmacc); + else if (type && strcmp(type, "machine") == 0) + ret = machine_remediation(ctx, user, realm, session_id, dmacc); + else if (type && strcmp(type, "reenroll") == 0) + ret = cert_reenroll(ctx, user, realm, session_id); + else + ret = no_sub_rem(ctx, user, realm, session_id); + free(type); + + return ret; +} + + +static xml_node_t * read_policy_file(struct hs20_svc *ctx, + const char *policy_id) +{ + char fname[200]; + + snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml", + ctx->root_dir, policy_id); + debug_print(ctx, 1, "Use policy file %s", fname); + + return node_from_file(ctx->xml, fname); +} + + +static void update_policy_update_uri(struct hs20_svc *ctx, const char *realm, + xml_node_t *policy) +{ + xml_node_t *node; + char *url; + + node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI"); + if (!node) + return; + + url = db_get_osu_config_val(ctx, realm, "policy_url"); + if (!url) + return; + xml_node_set_text(ctx->xml, node, url); + free(url); +} + + +static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, + const char *realm, int use_dmacc) +{ + char *policy_id; + xml_node_t *policy, *node; + + policy_id = db_get_val(ctx, user, realm, "policy", use_dmacc); + if (policy_id == NULL || strlen(policy_id) == 0) { + free(policy_id); + policy_id = strdup("default"); + if (policy_id == NULL) + return NULL; + } + policy = read_policy_file(ctx, policy_id); + free(policy_id); + if (policy == NULL) + return NULL; + + update_policy_update_uri(ctx, realm, policy); + + node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate"); + if (node && use_dmacc) { + char *pw; + pw = db_get_val(ctx, user, realm, "osu_password", use_dmacc); + if (pw == NULL || + build_username_password(ctx, node, user, pw) == NULL) { + debug_print(ctx, 1, "Failed to add Policy/PolicyUpdate/" + "UsernamePassword"); + free(pw); + xml_node_free(ctx->xml, policy); + return NULL; + } + free(pw); + } + + return policy; +} + + +static xml_node_t * hs20_policy_update(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + xml_node_t *policy; + char buf[400]; + const char *status; + char *identity; + + identity = db_get_val(ctx, user, realm, "identity", dmacc); + if (identity == NULL || strlen(identity) == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "user not found in database for policy update", + NULL); + os_free(identity); + return build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", + "Not found"); + } + os_free(identity); + + policy = build_policy(ctx, user, realm, dmacc); + if (!policy) { + return build_post_dev_data_response( + ctx, NULL, session_id, + "No update available at this time", NULL); + } + + db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE, + NULL); + + status = "Update complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Cred01/Policy", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { + xml_node_free(ctx->xml, spp_node); + xml_node_free(ctx->xml, policy); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, "policy update", + policy); + xml_node_free(ctx->xml, policy); + + return spp_node; +} + + +static xml_node_t * spp_get_mo(struct hs20_svc *ctx, xml_node_t *node, + const char *urn, int *valid, char **ret_err) +{ + xml_node_t *child, *tnds, *mo; + const char *name; + char *mo_urn; + char *str; + char fname[200]; + + *valid = -1; + if (ret_err) + *ret_err = NULL; + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (strcmp(name, "moContainer") != 0) + continue; + mo_urn = xml_node_get_attr_value_ns(ctx->xml, child, SPP_NS_URI, + "moURN"); + if (strcasecmp(urn, mo_urn) == 0) { + xml_node_get_attr_value_free(ctx->xml, mo_urn); + break; + } + xml_node_get_attr_value_free(ctx->xml, mo_urn); + } + + if (child == NULL) + return NULL; + + debug_print(ctx, 1, "moContainer text for %s", urn); + debug_dump_node(ctx, "moContainer", child); + + str = xml_node_get_text(ctx->xml, child); + debug_print(ctx, 1, "moContainer payload: '%s'", str); + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + debug_print(ctx, 1, "could not parse moContainer text"); + return NULL; + } + + snprintf(fname, sizeof(fname), "%s/spp/dm_ddf-v1_2.dtd", ctx->root_dir); + if (xml_validate_dtd(ctx->xml, tnds, fname, ret_err) == 0) + *valid = 1; + else if (ret_err && *ret_err && + os_strcmp(*ret_err, "No declaration for attribute xmlns of element MgmtTree\n") == 0) { + free(*ret_err); + debug_print(ctx, 1, "Ignore OMA-DM DDF DTD validation error for MgmtTree namespace declaration with xmlns attribute"); + *ret_err = NULL; + *valid = 1; + } else + *valid = 0; + + mo = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (mo == NULL) { + debug_print(ctx, 1, "invalid moContainer for %s", urn); + } + + return mo; +} + + +static xml_node_t * spp_exec_upload_mo(struct hs20_svc *ctx, + const char *session_id, const char *urn) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node, *exec_node; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + node = xml_node_create(ctx->xml, exec_node, ns, "uploadMO"); + xml_node_add_attr(ctx->xml, node, ns, "moURN", urn); + + return spp_node; +} + + +static xml_node_t * hs20_subscription_registration(struct hs20_svc *ctx, + const char *realm, + const char *session_id, + const char *redirect_uri, + const u8 *mac_addr) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *exec_node; + char uri[300], *val; + + if (db_add_session(ctx, NULL, realm, session_id, NULL, redirect_uri, + SUBSCRIPTION_REGISTRATION, mac_addr) < 0) + return NULL; + val = db_get_osu_config_val(ctx, realm, "signup_url"); + if (val == NULL) + return NULL; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", + uri); + return spp_node; +} + + +static xml_node_t * hs20_user_input_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + return build_sub_rem_resp(ctx, user, realm, session_id, 0, dmacc); +} + + +static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, + const char *field) +{ + char *cmd; + struct get_db_field_data data; + + cmd = sqlite3_mprintf("SELECT value FROM osu_config WHERE realm=%Q AND " + "field=%Q", realm, field); + if (cmd == NULL) + return NULL; + debug_print(ctx, 1, "DB: %s", cmd); + memset(&data, 0, sizeof(data)); + data.field = "value"; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "DB: Could not find osu_config %s: %s", + realm, sqlite3_errmsg(ctx->db)); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: return '%s'", data.value); + return data.value; +} + + +static xml_node_t * build_pps(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *pw, const char *cert, + int machine_managed, const char *test, + const char *imsi, const char *dmacc_username, + const char *dmacc_password, + xml_node_t *policy_node) +{ + xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp, *p; + xml_node_t *cred, *eap, *userpw; + + pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "PerProviderSubscription"); + if (!pps) { + xml_node_free(ctx->xml, policy_node); + return NULL; + } + + add_text_node(ctx, pps, "UpdateIdentifier", "1"); + + c = xml_node_create(ctx->xml, pps, NULL, "Cred01"); + + add_text_node(ctx, c, "CredentialPriority", "1"); + + if (imsi) + goto skip_aaa_trust_root; + aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot"); + aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1"); + add_text_node_conf(ctx, realm, aaa1, "CertURL", + "aaa_trust_root_cert_url"); + if (test && os_strcmp(test, "corrupt_aaa_hash") == 0) { + debug_print(ctx, 1, + "TEST: Corrupt PPS/Cred*/AAAServerTrustRoot/Root*/CertSHA256FingerPrint"); + add_text_node_conf_corrupt(ctx, realm, aaa1, + "CertSHA256Fingerprint", + "aaa_trust_root_cert_fingerprint"); + } else { + add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint", + "aaa_trust_root_cert_fingerprint"); + } + + if (test && os_strcmp(test, "corrupt_polupd_hash") == 0) { + debug_print(ctx, 1, + "TEST: Corrupt PPS/Cred*/Policy/PolicyUpdate/Trustroot/CertSHA256FingerPrint"); + p = xml_node_create(ctx->xml, c, NULL, "Policy"); + upd = xml_node_create(ctx->xml, p, NULL, "PolicyUpdate"); + add_text_node(ctx, upd, "UpdateInterval", "30"); + add_text_node(ctx, upd, "UpdateMethod", "SPP-ClientInitiated"); + add_text_node(ctx, upd, "Restriction", "Unrestricted"); + add_text_node_conf(ctx, realm, upd, "URI", "policy_url"); + trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot"); + add_text_node_conf(ctx, realm, trust, "CertURL", + "policy_trust_root_cert_url"); + add_text_node_conf_corrupt(ctx, realm, trust, + "CertSHA256Fingerprint", + "policy_trust_root_cert_fingerprint"); + } +skip_aaa_trust_root: + + upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate"); + add_text_node(ctx, upd, "UpdateInterval", "4294967295"); + add_text_node(ctx, upd, "UpdateMethod", "SPP-ClientInitiated"); + add_text_node(ctx, upd, "Restriction", "HomeSP"); + add_text_node_conf(ctx, realm, upd, "URI", "spp_http_auth_url"); + trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot"); + add_text_node_conf(ctx, realm, trust, "CertURL", "trust_root_cert_url"); + if (test && os_strcmp(test, "corrupt_subrem_hash") == 0) { + debug_print(ctx, 1, + "TEST: Corrupt PPS/Cred*/SubscriptionUpdate/Trustroot/CertSHA256FingerPrint"); + add_text_node_conf_corrupt(ctx, realm, trust, + "CertSHA256Fingerprint", + "trust_root_cert_fingerprint"); + } else { + add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint", + "trust_root_cert_fingerprint"); + } + + if (dmacc_username && + !build_username_password(ctx, upd, dmacc_username, + dmacc_password)) { + xml_node_free(ctx->xml, pps); + xml_node_free(ctx->xml, policy_node); + return NULL; + } + + if (policy_node) + xml_node_add_child(ctx->xml, c, policy_node); + + homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP"); + add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name"); + add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn"); + + xml_node_create(ctx->xml, c, NULL, "SubscriptionParameters"); + + cred = xml_node_create(ctx->xml, c, NULL, "Credential"); + add_creation_date(ctx, cred); + if (imsi) { + xml_node_t *sim; + const char *type = "18"; /* default to EAP-SIM */ + + sim = xml_node_create(ctx->xml, cred, NULL, "SIM"); + add_text_node(ctx, sim, "IMSI", imsi); + if (ctx->eap_method && os_strcmp(ctx->eap_method, "AKA") == 0) + type = "23"; + else if (ctx->eap_method && + os_strcmp(ctx->eap_method, "AKA'") == 0) + type = "50"; + add_text_node(ctx, sim, "EAPType", type); + } else if (cert) { + xml_node_t *dc; + dc = xml_node_create(ctx->xml, cred, NULL, + "DigitalCertificate"); + add_text_node(ctx, dc, "CertificateType", "x509v3"); + add_text_node(ctx, dc, "CertSHA256Fingerprint", cert); + } else { + userpw = build_username_password(ctx, cred, user, pw); + add_text_node(ctx, userpw, "MachineManaged", + machine_managed ? "TRUE" : "FALSE"); + eap = xml_node_create(ctx->xml, userpw, NULL, "EAPMethod"); + add_text_node(ctx, eap, "EAPType", "21"); + add_text_node(ctx, eap, "InnerMethod", "MS-CHAP-V2"); + } + add_text_node(ctx, cred, "Realm", realm); + + return pps; +} + + +static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, + const char *session_id, + const char *user, + const char *realm, + int add_est_user) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *enroll, *exec_node; + char *val; + char password[11]; + char *b64; + + if (add_est_user && new_password(password, sizeof(password)) < 0) + return NULL; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + enroll = xml_node_create(ctx->xml, exec_node, ns, "getCertificate"); + xml_node_add_attr(ctx->xml, enroll, NULL, "enrollmentProtocol", "EST"); + + val = db_get_osu_config_val(ctx, realm, "est_url"); + xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI", + val ? val : ""); + os_free(val); + + if (!add_est_user) + return spp_node; + + xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user); + + b64 = base64_encode(password, strlen(password), NULL); + if (b64 == NULL) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + xml_node_create_text(ctx->xml, enroll, ns, "estPassword", b64); + free(b64); + + db_update_session_password(ctx, user, realm, session_id, password); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx, + const char *session_id, + int enrollment_done) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node = NULL; + xml_node_t *pps, *tnds; + char buf[400]; + char *str; + char *user, *realm, *pw, *type, *mm, *test; + const char *status; + int cert = 0; + int machine_managed = 0; + char *fingerprint; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); + + if (!user || !realm || !pw) { + debug_print(ctx, 1, "Could not find session info from DB for " + "the new subscription"); + free(user); + free(realm); + free(pw); + return NULL; + } + + mm = db_get_session_val(ctx, NULL, NULL, session_id, "machine_managed"); + if (mm && atoi(mm)) + machine_managed = 1; + free(mm); + + type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); + if (type && strcmp(type, "cert") == 0) + cert = 1; + free(type); + + if (cert && !enrollment_done) { + xml_node_t *ret; + hs20_eventlog(ctx, user, realm, session_id, + "request client certificate enrollment", NULL); + ret = spp_exec_get_certificate(ctx, session_id, user, realm, 1); + free(user); + free(realm); + free(pw); + return ret; + } + + if (!cert && strlen(pw) == 0) { + machine_managed = 1; + free(pw); + pw = malloc(11); + if (pw == NULL || new_password(pw, 11) < 0) { + free(user); + free(realm); + free(pw); + return NULL; + } + } + + status = "Provisioning complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + test = db_get_session_val(ctx, NULL, NULL, session_id, "test"); + if (test) + debug_print(ctx, 1, "TEST: Requested special behavior: %s", + test); + pps = build_pps(ctx, user, realm, pw, + fingerprint ? fingerprint : NULL, machine_managed, + test, NULL, NULL, NULL, NULL); + free(fingerprint); + free(test); + if (!pps) { + xml_node_free(ctx->xml, spp_node); + free(user); + free(realm); + free(pw); + return NULL; + } + + debug_print(ctx, 1, "Request DB subscription registration on success " + "notification"); + if (machine_managed) { + db_update_session_password(ctx, user, realm, session_id, pw); + db_update_session_machine_managed(ctx, user, realm, session_id, + machine_managed); + } + db_add_session_pps(ctx, user, realm, session_id, pps); + + hs20_eventlog_node(ctx, user, realm, session_id, + "new subscription", pps); + free(user); + free(pw); + + tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL); + xml_node_free(ctx->xml, pps); + if (!tnds) { + xml_node_free(ctx->xml, spp_node); + free(realm); + return NULL; + } + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) { + xml_node_free(ctx->xml, spp_node); + free(realm); + return NULL; + } + + node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str); + free(str); + snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm); + free(realm); + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf); + xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_free_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + xml_node_t *cred; + char buf[400]; + char *status; + char *free_account, *pw; + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + if (free_account == NULL) + return NULL; + pw = db_get_val(ctx, free_account, realm, "password", 0); + if (pw == NULL) { + free(free_account); + return NULL; + } + + cred = build_credential_pw(ctx, free_account, realm, pw, 1); + free(free_account); + free(pw); + if (!cred) { + xml_node_free(ctx->xml, cred); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "free/public remediation", cred); + xml_node_free(ctx->xml, cred); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_complete(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper == USER_REMEDIATION) { + return hs20_user_input_remediation(ctx, user, realm, dmacc, + session_id); + } + + if (oper == FREE_REMEDIATION) { + return hs20_user_input_free_remediation(ctx, user, realm, + session_id); + } + + if (oper == SUBSCRIPTION_REGISTRATION) { + return hs20_user_input_registration(ctx, session_id, 0); + } + + debug_print(ctx, 1, "User session %s not in state for user input " + "completion", session_id); + return NULL; +} + + +static xml_node_t * hs20_cert_reenroll_complete(struct hs20_svc *ctx, + const char *session_id) +{ + char *user, *realm, *cert; + char *status; + xml_namespace_t *ns; + xml_node_t *spp_node, *cred; + char buf[400]; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + cert = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + if (!user || !realm || !cert) { + debug_print(ctx, 1, + "Could not find session info from DB for certificate reenrollment"); + free(user); + free(realm); + free(cert); + return NULL; + } + + cred = build_credential_cert(ctx, user, realm, cert); + if (!cred) { + debug_print(ctx, 1, "Could not build credential"); + free(user); + free(realm); + free(cert); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) { + debug_print(ctx, 1, "Could not build sppPostDevDataResponse"); + free(user); + free(realm); + free(cert); + xml_node_free(ctx->xml, cred); + return NULL; + } + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + debug_print(ctx, 1, "Could not add update node"); + xml_node_free(ctx->xml, spp_node); + free(user); + free(realm); + free(cert); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate reenrollment", cred); + xml_node_free(ctx->xml, cred); + + free(user); + free(realm); + free(cert); + return spp_node; +} + + +static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + + val = db_get_session_val(ctx, NULL, NULL, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper == SUBSCRIPTION_REGISTRATION) + return hs20_user_input_registration(ctx, session_id, 1); + if (oper == CERT_REENROLL) + return hs20_cert_reenroll_complete(ctx, session_id); + + debug_print(ctx, 1, "User session %s not in state for certificate " + "enrollment completion", session_id); + return NULL; +} + + +static xml_node_t * hs20_cert_enroll_failed(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + xml_node_t *spp_node, *node; + char *status; + xml_namespace_t *ns; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper != SUBSCRIPTION_REGISTRATION) { + debug_print(ctx, 1, "User session %s not in state for " + "enrollment failure", session_id); + return NULL; + } + + status = "Error occurred"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + "Credentials cannot be provisioned at this time"); + db_remove_session(ctx, user, realm, session_id); + + return spp_node; +} + + +static xml_node_t * hs20_sim_provisioning(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node = NULL; + xml_node_t *pps, *tnds; + char buf[400]; + char *str; + const char *status; + char dmacc_username[32]; + char dmacc_password[32]; + char *policy; + xml_node_t *policy_node = NULL; + + if (!ctx->imsi) { + debug_print(ctx, 1, "IMSI not available for SIM provisioning"); + return NULL; + } + + if (new_password(dmacc_username, sizeof(dmacc_username)) < 0 || + new_password(dmacc_password, sizeof(dmacc_password)) < 0) { + debug_print(ctx, 1, + "Failed to generate DMAcc username/password"); + return NULL; + } + + status = "Provisioning complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (!spp_node) + return NULL; + + policy = db_get_osu_config_val(ctx, realm, "sim_policy"); + if (policy) { + policy_node = read_policy_file(ctx, policy); + os_free(policy); + if (!policy_node) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + update_policy_update_uri(ctx, realm, policy_node); + node = get_node_uri(ctx->xml, policy_node, + "Policy/PolicyUpdate"); + if (node) + build_username_password(ctx, node, dmacc_username, + dmacc_password); + } + + pps = build_pps(ctx, NULL, realm, NULL, NULL, 0, NULL, ctx->imsi, + dmacc_username, dmacc_password, policy_node); + if (!pps) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + debug_print(ctx, 1, + "Request DB subscription registration on success notification"); + if (!user || !user[0]) + user = ctx->imsi; + db_add_session(ctx, user, realm, session_id, NULL, NULL, + SUBSCRIPTION_REGISTRATION, NULL); + db_add_session_dmacc(ctx, session_id, dmacc_username, dmacc_password); + if (ctx->eap_method) + db_add_session_eap_method(ctx, session_id, ctx->eap_method); + if (ctx->id_hash) + db_add_session_id_hash(ctx, session_id, ctx->id_hash); + db_add_session_pps(ctx, user, realm, session_id, pps); + + hs20_eventlog_node(ctx, user, realm, session_id, + "new subscription", pps); + + tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL); + xml_node_free(ctx->xml, pps); + if (!tnds) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (!str) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str); + free(str); + snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm); + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf); + xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS); + + return spp_node; +} + + +static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx, + xml_node_t *node, + const char *user, + const char *realm, + const char *session_id, + int dmacc) +{ + const char *req_reason; + char *redirect_uri = NULL; + char *req_reason_buf = NULL; + char str[200]; + xml_node_t *ret = NULL, *devinfo = NULL, *devdetail = NULL; + xml_node_t *mo, *macaddr; + char *version; + int valid; + char *supp, *pos; + char *err; + u8 wifi_mac_addr[ETH_ALEN]; + + version = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sppVersion"); + if (version == NULL || strstr(version, "1.0") == NULL) { + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "SPP version not supported"); + hs20_eventlog_node(ctx, user, realm, session_id, + "Unsupported sppVersion", ret); + xml_node_get_attr_value_free(ctx->xml, version); + return ret; + } + xml_node_get_attr_value_free(ctx->xml, version); + + mo = get_node(ctx->xml, node, "supportedMOList"); + if (mo == NULL) { + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No supportedMOList element", ret); + return ret; + } + supp = xml_node_get_text(ctx->xml, mo); + for (pos = supp; pos && *pos; pos++) + *pos = tolower(*pos); + if (supp == NULL || + strstr(supp, URN_OMA_DM_DEVINFO) == NULL || + strstr(supp, URN_OMA_DM_DEVDETAIL) == NULL || + strstr(supp, URN_HS20_PPS) == NULL) { + xml_node_get_text_free(ctx->xml, supp); + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "One or more mandatory MOs not supported"); + hs20_eventlog_node(ctx, user, realm, session_id, + "Unsupported MOs", ret); + return ret; + } + xml_node_get_text_free(ctx->xml, supp); + + req_reason_buf = xml_node_get_attr_value(ctx->xml, node, + "requestReason"); + if (req_reason_buf == NULL) { + debug_print(ctx, 1, "No requestReason attribute"); + return NULL; + } + req_reason = req_reason_buf; + + redirect_uri = xml_node_get_attr_value(ctx->xml, node, "redirectURI"); + + debug_print(ctx, 1, "requestReason: %s sessionID: %s redirectURI: %s", + req_reason, session_id, redirect_uri); + snprintf(str, sizeof(str), "sppPostDevData: requestReason=%s", + req_reason); + hs20_eventlog(ctx, user, realm, session_id, str, NULL); + + devinfo = spp_get_mo(ctx, node, URN_OMA_DM_DEVINFO, &valid, &err); + if (devinfo == NULL) { + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No DevInfo moContainer in sppPostDevData", + ret); + os_free(err); + goto out; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "Received DevInfo MO", devinfo); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors in DevInfo MO", + err); + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + os_free(err); + goto out; + } + os_free(err); + if (user) + db_update_mo(ctx, user, realm, "devinfo", devinfo); + + devdetail = spp_get_mo(ctx, node, URN_OMA_DM_DEVDETAIL, &valid, &err); + if (devdetail == NULL) { + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No DevDetail moContainer in sppPostDevData", + ret); + os_free(err); + goto out; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "Received DevDetail MO", devdetail); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors " + "in DevDetail MO", err); + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + os_free(err); + goto out; + } + os_free(err); + + os_memset(wifi_mac_addr, 0, ETH_ALEN); + macaddr = get_node(ctx->xml, devdetail, + "Ext/org.wi-fi/Wi-Fi/Wi-FiMACAddress"); + if (macaddr) { + char *addr, buf[50]; + + addr = xml_node_get_text(ctx->xml, macaddr); + if (addr && hwaddr_compact_aton(addr, wifi_mac_addr) == 0) { + snprintf(buf, sizeof(buf), "DevDetail MAC address: " + MACSTR, MAC2STR(wifi_mac_addr)); + hs20_eventlog(ctx, user, realm, session_id, buf, NULL); + xml_node_get_text_free(ctx->xml, addr); + } else { + hs20_eventlog(ctx, user, realm, session_id, + "Could not extract MAC address from DevDetail", + NULL); + } + } else { + hs20_eventlog(ctx, user, realm, session_id, + "No MAC address in DevDetail", NULL); + } + + if (user) + db_update_mo(ctx, user, realm, "devdetail", devdetail); + + if (user) + mo = spp_get_mo(ctx, node, URN_HS20_PPS, &valid, &err); + else { + mo = NULL; + err = NULL; + } + if (user && mo) { + hs20_eventlog_node(ctx, user, realm, session_id, + "Received PPS MO", mo); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors " + "in PPS MO", err); + xml_node_get_attr_value_free(ctx->xml, redirect_uri); + os_free(err); + return build_post_dev_data_response( + ctx, NULL, session_id, + "Error occurred", "Other"); + } + db_update_mo(ctx, user, realm, "pps", mo); + db_update_val(ctx, user, realm, "fetch_pps", "0", dmacc); + xml_node_free(ctx->xml, mo); + } + os_free(err); + + if (user && !mo) { + char *fetch; + int fetch_pps; + + fetch = db_get_val(ctx, user, realm, "fetch_pps", dmacc); + fetch_pps = fetch ? atoi(fetch) : 0; + free(fetch); + + if (fetch_pps) { + enum hs20_session_operation oper; + if (strcasecmp(req_reason, "Subscription remediation") + == 0) + oper = CONTINUE_SUBSCRIPTION_REMEDIATION; + else if (strcasecmp(req_reason, "Policy update") == 0) + oper = CONTINUE_POLICY_UPDATE; + else + oper = NO_OPERATION; + if (db_add_session(ctx, user, realm, session_id, NULL, + NULL, oper, NULL) < 0) + goto out; + + ret = spp_exec_upload_mo(ctx, session_id, + URN_HS20_PPS); + hs20_eventlog_node(ctx, user, realm, session_id, + "request PPS MO upload", + ret); + goto out; + } + } + + if (user && strcasecmp(req_reason, "MO upload") == 0) { + char *val = db_get_session_val(ctx, user, realm, session_id, + "operation"); + enum hs20_session_operation oper; + if (!val) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + goto out; + } + oper = atoi(val); + free(val); + if (oper == CONTINUE_SUBSCRIPTION_REMEDIATION) + req_reason = "Subscription remediation"; + else if (oper == CONTINUE_POLICY_UPDATE) + req_reason = "Policy update"; + else { + debug_print(ctx, 1, + "No pending operation in session %s", + session_id); + goto out; + } + } + + if (strcasecmp(req_reason, "Subscription registration") == 0) { + ret = hs20_subscription_registration(ctx, realm, session_id, + redirect_uri, + wifi_mac_addr); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription registration response", + ret); + goto out; + } + if (user && strcasecmp(req_reason, "Subscription remediation") == 0) { + ret = hs20_subscription_remediation(ctx, user, realm, + session_id, dmacc, + redirect_uri); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription remediation response", + ret); + goto out; + } + if (user && strcasecmp(req_reason, "Policy update") == 0) { + ret = hs20_policy_update(ctx, user, realm, session_id, dmacc); + hs20_eventlog_node(ctx, user, realm, session_id, + "policy update response", + ret); + goto out; + } + + if (strcasecmp(req_reason, "User input completed") == 0) { + db_add_session_devinfo(ctx, session_id, devinfo); + db_add_session_devdetail(ctx, session_id, devdetail); + ret = hs20_user_input_complete(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "user input completed response", ret); + goto out; + } + + if (strcasecmp(req_reason, "Certificate enrollment completed") == 0) { + ret = hs20_cert_enroll_completed(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate enrollment response", ret); + goto out; + } + + if (strcasecmp(req_reason, "Certificate enrollment failed") == 0) { + ret = hs20_cert_enroll_failed(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate enrollment failed response", + ret); + goto out; + } + + if (strcasecmp(req_reason, "Subscription provisioning") == 0) { + ret = hs20_sim_provisioning(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription provisioning response", + ret); + goto out; + } + + debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'", + req_reason, user); +out: + xml_node_get_attr_value_free(ctx->xml, req_reason_buf); + xml_node_get_attr_value_free(ctx->xml, redirect_uri); + if (devinfo) + xml_node_free(ctx->xml, devinfo); + if (devdetail) + xml_node_free(ctx->xml, devdetail); + return ret; +} + + +static xml_node_t * build_spp_exchange_complete(struct hs20_svc *ctx, + const char *session_id, + const char *status, + const char *error_code) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppExchangeComplete"); + + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); + + if (error_code) { + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int add_subscription(struct hs20_svc *ctx, const char *session_id) +{ + char *user, *realm, *pw, *pw_mm, *pps, *str; + char *osu_user, *osu_password, *eap_method; + char *policy = NULL; + char *sql; + int ret = -1; + char *free_account; + int free_acc; + char *type; + int cert = 0; + char *cert_pem, *fingerprint; + const char *method; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); + pw_mm = db_get_session_val(ctx, NULL, NULL, session_id, + "machine_managed"); + pps = db_get_session_val(ctx, NULL, NULL, session_id, "pps"); + cert_pem = db_get_session_val(ctx, NULL, NULL, session_id, "cert_pem"); + fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); + if (type && strcmp(type, "cert") == 0) + cert = 1; + free(type); + osu_user = db_get_session_val(ctx, NULL, NULL, session_id, "osu_user"); + osu_password = db_get_session_val(ctx, NULL, NULL, session_id, + "osu_password"); + eap_method = db_get_session_val(ctx, NULL, NULL, session_id, + "eap_method"); + + if (!user || !realm || !pw) { + debug_print(ctx, 1, "Could not find session info from DB for " + "the new subscription"); + goto out; + } + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + free_acc = free_account && strcmp(free_account, user) == 0; + free(free_account); + + policy = db_get_osu_config_val(ctx, realm, "sim_policy"); + + debug_print(ctx, 1, + "New subscription: user='%s' realm='%s' free_acc=%d", + user, realm, free_acc); + debug_print(ctx, 1, "New subscription: pps='%s'", pps); + + sql = sqlite3_mprintf("UPDATE eventlog SET user=%Q, realm=%Q WHERE " + "sessionid=%Q AND (user='' OR user IS NULL)", + user, realm, session_id); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update eventlog in " + "sqlite database: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); + } + + if (free_acc) { + hs20_eventlog(ctx, user, realm, session_id, + "completed shared free account registration", + NULL); + ret = 0; + goto out; + } + + str = db_get_session_val(ctx, NULL, NULL, session_id, "mac_addr"); + + if (eap_method && eap_method[0]) + method = eap_method; + else + method = cert ? "TLS" : "TTLS-MSCHAPV2"; + sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr,osu_user,osu_password,policy) VALUES (%Q,%Q,%d,%Q,%Q,%Q,%d,%Q,%Q,%Q,%Q)", + user, realm, cert ? 0 : 1, + method, + fingerprint ? fingerprint : "", + cert_pem ? cert_pem : "", + pw_mm && atoi(pw_mm) ? 1 : 0, + str ? str : "", + osu_user ? osu_user : "", + osu_password ? osu_password : "", + policy ? policy : ""); + free(str); + if (sql == NULL) + goto out; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add user in sqlite database: %s", + sqlite3_errmsg(ctx->db)); + sqlite3_free(sql); + goto out; + } + sqlite3_free(sql); + + if (cert) + ret = 0; + else + ret = update_password(ctx, user, realm, pw, 0); + if (ret < 0) { + sql = sqlite3_mprintf("DELETE FROM users WHERE identity=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')", + user, realm); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + sqlite3_exec(ctx->db, sql, NULL, NULL, NULL); + sqlite3_free(sql); + } + } + + if (pps) + db_update_mo_str(ctx, user, realm, "pps", pps); + + str = db_get_session_val(ctx, NULL, NULL, session_id, "devinfo"); + if (str) { + db_update_mo_str(ctx, user, realm, "devinfo", str); + free(str); + } + + str = db_get_session_val(ctx, NULL, NULL, session_id, "devdetail"); + if (str) { + db_update_mo_str(ctx, user, realm, "devdetail", str); + free(str); + } + + if (cert && user) { + const char *serialnum; + + str = db_get_session_val(ctx, NULL, NULL, session_id, + "mac_addr"); + + if (os_strncmp(user, "cert-", 5) == 0) + serialnum = user + 5; + else + serialnum = ""; + sql = sqlite3_mprintf("INSERT OR REPLACE INTO cert_enroll (mac_addr,user,realm,serialnum) VALUES(%Q,%Q,%Q,%Q)", + str ? str : "", user, realm ? realm : "", + serialnum); + free(str); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != + SQLITE_OK) { + debug_print(ctx, 1, + "Failed to add cert_enroll entry into sqlite database: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); + } + } + + str = db_get_session_val(ctx, NULL, NULL, session_id, + "mobile_identifier_hash"); + if (str) { + sql = sqlite3_mprintf("DELETE FROM sim_provisioning WHERE mobile_identifier_hash=%Q", + str); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != + SQLITE_OK) { + debug_print(ctx, 1, + "Failed to delete pending sim_provisioning entry: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); + } + os_free(str); + } + + if (ret == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "completed subscription registration", NULL); + } + +out: + free(user); + free(realm); + free(pw); + free(pw_mm); + free(pps); + free(cert_pem); + free(fingerprint); + free(osu_user); + free(osu_password); + free(eap_method); + os_free(policy); + return ret; +} + + +static xml_node_t * hs20_spp_update_response(struct hs20_svc *ctx, + xml_node_t *node, + const char *user, + const char *realm, + const char *session_id, + int dmacc) +{ + char *status; + xml_node_t *ret; + char *val; + enum hs20_session_operation oper; + + status = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sppStatus"); + if (status == NULL) { + debug_print(ctx, 1, "No sppStatus attribute"); + return NULL; + } + + debug_print(ctx, 1, "sppUpdateResponse: sppStatus: %s sessionID: %s", + status, session_id); + + val = db_get_session_val(ctx, NULL, NULL, session_id, "operation"); + if (!val) { + debug_print(ctx, 1, + "No session active for sessionID: %s", + session_id); + oper = NO_OPERATION; + } else + oper = atoi(val); + + if (strcasecmp(status, "OK") == 0) { + char *new_pw = NULL; + + xml_node_get_attr_value_free(ctx->xml, status); + + if (oper == USER_REMEDIATION) { + new_pw = db_get_session_val(ctx, user, realm, + session_id, "password"); + if (new_pw == NULL || strlen(new_pw) == 0) { + free(new_pw); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "No password " + "had been assigned for " + "session", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + oper = UPDATE_PASSWORD; + } + if (oper == UPDATE_PASSWORD) { + if (!new_pw) { + new_pw = db_get_session_val(ctx, user, realm, + session_id, + "password"); + if (!new_pw) { + db_remove_session(ctx, user, realm, + session_id); + return NULL; + } + } + debug_print(ctx, 1, "Update user '%s' password in DB", + user); + if (update_password(ctx, user, realm, new_pw, dmacc) < + 0) { + debug_print(ctx, 1, "Failed to update user " + "'%s' password in DB", user); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "Failed to " + "update database", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + hs20_eventlog(ctx, user, realm, + session_id, "Updated user password " + "in database", NULL); + } + if (oper == CLEAR_REMEDIATION) { + debug_print(ctx, 1, + "Clear remediation requirement for user '%s' in DB", + user); + if (clear_remediation(ctx, user, realm, dmacc) < 0) { + debug_print(ctx, 1, + "Failed to clear remediation requirement for user '%s' in DB", + user); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, + "Failed to update database", + ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + hs20_eventlog(ctx, user, realm, + session_id, + "Cleared remediation requirement in database", + NULL); + } + if (oper == SUBSCRIPTION_REGISTRATION) { + if (add_subscription(ctx, session_id) < 0) { + debug_print(ctx, 1, "Failed to add " + "subscription into DB"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "Failed to " + "update database", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + } + if (oper == POLICY_REMEDIATION || oper == POLICY_UPDATE) { + char *val; + val = db_get_val(ctx, user, realm, "remediation", + dmacc); + if (val && strcmp(val, "policy") == 0) + db_update_val(ctx, user, realm, "remediation", + "", dmacc); + free(val); + } + if (oper == POLICY_UPDATE) + db_update_val(ctx, user, realm, "polupd_done", "1", + dmacc); + if (oper == CERT_REENROLL) { + char *new_user; + char event[200]; + + new_user = db_get_session_val(ctx, NULL, NULL, + session_id, "user"); + if (!new_user) { + debug_print(ctx, 1, + "Failed to find new user name (cert-serialnum)"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, + "Failed to find new user name (cert reenroll)", + ret); + db_remove_session(ctx, NULL, NULL, session_id); + return ret; + } + + debug_print(ctx, 1, + "Update certificate user entry to use the new serial number (old=%s new=%s)", + user, new_user); + os_snprintf(event, sizeof(event), "renamed user to: %s", + new_user); + hs20_eventlog(ctx, user, realm, session_id, event, + NULL); + + if (db_update_val(ctx, user, realm, "identity", + new_user, 0) < 0 || + db_update_val(ctx, new_user, realm, "remediation", + "", 0) < 0) { + debug_print(ctx, 1, + "Failed to update user name (cert-serialnum)"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, + "Failed to update user name (cert reenroll)", + ret); + db_remove_session(ctx, NULL, NULL, session_id); + os_free(new_user); + return ret; + } + + os_free(new_user); + } + ret = build_spp_exchange_complete( + ctx, session_id, + "Exchange complete, release TLS connection", NULL); + hs20_eventlog_node(ctx, user, realm, session_id, + "Exchange completed", ret); + db_remove_session(ctx, NULL, NULL, session_id); + return ret; + } + + ret = build_spp_exchange_complete(ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, "Error occurred", ret); + db_remove_session(ctx, user, realm, session_id); + xml_node_get_attr_value_free(ctx->xml, status); + return ret; +} + + +#define SPP_SESSION_ID_LEN 16 + +static char * gen_spp_session_id(void) +{ + FILE *f; + int i; + char *session; + + session = os_malloc(SPP_SESSION_ID_LEN * 2 + 1); + if (session == NULL) + return NULL; + + f = fopen("/dev/urandom", "r"); + if (f == NULL) { + os_free(session); + return NULL; + } + for (i = 0; i < SPP_SESSION_ID_LEN; i++) + os_snprintf(session + i * 2, 3, "%02x", fgetc(f)); + + fclose(f); + return session; +} + +xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node, + const char *auth_user, + const char *auth_realm, int dmacc) +{ + xml_node_t *ret = NULL; + char *session_id; + const char *op_name; + char *xml_err; + char fname[200]; + + debug_dump_node(ctx, "received request", node); + + if (!dmacc && auth_user && auth_realm) { + char *real; + real = db_get_val(ctx, auth_user, auth_realm, "identity", 0); + if (!real) { + real = db_get_val(ctx, auth_user, auth_realm, + "identity", 1); + if (real) + dmacc = 1; + } + os_free(real); + } + + snprintf(fname, sizeof(fname), "%s/spp/spp.xsd", ctx->root_dir); + if (xml_validate(ctx->xml, node, fname, &xml_err) < 0) { + /* + * We may not be able to extract the sessionID from invalid + * input, but well, we can try. + */ + session_id = xml_node_get_attr_value_ns(ctx->xml, node, + SPP_NS_URI, + "sessionID"); + debug_print(ctx, 1, + "SPP message failed validation, xsd file: %s xml-error: %s", + fname, xml_err); + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "SPP message failed validation", node); + hs20_eventlog(ctx, auth_user, auth_realm, session_id, + "Validation errors", xml_err); + os_free(xml_err); + xml_node_get_attr_value_free(ctx->xml, session_id); + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppValidationError"); + return ret; + } + + session_id = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sessionID"); + if (session_id) { + char *tmp; + debug_print(ctx, 1, "Received sessionID %s", session_id); + tmp = os_strdup(session_id); + xml_node_get_attr_value_free(ctx->xml, session_id); + if (tmp == NULL) + return NULL; + session_id = tmp; + } else { + session_id = gen_spp_session_id(); + if (session_id == NULL) { + debug_print(ctx, 1, "Failed to generate sessionID"); + return NULL; + } + debug_print(ctx, 1, "Generated sessionID %s", session_id); + } + + op_name = xml_node_get_localname(ctx->xml, node); + if (op_name == NULL) { + debug_print(ctx, 1, "Could not get op_name"); + return NULL; + } + + if (strcmp(op_name, "sppPostDevData") == 0) { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "sppPostDevData received and validated", + node); + ret = hs20_spp_post_dev_data(ctx, node, auth_user, auth_realm, + session_id, dmacc); + } else if (strcmp(op_name, "sppUpdateResponse") == 0) { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "sppUpdateResponse received and validated", + node); + ret = hs20_spp_update_response(ctx, node, auth_user, + auth_realm, session_id, dmacc); + } else { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "Unsupported SPP message received and " + "validated", node); + debug_print(ctx, 1, "Unsupported operation '%s'", op_name); + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppUnknownCommandError"); + } + os_free(session_id); + + if (ret == NULL) { + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppInternalError"); + } + + return ret; +} + + +int hs20_spp_server_init(struct hs20_svc *ctx) +{ + char fname[200]; + ctx->db = NULL; + snprintf(fname, sizeof(fname), "%s/AS/DB/eap_user.db", ctx->root_dir); + if (sqlite3_open(fname, &ctx->db)) { + printf("Failed to open sqlite database: %s\n", + sqlite3_errmsg(ctx->db)); + sqlite3_close(ctx->db); + return -1; + } + + return 0; +} + + +void hs20_spp_server_deinit(struct hs20_svc *ctx) +{ + sqlite3_close(ctx->db); + ctx->db = NULL; +} diff --git a/contrib/wpa/hs20/server/spp_server.h b/contrib/wpa/hs20/server/spp_server.h new file mode 100644 index 000000000000..421974c607b8 --- /dev/null +++ b/contrib/wpa/hs20/server/spp_server.h @@ -0,0 +1,36 @@ +/* + * Hotspot 2.0 SPP server + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef SPP_SERVER_H +#define SPP_SERVER_H + +struct hs20_svc { + const void *ctx; + struct xml_node_ctx *xml; + char *root_dir; + FILE *debug_log; + sqlite3 *db; + const char *addr; + const char *test; + const char *imsi; + const char *eap_method; + const char *id_hash; +}; + + +void debug_print(struct hs20_svc *ctx, int print, const char *fmt, ...) + __attribute__ ((format (printf, 3, 4))); +void debug_dump_node(struct hs20_svc *ctx, const char *title, xml_node_t *node); + +xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node, + const char *auth_user, + const char *auth_realm, int dmacc); +int hs20_spp_server_init(struct hs20_svc *ctx); +void hs20_spp_server_deinit(struct hs20_svc *ctx); + +#endif /* SPP_SERVER_H */ diff --git a/contrib/wpa/hs20/server/sql-example.txt b/contrib/wpa/hs20/server/sql-example.txt new file mode 100644 index 000000000000..20dcf2f5c688 --- /dev/null +++ b/contrib/wpa/hs20/server/sql-example.txt @@ -0,0 +1,17 @@ +INSERT INTO osu_config(realm,field,value) VALUES('example.com','fqdn','example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','friendly_name','Example Operator'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','spp_http_auth_url','https://subscription-server.osu.example.com/hs20/spp.php?realm=example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','trust_root_cert_url','https://osu-server.osu.example.com/hs20/files/spp-root-ca.der'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','trust_root_cert_fingerprint','5b393a9246865569485c2605c3304e48212b449367858299beba9384c4cf4647'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','aaa_trust_root_cert_url','https://osu-server.osu.example.com/hs20/files/aaa-root-ca.der'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','aaa_trust_root_cert_fingerprint','5b393a9246865569485c2605c3304e48212b449367858299beba9384c4cf4647'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','free_account','free'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','policy_url','https://subscription-server.osu.example.com/hs20/spp.php?realm=example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','remediation_url','https://subscription-server.osu.example.com/hs20/remediation.php?session_id='); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','free_remediation_url','https://subscription-server.osu.example.com/hs20/free-remediation.php?session_id='); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','signup_url','https://subscription-server.osu.example.com/hs20/signup.php?session_id='); + + +INSERT INTO users(identity,realm,methods,password,phase2,shared) VALUES('free','example.com','TTLS-MSCHAPV2','free',1,1); + +INSERT INTO wildcards(identity,methods) VALUES('','TTLS,TLS'); diff --git a/contrib/wpa/hs20/server/sql.txt b/contrib/wpa/hs20/server/sql.txt new file mode 100644 index 000000000000..2cc6edea4063 --- /dev/null +++ b/contrib/wpa/hs20/server/sql.txt @@ -0,0 +1,108 @@ +CREATE TABLE eventlog( + user TEXT, + realm TEXT, + sessionid TEXT COLLATE NOCASE, + timestamp TEXT, + notes TEXT, + dump TEXT, + addr TEXT +); + +CREATE TABLE sessions( + timestamp TEXT, + id TEXT COLLATE NOCASE, + user TEXT, + realm TEXT, + password TEXT, + machine_managed BOOLEAN, + operation INTEGER, + type TEXT, + pps TEXT, + redirect_uri TEXT, + devinfo TEXT, + devdetail TEXT, + cert TEXT, + cert_pem TEXT, + mac_addr TEXT, + osu_user TEXT, + osu_password TEXT, + eap_method TEXT, + mobile_identifier_hash TEXT, + test TEXT +); + +CREATE index sessions_id_index ON sessions(id); + +CREATE TABLE osu_config( + realm TEXT, + field TEXT, + value TEXT +); + +CREATE TABLE users( + identity TEXT PRIMARY KEY, + methods TEXT, + password TEXT, + machine_managed BOOLEAN, + remediation TEXT, + phase2 INTEGER, + realm TEXT, + policy TEXT, + devinfo TEXT, + devdetail TEXT, + pps TEXT, + fetch_pps INTEGER, + osu_user TEXT, + osu_password TEXT, + shared INTEGER, + cert TEXT, + cert_pem TEXT, + t_c_timestamp INTEGER, + mac_addr TEXT, + last_msk TEXT, + polupd_done TEXT, + subrem TEXT +); + +CREATE TABLE wildcards( + identity TEXT PRIMARY KEY, + methods TEXT +); + +CREATE TABLE authlog( + timestamp TEXT, + session TEXT, + nas_ip TEXT, + username TEXT, + note TEXT +); + +CREATE TABLE pending_tc( + mac_addr TEXT PRIMARY KEY, + identity TEXT +); + +CREATE TABLE current_sessions( + mac_addr TEXT PRIMARY KEY, + identity TEXT, + start_time TEXT, + nas TEXT, + hs20_t_c_filtering BOOLEAN, + waiting_coa_ack BOOLEAN, + coa_ack_received BOOLEAN +); + +CREATE TABLE cert_enroll( + mac_addr TEXT PRIMARY KEY, + user TEXT, + realm TEXT, + serialnum TEXT +); + +CREATE TABLE sim_provisioning( + mobile_identifier_hash TEXT PRIMARY KEY, + imsi TEXT, + mac_addr TEXT, + eap_method TEXT, + timestamp TEXT +); diff --git a/contrib/wpa/hs20/server/www/add-free.php b/contrib/wpa/hs20/server/www/add-free.php new file mode 100644 index 000000000000..1efc65563274 --- /dev/null +++ b/contrib/wpa/hs20/server/www/add-free.php @@ -0,0 +1,50 @@ +query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; +$realm = $row['realm']; + +$row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch(); +if (!$row || strlen($row['value']) == 0) { + die("Free account disabled"); +} + +$user = $row['value']; + +$row = $db->query("SELECT password FROM users WHERE identity='$user' AND realm='$realm'")->fetch(); +if (!$row) + die("Free account not found"); + +$pw = $row['password']; + +if (!$db->exec("UPDATE sessions SET user='$user', password='$pw', realm='$realm', machine_managed='1' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for a new PPS MO')"); + +header("Location: $uri", true, 302); + +?> diff --git a/contrib/wpa/hs20/server/www/add-mo.php b/contrib/wpa/hs20/server/www/add-mo.php new file mode 100644 index 000000000000..a3b4513531f8 --- /dev/null +++ b/contrib/wpa/hs20/server/www/add-mo.php @@ -0,0 +1,56 @@ +

Invalid username

\n"; + echo "Try again\n"; + echo "\n"; + exit; +} + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} +$realm = $row['realm']; + +$userrow = $db->query("SELECT identity FROM users WHERE identity='$user' AND realm='$realm'")->fetch(); +if ($userrow) { + echo "

Selected username is not available

\n"; + echo "Try again\n"; + echo "\n"; + exit; +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; + +if (!$db->exec("UPDATE sessions SET user='$user', password='$pw', realm='$realm', type='password' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for a new PPS MO')"); + +header("Location: $uri", true, 302); + +?> diff --git a/contrib/wpa/hs20/server/www/cert-enroll.php b/contrib/wpa/hs20/server/www/cert-enroll.php new file mode 100644 index 000000000000..f023ca5a5b03 --- /dev/null +++ b/contrib/wpa/hs20/server/www/cert-enroll.php @@ -0,0 +1,39 @@ +query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; +$realm = $row['realm']; + +$user = sha1(mt_rand()); + +if (!$db->exec("UPDATE sessions SET user='$user', type='cert' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for client certificate enrollment')"); + +header("Location: $uri", true, 302); + +?> diff --git a/contrib/wpa/hs20/server/www/config.php b/contrib/wpa/hs20/server/www/config.php new file mode 100644 index 000000000000..4272b102a88c --- /dev/null +++ b/contrib/wpa/hs20/server/www/config.php @@ -0,0 +1,7 @@ + diff --git a/contrib/wpa/hs20/server/www/est.php b/contrib/wpa/hs20/server/www/est.php new file mode 100644 index 000000000000..b7fb260d56c4 --- /dev/null +++ b/contrib/wpa/hs20/server/www/est.php @@ -0,0 +1,232 @@ +1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, + 'uri'=>1, 'response'=>1); + $data = array(); + $keys = implode('|', array_keys($needed)); + preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', + $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed[$m[1]]); + } + if ($needed) { + error_log("EST: Missing auth parameter"); + die('Authentication failed'); + } + $user = $data['username']; + if (strlen($user) < 1) { + error_log("EST: Empty username"); + die('Authentication failed'); + } + + $sql = "SELECT rowid,password,operation FROM sessions " . + "WHERE user='$user' AND realm='$realm'"; + $q = $db->query($sql); + if (!$q) { + error_log("EST: Session not found for user=$user realm=$realm"); + die("Session not found"); + } + $row = $q->fetch(); + if (!$row) { + error_log("EST: Session fetch failed for user=$user realm=$realm"); + die('Session not found'); + } + $rowid = $row['rowid']; + + $oper = $row['operation']; + if ($oper != '5') { + error_log("EST: Unexpected operation $oper for user=$user realm=$realm"); + die("Session not found"); + } + $pw = $row['password']; + if (strlen($pw) < 1) { + error_log("EST: Empty password for user=$user realm=$realm"); + die('Authentication failed'); + } + + $A1 = md5($user . ':' . $realm . ':' . $pw); + $A2 = md5($method . ':' . $data['uri']); + $resp = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . + $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); + if ($data['response'] != $resp) { + error_log("EST: Incorrect authentication response for user=$user realm=$realm"); + die('Authentication failed'); + } +} else if (isset($_SERVER["SSL_CLIENT_VERIFY"]) && + $_SERVER["SSL_CLIENT_VERIFY"] == "SUCCESS" && + isset($_SERVER["SSL_CLIENT_M_SERIAL"])) { + $user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"]; + $sql = "SELECT rowid,password,operation FROM sessions " . + "WHERE user='$user' AND realm='$realm'"; + $q = $db->query($sql); + if (!$q) { + error_log("EST: Session not found for user=$user realm=$realm"); + die("Session not found"); + } + $row = $q->fetch(); + if (!$row) { + error_log("EST: Session fetch failed for user=$user realm=$realm"); + die('Session not found'); + } + $rowid = $row['rowid']; + + $oper = $row['operation']; + if ($oper != '10') { + error_log("EST: Unexpected operation $oper for user=$user realm=$realm"); + die("Session not found"); + } +} + + +if ($method == "GET" && $cmd == "cacerts") { + $fname = "$osu_root/est/$realm-cacerts.pkcs7"; + if (!file_exists($fname)) { + error_log("EST: cacerts - unknown realm $realm"); + die("Unknown realm"); + } + + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/pkcs7-mime"); + + $data = file_get_contents($fname); + echo wordwrap(base64_encode($data), 72, "\n", true); + echo "\n"; + error_log("EST: cacerts"); +} else if ($method == "GET" && $cmd == "csrattrs") { + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/csrattrs"); + readfile("$osu_root/est/est-attrs.b64"); + error_log("EST: csrattrs"); +} else if ($method == "POST" && + ($cmd == "simpleenroll" || $cmd == "simplereenroll")) { + $reenroll = $cmd == "simplereenroll"; + if (!$reenroll && (!isset($user) || strlen($user) == 0)) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("EST: simpleenroll - require authentication"); + die('Authentication required'); + } + if ($reenroll && + (!isset($user) || + !isset($_SERVER["SSL_CLIENT_VERIFY"]) || + $_SERVER["SSL_CLIENT_VERIFY"] != "SUCCESS")) { + header('HTTP/1.1 403 Forbidden'); + error_log("EST: simplereenroll - require certificate authentication"); + die('Authentication required'); + } + if (!isset($_SERVER["CONTENT_TYPE"])) { + error_log("EST: simpleenroll without Content-Type"); + die("Missing Content-Type"); + } + if (!stristr($_SERVER["CONTENT_TYPE"], "application/pkcs10")) { + error_log("EST: simpleenroll - unexpected Content-Type: " . + $_SERVER["CONTENT_TYPE"]); + die("Unexpected Content-Type"); + } + + $data = file_get_contents("php://input"); + error_log("EST: simpleenroll - POST data from php://input: " . $data); + $req = base64_decode($data); + if ($req == FALSE) { + error_log("EST: simpleenroll - Invalid base64-encoded PKCS#10 data"); + die("Invalid base64-encoded PKCS#10 data"); + } + $cadir = "$osu_root/est"; + $reqfile = "$cadir/tmp/cert-req.pkcs10"; + $f = fopen($reqfile, "wb"); + fwrite($f, $req); + fclose($f); + + $req_pem = "$reqfile.pem"; + if (file_exists($req_pem)) + unlink($req_pem); + exec("openssl req -in $reqfile -inform DER -out $req_pem -outform PEM"); + if (!file_exists($req_pem)) { + error_log("EST: simpleenroll - Failed to parse certificate request"); + die("Failed to parse certificate request"); + } + + /* FIX: validate request and add HS 2.0 extensions to cert */ + $cert_pem = "$cadir/tmp/req-signed.pem"; + if (file_exists($cert_pem)) + unlink($cert_pem); + exec("openssl x509 -req -in $req_pem -CAkey $cadir/cakey.pem -out $cert_pem -CA $cadir/cacert.pem -CAserial $cadir/serial -days 365 -text"); + if (!file_exists($cert_pem)) { + error_log("EST: simpleenroll - Failed to sign certificate"); + die("Failed to sign certificate"); + } + + $cert = file_get_contents($cert_pem); + $handle = popen("openssl x509 -in $cert_pem -serial -noout", "r"); + $serial = fread($handle, 200); + pclose($handle); + $pattern = "/serial=(?P[0-9a-fA-F:]*)/m"; + preg_match($pattern, $serial, $matches); + if (!isset($matches['snhex']) || strlen($matches['snhex']) < 1) { + error_log("EST: simpleenroll - Could not get serial number"); + die("Could not get serial number"); + } + $sn = str_replace(":", "", strtoupper($matches['snhex'])); + + $user = "cert-$sn"; + error_log("EST: user = $user"); + + $cert_der = "$cadir/tmp/req-signed.der"; + if (file_exists($cert_der)) + unlink($cert_der); + exec("openssl x509 -in $cert_pem -inform PEM -out $cert_der -outform DER"); + if (!file_exists($cert_der)) { + error_log("EST: simpleenroll - Failed to convert certificate"); + die("Failed to convert certificate"); + } + $der = file_get_contents($cert_der); + $fingerprint = hash("sha256", $der); + error_log("EST: sha256(DER cert): $fingerprint"); + + $pkcs7 = "$cadir/tmp/est-client.pkcs7"; + if (file_exists($pkcs7)) + unlink($pkcs7); + exec("openssl crl2pkcs7 -nocrl -certfile $cert_pem -out $pkcs7 -outform DER"); + if (!file_exists($pkcs7)) { + error_log("EST: simpleenroll - Failed to prepare PKCS#7 file"); + die("Failed to prepare PKCS#7 file"); + } + + if (!$db->exec("UPDATE sessions SET user='$user', cert='$fingerprint', cert_pem='$cert' WHERE rowid=$rowid")) { + error_log("EST: simpleenroll - Failed to update session database"); + die("Failed to update session database"); + } + + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/pkcs7-mime"); + + $data = file_get_contents($pkcs7); + $resp = wordwrap(base64_encode($data), 72, "\n", true); + echo $resp . "\n"; + error_log("EST: simpleenroll - PKCS#7 response: " . $resp); +} else { + header("HTTP/1.0 404 Not Found"); + error_log("EST: Unexpected method or path"); + die("Unexpected method or path"); +} + +?> diff --git a/contrib/wpa/hs20/server/www/free-remediation.php b/contrib/wpa/hs20/server/www/free-remediation.php new file mode 100644 index 000000000000..5648b30e8d6b --- /dev/null +++ b/contrib/wpa/hs20/server/www/free-remediation.php @@ -0,0 +1,19 @@ + + +Hotspot 2.0 - public and free hotspot - remediation + + + +

Hotspot 2.0 - public and free hotspot

+ +

Terms and conditions have changed. You need to accept the new terms +to continue using this network.

+ +

Terms and conditions..

+ +Accept
\n"; +?> + + + diff --git a/contrib/wpa/hs20/server/www/free.php b/contrib/wpa/hs20/server/www/free.php new file mode 100644 index 000000000000..8195069ed8ff --- /dev/null +++ b/contrib/wpa/hs20/server/www/free.php @@ -0,0 +1,23 @@ + + +Hotspot 2.0 - public and free hotspot + + + +Hotspot 2.0 - public and free hotspot\n"; + +echo "
\n"; +echo "\n"; + +?> + +

Terms and conditions..

+ +
+ + + diff --git a/contrib/wpa/hs20/server/www/redirect.php b/contrib/wpa/hs20/server/www/redirect.php new file mode 100644 index 000000000000..8fc9cd644273 --- /dev/null +++ b/contrib/wpa/hs20/server/www/redirect.php @@ -0,0 +1,32 @@ +query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; + +header("Location: $uri", true, 302); + +$user = $row['user']; +$realm = $row['realm']; + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'redirected after user input')"); + +?> diff --git a/contrib/wpa/hs20/server/www/remediation-pw.php b/contrib/wpa/hs20/server/www/remediation-pw.php new file mode 100644 index 000000000000..76fdccbdf9f7 --- /dev/null +++ b/contrib/wpa/hs20/server/www/remediation-pw.php @@ -0,0 +1,41 @@ +query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} +$user = $row['user']; +$realm = $row['realm']; + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; + +if (!$db->exec("UPDATE sessions SET password='$pw' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for subscription remediation')"); + +header("Location: $uri", true, 302); + +?> diff --git a/contrib/wpa/hs20/server/www/remediation.php b/contrib/wpa/hs20/server/www/remediation.php new file mode 100644 index 000000000000..3628065ac225 --- /dev/null +++ b/contrib/wpa/hs20/server/www/remediation.php @@ -0,0 +1,55 @@ + + +Hotspot 2.0 subscription remediation + + + +\n"; + +$row = $db->query("SELECT * FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$username = $row['user']; +echo "User: " . $username . "@" . $row['realm'] . "
\n"; + +$user = $db->query("SELECT machine_managed,methods FROM users WHERE identity='$username'")->fetch(); +if ($user == false) { + die("User not found"); +} + +echo "

\n"; + +$cert = $user['methods'] == "TLS" || strncmp($username, "cert-", 5) == 0; + +if ($cert) { + echo "Complete user subscription remediation
\n"; +} else if ($user['machine_managed'] == "1") { + echo "Complete user subscription remediation
\n"; + echo "This will provide a new machine-generated password.
\n"; +} else { + echo "
\n"; + echo "\n"; + echo "New password:
\n"; + echo "\n"; + echo "
\n"; +} + +?> + + + diff --git a/contrib/wpa/hs20/server/www/signup.php b/contrib/wpa/hs20/server/www/signup.php new file mode 100644 index 000000000000..80a9d403e8fc --- /dev/null +++ b/contrib/wpa/hs20/server/www/signup.php @@ -0,0 +1,59 @@ + + +Hotspot 2.0 signup + + + +query("SELECT realm,test FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found for id: $id"); +} +$realm = $row['realm']; +$test = $row['test']; + +if (strlen($test) > 0) { + echo "

Special test functionality: $test

\n"; +} + +echo "

Sign up for a subscription - $realm

\n"; + +echo "

This page can be used to select between three different types of subscriptions for testing purposes.

\n"; + +echo "

Option 1 - shared free access credential

\n"; + +$row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch(); +if ($row && strlen($row['value']) > 0) { + echo "

Sign up for free access

\n"; +} + +echo "

Option 2 - username/password credential

\n"; + +echo "
\n"; +echo "\n"; +?> +Select a username and password. Leave password empty to get automatically +generated and machine managed password.
+Username:
+Password:
+ +
+ +Option 3 - client certificate credential\n"; + +echo "

Enroll a client certificate

\n" +?> + + + diff --git a/contrib/wpa/hs20/server/www/spp.php b/contrib/wpa/hs20/server/www/spp.php new file mode 100644 index 000000000000..c56d3d69e0ed --- /dev/null +++ b/contrib/wpa/hs20/server/www/spp.php @@ -0,0 +1,168 @@ +1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, + 'uri'=>1, 'response'=>1); + $data = array(); + $keys = implode('|', array_keys($needed)); + preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', + $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed[$m[1]]); + } + if ($needed) { + error_log("spp.php - Authentication failed - missing: " . print_r($needed)); + die('Authentication failed'); + } + $user = $data['username']; + if (strlen($user) < 1) { + error_log("spp.php - Authentication failed - empty username"); + die('Authentication failed'); + } + + + $db = new PDO($osu_db); + if (!$db) { + error_log("spp.php - Could not access database"); + die("Could not access database"); + } + $row = $db->query("SELECT password FROM users " . + "WHERE identity='$user' AND realm='$realm'")->fetch(); + if (!$row) { + $row = $db->query("SELECT osu_password FROM users " . + "WHERE osu_user='$user' AND realm='$realm'")->fetch(); + $pw = $row['osu_password']; + } else + $pw = $row['password']; + if (!$row) { + error_log("spp.php - Authentication failed - user '$user' not found"); + die('Authentication failed'); + } + if (strlen($pw) < 1) { + error_log("spp.php - Authentication failed - empty password"); + die('Authentication failed'); + } + + $A1 = md5($user . ':' . $realm . ':' . $pw); + $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']); + $resp = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . + $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); + if ($data['response'] != $resp) { + error_log("Authentication failure - response mismatch"); + die('Authentication failed'); + } +} else if (isset($_SERVER["SSL_CLIENT_VERIFY"]) && + $_SERVER["SSL_CLIENT_VERIFY"] == "SUCCESS" && + isset($_SERVER["SSL_CLIENT_M_SERIAL"])) { + $user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"]; + putenv("HS20CERT=yes"); +} else if (isset($_GET["hotspot2dot0-mobile-identifier-hash"])) { + $id_hash = $_GET["hotspot2dot0-mobile-identifier-hash"]; + $id_hash = PREG_REPLACE("/[^0-9a-h]/i", '', $id_hash); + + $db = new PDO($osu_db); + if (!$db) { + error_log("spp.php - Could not access database"); + die("Could not access database"); + } + + $row = $db->query("SELECT * FROM sim_provisioning " . + "WHERE mobile_identifier_hash='$id_hash'")->fetch(); + if (!$row) { + error_log("spp.php - SIM provisioning failed - mobile_identifier_hash not found"); + die('SIM provisioning failed - mobile_identifier_hash not found'); + } + + $imsi = $row['imsi']; + $mac_addr = $row['mac_addr']; + $eap_method = $row['eap_method']; + + $row = $db->query("SELECT COUNT(*) FROM osu_config " . + "WHERE realm='$realm'")->fetch(); + if (!$row || intval($row[0]) < 1) { + error_log("spp.php - SIM provisioning failed - realm $realm not found"); + die('SIM provisioning failed'); + } + + error_log("spp.php - SIM provisioning for IMSI $imsi"); + putenv("HS20SIMPROV=yes"); + putenv("HS20IMSI=$imsi"); + putenv("HS20MACADDR=$mac_addr"); + putenv("HS20EAPMETHOD=$eap_method"); + putenv("HS20IDHASH=$id_hash"); +} else if (!isset($_SERVER["PATH_INFO"]) || + $_SERVER["PATH_INFO"] != "/signup") { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("spp.php - Authentication required (not signup)"); + die('Authentication required (not signup)'); +} + + +if (isset($user) && strlen($user) > 0) + putenv("HS20USER=$user"); +else + putenv("HS20USER"); + +putenv("HS20REALM=$realm"); +$postdata = file_get_contents("php://input"); +putenv("HS20POST=$postdata"); +$addr = $_SERVER["REMOTE_ADDR"]; +putenv("HS20ADDR=$addr"); +putenv("HS20TEST=$test"); + +$last = exec("$osu_root/spp/hs20_spp_server -r$osu_root -f/tmp/hs20_spp_server.log", $output, $ret); + +if ($ret == 2) { + if (empty($_SERVER['PHP_AUTH_DIGEST'])) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("spp.php - Authentication required (ret 2)"); + die('Authentication required'); + } else { + error_log("spp.php - Unexpected authentication error"); + die("Unexpected authentication error"); + } +} +if ($ret != 0) { + error_log("spp.php - Failed to process SPP request"); + die("Failed to process SPP request"); +} +//error_log("spp.php: Response: " . implode($output)); + +header("Content-Type: application/soap+xml"); + +echo implode($output); + +?> diff --git a/contrib/wpa/hs20/server/www/terms.php b/contrib/wpa/hs20/server/www/terms.php new file mode 100644 index 000000000000..acba23ef1ad7 --- /dev/null +++ b/contrib/wpa/hs20/server/www/terms.php @@ -0,0 +1,87 @@ +\n"; + echo "HS 2.0 Terms and Conditions\n"; + echo "\n"; +} + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (!isset($_GET["addr"])) { + die("Missing addr parameter"); +} +$addr = $_GET["addr"]; + +$accept = isset($_GET["accept"]) && $_GET["accept"] == "yes"; + +$res = $db->prepare("SELECT identity FROM pending_tc WHERE mac_addr=?"); +$res->execute(array($addr)); +$row = $res->fetch(); +if (!$row) { + die("No pending session for the specified MAC address"); +} +$identity = $row[0]; + +if (!$accept) { + print_header(); + + echo "

Accept the following terms and conditions by clicking here: Accept

\n
\n"; + readfile($t_c_file); +} else { + $res = $db->prepare("UPDATE users SET t_c_timestamp=? WHERE identity=?"); + if (!$res->execute(array($t_c_timestamp, $identity))) { + die("Failed to update user account."); + } + + $res = $db->prepare("DELETE FROM pending_tc WHERE mac_addr=?"); + $res->execute(array($addr)); + + $fp = fsockopen($hostapd_ctrl); + if (!$fp) { + die("Could not connect to hostapd(AS)"); + } + + fwrite($fp, "DAC_REQUEST coa $addr t_c_clear"); + fclose($fp); + + $waiting = true; + $ack = false; + for ($i = 1; $i <= 10; $i++) { + $res = $db->prepare("SELECT waiting_coa_ack,coa_ack_received FROM current_sessions WHERE mac_addr=?"); + $res->execute(array($addr)); + $row = $res->fetch(); + if (!$row) { + die("No current session for the specified MAC address"); + } + if (strlen($row[0]) > 0) + $waiting = $row[0] == 1; + if (strlen($row[1]) > 0) + $ack = $row[1] == 1; + $res->closeCursor(); + if (!$waiting) + break; + sleep(1); + } + if ($ack) { + header('X-WFA-Hotspot20-Filtering: removed'); + print_header(); + echo "

Terms and conditions were accepted.

\n"; + + echo "

Filtering disabled.

\n"; + } else { + print_header(); + echo "

Failed to disable filtering.

\n"; + } +} + +?> + + + diff --git a/contrib/wpa/hs20/server/www/users.php b/contrib/wpa/hs20/server/www/users.php new file mode 100644 index 000000000000..2bd555275dda --- /dev/null +++ b/contrib/wpa/hs20/server/www/users.php @@ -0,0 +1,377 @@ + 0) { + $row = $db->query("SELECT dump FROM eventlog WHERE rowid=$id")->fetch(); + $dump = $row['dump']; + if ($dump[0] == '<') { + header("Content-type: text/xml"); + echo "\n"; + echo $dump; + } else { + header("Content-type: text/plain"); + echo $dump; + } + exit; +} + +if ($cmd == 'mo' && $id > 0) { + $mo = $_GET["mo"]; + if (!isset($mo)) + exit; + if ($mo != "devinfo" && $mo != "devdetail" && $mo != "pps") + exit; + $row = $db->query("SELECT $mo FROM users WHERE rowid=$id")->fetch(); + header("Content-type: text/xml"); + echo "\n"; + echo $row[$mo]; + exit; +} + +if ($cmd == 'cert' && $id > 0) { + $row = $db->query("SELECT cert_pem FROM users WHERE rowid=$id")->fetch(); + header("Content-type: text/plain"); + echo $row['cert_pem']; + exit; +} + +?> + + +HS 2.0 users + + + 0) { + $db->exec("UPDATE users SET remediation='' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-user' && $id > 0) { + $db->exec("UPDATE users SET remediation='user' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-machine' && $id > 0) { + $db->exec("UPDATE users SET remediation='machine' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-reenroll' && $id > 0) { + $db->exec("UPDATE users SET remediation='reenroll' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-policy' && $id > 0) { + $db->exec("UPDATE users SET remediation='policy' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-free' && $id > 0) { + $db->exec("UPDATE users SET remediation='free' WHERE rowid=$id"); +} +if ($cmd == 'fetch-pps-on' && $id > 0) { + $db->exec("UPDATE users SET fetch_pps=1 WHERE rowid=$id"); +} +if ($cmd == 'fetch-pps-off' && $id > 0) { + $db->exec("UPDATE users SET fetch_pps=0 WHERE rowid=$id"); +} +if ($cmd == 'reset-pw' && $id > 0) { + $db->exec("UPDATE users SET password='ChangeMe' WHERE rowid=$id"); +} +if ($cmd == "policy" && $id > 0 && isset($_GET["policy"])) { + $policy = $_GET["policy"]; + if ($policy == "no-policy" || + is_readable("$osu_root/spp/policy/$policy.xml")) { + $db->exec("UPDATE users SET policy='$policy' WHERE rowid=$id"); + } +} +if ($cmd == "account-type" && $id > 0 && isset($_GET["type"])) { + $type = $_GET["type"]; + if ($type == "shared") + $db->exec("UPDATE users SET shared=1 WHERE rowid=$id"); + if ($type == "default") + $db->exec("UPDATE users SET shared=0 WHERE rowid=$id"); +} + +if ($cmd == "set-osu-cred" && $id > 0) { + $osu_user = $_POST["osu_user"]; + $osu_password = $_POST["osu_password"]; + if (strlen($osu_user) == 0) + $osu_password = ""; + $db->exec("UPDATE users SET osu_user='$osu_user', osu_password='$osu_password' WHERE rowid=$id"); +} + +if ($cmd == 'clear-t-c' && $id > 0) { + $db->exec("UPDATE users SET t_c_timestamp=NULL WHERE rowid=$id"); +} + +$dump = 0; + +if ($id > 0) { + +if (isset($_GET["dump"])) { + $dump = $_GET["dump"]; + if (!is_numeric($dump)) + $dump = 0; +} else + $dump = 0; + +echo "[All users] "; +if ($dump == 0) + echo "[Include debug dump] "; +else + echo "[Without debug dump] "; +echo "
\n"; + +$row = $db->query("SELECT rowid,* FROM users WHERE rowid=$id")->fetch(); + +echo "

" . $row['identity'] . "@" . $row['realm'] . "

\n"; + +echo "MO: "; +if (strlen($row['devinfo']) > 0) { + echo "[DevInfo]\n"; +} +if (strlen($row['devdetail']) > 0) { + echo "[DevDetail]\n"; +} +if (strlen($row['pps']) > 0) { + echo "[PPS]\n"; +} +if (strlen($row['cert_pem']) > 0) { + echo "[Certificate]\n"; +} +echo "
\n"; + +echo "Fetch PPS MO: "; +if ($row['fetch_pps'] == "1") { + echo "On next connection " . + "[" . + "do not fetch]
\n"; +} else { + echo "Do not fetch " . + "[" . + "request fetch]
\n"; +} + +$cert = $row['cert']; +if (strlen($cert) > 0) { + echo "Certificate fingerprint: $cert
\n"; +} + +echo "Remediation: "; +$rem = $row['remediation']; +if ($rem == "") { + echo "Not required"; + echo " [add:user]"; + echo " [add:machine]"; + if ($row['methods'] == 'TLS') { + echo " [add:reenroll]"; + } + echo " [add:policy]"; + echo " [add:free]"; +} else if ($rem == "user") { + echo "User [clear]"; +} else if ($rem == "policy") { + echo "Policy [clear]"; +} else if ($rem == "free") { + echo "Free [clear]"; +} else if ($rem == "reenroll") { + echo "Reenroll [clear]"; +} else { + echo "Machine [clear]"; +} +echo "
\n"; + +if (strncmp($row['identity'], "cert-", 5) != 0) + echo "Machine managed: " . ($row['machine_managed'] == "1" ? "TRUE" : "FALSE") . "
\n"; + +echo "
Policy:
\n"; + +echo "
Account type:
\n"; + +echo "Phase 2 method(s): " . $row['methods'] . "
\n"; + +echo "
\n"; +echo "Reset AAA password
\n"; + +echo "
\n"; +echo "
\n"; +echo "OSU credentials (if username empty, AAA credentials are used):
\n"; +echo "username: \n"; +echo "password: \n"; +echo "\n"; +echo "
\n"; + +if (strlen($row['t_c_timestamp']) > 0) { + echo "
\n"; + echo "Clear Terms and Conditions acceptance
\n"; +} + +echo "
\n"; + +$user = $row['identity']; +$osu_user = $row['osu_user']; +$realm = $row['realm']; +} + +if ($id > 0 || ($id == 0 && $cmd == 'eventlog')) { + + if ($id == 0) { + echo "[All users] "; + echo "
\n"; + } + +echo "\n"; +echo ""; +if ($id == 0) { + echo ""; + if ($id == 0) { + echo "
userrealm"; +} +echo "timeaddresssessionIDnotes"; +if ($dump > 0) + echo "dump"; +echo "\n"; +if (isset($_GET["limit"])) { + $limit = $_GET["limit"]; + if (!is_numeric($limit)) + $limit = 20; +} else + $limit = 20; +if ($id == 0) + $res = $db->query("SELECT rowid,* FROM eventlog ORDER BY timestamp DESC LIMIT $limit"); +else if (strlen($osu_user) > 0) + $res = $db->query("SELECT rowid,* FROM eventlog WHERE (user='$user' OR user='$osu_user') AND realm='$realm' ORDER BY timestamp DESC LIMIT $limit"); +else + $res = $db->query("SELECT rowid,* FROM eventlog WHERE user='$user' AND realm='$realm' ORDER BY timestamp DESC LIMIT $limit"); +foreach ($res as $row) { + echo "
" . $row['user'] . "\n"; + echo "" . $row['realm'] . "\n"; + } + echo "" . $row['timestamp'] . "\n"; + echo "" . $row['addr'] . "\n"; + echo "" . $row['sessionid'] . "\n"; + echo "" . $row['notes'] . "\n"; + $d = $row['dump']; + if (strlen($d) > 0) { + echo "["; + if ($d[0] == '<') + echo "XML"; + else + echo "txt"; + echo "]\n"; + if ($dump > 0) + echo "" . htmlspecialchars($d) . "\n"; + } +} +echo "
\n"; + +} + + +if ($id == 0 && $cmd != 'eventlog') { + +echo "[Eventlog] "; +echo "
\n"; + +echo "\n"; +echo "
UserRealmRemediationPolicyAccount typePhase 2 method(s)DevIdMAC AddressT&C\n"; + +$res = $db->query('SELECT rowid,* FROM users WHERE (phase2=1 OR methods=\'TLS\') ORDER BY identity'); +foreach ($res as $row) { + echo "
" . + $row['identity'] . " "; + echo "" . $row['realm']; + $rem = $row['remediation']; + echo ""; + if ($rem == "") { + echo "-"; + } else if ($rem == "user") { + echo "User"; + } else if ($rem == "policy") { + echo "Policy"; + } else if ($rem == "free") { + echo "Free"; + } else if ($rem == "reenroll") { + echo "Reenroll"; + } else { + echo "Machine"; + } + echo "" . $row['policy']; + if ($row['shared'] > 0) + echo "shared"; + else + echo "default"; + echo "" . $row['methods'] . ""; + echo ""; + $xml = xml_parser_create(); + xml_parse_into_struct($xml, $row['devinfo'], $devinfo); + foreach($devinfo as $k) { + if ($k['tag'] == 'DEVID') { + echo "" . $k['value'] . ""; + break; + } + } + echo "" . $row['mac_addr'] . ""; + echo "" . $row['t_c_timestamp'] . ""; + echo "\n"; +} +echo "
\n"; + +} + +?> + + diff --git a/contrib/wpa/src/common/dragonfly.c b/contrib/wpa/src/common/dragonfly.c index 1e842716668e..547be66f1561 100644 --- a/contrib/wpa/src/common/dragonfly.c +++ b/contrib/wpa/src/common/dragonfly.c @@ -213,37 +213,3 @@ int dragonfly_generate_scalar(const struct crypto_bignum *order, "dragonfly: Unable to get randomness for own scalar"); return -1; } - - -/* res = sqrt(val) */ -int dragonfly_sqrt(struct crypto_ec *ec, const struct crypto_bignum *val, - struct crypto_bignum *res) -{ - const struct crypto_bignum *prime; - struct crypto_bignum *tmp, *one; - int ret = 0; - u8 prime_bin[DRAGONFLY_MAX_ECC_PRIME_LEN]; - size_t prime_len; - - /* For prime p such that p = 3 mod 4, sqrt(w) = w^((p+1)/4) mod p */ - - prime = crypto_ec_get_prime(ec); - prime_len = crypto_ec_prime_len(ec); - tmp = crypto_bignum_init(); - one = crypto_bignum_init_uint(1); - - if (crypto_bignum_to_bin(prime, prime_bin, sizeof(prime_bin), - prime_len) < 0 || - (prime_bin[prime_len - 1] & 0x03) != 3 || - !tmp || !one || - /* tmp = (p+1)/4 */ - crypto_bignum_add(prime, one, tmp) < 0 || - crypto_bignum_rshift(tmp, 2, tmp) < 0 || - /* res = sqrt(val) */ - crypto_bignum_exptmod(val, tmp, prime, res) < 0) - ret = -1; - - crypto_bignum_deinit(tmp, 0); - crypto_bignum_deinit(one, 0); - return ret; -} diff --git a/contrib/wpa/src/common/dragonfly.h b/contrib/wpa/src/common/dragonfly.h index 84d67f575c54..ec3dd593eda4 100644 --- a/contrib/wpa/src/common/dragonfly.h +++ b/contrib/wpa/src/common/dragonfly.h @@ -27,7 +27,5 @@ int dragonfly_generate_scalar(const struct crypto_bignum *order, struct crypto_bignum *_rand, struct crypto_bignum *_mask, struct crypto_bignum *scalar); -int dragonfly_sqrt(struct crypto_ec *ec, const struct crypto_bignum *val, - struct crypto_bignum *res); #endif /* DRAGONFLY_H */ diff --git a/contrib/wpa/src/common/qca-vendor.h b/contrib/wpa/src/common/qca-vendor.h index b77e29939195..d9eab0212e73 100644 --- a/contrib/wpa/src/common/qca-vendor.h +++ b/contrib/wpa/src/common/qca-vendor.h @@ -1462,11 +1462,6 @@ enum qca_wlan_vendor_attr_p2p_listen_offload { * Used with event to notify the puncture pattern selected in ACS operation. * Encoding for this attribute will follow the convention used in the Disabled * Subchannel Bitmap field of the EHT Operation IE. - * - * @QCA_WLAN_VENDOR_ATTR_ACS_EHT_ENABLED: Flag attribute. - * Used with command to configure ACS operation for EHT mode. - * Disable (flag attribute not present) - EHT disabled and - * Enable (flag attribute present) - EHT enabled. */ enum qca_wlan_vendor_attr_acs_offload { QCA_WLAN_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0, @@ -1488,7 +1483,6 @@ enum qca_wlan_vendor_attr_acs_offload { QCA_WLAN_VENDOR_ATTR_ACS_EDMG_ENABLED = 16, QCA_WLAN_VENDOR_ATTR_ACS_EDMG_CHANNEL = 17, QCA_WLAN_VENDOR_ATTR_ACS_PUNCTURE_BITMAP = 18, - QCA_WLAN_VENDOR_ATTR_ACS_EHT_ENABLED = 19, /* keep last */ QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST, @@ -1794,53 +1788,36 @@ enum qca_access_policy { }; /** - * enum qca_vendor_attr_tsf_cmd: Vendor attributes for TSF capture - * @QCA_WLAN_VENDOR_ATTR_TSF_CMD: Required (u32) - * Specify the TSF command. Possible values are defined in - * &enum qca_tsf_cmd. - * @QCA_WLAN_VENDOR_ATTR_TSF_TIMER_VALUE: Optional (u64) - * This attribute contains TSF timer value. This attribute is only available - * in %QCA_TSF_GET or %QCA_TSF_SYNC_GET response. - * @QCA_WLAN_VENDOR_ATTR_TSF_SOC_TIMER_VALUE: Optional (u64) - * This attribute contains SOC timer value at TSF capture. This attribute is - * only available in %QCA_TSF_GET or %QCA_TSF_SYNC_GET response. - * @QCA_WLAN_VENDOR_ATTR_TSF_SYNC_INTERVAL: Optional (u32) - * This attribute is used to provide TSF sync interval and only applicable when - * TSF command is %QCA_TSF_SYNC_START. If this attribute is not provided, the - * driver will use the default value. Time unit is in milliseconds. + * enum qca_vendor_attr_get_tsf: Vendor attributes for TSF capture + * @QCA_WLAN_VENDOR_ATTR_TSF_CMD: enum qca_tsf_operation (u32) + * @QCA_WLAN_VENDOR_ATTR_TSF_TIMER_VALUE: Unsigned 64 bit TSF timer value + * @QCA_WLAN_VENDOR_ATTR_TSF_SOC_TIMER_VALUE: Unsigned 64 bit Synchronized + * SOC timer value at TSF capture */ enum qca_vendor_attr_tsf_cmd { QCA_WLAN_VENDOR_ATTR_TSF_INVALID = 0, QCA_WLAN_VENDOR_ATTR_TSF_CMD, QCA_WLAN_VENDOR_ATTR_TSF_TIMER_VALUE, QCA_WLAN_VENDOR_ATTR_TSF_SOC_TIMER_VALUE, - QCA_WLAN_VENDOR_ATTR_TSF_SYNC_INTERVAL, QCA_WLAN_VENDOR_ATTR_TSF_AFTER_LAST, QCA_WLAN_VENDOR_ATTR_TSF_MAX = QCA_WLAN_VENDOR_ATTR_TSF_AFTER_LAST - 1 }; /** - * enum qca_tsf_cmd: TSF driver commands + * enum qca_tsf_operation: TSF driver commands * @QCA_TSF_CAPTURE: Initiate TSF Capture * @QCA_TSF_GET: Get TSF capture value * @QCA_TSF_SYNC_GET: Initiate TSF capture and return with captured value * @QCA_TSF_AUTO_REPORT_ENABLE: Used in STA mode only. Once set, the target * will automatically send TSF report to the host. To query - * %QCA_WLAN_VENDOR_ATTR_GET_STA_INFO_UPLINK_DELAY, this operation needs to be + * QCA_WLAN_VENDOR_ATTR_GET_STA_INFO_UPLINK_DELAY, this operation needs to be * initiated first. * @QCA_TSF_AUTO_REPORT_DISABLE: Used in STA mode only. Once set, the target * will not automatically send TSF report to the host. If - * %QCA_TSF_AUTO_REPORT_ENABLE is initiated and - * %QCA_WLAN_VENDOR_ATTR_GET_STA_INFO_UPLINK_DELAY is not queried anymore, this + * QCA_TSF_AUTO_REPORT_ENABLE is initiated and + * QCA_WLAN_VENDOR_ATTR_GET_STA_INFO_UPLINK_DELAY is not queried anymore, this * operation needs to be initiated. - * @QCA_TSF_SYNC_START: Start periodic TSF sync feature. The driver periodically - * fetches TSF and host time mapping from the firmware with interval configured - * through the %QCA_WLAN_VENDOR_ATTR_TSF_SYNC_INTERVAL attribute. If the - * interval value is not provided the driver will use the default value. The - * userspace can query the TSF and host time mapping via the %QCA_TSF_GET - * command. - * @QCA_TSF_SYNC_STOP: Stop periodic TSF sync feature. */ enum qca_tsf_cmd { QCA_TSF_CAPTURE, @@ -1848,8 +1825,6 @@ enum qca_tsf_cmd { QCA_TSF_SYNC_GET, QCA_TSF_AUTO_REPORT_ENABLE, QCA_TSF_AUTO_REPORT_DISABLE, - QCA_TSF_SYNC_START, - QCA_TSF_SYNC_STOP, }; /** diff --git a/contrib/wpa/src/common/sae.c b/contrib/wpa/src/common/sae.c index c0f154e9134d..b768c22faa9d 100644 --- a/contrib/wpa/src/common/sae.c +++ b/contrib/wpa/src/common/sae.c @@ -290,16 +290,14 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1, int pwd_seed_odd = 0; u8 prime[SAE_MAX_ECC_PRIME_LEN]; size_t prime_len; - struct crypto_bignum *x = NULL, *y = NULL, *qr = NULL, *qnr = NULL; + struct crypto_bignum *x = NULL, *qr = NULL, *qnr = NULL; u8 x_bin[SAE_MAX_ECC_PRIME_LEN]; u8 x_cand_bin[SAE_MAX_ECC_PRIME_LEN]; u8 qr_bin[SAE_MAX_ECC_PRIME_LEN]; u8 qnr_bin[SAE_MAX_ECC_PRIME_LEN]; - u8 x_y[2 * SAE_MAX_ECC_PRIME_LEN]; int res = -1; u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_* * mask */ - unsigned int is_eq; os_memset(x_bin, 0, sizeof(x_bin)); @@ -398,42 +396,25 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1, goto fail; } - /* y = sqrt(x^3 + ax + b) mod p - * if LSB(save) == LSB(y): PWE = (x, y) - * else: PWE = (x, p - y) - * - * Calculate y and the two possible values for PWE and after that, - * use constant time selection to copy the correct alternative. - */ - y = crypto_ec_point_compute_y_sqr(sae->tmp->ec, x); - if (!y || - dragonfly_sqrt(sae->tmp->ec, y, y) < 0 || - crypto_bignum_to_bin(y, x_y, SAE_MAX_ECC_PRIME_LEN, - prime_len) < 0 || - crypto_bignum_sub(sae->tmp->prime, y, y) < 0 || - crypto_bignum_to_bin(y, x_y + SAE_MAX_ECC_PRIME_LEN, - SAE_MAX_ECC_PRIME_LEN, prime_len) < 0) { - wpa_printf(MSG_DEBUG, "SAE: Could not solve y"); - goto fail; - } - - is_eq = const_time_eq(pwd_seed_odd, x_y[prime_len - 1] & 0x01); - const_time_select_bin(is_eq, x_y, x_y + SAE_MAX_ECC_PRIME_LEN, - prime_len, x_y + prime_len); - os_memcpy(x_y, x_bin, prime_len); - wpa_hexdump_key(MSG_DEBUG, "SAE: PWE", x_y, 2 * prime_len); - crypto_ec_point_deinit(sae->tmp->pwe_ecc, 1); - sae->tmp->pwe_ecc = crypto_ec_point_from_bin(sae->tmp->ec, x_y); - if (!sae->tmp->pwe_ecc) { - wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE"); + if (!sae->tmp->pwe_ecc) + sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec); + if (!sae->tmp->pwe_ecc) res = -1; + else + res = crypto_ec_point_solve_y_coord(sae->tmp->ec, + sae->tmp->pwe_ecc, x, + pwd_seed_odd); + if (res < 0) { + /* + * This should not happen since we already checked that there + * is a result. + */ + wpa_printf(MSG_DEBUG, "SAE: Could not solve y"); } fail: - forced_memzero(x_y, sizeof(x_y)); crypto_bignum_deinit(qr, 0); crypto_bignum_deinit(qnr, 0); - crypto_bignum_deinit(y, 1); os_free(stub_password); bin_clear_free(tmp_password, password_len); crypto_bignum_deinit(x, 1); @@ -766,9 +747,19 @@ static struct crypto_ec_point * sswu(struct crypto_ec *ec, int group, const_time_select_bin(is_qr, bin1, bin2, prime_len, x_y); wpa_hexdump_key(MSG_DEBUG, "SSWU: x = CSEL(l, x1, x2)", x_y, prime_len); - /* y = sqrt(v) */ + /* y = sqrt(v) + * For prime p such that p = 3 mod 4 --> v^((p+1)/4) */ + if (crypto_bignum_to_bin(prime, bin1, sizeof(bin1), prime_len) < 0) + goto fail; + if ((bin1[prime_len - 1] & 0x03) != 3) { + wpa_printf(MSG_DEBUG, "SSWU: prime does not have p = 3 mod 4"); + goto fail; + } y = crypto_bignum_init(); - if (!y || dragonfly_sqrt(ec, v, y) < 0) + if (!y || + crypto_bignum_add(prime, one, t1) < 0 || + crypto_bignum_rshift(t1, 2, t1) < 0 || + crypto_bignum_exptmod(v, t1, prime, y) < 0) goto fail; debug_print_bignum("SSWU: y = sqrt(v)", y, prime_len); diff --git a/contrib/wpa/src/common/version.h b/contrib/wpa/src/common/version.h index 7502f58e0b87..0235c9bf6776 100644 --- a/contrib/wpa/src/common/version.h +++ b/contrib/wpa/src/common/version.h @@ -9,6 +9,6 @@ #define GIT_VERSION_STR_POSTFIX "" #endif /* GIT_VERSION_STR_POSTFIX */ -#define VERSION_STR "2.10" VERSION_STR_POSTFIX GIT_VERSION_STR_POSTFIX +#define VERSION_STR "2.10-devel" VERSION_STR_POSTFIX GIT_VERSION_STR_POSTFIX #endif /* VERSION_H */ diff --git a/contrib/wpa/src/crypto/crypto.h b/contrib/wpa/src/crypto/crypto.h index e6150b0cf16e..eb600699d3d0 100644 --- a/contrib/wpa/src/crypto/crypto.h +++ b/contrib/wpa/src/crypto/crypto.h @@ -882,6 +882,18 @@ int crypto_ec_point_mul(struct crypto_ec *e, const struct crypto_ec_point *p, */ int crypto_ec_point_invert(struct crypto_ec *e, struct crypto_ec_point *p); +/** + * crypto_ec_point_solve_y_coord - Solve y coordinate for an x coordinate + * @e: EC context from crypto_ec_init() + * @p: EC point to use for the returning the result + * @x: x coordinate + * @y_bit: y-bit (0 or 1) for selecting the y value to use + * Returns: 0 on success, -1 on failure + */ +int crypto_ec_point_solve_y_coord(struct crypto_ec *e, + struct crypto_ec_point *p, + const struct crypto_bignum *x, int y_bit); + /** * crypto_ec_point_compute_y_sqr - Compute y^2 = x^3 + ax + b * @e: EC context from crypto_ec_init() diff --git a/contrib/wpa/src/crypto/crypto_openssl.c b/contrib/wpa/src/crypto/crypto_openssl.c index 82c85762d84f..ef669c408474 100644 --- a/contrib/wpa/src/crypto/crypto_openssl.c +++ b/contrib/wpa/src/crypto/crypto_openssl.c @@ -24,9 +24,6 @@ #include #include #endif /* CONFIG_ECC */ -#if OPENSSL_VERSION_NUMBER >= 0x30000000L -#include -#endif /* OpenSSL version >= 3.0 */ #include "common.h" #include "utils/const_time.h" @@ -120,26 +117,6 @@ static const unsigned char * ASN1_STRING_get0_data(const ASN1_STRING *x) } #endif /* OpenSSL version < 1.1.0 */ - -void openssl_load_legacy_provider(void) -{ -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - static bool loaded = false; - OSSL_PROVIDER *legacy; - - if (loaded) - return; - - legacy = OSSL_PROVIDER_load(NULL, "legacy"); - - if (legacy) { - OSSL_PROVIDER_load(NULL, "default"); - loaded = true; - } -#endif /* OpenSSL version >= 3.0 */ -} - - static BIGNUM * get_group5_prime(void) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ @@ -246,7 +223,6 @@ static int openssl_digest_vector(const EVP_MD *type, size_t num_elem, #ifndef CONFIG_FIPS int md4_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac) { - openssl_load_legacy_provider(); return openssl_digest_vector(EVP_md4(), num_elem, addr, len, mac); } #endif /* CONFIG_FIPS */ @@ -258,8 +234,6 @@ int des_encrypt(const u8 *clear, const u8 *key, u8 *cypher) int i, plen, ret = -1; EVP_CIPHER_CTX *ctx; - openssl_load_legacy_provider(); - /* Add parity bits to the key */ next = 0; for (i = 0; i < 7; i++) { @@ -297,8 +271,6 @@ int rc4_skip(const u8 *key, size_t keylen, size_t skip, int res = -1; unsigned char skip_buf[16]; - openssl_load_legacy_provider(); - ctx = EVP_CIPHER_CTX_new(); if (!ctx || !EVP_CipherInit_ex(ctx, EVP_rc4(), NULL, NULL, NULL, 1) || @@ -1951,27 +1923,48 @@ int crypto_ec_point_invert(struct crypto_ec *e, struct crypto_ec_point *p) } +int crypto_ec_point_solve_y_coord(struct crypto_ec *e, + struct crypto_ec_point *p, + const struct crypto_bignum *x, int y_bit) +{ + if (TEST_FAIL()) + return -1; + if (!EC_POINT_set_compressed_coordinates_GFp(e->group, (EC_POINT *) p, + (const BIGNUM *) x, y_bit, + e->bnctx) || + !EC_POINT_is_on_curve(e->group, (EC_POINT *) p, e->bnctx)) + return -1; + return 0; +} + + struct crypto_bignum * crypto_ec_point_compute_y_sqr(struct crypto_ec *e, const struct crypto_bignum *x) { - BIGNUM *tmp; + BIGNUM *tmp, *tmp2, *y_sqr = NULL; if (TEST_FAIL()) return NULL; tmp = BN_new(); + tmp2 = BN_new(); - /* y^2 = x^3 + ax + b = (x^2 + a)x + b */ - if (tmp && + /* y^2 = x^3 + ax + b */ + if (tmp && tmp2 && BN_mod_sqr(tmp, (const BIGNUM *) x, e->prime, e->bnctx) && - BN_mod_add_quick(tmp, e->a, tmp, e->prime) && BN_mod_mul(tmp, tmp, (const BIGNUM *) x, e->prime, e->bnctx) && - BN_mod_add_quick(tmp, tmp, e->b, e->prime)) - return (struct crypto_bignum *) tmp; + BN_mod_mul(tmp2, e->a, (const BIGNUM *) x, e->prime, e->bnctx) && + BN_mod_add_quick(tmp2, tmp2, tmp, e->prime) && + BN_mod_add_quick(tmp2, tmp2, e->b, e->prime)) { + y_sqr = tmp2; + tmp2 = NULL; + } BN_clear_free(tmp); - return NULL; + BN_clear_free(tmp2); + + return (struct crypto_bignum *) y_sqr; } @@ -2487,13 +2480,12 @@ struct crypto_ec_key * crypto_ec_key_gen(int group) goto fail; } - eckey = EVP_PKEY_get1_EC_KEY(key); + eckey = EVP_PKEY_get0_EC_KEY(key); if (!eckey) { key = NULL; goto fail; } EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED); - EC_KEY_free(eckey); fail: EC_KEY_free(ec_params); @@ -2603,34 +2595,12 @@ struct wpabuf * crypto_ec_key_get_subject_public_key(struct crypto_ec_key *key) unsigned char *der = NULL; int der_len; struct wpabuf *buf; - EC_KEY *eckey; -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_PKEY *tmp; -#endif /* OpenSSL version >= 3.0 */ - - eckey = EVP_PKEY_get1_EC_KEY((EVP_PKEY *) key); - if (!eckey) - return NULL; /* For now, all users expect COMPRESSED form */ - EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED); - -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - tmp = EVP_PKEY_new(); - if (!tmp) - return NULL; - if (EVP_PKEY_set1_EC_KEY(tmp, eckey) != 1) { - EVP_PKEY_free(tmp); - return NULL; - } - key = (struct crypto_ec_key *) tmp; -#endif /* OpenSSL version >= 3.0 */ + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY((EVP_PKEY *) key), + POINT_CONVERSION_COMPRESSED); der_len = i2d_PUBKEY((EVP_PKEY *) key, &der); - EC_KEY_free(eckey); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_PKEY_free(tmp); -#endif /* OpenSSL version >= 3.0 */ if (der_len <= 0) { wpa_printf(MSG_INFO, "OpenSSL: i2d_PUBKEY() failed: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -2653,7 +2623,7 @@ struct wpabuf * crypto_ec_key_get_ecprivate_key(struct crypto_ec_key *key, struct wpabuf *buf; unsigned int key_flags; - eckey = EVP_PKEY_get1_EC_KEY((EVP_PKEY *) key); + eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY *) key); if (!eckey) return NULL; @@ -2667,7 +2637,6 @@ struct wpabuf * crypto_ec_key_get_ecprivate_key(struct crypto_ec_key *key, EC_KEY_set_conv_form(eckey, POINT_CONVERSION_UNCOMPRESSED); der_len = i2d_ECPrivateKey(eckey, &der); - EC_KEY_free(eckey); if (der_len <= 0) return NULL; buf = wpabuf_alloc_copy(der, der_len); @@ -2728,7 +2697,7 @@ struct wpabuf * crypto_ec_key_get_pubkey_point(struct crypto_ec_key *key, const struct crypto_ec_point * crypto_ec_key_get_public_key(struct crypto_ec_key *key) { - const EC_KEY *eckey; + EC_KEY *eckey; eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY *) key); if (!eckey) @@ -2740,7 +2709,7 @@ crypto_ec_key_get_public_key(struct crypto_ec_key *key) const struct crypto_bignum * crypto_ec_key_get_private_key(struct crypto_ec_key *key) { - const EC_KEY *eckey; + EC_KEY *eckey; eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY *) key); if (!eckey) diff --git a/contrib/wpa/src/crypto/crypto_wolfssl.c b/contrib/wpa/src/crypto/crypto_wolfssl.c index 00ecf61352a1..6f116eb62f53 100644 --- a/contrib/wpa/src/crypto/crypto_wolfssl.c +++ b/contrib/wpa/src/crypto/crypto_wolfssl.c @@ -1630,6 +1630,30 @@ int crypto_ec_point_invert(struct crypto_ec *e, struct crypto_ec_point *p) } +int crypto_ec_point_solve_y_coord(struct crypto_ec *e, + struct crypto_ec_point *p, + const struct crypto_bignum *x, int y_bit) +{ + byte buf[1 + 2 * MAX_ECC_BYTES]; + int ret; + int prime_len = crypto_ec_prime_len(e); + + if (TEST_FAIL()) + return -1; + + buf[0] = y_bit ? ECC_POINT_COMP_ODD : ECC_POINT_COMP_EVEN; + ret = crypto_bignum_to_bin(x, buf + 1, prime_len, prime_len); + if (ret <= 0) + return -1; + ret = wc_ecc_import_point_der(buf, 1 + 2 * ret, e->key.idx, + (ecc_point *) p); + if (ret != 0) + return -1; + + return 0; +} + + struct crypto_bignum * crypto_ec_point_compute_y_sqr(struct crypto_ec *e, const struct crypto_bignum *x) diff --git a/contrib/wpa/src/crypto/tls_openssl.c b/contrib/wpa/src/crypto/tls_openssl.c index c9e00b3af855..203b0f781ff5 100644 --- a/contrib/wpa/src/crypto/tls_openssl.c +++ b/contrib/wpa/src/crypto/tls_openssl.c @@ -957,10 +957,6 @@ void * tls_init(const struct tls_config *conf) const char *ciphers; if (tls_openssl_ref_count == 0) { - void openssl_load_legacy_provider(void); - - openssl_load_legacy_provider(); - tls_global = context = tls_context_new(conf); if (context == NULL) return NULL; @@ -3023,23 +3019,13 @@ static int tls_set_conn_flags(struct tls_connection *conn, unsigned int flags, #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ !defined(LIBRESSL_VERSION_NUMBER) && \ !defined(OPENSSL_IS_BORINGSSL) - { -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - int need_level = 0; -#else - int need_level = 1; -#endif - - if ((flags & - (TLS_CONN_ENABLE_TLSv1_0 | TLS_CONN_ENABLE_TLSv1_1)) && - SSL_get_security_level(ssl) > need_level) { - /* - * Need to drop to security level 1 (or 0 with OpenSSL - * 3.0) to allow TLS versions older than 1.2 to be used - * when explicitly enabled in configuration. - */ - SSL_set_security_level(conn->ssl, need_level); - } + if ((flags & (TLS_CONN_ENABLE_TLSv1_0 | TLS_CONN_ENABLE_TLSv1_1)) && + SSL_get_security_level(ssl) >= 2) { + /* + * Need to drop to security level 1 to allow TLS versions older + * than 1.2 to be used when explicitly enabled in configuration. + */ + SSL_set_security_level(conn->ssl, 1); } #endif diff --git a/contrib/wpa/src/eap_common/eap_pwd_common.c b/contrib/wpa/src/eap_common/eap_pwd_common.c index ff22b29b087a..2b2b8efdbd01 100644 --- a/contrib/wpa/src/eap_common/eap_pwd_common.c +++ b/contrib/wpa/src/eap_common/eap_pwd_common.c @@ -127,8 +127,7 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, u8 qr_or_qnr_bin[MAX_ECC_PRIME_LEN]; u8 x_bin[MAX_ECC_PRIME_LEN]; u8 prime_bin[MAX_ECC_PRIME_LEN]; - u8 x_y[2 * MAX_ECC_PRIME_LEN]; - struct crypto_bignum *tmp2 = NULL, *y = NULL; + struct crypto_bignum *tmp2 = NULL; struct crypto_hash *hash; unsigned char pwe_digest[SHA256_MAC_LEN], *prfbuf = NULL, ctr; int ret = 0, res; @@ -140,7 +139,6 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, u8 found_ctr = 0, is_odd = 0; int cmp_prime; unsigned int in_range; - unsigned int is_eq; if (grp->pwe) return -1; @@ -153,6 +151,11 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, if (crypto_bignum_to_bin(prime, prime_bin, sizeof(prime_bin), primebytelen) < 0) return -1; + grp->pwe = crypto_ec_point_init(grp->group); + if (!grp->pwe) { + wpa_printf(MSG_INFO, "EAP-pwd: unable to create bignums"); + goto fail; + } if ((prfbuf = os_malloc(primebytelen)) == NULL) { wpa_printf(MSG_INFO, "EAP-pwd: unable to malloc space for prf " @@ -258,37 +261,10 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, */ crypto_bignum_deinit(x_candidate, 1); x_candidate = crypto_bignum_init_set(x_bin, primebytelen); - if (!x_candidate) - goto fail; - - /* y = sqrt(x^3 + ax + b) mod p - * if LSB(y) == LSB(pwd-seed): PWE = (x, y) - * else: PWE = (x, p - y) - * - * Calculate y and the two possible values for PWE and after that, - * use constant time selection to copy the correct alternative. - */ - y = crypto_ec_point_compute_y_sqr(grp->group, x_candidate); - if (!y || - dragonfly_sqrt(grp->group, y, y) < 0 || - crypto_bignum_to_bin(y, x_y, MAX_ECC_PRIME_LEN, primebytelen) < 0 || - crypto_bignum_sub(prime, y, y) < 0 || - crypto_bignum_to_bin(y, x_y + MAX_ECC_PRIME_LEN, - MAX_ECC_PRIME_LEN, primebytelen) < 0) { - wpa_printf(MSG_DEBUG, "SAE: Could not solve y"); - goto fail; - } - - /* Constant time selection of the y coordinate from the two - * options */ - is_eq = const_time_eq(is_odd, x_y[primebytelen - 1] & 0x01); - const_time_select_bin(is_eq, x_y, x_y + MAX_ECC_PRIME_LEN, - primebytelen, x_y + primebytelen); - os_memcpy(x_y, x_bin, primebytelen); - wpa_hexdump_key(MSG_DEBUG, "EAP-pwd: PWE", x_y, 2 * primebytelen); - grp->pwe = crypto_ec_point_from_bin(grp->group, x_y); - if (!grp->pwe) { - wpa_printf(MSG_DEBUG, "EAP-pwd: Could not generate PWE"); + if (!x_candidate || + crypto_ec_point_solve_y_coord(grp->group, grp->pwe, x_candidate, + is_odd) != 0) { + wpa_printf(MSG_INFO, "EAP-pwd: Could not solve for y"); goto fail; } @@ -313,7 +289,6 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, /* cleanliness and order.... */ crypto_bignum_deinit(x_candidate, 1); crypto_bignum_deinit(tmp2, 1); - crypto_bignum_deinit(y, 1); crypto_bignum_deinit(qr, 1); crypto_bignum_deinit(qnr, 1); bin_clear_free(prfbuf, primebytelen); @@ -321,7 +296,6 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, os_memset(qnr_bin, 0, sizeof(qnr_bin)); os_memset(qr_or_qnr_bin, 0, sizeof(qr_or_qnr_bin)); os_memset(pwe_digest, 0, sizeof(pwe_digest)); - forced_memzero(x_y, sizeof(x_y)); return ret; } diff --git a/contrib/wpa/wpa_supplicant/ChangeLog b/contrib/wpa/wpa_supplicant/ChangeLog index efcc6cd9c9ba..5ca82457ad1b 100644 --- a/contrib/wpa/wpa_supplicant/ChangeLog +++ b/contrib/wpa/wpa_supplicant/ChangeLog @@ -1,58 +1,5 @@ ChangeLog for wpa_supplicant -2022-01-16 - v2.10 - * SAE changes - - improved protection against side channel attacks - [https://w1.fi/security/2022-1/] - - added support for the hash-to-element mechanism (sae_pwe=1 or - sae_pwe=2); this is currently disabled by default, but will likely - get enabled by default in the future - - fixed PMKSA caching with OKC - - added support for SAE-PK - * EAP-pwd changes - - improved protection against side channel attacks - [https://w1.fi/security/2022-1/] - * fixed P2P provision discovery processing of a specially constructed - invalid frame - [https://w1.fi/security/2021-1/] - * fixed P2P group information processing of a specially constructed - invalid frame - [https://w1.fi/security/2020-2/] - * fixed PMF disconnection protection bypass in AP mode - [https://w1.fi/security/2019-7/] - * added support for using OpenSSL 3.0 - * increased the maximum number of EAP message exchanges (mainly to - support cases with very large certificates) - * fixed various issues in experimental support for EAP-TEAP peer - * added support for DPP release 2 (Wi-Fi Device Provisioning Protocol) - * a number of MKA/MACsec fixes and extensions - * added support for SAE (WPA3-Personal) AP mode configuration - * added P2P support for EDMG (IEEE 802.11ay) channels - * fixed EAP-FAST peer with TLS GCM/CCM ciphers - * improved throughput estimation and BSS selection - * dropped support for libnl 1.1 - * added support for nl80211 control port for EAPOL frame TX/RX - * fixed OWE key derivation with groups 20 and 21; this breaks backwards - compatibility for these groups while the default group 19 remains - backwards compatible - * added support for Beacon protection - * added support for Extended Key ID for pairwise keys - * removed WEP support from the default build (CONFIG_WEP=y can be used - to enable it, if really needed) - * added a build option to remove TKIP support (CONFIG_NO_TKIP=y) - * added support for Transition Disable mechanism to allow the AP to - automatically disable transition mode to improve security - * extended D-Bus interface - * added support for PASN - * added a file-based backend for external password storage to allow - secret information to be moved away from the main configuration file - without requiring external tools - * added EAP-TLS peer support for TLS 1.3 (disabled by default for now) - * added support for SCS, MSCS, DSCP policy - * changed driver interface selection to default to automatic fallback - to other compiled in options - * a large number of other fixes, cleanup, and extensions - 2019-08-07 - v2.9 * SAE changes - disable use of groups using Brainpool curves diff --git a/contrib/wpa/wpa_supplicant/README b/contrib/wpa/wpa_supplicant/README index c643b2684700..05f15ff46bda 100644 --- a/contrib/wpa/wpa_supplicant/README +++ b/contrib/wpa/wpa_supplicant/README @@ -1,7 +1,7 @@ wpa_supplicant ============== -Copyright (c) 2003-2022, Jouni Malinen and contributors +Copyright (c) 2003-2019, Jouni Malinen and contributors All Rights Reserved. This program is licensed under the BSD license (the one with diff --git a/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/IIface.aidl b/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/IIface.aidl new file mode 100644 index 000000000000..ea11d426df1f --- /dev/null +++ b/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/IIface.aidl @@ -0,0 +1,16 @@ +/* + * binder interface for wpa_supplicant daemon + * Copyright (c) 2004-2016, Jouni Malinen + * Copyright (c) 2004-2016, Roshan Pius + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +package fi.w1.wpa_supplicant; + +/** + * Interface exposed by wpa_supplicant for each network interface it controls. + */ +interface IIface { +} diff --git a/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/ISupplicant.aidl b/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/ISupplicant.aidl new file mode 100644 index 000000000000..1cbee20a620f --- /dev/null +++ b/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/ISupplicant.aidl @@ -0,0 +1,59 @@ +/* + * WPA Supplicant - binder interface for wpa_supplicant daemon + * Copyright (c) 2004-2016, Jouni Malinen + * Copyright (c) 2004-2016, Roshan Pius + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +package fi.w1.wpa_supplicant; + +import android.os.PersistableBundle; +import fi.w1.wpa_supplicant.IIface; + +/** + * Interface exposed by the wpa_supplicant binder service registered + * with the service manager with name: fi.w1.wpa_supplicant. + */ +interface ISupplicant { + /* Error values returned by the service to RPC method calls. */ + const int ERROR_INVALID_ARGS = 1; + const int ERROR_UNKNOWN = 2; + const int ERROR_IFACE_EXISTS = 3; + const int ERROR_IFACE_UNKNOWN = 4; + + /** + * Registers a wireless interface in wpa_supplicant. + * + * @param args A dictionary with arguments used to add the interface to + * wpa_supplicant. + * The dictionary may contain the following entries: + * Ifname(String) Name of the network interface to control, e.g., + * wlan0. + * BridgeIfname(String) Name of the bridge interface to control, e.g., + * br0. + * Driver(String) Driver name which the interface uses, e.g., nl80211. + * ConfigFile(String) Configuration file path. + * + * @return Binder object representing the interface. + */ + IIface CreateInterface(in PersistableBundle args); + + /** + * Deregisters a wireless interface from wpa_supplicant. + * + * @param ifname Name of the network interface, e.g., wlan0 + */ + void RemoveInterface(in @utf8InCpp String ifname); + + /** + * Gets a binder object for the interface corresponding to ifname + * which wpa_supplicant already controls. + * + * @param ifname Name of the network interface, e.g., wlan0 + * + * @return Binder object representing the interface. + */ + IIface GetInterface(in @utf8InCpp String ifname); +} diff --git a/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/ISupplicantCallbacks.aidl b/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/ISupplicantCallbacks.aidl new file mode 100644 index 000000000000..d624d9133603 --- /dev/null +++ b/contrib/wpa/wpa_supplicant/binder/fi/w1/wpa_supplicant/ISupplicantCallbacks.aidl @@ -0,0 +1,20 @@ +/* + * binder interface for wpa_supplicant daemon + * Copyright (c) 2004-2016, Jouni Malinen + * Copyright (c) 2004-2016, Roshan Pius + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +package fi.w1.wpa_supplicant; + +import android.os.PersistableBundle; + +/** + * Callback Interface exposed by the wpa_supplicant service. Clients need + * to host an instance of this binder object and pass a reference of the object + * to wpa_supplicant via the registerCallbacksObject method. + */ +interface ISupplicantCallbacks { +} diff --git a/contrib/wpa/wpa_supplicant/dbus/dbus_new_handlers.c b/contrib/wpa/wpa_supplicant/dbus/dbus_new_handlers.c index 959a68b4cdf5..545e9f64295a 100644 --- a/contrib/wpa/wpa_supplicant/dbus/dbus_new_handlers.c +++ b/contrib/wpa/wpa_supplicant/dbus/dbus_new_handlers.c @@ -1780,7 +1780,6 @@ DBusMessage * wpas_dbus_handler_remove_all_creds(DBusMessage *message, } -#ifdef CONFIG_INTERWORKING DBusMessage * wpas_dbus_handler_interworking_select(DBusMessage *message, struct wpa_supplicant *wpa_s) @@ -1801,7 +1800,6 @@ wpas_dbus_handler_interworking_select(DBusMessage *message, return reply; } -#endif /* CONFIG_INTERWORKING */ /** diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/eapol_test.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/eapol_test.sgml index b9b0a95c2691..4cfa3c1db384 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/eapol_test.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/eapol_test.sgml @@ -198,7 +198,7 @@ eapol_test -ctest.conf -a127.0.0.1 -p1812 -ssecret -r1 Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_background.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_background.sgml index b0592e233b88..22241ccf9006 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_background.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_background.sgml @@ -94,7 +94,7 @@ Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_cli.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_cli.sgml index b3d95ee20abb..2ba1fe42236a 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_cli.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_cli.sgml @@ -349,7 +349,7 @@ CTRL-REQ-OTP-2:Challenge 1235663 needed for SSID foobar Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_gui.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_gui.sgml index c391645eef15..cb0c735e4ce7 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_gui.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_gui.sgml @@ -95,7 +95,7 @@ Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_passphrase.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_passphrase.sgml index 5934e79ae108..077296904f89 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_passphrase.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_passphrase.sgml @@ -66,7 +66,7 @@ Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_priv.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_priv.sgml index 4053eda3b3ba..0d5c94a9f776 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_priv.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_priv.sgml @@ -141,7 +141,7 @@ wpa_supplicant -i ath0 -c wpa_supplicant.conf Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_supplicant.sgml b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_supplicant.sgml index 02012d1b08de..e4a83698393a 100644 --- a/contrib/wpa/wpa_supplicant/doc/docbook/wpa_supplicant.sgml +++ b/contrib/wpa/wpa_supplicant/doc/docbook/wpa_supplicant.sgml @@ -753,7 +753,7 @@ fi Legal - wpa_supplicant is copyright (c) 2003-2022, + wpa_supplicant is copyright (c) 2003-2019, Jouni Malinen j@w1.fi and contributors. All Rights Reserved. diff --git a/contrib/wpa/wpa_supplicant/sme.c b/contrib/wpa/wpa_supplicant/sme.c index 7f43216c6582..1dc7001a7305 100644 --- a/contrib/wpa/wpa_supplicant/sme.c +++ b/contrib/wpa/wpa_supplicant/sme.c @@ -946,9 +946,6 @@ static void sme_auth_start_cb(struct wpa_radio_work *work, int deinit) struct wpa_supplicant *wpa_s = work->wpa_s; wpa_s->roam_in_progress = false; -#ifdef CONFIG_WNM - wpa_s->bss_trans_mgmt_in_progress = false; -#endif /* CONFIG_WNM */ if (deinit) { if (work->started) @@ -995,13 +992,6 @@ void sme_authenticate(struct wpa_supplicant *wpa_s, "SME: Reject sme_authenticate() in favor of explicit roam request"); return; } -#ifdef CONFIG_WNM - if (wpa_s->bss_trans_mgmt_in_progress) { - wpa_dbg(wpa_s, MSG_DEBUG, - "SME: Reject sme_authenticate() in favor of BSS transition management request"); - return; - } -#endif /* CONFIG_WNM */ if (radio_work_pending(wpa_s, "sme-connect")) { /* * The previous sme-connect work might no longer be valid due to diff --git a/contrib/wpa/wpa_supplicant/vs2005/eapol_test/eapol_test.vcproj b/contrib/wpa/wpa_supplicant/vs2005/eapol_test/eapol_test.vcproj new file mode 100755 index 000000000000..c92b8fd89d6c --- /dev/null +++ b/contrib/wpa/wpa_supplicant/vs2005/eapol_test/eapol_test.vcproj @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/wpa/wpa_supplicant/vs2005/wpa_cli/wpa_cli.vcproj b/contrib/wpa/wpa_supplicant/vs2005/wpa_cli/wpa_cli.vcproj new file mode 100755 index 000000000000..d2de768e7cdc --- /dev/null +++ b/contrib/wpa/wpa_supplicant/vs2005/wpa_cli/wpa_cli.vcproj @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/wpa/wpa_supplicant/vs2005/wpa_passphrase/wpa_passphrase.vcproj b/contrib/wpa/wpa_supplicant/vs2005/wpa_passphrase/wpa_passphrase.vcproj new file mode 100755 index 000000000000..97aa2c5aecb5 --- /dev/null +++ b/contrib/wpa/wpa_supplicant/vs2005/wpa_passphrase/wpa_passphrase.vcproj @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/wpa/wpa_supplicant/vs2005/wpa_supplicant/wpa_supplicant.vcproj b/contrib/wpa/wpa_supplicant/vs2005/wpa_supplicant/wpa_supplicant.vcproj new file mode 100755 index 000000000000..10c05b565597 --- /dev/null +++ b/contrib/wpa/wpa_supplicant/vs2005/wpa_supplicant/wpa_supplicant.vcproj @@ -0,0 +1,465 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/wpa/wpa_supplicant/wnm_sta.c b/contrib/wpa/wpa_supplicant/wnm_sta.c index 96160dccbf5b..8a1a44690ba5 100644 --- a/contrib/wpa/wpa_supplicant/wnm_sta.c +++ b/contrib/wpa/wpa_supplicant/wnm_sta.c @@ -1097,8 +1097,6 @@ static void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, struct wpa_ssid *ssid, int after_new_scan) { - struct wpa_radio_work *already_connecting; - wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Transition to BSS " MACSTR " based on BSS Transition Management Request (old BSSID " @@ -1123,18 +1121,9 @@ static void wnm_bss_tm_connect(struct wpa_supplicant *wpa_s, return; } - already_connecting = radio_work_pending(wpa_s, "sme-connect"); wpa_s->reassociate = 1; wpa_printf(MSG_DEBUG, "WNM: Issuing connect"); wpa_supplicant_connect(wpa_s, bss, ssid); - - /* - * Indicate that a BSS transition is in progress so scan results that - * come in before the 'sme-connect' radio work gets executed do not - * override the original connection attempt. - */ - if (!already_connecting && radio_work_pending(wpa_s, "sme-connect")) - wpa_s->bss_trans_mgmt_in_progress = true; wnm_deallocate_memory(wpa_s); } diff --git a/contrib/wpa/wpa_supplicant/wpa_cli.c b/contrib/wpa/wpa_supplicant/wpa_cli.c index 465d63862ee7..5bbc4535e364 100644 --- a/contrib/wpa/wpa_supplicant/wpa_cli.c +++ b/contrib/wpa/wpa_supplicant/wpa_cli.c @@ -1,6 +1,6 @@ /* * WPA Supplicant - command line interface for wpa_supplicant daemon - * Copyright (c) 2004-2022, Jouni Malinen + * Copyright (c) 2004-2019, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -29,7 +29,7 @@ static const char *const wpa_cli_version = "wpa_cli v" VERSION_STR "\n" -"Copyright (c) 2004-2022, Jouni Malinen and contributors"; +"Copyright (c) 2004-2019, Jouni Malinen and contributors"; #define VENDOR_ELEM_FRAME_ID \ " 0: Probe Req (P2P), 1: Probe Resp (P2P) , 2: Probe Resp (GO), " \ diff --git a/contrib/wpa/wpa_supplicant/wpa_supplicant.c b/contrib/wpa/wpa_supplicant/wpa_supplicant.c index cf990f5a8f85..53b44035f16e 100644 --- a/contrib/wpa/wpa_supplicant/wpa_supplicant.c +++ b/contrib/wpa/wpa_supplicant/wpa_supplicant.c @@ -1,6 +1,6 @@ /* * WPA Supplicant - * Copyright (c) 2003-2022, Jouni Malinen + * Copyright (c) 2003-2019, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -71,7 +71,7 @@ const char *const wpa_supplicant_version = "wpa_supplicant v" VERSION_STR "\n" -"Copyright (c) 2003-2022, Jouni Malinen and contributors"; +"Copyright (c) 2003-2019, Jouni Malinen and contributors"; const char *const wpa_supplicant_license = "This software may be distributed under the terms of the BSD license.\n" @@ -3621,11 +3621,6 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) struct ieee80211_vht_capabilities vhtcaps_mask; #endif /* CONFIG_VHT_OVERRIDES */ - wpa_s->roam_in_progress = false; -#ifdef CONFIG_WNM - wpa_s->bss_trans_mgmt_in_progress = false; -#endif /* CONFIG_WNM */ - if (deinit) { if (work->started) { wpa_s->connect_work = NULL; @@ -8178,10 +8173,6 @@ void wpas_request_disconnection(struct wpa_supplicant *wpa_s) eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL); radio_remove_works(wpa_s, "connect", 0); radio_remove_works(wpa_s, "sme-connect", 0); - wpa_s->roam_in_progress = false; -#ifdef CONFIG_WNM - wpa_s->bss_trans_mgmt_in_progress = false; -#endif /* CONFIG_WNM */ } diff --git a/contrib/wpa/wpa_supplicant/wpa_supplicant_i.h b/contrib/wpa/wpa_supplicant/wpa_supplicant_i.h index 8bb8672fbf7f..5fa765fda25c 100644 --- a/contrib/wpa/wpa_supplicant/wpa_supplicant_i.h +++ b/contrib/wpa/wpa_supplicant/wpa_supplicant_i.h @@ -1286,7 +1286,6 @@ struct wpa_supplicant { struct os_reltime wnm_cand_valid_until; u8 wnm_cand_from_bss[ETH_ALEN]; enum bss_trans_mgmt_status_code bss_tm_status; - bool bss_trans_mgmt_in_progress; struct wpabuf *coloc_intf_elems; u8 coloc_intf_dialog_token; u8 coloc_intf_auto_report; diff --git a/sys/contrib/openzfs/tests/zfs-tests/cmd/send_doall/.gitignore b/sys/contrib/openzfs/tests/zfs-tests/cmd/send_doall/.gitignore new file mode 100644 index 000000000000..6ba2e603f744 --- /dev/null +++ b/sys/contrib/openzfs/tests/zfs-tests/cmd/send_doall/.gitignore @@ -0,0 +1 @@ +/send_doall diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 000000000000..561a0ec5fcab --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE} + +${PACKAGE}FILES+= README + +KYUAFILE= yes + +SUBDIR+= etc +SUBDIR+= sys + +SUBDIR_PARALLEL= + +afterinstall: install-tests-local +install-tests-local: .PHONY + ${INSTALL_SYMLINK} -T 'package=tests' \ + ../local/tests ${DESTDIR}${TESTSDIR}/local + +.include "Makefile.inc0" +.include diff --git a/tests/README b/tests/README new file mode 100644 index 000000000000..c014db2cb9ba --- /dev/null +++ b/tests/README @@ -0,0 +1,62 @@ +src/tests: The FreeBSD test suite +================================= + +Usage of the FreeBSD test suite: +(1) Run the tests: + kyua test -k /usr/tests/Kyuafile +(2) See the test results: + kyua report + +For further information on using the test suite, read tests(7): + man tests + +Description of FreeBSD test suite +================================= +The build of the test suite is organized in the following manner: + +* The build of all test artifacts is protected by the MK_TESTS knob. + The user can disable these with the WITHOUT_TESTS setting in + src.conf(5). + +* The goal for /usr/tests/ (the installed test programs) is to follow + the same hierarchy as /usr/src/ wherever possible, which in turn drives + several of the design decisions described below. This simplifies the + discoverability of tests. We want a mapping such as: + + /usr/src/bin/cp/ -> /usr/tests/bin/cp/ + /usr/src/lib/libc/ -> /usr/tests/lib/libc/ + /usr/src/usr.bin/cut/ -> /usr/tests/usr.bin/cut/ + ... and many more ... + +* Test programs for specific utilities and libraries are located next + to the source code of such programs. For example, the tests for the + src/lib/libcrypt/ library live in src/lib/libcrypt/tests/. The tests/ + subdirectory is optional and should, in general, be avoided. + +* The src/tests/ hierarchy (this directory) provides generic test + infrastructure and glue code to join all test programs together into + a single test suite definition. + +* The src/tests/ hierarchy also includes cross-functional test programs: + i.e. test programs that cover more than a single utility or library + and thus don't fit anywhere else in the tree. Consider this to follow + the same rationale as src/share/man/: this directory contains generic + manual pages while the manual pages that are specific to individual + tools or libraries live next to the source code. + +In order to keep the src/tests/ hierarchy decoupled from the actual test +programs being installed --which is a worthy goal because it simplifies +the addition of new test programs and simplifies the maintenance of the +tree-- the top-level Kyuafile does not know which subdirectories may +exist upfront. Instead, such Kyuafile automatically detects, at +run-time, which */Kyuafile files exist and uses those directly. + +Similarly, every directory in src/ that wants to install a Kyuafile to +just recurse into other subdirectories reuses this Kyuafile with +auto-discovery features. As an example, take a look at src/lib/tests/ +whose sole purpose is to install a Kyuafile into /usr/tests/lib/. +The goal in this specific case is for /usr/tests/lib/ to be generated +entirely from src/lib/. + +-- +$FreeBSD$ diff --git a/usr.bin/bmake/tests/archives/fmt_44bsd/expected.status.1 b/usr.bin/bmake/tests/archives/fmt_44bsd/expected.status.1 new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/usr.bin/bmake/tests/archives/fmt_44bsd/expected.status.1 @@ -0,0 +1 @@ +0