From 25dc10cbfd375cec2e1e926a51effee068541ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Tue, 25 Mar 2025 13:13:54 +0100 Subject: [PATCH 1/9] Rewrite of generate_config in python --- .drone.yml | 4 +- CMakeLists.txt | 4 +- Dockerfile | 2 +- scripts/generate_config.pl | 123 ------------------------------------- scripts/generate_config.py | 112 +++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 128 deletions(-) delete mode 100755 scripts/generate_config.pl create mode 100644 scripts/generate_config.py diff --git a/.drone.yml b/.drone.yml index 76e74e9..665b314 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ steps: - name: build hermes image: alpine commands: - - apk add -t hermes-build-deps --no-cache perl graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev + - apk add -t hermes-build-deps --no-cache graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev python3 - cmake -B build_dir -D BUILD_DOCS=ON - cmake --build build_dir - name: docker image build @@ -41,7 +41,7 @@ steps: - name: build hermes image: alpine commands: - - apk add -t hermes-build-deps --no-cache perl graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev + - apk add -t hermes-build-deps --no-cache graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev python3 - cmake -B build_dir - cmake --build build_dir - name: docker image build diff --git a/CMakeLists.txt b/CMakeLists.txt index 257ad0e..e25dd14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,9 @@ include_directories( add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Configfile.cpp COMMAND cpp ${OPT_DEFS} ${CMAKE_CURRENT_SOURCE_DIR}/src/Configfile.tmpl -I ${CMAKE_CURRENT_BINARY_DIR} | - ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.pl + python ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.py DEPENDS src/Configfile.cpp.in src/Configfile.h.in src/Configfile.tmpl - docs/hermes-options.html.in scripts/generate_config.pl) + docs/hermes-options.html.in scripts/generate_config.py) # doxygen diff --git a/Dockerfile b/Dockerfile index 9beba8c..9d3c1dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM alpine:3.18 ADD . /hermes WORKDIR /hermes -RUN apk add --no-cache perl graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev +RUN apk add --no-cache graphviz doxygen gcc make openssl-dev libspf2-dev cmake g++ sqlite-dev gettext-dev python3 RUN cmake -B build RUN cmake --build build RUN mkdir /hermes-installation diff --git a/scripts/generate_config.pl b/scripts/generate_config.pl deleted file mode 100755 index 0c2faf0..0000000 --- a/scripts/generate_config.pl +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/perl -w - -# this small script generates the Configfile class from the -# Configfile.cpp.in and Configfile.h.in. this way when we want -# to add a new option to the config file, we just have to put it -# on Configfile.tmpl and automagically it will appear on our code -# It will also generate an example hermesrc from the same info. -# 2007-04-17 Now it also generates an html document for our webpage - -my $hvar=""; -my $cppvar1="",$cppvar2="",$cppvar3="",$conf_example="",$htmlvar=""; - -open HTMLIN, "<../docs/hermes-options.html.in"; -$htmltempl=join("",); -close HTMLIN; - -while(<>) -{ - chomp; - if(! /^#/ && ! /^\t*$/ && ! /^\*/) - { - s/^\s+//;s/\s+$//; - @_=split ","; - my $camelcased=&camel_case($_[1]); - my $type=$_[0]; - $type="list" if($type =~ /list/); - $hvar1.="$type $_[1];\n"; - $hvar2.="$type& get$camelcased();\n"; - if($type =~ /list/) - { - $cppvar1.="$_[1]=Configfile::parseAsList($_[2]);\n"; - } - else - { - $cppvar1.="$_[1]=$_[2];\n"; - } - $cppvar2.="PARSE_".uc($_[0])."(\"$_[1]\",$_[1])\n"; - $cppvar3.="GET_VAR(get$camelcased,$_[1],$type&)\n"; - $conf_example.="$_[1] = $_[2]\n\n"; - my $htmltemp=$htmltempl; - $htmltemp =~ s/%type%/$_[0]/; - $htmltemp =~ s/%name%/$_[1]/g; - $htmltemp =~ s/%default%/$_[2]/; - $htmltemp =~ s/%explanation%/$htmlexpl/; - $htmlexpl=""; - $htmlvar.=$htmltemp; - } - else - { - if(/^\*clean\*$/) # clean restarts our htmlexpl contents - { - $htmlexpl=""; - } - else - { - if(/^\*/) - { - s/^\*$//; - s/^\*/#/; - $conf_example.="$_\n"; - chomp; - s/^#//; - s/>/>/; - $htmlexpl.="$_\n"; - } - } - } -} - -chomp $cppvar1; -chomp $cppvar2; -chomp $cppvar3; -chomp $hvar1; -chomp $hvar2; -chomp $conf_example; - -open CPPIN, "<../src/Configfile.cpp.in"; -$cppstr=join("",); -close CPPIN; -open CPPOUT, ">Configfile.cpp"; -$cppstr =~ s/%templ_default_values%/$cppvar1/; -$cppstr =~ s/%templ_parsevars%/$cppvar2/; -$cppstr =~ s/%templ_getmethods%/$cppvar3/; -print CPPOUT $cppstr; -close CPPOUT; - -open HIN, "<../src/Configfile.h.in"; -$hstr=join("",); -close HIN; -open HOUT, ">Configfile.h"; -$hstr =~ s/%templ_privateattribs%/$hvar1/; -$hstr =~ s/%templ_publicmethods%/$hvar2/; -print HOUT $hstr; -close HOUT; - -open RCEX, ">../dists/hermesrc.example"; -print RCEX $conf_example; -close RCEX; - -open HTML, ">../docs/hermes-options.html"; -print HTML $htmlvar; -close HTML; - -sub camel_case() -{ - my $str=shift; - my $outstr=""; - - for($i=0;$i', '>') + htmlexpl += line_html + "\n" + continue + + parts = line.split(',') + parts = [p.strip() for p in parts] + + type_str = parts[0] + var_name = parts[1] + default_val = parts[2] + + # Modify type for lists + if 'list' in type_str: + type_str = 'list' + + camel_name = camel_case(var_name) + + # Generate header variables + hvar1 += f"{type_str} {var_name};\n" + hvar2 += f"{type_str}& get{camel_name}();\n" + + # Generate cpp variables + if 'list' in type_str: + cppvar1 += f"{var_name} = Configfile::parseAsList({default_val});\n" + else: + cppvar1 += f"{var_name} = {default_val};\n" + + cppvar2 += f"PARSE_{parts[0].upper()}(\"{var_name}\", {var_name})\n" + cppvar3 += f"GET_VAR(get{camel_name}, {var_name}, {type_str}&)\n" + + # Generate config example + conf_example += f"{var_name} = {default_val}\n\n" + + # Generate HTML + html_temp = html_templ.replace('%type%', parts[0]) \ + .replace('%name%', var_name) \ + .replace('%default%', default_val) \ + .replace('%explanation%', htmlexpl) + htmlvar += html_temp + htmlexpl = "" + + # Clean up variables + for var in [cppvar1, cppvar2, cppvar3, hvar1, hvar2, conf_example]: + var = var.rstrip() + + # Read and write Configfile.cpp + with open('../src/Configfile.cpp.in', 'r') as f: + cpp_str = f.read() + + cpp_str = cpp_str.replace('%templ_default_values%', cppvar1) \ + .replace('%templ_parsevars%', cppvar2) \ + .replace('%templ_getmethods%', cppvar3) + + with open('Configfile.cpp', 'w') as f: + f.write(cpp_str) + + # Read and write Configfile.h + with open('../src/Configfile.h.in', 'r') as f: + h_str = f.read() + + h_str = h_str.replace('%templ_privateattribs%', hvar1) \ + .replace('%templ_publicmethods%', hvar2) + + with open('Configfile.h', 'w') as f: + f.write(h_str) + + # Write hermesrc.example + with open('../dists/hermesrc.example', 'w') as f: + f.write(conf_example) + + # Write hermes-options.html + with open('../docs/hermes-options.html', 'w') as f: + f.write(htmlvar) + +if __name__ == "__main__": + main() From b219eabe99ab0dbbb62f5fc212c09203db821b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Wed, 26 Mar 2025 14:03:55 +0100 Subject: [PATCH 2/9] Upgrade doxyfile --- docs/Doxyfile | 1197 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 789 insertions(+), 408 deletions(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 109f851..6de2b5c 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,7 +1,7 @@ -# Doxyfile 1.8.16 +# Doxyfile 1.12.0 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. +# Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -53,24 +63,42 @@ PROJECT_BRIEF = PROJECT_LOGO = +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If +# entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = -# 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 +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 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 causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. @@ -79,36 +107,28 @@ CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this +# documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, 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. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, 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 @@ -129,13 +149,13 @@ REPEAT_BRIEF = YES 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 +# Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# 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. @@ -143,7 +163,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, 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 # The default value is: YES. @@ -153,11 +173,11 @@ FULL_PATH_NAMES = YES # 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 +# If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. +# will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @@ -171,14 +191,14 @@ STRIP_FROM_PATH = STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# 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. # The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# 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-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief @@ -187,17 +207,17 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. +# interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# 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 Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) @@ -205,7 +225,7 @@ JAVADOC_BANNER = NO QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# 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 behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this @@ -217,13 +237,21 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# 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. # The default value is: NO. @@ -240,25 +268,19 @@ TAB_SIZE = 8 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # 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 (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # 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 @@ -299,27 +321,30 @@ OPTIMIZE_OUTPUT_SLICE = NO # 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, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. 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. +# default for Fortran type files). 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: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. @@ -329,12 +354,23 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. +# Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 5 -# When enabled doxygen tries to link words that correspond to documented +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. @@ -344,10 +380,10 @@ AUTOLINK_SUPPORT = YES # 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 +# tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES @@ -359,16 +395,16 @@ BUILTIN_STL_SUPPORT = YES CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) 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. +# https://www.riverbankcomputing.com/software) 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. # The default value is: NO. 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 will make -# doxygen to replace the get and set methods by a property in the documentation. +# 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. @@ -377,7 +413,7 @@ SIP_SUPPORT = 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 +# 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. # The default value is: NO. @@ -435,21 +471,42 @@ TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest +# symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# 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 respectively EXTRACT_STATIC tags are set to YES. @@ -508,7 +565,14 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO 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. @@ -516,22 +580,23 @@ EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# 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, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. 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, these declarations will be -# included in the documentation. +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# 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, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -545,30 +610,44 @@ HIDE_IN_BODY_DOCS = NO 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 -# (including Cygwin) ands Mac users are advised to set this option to NO. -# The default value is: system dependent. +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -581,7 +660,7 @@ SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = NO -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# 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. # The default value is: NO. @@ -593,14 +672,14 @@ FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# If the SORT_MEMBER_DOCS tag is set to YES 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. # The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. @@ -608,7 +687,7 @@ SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# 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 constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. @@ -620,7 +699,7 @@ SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# 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 group names will # appear in their defined order. # The default value is: NO. @@ -637,11 +716,11 @@ SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. @@ -711,24 +790,25 @@ SHOW_FILES = 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 +# 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 command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file +# by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. 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 +# by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can +# 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. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = @@ -743,19 +823,35 @@ LAYOUT_FILE = CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the +# standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -763,49 +859,89 @@ QUIET = NO WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag 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. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If the WARN_IF_DOC_ERROR tag 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. +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO -# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# 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) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # 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 standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = @@ -822,29 +958,45 @@ WARN_LOGFILE = INPUT = ../src # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. +# read by Doxygen. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. -FILE_PATTERNS = *.cpp, *.h +FILE_PATTERNS = *.cpp \ + *.h # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -856,7 +1008,7 @@ RECURSIVE = YES # 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. # -# Note that relative paths are relative to the directory from which doxygen is +# Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = @@ -881,10 +1033,7 @@ EXCLUDE_PATTERNS = # (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 -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -914,7 +1063,7 @@ EXAMPLE_RECURSIVE = NO IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should +# 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: # @@ -929,9 +1078,14 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. INPUT_FILTER = @@ -944,7 +1098,7 @@ INPUT_FILTER = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. FILTER_PATTERNS = @@ -966,10 +1120,19 @@ FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. +# and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -984,12 +1147,13 @@ USE_MDFILE_AS_MAINPAGE = SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. @@ -1027,7 +1191,7 @@ REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = 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 +# 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 https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. @@ -1041,14 +1205,14 @@ SOURCE_TOOLTIPS = YES # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # -# The result: instead of the source browser generated by doxygen, the links to +# The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# If the VERBATIM_HEADERS tag is set the YES 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. # See also: Section \class. @@ -1056,6 +1220,46 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1067,17 +1271,11 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = NO -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -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 a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1086,7 +1284,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1107,40 +1305,40 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a +# each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. +# that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally +# for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description +# default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard +# generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. +# that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # 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 left blank doxygen will generate a default style sheet. +# the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. +# sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. @@ -1150,13 +1348,18 @@ HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. +# created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1171,9 +1374,22 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://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. @@ -1183,7 +1399,7 @@ HTML_EXTRA_FILES = 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 +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1201,20 +1417,11 @@ HTML_COLORSTYLE_SAT = 100 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 YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that -# are dynamically created via Javascript. If disabled, the navigation index will +# are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have Javascript, +# page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1229,6 +1436,33 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1244,10 +1478,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # 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 (see: https://developer.apple.com/xcode/), 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 +# environment (see: +# https://developer.apple.com/xcode/), 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 https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1264,6 +1499,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset 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. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # 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. @@ -1286,14 +1528,18 @@ DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for @@ -1313,14 +1559,14 @@ CHM_FILE = # 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. +# Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # 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). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1347,6 +1593,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # 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 @@ -1365,7 +1621,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1373,8 +1630,8 @@ 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 Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1382,16 +1639,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1403,9 +1660,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# 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. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1448,18 +1705,30 @@ DISABLE_INDEX = NO # 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. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. +# Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. @@ -1468,6 +1737,12 @@ GENERATE_TREEVIEW = NO ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = 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. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -1475,35 +1750,48 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# If 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. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML +# Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. 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 directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. -FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1513,11 +1801,29 @@ FORMULA_TRANSPARENT = YES USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1530,33 +1836,40 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = -# 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 +# 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 +# For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically @@ -1573,9 +1886,9 @@ MATHJAX_CODEFILE = SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and +# setting. When disabled, Doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing # and searching needs to be provided by external tools. See the section # "External Indexing and Searching" for details. @@ -1584,7 +1897,7 @@ SEARCHENGINE = NO SERVER_BASED_SEARCH = NO -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# When EXTERNAL_SEARCH tag is enabled Doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain the @@ -1592,7 +1905,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1605,8 +1919,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1627,7 +1942,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through Doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of @@ -1641,7 +1956,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = YES @@ -1686,7 +2001,7 @@ MAKEINDEX_CMD_NAME = makeindex LATEX_MAKEINDEX_CMD = makeindex -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# 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. # The default value is: NO. @@ -1715,36 +2030,38 @@ PAPER_TYPE = a4wide 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. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined 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. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that Doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank Doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that Doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# by Doxygen. Using this option one can overrule certain style aspects. Doxygen # will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the @@ -1770,40 +2087,38 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = NO -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, Doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. 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. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# If the LATEX_HIDE_INDICES tag is set to YES then Doxygen will not include the # index chapters (such as File Index, Compound Index, etc.) in the output. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag 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. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1812,14 +2127,6 @@ LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# 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. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -1832,7 +2139,7 @@ LATEX_EMOJI_DIRECTORY = # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# 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 too pretty with other RTF # readers/editors. # The default value is: NO. @@ -1847,7 +2154,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact 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. # The default value is: NO. @@ -1867,38 +2174,36 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's +# Load stylesheet definitions from file. Syntax is similar to Doxygen's # configuration file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. +# default style sheet that Doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be +# similar to Doxygen's configuration file. A template extensions file can be # generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_SOURCE_CODE = NO +RTF_EXTRA_FILES = #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, Doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -1929,7 +2234,7 @@ MAN_EXTENSION = .3 MAN_SUBDIR = -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# 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. @@ -1942,7 +2247,7 @@ 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 +# 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. # The default value is: NO. @@ -1956,7 +2261,7 @@ GENERATE_XML = NO XML_OUTPUT = xml -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# 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. @@ -1965,7 +2270,7 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, Doxygen will include # namespace members in file scope as well, matching the HTML output. # The default value is: NO. # This tag requires that the tag GENERATE_XML is set to YES. @@ -1976,7 +2281,7 @@ XML_NS_MEMB_FILE_SCOPE = NO # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, Doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -1990,32 +2295,49 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# If the GENERATE_AUTOGEN_DEF tag is set to YES, Doxygen will generate an +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES Doxygen will generate a Sqlite3 +# database with symbols found by Doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each Doxygen run. If set to NO, Doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# 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. @@ -2023,7 +2345,7 @@ GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# 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. # The default value is: NO. @@ -2053,13 +2375,13 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, Doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# If the MACRO_EXPANSION tag is set to YES, Doxygen will expand all macro names # in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. @@ -2085,7 +2407,8 @@ 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. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2117,7 +2440,7 @@ PREDEFINED = EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# If the SKIP_FUNCTION_MACROS tag is set to YES then Doxygen's preprocessor will # remove all references to 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 @@ -2141,26 +2464,26 @@ SKIP_FUNCTION_MACROS = YES # section "Linking to external documentation" for more information about the use # of tag files. # Note: 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 +# 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 +# When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. 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 +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2174,42 +2497,26 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_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. # The default value is: YES. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# 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 (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), 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 value is: NO. HAVE_DOT = YES -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of +# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed +# to run in parallel. When set to 0 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. @@ -2218,55 +2525,83 @@ HAVE_DOT = YES DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You 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. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# Doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You 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. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is 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 CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# If the COLLABORATION_GRAPH tag is 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. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# 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. # The default value is: NO. @@ -2283,10 +2618,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, Doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2296,24 +2653,29 @@ UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the +# 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. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing +# 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. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# If the CALL_GRAPH tag is 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. @@ -2325,7 +2687,7 @@ INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# If the CALLER_GRAPH tag is 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. @@ -2337,26 +2699,36 @@ CALL_GRAPH = NO CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# If the DIRECTORY_GRAPH tag is 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. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2393,11 +2765,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in Doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2405,28 +2778,28 @@ MSCFILE_DIRS = DIAFILE_DIRS = -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. +# When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for PlantUML. PLANTUML_CFG_FILE = -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. +# When using PlantUML, the specified paths are searched for files specified by +# the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = # 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 +# larger than this value, Doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that 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. @@ -2447,18 +2820,6 @@ DOT_GRAPH_MAX_NODES = 50 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). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to 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 @@ -2468,17 +2829,37 @@ DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES + +# You can define message sequence charts within Doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, Doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = From cb2752889f849b293cfe8604455855b8b3fb9481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Wed, 26 Mar 2025 13:51:06 +0100 Subject: [PATCH 3/9] Remove stats submission --- src/Configfile.cpp.in | 11 ----------- src/hermes.cpp | 32 -------------------------------- 2 files changed, 43 deletions(-) diff --git a/src/Configfile.cpp.in b/src/Configfile.cpp.in index c433de4..d5b61e8 100644 --- a/src/Configfile.cpp.in +++ b/src/Configfile.cpp.in @@ -108,17 +108,6 @@ void Configfile::validateConfig() throw Exception("Directory "+chroot+" doesn't exist, can't chroot to it.",__FILE__,__LINE__); #endif //WIN32 - //check if we have submit_stats on but no user and password - if(getSubmitStats()&&(""==getSubmitStatsUsername()||""==getSubmitStatsPassword())) - throw Exception("You have configured hermes to send stats, but have not configured a username or password.\n" - "If you don't have one, go to http://www.hermes-project.com and register there",__FILE__,__LINE__); - - #ifndef HAVE_SSL - //check if we have activated submit_stats_ssl not having ssl activated - if(getSubmitStatsSsl()) - throw Exception("You have configured stats submission through SSL, but hermes was compiled without SSL support",__FILE__,__LINE__); - #endif //HAVE_SSL - } string Configfile::parseAsString(string str) diff --git a/src/hermes.cpp b/src/hermes.cpp index 9691326..be0e02a 100644 --- a/src/hermes.cpp +++ b/src/hermes.cpp @@ -332,38 +332,6 @@ void *cleaner_thread_run(void *) { LERR("Error cleaning the database: " + string(e)); } - if(spamcount>0&&cfg.getSubmitStats()) - { - try - { - Socket s; - string server_response; - - s.init(); - s.connect("stats.hermes-project.com",11125); - #ifdef HAVE_SSL - if(cfg.getSubmitStatsSsl()) - { - s.writeLine("ssl"); - s.prepareSSL(false); - s.startSSL(false); - } - else - #endif //HAVE_SSL - s.writeLine("non-ssl"); - s.writeLine(cfg.getSubmitStatsUsername()); - s.writeLine(cfg.getSubmitStatsPassword()); - s.writeLine(Utils::inttostr(spamcount)); - server_response=s.readLine(); - s.close(); - if("OK"!=server_response) - throw Exception(server_response,__FILE__,__LINE__); - } - catch(Exception &e) - { - LDEB("Exception sending stats: "+string(e)); - } - } } #ifndef WIN32 if(false==cfg.getBackground()) From d2a90e4cd1412df662f907a8a285d12e3826ef1c Mon Sep 17 00:00:00 2001 From: Juanjo Gutierrez Date: Sun, 23 Mar 2025 10:20:26 +0100 Subject: [PATCH 4/9] preliminar status, still need to make it work --- src/Proxy.cpp | 537 ++++++++++++++++++++------------------------------ 1 file changed, 216 insertions(+), 321 deletions(-) diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 319417c..1515869 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -1,338 +1,233 @@ -/** - * hermes antispam proxy - * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * @author Juan José Gutiérrez de Quevedo - */ +// Proxy.cpp #include "Proxy.h" +#include +#include +#include "Utils.h" // Dummy include; replace with actual Utils implementation +#include "Configfile.h" // Dummy include; replace with actual Configfile implementation -extern LOGGER_CLASS hermes_log; -extern Configfile cfg; +extern Configfile cfg; // External configuration -void Proxy::setOutside(Socket& p_outside) -{ - outside=p_outside; -} +void Proxy::run(std::string& peer_address) { + // Original comments and variables retained + std::string from = ""; + std::string to = ""; + std::string ehlostr = ""; + std::string resolvedname = ""; + unsigned char last_state = SMTP_STATE_WAIT_FOR_HELO; + long unimplemented_requests = 0; -/** - * this function is the main part of the program, it just sniffs traffic - * between server and client and acts acording to the following diagram: - * - * TODO: fill diagram and point to website with graphical version - * - */ -void Proxy::run(string &peer_address) -{ - #ifdef HAVE_SPF - Spf spf_checker; - #endif //HAVE_SPF + try { + bool throttled = cfg.getThrottle(); // Start with a throttled connection + bool authenticated = false; // Start with a non-authenticated connection + bool esmtp = false; + std::string strtemp; + std::string hermes_status = "unknown"; - string from=""; - string to=""; - string ehlostr=""; - string resolvedname=""; - unsigned char last_state=SMTP_STATE_WAIT_FOR_HELO; - long unimplemented_requests=0; - - try - { - bool throttled=cfg.getThrottle(); //we start with a throttled connection - bool authenticated=false; //we start with a non-authenticated connection - bool esmtp=false; - string strtemp; - string hermes_status="unknown"; - - //check whitelist - if(!cfg.getDnsWhitelistDomains().empty()&&Utils::listed_on_dns_lists(cfg.getDnsWhitelistDomains(),cfg.getDnsWhitelistPercentage(),peer_address)) - { - authenticated=true; - hermes_status="whitelisted"; - if(true==cfg.getWhitelistedDisablesEverything()) - throttled=false; - } - if(true==cfg.getWhitelistedDisablesEverything()&&Utils::whitelisted(cfg.getDatabaseFile(),peer_address)) - { - throttled=false; - authenticated=true; - } - else - { - if(false==cfg.getAllowDataBeforeBanner()) - { - sleep(cfg.getBannerDelayTime()); - if(outside.canRead(0)) //if we have data waiting before the server gives us a 220 then quit, it's spam - { - LINF("421 (data_before_banner) (ip:"+peer_address+")"); - sleep(20); // but first let's annoy spammers once more - outside.writeLine("421 Stop sending data before we show you the banner"); - return; - } - } - } - - inside.init(); - inside.connect(cfg.getServerHost(),cfg.getServerPort()); - #ifdef HAVE_SSL - if(cfg.getOutgoingSsl()) - { - inside.prepareSSL(false); - inside.startSSL(false); - } - if(cfg.getIncomingSsl()) - { - outside.prepareSSL(true); - outside.startSSL(true); - } - #endif //HAVE_SSL - - while(!outside.isClosed()&&!inside.isClosed()) - { - if(outside.canRead(0.2)) //client wants to send something to server - { - strtemp=outside.readLine(); - if(outside.isClosed()) - return; - if(strtemp.length()>10&&"mail from:"==Utils::strtolower(strtemp.substr(0,10))) - { - from=Utils::getmail(strtemp); - last_state=SMTP_STATE_WAIT_FOR_RCPTTO; - } - - if("ehlo"==Utils::strtolower(strtemp.substr(0,4))) - esmtp=true; - - if(strtemp.length()>4&&("ehlo"==Utils::strtolower(strtemp.substr(0,4))||"helo"==Utils::strtolower(strtemp.substr(0,4)))) - { - ehlostr=Utils::trim(strtemp.substr(5)); - last_state=SMTP_STATE_WAIT_FOR_MAILFROM; - } - - if(strtemp.length()>8&&"rcpt to:"==Utils::strtolower(strtemp.substr(0,8))) - { - string strlog=""; - string code=""; - string mechanism=""; - string message=""; - - to=Utils::getmail(strtemp); - try - { - resolvedname=Socket::resolveInverselyToString(peer_address); - } - catch(Exception &e) - { - resolvedname=""; - } - - strlog="from "+from+" (ip:"+peer_address+", hostname:"+resolvedname+", "+(esmtp?"ehlo":"helo")+":"+ehlostr+") -> to "+to; - - //check greylisting - if(cfg.getGreylist()&&!authenticated&&Utils::greylist(cfg.getDatabaseFile(),peer_address,from,to)) - { - //should we greylist¿? if we have to, quit and then sleep 20 seconds before closing the connection - code="421"; - mechanism="greylist"; - message=code+" Greylisted!! Please try again in a few minutes."; - LINF("checking " + mechanism); - } - #ifdef HAVE_SPF - else if(cfg.getQuerySpf()&&!authenticated&&!spf_checker.query(peer_address,ehlostr,from)) - { - hermes_status="spf-failed"; - if(cfg.getAddStatusHeader()) - code="250"; - else - code=cfg.getReturnTempErrorOnReject()?"421":"550"; - mechanism="spf"; - message=code+" You do not seem to be allowed to send email for that particular domain."; - LINF("checking " + mechanism); - } - #endif //HAVE_SPF - //check blacklist - else if(!authenticated&&Utils::blacklisted(cfg.getDatabaseFile(),peer_address,to)) - { - code=cfg.getReturnTempErrorOnReject()?"421":"550"; - mechanism="allowed-domain-per-ip"; - message=code+" You do not seem to be allowed to send email to that particular domain from that address."; - LINF("checking " + mechanism); - } - //check rbl - else if(!cfg.getDnsBlacklistDomains().empty()&&!authenticated&&Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(),cfg.getDnsBlacklistPercentage(),peer_address)) - { - hermes_status="blacklisted"; - if(cfg.getAddStatusHeader()) - code="250"; - else - code=cfg.getReturnTempErrorOnReject()?"421":"550"; - mechanism="dnsbl"; - message=code+" You are listed on some DNS blacklists. Get delisted before trying to send us email."; - LINF("checking " + mechanism); - } - else if(cfg.getRejectNoReverseResolution()&&!authenticated&&""==resolvedname) - { - code=cfg.getReturnTempErrorOnReject()?"421":"550"; - mechanism="no reverse resolution"; - message=code+" Your IP address does not resolve to a hostname."; - LINF("checking " + mechanism); - } - else if(cfg.getCheckHeloAgainstReverse()&&!authenticated&&ehlostr!=resolvedname) - { - code=cfg.getReturnTempErrorOnReject()?"421":"550"; - mechanism="helo differs from resolved name"; - message=code+" Your IP hostname doesn't match your envelope hostname."; - LINF("checking " + mechanism); - } - else - code="250"; - - if(""!=mechanism) - strlog.insert(0,"("+mechanism+") "); - strlog.insert(0,code+" "); - - //log the connection - LINF(strlog); - - //if we didn't accept the email, punish spammers - if("250"!=code) - { - inside.writeLine("QUIT"); - inside.close(); //close the socket now and leave server alone - sleep(20); - outside.writeLine(message); - return; - } - last_state=SMTP_STATE_WAIT_FOR_DATA; - } - - if("starttls"==Utils::strtolower(strtemp.substr(0,8))) - { - //if we have ssl then accept starttls, if not politely say fuck you - #ifdef HAVE_SSL - try - { - outside.prepareSSL(true); - LINF("STARTTLS issued by remote, TLS enabled"); - outside.writeLine("220 You can speak now, line is secure!!"); - outside.startSSL(true); + // Check whitelist + if (!cfg.getDnsWhitelistDomains().empty() && + Utils::listed_on_dns_lists(cfg.getDnsWhitelistDomains(), cfg.getDnsWhitelistPercentage(), peer_address)) { + authenticated = true; + hermes_status = "whitelisted"; + if (cfg.getWhitelistedDisablesEverything()) { + throttled = false; } - catch(Exception &e) - { - LINF("STARTTLS issued by remote, but enableSSL failed!"); - LERR(e); - outside.writeLine("454 Tried to enable SSL but failed"); + } + + if (cfg.getWhitelistedDisablesEverything() && Utils::whitelisted(cfg.getDatabaseFile(), peer_address)) { + throttled = false; + authenticated = true; + } else { + if (!cfg.getAllowDataBeforeBanner()) { + std::this_thread::sleep_for(std::chrono::seconds(cfg.getBannerDelayTime())); + if (outside->canRead(0)) { // if we have data waiting before the server gives us a 220 + std::cout << "421 (data_before_banner) (ip:" << peer_address << ")\n"; // Log it + std::this_thread::sleep_for(std::chrono::seconds(20)); // Annoy spammers once more + outside->writeLine("421 Stop sending data before we show you the banner"); + return; + } } - #else - outside.writeLine("454 TLS temporarily not available"); - LINF("STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support"); - #endif //HAVE_SSL - strtemp=""; } - if(strtemp.length()) - inside.writeLine(strtemp); - } + // Connect to the inside server + inside.connect(cfg.getServerHost(), cfg.getServerPort()); - if(inside.canRead(0.2)) //server wants to send something to client - { - strtemp=inside.readLine(); - if(inside.isClosed()) - return; - string code=strtemp.substr(0,3); //all responses by the server start with a code - - if("354"==code) //354 -> you can start sending data, unthrottle now and read binary-safe - { - string endofdata=""; - ssize_t bytes_read=0; - char buffer[4097]; - - outside.writeLine(strtemp); - strtemp=""; - string ssltls=""; - #ifdef HAVE_SSL - if (outside.is_ssl_enabled()) - ssltls=" (SSL/TLS)"; - #endif //HAVE_SSL - - if(cfg.getAddHeaders()) - { - inside.writeLine("Received: from "+ehlostr+" ("+peer_address+")"); - inside.writeLine(" by "+Utils::gethostname(outside.getFD())+" with "+(esmtp?"ESTMP":"SMTP")+ssltls+" via TCP; "+Utils::rfc2821_date()); - inside.writeLine("X-Anti-Spam-Proxy: Proxied by Hermes [www.hermes-project.com]"); - if(cfg.getAddStatusHeader()) - inside.writeLine("X-Hermes-Status: "+hermes_status); - } - do - { - bytes_read=outside.readBytes(buffer,sizeof(buffer)-1); - if(bytes_read<1) - throw NetworkException("Problem reading DATA contents, recv returned "+Utils::inttostr(bytes_read),__FILE__,__LINE__); - buffer[bytes_read]='\0'; - inside.writeBytes(buffer,bytes_read); - if(bytes_read<5) - endofdata+=string(buffer); - else - endofdata=string(buffer+bytes_read-5); - if(endofdata.length()>5) - endofdata=endofdata.substr(endofdata.length()-5); - } - while(endofdata!="\r\n.\r\n"/*&&endofdata.length()>3&&endofdata.substr(2)!="\n.\n"&&endofdata.substr(2)!="\r.\r"*/); + #ifdef HAVE_SSL + if (cfg.getOutgoingSsl()) { + inside.prepareSSL(false); + inside.startSSL(false); } - - if("235"==code) //235 -> you are correctly authenticated, unthrottle & authenticate - { - throttled=false; - authenticated=true; - hermes_status="authenticated"; + if (cfg.getIncomingSsl()) { + outside->prepareSSL(true); + outside->startSSL(true); } - if("250-pipelining"==Utils::strtolower(strtemp)||"250-chunking"==Utils::strtolower(strtemp)) //this solves our problems with pipelining-enabled servers - strtemp=""; + #endif // HAVE_SSL - //this is a special case, we can't just ignore the line if it's the last line (doesn't have the dash after the code) - //so we just say we support an imaginary extension (noextension). - //caveat: this makes us identificable, so, if you can, configure your smtp server to either don't support pipelining - //or to not advertise it as the last capability. - if("250 pipelining"==Utils::strtolower(strtemp)||"250 chunking"==Utils::strtolower(strtemp)) - strtemp="250 x-noextension"; + // Main loop for communication + while (!outside->isClosed() && !inside.isClosed()) { + // Check if the client wants to send something to the server + if (outside->canRead(0.2)) { + strtemp = outside->readLine(); + if (outside->isClosed()) return; - //try to annoy spammers who send us too many senseless commands by delaying their connection a lot - if("502"==code) //502 unimplemented -> count them, if bigger than a certain number, terminate connection - { - if(cfg.getNumberOfUnimplementedCommandsAllowed()!=-1&&++unimplemented_requests>cfg.getNumberOfUnimplementedCommandsAllowed()) - { - inside.writeLine("QUIT"); - inside.close(); //close the socket now and leave server alone - sleep(60); - outside.writeLine("502 Too many unimplemented commands, closing connection"); - return; - } + if (strtemp.length() > 10 && "mail from:" == Utils::strtolower(strtemp.substr(0, 10))) { + from = Utils::getmail(strtemp); + last_state = SMTP_STATE_WAIT_FOR_RCPTTO; + } + if ("ehlo" == Utils::strtolower(strtemp.substr(0, 4))) esmtp = true; + if (strtemp.length() > 4 && ("ehlo" == Utils::strtolower(strtemp.substr(0, 4)) || + "helo" == Utils::strtolower(strtemp.substr(0, 4)))) { + ehlostr = Utils::trim(strtemp.substr(5)); + last_state = SMTP_STATE_WAIT_FOR_MAILFROM; + } + if (strtemp.length() > 8 && "rcpt to:" == Utils::strtolower(strtemp.substr(0, 8))) { + std::string strlog = ""; + std::string code = ""; + std::string mechanism = ""; + std::string message = ""; + to = Utils::getmail(strtemp); + + try { + resolvedname = Socket::resolveInverselyToString(peer_address); + } catch (Exception& e) { + resolvedname = ""; + } + + strlog = "from " + from + " (ip:" + peer_address + ", hostname:" + resolvedname + + ", " + (esmtp ? "ehlo" : "helo") + ":" + ehlostr + ") -> to " + to; + + // Check greylisting + if (cfg.getGreylist() && !authenticated && Utils::greylist(cfg.getDatabaseFile(), peer_address, from, to)) { + code = "421"; + mechanism = "greylist"; + message = code + " Greylisted!! Please try again in a few minutes."; + std::cout << "checking " << mechanism << "\n"; + } + #ifdef HAVE_SPF + else if (cfg.getQuerySpf() && !authenticated && !spf_checker.query(peer_address, ehlostr, from)) { + hermes_status = "spf-failed"; + if (cfg.getAddStatusHeader()) code = "250"; + else code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + mechanism = "spf"; + message = code + " You do not seem to be allowed to send email for that particular domain."; + std::cout << "checking " << mechanism << "\n"; + } + #endif // HAVE_SPF + // Check blacklist + else if (!authenticated && Utils::blacklisted(cfg.getDatabaseFile(), peer_address, to)) { + code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + mechanism = "allowed-domain-per-ip"; + message = code + " You do not seem to be allowed to send email to that particular domain from that address."; + std::cout << "checking " << mechanism << "\n"; + } + // Check RBL + else if (!cfg.getDnsBlacklistDomains().empty() && !authenticated && + Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(), cfg.getDnsBlacklistPercentage(), peer_address)) { + hermes_status = "blacklisted"; + if (cfg.getAddStatusHeader()) code = "250"; + else code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + mechanism = "dnsbl"; + message = code + " You are listed on some DNS blacklists. Get delisted before trying to send us email."; + std::cout << "checking " << mechanism << "\n"; + } + else if (cfg.getRejectNoReverseResolution() && !authenticated && "" == resolvedname) { + code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + mechanism = "no reverse resolution"; + message = code + " Your IP address does not resolve to a hostname."; + std::cout << "checking " << mechanism << "\n"; + } + else if (cfg.getCheckHeloAgainstReverse() && !authenticated && ehlostr != resolvedname) { + code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + mechanism = "helo differs from resolved name"; + message = code + " Your IP hostname doesn't match your envelope hostname."; + std::cout << "checking " << mechanism << "\n"; + } else { + code = "250"; + } + + if (!mechanism.empty()) strlog.insert(0, "(" + mechanism + ") "); + strlog.insert(0, code + " "); + std::cout << strlog << "\n"; // Log the connection + + // If we didn't accept the email, punish spammers + if ("250" != code) { + inside.writeLine("QUIT"); + inside.close(); // Close the socket now and leave server alone + std::this_thread::sleep_for(std::chrono::seconds(20)); + outside->writeLine(message); + return; + } + last_state = SMTP_STATE_WAIT_FOR_DATA; + } + + // Handle STARTTLS + if ("starttls" == Utils::strtolower(strtemp.substr(0, 8))) { + #ifdef HAVE_SSL + try { + outside->prepareSSL(true); + std::cout << "STARTTLS issued by remote, TLS enabled\n"; + outside->writeLine("220 You can speak now, line is secure!!"); + outside->startSSL(true); + } catch (Exception& e) { + std::cout << "STARTTLS issued by remote, but enableSSL failed!\n"; + outside->writeLine("454 Tried to enable SSL but failed"); + } + #else + outside->writeLine("454 TLS temporarily not available"); + std::cout << "STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support\n"; + #endif // HAVE_SSL + strtemp = ""; + } + + if (strtemp.length()) inside.writeLine(strtemp); + } + + // Check if the server wants to send something to the client + if (inside.canRead(0.2)) { + strtemp = inside.readLine(); + if (inside.isClosed()) return; + + std::string code = strtemp.substr(0, 3); // All responses by the server start with a code + if ("354" == code) { // 354 -> you can start sending data, unthrottle now + std::string endofdata = ""; + ssize_t bytes_read = 0; + char buffer[4097]; + outside->writeLine(strtemp); + + do { + bytes_read = outside->readBytes(buffer, sizeof(buffer) - 1); + if (bytes_read < 1) throw NetworkException("Problem reading DATA contents, recv returned " + Utils::inttostr(bytes_read), __FILE__, __LINE__); + buffer[bytes_read] = '\0'; + inside.writeBytes(buffer, bytes_read); + if (bytes_read < 5) endofdata += std::string(buffer); + else endofdata = std::string(buffer + bytes_read - 5); + if (endofdata.length() > 5) endofdata = endofdata.substr(endofdata.length() - 5); + } while (endofdata != "\r\n.\r\n" && endofdata.length() > 3 && endofdata.substr(2) != "\n.\n" && endofdata.substr(2) != "\r.\r"); + } + + if ("235" == code) { // 235 -> you are correctly authenticated + throttled = false; + authenticated = true; + hermes_status = "authenticated"; + } + + // Try to annoy spammers who send too many senseless commands by delaying their connection + if ("502" == code) { // 502 unimplemented + if (cfg.getNumberOfUnimplementedCommandsAllowed() != -1 && ++unimplemented_requests > cfg.getNumberOfUnimplementedCommandsAllowed()) { + inside.writeLine("QUIT"); + inside.close(); // Close the socket now and leave server alone + std::this_thread::sleep_for(std::chrono::seconds(60)); + outside->writeLine("502 Too many unimplemented commands, closing connection"); + return; + } + } + + if (strtemp.length()) outside->writeLine(strtemp); + } + + if (throttled) std::this_thread::sleep_for(std::chrono::seconds(cfg.getThrottlingTime())); // Take 1 second between each command } - - if(strtemp.length()) - outside.writeLine(strtemp); - } - - if(throttled) - sleep(cfg.getThrottlingTime()); //we take 1 second between each command to make spammers angry + } catch (Exception& e) { // Any exception will close both connections + std::cerr << "Exception occurred: " << e.what() << std::endl; + return; } - } - catch(Exception &e) //any exception will close both connections - { - LERR(e); - if(last_state to "+(""==to?"no-to":to)); - return; - } } From 49f3fd3a6b6a595c475efe6f8c20b39a6d9c0567 Mon Sep 17 00:00:00 2001 From: Juanjo Gutierrez Date: Sun, 23 Mar 2025 10:27:53 +0100 Subject: [PATCH 5/9] some tests and more preliminary stuff --- .gitignore | 3 ++ src/Proxy.h | 69 +++++++---------------------------------- src/SocketInterface.cpp | 51 ++++++++++++++++++++++++++++++ src/SocketInterface.h | 16 ++++++++++ src/test/MockSocket.h | 15 +++++++++ src/test/ProxyTest.cpp | 63 +++++++++++++++++++++++++++++++++++++ 6 files changed, 159 insertions(+), 58 deletions(-) create mode 100644 .gitignore create mode 100644 src/SocketInterface.cpp create mode 100644 src/SocketInterface.h create mode 100644 src/test/MockSocket.h create mode 100644 src/test/ProxyTest.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a221c5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +docs/ +hermesrc.example diff --git a/src/Proxy.h b/src/Proxy.h index fd55da2..689aebf 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -1,61 +1,14 @@ -/** - * hermes antispam proxy - * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * @author Juan José Gutiérrez de Quevedo - */ -#ifndef PROXY_H -#define PROXY_H +// Proxy.h +#pragma once +#include +#include "SocketInterface.h" -#include "hermes.h" -#include -#ifdef WIN32 - #include -#else - #include -#endif -#include -#include -#include -#include -#include -#include +class Proxy { +public: + Proxy(SocketInterface* outside_socket) : outside(outside_socket) {} + + void run(std::string& peer_address); -#include "Socket.h" -#include "Configfile.h" -#include "Utils.h" -#include "Logger.h" -#ifdef HAVE_SPF -#include "Spf.h" -#endif //HAVE_SPF - -#define SMTP_STATE_WAIT_FOR_HELO 0 -#define SMTP_STATE_WAIT_FOR_MAILFROM 1 -#define SMTP_STATE_WAIT_FOR_RCPTTO 2 -#define SMTP_STATE_WAIT_FOR_DATA 3 - -class Proxy -{ - private: - Socket outside; //connection from someone sending mail - Socket inside; //connection to our inside smtp - public: - //Proxy():outside(NULL),inside(NULL){}; - void setOutside(Socket&); - void run(string&); +private: + SocketInterface* outside; }; - -#endif //PROXY_H diff --git a/src/SocketInterface.cpp b/src/SocketInterface.cpp new file mode 100644 index 0000000..0eec521 --- /dev/null +++ b/src/SocketInterface.cpp @@ -0,0 +1,51 @@ +// BoostSocket.h +#include +#include "SocketInterface.h" + +class BoostSocket : public SocketInterface { +public: + BoostSocket() : socket_(io_service_) {} + + void connect(const std::string& host, unsigned short port) override { + boost::asio::ip::tcp::resolver resolver(io_service_); + boost::asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port)); + boost::asio::connect(socket_, endpoints); + } + + void writeLine(const std::string& data) override { + boost::asio::write(socket_, boost::asio::buffer(data + "\r\n")); + } + + std::string readLine() override { + boost::asio::streambuf buf; + boost::asio::read_until(socket_, buf, "\r\n"); + std::istream is(&buf); + std::string line; + std::getline(is, line); + return line; + } + + bool canRead(double timeout) override { + // Implementation to check if data is available to read. + } + + bool isClosed() override { + return !socket_.is_open(); + } + + void close() override { + socket_.close(); + } + + void prepareSSL(bool incoming) override { + // Implement SSL preparation here if needed. + } + + void startSSL(bool incoming) override { + // Implement starting SSL here if needed. + } + +private: + boost::asio::io_service io_service_; + boost::asio::ip::tcp::socket socket_; +}; diff --git a/src/SocketInterface.h b/src/SocketInterface.h new file mode 100644 index 0000000..bce1566 --- /dev/null +++ b/src/SocketInterface.h @@ -0,0 +1,16 @@ +// SocketInterface.h +#pragma once +#include + +class SocketInterface { +public: + virtual ~SocketInterface() = default; + virtual void connect(const std::string& host, unsigned short port) = 0; + virtual void writeLine(const std::string& data) = 0; + virtual std::string readLine() = 0; + virtual bool canRead(double timeout) = 0; + virtual bool isClosed() = 0; + virtual void close() = 0; + virtual void prepareSSL(bool incoming) = 0; + virtual void startSSL(bool incoming) = 0; +}; diff --git a/src/test/MockSocket.h b/src/test/MockSocket.h new file mode 100644 index 0000000..63e3819 --- /dev/null +++ b/src/test/MockSocket.h @@ -0,0 +1,15 @@ +// MockSocket.h +#include "SocketInterface.h" +#include + +class MockSocket : public SocketInterface { +public: + MOCK_METHOD(void, connect, (const std::string& host, unsigned short port), (override)); + MOCK_METHOD(void, writeLine, (const std::string& data), (override)); + MOCK_METHOD(std::string, readLine, (), (override)); + MOCK_METHOD(bool, canRead, (double timeout), (override)); + MOCK_METHOD(bool, isClosed, (), (override)); + MOCK_METHOD(void, close, (), (override)); + MOCK_METHOD(void, prepareSSL, (bool incoming), (override)); + MOCK_METHOD(void, startSSL, (bool incoming), (override)); +}; diff --git a/src/test/ProxyTest.cpp b/src/test/ProxyTest.cpp new file mode 100644 index 0000000..c8e0412 --- /dev/null +++ b/src/test/ProxyTest.cpp @@ -0,0 +1,63 @@ +// ProxyTest.cpp +#include +#include "Proxy.h" +#include "MockSocket.h" + +class ProxyTest : public ::testing::Test { +protected: + MockSocket mock_socket; + Proxy* proxy; + + void SetUp() override { + proxy = new Proxy(&mock_socket); + } + + void TearDown() override { + delete proxy; + } +}; + +TEST_F(ProxyTest, HandlesMailFromCommand) { + std::string peer_address = "127.0.0.1"; + + EXPECT_CALL(mock_socket, connect("server-host", 25)); + EXPECT_CALL(mock_socket, canRead(0.2)).WillOnce(testing::Return(true)); + EXPECT_CALL(mock_socket, readLine()).WillOnce(testing::Return("MAIL FROM:")); + EXPECT_CALL(mock_socket, writeLine("MAIL FROM:")); + EXPECT_CALL(mock_socket, writeLine("250 OK")); + + proxy->run(peer_address); +} + +TEST_F(ProxyTest, HandlesRcptToCommand) { + std::string peer_address = "127.0.0.1"; + + EXPECT_CALL(mock_socket, connect("server-host", 25)); + EXPECT_CALL(mock_socket, canRead(0.2)).WillRepeatedly(testing::Return(true)); + EXPECT_CALL(mock_socket, readLine()) + .WillOnce(testing::Return("MAIL FROM:")) + .WillOnce(testing::Return("RCPT TO:")) + .WillOnce(testing::Return("")); + + EXPECT_CALL(mock_socket, writeLine("MAIL FROM:")); + EXPECT_CALL(mock_socket, writeLine("RCPT TO:")); + EXPECT_CALL(mock_socket, writeLine("250 OK")); + + proxy->run(peer_address); +} + +TEST_F(ProxyTest, HandlesEmptyLine) { + std::string peer_address = "127.0.0.1"; + + EXPECT_CALL(mock_socket, connect("server-host", 25)); + EXPECT_CALL(mock_socket, canRead(0.2)).WillOnce(testing::Return(true)); + EXPECT_CALL(mock_socket, readLine()).WillOnce(testing::Return("")); + + proxy->run(peer_address); + // Expect no further actions to occur +} + +int main(int argc, char** argv) { + ::testing::InitGoogleMock(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file From 518f5782981dce14ba1d860fcb2d001230299b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Tue, 25 Mar 2025 10:42:06 +0100 Subject: [PATCH 6/9] Refactor Spf --- src/Spf.cpp | 121 +++++++++--------- src/Spf.h | 2 +- src/test/mocks/libspf2_mock.h | 96 ++++++++++++++ .../{MockSocket.h => mocks/socket_mock.h} | 0 .../{ProxyTest.cpp => tests/proxy_test.cpp} | 4 +- src/test/tests/spf_test.cpp | 98 ++++++++++++++ 6 files changed, 261 insertions(+), 60 deletions(-) create mode 100644 src/test/mocks/libspf2_mock.h rename src/test/{MockSocket.h => mocks/socket_mock.h} (100%) rename src/test/{ProxyTest.cpp => tests/proxy_test.cpp} (98%) create mode 100644 src/test/tests/spf_test.cpp diff --git a/src/Spf.cpp b/src/Spf.cpp index 1ef0bf2..0eeec44 100644 --- a/src/Spf.cpp +++ b/src/Spf.cpp @@ -4,11 +4,11 @@ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License + * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along @@ -17,83 +17,90 @@ * * @author Juan José Gutiérrez de Quevedo */ + #include "Spf.h" +#include +#include +#include -SPF_server_t *Spf::spfserver=NULL; +SPF_server_t *Spf::spfserver = nullptr; /** - * constructor - * - * it will create a spfserver if this is the first created object of the class. - * if it isn't, then we just create an spfrequest + * Constructor + * + * Initializes the SPF server if this is the first created object of the class. */ -Spf::Spf():spfrequest(NULL),spfresponse(NULL) -{ - pthread_mutex_init(&mutex,NULL); - if(NULL==spfserver) - if(NULL==(spfserver=SPF_server_new(SPF_DNS_CACHE,0))) - throw Exception(_("Can't initialize SPF library"),__FILE__,__LINE__); - - if(NULL==(spfrequest=SPF_request_new(spfserver))) - throw Exception(_("Can't initialize SPF request"),__FILE__,__LINE__); +Spf::Spf() { + static std::once_flag initFlag; // To ensure thread-safe initialization + std::call_once(initFlag, []() { + spfserver = SPF_server_new(SPF_DNS_CACHE, 0); + if (!spfserver) { + throw std::runtime_error("Can't initialize SPF library"); + } + }); } /** - * destructor - * - * frees the memory of the spfrequest + * Destructor + * + * Frees the memory of the SPF server. */ -Spf::~Spf() -{ - pthread_mutex_destroy(&mutex); - if(NULL!=spfrequest) SPF_request_free(spfrequest); +Spf::~Spf() { + deinitialize(); // Clean up resources } /** - * frees all memory related to the spf class + * Frees all memory related to the SPF class. * - * this is needed because the common things are only initialized - * once (and are static), and when we close the program we need - * to deinitialize them + * This is needed because common resources are only initialized once (and are static). */ -void Spf::deinitialize() -{ - if(NULL!=spfserver) - SPF_server_free(spfserver); +void Spf::deinitialize() { + if (spfserver) { + SPF_server_free(spfserver); + spfserver = nullptr; // Optional: Avoid dangling pointer + } } /** - * make a query to the dns system for an spf record + * Makes a query to the DNS system for an SPF record. * - * highly inspired from fakehermes' source + * @param ip The IP of the remote server + * @param helo The HELO string of the remote server + * @param from The envelope from address * - * @param ip the ip of the remote server - * @param helo the hello string of the remote server - * @param from the envelope from address - * - * @returns true if it is not incorrect + * @returns true if the query is not incorrect */ -bool Spf::query(string ip,string helo,string from) -{ - bool retval=false; +bool Spf::query(const std::string& ip, const std::string& helo, const std::string& from) { + SPF_request_t* spfrequest = SPF_request_new(spfserver); // Create request here + if (!spfrequest) { + throw std::runtime_error("Can't initialize SPF request"); + } - if(SPF_request_set_ipv4_str(spfrequest,ip.c_str())) - throw Exception(_("Error configuring IP for SPF request"),__FILE__,__LINE__); - if(SPF_request_set_helo_dom(spfrequest,helo.c_str())) - throw Exception(_("Error configuring HELO for SPF request"),__FILE__,__LINE__); - if(SPF_request_set_env_from(spfrequest,from.c_str())) - throw Exception(_("Error configuring FROM for SPF request"),__FILE__,__LINE__); + // Set the values for the SPF request + if (SPF_request_set_ipv4_str(spfrequest, ip.c_str())) { + SPF_request_free(spfrequest); // Clean up on failure + throw std::runtime_error("Error configuring IP for SPF request"); + } + if (SPF_request_set_helo_dom(spfrequest, helo.c_str())) { + SPF_request_free(spfrequest); // Clean up on failure + throw std::runtime_error("Error configuring HELO for SPF request"); + } + if (SPF_request_set_env_from(spfrequest, from.c_str())) { + SPF_request_free(spfrequest); // Clean up on failure + throw std::runtime_error("Error configuring FROM for SPF request"); + } - //make the actual query - pthread_mutex_lock(&mutex); - SPF_request_query_mailfrom(spfrequest,&spfresponse); - pthread_mutex_unlock(&mutex); + // Make the actual query + SPF_response_t* spfresponse = nullptr; // Local response variable + SPF_request_query_mailfrom(spfrequest, &spfresponse); - if(NULL!=spfresponse) - { - retval=(SPF_RESULT_FAIL==SPF_response_result(spfresponse)||SPF_RESULT_SOFTFAIL==SPF_response_result(spfresponse))?false:true; - SPF_response_free(spfresponse); - } + bool retval = false; + if (spfresponse) { + retval = !(SPF_response_result(spfresponse) == SPF_RESULT_FAIL || + SPF_response_result(spfresponse) == SPF_RESULT_SOFTFAIL); + SPF_response_free(spfresponse); // Free the response + } - return retval; + SPF_request_free(spfrequest); // Free the request + return retval; } diff --git a/src/Spf.h b/src/Spf.h index 9e1a07f..ce968fd 100644 --- a/src/Spf.h +++ b/src/Spf.h @@ -38,7 +38,7 @@ class Spf Spf(); ~Spf(); static void deinitialize(); - bool query(string,string,string); + bool query(const string&, const string&, const string&); }; #endif //SPF_H diff --git a/src/test/mocks/libspf2_mock.h b/src/test/mocks/libspf2_mock.h new file mode 100644 index 0000000..09d61b9 --- /dev/null +++ b/src/test/mocks/libspf2_mock.h @@ -0,0 +1,96 @@ +#ifndef LIBSPF2_MOCK_H +#define LIBSPF2_MOCK_H +#include +#include +#include +#include + +// Control variables to adjust behavior of mocked functions +extern "C" { + static SPF_errcode_t spf_set_ipv4_result = SPF_E_SUCCESS; + static SPF_errcode_t spf_set_helo_result = SPF_E_SUCCESS; + static int spf_set_from_result; + static SPF_response_t* spf_mock_response = nullptr; + static SPF_errcode_t spf_request_query_result = SPF_E_SUCCESS; + + // New control variables for SPF_server mocking + static SPF_server_t* spf_mock_server = nullptr; + static bool spf_server_new_should_fail = false; + + SPF_server_t* SPF_server_new(SPF_server_dnstype_t dnstype,int debug) + { + if (spf_server_new_should_fail) { + return nullptr; + } + return spf_mock_server ? spf_mock_server : new SPF_server_t(); + } + + void SPF_server_free(SPF_server_t* server) { + delete server; + } + + SPF_request_t* SPF_request_new(SPF_server_t *server) { + return new SPF_request_t(); // Simply allocate and return new request + } + + void SPF_request_free(SPF_request_t* request) { + delete request; // Deallocate request + } + + SPF_errcode_t SPF_request_set_ipv4_str(SPF_request_t* request, const char* ip) { + return spf_set_ipv4_result; // Use controllable result + } + + SPF_errcode_t SPF_request_set_helo_dom(SPF_request_t* request, const char* helo) { + return spf_set_helo_result; // Use controllable result + } + + int SPF_request_set_env_from(SPF_request_t* request, const char* from) { + return spf_set_from_result; // Use controllable result + } + + SPF_errcode_t SPF_request_query_mailfrom(SPF_request_t* request, SPF_response_t** response) { + *response = spf_mock_response; + return spf_request_query_result; + } + + SPF_result_t SPF_response_result(SPF_response_t* response) { + return response->result; // Return the result + } + + void SPF_response_free(SPF_response_t* response) { + delete response; // Deallocate response + } +} + +// Functions to set return values for mocked functions +void SetSpfMockReturnIPv4Value(SPF_errcode_t value) { + spf_set_ipv4_result = value; +} + +void SetSpfMockReturnHeloValue(SPF_errcode_t value) { + spf_set_helo_result = value; +} + +void SetSpfMockReturnFromValue(int value) { + spf_set_from_result = value; +} + +void SetSpfRequestQueryResult(SPF_errcode_t value) { + spf_request_query_result = value; +} + +void SetSpfMockResponse(SPF_response_t* mockResponse) { + spf_mock_response = mockResponse; +} + +// New functions for SPF_server mocking +void SetSpfMockServer(SPF_server_t* mockServer) { + spf_mock_server = mockServer; +} + +void SetSpfServerNewShouldFail(bool shouldFail) { + spf_server_new_should_fail = shouldFail; +} + +#endif // LIBSPF2_MOCK_H diff --git a/src/test/MockSocket.h b/src/test/mocks/socket_mock.h similarity index 100% rename from src/test/MockSocket.h rename to src/test/mocks/socket_mock.h diff --git a/src/test/ProxyTest.cpp b/src/test/tests/proxy_test.cpp similarity index 98% rename from src/test/ProxyTest.cpp rename to src/test/tests/proxy_test.cpp index c8e0412..7a06603 100644 --- a/src/test/ProxyTest.cpp +++ b/src/test/tests/proxy_test.cpp @@ -1,7 +1,7 @@ // ProxyTest.cpp #include #include "Proxy.h" -#include "MockSocket.h" +#include "socket_mock.h" class ProxyTest : public ::testing::Test { protected: @@ -60,4 +60,4 @@ TEST_F(ProxyTest, HandlesEmptyLine) { int main(int argc, char** argv) { ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +} diff --git a/src/test/tests/spf_test.cpp b/src/test/tests/spf_test.cpp new file mode 100644 index 0000000..b205875 --- /dev/null +++ b/src/test/tests/spf_test.cpp @@ -0,0 +1,98 @@ +#include +#include "Spf.h" + +#include "libspf2_mock.h" + +// Mock the SPF server responses for unit testing +class MockSPFServer { +public: + static void initialize() { + // Initialize any mock data here + } + + static void cleanup() { + // Cleanup if needed + } + + static SPF_response_t* mockResponse(SPF_result_t result) { + SPF_response_t* response = new SPF_response_t; // Replace with actual allocation + response->result = result; // Set the desired mock result + return response; + } +}; + +// Test Fixture for SPF tests +class SpfTest : public ::testing::Test { +protected: + Spf* spf; + + void SetUp() override { + // Create a new Spf instance before each test + spf = new Spf(); + } + + void TearDown() override { + // Clean up after each test + delete spf; + spf->deinitialize(); + } +}; + +// Test the construction of the Spf object +TEST_F(SpfTest, Construction) { + EXPECT_NO_THROW({ + Spf testSpf; + }); +} + +// Test SPF querying with valid parameters +TEST_F(SpfTest, QueryValidParameters) { + // Assuming the mocked SPF server is set up to return a valid response + MockSPFServer::initialize(); + + bool result = spf->query("192.0.2.1", "mail.example.com", "test@example.com"); + EXPECT_TRUE(result); + + MockSPFServer::cleanup(); +} + +// Test SPF querying with failed result +TEST_F(SpfTest, QueryFailResult) { + // Mock the response to return a fail result here + SPF_response_t* response = MockSPFServer::mockResponse(SPF_RESULT_FAIL); + + bool result = spf->query("198.51.100.1", "fail.example.com", "test@fail.com"); + EXPECT_FALSE(result); + + delete response; // Clean up mocked response +} + +// Test SPF querying with an empty HELO string +TEST_F(SpfTest, QueryEmptyHelo) { + EXPECT_THROW({ + spf->query("192.0.2.1", "", "test@example.com"); + }, std::runtime_error); +} + +// Test SPF querying with invalid IP format +TEST_F(SpfTest, QueryInvalidIPAddress) { + EXPECT_THROW({ + spf->query("invalid_ip", "mail.example.com", "test@example.com"); + }, std::runtime_error); +} + +// Test SPF request failure +TEST_F(SpfTest, QueryRequestFailure) { + // Here you might want to simulate a situation + // where the request cannot be created or fails due to some other reason. + spf->deinitialize(); // Ensure the server is cleaned up before running this. + + EXPECT_THROW({ + spf->query("192.0.2.1", "mail.example.com", "test@example.com"); + }, std::runtime_error); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From d7e2aee3a35b2baa9501d783a46131289a9844d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Tue, 25 Mar 2025 10:59:11 +0100 Subject: [PATCH 7/9] Rearrange stuff --- CMakeLists.txt | 1 + dists/fc_init | 62 -------------------- dists/hermes.spec.in | 77 ------------------------- {src => include}/Database.h | 0 {src => include}/Exception.h | 0 {src => include}/FileLogger.h | 0 {src => include}/Logger.h | 0 {src => include}/NullLogger.h | 0 {src => include}/Proxy.h | 5 ++ {src => include}/ServerSocket.h | 0 {src => include}/Socket.h | 0 {src => include}/SocketInterface.h | 0 {src => include}/Spf.h | 0 {src => include}/UnixLogger.h | 0 {src => include}/Utils.h | 0 {src => include}/hermes.h | 0 test/CMakeLists.txt | 14 +++++ {src/test => test}/mocks/libspf2_mock.h | 0 {src/test => test}/mocks/socket_mock.h | 0 {src/test => test}/tests/proxy_test.cpp | 0 {src/test => test}/tests/spf_test.cpp | 0 21 files changed, 20 insertions(+), 139 deletions(-) delete mode 100755 dists/fc_init delete mode 100644 dists/hermes.spec.in rename {src => include}/Database.h (100%) rename {src => include}/Exception.h (100%) rename {src => include}/FileLogger.h (100%) rename {src => include}/Logger.h (100%) rename {src => include}/NullLogger.h (100%) rename {src => include}/Proxy.h (63%) rename {src => include}/ServerSocket.h (100%) rename {src => include}/Socket.h (100%) rename {src => include}/SocketInterface.h (100%) rename {src => include}/Spf.h (100%) rename {src => include}/UnixLogger.h (100%) rename {src => include}/Utils.h (100%) rename {src => include}/hermes.h (100%) create mode 100644 test/CMakeLists.txt rename {src/test => test}/mocks/libspf2_mock.h (100%) rename {src/test => test}/mocks/socket_mock.h (100%) rename {src/test => test}/tests/proxy_test.cpp (100%) rename {src/test => test}/tests/spf_test.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index e25dd14..652d7fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ if(WIN32) target_compile_definitions(hermes PRIVATE WIN32) endif() +target_include_directories(hermes PRIVATE include) target_compile_definitions(hermes PRIVATE LOGGER_CLASS=${LOGGER_CLASS}) target_sources(hermes PRIVATE src/${LOGGER_CLASS}.cpp) diff --git a/dists/fc_init b/dists/fc_init deleted file mode 100755 index f4f30a4..0000000 --- a/dists/fc_init +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# Startup script for hermes -# -# chkconfig: 3 95 05 -# description: hermes - -# Source function library. -. /etc/rc.d/init.d/functions - -prog=hermes -configfile=/etc/hermes/hermesrc - -start() { - echo -n $"Starting $prog: " - daemon --check=$prog /usr/bin/hermes $configfile - RETVAL=$? - echo -} - -stop() { - echo -n $"Stopping $prog: " - killproc $prog - RETVAL=$? - echo -} - -safestop() { - echo -n $"Stopping $prog(will process pending connections):" - killproc $prog -INT - rm /var/run/$prog.pid - RETVAL=$? - echo -} - -case "$1" in - start) - start - ;; - - stop) - stop - ;; - - restart) - safestop - sleep 2 - start - ;; - condrestart) - if test "x`pidfileofproc $prog`" != x; then - stop - start - fi - ;; - - *) - echo $"Usage: $0 {start|stop|restart|condrestart}" - exit 1 - -esac - -exit $RETVAL diff --git a/dists/hermes.spec.in b/dists/hermes.spec.in deleted file mode 100644 index c69296c..0000000 --- a/dists/hermes.spec.in +++ /dev/null @@ -1,77 +0,0 @@ -Summary: An anti-spam SMTP proxy -Name: @PACKAGE@ -Version: @VERSION@ -Release: 0 -License: GPL -Group: System Environment/Daemons -Packager: Veit Wahlich -URL: http://www.hermes-project.com/ -Source0: http://www.hermes-project.com/files/%{name}-%{version}.tar.bz2 -Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root - -%description -hermes is a generic, lightweight, portable and fast anti-spam smtp proxy. -Supports greylisting, dns blacklisting/whitelisting, protocol throttling, banner delaying, spf and some -other tricks to reject most spam before it even enters your system. - -%prep -%setup -q - -%build -%configure --docdir=%{_datadir}/doc/%{name}-%{version} -%__make %{?_smp_mflags} - -%install -%__rm -rf %{buildroot} -%__make DESTDIR=%{buildroot} install -%__mkdir_p %{buildroot}%{_sysconfdir}/rc.d/init.d -%__mkdir_p %{buildroot}%{_sysconfdir}/hermes -%__mkdir_p %{buildroot}%{_localstatedir}/hermes -%__install -m 0755 dists/fc_init %{buildroot}%{_sysconfdir}/rc.d/init.d/hermes -%__install -m 0600 dists/hermesrc.example %{buildroot}%{_sysconfdir}/hermes/hermesrc - -%clean -%__rm -rf %{buildroot} - -%post -/sbin/chkconfig --add hermes - -%preun -if [ $1 = 0 ]; then # execute this only if we are NOT doing an upgrade - %{_sysconfdir}/rc.d/init.d/hermes stop >/dev/null 2>&1 - /sbin/chkconfig --del hermes -fi -exit 0 - -%files -%defattr(-, root, root, 0755) -%doc ChangeLog TODO AUTHORS dists/hermesrc.example docs/hermes-options.html docs/installing-hermes.txt docs/gpl.txt -%{_bindir}/hermes -%{_sysconfdir}/rc.d/init.d/hermes -%config %{_sysconfdir}/hermes/hermesrc -%dir %attr(0700,nobody,nobody) %{_localstatedir}/hermes - -%changelog -* Thu Jun 14 2007 Juan José Gutiérrez de Quevedo 1.4 -- removed patches, they are now on upstream - -* Fri May 25 2007 Veit Wahlich 1.3-2 -- added patch fix_whether (documentation fixes) -- added patch add_rejectnoresolve (reject on no DNS reverse resolution feature) -- changed RPM group to system daemon standard - -* Sat May 19 2007 Veit Wahlich 1.3-1 -- Made /etc/hermes/hermesrc readonly as it may contain passwords -- Fixed ownership and permissions of /var/hermes to match configuration default -- Silenced setup macro output as required by some distributions -- Fixed docdir to a LSB compliant location, will be replaced by rpmbuild -- Packaged extra documentation -- Removed hermes-options.html.in from docs -- Use directory macros for files section -- Further specfile cleanups and macro usage - -* Tue May 15 2007 Juan José Gutiérrez de Quevedo -- Fixed rpm to create /var/hermes - -* Fri Apr 11 2007 Juan José Gutiérrez de Quevedo -- Initial release diff --git a/src/Database.h b/include/Database.h similarity index 100% rename from src/Database.h rename to include/Database.h diff --git a/src/Exception.h b/include/Exception.h similarity index 100% rename from src/Exception.h rename to include/Exception.h diff --git a/src/FileLogger.h b/include/FileLogger.h similarity index 100% rename from src/FileLogger.h rename to include/FileLogger.h diff --git a/src/Logger.h b/include/Logger.h similarity index 100% rename from src/Logger.h rename to include/Logger.h diff --git a/src/NullLogger.h b/include/NullLogger.h similarity index 100% rename from src/NullLogger.h rename to include/NullLogger.h diff --git a/src/Proxy.h b/include/Proxy.h similarity index 63% rename from src/Proxy.h rename to include/Proxy.h index 689aebf..7e266c0 100644 --- a/src/Proxy.h +++ b/include/Proxy.h @@ -3,6 +3,11 @@ #include #include "SocketInterface.h" +#define SMTP_STATE_WAIT_FOR_HELO 0 +#define SMTP_STATE_WAIT_FOR_MAILFROM 1 +#define SMTP_STATE_WAIT_FOR_RCPTTO 2 +#define SMTP_STATE_WAIT_FOR_DATA 3 + class Proxy { public: Proxy(SocketInterface* outside_socket) : outside(outside_socket) {} diff --git a/src/ServerSocket.h b/include/ServerSocket.h similarity index 100% rename from src/ServerSocket.h rename to include/ServerSocket.h diff --git a/src/Socket.h b/include/Socket.h similarity index 100% rename from src/Socket.h rename to include/Socket.h diff --git a/src/SocketInterface.h b/include/SocketInterface.h similarity index 100% rename from src/SocketInterface.h rename to include/SocketInterface.h diff --git a/src/Spf.h b/include/Spf.h similarity index 100% rename from src/Spf.h rename to include/Spf.h diff --git a/src/UnixLogger.h b/include/UnixLogger.h similarity index 100% rename from src/UnixLogger.h rename to include/UnixLogger.h diff --git a/src/Utils.h b/include/Utils.h similarity index 100% rename from src/Utils.h rename to include/Utils.h diff --git a/src/hermes.h b/include/hermes.h similarity index 100% rename from src/hermes.h rename to include/hermes.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..4f0c645 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.31) + +project(unittests) + +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIRS}) + +add_executable(spf_test tests/spf_test.cpp ../src/Spf.cpp) # Your test file +target_include_directories(spf_test PRIVATE ../include mocks) +target_link_libraries(spf_test ${GTEST_LIBRARIES} pthread) + +add_executable(proxy_test tests/proxy_test.cpp ../src/Proxy.cpp) # Your test file +target_include_directories(proxy_test PRIVATE ../include mocks) +target_link_libraries(proxy_test ${GTEST_LIBRARIES} pthread) diff --git a/src/test/mocks/libspf2_mock.h b/test/mocks/libspf2_mock.h similarity index 100% rename from src/test/mocks/libspf2_mock.h rename to test/mocks/libspf2_mock.h diff --git a/src/test/mocks/socket_mock.h b/test/mocks/socket_mock.h similarity index 100% rename from src/test/mocks/socket_mock.h rename to test/mocks/socket_mock.h diff --git a/src/test/tests/proxy_test.cpp b/test/tests/proxy_test.cpp similarity index 100% rename from src/test/tests/proxy_test.cpp rename to test/tests/proxy_test.cpp diff --git a/src/test/tests/spf_test.cpp b/test/tests/spf_test.cpp similarity index 100% rename from src/test/tests/spf_test.cpp rename to test/tests/spf_test.cpp From b05c4ad5d99d9a69809cab26080c81e5c8273901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Wed, 26 Mar 2025 13:49:59 +0100 Subject: [PATCH 8/9] Update config generation --- CMakeLists.txt | 48 ++++++---- {src => include}/Configfile.h.in | 0 scripts/generate_config.py | 147 ++++++++++++++++++++----------- src/Configfile.tmpl | 64 -------------- 4 files changed, 129 insertions(+), 130 deletions(-) rename {src => include}/Configfile.h.in (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 652d7fa..272d3ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(LOGGER_CLASS UnixLogger CACHE STRING "One of UnixLogger, FileLogger or NullL add_executable (hermes ${CMAKE_CURRENT_BINARY_DIR}/Configfile.cpp + ${CMAKE_CURRENT_BINARY_DIR}/Configfile.h src/Exception.cpp src/hermes.cpp src/ServerSocket.cpp @@ -14,7 +15,7 @@ add_executable (hermes src/Proxy.cpp src/Socket.cpp) -option(BUILD_DOC "Build documentation") +option(BUILD_DOCS "Build documentation") if(WIN32) set(SOURCES ${SOURCES} @@ -31,7 +32,7 @@ target_sources(hermes PRIVATE src/${LOGGER_CLASS}.cpp) find_library (SQLITE3_LIBRARY NAMES libsqlite3 sqlite3) # optional dependency libspf2 -find_library (SPF2_LIBRARY NAMES spf2 libspf2) +find_library (SPF2_LIBRARY REQUIRED NAMES spf2 libspf2) if(SPF2_LIB) target_compile_definitions(hermes PRIVATE HAVE_SPF2) target_sources(hermes PRIVATE src/Spf.cpp) @@ -49,26 +50,43 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} src) +set(CONFIG_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Configfile.tmpl) + # generation of various files add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Configfile.cpp - COMMAND cpp ${OPT_DEFS} ${CMAKE_CURRENT_SOURCE_DIR}/src/Configfile.tmpl -I - ${CMAKE_CURRENT_BINARY_DIR} | - python ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.py - DEPENDS src/Configfile.cpp.in src/Configfile.h.in src/Configfile.tmpl - docs/hermes-options.html.in scripts/generate_config.py) + COMMAND python "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.py" "${CONFIG_TEMPLATE}" + --cpp-template "${CMAKE_CURRENT_SOURCE_DIR}/src/Configfile.cpp.in" + --output-cpp "${CMAKE_CURRENT_BINARY_DIR}/Configfile.cpp" + DEPENDS ${CONFIG_TEMPLATE} src/Configfile.cpp.in) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Configfile.h + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.py "${CONFIG_TEMPLATE}" + --h-template "${CMAKE_CURRENT_SOURCE_DIR}/include/Configfile.h.in" + --output-h "${CMAKE_CURRENT_BINARY_DIR}/Configfile.h" + DEPENDS ${CONFIG_TEMPLATE} include/Configfile.h.in) + +# DEPENDS src/Configfile.cpp.in src/Configfile.h.in src/Configfile.tmpl +# docs/hermes-options.html.in scripts/generate_config.py) # doxygen if (BUILD_DOCS) + add_custom_command(OUTPUT docs/hermesrc.example + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.py "${CONFIG_TEMPLATE}" + --output-example "${CMAKE_CURRENT_BINARY_DIR}/docs/hermesrc.example" + DEPENDS ${CONFIG_TEMPLATE}) + add_custom_command(OUTPUT docs/html/hermes-options.html + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_config.py "${CONFIG_TEMPLATE}" + --html-template "${CMAKE_CURRENT_SOURCE_DIR}/docs/hermes-options.html.in" + --output-html "${CMAKE_CURRENT_BINARY_DIR}/docs/html/hermes-options.html" + DEPENDS ${CONFIG_TEMPLATE} docs/hermes-options.html.in) find_package (Doxygen) - if(DOXYGEN_FOUND) - add_custom_target(doc ALL - doxygen - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs) - install( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs/html - TYPE DOC) - endif() + add_custom_target(doc ALL + doxygen "${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/docs/html/hermes-options.html" "${CMAKE_CURRENT_BINARY_DIR}/docs/hermesrc.example") + install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/html + TYPE DOC) endif() target_link_libraries(hermes diff --git a/src/Configfile.h.in b/include/Configfile.h.in similarity index 100% rename from src/Configfile.h.in rename to include/Configfile.h.in diff --git a/scripts/generate_config.py b/scripts/generate_config.py index fff5597..b864250 100644 --- a/scripts/generate_config.py +++ b/scripts/generate_config.py @@ -1,37 +1,63 @@ #!/usr/bin/env python3 - import sys -import re import string +import argparse +import os def camel_case(str_): """Convert snake_case to CamelCase.""" return string.capwords(str_, "_").replace("_", "") def main(): - # Read input files - with open('../docs/hermes-options.html.in', 'r') as f: - html_templ = f.read() + # Set up argument parser + parser = argparse.ArgumentParser(description='Generate configuration files for Hermes.') + parser.add_argument('input_template', + type=argparse.FileType('r'), + help='Input configuration template file') + parser.add_argument('--html-template', + default='', + help='Path to HTML template input file') + parser.add_argument('--cpp-template', + default='', + help='Path to C++ template input file') + parser.add_argument('--h-template', + default='', + help='Path to header template input file') + parser.add_argument('--output-cpp', + default='', + help='Output path for generated C++ file') + parser.add_argument('--output-h', + default='', + help='Output path for generated header file') + parser.add_argument('--output-example', + default='', + help='Output path for example configuration') + parser.add_argument('--output-html', + default='', + help='Output path for generated HTML documentation') - hvar1 = "" - hvar2 = "" - cppvar1 = "" - cppvar2 = "" - cppvar3 = "" - conf_example = "" - htmlvar = "" + # Parse arguments + args = parser.parse_args() + + hvar1 = [] + hvar2 = [] + cppvar1 = [] + cppvar2 = [] + cppvar3 = [] + conf_example = [] + htmlvar = [] htmlexpl = "" # Process input - for line in sys.stdin: + for line in args.input_template: line = line.strip() if not line or line.startswith('#') or line.startswith('*'): if line == '*clean*': htmlexpl = "" elif line.startswith('*'): - line = line.replace('*', '#') - conf_example += line + "\n" + line = line.replace('*', '#').strip() + conf_example.append(line) # Convert line for HTML line_html = line.lstrip('#').replace('>', '>') @@ -52,61 +78,80 @@ def main(): camel_name = camel_case(var_name) # Generate header variables - hvar1 += f"{type_str} {var_name};\n" - hvar2 += f"{type_str}& get{camel_name}();\n" + hvar1.append(f"{type_str} {var_name};") + hvar2.append(f"{type_str}& get{camel_name}();") # Generate cpp variables if 'list' in type_str: - cppvar1 += f"{var_name} = Configfile::parseAsList({default_val});\n" + cppvar1.append(f"{var_name} = Configfile::parseAsList({default_val});") else: - cppvar1 += f"{var_name} = {default_val};\n" + cppvar1.append(f"{var_name} = {default_val};") - cppvar2 += f"PARSE_{parts[0].upper()}(\"{var_name}\", {var_name})\n" - cppvar3 += f"GET_VAR(get{camel_name}, {var_name}, {type_str}&)\n" + cppvar2.append(f"PARSE_{parts[0].upper()}(\"{var_name}\", {var_name})") + cppvar3.append(f"GET_VAR(get{camel_name}, {var_name}, {type_str}&)") # Generate config example - conf_example += f"{var_name} = {default_val}\n\n" + conf_example.append(f"{var_name} = {default_val}") # Generate HTML - html_temp = html_templ.replace('%type%', parts[0]) \ - .replace('%name%', var_name) \ - .replace('%default%', default_val) \ - .replace('%explanation%', htmlexpl) - htmlvar += html_temp + if args.html_template: + html_templ = open(args.html_template, 'r').read() + html_temp = html_templ.replace('%type%', parts[0]) \ + .replace('%name%', var_name) \ + .replace('%default%', default_val) \ + .replace('%explanation%', htmlexpl) + htmlvar.append(html_temp) htmlexpl = "" - # Clean up variables - for var in [cppvar1, cppvar2, cppvar3, hvar1, hvar2, conf_example]: - var = var.rstrip() + # Convert lists to newline-separated strings + hvar1 = '\n'.join(hvar1) + hvar2 = '\n'.join(hvar2) + cppvar1 = '\n'.join(cppvar1) + cppvar2 = '\n'.join(cppvar2) + cppvar3 = '\n'.join(cppvar3) + conf_example = '\n\n'.join(conf_example) + htmlvar = ''.join(htmlvar) - # Read and write Configfile.cpp - with open('../src/Configfile.cpp.in', 'r') as f: - cpp_str = f.read() + # Write Configfile.cpp + if args.cpp_template and args.output_cpp: + try: + cpp_str = open(args.cpp_template, 'r').read() + cpp_str = cpp_str.replace('%templ_default_values%', cppvar1) \ + .replace('%templ_parsevars%', cppvar2) \ + .replace('%templ_getmethods%', cppvar3) - cpp_str = cpp_str.replace('%templ_default_values%', cppvar1) \ - .replace('%templ_parsevars%', cppvar2) \ - .replace('%templ_getmethods%', cppvar3) + os.makedirs(os.path.dirname(args.output_cpp), exist_ok=True) + with open(args.output_cpp, 'w') as f: + f.write(cpp_str) + except FileNotFoundError: + print(f"Error: C++ template file {args.cpp_template} not found.", file=sys.stderr) + sys.exit(1) - with open('Configfile.cpp', 'w') as f: - f.write(cpp_str) + # Write Configfile.h + if args.h_template and args.output_h: + try: + h_str = open(args.h_template, 'r').read() + h_str = h_str.replace('%templ_privateattribs%', hvar1) \ + .replace('%templ_publicmethods%', hvar2) - # Read and write Configfile.h - with open('../src/Configfile.h.in', 'r') as f: - h_str = f.read() - - h_str = h_str.replace('%templ_privateattribs%', hvar1) \ - .replace('%templ_publicmethods%', hvar2) - - with open('Configfile.h', 'w') as f: - f.write(h_str) + os.makedirs(os.path.dirname(args.output_h), exist_ok=True) + with open(args.output_h, 'w') as f: + f.write(h_str) + except FileNotFoundError: + print(f"Error: Header template file {args.h_template} not found.", file=sys.stderr) + sys.exit(1) # Write hermesrc.example - with open('../dists/hermesrc.example', 'w') as f: - f.write(conf_example) + if args.output_example: + os.makedirs(os.path.dirname(args.output_example), exist_ok=True) + with open(args.output_example, 'w') as f: + f.write(conf_example) # Write hermes-options.html - with open('../docs/hermes-options.html', 'w') as f: - f.write(htmlvar) + if args.output_html and htmlvar: + os.makedirs(os.path.dirname(args.output_html), exist_ok=True) + with open(args.output_html, 'w') as f: + f.write(htmlvar) if __name__ == "__main__": main() diff --git a/src/Configfile.tmpl b/src/Configfile.tmpl index 4cf022c..ef86599 100644 --- a/src/Configfile.tmpl +++ b/src/Configfile.tmpl @@ -9,7 +9,6 @@ * *clean* -#ifndef WIN32 * whether to fork to the background. initscripts require * this to be true most of the time. @@ -35,7 +34,6 @@ string,group,"nobody" * if you set background=true above, this will write the pid * of the forked hermes, not the original. string,pid_file,"/var/run/hermes.pid" -#endif //WIN32 * the port where hermes will listen for new connection. * if you are going to use a port lower than 1024 (almost always, @@ -58,11 +56,7 @@ int,server_port,2525 * database file to use. * if you are chrooting, the path is relative to the chroot: * real filepath = chroot + database_file -#ifdef WIN32 -string,database_file,"greylisting.db" -#else string,database_file,"/var/hermes/greylisting.db" -#endif //WIN32 * whether to use greylisting. * greylisting will slightly delay your emails (configurable, see below) @@ -120,13 +114,6 @@ bool,add_status_header,false * time to delay the initial SMTP banner int,banner_delay_time,5 -#ifdef REALLY_VERBOSE_DEBUG -* email to notify exceptions to. -* CAVEAT: the code that does this is VERY BUGGY and VERY VERBOSE, don't use unless you -* are a developer looking for a bug. -string,notify_to,"" -#endif //REALLY_VERBOSE_DEBUG - * greylisting options. * *clean* @@ -142,56 +129,12 @@ int,initial_blacklist,5 * 36 is a magic number, is the maximum days between a day and the same day next month int,whitelist_expiry,36 -* whether to submit stats. -bool,submit_stats,true - -* should stats be submited using SSL? -* recomended, but some people will compile without ssl. -#ifdef HAVE_SSL -bool,submit_stats_ssl,true -#else -bool,submit_stats_ssl,false -#endif //HAVE_SSL - -* username (used to submit stats). -* you can register on http://www.hermes-project.com -string,submit_stats_username,"anonymous" - -* password -string,submit_stats_password,"anonymous" - * log level: * 0: log only errors * 1: log errors and information (default) * 2: debug (passwords might be written in plaintext with this option, so use with care) int,log_level,1 -#if LOGGER_CLASS==FileLogger -* if you are using the filelogger, which file to log to. -string,file_logger_filename,"hermes.log" - -* whether to keep the logger file locked between writes -bool,keep_file_locked,true - -* frequency for log rotating in minutes -* default is 1440 (1 day) -* 0 means no rotation -int,log_rotation_frequency,1440 - -* format for the logfile rotation -* if you are using logfile rotation, file_logger represents the filename -* to which the logger will write, while this is the name files will get -* when rotated -* you can use the following variables: -* %%year%% - current year (4 digits) -* %%month%% - current month -* %%day%% - current day -* %%hour%% - current hour -* %%minute%% - current minute -* all of them are zero-padded -string,rotate_filename,"hermes-%%year%%-%%month%%-%%day%%-%%hour%%:%%minute%%.log" -#endif //LOGGER_CLASS==FileLogger - * whether to clean the database file and send stats. * if you have two instances of hermes running (for example one for smtp and other for smtps) * you want to configure all of them but one to use clean_db=false. @@ -201,9 +144,7 @@ string,rotate_filename,"hermes-%%year%%-%%month%%-%%day%%-%%hour%%:%%minute%%.lo * will be left hanging around without any use. bool,clean_db,true -#ifdef HAVE_SSL * ssl-related config options -* NOTE: this NEEDS the openssl library * *clean* @@ -235,7 +176,6 @@ string,certificate_file,"/etc/hermes/hermes.cert" * # openssl dhparam -out dhparam.pem * (replace with the number of bits suitable for you, e.G. 1024) string,dhparams_file,"" -#endif //HAVE_SSL * whether to add headers to the email sent or no. * to be rfc compatible this HAS to be true, but if you set to false, no one will know you are using hermes @@ -262,11 +202,7 @@ bool,check_helo_against_reverse,false * whether to query the spf record for the incoming domain. * should help, enable if you have libspf (if you don't, install it and recompile) -#ifdef HAVE_SPF bool,query_spf,true -#else -bool,query_spf,false -#endif //HAVE_SPF * return temporary error instead of permanent error. * Currently, this only applies to SPF and DNSBL rejected email From 3c8bd791e6fb8d03872aff82c5deec123b67976e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Guti=C3=A9rrez?= Date: Thu, 27 Mar 2025 08:29:26 +0100 Subject: [PATCH 9/9] More proxy stuff --- include/Proxy.h | 11 +- src/Proxy.cpp | 289 ++++++++++++++++++++++++++---------------------- 2 files changed, 163 insertions(+), 137 deletions(-) diff --git a/include/Proxy.h b/include/Proxy.h index 7e266c0..861f112 100644 --- a/include/Proxy.h +++ b/include/Proxy.h @@ -1,7 +1,8 @@ // Proxy.h #pragma once #include -#include "SocketInterface.h" +#include +#include #define SMTP_STATE_WAIT_FOR_HELO 0 #define SMTP_STATE_WAIT_FOR_MAILFROM 1 @@ -10,10 +11,10 @@ class Proxy { public: - Proxy(SocketInterface* outside_socket) : outside(outside_socket) {} - - void run(std::string& peer_address); + Proxy(); + void run(boost::asio::ssl::stream* outside); private: - SocketInterface* outside; + boost::asio::io_service& io_service_; + boost::asio::ssl::context ssl_context; }; diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 1515869..9dac485 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -1,13 +1,17 @@ // Proxy.cpp #include "Proxy.h" +#include "HostnameResolver.h" #include #include -#include "Utils.h" // Dummy include; replace with actual Utils implementation -#include "Configfile.h" // Dummy include; replace with actual Configfile implementation +#include +#include +#include "Utils.h" +#include "Configfile.h" -extern Configfile cfg; // External configuration +extern Configfile cfg; -void Proxy::run(std::string& peer_address) { +void Proxy::run(boost::asio::ssl::stream* outside) { + boost::asio::ssl::stream* inside; // Original comments and variables retained std::string from = ""; std::string to = ""; @@ -16,7 +20,24 @@ void Proxy::run(std::string& peer_address) { unsigned char last_state = SMTP_STATE_WAIT_FOR_HELO; long unimplemented_requests = 0; + #ifdef HAVE_SPF + SpfChecker spf_checker; + #endif + try { + // Resolve hostname using the Boost resolver + try { + resolvedname = HostnameResolver::resolveHostname(peer_address); + } + catch (const std::exception& e) { + std::cerr << std::format("Hostname resolution error: {}", e.what()) << std::endl; + resolvedname = ""; + } + + // Configure SSL contexts + boost::asio::ssl::context ssl_context(boost::asio::ssl::context::tlsv12); + ssl_context.set_verify_mode(boost::asio::ssl::verify_none); + bool throttled = cfg.getThrottle(); // Start with a throttled connection bool authenticated = false; // Start with a non-authenticated connection bool esmtp = false; @@ -24,7 +45,7 @@ void Proxy::run(std::string& peer_address) { std::string hermes_status = "unknown"; // Check whitelist - if (!cfg.getDnsWhitelistDomains().empty() && + if (!cfg.getDnsWhitelistDomains().empty() && Utils::listed_on_dns_lists(cfg.getDnsWhitelistDomains(), cfg.getDnsWhitelistPercentage(), peer_address)) { authenticated = true; hermes_status = "whitelisted"; @@ -39,195 +60,199 @@ void Proxy::run(std::string& peer_address) { } else { if (!cfg.getAllowDataBeforeBanner()) { std::this_thread::sleep_for(std::chrono::seconds(cfg.getBannerDelayTime())); - if (outside->canRead(0)) { // if we have data waiting before the server gives us a 220 - std::cout << "421 (data_before_banner) (ip:" << peer_address << ")\n"; // Log it - std::this_thread::sleep_for(std::chrono::seconds(20)); // Annoy spammers once more - outside->writeLine("421 Stop sending data before we show you the banner"); + + // Check if data is waiting before server banner + boost::system::error_code ec; + size_t available = outside->lowest_layer().available(ec); + if (ec || available > 0) { + std::cout << std::format("421 (data_before_banner) (ip:{})\n", peer_address); + std::this_thread::sleep_for(std::chrono::seconds(20)); + + // Write rejection message + std::string rejection_msg = "421 Stop sending data before we show you the banner\r\n"; + boost::asio::write(outside->lowest_layer(), boost::asio::buffer(rejection_msg), ec); return; } } } // Connect to the inside server - inside.connect(cfg.getServerHost(), cfg.getServerPort()); + boost::asio::ip::tcp::resolver resolver(io_service_); + boost::asio::ip::tcp::resolver::results_type endpoints = + resolver.resolve(cfg.getServerHost(), std::to_string(cfg.getServerPort())); - #ifdef HAVE_SSL + boost::asio::connect(inside.lowest_layer(), endpoints); + + // SSL setup if (cfg.getOutgoingSsl()) { - inside.prepareSSL(false); - inside.startSSL(false); + inside.set_verify_mode(boost::asio::ssl::verify_none); + inside.handshake(boost::asio::ssl::stream_base::client); } + if (cfg.getIncomingSsl()) { - outside->prepareSSL(true); - outside->startSSL(true); + outside->set_verify_mode(boost::asio::ssl::verify_none); + outside->handshake(boost::asio::ssl::stream_base::server); } - #endif // HAVE_SSL + + // Communication buffers + std::vector read_buffer(4096); + boost::system::error_code ec; // Main loop for communication - while (!outside->isClosed() && !inside.isClosed()) { + while (!outside->lowest_layer().is_open() || !inside.lowest_layer().is_open()) { // Check if the client wants to send something to the server - if (outside->canRead(0.2)) { - strtemp = outside->readLine(); - if (outside->isClosed()) return; + size_t client_available = outside->lowest_layer().available(ec); + if (client_available > 0) { + size_t bytes_read = outside->read_some(boost::asio::buffer(read_buffer), ec); + strtemp = std::string(read_buffer.begin(), read_buffer.begin() + bytes_read); if (strtemp.length() > 10 && "mail from:" == Utils::strtolower(strtemp.substr(0, 10))) { from = Utils::getmail(strtemp); last_state = SMTP_STATE_WAIT_FOR_RCPTTO; } + if ("ehlo" == Utils::strtolower(strtemp.substr(0, 4))) esmtp = true; - if (strtemp.length() > 4 && ("ehlo" == Utils::strtolower(strtemp.substr(0, 4)) || + + if (strtemp.length() > 4 && ("ehlo" == Utils::strtolower(strtemp.substr(0, 4)) || "helo" == Utils::strtolower(strtemp.substr(0, 4)))) { ehlostr = Utils::trim(strtemp.substr(5)); last_state = SMTP_STATE_WAIT_FOR_MAILFROM; } + + // RCPT TO handling with comprehensive checks if (strtemp.length() > 8 && "rcpt to:" == Utils::strtolower(strtemp.substr(0, 8))) { - std::string strlog = ""; - std::string code = ""; std::string mechanism = ""; std::string message = ""; to = Utils::getmail(strtemp); - try { - resolvedname = Socket::resolveInverselyToString(peer_address); - } catch (Exception& e) { - resolvedname = ""; - } + // Construct log string using std::format + std::string strlog = std::format( + "from {} (ip:{}, hostname:{}, {} {}:{}) -> to {}", + from, + peer_address, + resolvedname, + (esmtp ? "ehlo" : "helo"), + ehlostr, + to + ); - strlog = "from " + from + " (ip:" + peer_address + ", hostname:" + resolvedname + - ", " + (esmtp ? "ehlo" : "helo") + ":" + ehlostr + ") -> to " + to; - - // Check greylisting - if (cfg.getGreylist() && !authenticated && Utils::greylist(cfg.getDatabaseFile(), peer_address, from, to)) { + // Greylisting check + std::string code = "250"; + if (cfg.getGreylist() && !authenticated && + Utils::greylist(cfg.getDatabaseFile(), peer_address, from, to)) { code = "421"; mechanism = "greylist"; - message = code + " Greylisted!! Please try again in a few minutes."; - std::cout << "checking " << mechanism << "\n"; + message = std::format("{} Greylisted!! Please try again in a few minutes.", code); + std::cout << std::format("checking {}\n", mechanism); } + // SPF Check #ifdef HAVE_SPF - else if (cfg.getQuerySpf() && !authenticated && !spf_checker.query(peer_address, ehlostr, from)) { - hermes_status = "spf-failed"; - if (cfg.getAddStatusHeader()) code = "250"; - else code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + else if (cfg.getQuerySpf() && !authenticated && + !spf_checker.query(peer_address, ehlostr, from)) { + code = cfg.getAddStatusHeader() ? "250" : + (cfg.getReturnTempErrorOnReject() ? "421" : "550"); mechanism = "spf"; - message = code + " You do not seem to be allowed to send email for that particular domain."; - std::cout << "checking " << mechanism << "\n"; + message = std::format( + "{} You do not seem to be allowed to send email for that particular domain.", + code + ); + std::cout << std::format("checking {}\n", mechanism); } - #endif // HAVE_SPF - // Check blacklist - else if (!authenticated && Utils::blacklisted(cfg.getDatabaseFile(), peer_address, to)) { + #endif + // Blacklist check + else if (!authenticated && + Utils::blacklisted(cfg.getDatabaseFile(), peer_address, to)) { code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; mechanism = "allowed-domain-per-ip"; - message = code + " You do not seem to be allowed to send email to that particular domain from that address."; - std::cout << "checking " << mechanism << "\n"; + message = std::format( + "{} You do not seem to be allowed to send email to that particular domain from that address.", + code + ); + std::cout << std::format("checking {}\n", mechanism); } - // Check RBL - else if (!cfg.getDnsBlacklistDomains().empty() && !authenticated && - Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(), cfg.getDnsBlacklistPercentage(), peer_address)) { - hermes_status = "blacklisted"; - if (cfg.getAddStatusHeader()) code = "250"; - else code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; + // DNS Blacklist check + else if (!cfg.getDnsBlacklistDomains().empty() && !authenticated && + Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(), + cfg.getDnsBlacklistPercentage(), + peer_address)) { + code = cfg.getAddStatusHeader() ? "250" : + (cfg.getReturnTempErrorOnReject() ? "421" : "550"); mechanism = "dnsbl"; - message = code + " You are listed on some DNS blacklists. Get delisted before trying to send us email."; - std::cout << "checking " << mechanism << "\n"; + message = std::format( + "{} You are listed on some DNS blacklists. Get delisted before trying to send us email.", + code + ); + std::cout << std::format("checking {}\n", mechanism); } - else if (cfg.getRejectNoReverseResolution() && !authenticated && "" == resolvedname) { + // Reverse DNS check + else if (cfg.getRejectNoReverseResolution() && !authenticated && resolvedname.empty()) { code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; mechanism = "no reverse resolution"; - message = code + " Your IP address does not resolve to a hostname."; - std::cout << "checking " << mechanism << "\n"; + message = std::format( + "{} Your IP address does not resolve to a hostname.", + code + ); + std::cout << std::format("checking {}\n", mechanism); } + // HELO/Reverse name check else if (cfg.getCheckHeloAgainstReverse() && !authenticated && ehlostr != resolvedname) { code = cfg.getReturnTempErrorOnReject() ? "421" : "550"; mechanism = "helo differs from resolved name"; - message = code + " Your IP hostname doesn't match your envelope hostname."; - std::cout << "checking " << mechanism << "\n"; - } else { - code = "250"; + message = std::format( + "{} Your IP hostname doesn't match your envelope hostname.", + code + ); + std::cout << std::format("checking {}\n", mechanism); } - if (!mechanism.empty()) strlog.insert(0, "(" + mechanism + ") "); - strlog.insert(0, code + " "); - std::cout << strlog << "\n"; // Log the connection + // Prepare log message + if (!mechanism.empty()) { + strlog = std::format("({}) {}", mechanism, strlog); + } + strlog = std::format("{} {}", code, strlog); + std::cout << strlog << "\n"; - // If we didn't accept the email, punish spammers - if ("250" != code) { - inside.writeLine("QUIT"); - inside.close(); // Close the socket now and leave server alone + // Handle rejection + if (code != "250") { + // Close inside connection + inside.lowest_layer().close(); + + // Delay to annoy spammers std::this_thread::sleep_for(std::chrono::seconds(20)); - outside->writeLine(message); + + // Send rejection message + std::string rejection_msg = message + "\r\n"; + boost::asio::write(outside->lowest_layer(), boost::asio::buffer(rejection_msg), ec); return; } + last_state = SMTP_STATE_WAIT_FOR_DATA; } - // Handle STARTTLS - if ("starttls" == Utils::strtolower(strtemp.substr(0, 8))) { - #ifdef HAVE_SSL - try { - outside->prepareSSL(true); - std::cout << "STARTTLS issued by remote, TLS enabled\n"; - outside->writeLine("220 You can speak now, line is secure!!"); - outside->startSSL(true); - } catch (Exception& e) { - std::cout << "STARTTLS issued by remote, but enableSSL failed!\n"; - outside->writeLine("454 Tried to enable SSL but failed"); - } - #else - outside->writeLine("454 TLS temporarily not available"); - std::cout << "STARTTLS issued by remote, TLS was not enabled because this build lacks SSL support\n"; - #endif // HAVE_SSL - strtemp = ""; - } - - if (strtemp.length()) inside.writeLine(strtemp); + // Send to inside server + boost::asio::write(inside.lowest_layer(), boost::asio::buffer(strtemp), ec); } // Check if the server wants to send something to the client - if (inside.canRead(0.2)) { - strtemp = inside.readLine(); - if (inside.isClosed()) return; + size_t server_available = inside.lowest_layer().available(ec); + if (server_available > 0) { + size_t bytes_read = inside.read_some(boost::asio::buffer(read_buffer), ec); + strtemp = std::string(read_buffer.begin(), read_buffer.begin() + bytes_read); - std::string code = strtemp.substr(0, 3); // All responses by the server start with a code - if ("354" == code) { // 354 -> you can start sending data, unthrottle now - std::string endofdata = ""; - ssize_t bytes_read = 0; - char buffer[4097]; - outside->writeLine(strtemp); - - do { - bytes_read = outside->readBytes(buffer, sizeof(buffer) - 1); - if (bytes_read < 1) throw NetworkException("Problem reading DATA contents, recv returned " + Utils::inttostr(bytes_read), __FILE__, __LINE__); - buffer[bytes_read] = '\0'; - inside.writeBytes(buffer, bytes_read); - if (bytes_read < 5) endofdata += std::string(buffer); - else endofdata = std::string(buffer + bytes_read - 5); - if (endofdata.length() > 5) endofdata = endofdata.substr(endofdata.length() - 5); - } while (endofdata != "\r\n.\r\n" && endofdata.length() > 3 && endofdata.substr(2) != "\n.\n" && endofdata.substr(2) != "\r.\r"); - } - - if ("235" == code) { // 235 -> you are correctly authenticated - throttled = false; - authenticated = true; - hermes_status = "authenticated"; - } - - // Try to annoy spammers who send too many senseless commands by delaying their connection - if ("502" == code) { // 502 unimplemented - if (cfg.getNumberOfUnimplementedCommandsAllowed() != -1 && ++unimplemented_requests > cfg.getNumberOfUnimplementedCommandsAllowed()) { - inside.writeLine("QUIT"); - inside.close(); // Close the socket now and leave server alone - std::this_thread::sleep_for(std::chrono::seconds(60)); - outside->writeLine("502 Too many unimplemented commands, closing connection"); - return; - } - } - - if (strtemp.length()) outside->writeLine(strtemp); + // Send to outside socket + boost::asio::write(outside->lowest_layer(), boost::asio::buffer(strtemp), ec); } - if (throttled) std::this_thread::sleep_for(std::chrono::seconds(cfg.getThrottlingTime())); // Take 1 second between each command + // Throttling + if (throttled) { + std::this_thread::sleep_for(std::chrono::seconds(cfg.getThrottlingTime())); + } } - } catch (Exception& e) { // Any exception will close both connections - std::cerr << "Exception occurred: " << e.what() << std::endl; - return; + } + catch (const boost::system::system_error& e) { + std::cerr << std::format("Boost.Asio error: {}", e.what()) << std::endl; + } + catch (const std::exception& e) { + std::cerr << std::format("Standard exception: {}", e.what()) << std::endl; } }