pax_global_header00006660000000000000000000000064141124444600014512gustar00rootroot0000000000000052 comment=3013658b200f517b63266a9e2c44ba9a9470479b sslh-1.22c/000077500000000000000000000000001411244446000125525ustar00rootroot00000000000000sslh-1.22c/.gitignore000066400000000000000000000001201411244446000145330ustar00rootroot00000000000000*.swp *.swo *.o cscope.* echosrv sslh-fork sslh-select sslh.8.gz tags version.h sslh-1.22c/COPYING000066400000000000000000000432541411244446000136150ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. sslh-1.22c/ChangeLog000066400000000000000000000275251411244446000143370ustar00rootroot00000000000000v1.22: 17AUG2021 sslh-select now supports UDP protocols. Probes specified in the `protocols` configuration entry are tried on incoming packets, TCP or UDP, and forwarded based on the input protocol (an incoming TCP connection will be forwarded as TCP, and same with UDP). This has been tested with DNS as shown in udp.cfg: incoming packets that contain my domain name are assumed to be a DNS request and forwarded accordingly. Note this could cause problems if combined with incoming TLS with SNI. UDP clients and servers need to agree on the IPv4/IPv6 they use: use the same protocol on all sides! Often, this means explicitely using 'ip4-localhost'. UDP sender-receiver pairs (connections, so to speak) are kept for 60s, which can be changed with `udp_timeout` in the configuration. Added probes for UDP protocols QUICK and Teamspeak. Added probes for syslog protocol. sslh-select refactored to change linear searches through connections to linear searches through fd_set. Fixed a libconfig call to support libconfig 1.7.3. Added symbol to support libconfig 1.4.9, still in use in CentOS7. Warn about unknown settings in the configuration file. Added per-protocol `transparent` option. sslh-fork drops the capability after creating the server-side transparent socket. Transparent now uses CAP_NET_RAW instead of CAP_NET_ADMIN. Removed compile-time option to use POSIX regex. Now regex must be PCRE2 (Perl-Compatible). This was in fact the case since v1.21, as PCRE are used to parse the config file. v1.21: 11JUL2020 WARNING: Moved configuration and command-line management to use conf2struct. Changes are: * `--ssl` and using `name: 'ssl'` in config file is no longer supported, use `tls` instead. * command line option <-F|--config> no longer defaults to /etc/sslh.cfg, so you have to specify it explicitly. * command line option <-v|--verbose> takes a mandatory integer parameter Added TCP_FASTOPEN support for client sockets (if tfo_ok is specified in their configuration) and for listenint socket, if all client protocols support it. (Craig Andrews) Added 'minlength' option to skip a probe if less than that many bytes have been received (mostly for regex) Update Let's Encrypt entry in example.cfg for tls-alpn-01 challenges; tls-sni-* challenges are now deprecated. Log to syslog even if in foreground (for people who use fail2ban) Use syslog_facility: "none" to disable syslog output. Changed exit code for illegal command line parameter from 1 to 6 (for testing purposes) v1.20: 20NOV2018 Added support for socks5 protocol (Eugene Protozanov) New probing method: Before, probes were tried in order, repeating on the same probe as long it returned PROBE_AGAIN before moving to the next one. This means a probe which requires a lot of data (i.e. returne PROBE_AGAIN for a long time) could prevent sucessful matches from subsequent probes. The configuration file needed to take that into account. Now, all probes are tried each time new data is found. If any probe matches, use it. If at least one probe requires more data, wait for more. If all probes failed, connect to the last one. So the only thing to know when writing the configuration file is that 'anyprot' needs to be last. Test suite heavily refactored; `t` uses `test.cfg` to decide which probes to test and all setup is automatic; probes get tested with 'fast' (entire first message in one packet) and 'slow' (one byte at a time); when SNI/ALPN are defined, all combinations are tested. Old 'tls' probe removed, 'sni_alpn' probe renamed as 'tls'. You'll need to change 'sni_alpn' to 'tls' in your configuration file, if ever you used it. v1.19: 20JAN2018 Added 'syslog_facility' configuration option to specify where to log. TLS now supports SNI and ALPN (Travis Burtrum), including support for Let's Encrypt challenges (Jonathan McCrohan) ADB probe. (Mike Frysinger) Added per-protocol 'fork' option. (Oleg Oshmyan) Added chroot option. (Mike Frysinger) A truckload of bug fixes and documentation improvements (Various contributors) v1.18: 29MAR2016 Added USELIBPCRE to make use of regex engine optional. Added support for RFC4366 SNI and RFC7301 ALPN (Travis Burtrum) Changed connection log to include the name of the probe that triggered. Changed configuration file format: 'probe' field is no longer required, 'name' field can now contain 'tls' or 'regex', with corresponding options (see example.cfg) Added 'log_level' option to each protocol, which allows to turn off generation of log at each connection. Added 'keepalive' option. v1.17: 09MAR2015 Support RFC5952-style IPv6 addresses, e.g. [::]:443. Transparant proxy support for FreeBSD. (Ruben van Staveren) Using -F with no argument will try /etc/sslh/sslh.cfg and then /etc/sslh.cfg as configuration files. (argument to -F can no longer be separated from the option by a space, e.g. must be -Ffoo.cfg) Call setgroups() before setgid() (fixes potential privilege escalation). (Lars Vogdt) Use portable way of getting modified time for OSX support. (Aaron Madlon-Kay) Example configuration for fail2ban. (Every Mouw) v1.16: 11FEB2014 Probes made more resilient, to incoming data containing NULLs. Also made them behave properly when receiving too short packets to probe on the first incoming packet. (Ondrej Kuznk) Libcap support: Keep only CAP_NET_ADMIN if started as root with transparent proxying and dropping priviledges (enable USELIBCAP in Makefile). This avoids having to mess with filesystem capabilities. (Sebastian Schmidt/yath) Fixed bugs related to getpeername that would cause sslh to quit erroneously (getpeername can return actual errors if connections are dropped before getting to getpeername). Set IP_FREEDBIND if available to bind to addresses that don't yet exist. v1.15: 27JUL2013 Added --transparent option for transparent proxying. See README for iptables magic and capability management. Fixed bug in sslh-select: if number of opened file descriptor became bigger than FD_SETSIZE, bad things would happen. Fixed bug in sslh-select: if socket dropped while deferred_data was present, sslh-select would crash. Increased FD_SETSIZE for Cygwin, as the default 64 is too low for even moderate load. v1.14: 21DEC2012 Corrected OpenVPN probe to support pre-shared secret mode (OpenVPN port-sharing code is... wrong). Thanks to Kai Ellinger for help in investigating and testing. Added an actual TLS/SSL probe. Added configurable --on-timeout protocol specification. Added a --anyprot protocol probe (equivalent to what --ssl was). Makefile respects the user's compiler and CFLAG choices (falling back to the current values if undefined), as well as LDFLAGS. (Michael Palimaka) Added "After" and "KillMode" to systemd.sslh.service (Thomas Weischuh). Added LSB tags to etc.init.d.sslh (Thomas Varis). v1.13: 18MAY2012 Write PID file before dropping privileges. Added --background, which overrides 'foreground' configuration file setting. Added example systemd service file from Archlinux in scripts/ https://projects.archlinux.org/svntogit/community.git/tree/trunk/sslh.service?h=packages/sslh (Sbastien Luttringer) v1.12: 08MAY2012 Added support for configuration file. New protocol probes can be defined using regular expressions that match the first packet sent by the client. sslh now connects timed out connections to the first configured protocol instead of 'ssh' (just make sure ssh is the first defined protocol). sslh now tries protocols in the order in which they are defined (just make sure sslh is the last defined protocol). v1.11: 21APR2012 WARNING: defaults have been removed for --user and --pidfile options, update your start-up scripts! No longer stop sslh when reverse DNS requests fail for logging. Added HTTP probe. No longer create new session if running in foreground. No longer default to changing user to 'nobody'. If --user isn't specified, just run as current user. No longer create PID file by default, it should be explicitely set with --pidfile. No longer log to syslog if in foreground. Logs are instead output to stderr. The four changes above make it straightforward to integrate sslh with systemd, and should help with launchd. v1.10: 27NOV2011 Fixed calls referring to sockaddr length so they work with FreeBSD. Try target addresses in turn until one works if there are several (e.g. "localhost:22" resolves to an IPv6 address and an IPv4 address and sshd does not listen on IPv6). Fixed sslh-fork so killing the head process kills the listener processes. Heavily cleaned up test suite. Added stress test t_load script. Added coverage (requires lcov). Support for XMPP (Arnaud Gendre). Updated README.MacOSX (Aaron Madlon-Kay). v1.9: 02AUG2011 WARNING: This version does not work with FreeBSD and derivatives! WARNING: Options changed, you'll need to update your start-up scripts! Log format changed, you'll need to update log processing scripts! Now supports IPv6 throughout (both on listening and forwarding) Logs now contain IPv6 addresses, local forwarding address, and resolves names (unless --numeric is specified). Introduced long options. Options -l, -s and -o replaced by their long counterparts. Defaults for SSL and SSH options suppressed (it's legitimate to want to use sslh to mux OpenVPN and tinc while not caring about SSH nor SSL). Bind to multiple addresses with multiple -p options. Support for tinc VPN (experimental). Numeric logging option. v1.8: 15JUL2011 Changed log format to make it possible to link connections to subsequent logs from other services. Updated CentOS init.d script (Andre Krajnik). Fixed zombie issue with OpenBSD (The SA_NOCLDWAIT flag is not propagated to the child process, so we set up signals after the fork.) (Franois FRITZ) Added -o "OpenVPN" and OpenVPN probing and support. Added single-threaded, select(2)-based version. Added support for "Bold" SSH clients (clients that speak first) Thanks to Guillaume Ricaud for spotting a regression bug. Added -f "foreground" option. Added test suite. (only tests connexions. No test for libwrap, setsid, setuid and so on) and corresponding 'make test' target. Added README.MacOSX (thanks Aaron Madlon-Kay) Documented use with proxytunnel and corkscrew in README. v1.7: 01FEB2010 Added CentOS init.d script (Andre Krajnik). Fixed default ssl address inconsistancy, now defaults to "localhost:443" and fixed documentation accordingly (pointed by Markus Schalke). Children no longer bind to the listen socket, so parent server can be stopped without killing an active child (pointed by Matthias Buecher). Inetd support (Dima Barsky). v1.6: 25APR2009 Added -V, version option. Install target directory configurable in Makefile Changed syslog prefix in auth.log to "sslh[%pid]" Man page new 'make install' and 'make install-debian' targets PID file now specified using -P command line option Actually fixed zombie generation (the v1.5 patch got lost, doh!) v1.5: 10DEC2008 Fixed zombie generation. Added support scripts (), Makefile. Changed all 'connexions' to 'connections' to please pesky users. Damn users. v1.4: 13JUL2008 Added libwrap support for ssh service (Christian Weinberger) Only SSH is libwraped, not SSL. v1.3: 14MAY2008 Added parsing for local interface to listen on Changed default SSL connection to port 442 (443 doesn't make sense as a default as we're already listening on 443) Syslog incoming connections v1.2: 12MAY2008 Fixed compilation warning for AMD64 (Thx Daniel Lange) v1.1: 21MAY2007 Making sslhc more like a real daemon: * If $PIDFILE is defined, write first PID to it upon startup * Fork at startup (detach from terminal) (thanks to http://www.enderunix.org/docs/eng/daemon.php -- good checklist) * Less memory usage (?) v1.0: Basic functionality: privilege dropping, target hostnames and ports configurable. sslh-1.22c/Dockerfile000066400000000000000000000005321411244446000145440ustar00rootroot00000000000000FROM alpine:latest as build ADD . /sslh RUN \ apk add \ gcc \ libconfig-dev \ make \ musl-dev \ pcre-dev \ perl && \ cd /sslh && \ make sslh-select && \ strip sslh-select FROM alpine:latest COPY --from=build /sslh/sslh-select /sslh RUN apk --no-cache add libconfig pcre ENTRYPOINT [ "/sslh", "--foreground"] sslh-1.22c/Makefile000066400000000000000000000074171411244446000142230ustar00rootroot00000000000000 VERSION=$(shell ./genver.sh -r) # Configuration -- you probably need to `make clean` if you # change any of these ENABLE_REGEX=1 # Enable regex probes USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files) USELIBWRAP?= # Use libwrap? USELIBCAP= # Use libcap? USESYSTEMD= # Make use of systemd socket activation USELIBBSD?= # Use libbsd (needed to update process name in `ps`) COV_TEST= # Perform test coverage? PREFIX?=/usr BINDIR?=$(PREFIX)/sbin MANDIR?=$(PREFIX)/share/man/man8 MAN=sslh.8.gz # man page name # End of configuration -- the rest should take care of # itself ifneq ($(strip $(COV_TEST)),) CFLAGS_COV=-fprofile-arcs -ftest-coverage endif CC ?= gcc CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV) LIBS=-lm -lpcre2-8 OBJS=sslh-conf.o common.o sslh-main.o probe.o tls.o argtable3.o udp-listener.o collection.o gap.o CONDITIONAL_TARGETS= ifneq ($(strip $(USELIBWRAP)),) LIBS:=$(LIBS) -lwrap CPPFLAGS+=-DLIBWRAP endif ifneq ($(strip $(ENABLE_REGEX)),) CPPFLAGS+=-DENABLE_REGEX endif ifneq ($(strip $(USELIBCONFIG)),) LIBS:=$(LIBS) -lconfig CPPFLAGS+=-DLIBCONFIG endif ifneq ($(strip $(USELIBCAP)),) LIBS:=$(LIBS) -lcap CPPFLAGS+=-DLIBCAP endif ifneq ($(strip $(USESYSTEMD)),) LIBS:=$(LIBS) -lsystemd CPPFLAGS+=-DSYSTEMD CONDITIONAL_TARGETS+=systemd-sslh-generator endif ifneq ($(strip $(USELIBBSD)),) LIBS:=$(LIBS) -lbsd CPPFLAGS+=-DLIBBSD endif all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS) .c.o: *.h version.h $(CC) $(CFLAGS) $(CPPFLAGS) -c $< version.h: ./genver.sh >version.h sslh: sslh-fork sslh-select $(OBJS): version.h common.h collection.h sslh-conf.h gap.h sslh-conf.c sslh-conf.h: sslhconf.cfg conf2struct sslhconf.cfg sslh-fork: version.h $(OBJS) sslh-fork.o Makefile $(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS) #strip sslh-fork sslh-select: version.h $(OBJS) sslh-select.o Makefile $(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS) #strip sslh-select systemd-sslh-generator: systemd-sslh-generator.o $(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig echosrv-conf.c echosrv-conf.h: echosrv.cfg conf2struct echosrv.cfg echosrv: version.h echosrv-conf.c echosrv.o echosrv-conf.o argtable3.o $(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o echosrv-conf.o argtable3.o $(LIBS) $(MAN): sslh.pod Makefile pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN) # Create release: export clean tree and tag current # configuration release: git archive master --prefix="sslh-$(VERSION)/" | gzip > /tmp/sslh-$(VERSION).tar.gz gpg --detach-sign --armor /tmp/sslh-$(VERSION).tar.gz # Build docker image docker: docker image build -t "sslh:${VERSION}" . docker image tag "sslh:${VERSION}" sslh:latest docker-clean: yes | docker image rm "sslh:${VERSION}" sslh:latest yes | docker image prune # generic install: install binary and man page install: sslh $(MAN) mkdir -p $(DESTDIR)/$(BINDIR) mkdir -p $(DESTDIR)/$(MANDIR) install -p sslh-fork $(DESTDIR)/$(BINDIR)/sslh install -p -m 0644 $(MAN) $(DESTDIR)/$(MANDIR)/$(MAN) # "extended" install for Debian: install startup script install-debian: install sslh $(MAN) sed -e "s+^PREFIX=+PREFIX=$(PREFIX)+" scripts/etc.init.d.sslh > /etc/init.d/sslh chmod 755 /etc/init.d/sslh update-rc.d sslh defaults uninstall: rm -f $(DESTDIR)$(BINDIR)/sslh $(DESTDIR)$(MANDIR)/$(MAN) $(DESTDIR)/etc/init.d/sslh $(DESTDIR)/etc/default/sslh update-rc.d sslh remove distclean: clean rm -f tags sslh-conf.[ch] echosrv-conf.[ch] cscope.* clean: rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info tags: ctags --globals -T *.[ch] cscope: -find . -name "*.[chS]" >cscope.files -cscope -b -R test: ./t sslh-1.22c/README.md000066400000000000000000000040661411244446000140370ustar00rootroot00000000000000sslh -- A ssl/ssh multiplexer ============================= `sslh` accepts connections on specified ports, and forwards them further based on tests performed on the first data packet sent by the remote client. Probes for HTTP, TLS/SSL (including SNI and ALPN), SSH, OpenVPN, tinc, XMPP, SOCKS5, are implemented, and any other protocol that can be tested using a regular expression, can be recognised. A typical use case is to allow serving several services on port 443 (e.g. to connect to SSH from inside a corporate firewall, which almost never block port 443) while still serving HTTPS on that port. Hence `sslh` acts as a protocol demultiplexer, or a switchboard. With the SNI and ALPN probe, it makes a good front-end to a virtual host farm hosted behind a single IP address. `sslh` has the bells and whistles expected from a mature daemon: privilege and capabilities dropping, inetd support, systemd support, transparent proxying, chroot, logging, IPv4 and IPv6, TCP and UDP, a fork-based and a select-based model, and more. Install ======= Please refer to the [install guide](doc/INSTALL.md). Configuration ============= Please refer to the [configuration guide](doc/config.md). Docker image ------------ How to use --- Build docker image make docker ```bash docker container run \ --rm \ -it \ --listen=0.0.0.0:443 \ --ssh=hostname:22 \ --tlshostname:443 \ sslh:latest ``` docker-compose example ``` --- version: "3" services: sslh: image: sslh:latest hostname: sslh ports: - 443:443/tcp command: --listen=0.0.0.0:443 --tlshostname:443 --openvpn=openvpn:1194 depends_on: - nginx - openvpn nginx: image: nginx hostname: nginx openvpn: image: openvpn:latest hostname: openvpn ``` Comments? Questions? ==================== You can subscribe to the `sslh` mailing list here: This mailing list should be used for discussion, feature requests, and will be the preferred channel for announcements. Of course, check the [FAQ](doc/FAQ.md) first! sslh-1.22c/TODO000066400000000000000000000017221411244446000132440ustar00rootroot00000000000000Here's a list of features that have been suggested or sometimes requested. This list is not a roadmap and shouldn't be construed to mean that any of this will happen. - configurable behaviour depending on services (e.g. select() for ssl but fork() for ssh). - have certain services available only from specified subnets - some sort of "service knocking" allowing to activate a service upon some external even, similar to port knocking; for example, go to a specific URL to enable sslh forwarding to sshd for a set period of time: * sslh listens on 443 and only directs to httpd * user goes somewhere to https://example.org/open_ssh.cgi * open_ssh.cgi tells sslh * sslh starts checking if incoming connections are ssh, and if they are, forward to sshd * 10 minutes later, sslh stops forwarding to ssh That would make it almost impossible for an observer (someone who'd telnet regularly on 443) to ever notice both services are available on 443. sslh-1.22c/argtable3.c000066400000000000000000006264701411244446000146010ustar00rootroot00000000000000/******************************************************************************* * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #define ARG_AMALGAMATION /******************************************************************************* * argtable3_private: Declares private types, constants, and interfaces * * This file is part of the argtable3 library. * * Copyright (C) 2013-2019 Tom G. Huang * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #ifndef ARG_UTILS_H #define ARG_UTILS_H #include #define ARG_ENABLE_TRACE 0 #define ARG_ENABLE_LOG 1 #ifdef __cplusplus extern "C" { #endif enum { ARG_ERR_MINCOUNT = 1, ARG_ERR_MAXCOUNT, ARG_ERR_BADINT, ARG_ERR_OVERFLOW, ARG_ERR_BADDOUBLE, ARG_ERR_BADDATE, ARG_ERR_REGNOMATCH }; typedef void(arg_panicfn)(const char* fmt, ...); #if defined(_MSC_VER) #define ARG_TRACE(x) \ __pragma(warning(push)) __pragma(warning(disable : 4127)) do { \ if (ARG_ENABLE_TRACE) \ dbg_printf x; \ } \ while (0) \ __pragma(warning(pop)) #define ARG_LOG(x) \ __pragma(warning(push)) __pragma(warning(disable : 4127)) do { \ if (ARG_ENABLE_LOG) \ dbg_printf x; \ } \ while (0) \ __pragma(warning(pop)) #else #define ARG_TRACE(x) \ do { \ if (ARG_ENABLE_TRACE) \ dbg_printf x; \ } while (0) #define ARG_LOG(x) \ do { \ if (ARG_ENABLE_LOG) \ dbg_printf x; \ } while (0) #endif /* * Rename a few generic names to unique names. * They can be a problem for the platforms like NuttX, where * the namespace is flat for everything including apps and libraries. */ #define xmalloc argtable3_xmalloc #define xcalloc argtable3_xcalloc #define xrealloc argtable3_xrealloc #define xfree argtable3_xfree extern void dbg_printf(const char* fmt, ...); extern void arg_set_panic(arg_panicfn* proc); extern void* xmalloc(size_t size); extern void* xcalloc(size_t count, size_t size); extern void* xrealloc(void* ptr, size_t size); extern void xfree(void* ptr); struct arg_hashtable_entry { void *k, *v; unsigned int h; struct arg_hashtable_entry* next; }; typedef struct arg_hashtable { unsigned int tablelength; struct arg_hashtable_entry** table; unsigned int entrycount; unsigned int loadlimit; unsigned int primeindex; unsigned int (*hashfn)(const void* k); int (*eqfn)(const void* k1, const void* k2); } arg_hashtable_t; /** * @brief Create a hash table. * * @param minsize minimum initial size of hash table * @param hashfn function for hashing keys * @param eqfn function for determining key equality * @return newly created hash table or NULL on failure */ arg_hashtable_t* arg_hashtable_create(unsigned int minsize, unsigned int (*hashfn)(const void*), int (*eqfn)(const void*, const void*)); /** * @brief This function will cause the table to expand if the insertion would take * the ratio of entries to table size over the maximum load factor. * * This function does not check for repeated insertions with a duplicate key. * The value returned when using a duplicate key is undefined -- when * the hash table changes size, the order of retrieval of duplicate key * entries is reversed. * If in doubt, remove before insert. * * @param h the hash table to insert into * @param k the key - hash table claims ownership and will free on removal * @param v the value - does not claim ownership * @return non-zero for successful insertion */ void arg_hashtable_insert(arg_hashtable_t* h, void* k, void* v); #define ARG_DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \ int fnname(arg_hashtable_t* h, keytype* k, valuetype* v) { return arg_hashtable_insert(h, k, v); } /** * @brief Search the specified key in the hash table. * * @param h the hash table to search * @param k the key to search for - does not claim ownership * @return the value associated with the key, or NULL if none found */ void* arg_hashtable_search(arg_hashtable_t* h, const void* k); #define ARG_DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \ valuetype* fnname(arg_hashtable_t* h, keytype* k) { return (valuetype*)(arg_hashtable_search(h, k)); } /** * @brief Remove the specified key from the hash table. * * @param h the hash table to remove the item from * @param k the key to search for - does not claim ownership */ void arg_hashtable_remove(arg_hashtable_t* h, const void* k); #define ARG_DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \ void fnname(arg_hashtable_t* h, keytype* k) { arg_hashtable_remove(h, k); } /** * @brief Return the number of keys in the hash table. * * @param h the hash table * @return the number of items stored in the hash table */ unsigned int arg_hashtable_count(arg_hashtable_t* h); /** * @brief Change the value associated with the key. * * function to change the value associated with a key, where there already * exists a value bound to the key in the hash table. * Source due to Holger Schemel. * * @name hashtable_change * @param h the hash table * @param key * @param value */ int arg_hashtable_change(arg_hashtable_t* h, void* k, void* v); /** * @brief Free the hash table and the memory allocated for each key-value pair. * * @param h the hash table * @param free_values whether to call 'free' on the remaining values */ void arg_hashtable_destroy(arg_hashtable_t* h, int free_values); typedef struct arg_hashtable_itr { arg_hashtable_t* h; struct arg_hashtable_entry* e; struct arg_hashtable_entry* parent; unsigned int index; } arg_hashtable_itr_t; arg_hashtable_itr_t* arg_hashtable_itr_create(arg_hashtable_t* h); void arg_hashtable_itr_destroy(arg_hashtable_itr_t* itr); /** * @brief Return the value of the (key,value) pair at the current position. */ extern void* arg_hashtable_itr_key(arg_hashtable_itr_t* i); /** * @brief Return the value of the (key,value) pair at the current position. */ extern void* arg_hashtable_itr_value(arg_hashtable_itr_t* i); /** * @brief Advance the iterator to the next element. Returns zero if advanced to end of table. */ int arg_hashtable_itr_advance(arg_hashtable_itr_t* itr); /** * @brief Remove current element and advance the iterator to the next element. */ int arg_hashtable_itr_remove(arg_hashtable_itr_t* itr); /** * @brief Search and overwrite the supplied iterator, to point to the entry matching the supplied key. * * @return Zero if not found. */ int arg_hashtable_itr_search(arg_hashtable_itr_t* itr, arg_hashtable_t* h, void* k); #define ARG_DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \ int fnname(arg_hashtable_itr_t* i, arg_hashtable_t* h, keytype* k) { return (arg_hashtable_iterator_search(i, h, k)); } #ifdef __cplusplus } #endif #endif /******************************************************************************* * arg_utils: Implements memory, panic, and other utility functions * * This file is part of the argtable3 library. * * Copyright (C) 2013-2019 Tom G. Huang * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #include #include static void panic(const char* fmt, ...); static arg_panicfn* s_panic = panic; void dbg_printf(const char* fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } static void panic(const char* fmt, ...) { va_list args; char* s; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4996) #endif s = getenv("EF_DUMPCORE"); #if defined(_MSC_VER) #pragma warning(pop) #endif if (s != NULL && *s != '\0') { abort(); } else { exit(EXIT_FAILURE); } } void arg_set_panic(arg_panicfn* proc) { s_panic = proc; } void* xmalloc(size_t size) { void* ret = malloc(size); if (!ret) { s_panic("Out of memory!\n"); } return ret; } void* xcalloc(size_t count, size_t size) { size_t allocated_count = count && size ? count : 1; size_t allocated_size = count && size ? size : 1; void* ret = calloc(allocated_count, allocated_size); if (!ret) { s_panic("Out of memory!\n"); } return ret; } void* xrealloc(void* ptr, size_t size) { size_t allocated_size = size ? size : 1; void* ret = realloc(ptr, allocated_size); if (!ret) { s_panic("Out of memory!\n"); } return ret; } void xfree(void* ptr) { free(ptr); } static void merge(void* data, int esize, int i, int j, int k, arg_comparefn* comparefn) { char* a = (char*)data; char* m; int ipos, jpos, mpos; /* Initialize the counters used in merging. */ ipos = i; jpos = j + 1; mpos = 0; /* Allocate storage for the merged elements. */ m = (char*)xmalloc(esize * ((k - i) + 1)); /* Continue while either division has elements to merge. */ while (ipos <= j || jpos <= k) { if (ipos > j) { /* The left division has no more elements to merge. */ while (jpos <= k) { memcpy(&m[mpos * esize], &a[jpos * esize], esize); jpos++; mpos++; } continue; } else if (jpos > k) { /* The right division has no more elements to merge. */ while (ipos <= j) { memcpy(&m[mpos * esize], &a[ipos * esize], esize); ipos++; mpos++; } continue; } /* Append the next ordered element to the merged elements. */ if (comparefn(&a[ipos * esize], &a[jpos * esize]) < 0) { memcpy(&m[mpos * esize], &a[ipos * esize], esize); ipos++; mpos++; } else { memcpy(&m[mpos * esize], &a[jpos * esize], esize); jpos++; mpos++; } } /* Prepare to pass back the merged data. */ memcpy(&a[i * esize], m, esize * ((k - i) + 1)); xfree(m); } void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn) { int j; /* Stop the recursion when no more divisions can be made. */ if (i < k) { /* Determine where to divide the elements. */ j = (int)(((i + k - 1)) / 2); /* Recursively sort the two divisions. */ arg_mgsort(data, size, esize, i, j, comparefn); arg_mgsort(data, size, esize, j + 1, k, comparefn); merge(data, esize, i, j, k, comparefn); } } /******************************************************************************* * arg_hashtable: Implements the hash table utilities * * This file is part of the argtable3 library. * * Copyright (C) 2013-2019 Tom G. Huang * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #include #include /* * This hash table module is adapted from the C hash table implementation by * Christopher Clark. Here is the copyright notice from the library: * * Copyright (c) 2002, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Credit for primes table: Aaron Krowne * http://br.endernet.org/~akrowne/ * http://planetmath.org/encyclopedia/GoodHashTablePrimes.html */ static const unsigned int primes[] = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741}; const unsigned int prime_table_length = sizeof(primes) / sizeof(primes[0]); const float max_load_factor = (float)0.65; static unsigned int enhanced_hash(arg_hashtable_t* h, const void* k) { /* * Aim to protect against poor hash functions by adding logic here. * The logic is taken from Java 1.4 hash table source. */ unsigned int i = h->hashfn(k); i += ~(i << 9); i ^= ((i >> 14) | (i << 18)); /* >>> */ i += (i << 4); i ^= ((i >> 10) | (i << 22)); /* >>> */ return i; } static unsigned int index_for(unsigned int tablelength, unsigned int hashvalue) { return (hashvalue % tablelength); } arg_hashtable_t* arg_hashtable_create(unsigned int minsize, unsigned int (*hashfn)(const void*), int (*eqfn)(const void*, const void*)) { arg_hashtable_t* h; unsigned int pindex; unsigned int size = primes[0]; /* Check requested hash table isn't too large */ if (minsize > (1u << 30)) return NULL; /* * Enforce size as prime. The reason is to avoid clustering of values * into a small number of buckets (yes, distribution). A more even * distributed hash table will perform more consistently. */ for (pindex = 0; pindex < prime_table_length; pindex++) { if (primes[pindex] > minsize) { size = primes[pindex]; break; } } h = (arg_hashtable_t*)xmalloc(sizeof(arg_hashtable_t)); h->table = (struct arg_hashtable_entry**)xmalloc(sizeof(struct arg_hashtable_entry*) * size); memset(h->table, 0, size * sizeof(struct arg_hashtable_entry*)); h->tablelength = size; h->primeindex = pindex; h->entrycount = 0; h->hashfn = hashfn; h->eqfn = eqfn; h->loadlimit = (unsigned int)ceil(size * max_load_factor); return h; } static int arg_hashtable_expand(arg_hashtable_t* h) { /* Double the size of the table to accommodate more entries */ struct arg_hashtable_entry** newtable; struct arg_hashtable_entry* e; unsigned int newsize; unsigned int i; unsigned int index; /* Check we're not hitting max capacity */ if (h->primeindex == (prime_table_length - 1)) return 0; newsize = primes[++(h->primeindex)]; newtable = (struct arg_hashtable_entry**)xmalloc(sizeof(struct arg_hashtable_entry*) * newsize); memset(newtable, 0, newsize * sizeof(struct arg_hashtable_entry*)); /* * This algorithm is not 'stable': it reverses the list * when it transfers entries between the tables */ for (i = 0; i < h->tablelength; i++) { while (NULL != (e = h->table[i])) { h->table[i] = e->next; index = index_for(newsize, e->h); e->next = newtable[index]; newtable[index] = e; } } xfree(h->table); h->table = newtable; h->tablelength = newsize; h->loadlimit = (unsigned int)ceil(newsize * max_load_factor); return -1; } unsigned int arg_hashtable_count(arg_hashtable_t* h) { return h->entrycount; } void arg_hashtable_insert(arg_hashtable_t* h, void* k, void* v) { /* This method allows duplicate keys - but they shouldn't be used */ unsigned int index; struct arg_hashtable_entry* e; if ((h->entrycount + 1) > h->loadlimit) { /* * Ignore the return value. If expand fails, we should * still try cramming just this value into the existing table * -- we may not have memory for a larger table, but one more * element may be ok. Next time we insert, we'll try expanding again. */ arg_hashtable_expand(h); } e = (struct arg_hashtable_entry*)xmalloc(sizeof(struct arg_hashtable_entry)); e->h = enhanced_hash(h, k); index = index_for(h->tablelength, e->h); e->k = k; e->v = v; e->next = h->table[index]; h->table[index] = e; h->entrycount++; } void* arg_hashtable_search(arg_hashtable_t* h, const void* k) { struct arg_hashtable_entry* e; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); e = h->table[index]; while (e != NULL) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v; e = e->next; } return NULL; } void arg_hashtable_remove(arg_hashtable_t* h, const void* k) { /* * TODO: consider compacting the table when the load factor drops enough, * or provide a 'compact' method. */ struct arg_hashtable_entry* e; struct arg_hashtable_entry** pE; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); pE = &(h->table[index]); e = *pE; while (NULL != e) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { *pE = e->next; h->entrycount--; xfree(e->k); xfree(e->v); xfree(e); return; } pE = &(e->next); e = e->next; } } void arg_hashtable_destroy(arg_hashtable_t* h, int free_values) { unsigned int i; struct arg_hashtable_entry *e, *f; struct arg_hashtable_entry** table = h->table; if (free_values) { for (i = 0; i < h->tablelength; i++) { e = table[i]; while (NULL != e) { f = e; e = e->next; xfree(f->k); xfree(f->v); xfree(f); } } } else { for (i = 0; i < h->tablelength; i++) { e = table[i]; while (NULL != e) { f = e; e = e->next; xfree(f->k); xfree(f); } } } xfree(h->table); xfree(h); } arg_hashtable_itr_t* arg_hashtable_itr_create(arg_hashtable_t* h) { unsigned int i; unsigned int tablelength; arg_hashtable_itr_t* itr = (arg_hashtable_itr_t*)xmalloc(sizeof(arg_hashtable_itr_t)); itr->h = h; itr->e = NULL; itr->parent = NULL; tablelength = h->tablelength; itr->index = tablelength; if (0 == h->entrycount) return itr; for (i = 0; i < tablelength; i++) { if (h->table[i] != NULL) { itr->e = h->table[i]; itr->index = i; break; } } return itr; } void arg_hashtable_itr_destroy(arg_hashtable_itr_t* itr) { xfree(itr); } void* arg_hashtable_itr_key(arg_hashtable_itr_t* i) { return i->e->k; } void* arg_hashtable_itr_value(arg_hashtable_itr_t* i) { return i->e->v; } int arg_hashtable_itr_advance(arg_hashtable_itr_t* itr) { unsigned int j; unsigned int tablelength; struct arg_hashtable_entry** table; struct arg_hashtable_entry* next; if (itr->e == NULL) return 0; /* stupidity check */ next = itr->e->next; if (NULL != next) { itr->parent = itr->e; itr->e = next; return -1; } tablelength = itr->h->tablelength; itr->parent = NULL; if (tablelength <= (j = ++(itr->index))) { itr->e = NULL; return 0; } table = itr->h->table; while (NULL == (next = table[j])) { if (++j >= tablelength) { itr->index = tablelength; itr->e = NULL; return 0; } } itr->index = j; itr->e = next; return -1; } int arg_hashtable_itr_remove(arg_hashtable_itr_t* itr) { struct arg_hashtable_entry* remember_e; struct arg_hashtable_entry* remember_parent; int ret; /* Do the removal */ if ((itr->parent) == NULL) { /* element is head of a chain */ itr->h->table[itr->index] = itr->e->next; } else { /* element is mid-chain */ itr->parent->next = itr->e->next; } /* itr->e is now outside the hashtable */ remember_e = itr->e; itr->h->entrycount--; xfree(remember_e->k); xfree(remember_e->v); /* Advance the iterator, correcting the parent */ remember_parent = itr->parent; ret = arg_hashtable_itr_advance(itr); if (itr->parent == remember_e) { itr->parent = remember_parent; } xfree(remember_e); return ret; } int arg_hashtable_itr_search(arg_hashtable_itr_t* itr, arg_hashtable_t* h, void* k) { struct arg_hashtable_entry* e; struct arg_hashtable_entry* parent; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); e = h->table[index]; parent = NULL; while (e != NULL) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { itr->index = index; itr->e = e; itr->parent = parent; itr->h = h; return -1; } parent = e; e = e->next; } return 0; } int arg_hashtable_change(arg_hashtable_t* h, void* k, void* v) { struct arg_hashtable_entry* e; unsigned int hashvalue; unsigned int index; hashvalue = enhanced_hash(h, k); index = index_for(h->tablelength, hashvalue); e = h->table[index]; while (e != NULL) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { xfree(e->v); e->v = v; return -1; } e = e->next; } return 0; } /******************************************************************************* * arg_dstr: Implements the dynamic string utilities * * This file is part of the argtable3 library. * * Copyright (C) 2013-2019 Tom G. Huang * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #include #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4996) #endif #define START_VSNBUFF 16 /* * This dynamic string module is adapted from TclResult.c in the Tcl library. * Here is the copyright notice from the library: * * This software is copyrighted by the Regents of the University of * California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState * Corporation and other parties. The following terms apply to all files * associated with the software unless explicitly disclaimed in * individual files. * * The authors hereby grant permission to use, copy, modify, distribute, * and license this software and its documentation for any purpose, provided * that existing copyright notices are retained in all copies and that this * notice is included verbatim in any distributions. No written agreement, * license, or royalty fee is required for any of the authorized uses. * Modifications to this software may be copyrighted by their authors * and need not follow the licensing terms described here, provided that * the new terms are clearly indicated on the first page of each file where * they apply. * * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY * DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE * IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE * NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. * * GOVERNMENT USE: If you are acquiring this software on behalf of the * U.S. government, the Government shall have only "Restricted Rights" * in the software and related documentation as defined in the Federal * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you * are acquiring the software on behalf of the Department of Defense, the * software shall be classified as "Commercial Computer Software" and the * Government shall have only "Restricted Rights" as defined in Clause * 252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the * authors grant the U.S. Government and others acting in its behalf * permission to use and distribute the software in accordance with the * terms specified in this license. */ typedef struct _internal_arg_dstr { char* data; arg_dstr_freefn* free_proc; char sbuf[ARG_DSTR_SIZE + 1]; char* append_data; int append_data_size; int append_used; } _internal_arg_dstr_t; static void setup_append_buf(arg_dstr_t res, int newSpace); arg_dstr_t arg_dstr_create(void) { _internal_arg_dstr_t* h = (_internal_arg_dstr_t*)xmalloc(sizeof(_internal_arg_dstr_t)); memset(h, 0, sizeof(_internal_arg_dstr_t)); h->sbuf[0] = 0; h->data = h->sbuf; h->free_proc = ARG_DSTR_STATIC; return h; } void arg_dstr_destroy(arg_dstr_t ds) { if (ds == NULL) return; arg_dstr_reset(ds); xfree(ds); return; } void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc) { int length; register arg_dstr_freefn* old_free_proc = ds->free_proc; char* old_result = ds->data; if (str == NULL) { ds->sbuf[0] = 0; ds->data = ds->sbuf; ds->free_proc = ARG_DSTR_STATIC; } else if (free_proc == ARG_DSTR_VOLATILE) { length = (int)strlen(str); if (length > ARG_DSTR_SIZE) { ds->data = (char*)xmalloc((unsigned)length + 1); ds->free_proc = ARG_DSTR_DYNAMIC; } else { ds->data = ds->sbuf; ds->free_proc = ARG_DSTR_STATIC; } strcpy(ds->data, str); } else { ds->data = str; ds->free_proc = free_proc; } /* * If the old result was dynamically-allocated, free it up. Do it here, * rather than at the beginning, in case the new result value was part of * the old result value. */ if ((old_free_proc != 0) && (old_result != ds->data)) { if (old_free_proc == ARG_DSTR_DYNAMIC) { xfree(old_result); } else { (*old_free_proc)(old_result); } } if ((ds->append_data != NULL) && (ds->append_data_size > 0)) { xfree(ds->append_data); ds->append_data = NULL; ds->append_data_size = 0; } } char* arg_dstr_cstr(arg_dstr_t ds) /* Interpreter whose result to return. */ { return ds->data; } void arg_dstr_cat(arg_dstr_t ds, const char* str) { setup_append_buf(ds, (int)strlen(str) + 1); memcpy(ds->data + strlen(ds->data), str, strlen(str)); } void arg_dstr_catc(arg_dstr_t ds, char c) { setup_append_buf(ds, 2); memcpy(ds->data + strlen(ds->data), &c, 1); } /* * The logic of the `arg_dstr_catf` function is adapted from the `bformat` * function in The Better String Library by Paul Hsieh. Here is the copyright * notice from the library: * * Copyright (c) 2014, Paul Hsieh * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of bstrlib nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...) { va_list arglist; char* buff; int n, r; size_t slen; if (fmt == NULL) return; /* Since the length is not determinable beforehand, a search is performed using the truncating "vsnprintf" call (to avoid buffer overflows) on increasing potential sizes for the output result. */ if ((n = (int)(2 * strlen(fmt))) < START_VSNBUFF) n = START_VSNBUFF; buff = (char*)xmalloc(n + 2); memset(buff, 0, n + 2); for (;;) { va_start(arglist, fmt); r = vsnprintf(buff, n + 1, fmt, arglist); va_end(arglist); slen = strlen(buff); if (slen < (size_t)n) break; if (r > n) n = r; else n += n; xfree(buff); buff = (char*)xmalloc(n + 2); memset(buff, 0, n + 2); } arg_dstr_cat(ds, buff); xfree(buff); } static void setup_append_buf(arg_dstr_t ds, int new_space) { int total_space; /* * Make the append buffer larger, if that's necessary, then copy the * data into the append buffer and make the append buffer the official * data. */ if (ds->data != ds->append_data) { /* * If the buffer is too big, then free it up so we go back to a * smaller buffer. This avoids tying up memory forever after a large * operation. */ if (ds->append_data_size > 500) { xfree(ds->append_data); ds->append_data = NULL; ds->append_data_size = 0; } ds->append_used = (int)strlen(ds->data); } else if (ds->data[ds->append_used] != 0) { /* * Most likely someone has modified a result created by * arg_dstr_cat et al. so that it has a different size. Just * recompute the size. */ ds->append_used = (int)strlen(ds->data); } total_space = new_space + ds->append_used; if (total_space >= ds->append_data_size) { char* newbuf; if (total_space < 100) { total_space = 200; } else { total_space *= 2; } newbuf = (char*)xmalloc((unsigned)total_space); memset(newbuf, 0, total_space); strcpy(newbuf, ds->data); if (ds->append_data != NULL) { xfree(ds->append_data); } ds->append_data = newbuf; ds->append_data_size = total_space; } else if (ds->data != ds->append_data) { strcpy(ds->append_data, ds->data); } arg_dstr_free(ds); ds->data = ds->append_data; } void arg_dstr_free(arg_dstr_t ds) { if (ds->free_proc != NULL) { if (ds->free_proc == ARG_DSTR_DYNAMIC) { xfree(ds->data); } else { (*ds->free_proc)(ds->data); } ds->free_proc = NULL; } } void arg_dstr_reset(arg_dstr_t ds) { arg_dstr_free(ds); if ((ds->append_data != NULL) && (ds->append_data_size > 0)) { xfree(ds->append_data); ds->append_data = NULL; ds->append_data_size = 0; } ds->data = ds->sbuf; ds->sbuf[0] = 0; } #if defined(_MSC_VER) #pragma warning(pop) #endif /* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #if ARG_REPLACE_GETOPT == 1 #ifndef _GETOPT_H_ #define _GETOPT_H_ /* * GNU-like getopt_long()/getopt_long_only() with 4.4BSD optreset extension. * getopt() is declared here too for GNU programs. */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 struct option { /* name of long option */ const char *name; /* * one of no_argument, required_argument, and optional_argument: * whether option takes an argument */ int has_arg; /* if not NULL, set *flag to val when option found */ int *flag; /* if flag not NULL, value to set *flag to; else return value */ int val; }; #ifdef __cplusplus extern "C" { #endif int getopt_long(int, char * const *, const char *, const struct option *, int *); int getopt_long_only(int, char * const *, const char *, const struct option *, int *); #ifndef _GETOPT_DECLARED #define _GETOPT_DECLARED int getopt(int, char * const [], const char *); extern char *optarg; /* getopt(3) external variables */ extern int optind, opterr, optopt; #endif #ifndef _OPTRESET_DECLARED #define _OPTRESET_DECLARED extern int optreset; /* getopt(3) external variable */ #endif #ifdef __cplusplus } #endif #endif /* !_GETOPT_H_ */ #endif /* ARG_REPLACE_GETOPT == 1 */ /* $OpenBSD: getopt_long.c,v 1.26 2013/06/08 22:47:56 millert Exp $ */ /* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ /* * Copyright (c) 2002 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "argtable3.h" #if ARG_REPLACE_GETOPT == 1 #ifndef ARG_AMALGAMATION #include "arg_getopt.h" #endif #include #include #include #define GNU_COMPATIBLE /* Be more compatible, configure's use us! */ int opterr = 1; /* if error message should be printed */ int optind = 1; /* index into parent argv vector */ int optopt = '?'; /* character checked for validity */ int optreset; /* reset getopt */ char *optarg; /* argument associated with option */ #define PRINT_ERROR ((opterr) && (*options != ':')) #define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ #define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ #define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ /* return values */ #define BADCH (int)'?' #define BADARG ((*options == ':') ? (int)':' : (int)'?') #define INORDER (int)1 #define EMSG "" #ifdef GNU_COMPATIBLE #define NO_PREFIX (-1) #define D_PREFIX 0 #define DD_PREFIX 1 #define W_PREFIX 2 #endif static int getopt_internal(int, char * const *, const char *, const struct option *, int *, int); static int parse_long_options(char * const *, const char *, const struct option *, int *, int, int); static int gcd(int, int); static void permute_args(int, int, int, char * const *); static char *place = EMSG; /* option letter processing */ /* XXX: set optreset to 1 rather than these two */ static int nonopt_start = -1; /* first non option argument (for permute) */ static int nonopt_end = -1; /* first option after non options (for permute) */ /* Error messages */ static const char recargchar[] = "option requires an argument -- %c"; static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */ #ifdef GNU_COMPATIBLE static int dash_prefix = NO_PREFIX; static const char gnuoptchar[] = "invalid option -- %c"; static const char recargstring[] = "option `%s%s' requires an argument"; static const char ambig[] = "option `%s%.*s' is ambiguous"; static const char noarg[] = "option `%s%.*s' doesn't allow an argument"; static const char illoptstring[] = "unrecognized option `%s%s'"; #else static const char recargstring[] = "option requires an argument -- %s"; static const char ambig[] = "ambiguous option -- %.*s"; static const char noarg[] = "option doesn't take an argument -- %.*s"; static const char illoptstring[] = "unknown option -- %s"; #endif #ifdef _WIN32 /* * Windows needs warnx(). We change the definition though: * 1. (another) global is defined, opterrmsg, which holds the error message * 2. errors are always printed out on stderr w/o the program name * Note that opterrmsg always gets set no matter what opterr is set to. The * error message will not be printed if opterr is 0 as usual. */ #include #include #define MAX_OPTERRMSG_SIZE 128 extern char opterrmsg[MAX_OPTERRMSG_SIZE]; char opterrmsg[MAX_OPTERRMSG_SIZE]; /* buffer for the last error message */ static void warnx(const char* fmt, ...) { va_list ap; va_start(ap, fmt); /* * Make sure opterrmsg is always zero-terminated despite the _vsnprintf() * implementation specifics and manually suppress the warning. */ memset(opterrmsg, 0, sizeof(opterrmsg)); if (fmt != NULL) #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) _vsnprintf_s(opterrmsg, sizeof(opterrmsg), sizeof(opterrmsg) - 1, fmt, ap); #else _vsnprintf(opterrmsg, sizeof(opterrmsg) - 1, fmt, ap); #endif va_end(ap); #ifdef _MSC_VER #pragma warning(suppress : 6053) #endif fprintf(stderr, "%s\n", opterrmsg); } #else #include #endif /*_WIN32*/ /* * Compute the greatest common divisor of a and b. */ static int gcd(int a, int b) { int c; c = a % b; while (c != 0) { a = b; b = c; c = a % b; } return (b); } /* * Exchange the block from nonopt_start to nonopt_end with the block * from nonopt_end to opt_end (keeping the same order of arguments * in each block). */ static void permute_args(int panonopt_start, int panonopt_end, int opt_end, char * const *nargv) { int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; char *swap; /* * compute lengths of blocks and number and size of cycles */ nnonopts = panonopt_end - panonopt_start; nopts = opt_end - panonopt_end; ncycle = gcd(nnonopts, nopts); cyclelen = (opt_end - panonopt_start) / ncycle; for (i = 0; i < ncycle; i++) { cstart = panonopt_end+i; pos = cstart; for (j = 0; j < cyclelen; j++) { if (pos >= panonopt_end) pos -= nnonopts; else pos += nopts; swap = nargv[pos]; /* LINTED const cast */ ((char **) nargv)[pos] = nargv[cstart]; /* LINTED const cast */ ((char **)nargv)[cstart] = swap; } } } /* * parse_long_options -- * Parse long options in argc/argv argument vector. * Returns -1 if short_too is set and the option does not match long_options. */ static int parse_long_options(char * const *nargv, const char *options, const struct option *long_options, int *idx, int short_too, int flags) { char *current_argv, *has_equal; #ifdef GNU_COMPATIBLE char *current_dash; #endif size_t current_argv_len; int i, match, exact_match, second_partial_match; current_argv = place; #ifdef GNU_COMPATIBLE switch (dash_prefix) { case D_PREFIX: current_dash = "-"; break; case DD_PREFIX: current_dash = "--"; break; case W_PREFIX: current_dash = "-W "; break; default: current_dash = ""; break; } #endif match = -1; exact_match = 0; second_partial_match = 0; optind++; if ((has_equal = strchr(current_argv, '=')) != NULL) { /* argument found (--option=arg) */ current_argv_len = has_equal - current_argv; has_equal++; } else current_argv_len = strlen(current_argv); for (i = 0; long_options[i].name; i++) { /* find matching long option */ if (strncmp(current_argv, long_options[i].name, current_argv_len)) continue; if (strlen(long_options[i].name) == current_argv_len) { /* exact match */ match = i; exact_match = 1; break; } /* * If this is a known short option, don't allow * a partial match of a single character. */ if (short_too && current_argv_len == 1) continue; if (match == -1) /* first partial match */ match = i; else if ((flags & FLAG_LONGONLY) || long_options[i].has_arg != long_options[match].has_arg || long_options[i].flag != long_options[match].flag || long_options[i].val != long_options[match].val) second_partial_match = 1; } if (!exact_match && second_partial_match) { /* ambiguous abbreviation */ if (PRINT_ERROR) warnx(ambig, #ifdef GNU_COMPATIBLE current_dash, #endif (int)current_argv_len, current_argv); optopt = 0; return (BADCH); } if (match != -1) { /* option found */ if (long_options[match].has_arg == no_argument && has_equal) { if (PRINT_ERROR) warnx(noarg, #ifdef GNU_COMPATIBLE current_dash, #endif (int)current_argv_len, current_argv); /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) optopt = long_options[match].val; else optopt = 0; #ifdef GNU_COMPATIBLE return (BADCH); #else return (BADARG); #endif } if (long_options[match].has_arg == required_argument || long_options[match].has_arg == optional_argument) { if (has_equal) optarg = has_equal; else if (long_options[match].has_arg == required_argument) { /* * optional argument doesn't use next nargv */ optarg = nargv[optind++]; } } if ((long_options[match].has_arg == required_argument) && (optarg == NULL)) { /* * Missing argument; leading ':' indicates no error * should be generated. */ if (PRINT_ERROR) warnx(recargstring, #ifdef GNU_COMPATIBLE current_dash, #endif current_argv); /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) optopt = long_options[match].val; else optopt = 0; --optind; return (BADARG); } } else { /* unknown option */ if (short_too) { --optind; return (-1); } if (PRINT_ERROR) warnx(illoptstring, #ifdef GNU_COMPATIBLE current_dash, #endif current_argv); optopt = 0; return (BADCH); } if (idx) *idx = match; if (long_options[match].flag) { *long_options[match].flag = long_options[match].val; return (0); } else return (long_options[match].val); } /* * getopt_internal -- * Parse argc/argv argument vector. Called by user level routines. */ static int getopt_internal(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx, int flags) { char *oli; /* option letter list index */ int optchar, short_too; static int posixly_correct = -1; if (options == NULL) return (-1); /* * XXX Some GNU programs (like cvs) set optind to 0 instead of * XXX using optreset. Work around this braindamage. */ if (optind == 0) optind = optreset = 1; /* * Disable GNU extensions if POSIXLY_CORRECT is set or options * string begins with a '+'. */ if (posixly_correct == -1 || optreset) { #if defined(_WIN32) && ((defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__))) size_t requiredSize; getenv_s(&requiredSize, NULL, 0, "POSIXLY_CORRECT"); posixly_correct = requiredSize != 0; #else posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); #endif } if (*options == '-') flags |= FLAG_ALLARGS; else if (posixly_correct || *options == '+') flags &= ~FLAG_PERMUTE; if (*options == '+' || *options == '-') options++; optarg = NULL; if (optreset) nonopt_start = nonopt_end = -1; start: if (optreset || !*place) { /* update scanning pointer */ optreset = 0; if (optind >= nargc) { /* end of argument vector */ place = EMSG; if (nonopt_end != -1) { /* do permutation, if we have to */ permute_args(nonopt_start, nonopt_end, optind, nargv); optind -= nonopt_end - nonopt_start; } else if (nonopt_start != -1) { /* * If we skipped non-options, set optind * to the first of them. */ optind = nonopt_start; } nonopt_start = nonopt_end = -1; return (-1); } if (*(place = nargv[optind]) != '-' || #ifdef GNU_COMPATIBLE place[1] == '\0') { #else (place[1] == '\0' && strchr(options, '-') == NULL)) { #endif place = EMSG; /* found non-option */ if (flags & FLAG_ALLARGS) { /* * GNU extension: * return non-option as argument to option 1 */ optarg = nargv[optind++]; return (INORDER); } if (!(flags & FLAG_PERMUTE)) { /* * If no permutation wanted, stop parsing * at first non-option. */ return (-1); } /* do permutation */ if (nonopt_start == -1) nonopt_start = optind; else if (nonopt_end != -1) { permute_args(nonopt_start, nonopt_end, optind, nargv); nonopt_start = optind - (nonopt_end - nonopt_start); nonopt_end = -1; } optind++; /* process next argument */ goto start; } if (nonopt_start != -1 && nonopt_end == -1) nonopt_end = optind; /* * If we have "-" do nothing, if "--" we are done. */ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { optind++; place = EMSG; /* * We found an option (--), so if we skipped * non-options, we have to permute. */ if (nonopt_end != -1) { permute_args(nonopt_start, nonopt_end, optind, nargv); optind -= nonopt_end - nonopt_start; } nonopt_start = nonopt_end = -1; return (-1); } } /* * Check long options if: * 1) we were passed some * 2) the arg is not just "-" * 3) either the arg starts with -- we are getopt_long_only() */ if (long_options != NULL && place != nargv[optind] && (*place == '-' || (flags & FLAG_LONGONLY))) { short_too = 0; #ifdef GNU_COMPATIBLE dash_prefix = D_PREFIX; #endif if (*place == '-') { place++; /* --foo long option */ if (*place == '\0') return (BADARG); /* malformed option */ #ifdef GNU_COMPATIBLE dash_prefix = DD_PREFIX; #endif } else if (*place != ':' && strchr(options, *place) != NULL) short_too = 1; /* could be short option too */ optchar = parse_long_options(nargv, options, long_options, idx, short_too, flags); if (optchar != -1) { place = EMSG; return (optchar); } } if ((optchar = (int)*place++) == (int)':' || (optchar == (int)'-' && *place != '\0') || (oli = strchr(options, optchar)) == NULL) { /* * If the user specified "-" and '-' isn't listed in * options, return -1 (non-option) as per POSIX. * Otherwise, it is an unknown option character (or ':'). */ if (optchar == (int)'-' && *place == '\0') return (-1); if (!*place) ++optind; #ifdef GNU_COMPATIBLE if (PRINT_ERROR) warnx(posixly_correct ? illoptchar : gnuoptchar, optchar); #else if (PRINT_ERROR) warnx(illoptchar, optchar); #endif optopt = optchar; return (BADCH); } if (long_options != NULL && optchar == 'W' && oli[1] == ';') { /* -W long-option */ if (*place) /* no space */ /* NOTHING */; else if (++optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) warnx(recargchar, optchar); optopt = optchar; return (BADARG); } else /* white space */ place = nargv[optind]; #ifdef GNU_COMPATIBLE dash_prefix = W_PREFIX; #endif optchar = parse_long_options(nargv, options, long_options, idx, 0, flags); place = EMSG; return (optchar); } if (*++oli != ':') { /* doesn't take argument */ if (!*place) ++optind; } else { /* takes (optional) argument */ optarg = NULL; if (*place) /* no white space */ optarg = place; else if (oli[1] != ':') { /* arg not optional */ if (++optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) warnx(recargchar, optchar); optopt = optchar; return (BADARG); } else optarg = nargv[optind]; } place = EMSG; ++optind; } /* dump back option letter */ return (optchar); } /* * getopt -- * Parse argc/argv argument vector. * * [eventually this will replace the BSD getopt] */ int getopt(int nargc, char * const *nargv, const char *options) { /* * We don't pass FLAG_PERMUTE to getopt_internal() since * the BSD getopt(3) (unlike GNU) has never done this. * * Furthermore, since many privileged programs call getopt() * before dropping privileges it makes sense to keep things * as simple (and bug-free) as possible. */ return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); } /* * getopt_long -- * Parse argc/argv argument vector. */ int getopt_long(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx) { return (getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE)); } /* * getopt_long_only -- * Parse argc/argv argument vector. */ int getopt_long_only(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx) { return (getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE|FLAG_LONGONLY)); } #endif /* ARG_REPLACE_GETOPT == 1 */ /******************************************************************************* * arg_date: Implements the date command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include char* arg_strptime(const char* buf, const char* fmt, struct tm* tm); static void arg_date_resetfn(struct arg_date* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } static int arg_date_scanfn(struct arg_date* parent, const char* argval) { int errorcode = 0; if (parent->count == parent->hdr.maxcount) { errorcode = ARG_ERR_MAXCOUNT; } else if (!argval) { /* no argument value was given, leave parent->tmval[] unaltered but still count it */ parent->count++; } else { const char* pend; struct tm tm = parent->tmval[parent->count]; /* parse the given argument value, store result in parent->tmval[] */ pend = arg_strptime(argval, parent->format, &tm); if (pend && pend[0] == '\0') parent->tmval[parent->count++] = tm; else errorcode = ARG_ERR_BADDATE; } ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static int arg_date_checkfn(struct arg_date* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static void arg_date_errorfn(struct arg_date* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; /* make argval NULL safe */ argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_cat(ds, "missing option "); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_cat(ds, "excess option "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; case ARG_ERR_BADDATE: { struct tm tm; char buff[200]; arg_dstr_catf(ds, "illegal timestamp format \"%s\"\n", argval); memset(&tm, 0, sizeof(tm)); arg_strptime("1999-12-31 23:59:59", "%F %H:%M:%S", &tm); strftime(buff, sizeof(buff), parent->format, &tm); arg_dstr_catf(ds, "correct format is \"%s\"\n", buff); break; } } } struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary) { return arg_daten(shortopts, longopts, format, datatype, 0, 1, glossary); } struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary) { return arg_daten(shortopts, longopts, format, datatype, 1, 1, glossary); } struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary) { size_t nbytes; struct arg_date* result; /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; /* default time format is the national date format for the locale */ if (!format) format = "%x"; nbytes = sizeof(struct arg_date) /* storage for struct arg_date */ + maxcount * sizeof(struct tm); /* storage for tmval[maxcount] array */ /* allocate storage for the arg_date struct + tmval[] array. */ /* we use calloc because we want the tmval[] array zero filled. */ result = (struct arg_date*)xcalloc(1, nbytes); /* init the arg_hdr struct */ result->hdr.flag = ARG_HASVALUE; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.datatype = datatype ? datatype : format; result->hdr.glossary = glossary; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_date_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_date_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_date_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_date_errorfn; /* store the tmval[maxcount] array immediately after the arg_date struct */ result->tmval = (struct tm*)(result + 1); /* init the remaining arg_date member variables */ result->count = 0; result->format = format; ARG_TRACE(("arg_daten() returns %p\n", result)); return result; } /*- * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc. * All rights reserved. * * This code was contributed to The NetBSD Foundation by Klaus Klein. * Heavily optimised by David Laight * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include /* * We do not implement alternate representations. However, we always * check whether a given modifier is allowed for a certain conversion. */ #define ALT_E 0x01 #define ALT_O 0x02 #define LEGAL_ALT(x) \ { \ if (alt_format & ~(x)) \ return (0); \ } #define TM_YEAR_BASE (1900) static int conv_num(const char**, int*, int, int); static const char* day[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; static const char* abday[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char* mon[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; static const char* abmon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static const char* am_pm[2] = {"AM", "PM"}; static int arg_strcasecmp(const char* s1, const char* s2) { const unsigned char* us1 = (const unsigned char*)s1; const unsigned char* us2 = (const unsigned char*)s2; while (tolower(*us1) == tolower(*us2++)) if (*us1++ == '\0') return 0; return tolower(*us1) - tolower(*--us2); } static int arg_strncasecmp(const char* s1, const char* s2, size_t n) { if (n != 0) { const unsigned char* us1 = (const unsigned char*)s1; const unsigned char* us2 = (const unsigned char*)s2; do { if (tolower(*us1) != tolower(*us2++)) return tolower(*us1) - tolower(*--us2); if (*us1++ == '\0') break; } while (--n != 0); } return 0; } char* arg_strptime(const char* buf, const char* fmt, struct tm* tm) { char c; const char* bp; size_t len = 0; int alt_format, i, split_year = 0; bp = buf; while ((c = *fmt) != '\0') { /* Clear `alternate' modifier prior to new conversion. */ alt_format = 0; /* Eat up white-space. */ if (isspace(c)) { while (isspace((int)(*bp))) bp++; fmt++; continue; } if ((c = *fmt++) != '%') goto literal; again: switch (c = *fmt++) { case '%': /* "%%" is converted to "%". */ literal: if (c != *bp++) return (0); break; /* * "Alternative" modifiers. Just set the appropriate flag * and start over again. */ case 'E': /* "%E?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_E; goto again; case 'O': /* "%O?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_O; goto again; /* * "Complex" conversion rules, implemented through recursion. */ case 'c': /* Date and time, using the locale's format. */ LEGAL_ALT(ALT_E); bp = arg_strptime(bp, "%x %X", tm); if (!bp) return (0); break; case 'D': /* The date as "%m/%d/%y". */ LEGAL_ALT(0); bp = arg_strptime(bp, "%m/%d/%y", tm); if (!bp) return (0); break; case 'R': /* The time as "%H:%M". */ LEGAL_ALT(0); bp = arg_strptime(bp, "%H:%M", tm); if (!bp) return (0); break; case 'r': /* The time in 12-hour clock representation. */ LEGAL_ALT(0); bp = arg_strptime(bp, "%I:%M:%S %p", tm); if (!bp) return (0); break; case 'T': /* The time as "%H:%M:%S". */ LEGAL_ALT(0); bp = arg_strptime(bp, "%H:%M:%S", tm); if (!bp) return (0); break; case 'X': /* The time, using the locale's format. */ LEGAL_ALT(ALT_E); bp = arg_strptime(bp, "%H:%M:%S", tm); if (!bp) return (0); break; case 'x': /* The date, using the locale's format. */ LEGAL_ALT(ALT_E); bp = arg_strptime(bp, "%m/%d/%y", tm); if (!bp) return (0); break; /* * "Elementary" conversion rules. */ case 'A': /* The day of week, using the locale's form. */ case 'a': LEGAL_ALT(0); for (i = 0; i < 7; i++) { /* Full name. */ len = strlen(day[i]); if (arg_strncasecmp(day[i], bp, len) == 0) break; /* Abbreviated name. */ len = strlen(abday[i]); if (arg_strncasecmp(abday[i], bp, len) == 0) break; } /* Nothing matched. */ if (i == 7) return (0); tm->tm_wday = i; bp += len; break; case 'B': /* The month, using the locale's form. */ case 'b': case 'h': LEGAL_ALT(0); for (i = 0; i < 12; i++) { /* Full name. */ len = strlen(mon[i]); if (arg_strncasecmp(mon[i], bp, len) == 0) break; /* Abbreviated name. */ len = strlen(abmon[i]); if (arg_strncasecmp(abmon[i], bp, len) == 0) break; } /* Nothing matched. */ if (i == 12) return (0); tm->tm_mon = i; bp += len; break; case 'C': /* The century number. */ LEGAL_ALT(ALT_E); if (!(conv_num(&bp, &i, 0, 99))) return (0); if (split_year) { tm->tm_year = (tm->tm_year % 100) + (i * 100); } else { tm->tm_year = i * 100; split_year = 1; } break; case 'd': /* The day of month. */ case 'e': LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) return (0); break; case 'k': /* The hour (24-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'H': LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) return (0); break; case 'l': /* The hour (12-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'I': LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) return (0); if (tm->tm_hour == 12) tm->tm_hour = 0; break; case 'j': /* The day of year. */ LEGAL_ALT(0); if (!(conv_num(&bp, &i, 1, 366))) return (0); tm->tm_yday = i - 1; break; case 'M': /* The minute. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_min, 0, 59))) return (0); break; case 'm': /* The month. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &i, 1, 12))) return (0); tm->tm_mon = i - 1; break; case 'p': /* The locale's equivalent of AM/PM. */ LEGAL_ALT(0); /* AM? */ if (arg_strcasecmp(am_pm[0], bp) == 0) { if (tm->tm_hour > 11) return (0); bp += strlen(am_pm[0]); break; } /* PM? */ else if (arg_strcasecmp(am_pm[1], bp) == 0) { if (tm->tm_hour > 11) return (0); tm->tm_hour += 12; bp += strlen(am_pm[1]); break; } /* Nothing matched. */ return (0); case 'S': /* The seconds. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) return (0); break; case 'U': /* The week of year, beginning on sunday. */ case 'W': /* The week of year, beginning on monday. */ LEGAL_ALT(ALT_O); /* * XXX This is bogus, as we can not assume any valid * information present in the tm structure at this * point to calculate a real value, so just check the * range for now. */ if (!(conv_num(&bp, &i, 0, 53))) return (0); break; case 'w': /* The day of week, beginning on sunday. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) return (0); break; case 'Y': /* The year. */ LEGAL_ALT(ALT_E); if (!(conv_num(&bp, &i, 0, 9999))) return (0); tm->tm_year = i - TM_YEAR_BASE; break; case 'y': /* The year within 100 years of the epoch. */ LEGAL_ALT(ALT_E | ALT_O); if (!(conv_num(&bp, &i, 0, 99))) return (0); if (split_year) { tm->tm_year = ((tm->tm_year / 100) * 100) + i; break; } split_year = 1; if (i <= 68) tm->tm_year = i + 2000 - TM_YEAR_BASE; else tm->tm_year = i + 1900 - TM_YEAR_BASE; break; /* * Miscellaneous conversions. */ case 'n': /* Any kind of white-space. */ case 't': LEGAL_ALT(0); while (isspace((int)(*bp))) bp++; break; default: /* Unknown/unsupported conversion. */ return (0); } } /* LINTED functional specification */ return ((char*)bp); } static int conv_num(const char** buf, int* dest, int llim, int ulim) { int result = 0; /* The limit also determines the number of valid digits. */ int rulim = ulim; if (**buf < '0' || **buf > '9') return (0); do { result *= 10; result += *(*buf)++ - '0'; rulim /= 10; } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9'); if (result < llim || result > ulim) return (0); *dest = result; return (1); } /******************************************************************************* * arg_dbl: Implements the double command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include static void arg_dbl_resetfn(struct arg_dbl* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } static int arg_dbl_scanfn(struct arg_dbl* parent, const char* argval) { int errorcode = 0; if (parent->count == parent->hdr.maxcount) { /* maximum number of arguments exceeded */ errorcode = ARG_ERR_MAXCOUNT; } else if (!argval) { /* a valid argument with no argument value was given. */ /* This happens when an optional argument value was invoked. */ /* leave parent argument value unaltered but still count the argument. */ parent->count++; } else { double val; char* end; /* extract double from argval into val */ val = strtod(argval, &end); /* if success then store result in parent->dval[] array otherwise return error*/ if (*end == 0) parent->dval[parent->count++] = val; else errorcode = ARG_ERR_BADDOUBLE; } ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static int arg_dbl_checkfn(struct arg_dbl* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static void arg_dbl_errorfn(struct arg_dbl* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; /* make argval NULL safe */ argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_cat(ds, "missing option "); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_cat(ds, "excess option "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; case ARG_ERR_BADDOUBLE: arg_dstr_catf(ds, "invalid argument \"%s\" to option ", argval); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; } } struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_dbln(shortopts, longopts, datatype, 0, 1, glossary); } struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_dbln(shortopts, longopts, datatype, 1, 1, glossary); } struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { size_t nbytes; struct arg_dbl* result; size_t addr; size_t rem; /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; nbytes = sizeof(struct arg_dbl) /* storage for struct arg_dbl */ + (maxcount + 1) * sizeof(double); /* storage for dval[maxcount] array plus one extra for padding to memory boundary */ result = (struct arg_dbl*)xmalloc(nbytes); /* init the arg_hdr struct */ result->hdr.flag = ARG_HASVALUE; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.datatype = datatype ? datatype : ""; result->hdr.glossary = glossary; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_dbl_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_dbl_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_dbl_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_dbl_errorfn; /* Store the dval[maxcount] array on the first double boundary that * immediately follows the arg_dbl struct. We do the memory alignment * purely for SPARC and Motorola systems. They require floats and * doubles to be aligned on natural boundaries. */ addr = (size_t)(result + 1); rem = addr % sizeof(double); result->dval = (double*)(addr + sizeof(double) - rem); ARG_TRACE(("addr=%p, dval=%p, sizeof(double)=%d rem=%d\n", addr, result->dval, (int)sizeof(double), (int)rem)); result->count = 0; ARG_TRACE(("arg_dbln() returns %p\n", result)); return result; } /******************************************************************************* * arg_end: Implements the error handling utilities * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include static void arg_end_resetfn(struct arg_end* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } static void arg_end_errorfn(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname) { /* suppress unreferenced formal parameter warning */ (void)parent; progname = progname ? progname : ""; argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (error) { case ARG_ELIMIT: arg_dstr_cat(ds, "too many errors to display"); break; case ARG_EMALLOC: arg_dstr_cat(ds, "insufficient memory"); break; case ARG_ENOMATCH: arg_dstr_catf(ds, "unexpected argument \"%s\"", argval); break; case ARG_EMISSARG: arg_dstr_catf(ds, "option \"%s\" requires an argument", argval); break; case ARG_ELONGOPT: arg_dstr_catf(ds, "invalid option \"%s\"", argval); break; default: arg_dstr_catf(ds, "invalid option \"-%c\"", error); break; } arg_dstr_cat(ds, "\n"); } struct arg_end* arg_end(int maxcount) { size_t nbytes; struct arg_end* result; nbytes = sizeof(struct arg_end) + maxcount * sizeof(int) /* storage for int error[maxcount] array*/ + maxcount * sizeof(void*) /* storage for void* parent[maxcount] array */ + maxcount * sizeof(char*); /* storage for char* argval[maxcount] array */ result = (struct arg_end*)xmalloc(nbytes); /* init the arg_hdr struct */ result->hdr.flag = ARG_TERMINATOR; result->hdr.shortopts = NULL; result->hdr.longopts = NULL; result->hdr.datatype = NULL; result->hdr.glossary = NULL; result->hdr.mincount = 1; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_end_resetfn; result->hdr.scanfn = NULL; result->hdr.checkfn = NULL; result->hdr.errorfn = (arg_errorfn*)arg_end_errorfn; /* store error[maxcount] array immediately after struct arg_end */ result->error = (int*)(result + 1); /* store parent[maxcount] array immediately after error[] array */ result->parent = (void**)(result->error + maxcount); /* store argval[maxcount] array immediately after parent[] array */ result->argval = (const char**)(result->parent + maxcount); ARG_TRACE(("arg_end(%d) returns %p\n", maxcount, result)); return result; } void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname) { int i; ARG_TRACE(("arg_errors()\n")); for (i = 0; i < end->count; i++) { struct arg_hdr* errorparent = (struct arg_hdr*)(end->parent[i]); if (errorparent->errorfn) errorparent->errorfn(end->parent[i], ds, end->error[i], end->argval[i], progname); } } void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname) { arg_dstr_t ds = arg_dstr_create(); arg_print_errors_ds(ds, end, progname); fputs(arg_dstr_cstr(ds), fp); arg_dstr_destroy(ds); } /******************************************************************************* * arg_file: Implements the file command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #ifdef WIN32 #define FILESEPARATOR1 '\\' #define FILESEPARATOR2 '/' #else #define FILESEPARATOR1 '/' #define FILESEPARATOR2 '/' #endif static void arg_file_resetfn(struct arg_file* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } /* Returns ptr to the base filename within *filename */ static const char* arg_basename(const char* filename) { const char *result = NULL, *result1, *result2; /* Find the last occurrence of eother file separator character. */ /* Two alternative file separator chars are supported as legal */ /* file separators but not both together in the same filename. */ result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL); result2 = (filename ? strrchr(filename, FILESEPARATOR2) : NULL); if (result2) result = result2 + 1; /* using FILESEPARATOR2 (the alternative file separator) */ if (result1) result = result1 + 1; /* using FILESEPARATOR1 (the preferred file separator) */ if (!result) result = filename; /* neither file separator was found so basename is the whole filename */ /* special cases of "." and ".." are not considered basenames */ if (result && (strcmp(".", result) == 0 || strcmp("..", result) == 0)) result = filename + strlen(filename); return result; } /* Returns ptr to the file extension within *basename */ static const char* arg_extension(const char* basename) { /* find the last occurrence of '.' in basename */ const char* result = (basename ? strrchr(basename, '.') : NULL); /* if no '.' was found then return pointer to end of basename */ if (basename && !result) result = basename + strlen(basename); /* special case: basenames with a single leading dot (eg ".foo") are not considered as true extensions */ if (basename && result == basename) result = basename + strlen(basename); /* special case: empty extensions (eg "foo.","foo..") are not considered as true extensions */ if (basename && result && strlen(result) == 1) result = basename + strlen(basename); return result; } static int arg_file_scanfn(struct arg_file* parent, const char* argval) { int errorcode = 0; if (parent->count == parent->hdr.maxcount) { /* maximum number of arguments exceeded */ errorcode = ARG_ERR_MAXCOUNT; } else if (!argval) { /* a valid argument with no argument value was given. */ /* This happens when an optional argument value was invoked. */ /* leave parent arguiment value unaltered but still count the argument. */ parent->count++; } else { parent->filename[parent->count] = argval; parent->basename[parent->count] = arg_basename(argval); parent->extension[parent->count] = arg_extension(parent->basename[parent->count]); /* only seek extensions within the basename (not the file path)*/ parent->count++; } ARG_TRACE(("%s4:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static int arg_file_checkfn(struct arg_file* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static void arg_file_errorfn(struct arg_file* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; /* make argval NULL safe */ argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_cat(ds, "missing option "); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_cat(ds, "excess option "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; default: arg_dstr_catf(ds, "unknown error at \"%s\"\n", argval); } } struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_filen(shortopts, longopts, datatype, 0, 1, glossary); } struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_filen(shortopts, longopts, datatype, 1, 1, glossary); } struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { size_t nbytes; struct arg_file* result; int i; /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; nbytes = sizeof(struct arg_file) /* storage for struct arg_file */ + sizeof(char*) * maxcount /* storage for filename[maxcount] array */ + sizeof(char*) * maxcount /* storage for basename[maxcount] array */ + sizeof(char*) * maxcount; /* storage for extension[maxcount] array */ result = (struct arg_file*)xmalloc(nbytes); /* init the arg_hdr struct */ result->hdr.flag = ARG_HASVALUE; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.glossary = glossary; result->hdr.datatype = datatype ? datatype : ""; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_file_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_file_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_file_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_file_errorfn; /* store the filename,basename,extension arrays immediately after the arg_file struct */ result->filename = (const char**)(result + 1); result->basename = result->filename + maxcount; result->extension = result->basename + maxcount; result->count = 0; /* foolproof the string pointers by initialising them with empty strings */ for (i = 0; i < maxcount; i++) { result->filename[i] = ""; result->basename[i] = ""; result->extension[i] = ""; } ARG_TRACE(("arg_filen() returns %p\n", result)); return result; } /******************************************************************************* * arg_int: Implements the int command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #include static void arg_int_resetfn(struct arg_int* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } /* strtol0x() is like strtol() except that the numeric string is */ /* expected to be prefixed by "0X" where X is a user supplied char. */ /* The string may optionally be prefixed by white space and + or - */ /* as in +0X123 or -0X123. */ /* Once the prefix has been scanned, the remainder of the numeric */ /* string is converted using strtol() with the given base. */ /* eg: to parse hex str="-0X12324", specify X='X' and base=16. */ /* eg: to parse oct str="+0o12324", specify X='O' and base=8. */ /* eg: to parse bin str="-0B01010", specify X='B' and base=2. */ /* Failure of conversion is indicated by result where *endptr==str. */ static long int strtol0X(const char* str, const char** endptr, char X, int base) { long int val; /* stores result */ int s = 1; /* sign is +1 or -1 */ const char* ptr = str; /* ptr to current position in str */ /* skip leading whitespace */ while (isspace((int)(*ptr))) ptr++; /* printf("1) %s\n",ptr); */ /* scan optional sign character */ switch (*ptr) { case '+': ptr++; s = 1; break; case '-': ptr++; s = -1; break; default: s = 1; break; } /* printf("2) %s\n",ptr); */ /* '0X' prefix */ if ((*ptr++) != '0') { /* printf("failed to detect '0'\n"); */ *endptr = str; return 0; } /* printf("3) %s\n",ptr); */ if (toupper(*ptr++) != toupper(X)) { /* printf("failed to detect '%c'\n",X); */ *endptr = str; return 0; } /* printf("4) %s\n",ptr); */ /* attempt conversion on remainder of string using strtol() */ val = strtol(ptr, (char**)endptr, base); if (*endptr == ptr) { /* conversion failed */ *endptr = str; return 0; } /* success */ return s * val; } /* Returns 1 if str matches suffix (case insensitive). */ /* Str may contain trailing whitespace, but nothing else. */ static int detectsuffix(const char* str, const char* suffix) { /* scan pairwise through strings until mismatch detected */ while (toupper(*str) == toupper(*suffix)) { /* printf("'%c' '%c'\n", *str, *suffix); */ /* return 1 (success) if match persists until the string terminator */ if (*str == '\0') return 1; /* next chars */ str++; suffix++; } /* printf("'%c' '%c' mismatch\n", *str, *suffix); */ /* return 0 (fail) if the matching did not consume the entire suffix */ if (*suffix != 0) return 0; /* failed to consume entire suffix */ /* skip any remaining whitespace in str */ while (isspace((int)(*str))) str++; /* return 1 (success) if we have reached end of str else return 0 (fail) */ return (*str == '\0') ? 1 : 0; } static int arg_int_scanfn(struct arg_int* parent, const char* argval) { int errorcode = 0; if (parent->count == parent->hdr.maxcount) { /* maximum number of arguments exceeded */ errorcode = ARG_ERR_MAXCOUNT; } else if (!argval) { /* a valid argument with no argument value was given. */ /* This happens when an optional argument value was invoked. */ /* leave parent arguiment value unaltered but still count the argument. */ parent->count++; } else { long int val; const char* end; /* attempt to extract hex integer (eg: +0x123) from argval into val conversion */ val = strtol0X(argval, &end, 'X', 16); if (end == argval) { /* hex failed, attempt octal conversion (eg +0o123) */ val = strtol0X(argval, &end, 'O', 8); if (end == argval) { /* octal failed, attempt binary conversion (eg +0B101) */ val = strtol0X(argval, &end, 'B', 2); if (end == argval) { /* binary failed, attempt decimal conversion with no prefix (eg 1234) */ val = strtol(argval, (char**)&end, 10); if (end == argval) { /* all supported number formats failed */ return ARG_ERR_BADINT; } } } } /* Safety check for integer overflow. WARNING: this check */ /* achieves nothing on machines where size(int)==size(long). */ if (val > INT_MAX || val < INT_MIN) errorcode = ARG_ERR_OVERFLOW; /* Detect any suffixes (KB,MB,GB) and multiply argument value appropriately. */ /* We need to be mindful of integer overflows when using such big numbers. */ if (detectsuffix(end, "KB")) /* kilobytes */ { if (val > (INT_MAX / 1024) || val < (INT_MIN / 1024)) errorcode = ARG_ERR_OVERFLOW; /* Overflow would occur if we proceed */ else val *= 1024; /* 1KB = 1024 */ } else if (detectsuffix(end, "MB")) /* megabytes */ { if (val > (INT_MAX / 1048576) || val < (INT_MIN / 1048576)) errorcode = ARG_ERR_OVERFLOW; /* Overflow would occur if we proceed */ else val *= 1048576; /* 1MB = 1024*1024 */ } else if (detectsuffix(end, "GB")) /* gigabytes */ { if (val > (INT_MAX / 1073741824) || val < (INT_MIN / 1073741824)) errorcode = ARG_ERR_OVERFLOW; /* Overflow would occur if we proceed */ else val *= 1073741824; /* 1GB = 1024*1024*1024 */ } else if (!detectsuffix(end, "")) errorcode = ARG_ERR_BADINT; /* invalid suffix detected */ /* if success then store result in parent->ival[] array */ if (errorcode == 0) parent->ival[parent->count++] = (int)val; } /* printf("%s:scanfn(%p,%p) returns %d\n",__FILE__,parent,argval,errorcode); */ return errorcode; } static int arg_int_checkfn(struct arg_int* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; /*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/ return errorcode; } static void arg_int_errorfn(struct arg_int* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; /* make argval NULL safe */ argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_cat(ds, "missing option "); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_cat(ds, "excess option "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; case ARG_ERR_BADINT: arg_dstr_catf(ds, "invalid argument \"%s\" to option ", argval); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_OVERFLOW: arg_dstr_cat(ds, "integer overflow at option "); arg_print_option_ds(ds, shortopts, longopts, datatype, " "); arg_dstr_catf(ds, "(%s is too large)\n", argval); break; } } struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_intn(shortopts, longopts, datatype, 0, 1, glossary); } struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_intn(shortopts, longopts, datatype, 1, 1, glossary); } struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { size_t nbytes; struct arg_int* result; /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; nbytes = sizeof(struct arg_int) /* storage for struct arg_int */ + maxcount * sizeof(int); /* storage for ival[maxcount] array */ result = (struct arg_int*)xmalloc(nbytes); /* init the arg_hdr struct */ result->hdr.flag = ARG_HASVALUE; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.datatype = datatype ? datatype : ""; result->hdr.glossary = glossary; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_int_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_int_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_int_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_int_errorfn; /* store the ival[maxcount] array immediately after the arg_int struct */ result->ival = (int*)(result + 1); result->count = 0; ARG_TRACE(("arg_intn() returns %p\n", result)); return result; } /******************************************************************************* * arg_lit: Implements the literature command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include static void arg_lit_resetfn(struct arg_lit* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } static int arg_lit_scanfn(struct arg_lit* parent, const char* argval) { int errorcode = 0; if (parent->count < parent->hdr.maxcount) parent->count++; else errorcode = ARG_ERR_MAXCOUNT; ARG_TRACE(("%s:scanfn(%p,%s) returns %d\n", __FILE__, parent, argval, errorcode)); return errorcode; } static int arg_lit_checkfn(struct arg_lit* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static void arg_lit_errorfn(struct arg_lit* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_catf(ds, "%s: missing option ", progname); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); arg_dstr_cat(ds, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_catf(ds, "%s: extraneous option ", progname); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; } ARG_TRACE(("%s:errorfn(%p, %p, %d, %s, %s)\n", __FILE__, parent, ds, errorcode, argval, progname)); } struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary) { return arg_litn(shortopts, longopts, 0, 1, glossary); } struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary) { return arg_litn(shortopts, longopts, 1, 1, glossary); } struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary) { struct arg_lit* result; /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; result = (struct arg_lit*)xmalloc(sizeof(struct arg_lit)); /* init the arg_hdr struct */ result->hdr.flag = 0; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.datatype = NULL; result->hdr.glossary = glossary; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_lit_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_lit_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_lit_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_lit_errorfn; /* init local variables */ result->count = 0; ARG_TRACE(("arg_litn() returns %p\n", result)); return result; } /******************************************************************************* * arg_rem: Implements the rem command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include struct arg_rem* arg_rem(const char* datatype, const char* glossary) { struct arg_rem* result = (struct arg_rem*)xmalloc(sizeof(struct arg_rem)); result->hdr.flag = 0; result->hdr.shortopts = NULL; result->hdr.longopts = NULL; result->hdr.datatype = datatype; result->hdr.glossary = glossary; result->hdr.mincount = 1; result->hdr.maxcount = 1; result->hdr.parent = result; result->hdr.resetfn = NULL; result->hdr.scanfn = NULL; result->hdr.checkfn = NULL; result->hdr.errorfn = NULL; ARG_TRACE(("arg_rem() returns %p\n", result)); return result; } /******************************************************************************* * arg_rex: Implements the regex command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #ifndef _TREX_H_ #define _TREX_H_ /* * This module uses the T-Rex regular expression library to implement the regex * logic. Here is the copyright notice of the library: * * Copyright (C) 2003-2006 Alberto Demichelis * * This software is provided 'as-is', without any express * or implied warranty. In no event will the authors be held * liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for * any purpose, including commercial applications, and to alter * it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; * you must not claim that you wrote the original software. * If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but * is not required. * * 2. Altered source versions must be plainly marked as such, * and must not be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any * source distribution. */ #ifdef __cplusplus extern "C" { #endif #define TRexChar char #define MAX_CHAR 0xFF #define _TREXC(c) (c) #define trex_strlen strlen #define trex_printf printf #ifndef TREX_API #define TREX_API extern #endif #define TRex_True 1 #define TRex_False 0 #define TREX_ICASE ARG_REX_ICASE typedef unsigned int TRexBool; typedef struct TRex TRex; typedef struct { const TRexChar* begin; int len; } TRexMatch; #ifdef __GNUC__ TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) __attribute__((optimize(0))); #else TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags); #endif TREX_API void trex_free(TRex* exp); TREX_API TRexBool trex_match(TRex* exp, const TRexChar* text); TREX_API TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end); TREX_API TRexBool trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end); TREX_API int trex_getsubexpcount(TRex* exp); TREX_API TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch* subexp); #ifdef __cplusplus } #endif #endif struct privhdr { const char* pattern; int flags; }; static void arg_rex_resetfn(struct arg_rex* parent) { ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); parent->count = 0; } static int arg_rex_scanfn(struct arg_rex* parent, const char* argval) { int errorcode = 0; const TRexChar* error = NULL; TRex* rex = NULL; TRexBool is_match = TRex_False; if (parent->count == parent->hdr.maxcount) { /* maximum number of arguments exceeded */ errorcode = ARG_ERR_MAXCOUNT; } else if (!argval) { /* a valid argument with no argument value was given. */ /* This happens when an optional argument value was invoked. */ /* leave parent argument value unaltered but still count the argument. */ parent->count++; } else { struct privhdr* priv = (struct privhdr*)parent->hdr.priv; /* test the current argument value for a match with the regular expression */ /* if a match is detected, record the argument value in the arg_rex struct */ rex = trex_compile(priv->pattern, &error, priv->flags); is_match = trex_match(rex, argval); if (!is_match) errorcode = ARG_ERR_REGNOMATCH; else parent->sval[parent->count++] = argval; trex_free(rex); } ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static int arg_rex_checkfn(struct arg_rex* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; #if 0 struct privhdr *priv = (struct privhdr*)parent->hdr.priv; /* free the regex "program" we constructed in resetfn */ regfree(&(priv->regex)); /*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/ #endif return errorcode; } static void arg_rex_errorfn(struct arg_rex* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; /* make argval NULL safe */ argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_cat(ds, "missing option "); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_cat(ds, "excess option "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; case ARG_ERR_REGNOMATCH: arg_dstr_cat(ds, "illegal value "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; default: { #if 0 char errbuff[256]; regerror(errorcode, NULL, errbuff, sizeof(errbuff)); printf("%s\n", errbuff); #endif } break; } } struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary) { return arg_rexn(shortopts, longopts, pattern, datatype, 0, 1, flags, glossary); } struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary) { return arg_rexn(shortopts, longopts, pattern, datatype, 1, 1, flags, glossary); } struct arg_rex* arg_rexn(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int mincount, int maxcount, int flags, const char* glossary) { size_t nbytes; struct arg_rex* result; struct privhdr* priv; int i; const TRexChar* error = NULL; TRex* rex = NULL; if (!pattern) { printf("argtable: ERROR - illegal regular expression pattern \"(NULL)\"\n"); printf("argtable: Bad argument table.\n"); return NULL; } /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; nbytes = sizeof(struct arg_rex) /* storage for struct arg_rex */ + sizeof(struct privhdr) /* storage for private arg_rex data */ + maxcount * sizeof(char*); /* storage for sval[maxcount] array */ /* init the arg_hdr struct */ result = (struct arg_rex*)xmalloc(nbytes); result->hdr.flag = ARG_HASVALUE; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.datatype = datatype ? datatype : pattern; result->hdr.glossary = glossary; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_rex_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_rex_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_rex_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_rex_errorfn; /* store the arg_rex_priv struct immediately after the arg_rex struct */ result->hdr.priv = result + 1; priv = (struct privhdr*)(result->hdr.priv); priv->pattern = pattern; priv->flags = flags; /* store the sval[maxcount] array immediately after the arg_rex_priv struct */ result->sval = (const char**)(priv + 1); result->count = 0; /* foolproof the string pointers by initializing them to reference empty strings */ for (i = 0; i < maxcount; i++) result->sval[i] = ""; /* here we construct and destroy a regex representation of the regular * expression for no other reason than to force any regex errors to be * trapped now rather than later. If we don't, then errors may go undetected * until an argument is actually parsed. */ rex = trex_compile(priv->pattern, &error, priv->flags); if (rex == NULL) { ARG_LOG(("argtable: %s \"%s\"\n", error ? error : _TREXC("undefined"), priv->pattern)); ARG_LOG(("argtable: Bad argument table.\n")); } trex_free(rex); ARG_TRACE(("arg_rexn() returns %p\n", result)); return result; } /* see copyright notice in trex.h */ #include #include #include #include #ifdef _UINCODE #define scisprint iswprint #define scstrlen wcslen #define scprintf wprintf #define _SC(x) L(x) #else #define scisprint isprint #define scstrlen strlen #define scprintf printf #define _SC(x) (x) #endif #ifdef ARG_REX_DEBUG #include static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_OR"), _SC("OP_EXPR"), _SC("OP_NOCAPEXPR"), _SC("OP_DOT"), _SC("OP_CLASS"), _SC("OP_CCLASS"), _SC("OP_NCLASS"), _SC("OP_RANGE"), _SC("OP_CHAR"), _SC("OP_EOL"), _SC("OP_BOL"), _SC("OP_WB")}; #endif #define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */ #define OP_OR (MAX_CHAR + 2) #define OP_EXPR (MAX_CHAR + 3) /* parentesis () */ #define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */ #define OP_DOT (MAX_CHAR + 5) #define OP_CLASS (MAX_CHAR + 6) #define OP_CCLASS (MAX_CHAR + 7) #define OP_NCLASS (MAX_CHAR + 8) /* negates class the [^ */ #define OP_RANGE (MAX_CHAR + 9) #define OP_CHAR (MAX_CHAR + 10) #define OP_EOL (MAX_CHAR + 11) #define OP_BOL (MAX_CHAR + 12) #define OP_WB (MAX_CHAR + 13) #define TREX_SYMBOL_ANY_CHAR ('.') #define TREX_SYMBOL_GREEDY_ONE_OR_MORE ('+') #define TREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*') #define TREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?') #define TREX_SYMBOL_BRANCH ('|') #define TREX_SYMBOL_END_OF_STRING ('$') #define TREX_SYMBOL_BEGINNING_OF_STRING ('^') #define TREX_SYMBOL_ESCAPE_CHAR ('\\') typedef int TRexNodeType; typedef struct tagTRexNode { TRexNodeType type; int left; int right; int next; } TRexNode; struct TRex { const TRexChar* _eol; const TRexChar* _bol; const TRexChar* _p; int _first; int _op; TRexNode* _nodes; int _nallocated; int _nsize; int _nsubexpr; TRexMatch* _matches; int _currsubexp; void* _jmpbuf; const TRexChar** _error; int _flags; }; static int trex_list(TRex* exp); static int trex_newnode(TRex* exp, TRexNodeType type) { TRexNode n; int newid; n.type = type; n.next = n.right = n.left = -1; if (type == OP_EXPR) n.right = exp->_nsubexpr++; if (exp->_nallocated < (exp->_nsize + 1)) { exp->_nallocated *= 2; exp->_nodes = (TRexNode*)xrealloc(exp->_nodes, exp->_nallocated * sizeof(TRexNode)); } exp->_nodes[exp->_nsize++] = n; newid = exp->_nsize - 1; return (int)newid; } static void trex_error(TRex* exp, const TRexChar* error) { if (exp->_error) *exp->_error = error; longjmp(*((jmp_buf*)exp->_jmpbuf), -1); } static void trex_expect(TRex* exp, int n) { if ((*exp->_p) != n) trex_error(exp, _SC("expected paren")); exp->_p++; } static TRexChar trex_escapechar(TRex* exp) { if (*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) { exp->_p++; switch (*exp->_p) { case 'v': exp->_p++; return '\v'; case 'n': exp->_p++; return '\n'; case 't': exp->_p++; return '\t'; case 'r': exp->_p++; return '\r'; case 'f': exp->_p++; return '\f'; default: return (*exp->_p++); } } else if (!scisprint((int)(*exp->_p))) trex_error(exp, _SC("letter expected")); return (*exp->_p++); } static int trex_charclass(TRex* exp, int classid) { int n = trex_newnode(exp, OP_CCLASS); exp->_nodes[n].left = classid; return n; } static int trex_charnode(TRex* exp, TRexBool isclass) { TRexChar t; if (*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) { exp->_p++; switch (*exp->_p) { case 'n': exp->_p++; return trex_newnode(exp, '\n'); case 't': exp->_p++; return trex_newnode(exp, '\t'); case 'r': exp->_p++; return trex_newnode(exp, '\r'); case 'f': exp->_p++; return trex_newnode(exp, '\f'); case 'v': exp->_p++; return trex_newnode(exp, '\v'); case 'a': case 'A': case 'w': case 'W': case 's': case 'S': case 'd': case 'D': case 'x': case 'X': case 'c': case 'C': case 'p': case 'P': case 'l': case 'u': { t = *exp->_p; exp->_p++; return trex_charclass(exp, t); } case 'b': case 'B': if (!isclass) { int node = trex_newnode(exp, OP_WB); exp->_nodes[node].left = *exp->_p; exp->_p++; return node; } /* fall through */ default: t = *exp->_p; exp->_p++; return trex_newnode(exp, t); } } else if (!scisprint((int)(*exp->_p))) { trex_error(exp, _SC("letter expected")); } t = *exp->_p; exp->_p++; return trex_newnode(exp, t); } static int trex_class(TRex* exp) { int ret = -1; int first = -1, chain; if (*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) { ret = trex_newnode(exp, OP_NCLASS); exp->_p++; } else ret = trex_newnode(exp, OP_CLASS); if (*exp->_p == ']') trex_error(exp, _SC("empty class")); chain = ret; while (*exp->_p != ']' && exp->_p != exp->_eol) { if (*exp->_p == '-' && first != -1) { int r, t; if (*exp->_p++ == ']') trex_error(exp, _SC("unfinished range")); r = trex_newnode(exp, OP_RANGE); if (first > *exp->_p) trex_error(exp, _SC("invalid range")); if (exp->_nodes[first].type == OP_CCLASS) trex_error(exp, _SC("cannot use character classes in ranges")); exp->_nodes[r].left = exp->_nodes[first].type; t = trex_escapechar(exp); exp->_nodes[r].right = t; exp->_nodes[chain].next = r; chain = r; first = -1; } else { if (first != -1) { int c = first; exp->_nodes[chain].next = c; chain = c; first = trex_charnode(exp, TRex_True); } else { first = trex_charnode(exp, TRex_True); } } } if (first != -1) { int c = first; exp->_nodes[chain].next = c; chain = c; first = -1; } /* hack? */ exp->_nodes[ret].left = exp->_nodes[ret].next; exp->_nodes[ret].next = -1; return ret; } static int trex_parsenumber(TRex* exp) { int ret = *exp->_p - '0'; int positions = 10; exp->_p++; while (isdigit((int)(*exp->_p))) { ret = ret * 10 + (*exp->_p++ - '0'); if (positions == 1000000000) trex_error(exp, _SC("overflow in numeric constant")); positions *= 10; }; return ret; } static int trex_element(TRex* exp) { int ret = -1; switch (*exp->_p) { case '(': { int expr, newn; exp->_p++; if (*exp->_p == '?') { exp->_p++; trex_expect(exp, ':'); expr = trex_newnode(exp, OP_NOCAPEXPR); } else expr = trex_newnode(exp, OP_EXPR); newn = trex_list(exp); exp->_nodes[expr].left = newn; ret = expr; trex_expect(exp, ')'); } break; case '[': exp->_p++; ret = trex_class(exp); trex_expect(exp, ']'); break; case TREX_SYMBOL_END_OF_STRING: exp->_p++; ret = trex_newnode(exp, OP_EOL); break; case TREX_SYMBOL_ANY_CHAR: exp->_p++; ret = trex_newnode(exp, OP_DOT); break; default: ret = trex_charnode(exp, TRex_False); break; } { TRexBool isgreedy = TRex_False; unsigned short p0 = 0, p1 = 0; switch (*exp->_p) { case TREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = TRex_True; break; case TREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = TRex_True; break; case TREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = TRex_True; break; case '{': exp->_p++; if (!isdigit((int)(*exp->_p))) trex_error(exp, _SC("number expected")); p0 = (unsigned short)trex_parsenumber(exp); /*******************************/ switch (*exp->_p) { case '}': p1 = p0; exp->_p++; break; case ',': exp->_p++; p1 = 0xFFFF; if (isdigit((int)(*exp->_p))) { p1 = (unsigned short)trex_parsenumber(exp); } trex_expect(exp, '}'); break; default: trex_error(exp, _SC(", or } expected")); } /*******************************/ isgreedy = TRex_True; break; } if (isgreedy) { int nnode = trex_newnode(exp, OP_GREEDY); exp->_nodes[nnode].left = ret; exp->_nodes[nnode].right = ((p0) << 16) | p1; ret = nnode; } } if ((*exp->_p != TREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != TREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != TREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) { int nnode = trex_element(exp); exp->_nodes[ret].next = nnode; } return ret; } static int trex_list(TRex* exp) { int ret = -1, e; if (*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) { exp->_p++; ret = trex_newnode(exp, OP_BOL); } e = trex_element(exp); if (ret != -1) { exp->_nodes[ret].next = e; } else ret = e; if (*exp->_p == TREX_SYMBOL_BRANCH) { int temp, tright; exp->_p++; temp = trex_newnode(exp, OP_OR); exp->_nodes[temp].left = ret; tright = trex_list(exp); exp->_nodes[temp].right = tright; ret = temp; } return ret; } static TRexBool trex_matchcclass(int cclass, TRexChar c) { switch (cclass) { case 'a': return isalpha(c) ? TRex_True : TRex_False; case 'A': return !isalpha(c) ? TRex_True : TRex_False; case 'w': return (isalnum(c) || c == '_') ? TRex_True : TRex_False; case 'W': return (!isalnum(c) && c != '_') ? TRex_True : TRex_False; case 's': return isspace(c) ? TRex_True : TRex_False; case 'S': return !isspace(c) ? TRex_True : TRex_False; case 'd': return isdigit(c) ? TRex_True : TRex_False; case 'D': return !isdigit(c) ? TRex_True : TRex_False; case 'x': return isxdigit(c) ? TRex_True : TRex_False; case 'X': return !isxdigit(c) ? TRex_True : TRex_False; case 'c': return iscntrl(c) ? TRex_True : TRex_False; case 'C': return !iscntrl(c) ? TRex_True : TRex_False; case 'p': return ispunct(c) ? TRex_True : TRex_False; case 'P': return !ispunct(c) ? TRex_True : TRex_False; case 'l': return islower(c) ? TRex_True : TRex_False; case 'u': return isupper(c) ? TRex_True : TRex_False; } return TRex_False; /*cannot happen*/ } static TRexBool trex_matchclass(TRex* exp, TRexNode* node, TRexChar c) { do { switch (node->type) { case OP_RANGE: if (exp->_flags & TREX_ICASE) { if (c >= toupper(node->left) && c <= toupper(node->right)) return TRex_True; if (c >= tolower(node->left) && c <= tolower(node->right)) return TRex_True; } else { if (c >= node->left && c <= node->right) return TRex_True; } break; case OP_CCLASS: if (trex_matchcclass(node->left, c)) return TRex_True; break; default: if (exp->_flags & TREX_ICASE) { if (c == tolower(node->type) || c == toupper(node->type)) return TRex_True; } else { if (c == node->type) return TRex_True; } } } while ((node->next != -1) && ((node = &exp->_nodes[node->next]) != NULL)); return TRex_False; } static const TRexChar* trex_matchnode(TRex* exp, TRexNode* node, const TRexChar* str, TRexNode* next) { TRexNodeType type = node->type; switch (type) { case OP_GREEDY: { /* TRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL; */ TRexNode* greedystop = NULL; int p0 = (node->right >> 16) & 0x0000FFFF, p1 = node->right & 0x0000FFFF, nmaches = 0; const TRexChar *s = str, *good = str; if (node->next != -1) { greedystop = &exp->_nodes[node->next]; } else { greedystop = next; } while ((nmaches == 0xFFFF || nmaches < p1)) { const TRexChar* stop; if ((s = trex_matchnode(exp, &exp->_nodes[node->left], s, greedystop)) == NULL) break; nmaches++; good = s; if (greedystop) { /* checks that 0 matches satisfy the expression(if so skips) */ /* if not would always stop(for instance if is a '?') */ if (greedystop->type != OP_GREEDY || (greedystop->type == OP_GREEDY && ((greedystop->right >> 16) & 0x0000FFFF) != 0)) { TRexNode* gnext = NULL; if (greedystop->next != -1) { gnext = &exp->_nodes[greedystop->next]; } else if (next && next->next != -1) { gnext = &exp->_nodes[next->next]; } stop = trex_matchnode(exp, greedystop, s, gnext); if (stop) { /* if satisfied stop it */ if (p0 == p1 && p0 == nmaches) break; else if (nmaches >= p0 && p1 == 0xFFFF) break; else if (nmaches >= p0 && nmaches <= p1) break; } } } if (s >= exp->_eol) break; } if (p0 == p1 && p0 == nmaches) return good; else if (nmaches >= p0 && p1 == 0xFFFF) return good; else if (nmaches >= p0 && nmaches <= p1) return good; return NULL; } case OP_OR: { const TRexChar* asd = str; TRexNode* temp = &exp->_nodes[node->left]; while ((asd = trex_matchnode(exp, temp, asd, NULL)) != NULL) { if (temp->next != -1) temp = &exp->_nodes[temp->next]; else return asd; } asd = str; temp = &exp->_nodes[node->right]; while ((asd = trex_matchnode(exp, temp, asd, NULL)) != NULL) { if (temp->next != -1) temp = &exp->_nodes[temp->next]; else return asd; } return NULL; break; } case OP_EXPR: case OP_NOCAPEXPR: { TRexNode* n = &exp->_nodes[node->left]; const TRexChar* cur = str; int capture = -1; if (node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) { capture = exp->_currsubexp; exp->_matches[capture].begin = cur; exp->_currsubexp++; } do { TRexNode* subnext = NULL; if (n->next != -1) { subnext = &exp->_nodes[n->next]; } else { subnext = next; } if ((cur = trex_matchnode(exp, n, cur, subnext)) == NULL) { if (capture != -1) { exp->_matches[capture].begin = 0; exp->_matches[capture].len = 0; } return NULL; } } while ((n->next != -1) && ((n = &exp->_nodes[n->next]) != NULL)); if (capture != -1) exp->_matches[capture].len = (int)(cur - exp->_matches[capture].begin); return cur; } case OP_WB: if ((str == exp->_bol && !isspace((int)(*str))) || (str == exp->_eol && !isspace((int)(*(str - 1)))) || (!isspace((int)(*str)) && isspace((int)(*(str + 1)))) || (isspace((int)(*str)) && !isspace((int)(*(str + 1))))) { return (node->left == 'b') ? str : NULL; } return (node->left == 'b') ? NULL : str; case OP_BOL: if (str == exp->_bol) return str; return NULL; case OP_EOL: if (str == exp->_eol) return str; return NULL; case OP_DOT: { str++; } return str; case OP_NCLASS: case OP_CLASS: if (trex_matchclass(exp, &exp->_nodes[node->left], *str) ? (type == OP_CLASS ? TRex_True : TRex_False) : (type == OP_NCLASS ? TRex_True : TRex_False)) { str++; return str; } return NULL; case OP_CCLASS: if (trex_matchcclass(node->left, *str)) { str++; return str; } return NULL; default: /* char */ if (exp->_flags & TREX_ICASE) { if (*str != tolower(node->type) && *str != toupper(node->type)) return NULL; } else { if (*str != node->type) return NULL; } str++; return str; } } /* public api */ TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) { TRex* exp = (TRex*)xmalloc(sizeof(TRex)); exp->_eol = exp->_bol = NULL; exp->_p = pattern; exp->_nallocated = (int)scstrlen(pattern) * sizeof(TRexChar); exp->_nodes = (TRexNode*)xmalloc(exp->_nallocated * sizeof(TRexNode)); exp->_nsize = 0; exp->_matches = 0; exp->_nsubexpr = 0; exp->_first = trex_newnode(exp, OP_EXPR); exp->_error = error; exp->_jmpbuf = xmalloc(sizeof(jmp_buf)); exp->_flags = flags; if (setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) { int res = trex_list(exp); exp->_nodes[exp->_first].left = res; if (*exp->_p != '\0') trex_error(exp, _SC("unexpected character")); #ifdef ARG_REX_DEBUG { int nsize, i; nsize = exp->_nsize; scprintf(_SC("\n")); for (i = 0; i < nsize; i++) { if (exp->_nodes[i].type > MAX_CHAR) scprintf(_SC("[%02d] %10s "), i, g_nnames[exp->_nodes[i].type - MAX_CHAR]); else scprintf(_SC("[%02d] %10c "), i, exp->_nodes[i].type); scprintf(_SC("left %02d right %02d next %02d\n"), exp->_nodes[i].left, exp->_nodes[i].right, exp->_nodes[i].next); } scprintf(_SC("\n")); } #endif exp->_matches = (TRexMatch*)xmalloc(exp->_nsubexpr * sizeof(TRexMatch)); memset(exp->_matches, 0, exp->_nsubexpr * sizeof(TRexMatch)); } else { trex_free(exp); return NULL; } return exp; } void trex_free(TRex* exp) { if (exp) { xfree(exp->_nodes); xfree(exp->_jmpbuf); xfree(exp->_matches); xfree(exp); } } TRexBool trex_match(TRex* exp, const TRexChar* text) { const TRexChar* res = NULL; exp->_bol = text; exp->_eol = text + scstrlen(text); exp->_currsubexp = 0; res = trex_matchnode(exp, exp->_nodes, text, NULL); if (res == NULL || res != exp->_eol) return TRex_False; return TRex_True; } TRexBool trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end) { const TRexChar* cur = NULL; int node = exp->_first; if (text_begin >= text_end) return TRex_False; exp->_bol = text_begin; exp->_eol = text_end; do { cur = text_begin; while (node != -1) { exp->_currsubexp = 0; cur = trex_matchnode(exp, &exp->_nodes[node], cur, NULL); if (!cur) break; node = exp->_nodes[node].next; } text_begin++; } while (cur == NULL && text_begin != text_end); if (cur == NULL) return TRex_False; --text_begin; if (out_begin) *out_begin = text_begin; if (out_end) *out_end = cur; return TRex_True; } TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end) { return trex_searchrange(exp, text, text + scstrlen(text), out_begin, out_end); } int trex_getsubexpcount(TRex* exp) { return exp->_nsubexpr; } TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch* subexp) { if (n < 0 || n >= exp->_nsubexpr) return TRex_False; *subexp = exp->_matches[n]; return TRex_True; } /******************************************************************************* * arg_str: Implements the str command-line option * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include static void arg_str_resetfn(struct arg_str* parent) { int i; ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); for (i = 0; i < parent->count; i++) { parent->sval[i] = ""; } parent->count = 0; } static int arg_str_scanfn(struct arg_str* parent, const char* argval) { int errorcode = 0; if (parent->count == parent->hdr.maxcount) { /* maximum number of arguments exceeded */ errorcode = ARG_ERR_MAXCOUNT; } else if (!argval) { /* a valid argument with no argument value was given. */ /* This happens when an optional argument value was invoked. */ /* leave parent argument value unaltered but still count the argument. */ parent->count++; } else { parent->sval[parent->count++] = argval; } ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static int arg_str_checkfn(struct arg_str* parent) { int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0; ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); return errorcode; } static void arg_str_errorfn(struct arg_str* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) { const char* shortopts = parent->hdr.shortopts; const char* longopts = parent->hdr.longopts; const char* datatype = parent->hdr.datatype; /* make argval NULL safe */ argval = argval ? argval : ""; arg_dstr_catf(ds, "%s: ", progname); switch (errorcode) { case ARG_ERR_MINCOUNT: arg_dstr_cat(ds, "missing option "); arg_print_option_ds(ds, shortopts, longopts, datatype, "\n"); break; case ARG_ERR_MAXCOUNT: arg_dstr_cat(ds, "excess option "); arg_print_option_ds(ds, shortopts, longopts, argval, "\n"); break; } } struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_strn(shortopts, longopts, datatype, 0, 1, glossary); } struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary) { return arg_strn(shortopts, longopts, datatype, 1, 1, glossary); } struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary) { size_t nbytes; struct arg_str* result; int i; /* should not allow this stupid error */ /* we should return an error code warning this logic error */ /* foolproof things by ensuring maxcount is not less than mincount */ maxcount = (maxcount < mincount) ? mincount : maxcount; nbytes = sizeof(struct arg_str) /* storage for struct arg_str */ + maxcount * sizeof(char*); /* storage for sval[maxcount] array */ result = (struct arg_str*)xmalloc(nbytes); /* init the arg_hdr struct */ result->hdr.flag = ARG_HASVALUE; result->hdr.shortopts = shortopts; result->hdr.longopts = longopts; result->hdr.datatype = datatype ? datatype : ""; result->hdr.glossary = glossary; result->hdr.mincount = mincount; result->hdr.maxcount = maxcount; result->hdr.parent = result; result->hdr.resetfn = (arg_resetfn*)arg_str_resetfn; result->hdr.scanfn = (arg_scanfn*)arg_str_scanfn; result->hdr.checkfn = (arg_checkfn*)arg_str_checkfn; result->hdr.errorfn = (arg_errorfn*)arg_str_errorfn; /* store the sval[maxcount] array immediately after the arg_str struct */ result->sval = (const char**)(result + 1); result->count = 0; /* foolproof the string pointers by initializing them to reference empty strings */ for (i = 0; i < maxcount; i++) result->sval[i] = ""; ARG_TRACE(("arg_strn() returns %p\n", result)); return result; } /******************************************************************************* * arg_cmd: Provides the sub-command mechanism * * This file is part of the argtable3 library. * * Copyright (C) 2013-2019 Tom G. Huang * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #endif #include #include #include #define MAX_MODULE_VERSION_SIZE 128 static arg_hashtable_t* s_hashtable = NULL; static char* s_module_name = NULL; static int s_mod_ver_major = 0; static int s_mod_ver_minor = 0; static int s_mod_ver_patch = 0; static char* s_mod_ver_tag = NULL; static char* s_mod_ver = NULL; void arg_set_module_name(const char* name) { size_t slen; xfree(s_module_name); slen = strlen(name); s_module_name = (char*)xmalloc(slen + 1); memset(s_module_name, 0, slen + 1); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncpy_s(s_module_name, slen + 1, name, slen); #else memcpy(s_module_name, name, slen); #endif } void arg_set_module_version(int major, int minor, int patch, const char* tag) { size_t slen_tag, slen_ds; arg_dstr_t ds; s_mod_ver_major = major; s_mod_ver_minor = minor; s_mod_ver_patch = patch; xfree(s_mod_ver_tag); slen_tag = strlen(tag); s_mod_ver_tag = (char*)xmalloc(slen_tag + 1); memset(s_mod_ver_tag, 0, slen_tag + 1); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncpy_s(s_mod_ver_tag, slen_tag + 1, tag, slen_tag); #else memcpy(s_mod_ver_tag, tag, slen_tag); #endif ds = arg_dstr_create(); arg_dstr_catf(ds, "%d.", s_mod_ver_major); arg_dstr_catf(ds, "%d.", s_mod_ver_minor); arg_dstr_catf(ds, "%d.", s_mod_ver_patch); arg_dstr_cat(ds, s_mod_ver_tag); xfree(s_mod_ver); slen_ds = strlen(arg_dstr_cstr(ds)); s_mod_ver = (char*)xmalloc(slen_ds + 1); memset(s_mod_ver, 0, slen_ds + 1); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncpy_s(s_mod_ver, slen_ds + 1, arg_dstr_cstr(ds), slen_ds); #else memcpy(s_mod_ver, arg_dstr_cstr(ds), slen_ds); #endif arg_dstr_destroy(ds); } static unsigned int hash_key(const void* key) { const char* str = (const char*)key; int c; unsigned int hash = 5381; while ((c = *str++) != 0) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } static int equal_keys(const void* key1, const void* key2) { char* k1 = (char*)key1; char* k2 = (char*)key2; return (0 == strcmp(k1, k2)); } void arg_cmd_init(void) { s_hashtable = arg_hashtable_create(32, hash_key, equal_keys); } void arg_cmd_uninit(void) { arg_hashtable_destroy(s_hashtable, 1); } void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description) { arg_cmd_info_t* cmd_info; size_t slen_name; void* k; assert(strlen(name) < ARG_CMD_NAME_LEN); assert(strlen(description) < ARG_CMD_DESCRIPTION_LEN); /* Check if the command already exists. */ /* If the command exists, replace the existing command. */ /* If the command doesn't exist, insert the command. */ cmd_info = (arg_cmd_info_t*)arg_hashtable_search(s_hashtable, name); if (cmd_info) { arg_hashtable_remove(s_hashtable, name); cmd_info = NULL; } cmd_info = (arg_cmd_info_t*)xmalloc(sizeof(arg_cmd_info_t)); memset(cmd_info, 0, sizeof(arg_cmd_info_t)); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncpy_s(cmd_info->name, ARG_CMD_NAME_LEN, name, strlen(name)); strncpy_s(cmd_info->description, ARG_CMD_DESCRIPTION_LEN, description, strlen(description)); #else memcpy(cmd_info->name, name, strlen(name)); memcpy(cmd_info->description, description, strlen(description)); #endif cmd_info->proc = proc; slen_name = strlen(name); k = xmalloc(slen_name + 1); memset(k, 0, slen_name + 1); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncpy_s((char*)k, slen_name + 1, name, slen_name); #else memcpy((char*)k, name, slen_name); #endif arg_hashtable_insert(s_hashtable, k, cmd_info); } void arg_cmd_unregister(const char* name) { arg_hashtable_remove(s_hashtable, name); } int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res) { arg_cmd_info_t* cmd_info = arg_cmd_info(name); assert(cmd_info != NULL); assert(cmd_info->proc != NULL); return cmd_info->proc(argc, argv, res); } arg_cmd_info_t* arg_cmd_info(const char* name) { return (arg_cmd_info_t*)arg_hashtable_search(s_hashtable, name); } unsigned int arg_cmd_count(void) { return arg_hashtable_count(s_hashtable); } arg_cmd_itr_t arg_cmd_itr_create(void) { return (arg_cmd_itr_t)arg_hashtable_itr_create(s_hashtable); } int arg_cmd_itr_advance(arg_cmd_itr_t itr) { return arg_hashtable_itr_advance((arg_hashtable_itr_t*)itr); } char* arg_cmd_itr_key(arg_cmd_itr_t itr) { return (char*)arg_hashtable_itr_key((arg_hashtable_itr_t*)itr); } arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr) { return (arg_cmd_info_t*)arg_hashtable_itr_value((arg_hashtable_itr_t*)itr); } void arg_cmd_itr_destroy(arg_cmd_itr_t itr) { arg_hashtable_itr_destroy((arg_hashtable_itr_t*)itr); } int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k) { return arg_hashtable_itr_search((arg_hashtable_itr_t*)itr, s_hashtable, k); } static const char* module_name(void) { if (s_module_name == NULL || strlen(s_module_name) == 0) return ""; return s_module_name; } static const char* module_version(void) { if (s_mod_ver == NULL || strlen(s_mod_ver) == 0) return "0.0.0.0"; return s_mod_ver; } void arg_make_get_help_msg(arg_dstr_t res) { arg_dstr_catf(res, "%s v%s\n", module_name(), module_version()); arg_dstr_catf(res, "Please type '%s help' to get more information.\n", module_name()); } void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable) { arg_cmd_info_t* cmd_info = (arg_cmd_info_t*)arg_hashtable_search(s_hashtable, cmd_name); if (cmd_info) { arg_dstr_catf(ds, "%s: %s\n", cmd_name, cmd_info->description); } arg_dstr_cat(ds, "Usage:\n"); arg_dstr_catf(ds, " %s", module_name()); arg_print_syntaxv_ds(ds, argtable, "\n \nAvailable options:\n"); arg_print_glossary_ds(ds, argtable, " %-23s %s\n"); arg_dstr_cat(ds, "\n"); } void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end) { arg_print_errors_ds(ds, end, module_name()); arg_dstr_cat(ds, "Usage: \n"); arg_dstr_catf(ds, " %s", module_name()); arg_print_syntaxv_ds(ds, argtable, "\n"); arg_dstr_cat(ds, "\n"); } int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode) { /* help handling * note: '-h|--help' takes precedence over error reporting */ if (help > 0) { arg_make_help_msg(ds, name, argtable); *exitcode = EXIT_SUCCESS; return 1; } /* syntax error handling */ if (nerrors > 0) { arg_make_syntax_err_msg(ds, argtable, end); *exitcode = EXIT_FAILURE; return 1; } return 0; } /******************************************************************************* * argtable3: Implements the main interfaces of the library * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #include "argtable3.h" #ifndef ARG_AMALGAMATION #include "argtable3_private.h" #if ARG_REPLACE_GETOPT == 1 #include "arg_getopt.h" #else #include #endif #else #if ARG_REPLACE_GETOPT == 0 #include #endif #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #undef WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include static void arg_register_error(struct arg_end* end, void* parent, int error, const char* argval) { /* printf("arg_register_error(%p,%p,%d,%s)\n",end,parent,error,argval); */ if (end->count < end->hdr.maxcount) { end->error[end->count] = error; end->parent[end->count] = parent; end->argval[end->count] = argval; end->count++; } else { end->error[end->hdr.maxcount - 1] = ARG_ELIMIT; end->parent[end->hdr.maxcount - 1] = end; end->argval[end->hdr.maxcount - 1] = NULL; } } /* * Return index of first table entry with a matching short option * or -1 if no match was found. */ static int find_shortoption(struct arg_hdr** table, char shortopt) { int tabindex; for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { if (table[tabindex]->shortopts && strchr(table[tabindex]->shortopts, shortopt)) return tabindex; } return -1; } struct longoptions { int getoptval; int noptions; struct option* options; }; #if 0 static void dump_longoptions(struct longoptions * longoptions) { int i; printf("getoptval = %d\n", longoptions->getoptval); printf("noptions = %d\n", longoptions->noptions); for (i = 0; i < longoptions->noptions; i++) { printf("options[%d].name = \"%s\"\n", i, longoptions->options[i].name); printf("options[%d].has_arg = %d\n", i, longoptions->options[i].has_arg); printf("options[%d].flag = %p\n", i, longoptions->options[i].flag); printf("options[%d].val = %d\n", i, longoptions->options[i].val); } } #endif static struct longoptions* alloc_longoptions(struct arg_hdr** table) { struct longoptions* result; size_t nbytes; int noptions = 1; size_t longoptlen = 0; int tabindex; int option_index = 0; char* store; /* * Determine the total number of option structs required * by counting the number of comma separated long options * in all table entries and return the count in noptions. * note: noptions starts at 1 not 0 because we getoptlong * requires a NULL option entry to terminate the option array. * While we are at it, count the number of chars required * to store private copies of all the longoption strings * and return that count in logoptlen. */ tabindex = 0; do { const char* longopts = table[tabindex]->longopts; longoptlen += (longopts ? strlen(longopts) : 0) + 1; while (longopts) { noptions++; longopts = strchr(longopts + 1, ','); } } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); /*printf("%d long options consuming %d chars in total\n",noptions,longoptlen);*/ /* allocate storage for return data structure as: */ /* (struct longoptions) + (struct options)[noptions] + char[longoptlen] */ nbytes = sizeof(struct longoptions) + sizeof(struct option) * noptions + longoptlen; result = (struct longoptions*)xmalloc(nbytes); result->getoptval = 0; result->noptions = noptions; result->options = (struct option*)(result + 1); store = (char*)(result->options + noptions); for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { const char* longopts = table[tabindex]->longopts; while (longopts && *longopts) { char* storestart = store; /* copy progressive longopt strings into the store */ while (*longopts != 0 && *longopts != ',') *store++ = *longopts++; *store++ = 0; if (*longopts == ',') longopts++; /*fprintf(stderr,"storestart=\"%s\"\n",storestart);*/ result->options[option_index].name = storestart; result->options[option_index].flag = &(result->getoptval); result->options[option_index].val = tabindex; if (table[tabindex]->flag & ARG_HASOPTVALUE) result->options[option_index].has_arg = 2; else if (table[tabindex]->flag & ARG_HASVALUE) result->options[option_index].has_arg = 1; else result->options[option_index].has_arg = 0; option_index++; } } /* terminate the options array with a zero-filled entry */ result->options[option_index].name = 0; result->options[option_index].has_arg = 0; result->options[option_index].flag = 0; result->options[option_index].val = 0; /*dump_longoptions(result);*/ return result; } static char* alloc_shortoptions(struct arg_hdr** table) { char* result; size_t len = 2; int tabindex; char* res; /* determine the total number of option chars required */ for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { struct arg_hdr* hdr = table[tabindex]; len += 3 * (hdr->shortopts ? strlen(hdr->shortopts) : 0); } result = xmalloc(len); res = result; /* add a leading ':' so getopt return codes distinguish */ /* unrecognised option and options missing argument values */ *res++ = ':'; for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { struct arg_hdr* hdr = table[tabindex]; const char* shortopts = hdr->shortopts; while (shortopts && *shortopts) { *res++ = *shortopts++; if (hdr->flag & ARG_HASVALUE) *res++ = ':'; if (hdr->flag & ARG_HASOPTVALUE) *res++ = ':'; } } /* null terminate the string */ *res = 0; /*printf("alloc_shortoptions() returns \"%s\"\n",(result?result:"NULL"));*/ return result; } /* return index of the table terminator entry */ static int arg_endindex(struct arg_hdr** table) { int tabindex = 0; while (!(table[tabindex]->flag & ARG_TERMINATOR)) tabindex++; return tabindex; } static void arg_parse_tagged(int argc, char** argv, struct arg_hdr** table, struct arg_end* endtable) { struct longoptions* longoptions; char* shortoptions; int copt; /*printf("arg_parse_tagged(%d,%p,%p,%p)\n",argc,argv,table,endtable);*/ /* allocate short and long option arrays for the given opttable[]. */ /* if the allocs fail then put an error msg in the last table entry. */ longoptions = alloc_longoptions(table); shortoptions = alloc_shortoptions(table); /*dump_longoptions(longoptions);*/ /* reset getopts internal option-index to zero, and disable error reporting */ optind = 0; opterr = 0; /* fetch and process args using getopt_long */ #ifdef ARG_LONG_ONLY while ((copt = getopt_long_only(argc, argv, shortoptions, longoptions->options, NULL)) != -1) { #else while ((copt = getopt_long(argc, argv, shortoptions, longoptions->options, NULL)) != -1) { #endif /* printf("optarg='%s'\n",optarg); printf("optind=%d\n",optind); printf("copt=%c\n",(char)copt); printf("optopt=%c (%d)\n",optopt, (int)(optopt)); */ switch (copt) { case 0: { int tabindex = longoptions->getoptval; void* parent = table[tabindex]->parent; /*printf("long option detected from argtable[%d]\n", tabindex);*/ if (optarg && optarg[0] == 0 && (table[tabindex]->flag & ARG_HASVALUE)) { /* printf(": long option %s requires an argument\n",argv[optind-1]); */ arg_register_error(endtable, endtable, ARG_EMISSARG, argv[optind - 1]); /* continue to scan the (empty) argument value to enforce argument count checking */ } if (table[tabindex]->scanfn) { int errorcode = table[tabindex]->scanfn(parent, optarg); if (errorcode != 0) arg_register_error(endtable, parent, errorcode, optarg); } } break; case '?': /* * getopt_long() found an unrecognised short option. * if it was a short option its value is in optopt * if it was a long option then optopt=0 */ switch (optopt) { case 0: /*printf("?0 unrecognised long option %s\n",argv[optind-1]);*/ arg_register_error(endtable, endtable, ARG_ELONGOPT, argv[optind - 1]); break; default: /*printf("?* unrecognised short option '%c'\n",optopt);*/ arg_register_error(endtable, endtable, optopt, NULL); break; } break; case ':': /* * getopt_long() found an option with its argument missing. */ /*printf(": option %s requires an argument\n",argv[optind-1]); */ arg_register_error(endtable, endtable, ARG_EMISSARG, argv[optind - 1]); break; default: { /* getopt_long() found a valid short option */ int tabindex = find_shortoption(table, (char)copt); /*printf("short option detected from argtable[%d]\n", tabindex);*/ if (tabindex == -1) { /* should never get here - but handle it just in case */ /*printf("unrecognised short option %d\n",copt);*/ arg_register_error(endtable, endtable, copt, NULL); } else { if (table[tabindex]->scanfn) { void* parent = table[tabindex]->parent; int errorcode = table[tabindex]->scanfn(parent, optarg); if (errorcode != 0) arg_register_error(endtable, parent, errorcode, optarg); } } break; } } } xfree(shortoptions); xfree(longoptions); } static void arg_parse_untagged(int argc, char** argv, struct arg_hdr** table, struct arg_end* endtable) { int tabindex = 0; int errorlast = 0; const char* optarglast = NULL; void* parentlast = NULL; /*printf("arg_parse_untagged(%d,%p,%p,%p)\n",argc,argv,table,endtable);*/ while (!(table[tabindex]->flag & ARG_TERMINATOR)) { void* parent; int errorcode; /* if we have exhausted our argv[optind] entries then we have finished */ if (optind >= argc) { /*printf("arg_parse_untagged(): argv[] exhausted\n");*/ return; } /* skip table entries with non-null long or short options (they are not untagged entries) */ if (table[tabindex]->longopts || table[tabindex]->shortopts) { /*printf("arg_parse_untagged(): skipping argtable[%d] (tagged argument)\n",tabindex);*/ tabindex++; continue; } /* skip table entries with NULL scanfn */ if (!(table[tabindex]->scanfn)) { /*printf("arg_parse_untagged(): skipping argtable[%d] (NULL scanfn)\n",tabindex);*/ tabindex++; continue; } /* attempt to scan the current argv[optind] with the current */ /* table[tabindex] entry. If it succeeds then keep it, otherwise */ /* try again with the next table[] entry. */ parent = table[tabindex]->parent; errorcode = table[tabindex]->scanfn(parent, argv[optind]); if (errorcode == 0) { /* success, move onto next argv[optind] but stay with same table[tabindex] */ /*printf("arg_parse_untagged(): argtable[%d] successfully matched\n",tabindex);*/ optind++; /* clear the last tentative error */ errorlast = 0; } else { /* failure, try same argv[optind] with next table[tabindex] entry */ /*printf("arg_parse_untagged(): argtable[%d] failed match\n",tabindex);*/ tabindex++; /* remember this as a tentative error we may wish to reinstate later */ errorlast = errorcode; optarglast = argv[optind]; parentlast = parent; } } /* if a tenative error still remains at this point then register it as a proper error */ if (errorlast) { arg_register_error(endtable, parentlast, errorlast, optarglast); optind++; } /* only get here when not all argv[] entries were consumed */ /* register an error for each unused argv[] entry */ while (optind < argc) { /*printf("arg_parse_untagged(): argv[%d]=\"%s\" not consumed\n",optind,argv[optind]);*/ arg_register_error(endtable, endtable, ARG_ENOMATCH, argv[optind++]); } return; } static void arg_parse_check(struct arg_hdr** table, struct arg_end* endtable) { int tabindex = 0; /* printf("arg_parse_check()\n"); */ do { if (table[tabindex]->checkfn) { void* parent = table[tabindex]->parent; int errorcode = table[tabindex]->checkfn(parent); if (errorcode != 0) arg_register_error(endtable, parent, errorcode, NULL); } } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); } static void arg_reset(void** argtable) { struct arg_hdr** table = (struct arg_hdr**)argtable; int tabindex = 0; /*printf("arg_reset(%p)\n",argtable);*/ do { if (table[tabindex]->resetfn) table[tabindex]->resetfn(table[tabindex]->parent); } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); } int arg_parse(int argc, char** argv, void** argtable) { struct arg_hdr** table = (struct arg_hdr**)argtable; struct arg_end* endtable; int endindex; char** argvcopy = NULL; int i; /*printf("arg_parse(%d,%p,%p)\n",argc,argv,argtable);*/ /* reset any argtable data from previous invocations */ arg_reset(argtable); /* locate the first end-of-table marker within the array */ endindex = arg_endindex(table); endtable = (struct arg_end*)table[endindex]; /* Special case of argc==0. This can occur on Texas Instruments DSP. */ /* Failure to trap this case results in an unwanted NULL result from */ /* the malloc for argvcopy (next code block). */ if (argc == 0) { /* We must still perform post-parse checks despite the absence of command line arguments */ arg_parse_check(table, endtable); /* Now we are finished */ return endtable->count; } argvcopy = (char**)xmalloc(sizeof(char*) * (argc + 1)); /* Fill in the local copy of argv[]. We need a local copy because getopt rearranges argv[] which adversely affects susbsequent parsing attempts. */ for (i = 0; i < argc; i++) argvcopy[i] = argv[i]; argvcopy[argc] = NULL; /* parse the command line (local copy) for tagged options */ arg_parse_tagged(argc, argvcopy, table, endtable); /* parse the command line (local copy) for untagged options */ arg_parse_untagged(argc, argvcopy, table, endtable); /* if no errors so far then perform post-parse checks otherwise dont bother */ if (endtable->count == 0) arg_parse_check(table, endtable); /* release the local copt of argv[] */ xfree(argvcopy); return endtable->count; } /* * Concatenate contents of src[] string onto *pdest[] string. * The *pdest pointer is altered to point to the end of the * target string and *pndest is decremented by the same number * of chars. * Does not append more than *pndest chars into *pdest[] * so as to prevent buffer overruns. * Its something like strncat() but more efficient for repeated * calls on the same destination string. * Example of use: * char dest[30] = "good" * size_t ndest = sizeof(dest); * char *pdest = dest; * arg_char(&pdest,"bye ",&ndest); * arg_char(&pdest,"cruel ",&ndest); * arg_char(&pdest,"world!",&ndest); * Results in: * dest[] == "goodbye cruel world!" * ndest == 10 */ static void arg_cat(char** pdest, const char* src, size_t* pndest) { char* dest = *pdest; char* end = dest + *pndest; /*locate null terminator of dest string */ while (dest < end && *dest != 0) dest++; /* concat src string to dest string */ while (dest < end && *src != 0) *dest++ = *src++; /* null terminate dest string */ *dest = 0; /* update *pdest and *pndest */ *pndest = end - dest; *pdest = dest; } static void arg_cat_option(char* dest, size_t ndest, const char* shortopts, const char* longopts, const char* datatype, int optvalue) { if (shortopts) { char option[3]; /* note: option array[] is initialiazed dynamically here to satisfy */ /* a deficiency in the watcom compiler wrt static array initializers. */ option[0] = '-'; option[1] = shortopts[0]; option[2] = 0; arg_cat(&dest, option, &ndest); if (datatype) { arg_cat(&dest, " ", &ndest); if (optvalue) { arg_cat(&dest, "[", &ndest); arg_cat(&dest, datatype, &ndest); arg_cat(&dest, "]", &ndest); } else arg_cat(&dest, datatype, &ndest); } } else if (longopts) { size_t ncspn; /* add "--" tag prefix */ arg_cat(&dest, "--", &ndest); /* add comma separated option tag */ ncspn = strcspn(longopts, ","); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncat_s(dest, ndest, longopts, (ncspn < ndest) ? ncspn : ndest); #else strncat(dest, longopts, (ncspn < ndest) ? ncspn : ndest); #endif if (datatype) { arg_cat(&dest, "=", &ndest); if (optvalue) { arg_cat(&dest, "[", &ndest); arg_cat(&dest, datatype, &ndest); arg_cat(&dest, "]", &ndest); } else arg_cat(&dest, datatype, &ndest); } } else if (datatype) { if (optvalue) { arg_cat(&dest, "[", &ndest); arg_cat(&dest, datatype, &ndest); arg_cat(&dest, "]", &ndest); } else arg_cat(&dest, datatype, &ndest); } } static void arg_cat_optionv(char* dest, size_t ndest, const char* shortopts, const char* longopts, const char* datatype, int optvalue, const char* separator) { separator = separator ? separator : ""; if (shortopts) { const char* c = shortopts; while (*c) { /* "-a|-b|-c" */ char shortopt[3]; /* note: shortopt array[] is initialiazed dynamically here to satisfy */ /* a deficiency in the watcom compiler wrt static array initializers. */ shortopt[0] = '-'; shortopt[1] = *c; shortopt[2] = 0; arg_cat(&dest, shortopt, &ndest); if (*++c) arg_cat(&dest, separator, &ndest); } } /* put separator between long opts and short opts */ if (shortopts && longopts) arg_cat(&dest, separator, &ndest); if (longopts) { const char* c = longopts; while (*c) { size_t ncspn; /* add "--" tag prefix */ arg_cat(&dest, "--", &ndest); /* add comma separated option tag */ ncspn = strcspn(c, ","); #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || (defined(__STDC_SECURE_LIB__) && defined(__STDC_WANT_SECURE_LIB__)) strncat_s(dest, ndest, c, (ncspn < ndest) ? ncspn : ndest); #else strncat(dest, c, (ncspn < ndest) ? ncspn : ndest); #endif c += ncspn; /* add given separator in place of comma */ if (*c == ',') { arg_cat(&dest, separator, &ndest); c++; } } } if (datatype) { if (longopts) arg_cat(&dest, "=", &ndest); else if (shortopts) arg_cat(&dest, " ", &ndest); if (optvalue) { arg_cat(&dest, "[", &ndest); arg_cat(&dest, datatype, &ndest); arg_cat(&dest, "]", &ndest); } else arg_cat(&dest, datatype, &ndest); } } void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix) { char syntax[200] = ""; suffix = suffix ? suffix : ""; /* there is no way of passing the proper optvalue for optional argument values here, so we must ignore it */ arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, 0, "|"); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, (char*)suffix); } /* this function should be deprecated because it doesn't consider optional argument values (ARG_HASOPTVALUE) */ void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix) { arg_dstr_t ds = arg_dstr_create(); arg_print_option_ds(ds, shortopts, longopts, datatype, suffix); fputs(arg_dstr_cstr(ds), fp); arg_dstr_destroy(ds); } /* * Print a GNU style [OPTION] string in which all short options that * do not take argument values are presented in abbreviated form, as * in: -xvfsd, or -xvf[sd], or [-xvsfd] */ static void arg_print_gnuswitch_ds(arg_dstr_t ds, struct arg_hdr** table) { int tabindex; char* format1 = " -%c"; char* format2 = " [-%c"; char* suffix = ""; /* print all mandatory switches that are without argument values */ for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { /* skip optional options */ if (table[tabindex]->mincount < 1) continue; /* skip non-short options */ if (table[tabindex]->shortopts == NULL) continue; /* skip options that take argument values */ if (table[tabindex]->flag & ARG_HASVALUE) continue; /* print the short option (only the first short option char, ignore multiple choices)*/ arg_dstr_catf(ds, format1, table[tabindex]->shortopts[0]); format1 = "%c"; format2 = "[%c"; } /* print all optional switches that are without argument values */ for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { /* skip mandatory args */ if (table[tabindex]->mincount > 0) continue; /* skip args without short options */ if (table[tabindex]->shortopts == NULL) continue; /* skip args with values */ if (table[tabindex]->flag & ARG_HASVALUE) continue; /* print first short option */ arg_dstr_catf(ds, format2, table[tabindex]->shortopts[0]); format2 = "%c"; suffix = "]"; } arg_dstr_catf(ds, "%s", suffix); } void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix) { struct arg_hdr** table = (struct arg_hdr**)argtable; int i, tabindex; /* print GNU style [OPTION] string */ arg_print_gnuswitch_ds(ds, table); /* print remaining options in abbreviated style */ for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { char syntax[200] = ""; const char *shortopts, *longopts, *datatype; /* skip short options without arg values (they were printed by arg_print_gnu_switch) */ if (table[tabindex]->shortopts && !(table[tabindex]->flag & ARG_HASVALUE)) continue; shortopts = table[tabindex]->shortopts; longopts = table[tabindex]->longopts; datatype = table[tabindex]->datatype; arg_cat_option(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE); if (strlen(syntax) > 0) { /* print mandatory instances of this option */ for (i = 0; i < table[tabindex]->mincount; i++) { arg_dstr_cat(ds, " "); arg_dstr_cat(ds, syntax); } /* print optional instances enclosed in "[..]" */ switch (table[tabindex]->maxcount - table[tabindex]->mincount) { case 0: break; case 1: arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]"); break; case 2: arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]"); arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]"); break; default: arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]..."); break; } } } if (suffix) { arg_dstr_cat(ds, (char*)suffix); } } void arg_print_syntax(FILE* fp, void** argtable, const char* suffix) { arg_dstr_t ds = arg_dstr_create(); arg_print_syntax_ds(ds, argtable, suffix); fputs(arg_dstr_cstr(ds), fp); arg_dstr_destroy(ds); } void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix) { struct arg_hdr** table = (struct arg_hdr**)argtable; int i, tabindex; /* print remaining options in abbreviated style */ for (tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { char syntax[200] = ""; const char *shortopts, *longopts, *datatype; shortopts = table[tabindex]->shortopts; longopts = table[tabindex]->longopts; datatype = table[tabindex]->datatype; arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE, "|"); /* print mandatory options */ for (i = 0; i < table[tabindex]->mincount; i++) { arg_dstr_cat(ds, " "); arg_dstr_cat(ds, syntax); } /* print optional args enclosed in "[..]" */ switch (table[tabindex]->maxcount - table[tabindex]->mincount) { case 0: break; case 1: arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]"); break; case 2: arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]"); arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]"); break; default: arg_dstr_cat(ds, " ["); arg_dstr_cat(ds, syntax); arg_dstr_cat(ds, "]..."); break; } } if (suffix) { arg_dstr_cat(ds, (char*)suffix); } } void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix) { arg_dstr_t ds = arg_dstr_create(); arg_print_syntaxv_ds(ds, argtable, suffix); fputs(arg_dstr_cstr(ds), fp); arg_dstr_destroy(ds); } void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format) { struct arg_hdr** table = (struct arg_hdr**)argtable; int tabindex; format = format ? format : " %-20s %s\n"; for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { if (table[tabindex]->glossary) { char syntax[200] = ""; const char* shortopts = table[tabindex]->shortopts; const char* longopts = table[tabindex]->longopts; const char* datatype = table[tabindex]->datatype; const char* glossary = table[tabindex]->glossary; arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE, ", "); arg_dstr_catf(ds, format, syntax, glossary); } } } void arg_print_glossary(FILE* fp, void** argtable, const char* format) { arg_dstr_t ds = arg_dstr_create(); arg_print_glossary_ds(ds, argtable, format); fputs(arg_dstr_cstr(ds), fp); arg_dstr_destroy(ds); } /** * Print a piece of text formatted, which means in a column with a * left and a right margin. The lines are wrapped at whitspaces next * to right margin. The function does not indent the first line, but * only the following ones. * * Example: * arg_print_formatted( fp, 0, 5, "Some text that doesn't fit." ) * will result in the following output: * * Some * text * that * doesn' * t fit. * * Too long lines will be wrapped in the middle of a word. * * arg_print_formatted( fp, 2, 7, "Some text that doesn't fit." ) * will result in the following output: * * Some * text * that * doesn' * t fit. * * As you see, the first line is not indented. This enables output of * lines, which start in a line where output already happened. * * Author: Uli Fouquet */ static void arg_print_formatted_ds(arg_dstr_t ds, const unsigned lmargin, const unsigned rmargin, const char* text) { const unsigned int textlen = (unsigned int)strlen(text); unsigned int line_start = 0; unsigned int line_end = textlen; const unsigned int colwidth = (rmargin - lmargin) + 1; assert(strlen(text) < UINT_MAX); /* Someone doesn't like us... */ if (line_end < line_start) { arg_dstr_catf(ds, "%s\n", text); } while (line_end > line_start) { /* Eat leading white spaces. This is essential because while wrapping lines, there will often be a whitespace at beginning of line */ while (isspace((int)(*(text + line_start)))) { line_start++; } /* Find last whitespace, that fits into line */ if (line_end - line_start > colwidth) { line_end = line_start + colwidth; while ((line_end > line_start) && !isspace((int)(*(text + line_end)))) { line_end--; } /* Consume trailing spaces */ while ((line_end > line_start) && isspace((int)(*(text + line_end)))) { line_end--; } /* Restore the last non-space character */ line_end++; } /* Output line of text */ while (line_start < line_end) { char c = *(text + line_start); arg_dstr_catc(ds, c); line_start++; } arg_dstr_cat(ds, "\n"); /* Initialize another line */ if (line_end < textlen) { unsigned i; for (i = 0; i < lmargin; i++) { arg_dstr_cat(ds, " "); } line_end = textlen; } } /* lines of text */ } /** * Prints the glossary in strict GNU format. * Differences to arg_print_glossary() are: * - wraps lines after 80 chars * - indents lines without shortops * - does not accept formatstrings * * Contributed by Uli Fouquet */ void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable) { struct arg_hdr** table = (struct arg_hdr**)argtable; int tabindex; for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) { if (table[tabindex]->glossary) { char syntax[200] = ""; const char* shortopts = table[tabindex]->shortopts; const char* longopts = table[tabindex]->longopts; const char* datatype = table[tabindex]->datatype; const char* glossary = table[tabindex]->glossary; if (!shortopts && longopts) { /* Indent trailing line by 4 spaces... */ memset(syntax, ' ', 4); *(syntax + 4) = '\0'; } arg_cat_optionv(syntax, sizeof(syntax) - 1, shortopts, longopts, datatype, table[tabindex]->flag & ARG_HASOPTVALUE, ", "); /* If syntax fits not into column, print glossary in new line... */ if (strlen(syntax) > 25) { arg_dstr_catf(ds, " %-25s %s\n", syntax, ""); *syntax = '\0'; } arg_dstr_catf(ds, " %-25s ", syntax); arg_print_formatted_ds(ds, 28, 79, glossary); } } /* for each table entry */ arg_dstr_cat(ds, "\n"); } void arg_print_glossary_gnu(FILE* fp, void** argtable) { arg_dstr_t ds = arg_dstr_create(); arg_print_glossary_gnu_ds(ds, argtable); fputs(arg_dstr_cstr(ds), fp); arg_dstr_destroy(ds); } /** * Checks the argtable[] array for NULL entries and returns 1 * if any are found, zero otherwise. */ int arg_nullcheck(void** argtable) { struct arg_hdr** table = (struct arg_hdr**)argtable; int tabindex; /*printf("arg_nullcheck(%p)\n",argtable);*/ if (!table) return 1; tabindex = 0; do { /*printf("argtable[%d]=%p\n",tabindex,argtable[tabindex]);*/ if (!table[tabindex]) return 1; } while (!(table[tabindex++]->flag & ARG_TERMINATOR)); return 0; } /* * arg_free() is deprecated in favour of arg_freetable() due to a flaw in its design. * The flaw results in memory leak in the (very rare) case that an intermediate * entry in the argtable array failed its memory allocation while others following * that entry were still allocated ok. Those subsequent allocations will not be * deallocated by arg_free(). * Despite the unlikeliness of the problem occurring, and the even unlikelier event * that it has any deliterious effect, it is fixed regardless by replacing arg_free() * with the newer arg_freetable() function. * We still keep arg_free() for backwards compatibility. */ void arg_free(void** argtable) { struct arg_hdr** table = (struct arg_hdr**)argtable; int tabindex = 0; int flag; /*printf("arg_free(%p)\n",argtable);*/ do { /* if we encounter a NULL entry then somewhat incorrectly we presume we have come to the end of the array. It isnt strictly true because an intermediate entry could be NULL with other non-NULL entries to follow. The subsequent argtable entries would then not be freed as they should. */ if (table[tabindex] == NULL) break; flag = table[tabindex]->flag; xfree(table[tabindex]); table[tabindex++] = NULL; } while (!(flag & ARG_TERMINATOR)); } /* frees each non-NULL element of argtable[], where n is the size of the number of entries in the array */ void arg_freetable(void** argtable, size_t n) { struct arg_hdr** table = (struct arg_hdr**)argtable; size_t tabindex = 0; /*printf("arg_freetable(%p)\n",argtable);*/ for (tabindex = 0; tabindex < n; tabindex++) { if (table[tabindex] == NULL) continue; xfree(table[tabindex]); table[tabindex] = NULL; }; } #ifdef _WIN32 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { return TRUE; UNREFERENCED_PARAMETER(hinstDLL); UNREFERENCED_PARAMETER(fdwReason); UNREFERENCED_PARAMETER(lpvReserved); } #endif sslh-1.22c/argtable3.h000066400000000000000000000343171411244446000145770ustar00rootroot00000000000000/******************************************************************************* * argtable3: Declares the main interfaces of the library * * This file is part of the argtable3 library. * * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of STEWART HEITMANN nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ #ifndef ARGTABLE3 #define ARGTABLE3 #include /* FILE */ #include /* struct tm */ #ifdef __cplusplus extern "C" { #endif #define ARG_REX_ICASE 1 #define ARG_DSTR_SIZE 200 #define ARG_CMD_NAME_LEN 100 #define ARG_CMD_DESCRIPTION_LEN 256 #ifndef ARG_REPLACE_GETOPT #define ARG_REPLACE_GETOPT 1 /* use the embedded getopt as the system getopt(3) */ #endif /* ARG_REPLACE_GETOPT */ /* bit masks for arg_hdr.flag */ enum { ARG_TERMINATOR = 0x1, ARG_HASVALUE = 0x2, ARG_HASOPTVALUE = 0x4 }; #if defined(_WIN32) #if defined(argtable3_EXPORTS) #define ARG_EXTERN __declspec(dllexport) #elif defined(argtable3_IMPORTS) #define ARG_EXTERN __declspec(dllimport) #else #define ARG_EXTERN #endif #else #define ARG_EXTERN #endif typedef struct _internal_arg_dstr* arg_dstr_t; typedef void* arg_cmd_itr_t; typedef void(arg_resetfn)(void* parent); typedef int(arg_scanfn)(void* parent, const char* argval); typedef int(arg_checkfn)(void* parent); typedef void(arg_errorfn)(void* parent, arg_dstr_t ds, int error, const char* argval, const char* progname); typedef void(arg_dstr_freefn)(char* buf); typedef int(arg_cmdfn)(int argc, char* argv[], arg_dstr_t res); typedef int(arg_comparefn)(const void* k1, const void* k2); /* * The arg_hdr struct defines properties that are common to all arg_xxx structs. * The argtable library requires each arg_xxx struct to have an arg_hdr * struct as its first data member. * The argtable library functions then use this data to identify the * properties of the command line option, such as its option tags, * datatype string, and glossary strings, and so on. * Moreover, the arg_hdr struct contains pointers to custom functions that * are provided by each arg_xxx struct which perform the tasks of parsing * that particular arg_xxx arguments, performing post-parse checks, and * reporting errors. * These functions are private to the individual arg_xxx source code * and are the pointer to them are initiliased by that arg_xxx struct's * constructor function. The user could alter them after construction * if desired, but the original intention is for them to be set by the * constructor and left unaltered. */ typedef struct arg_hdr { char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */ const char* shortopts; /* String defining the short options */ const char* longopts; /* String defiing the long options */ const char* datatype; /* Description of the argument data type */ const char* glossary; /* Description of the option as shown by arg_print_glossary function */ int mincount; /* Minimum number of occurences of this option accepted */ int maxcount; /* Maximum number of occurences if this option accepted */ void* parent; /* Pointer to parent arg_xxx struct */ arg_resetfn* resetfn; /* Pointer to parent arg_xxx reset function */ arg_scanfn* scanfn; /* Pointer to parent arg_xxx scan function */ arg_checkfn* checkfn; /* Pointer to parent arg_xxx check function */ arg_errorfn* errorfn; /* Pointer to parent arg_xxx error function */ void* priv; /* Pointer to private header data for use by arg_xxx functions */ } arg_hdr_t; typedef struct arg_rem { struct arg_hdr hdr; /* The mandatory argtable header struct */ } arg_rem_t; typedef struct arg_lit { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of matching command line args */ } arg_lit_t; typedef struct arg_int { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of matching command line args */ int* ival; /* Array of parsed argument values */ } arg_int_t; typedef struct arg_dbl { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of matching command line args */ double* dval; /* Array of parsed argument values */ } arg_dbl_t; typedef struct arg_str { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of matching command line args */ const char** sval; /* Array of parsed argument values */ } arg_str_t; typedef struct arg_rex { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of matching command line args */ const char** sval; /* Array of parsed argument values */ } arg_rex_t; typedef struct arg_file { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of matching command line args*/ const char** filename; /* Array of parsed filenames (eg: /home/foo.bar) */ const char** basename; /* Array of parsed basenames (eg: foo.bar) */ const char** extension; /* Array of parsed extensions (eg: .bar) */ } arg_file_t; typedef struct arg_date { struct arg_hdr hdr; /* The mandatory argtable header struct */ const char* format; /* strptime format string used to parse the date */ int count; /* Number of matching command line args */ struct tm* tmval; /* Array of parsed time values */ } arg_date_t; enum { ARG_ELIMIT = 1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG }; typedef struct arg_end { struct arg_hdr hdr; /* The mandatory argtable header struct */ int count; /* Number of errors encountered */ int* error; /* Array of error codes */ void** parent; /* Array of pointers to offending arg_xxx struct */ const char** argval; /* Array of pointers to offending argv[] string */ } arg_end_t; typedef struct arg_cmd_info { char name[ARG_CMD_NAME_LEN]; char description[ARG_CMD_DESCRIPTION_LEN]; arg_cmdfn* proc; } arg_cmd_info_t; /**** arg_xxx constructor functions *********************************/ ARG_EXTERN struct arg_rem* arg_rem(const char* datatype, const char* glossary); ARG_EXTERN struct arg_lit* arg_lit0(const char* shortopts, const char* longopts, const char* glossary); ARG_EXTERN struct arg_lit* arg_lit1(const char* shortopts, const char* longopts, const char* glossary); ARG_EXTERN struct arg_lit* arg_litn(const char* shortopts, const char* longopts, int mincount, int maxcount, const char* glossary); ARG_EXTERN struct arg_int* arg_int0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_int* arg_int1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_int* arg_intn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); ARG_EXTERN struct arg_dbl* arg_dbl0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_dbl* arg_dbl1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_dbl* arg_dbln(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); ARG_EXTERN struct arg_str* arg_str0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_str* arg_str1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_str* arg_strn(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); ARG_EXTERN struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary); ARG_EXTERN struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary); ARG_EXTERN struct arg_rex* arg_rexn(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int mincount, int maxcount, int flags, const char* glossary); ARG_EXTERN struct arg_file* arg_file0(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_file* arg_file1(const char* shortopts, const char* longopts, const char* datatype, const char* glossary); ARG_EXTERN struct arg_file* arg_filen(const char* shortopts, const char* longopts, const char* datatype, int mincount, int maxcount, const char* glossary); ARG_EXTERN struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary); ARG_EXTERN struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary); ARG_EXTERN struct arg_date* arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary); ARG_EXTERN struct arg_end* arg_end(int maxerrors); #define ARG_DSTR_STATIC ((arg_dstr_freefn*)0) #define ARG_DSTR_VOLATILE ((arg_dstr_freefn*)1) #define ARG_DSTR_DYNAMIC ((arg_dstr_freefn*)3) /**** other functions *******************************************/ ARG_EXTERN int arg_nullcheck(void** argtable); ARG_EXTERN int arg_parse(int argc, char** argv, void** argtable); ARG_EXTERN void arg_print_option(FILE* fp, const char* shortopts, const char* longopts, const char* datatype, const char* suffix); ARG_EXTERN void arg_print_syntax(FILE* fp, void** argtable, const char* suffix); ARG_EXTERN void arg_print_syntaxv(FILE* fp, void** argtable, const char* suffix); ARG_EXTERN void arg_print_glossary(FILE* fp, void** argtable, const char* format); ARG_EXTERN void arg_print_glossary_gnu(FILE* fp, void** argtable); ARG_EXTERN void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname); ARG_EXTERN void arg_print_option_ds(arg_dstr_t ds, const char* shortopts, const char* longopts, const char* datatype, const char* suffix); ARG_EXTERN void arg_print_syntax_ds(arg_dstr_t ds, void** argtable, const char* suffix); ARG_EXTERN void arg_print_syntaxv_ds(arg_dstr_t ds, void** argtable, const char* suffix); ARG_EXTERN void arg_print_glossary_ds(arg_dstr_t ds, void** argtable, const char* format); ARG_EXTERN void arg_print_glossary_gnu_ds(arg_dstr_t ds, void** argtable); ARG_EXTERN void arg_print_errors_ds(arg_dstr_t ds, struct arg_end* end, const char* progname); ARG_EXTERN void arg_freetable(void** argtable, size_t n); ARG_EXTERN arg_dstr_t arg_dstr_create(void); ARG_EXTERN void arg_dstr_destroy(arg_dstr_t ds); ARG_EXTERN void arg_dstr_reset(arg_dstr_t ds); ARG_EXTERN void arg_dstr_free(arg_dstr_t ds); ARG_EXTERN void arg_dstr_set(arg_dstr_t ds, char* str, arg_dstr_freefn* free_proc); ARG_EXTERN void arg_dstr_cat(arg_dstr_t ds, const char* str); ARG_EXTERN void arg_dstr_catc(arg_dstr_t ds, char c); ARG_EXTERN void arg_dstr_catf(arg_dstr_t ds, const char* fmt, ...); ARG_EXTERN char* arg_dstr_cstr(arg_dstr_t ds); ARG_EXTERN void arg_cmd_init(void); ARG_EXTERN void arg_cmd_uninit(void); ARG_EXTERN void arg_cmd_register(const char* name, arg_cmdfn* proc, const char* description); ARG_EXTERN void arg_cmd_unregister(const char* name); ARG_EXTERN int arg_cmd_dispatch(const char* name, int argc, char* argv[], arg_dstr_t res); ARG_EXTERN unsigned int arg_cmd_count(void); ARG_EXTERN arg_cmd_info_t* arg_cmd_info(const char* name); ARG_EXTERN arg_cmd_itr_t arg_cmd_itr_create(void); ARG_EXTERN void arg_cmd_itr_destroy(arg_cmd_itr_t itr); ARG_EXTERN int arg_cmd_itr_advance(arg_cmd_itr_t itr); ARG_EXTERN char* arg_cmd_itr_key(arg_cmd_itr_t itr); ARG_EXTERN arg_cmd_info_t* arg_cmd_itr_value(arg_cmd_itr_t itr); ARG_EXTERN int arg_cmd_itr_search(arg_cmd_itr_t itr, void* k); ARG_EXTERN void arg_mgsort(void* data, int size, int esize, int i, int k, arg_comparefn* comparefn); ARG_EXTERN void arg_make_get_help_msg(arg_dstr_t res); ARG_EXTERN void arg_make_help_msg(arg_dstr_t ds, char* cmd_name, void** argtable); ARG_EXTERN void arg_make_syntax_err_msg(arg_dstr_t ds, void** argtable, struct arg_end* end); ARG_EXTERN int arg_make_syntax_err_help_msg(arg_dstr_t ds, char* name, int help, int nerrors, void** argtable, struct arg_end* end, int* exitcode); ARG_EXTERN void arg_set_module_name(const char* name); ARG_EXTERN void arg_set_module_version(int major, int minor, int patch, const char* tag); /**** deprecated functions, for back-compatibility only ********/ ARG_EXTERN void arg_free(void** argtable); #ifdef __cplusplus } #endif #endif sslh-1.22c/basic.cfg000066400000000000000000000013751411244446000143220ustar00rootroot00000000000000# This is a basic configuration file that should provide # sensible values for "standard" setup. verbose: 0; foreground: false; inetd: false; numeric: false; transparent: false; timeout: 2; user: "nobody"; pidfile: "/var/run/sslh.pid"; chroot: "/var/empty"; # Change hostname with your external address name. listen: ( { host: "thelonious"; port: "443"; } ); protocols: ( { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; }, { name: "openvpn"; host: "localhost"; port: "1194"; }, { name: "xmpp"; host: "localhost"; port: "5222"; }, { name: "http"; host: "localhost"; port: "80"; }, { name: "tls"; host: "localhost"; port: "443"; log_level: 0; }, { name: "anyprot"; host: "localhost"; port: "443"; } ); sslh-1.22c/collection.c000066400000000000000000000053261411244446000150570ustar00rootroot00000000000000/* collection.c: management of a collection of connections, for sslh-select # Copyright (C) 2021 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #include "common.h" #include "collection.h" #include "sslh-conf.h" #include "gap.h" /* Info to keep track of all connections */ struct cnx_collection { gap_array* fd2cnx; /* Array indexed by file descriptor to things in cnx[] */ }; /* Allocates and initialises a new collection of connections with at least * `len` elements. */ cnx_collection* collection_init(int len) { cnx_collection* collection; collection = malloc(sizeof(*collection)); CHECK_ALLOC(collection, "collection_init(collection)"); memset(collection, 0, sizeof(*collection)); collection->fd2cnx = gap_init(len); return collection; } /* Caveat: might not work, as has never been used */ void collection_destroy(cnx_collection* collection) { /* Caveat 2: no code to free connections yet */ gap_destroy(collection->fd2cnx); free(collection); } /* Points the file descriptor to the specified connection index */ int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd) { gap_set(collection->fd2cnx, fd, cnx); return 0; } /* Allocates a connection and inits it with specified file descriptor */ struct connection* collection_alloc_cnx_from_fd(struct cnx_collection* collection, int fd) { struct connection* cnx = malloc(sizeof(*cnx)); if (!cnx) return NULL; init_cnx(cnx); cnx->type = SOCK_STREAM; cnx->q[0].fd = fd; cnx->state = ST_PROBING; cnx->probe_timeout = time(NULL) + cfg.timeout; gap_set(collection->fd2cnx, fd, cnx); return cnx; } /* Remove a connection from the collection */ int collection_remove_cnx(cnx_collection* collection, struct connection *cnx) { if (cnx->q[0].fd != -1) gap_set(collection->fd2cnx, cnx->q[0].fd, NULL); if (cnx->q[1].fd != -1) gap_set(collection->fd2cnx, cnx->q[1].fd, NULL); free(cnx); return 0; } /* Returns the connection that contains the file descriptor */ struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd) { return gap_get(collection->fd2cnx, fd); } sslh-1.22c/collection.h000066400000000000000000000011041411244446000150520ustar00rootroot00000000000000#ifndef COLLECTION_H #define COLLECTION_H typedef struct cnx_collection cnx_collection; cnx_collection* collection_init(int len); void collection_destroy(cnx_collection* collection); struct connection* collection_alloc_cnx_from_fd(cnx_collection* collection, int fd); int collection_add_fd(cnx_collection* collection, struct connection* cnx, int fd); /* Remove a connection from the collection */ int collection_remove_cnx(cnx_collection* collection, struct connection *cnx); struct connection* collection_get_cnx_from_fd(struct cnx_collection* collection, int fd); #endif sslh-1.22c/common.c000066400000000000000000000626601411244446000142200ustar00rootroot00000000000000/* Code and variables that is common to both fork and select-based * servers. * * No code here should assume whether sockets are blocking or not. **/ #define SYSLOG_NAMES #define _GNU_SOURCE #include #include #include #include #include #include #include "common.h" #include "probe.h" #include "sslh-conf.h" /* Added to make the code compilable under CYGWIN * */ #ifndef SA_NOCLDWAIT #define SA_NOCLDWAIT 0 #endif /* Make use of systemd socket activation * */ #ifdef SYSTEMD #include #endif #ifdef LIBBSD #include #endif /* * Settings that depend on the command line or the config file */ struct sslhcfg_item cfg; struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */ static int do_syslog = 1; /* Should we syslog? controled by syslog_facility = "none" */ #ifdef LIBWRAP #include int allow_severity =0, deny_severity = 0; #endif typedef enum { CR_DIE, CR_WARN } CR_ACTION; /* check result and die, printing the offending address and error */ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall) { char buf[NI_MAXHOST]; if (res == -1) { fprintf(stderr, "%s:%s: %s\n", sprintaddr(buf, sizeof(buf), addr), syscall, strerror(errno)); if (act == CR_DIE) exit(1); } } int get_fd_sockets(struct listen_endpoint *sockfd[]) { int sd = 0; #ifdef SYSTEMD sd = sd_listen_fds(0); if (sd < 0) { fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd)); exit(1); } if (sd > 0) { int i; *sockfd = malloc(sd * sizeof(*sockfd[0])); CHECK_ALLOC(*sockfd, "malloc"); for (i = 0; i < sd; i++) { (*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i; (*sockfd)[i].type = SOCK_STREAM; } } #endif return sd; } /* Set TCP_FASTOPEN on listening socket if all client protocols support it */ int make_listen_tfo(int s) { int i, qlen = 5; /* Don't do it if not supported */ if (!TCP_FASTOPEN) return 0; /* Don't do it if any protocol does not specify it */ for (i = 0; i < cfg.protocols_len; i++) { if (! cfg.protocols[i].tfo_ok) return 0; } return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen)); } /* Starts listening on a single address * Returns a socket filehandle, or dies with message in case of major error */ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp) { struct sockaddr_storage *saddr; int sockfd, one, res; saddr = (struct sockaddr_storage*)addr->ai_addr; sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0); check_res_dump(CR_DIE, sockfd, addr, "socket"); one = 1; res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)"); res = make_listen_tfo(sockfd); check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)"); if (keepalive) { res = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one)); check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)"); } if (IP_FREEBIND) { res = setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one)); check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)"); } if (addr->ai_addr->sa_family == AF_INET6) { res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one)); check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)"); } res = bind(sockfd, addr->ai_addr, addr->ai_addrlen); check_res_dump(CR_DIE, res, addr, "bind"); if (!udp) { res = listen (sockfd, 50); check_res_dump(CR_DIE, res, addr, "listen"); } return sockfd; } /* Starts listening sockets on specified addresses. * OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects * Returns number of addresses bound */ int start_listen_sockets(struct listen_endpoint *sockfd[]) { struct addrinfo *addr, *start_addr; char buf[NI_MAXHOST]; int i, res; int num_addr = 0, keepalive = 0, udp = 0; int sd_socks = 0; sd_socks = get_fd_sockets(sockfd); if (sd_socks > 0) { return sd_socks; } *sockfd = NULL; if (cfg.verbose) fprintf(stderr, "Listening to:\n"); for (i = 0; i < cfg.listen_len; i++) { keepalive = cfg.listen[i].keepalive; udp = cfg.listen[i].is_udp; res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port); if (res) exit(4); for (addr = start_addr; addr; addr = addr->ai_next) { num_addr++; *sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0])); (*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp); (*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM; if (cfg.verbose) fprintf(stderr, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr), cfg.listen[i].keepalive ? "keepalive" : "", cfg.listen[i].is_udp ? "udp" : ""); } freeaddrinfo(start_addr); } return num_addr; } /* returns 1 if given address is on the local machine: iterate through all * network interfaces and check their addresses */ int is_same_machine(struct addrinfo* from) { struct ifaddrs *ifaddrs_p = NULL, *ifa; int match = 0; getifaddrs(&ifaddrs_p); for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) continue; if (from->ai_addr->sa_family == ifa->ifa_addr->sa_family) { int family = ifa->ifa_addr->sa_family; if (family == AF_INET) { struct sockaddr_in *from_addr = (struct sockaddr_in*)from->ai_addr; struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr; if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) { match = 1; break; } } else if (family == AF_INET6) { struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from->ai_addr; struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr; if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) { match = 1; break; } } } } freeifaddrs(ifaddrs_p); return match; } /* Transparent proxying: bind the peer address of fd to the peer address of * fd_from */ #define IP_TRANSPARENT 19 int bind_peer(int fd, int fd_from) { struct addrinfo from; struct sockaddr_storage ss; int res, trans = 1; memset(&from, 0, sizeof(from)); from.ai_addr = (struct sockaddr*)&ss; from.ai_addrlen = sizeof(ss); /* getpeername can fail with ENOTCONN if connection was dropped before we * got here */ res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen); CHECK_RES_RETURN(res, "getpeername", res); /* if the destination is the same machine, there's no need to do bind */ if (is_same_machine(&from)) return 0; #ifndef IP_BINDANY /* use IP_TRANSPARENT */ res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans)); CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT"); #else if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */ res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans)); CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res); #ifdef IPV6_BINDANY } else { /* IPv6 */ res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans)); CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res); #endif /* IPV6_BINDANY */ } #endif /* IP_TRANSPARENT / IP_BINDANY */ res = bind(fd, from.ai_addr, from.ai_addrlen); CHECK_RES_RETURN(res, "bind", res); return 0; } /* Make the file descriptor non-block */ int set_nonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL); CHECK_RES_RETURN(flags, "fcntl", -1); flags |= O_NONBLOCK; flags = fcntl(fd, F_SETFL, flags); CHECK_RES_RETURN(flags, "fcntl", -1); return flags; } /* Connect to first address that works and returns a file descriptor, or -1 if * none work. * If transparent proxying is on, use fd_from peer address on external address * of new file descriptor. */ int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking) { struct addrinfo *a, from; struct sockaddr_storage ss; char buf[NI_MAXHOST]; int fd, res, one; int transparent = cnx->proto->transparent || cfg.transparent; memset(&from, 0, sizeof(from)); from.ai_addr = (struct sockaddr*)&ss; from.ai_addrlen = sizeof(ss); res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen); CHECK_RES_RETURN(res, "getpeername", res); for (a = cnx->proto->saddr; a; a = a->ai_next) { /* When transparent, make sure both connections use the same address family */ if (transparent && a->ai_family != from.ai_addr->sa_family) continue; if (cfg.verbose) fprintf(stderr, "connecting to %s family %d len %d\n", sprintaddr(buf, sizeof(buf), a), a->ai_addr->sa_family, a->ai_addrlen); /* XXX Needs to match ai_family from fd_from when being transparent! */ fd = socket(a->ai_family, SOCK_STREAM, 0); if (fd == -1) { log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx->proto->name, strerror(errno)); } else { one = 1; setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one)); /* no need to check return value; if it's not supported, that's okay */ if (blocking == NON_BLOCKING) { set_nonblock(fd); } if (transparent) { res = bind_peer(fd, fd_from); CHECK_RES_RETURN(res, "bind_peer", res); } res = connect(fd, a->ai_addr, a->ai_addrlen); /* EINPROGRESS indicates it might take time. If it eventually * fails, it'll be caught as a failed read */ if ((res == -1) && (errno != EINPROGRESS)) { log_message(LOG_ERR, "forward to %s failed:connect: %s\n", cnx->proto->name, strerror(errno)); close(fd); continue; /* Try the next address */ } if (cnx->proto->keepalive) { res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one)); CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res); } return fd; } } return -1; } /* Store some data to write to the queue later */ int defer_write(struct queue *q, void* data, int data_size) { char *p; ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data; if (cfg.verbose) fprintf(stderr, "**** writing deferred on fd %d\n", q->fd); p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size); CHECK_ALLOC(p, "realloc"); q->begin_deferred_data = p; q->deferred_data = p + data_offset; p += data_offset + q->deferred_data_size; q->deferred_data_size += data_size; memcpy(p, data, data_size); return 0; } /* tries to flush some of the data for specified queue * Upon success, the number of bytes written is returned. * Upon failure, -1 returned (e.g. connexion closed) * */ int flush_deferred(struct queue *q) { int n; if (cfg.verbose) fprintf(stderr, "flushing deferred data to fd %d\n", q->fd); n = write(q->fd, q->deferred_data, q->deferred_data_size); if (n == -1) return n; if (n == q->deferred_data_size) { /* All has been written -- release the memory */ free(q->begin_deferred_data); q->begin_deferred_data = NULL; q->deferred_data = NULL; q->deferred_data_size = 0; } else { /* There is data left */ q->deferred_data += n; q->deferred_data_size -= n; } return n; } void init_cnx(struct connection *cnx) { memset(cnx, 0, sizeof(*cnx)); cnx->q[0].fd = -1; cnx->q[1].fd = -1; cnx->proto = NULL; } void dump_connection(struct connection *cnx) { printf("state: %d\n", cnx->state); printf("0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size); hexdump(cnx->q[0].deferred_data, cnx->q[0].deferred_data_size); printf("1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size); hexdump(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size); } /* * moves data from one fd to other * * returns number of bytes copied if success * returns 0 (FD_CNXCLOSED) if incoming socket closed * returns FD_NODATA if no data was available * returns FD_STALLED if data was read, could not be written, and has been * stored in temporary buffer. */ int fd2fd(struct queue *target_q, struct queue *from_q) { char buffer[BUFSIZ]; int target, from, size_r, size_w; target = target_q->fd; from = from_q->fd; size_r = read(from, buffer, sizeof(buffer)); if (size_r == -1) { switch (errno) { case EAGAIN: if (cfg.verbose) fprintf(stderr, "reading 0 from %d\n", from); return FD_NODATA; case ECONNRESET: case EPIPE: return FD_CNXCLOSED; } } CHECK_RES_RETURN(size_r, "read",FD_CNXCLOSED); if (size_r == 0) return FD_CNXCLOSED; size_w = write(target, buffer, size_r); /* process -1 when we know how to deal with it */ if (size_w == -1) { switch (errno) { case EAGAIN: /* write blocked: Defer data */ defer_write(target_q, buffer, size_r); return FD_STALLED; case ECONNRESET: case EPIPE: /* remote end closed -- drop the connection */ return FD_CNXCLOSED; } } else if (size_w < size_r) { /* incomplete write -- defer the rest of the data */ defer_write(target_q, buffer + size_w, size_r - size_w); return FD_STALLED; } CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED); return size_w; } /* returns a string that prints the IP and port of the sockaddr */ char* sprintaddr(char* buf, size_t size, struct addrinfo *a) { char host[NI_MAXHOST], serv[NI_MAXSERV]; int res; res = getnameinfo(a->ai_addr, a->ai_addrlen, host, sizeof(host), serv, sizeof(serv), cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 ); if (res) { log_message(LOG_ERR, "sprintaddr:getnameinfo: %s\n", gai_strerror(res)); /* Name resolution failed: do it numerically instead */ res = getnameinfo(a->ai_addr, a->ai_addrlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); /* should not fail but... */ if (res) { log_message(LOG_ERR, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res)); strcpy(host, "?"); strcpy(serv, "?"); } } snprintf(buf, size, "%s:%s", host, serv); return buf; } /* Turns a hostname and port (or service) into a list of struct addrinfo * returns 0 on success, -1 otherwise and logs error */ int resolve_split_name(struct addrinfo **out, char* host, char* serv) { struct addrinfo hint; char *end; int res; memset(&hint, 0, sizeof(hint)); hint.ai_family = PF_UNSPEC; hint.ai_socktype = SOCK_STREAM; /* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets * around IP address */ if (host[0] == '[') { end = strrchr(host, ']'); if (!end) { fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host); return -1; } host++; /* skip first bracket */ *end = 0; /* remove last bracket */ } res = getaddrinfo(host, serv, &hint, out); if (res) log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv); return res; } /* turns a "hostname:port" string into a list of struct addrinfo; out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done fullname: input string -- it gets clobbered */ void resolve_name(struct addrinfo **out, char* fullname) { char *serv, *host; int res; /* Find port */ char *sep = strrchr(fullname, ':'); if (!sep) { /* No separator: parameter is just a port */ fprintf(stderr, "%s: names must be fully specified as hostname:port\n", fullname); exit(1); } serv = sep+1; *sep = 0; host = fullname; res = resolve_split_name(out, host, serv); if (res) { fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname); if (res == EAI_SERVICE) fprintf(stderr, "(Check you have specified all ports)\n"); exit(4); } } /* Log to syslog or stderr if foreground */ void log_message(int type, const char* msg, ...) { va_list ap; va_start(ap, msg); if (cfg.foreground) vfprintf(stderr, msg, ap); va_end(ap); if (do_syslog) { va_start(ap, msg); vsyslog(type, msg, ap); va_end(ap); } } /* Fills a connection description; returns 0 on failure */ int get_connection_desc(struct connection_desc* desc, const struct connection *cnx) { int res; struct addrinfo addr; struct sockaddr_storage ss; addr.ai_addr = (struct sockaddr*)&ss; addr.ai_addrlen = sizeof(ss); res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; /* Can happen if connection drops before we get here. In that case, don't log anything (there is no connection) */ sprintaddr(desc->peer, sizeof(desc->peer), &addr); addr.ai_addrlen = sizeof(ss); res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; sprintaddr(desc->service, sizeof(desc->service), &addr); addr.ai_addrlen = sizeof(ss); res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; sprintaddr(desc->target, sizeof(desc->target), &addr); addr.ai_addrlen = sizeof(ss); res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; sprintaddr(desc->local, sizeof(desc->local), &addr); return 1; } /* syslogs who connected to where * desc: string description of the connection. if NULL, log_connection will * manage on its own * cnx: connection descriptor * */ void log_connection(struct connection_desc* desc, const struct connection *cnx) { struct connection_desc d; if (cnx->proto->log_level < 1) return; if (!desc) { desc = &d; get_connection_desc(desc, cnx); } log_message(LOG_INFO, "%s:connection from %s to %s forwarded from %s to %s\n", cnx->proto->name, desc->peer, desc->service, desc->local, desc->target); } void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx) { #ifdef LIBBSD struct connection_desc d; if (!desc) { desc = &d; get_connection_desc(desc, cnx); } setproctitle("shovel %s %s->%s => %s->%s", cnx->proto->name, desc->peer, desc->service, desc->local, desc->target); #endif } /* libwrap (tcpd): check the connection is legal. This is necessary because * the actual server will only see a connection coming from localhost and can't * apply the rules itself. * * Returns -1 if access is denied, 0 otherwise */ int check_access_rights(int in_socket, const char* service) { #ifdef LIBWRAP union { struct sockaddr saddr; struct sockaddr_storage ss; } peer; socklen_t size = sizeof(peer); char addr_str[NI_MAXHOST], host[NI_MAXHOST]; int res; res = getpeername(in_socket, &peer.saddr, &size); CHECK_RES_RETURN(res, "getpeername", res); /* extract peer address */ res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST); if (res) { if (cfg.verbose) fprintf(stderr, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res)); strcpy(addr_str, STRING_UNKNOWN); } /* extract peer name */ strcpy(host, STRING_UNKNOWN); if (!cfg.numeric) { res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD); if (res) { if (cfg.verbose) fprintf(stderr, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res)); } } if (!hosts_ctl(service, host, addr_str, STRING_UNKNOWN)) { if (cfg.verbose) fprintf(stderr, "access denied\n"); log_message(LOG_INFO, "connection from %s(%s): access denied", host, addr_str); close(in_socket); return -1; } #endif return 0; } void setup_signals(void) { int res; struct sigaction action; /* Request no SIGCHLD is sent upon termination of * the children */ memset(&action, 0, sizeof(action)); action.sa_handler = NULL; action.sa_flags = SA_NOCLDWAIT; res = sigaction(SIGCHLD, &action, NULL); CHECK_RES_DIE(res, "sigaction"); /* Set SIGTERM to exit. For some reason if it's not set explicitly, * coverage information is lost when killing the process */ memset(&action, 0, sizeof(action)); action.sa_handler = exit; res = sigaction(SIGTERM, &action, NULL); CHECK_RES_DIE(res, "sigaction"); /* Ignore SIGPIPE . */ action.sa_handler = SIG_IGN; res = sigaction(SIGPIPE, &action, NULL); CHECK_RES_DIE(res, "sigaction"); } /* Open syslog connection with appropriate banner; * banner is made up of basename(bin_name)+"[pid]" */ void setup_syslog(const char* bin_name) { char *name1, *name2; int res, fn; if (!strcmp(cfg.syslog_facility, "none")) { do_syslog = 0; return; } name1 = strdup(bin_name); res = asprintf(&name2, "%s[%d]", basename(name1), getpid()); CHECK_RES_DIE(res, "asprintf"); for (fn = 0; facilitynames[fn].c_val != -1; fn++) if (strcmp(facilitynames[fn].c_name, cfg.syslog_facility) == 0) break; if (facilitynames[fn].c_val == -1) { fprintf(stderr, "Unknown facility %s\n", cfg.syslog_facility); exit(1); } openlog(name2, LOG_CONS, facilitynames[fn].c_val); free(name1); /* Don't free name2, as openlog(3) uses it (at least in glibc) */ log_message(LOG_INFO, "%s %s started\n", server_type, VERSION); } /* Ask OS to keep capabilities over a setuid(nonzero) */ void set_keepcaps(int val) { #ifdef LIBCAP int res; res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0); if (res) { perror("prctl"); exit(1); } #endif } /* Returns true if anything requires transparent proxying. */ static int use_transparent(void) { #ifdef LIBCAP if (cfg.transparent) return 1; for (int i = 0; i < cfg.protocols_len; i++) if (cfg.protocols[i].transparent) return 1; #endif return 0; } /* set needed capabilities for effective and permitted, clear rest * IN: cap_net_admin: set to 1 to set CAP_NET_RAW * */ void set_capabilities(int cap_net_admin) { #ifdef LIBCAP int res; cap_t caps; cap_value_t cap_list[10]; int ncap = 0; if (cap_net_admin) cap_list[ncap++] = CAP_NET_RAW; caps = cap_init(); #define _cap_set_flag(flag) do { \ res = cap_clear_flag(caps, flag); \ CHECK_RES_DIE(res, "cap_clear_flag(" #flag ")"); \ if (ncap > 0) { \ res = cap_set_flag(caps, flag, ncap, cap_list, CAP_SET); \ CHECK_RES_DIE(res, "cap_set_flag(" #flag ")"); \ } \ } while(0) _cap_set_flag(CAP_EFFECTIVE); _cap_set_flag(CAP_PERMITTED); #undef _cap_set_flag res = cap_set_proc(caps); CHECK_RES_DIE(res, "cap_set_proc"); res = cap_free(caps); if (res) { perror("cap_free"); exit(1); } #endif } /* We don't want to run as root -- drop privileges if required */ void drop_privileges(const char* user_name, const char* chroot_path) { int res; struct passwd *pw = NULL; if (user_name) { pw = getpwnam(user_name); if (!pw) { fprintf(stderr, "%s: not found\n", user_name); exit(2); } if (cfg.verbose) fprintf(stderr, "turning into %s\n", user_name); } if (chroot_path) { if (cfg.verbose) fprintf(stderr, "chrooting into %s\n", chroot_path); res = chroot(chroot_path); CHECK_RES_DIE(res, "chroot"); } if (user_name) { set_keepcaps(1); /* remove extraneous groups in case we belong to several extra groups * that may have unwanted rights. If non-root when calling setgroups(), * it fails, which is fine because... we have no unwanted rights * (see POS36-C for security context) * */ setgroups(0, NULL); res = setgid(pw->pw_gid); CHECK_RES_DIE(res, "setgid"); res = setuid(pw->pw_uid); CHECK_RES_DIE(res, "setuid"); set_capabilities(use_transparent()); set_keepcaps(0); } } /* Writes my PID */ void write_pid_file(const char* pidfile) { FILE *f; f = fopen(pidfile, "w"); if (!f) { perror(pidfile); exit(3); } fprintf(f, "%d\n", getpid()); fclose(f); } sslh-1.22c/common.h000066400000000000000000000115321411244446000142150ustar00rootroot00000000000000#ifndef COMMON_H #define COMMON_H /* FD_SETSIZE is 64 on Cygwin, which is really low. Just redefining it is * enough for the macros to adapt (http://support.microsoft.com/kb/111855) */ #ifdef __CYGWIN__ #define FD_SETSIZE 4096 #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LIBCAP #include #include #endif #include "version.h" #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define CHECK_RES_DIE(res, str) \ if (res == -1) { \ fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \ perror(str); \ exit(1); \ } #define CHECK_RES_RETURN(res, str, ret) \ if (res == -1) { \ log_message(LOG_CRIT, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, str, errno, strerror(errno)); \ return ret; \ } #define CHECK_ALLOC(a, str) \ if (!a) { \ fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \ perror(str); \ exit(1); \ } #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #if 1 #define TRACE fprintf(stderr, "%s:%d\n", __FILE__, __LINE__); #else #define TRACE #endif #ifndef IP_FREEBIND #define IP_FREEBIND 0 #endif #ifndef TCP_FASTOPEN #define TCP_FASTOPEN 0 #endif #ifndef TCP_FASTOPEN_CONNECT #define TCP_FASTOPEN_CONNECT 30 /* Attempt FastOpen with connect. */ #endif enum connection_state { ST_PROBING=1, /* Waiting for timeout to find where to forward */ ST_SHOVELING /* Connexion is established */ }; /* this is used to pass protocols through the command-line parameter parsing */ #define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */ /* A 'queue' is composed of a file descriptor (which can be read from or * written to), and a queue for deferred write data */ struct queue { int fd; void *begin_deferred_data; void *deferred_data; int deferred_data_size; }; struct connection { int type; /* SOCK_DGRAM | SOCK_STREAM */ struct sslhcfg_protocols_item* proto; /* Where to connect to */ /* SOCK_STREAM */ enum connection_state state; time_t probe_timeout; /* q[0]: queue for external connection (client); * q[1]: queue for internal connection (httpd or sshd); * */ struct queue q[2]; /* SOCK_DGRAM */ struct sockaddr client_addr; /* Contains the remote client address */ socklen_t addrlen; int local_endpoint; /* Contains the local address */ time_t last_active; /* We need one local socket for each target server, so we know where to * forward server responses */ int target_sock; }; struct listen_endpoint { int socketfd; /* file descriptor of listening socket */ int type; /* SOCK_DGRAM | SOCK_STREAM */ }; #define FD_CNXCLOSED 0 #define FD_NODATA -1 #define FD_STALLED -2 /* String description of a connection */ #define MAX_NAMELENGTH (NI_MAXHOST + NI_MAXSERV + 1) struct connection_desc { char peer[MAX_NAMELENGTH], service[MAX_NAMELENGTH], local[MAX_NAMELENGTH], target[MAX_NAMELENGTH]; }; typedef enum { NON_BLOCKING = 0, BLOCKING = 1 } connect_blocking; /* common.c */ void init_cnx(struct connection *cnx); int set_nonblock(int fd); int connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking); int fd2fd(struct queue *target, struct queue *from); char* sprintaddr(char* buf, size_t size, struct addrinfo *a); void resolve_name(struct addrinfo **out, char* fullname); int get_connection_desc(struct connection_desc* desc, const struct connection *cnx); void log_connection(struct connection_desc* desc, const struct connection *cnx); void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx); int check_access_rights(int in_socket, const char* service); void setup_signals(void); void setup_syslog(const char* bin_name); void drop_privileges(const char* user_name, const char* chroot_path); void set_capabilities(int cap_net_admin); void write_pid_file(const char* pidfile); void log_message(int type, const char* msg, ...); void dump_connection(struct connection *cnx); int resolve_split_name(struct addrinfo **out, char* hostname, char* port); int start_listen_sockets(struct listen_endpoint *sockfd[]); int defer_write(struct queue *q, void* data, int data_size); int flush_deferred(struct queue *q); extern struct sslhcfg_item cfg; extern struct addrinfo *addr_listen; extern const char* server_type; /* sslh-fork.c */ void start_shoveler(int); void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen); #endif sslh-1.22c/doc/000077500000000000000000000000001411244446000133175ustar00rootroot00000000000000sslh-1.22c/doc/FAQ.md000066400000000000000000000132121411244446000142470ustar00rootroot00000000000000Frequently Asked Questions ========================== When something doesn't work, look up here... and if it still doesn't work, report how what was suggested here went. It's also worth reading [how to ask questions](http://www.catb.org/~esr/faqs/smart-questions.html) before posting on the mailing list or opening an issue in Github. Getting more info ================= In general, if something doesn't work, you'll want to run `sslh` with lots of logging, and the logging directly in the terminal (Otherwise, logs are sent to `syslog`, and usually end up in `/var/log/auth.log`). You will achieve this by running `sslh` in foreground with verbose: ``` sslh -v 1 -f -F myconfig.cfg ``` Higher values of `verbose` produce more information. 1 is usually sufficient. 2 will also print incoming packets used for probing. forward to [PROBE] failed:connect: Connection refused ===================================================== Usually this means `sslh` is configured to forward a protocol somewhere, but no service is listening on the target address. Check your `sslh` configuration, check the corresponding server really is listening and running. Finally, check the server is listening where you expect it to: ``` netstat -lpt ``` I get a segmentation fault! =========================== Well, it's not yours (fault): a segfault is always a bug in the programme. Usually standard use cases are well tested, so it may be related to something unusual in your configuration, or even something wrong, but it should still never result in a segfault. Thankfully, when they are deterministic, segfaults are usually fairly easy to fix if you're willing to run a few diagnostics to help the developer. First, make sure you have debug symbols: ``` $ file sslh-select sslh-select: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a758ac75ff11f1ace577705b4d6627e301940b59, with debug_info, not stripped ``` Note `with debug_info, not stripped` at the end. If you don't have that, your distribution stripped the binary: you will need to get the source code and compile it yourself (that way, you will also get the latest version). Install `valgrind` and run `sslh` under it: ``` valgrind --leak-check=full ./sslh-fork -v 2 -f -F yourconfig.cfg ``` Report the full output to the mailing list or github. Valgrind is very powerful and gives precise hints of what is wrong and why. For example on `sslh` issue (#273)[https://github.com/yrutschle/sslh/issues/273]: ``` sudo valgrind --leak-check=full ./sslh-fork -v 2 -f -F /etc/sslh.cfg ==20037== Memcheck, a memory error detector ==20037== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==20037== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==20037== Command: ./sslh-fork -v 2 -f -F /etc/sslh.cfg ==20037== sslh-fork v1.21b-1-g2c93a01-dirty started --20037-- WARNING: unhandled arm-linux syscall: 403 --20037-- You may be able to write your own handler. --20037-- Read the file README_MISSING_SYSCALL_OR_IOCTL. --20037-- Nevertheless we consider this a bug. Please report --20037-- it at http://valgrind.org/support/bug_reports.html. ==20040== Conditional jump or move depends on uninitialised value(s) ==20040== at 0x112A3C: parse_tls_header (tls.c:162) ==20040== by 0x111CEF: is_tls_protocol (probe.c:214) ==20040== by 0x11239F: probe_client_protocol (probe.c:366) ==20040== by 0x10A8F7: start_shoveler (sslh-fork.c:98) ==20040== by 0x10AE9B: main_loop (sslh-fork.c:200) ==20040== by 0x1114FB: main (sslh-main.c:322) ==20040== ``` Here we see that something wrong is happening at `tls.c` line 162, and it's linked to an uninitialised value. Using sslh for virtual hosting ============================== Virtual hosting refers to having several domain names behind a single IP address. All Web servers handle this, but sometimes it can be useful to do it with `sslh`. TLS virtual hosting with SNI ---------------------------- For TLS, this is done very simply using Server Name Indication, SNI for short, which is a TLS extension whereby the client indicates the name of the server it wishes to connect to. This can be a very powerful way to separate several TLS-based services hosted behind the same port: simply name each service with its own hostname. For example, we could define `mail.rutschle.net`, `im.rutschle.net`, `www.rutschle.net`, all of which point to the same IP address. `sslh` uses the `sni_hostnames` setting of the TLS probe to do this, e.g.: ``` protocols: ( { name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net" ]; }, { name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net" ]; }, { name: "tls"; host: "localhost"; port: "4443"; sni_hostnames: [ "www.rutschle.net" ]; } ); ``` HTTP virtual hosting with regex ------------------------------- If you wish to serve several Web domains over HTTP through `sslh`, you can do this simply by using regular expressions on the Host specification part of the HTTP query. The following example forwards connections to `host_A.acme` to 192.168.0.2, and connections to `host_B.acme` to 192.168.0.3. ``` protocols: ( { name: "regex"; host: "192.168.0.2"; port: "80"; regex_patterns: ["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_A.acme"] }, { name: "regex"; host: "192.168.0.3"; port: "80"; regex_patterns: ["^(GET|POST|PUT|OPTIONS|DELETE|HEADER) [^ ]* HTTP/[0-9.]*[\r\n]*Host: host_B.acme"] } ); ``` sslh-1.22c/doc/INSTALL.md000066400000000000000000000070331411244446000147520ustar00rootroot00000000000000Compile and install =================== Dependencies ------------ `sslh` uses: * [libconfig](http://www.hyperrealm.com/libconfig/). For Debian this is contained in package `libconfig8-dev`. You can compile with or without it using USELIBCONFIG in the Makefile. * [libwrap](http://packages.debian.org/source/unstable/tcp-wrappers). For Debian, this is contained in packages `libwrap0-dev`. You can compile with or without it using USELIBWRAP in the Makefile. * [libsystemd](http://packages.debian.org/source/unstable/libsystemd-dev), in package `libsystemd-dev`. You can compile with or without it using USESYSTEMD in the Makefile. * [libcap](http://packages.debian.org/source/unstable/libcap-dev), in package `libcap-dev`. You can compile with or without it using USELIBCAP in the Makefile * libbsd, to enable to change the process name (as shown in `ps`, so each forked process shows what protocol and what connection it is serving), which requires `libbsd` at runtime, and `libbsd-dev` at compile-time. For OpenSUSE, these are contained in packages libconfig9 and libconfig-dev in repository For Fedora, you'll need packages `libconfig` and `libconfig-devel`: yum install libconfig libconfig-devel If you want to rebuild `sslh-conf.c` (after a `make distclean` for example), you will also need to add [conf2struct](https://www.rutschle.net/tech/conf2struct/README.html) (v1.5) to your path. Compilation ----------- After this, the Makefile should work: make install There are a couple of configuration options at the beginning of the Makefile: * `USELIBWRAP` compiles support for host access control (see `hosts_access(3)`), you will need `libwrap` headers and library to compile (`libwrap0-dev` in Debian). * `USELIBCONFIG` compiles support for the configuration file. You will need `libconfig` headers to compile (`libconfig8-dev` in Debian). * `USESYSTEMD` compiles support for using systemd socket activation. You will need `systemd` headers to compile (`systemd-devel` in Fedora). * `USELIBBSD` compiles support for updating the process name (as shown by `ps`). Binaries -------- The Makefile produces two different executables: `sslh-fork` and `sslh-select`: * `sslh-fork` forks a new process for each incoming connection. It is well-tested and very reliable, but incurs the overhead of many processes. If you are going to use `sslh` for a "small" setup (less than a dozen ssh connections and a low-traffic https server) then `sslh-fork` is probably more suited for you. * `sslh-select` uses only one thread, which monitors all connections at once. It is more recent and less tested, but only incurs a 16 byte overhead per connection. Also, if it stops, you'll lose all connections, which means you can't upgrade it remotely. If you are going to use `sslh` on a "medium" setup (a few thousand ssh connections, and another few thousand ssl connections), `sslh-select` will be better. If you have a very large site (tens of thousands of connections), you'll need a vapourware version that would use libevent or something like that. Installation ------------ * In general: make cp sslh-fork /usr/local/sbin/sslh cp basic.cfg /etc/sslh.cfg vi /etc/sslh.cfg * For Debian: cp scripts/etc.init.d.sslh /etc/init.d/sslh * For CentOS: cp scripts/etc.rc.d.init.d.sslh.centos /etc/rc.d/init.d/sslh You might need to create links in /etc/rc.d so that the server start automatically at boot-up, e.g. under Debian: update-rc.d sslh defaults sslh-1.22c/doc/README.MacOSX000066400000000000000000000025051411244446000152720ustar00rootroot00000000000000 sslh is available for Mac OS X via MacPorts. If you have MacPorts installed on your system you can install sslh by executing the following in the Terminal: port install sslh Also, the following is a helpful launchd configuration that covers the most common use case of sslh. Save the following into a text file, e.g. /Library/LaunchDaemons/net.rutschle.sslh.plist, then load it with launchctl or simply reboot. ----BEGIN FILE---- Disabled KeepAlive Label net.rutschle.sslh ProgramArguments /opt/local/sbin/sslh -f -v -u nobody -p 0.0.0.0:443 --ssh localhost:22 --ssl localhost:443 QueueDirectories RunAtLoad StandardErrorPath /Library/Logs/sslh.log StandardOutPath /Library/Logs/sslh.log WatchPaths ----END FILE---- sslh-1.22c/doc/config.md000066400000000000000000000323541411244446000151150ustar00rootroot00000000000000Configuration ============= If you use the scripts provided, sslh will get its configuration from /etc/sslh.cfg. Please refer to example.cfg for an overview of all the settings. A good scheme is to use the external name of the machine in `listen`, and bind `httpd` to `localhost:443` (instead of all binding to all interfaces): that way, HTTPS connections coming from inside your network don't need to go through `sslh`, and `sslh` is only there as a frontal for connections coming from the internet. Note that 'external name' in this context refers to the actual IP address of the machine as seen from your network, i.e. that that is not `127.0.0.1` in the output of `ifconfig(8)`. Libwrap support --------------- Sslh can optionally perform `libwrap` checks for the sshd service: because the connection to `sshd` will be coming locally from `sslh`, `sshd` cannot determine the IP of the client. OpenVPN support --------------- OpenVPN clients connecting to OpenVPN running with `-port-share` reportedly take more than one second between the time the TCP connection is established and the time they send the first data packet. This results in `sslh` with default settings timing out and assuming an SSH connection. To support OpenVPN connections reliably, it is necessary to increase `sslh`'s timeout to 5 seconds. Instead of using OpenVPN's port sharing, it is more reliable to use `sslh`'s `--openvpn` option to get `sslh` to do the port sharing. Using proxytunnel with sslh --------------------------- If you are connecting through a proxy that checks that the outgoing connection really is SSL and rejects SSH, you can encapsulate all your traffic in SSL using `proxytunnel` (this should work with `corkscrew` as well). On the server side you receive the traffic with `stunnel` to decapsulate SSL, then pipe through `sslh` to switch HTTP on one side and SSL on the other. In that case, you end up with something like this: ssh -> proxytunnel -e ----[ssh/ssl]---> stunnel ---[ssh]---> sslh --> sshd Web browser -------------[http/ssl]---> stunnel ---[http]--> sslh --> httpd Configuration goes like this on the server side, using `stunnel3`: stunnel -f -p mycert.pem -d thelonious:443 -l /usr/local/sbin/sslh -- \ sslh -i --http localhost:80 --ssh localhost:22 * stunnel options: * `-f` for foreground/debugging * `-p` for specifying the key and certificate * `-d` for specifying which interface and port we're listening to for incoming connexions * `-l` summons `sslh` in inetd mode. * sslh options: * `-i` for inetd mode * `--http` to forward HTTP connexions to port 80, and SSH connexions to port 22. Capabilities support -------------------- On Linux (only?), you can compile sslh with `USELIBCAP=1` to make use of POSIX capabilities; this will save the required capabilities needed for transparent proxying for unprivileged processes. Alternatively, you may use filesystem capabilities instead of starting sslh as root and asking it to drop privileges. You will need `CAP_NET_BIND_SERVICE` for listening on port 443 and `CAP_NET_RAW` for transparent proxying (see `capabilities(7)`). You can use the `setcap(8)` utility to give these capabilities to the executable: sudo setcap cap_net_bind_service,cap_net_raw+pe sslh-select Then you can run sslh-select as an unpriviledged user, e.g.: sslh-select -p myname:443 --ssh localhost:22 --tls localhost:443 Transparent proxy support ------------------------- On Linux and FreeBSD you can use the `--transparent` option to request transparent proxying. This means services behind `sslh` (Apache, `sshd` and so on) will see the external IP and ports as if the external world connected directly to them. This simplifies IP-based access control (or makes it possible at all). You can refer to Sean Warn'ѕ [tutorial](tproxy.md) for a different set-up which enables transparent proxying between two different machines. The following may only work if `sslh` and the final servers are on the same machine. Note that getting this to work is very tricky and detail-dependant: depending on whether the target server and sslh are on the same machine, different machines, or different dockers, and tool versions, all seem to change the required network configuration somewhat. If it doesn't work, it's almost certain that the problem is not linked to `sslh` but to the network setup that surrounds it. Linux: `sslh` needs extended rights to perform this: you'll need to give it `CAP_NET_RAW` capabilities (see appropriate chapter) or run it as root (but don't do that). The firewalling tables also need to be adjusted as follows. I don't think it is possible to have `httpd` and `sslh` both listen to 443 in this scheme -- let me know if you manage that: # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination sysctl -w net.ipv4.conf.default.route_localnet=1 sysctl -w net.ipv4.conf.all.route_localnet=1 # DROP martian packets as they would have been if route_localnet was zero # Note: packets not leaving the server aren't affected by this, thus sslh will still work iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh") iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark) iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f # Configure routing for those marked packets ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 Tranparent proxying with IPv6 is similarly set up as follows: # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination # Not sure if this is needed for ipv6 though sysctl -w net.ipv4.conf.default.route_localnet=1 sysctl -w net.ipv4.conf.all.route_localnet=1 # DROP martian packets as they would have been if route_localnet was zero # Note: packets not leaving the server aren't affected by this, thus sslh will still work ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh") ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark) ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f # Configure routing for those marked packets ip -6 rule add fwmark 0x1 lookup 100 ip -6 route add local ::/0 dev lo table 100 Explanation: To be able to use `localhost` as destination in your sslh config along with transparent proxying you have to allow routing of loopback addresses as done above. This is something you usually should not do (see [this stackoverflow post](https://serverfault.com/questions/656279/how-to-force-linux-to-accept-packet-with-loopback-ip/656484#656484)) The two `DROP` iptables rules emulate the behaviour of `route_localnet` set to off (with one small difference: allowing the reroute-check to happen after the fwmark is set on packets destined for sslh). See [this diagram](https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg) for a good visualisation showing how packets will traverse the iptables chains. Note: You have to run `sslh` as dedicated user (in this example the user is also named `sslh`), to not mess up with your normal networking. These rules will allow you to connect directly to ssh on port 22 (or to any other service behind sslh) as well as through sslh on port 443. Also remember that iptables configuration and ip routes and rules won't be necessarily persisted after you reboot. Make sure to save them properly. For example in CentOS7, you would do `iptables-save > /etc/sysconfig/iptables`, and add both `ip` commands to your `/etc/rc.local`. FreeBSD: Given you have no firewall defined yet, you can use the following configuration to have ipfw properly redirect traffic back to sslh /etc/rc.conf firewall_enable="YES" firewall_type="open" firewall_logif="YES" firewall_coscripts="/etc/ipfw/sslh.rules" /etc/ipfw/sslh.rules #! /bin/sh # ssl ipfw add 20000 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8443 to any out ipfw add 20010 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8443 to any out # ssh ipfw add 20100 fwd 192.0.2.1,443 log tcp from 192.0.2.1 8022 to any out ipfw add 20110 fwd 2001:db8::1,443 log tcp from 2001:db8::1 8022 to any out # xmpp ipfw add 20200 fwd 192.0.2.1,443 log tcp from 192.0.2.1 5222 to any out ipfw add 20210 fwd 2001:db8::1,443 log tcp from 2001:db8::1 5222 to any out # openvpn (running on other internal system) ipfw add 20300 fwd 192.0.2.1,443 log tcp from 198.51.100.7 1194 to any out ipfw add 20310 fwd 2001:db8::1,443 log tcp from 2001:db8:1::7 1194 to any out General notes: This will only work if `sslh` does not use any loopback addresses (no `127.0.0.1` or `localhost`), you'll need to use explicit IP addresses (or names): sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --tls 192.168.0.1:4443 This will not work: sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:4443 Transparent proxying means the target server sees the real origin address, so it means if the client connects using IPv6, the server must also support IPv6. It is easy to support both IPv4 and IPv6 by configuring the server accordingly, and setting `sslh` to connect to a name that resolves to both IPv4 and IPv6, e.g.: sslh --transparent --listen :443 --ssh insideaddr:22 /etc/hosts: 192.168.0.1 insideaddr 201::::2 insideaddr Upon incoming IPv6 connection, `sslh` will first try to connect to the IPv4 address (which will fail), then connect to the IPv6 address. Systemd Socket Activation ------------------------- If compiled with `USESYSTEMD` then it is possible to activate the service on demand and avoid running any code as root. In this mode any listen configuration options are ignored and the sockets are passed by systemd to the service. Example socket unit: [Unit] Before=sslh.service [Socket] ListenStream=1.2.3.4:443 ListenStream=5.6.7.8:444 ListenStream=9.10.11.12:445 FreeBind=true [Install] WantedBy=sockets.target Example service unit: [Unit] PartOf=sslh.socket [Service] ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --tls 127.0.0.1:443 KillMode=process CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW PrivateTmp=true PrivateDevices=true ProtectSystem=full ProtectHome=true User=sslh With this setup only the socket needs to be enabled. The sslh service will be started on demand and does not need to run as root to bind the sockets as systemd has already bound and passed them over. If the sslh service is started on its own without the sockets being passed by systemd then it will look to use those defined on the command line or config file as usual. Any number of ListenStreams can be defined in the socket file and systemd will pass them all over to sslh to use as usual. To avoid inconsistency between starting via socket and starting directly via the service Requires=sslh.socket can be added to the service unit to mandate the use of the socket configuration. Rather than overwriting the entire socket file drop in values can be placed in /etc/systemd/system/sslh.socket.d/.conf with additional ListenStream values that will be merged. In addition to the above with manual .socket file configuration there is an optional systemd generator which can be compiled - systemd-sslh-generator This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists instead) configuration file and dynamically generates a socket file to use. This will also merge with any sslh.socket.d drop in configuration but will be overriden by a /etc/systemd/system/sslh.socket file. To use the generator place it in /usr/lib/systemd/system-generators and then call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate the new dynamic socket unit. Fail2ban -------- If using transparent proxying, just use the standard ssh rules. If you can't or don't want to use transparent proxying, you can set `fail2ban` rules to block repeated ssh connections from an IP address (obviously this depends on the site, there might be legitimate reasons you would get many connections to ssh from the same IP address...) See example files in scripts/fail2ban. UDP --- `sslh` can perform demultiplexing on UDP packets as well. This only works with `sslh-select` (it is not possible to support UDP with a forking model). Specify a listening address and target protocols with `is_udp: true`. `sslh` will wait for incoming UDP packets, run the probes in the usual fashion, and forward packets to the appropriate target. `sslh` will then remember the association between remote host to target server for 60 seconds by default, which can be overriden with `udp_timeout`. This allows to process both single-datagram protocols such as DNS, and connection-based protocols such as QUIC. sslh-1.22c/doc/tproxy.md000066400000000000000000000203541411244446000152120ustar00rootroot00000000000000Transparent Proxy to Two Hosts ============================== Tutorial by Sean Warner. 19 June 2019 20:35 Aim --- * Show that `sslh` can transparently proxy requests from the internet to services on two separate hosts that are both on the same LAN. * The IP address of the client initiating the request is what the destination should see… and not the IP address of the host that `sslh` is running on, which is what happens when `sslh` is not running in transparent mode. * The solution here only works for my very specific use-case but hopefully others can adapt it to suits their needs. Overview of my Network ---------------------- Two Raspberry Pis on my home LAN: * Pi A: 192.168.1.124 – `sslh` (Port 4433), Apache2 web server for https (port 443), `stunnel` (port 4480) to decrypt ssh traffic and forward to SSH server (also on Pi A at Port 1022) * Pi B: 192.168.1.123 - HTTP server (port 8000), SSH server (port 1022 on PiB). * I send traffic from the internet to my router's external port 443 then use a port forward rule in my router to map that to internal port 4433 where sslh is listening. ![Architecture](tproxy.svg) `sslh` build ------------   `sslh` Version: sslh v1.19c-2-gf451cc8-dirty. I compiled sslh from sources giving the binary pretty much all possible options such as Posix capabilities and systemd support.. here are the first few lines of the makefile:   ``` # Configuration VERSION=$(shell ./genver.sh -r) ENABLE_REGEX=1         # Enable regex probes USELIBCONFIG=1         # Use libconfig? (necessary to use configuration files) USELIBPCRE=1 # Use libpcre? (needed for regex on musl) USELIBWRAP=1         # Use libwrap? USELIBCAP=1         # Use libcap? USESYSTEMD=1         # Make use of systemd socket activation COV_TEST=         # Perform test coverage? PREFIX=/usr/local BINDIR=$(PREFIX)/sbin MANDIR=$(PREFIX)/share/man/man8 MAN=sslh.8.gz         # man page name # End of configuration -- the rest should take care of # itself ```   systemd setup ------------- Create an sslh systemd service file... ``` # nano /lib/systemd/system/sslh.service ``` Paste in this contents… ``` [Unit] Description=SSL/SSH multiplexer After=network.target Documentation=man:sslh(8) [Service] #EnvironmentFile=/etc/default/sslh #ExecStart=/usr/local/sbin/sslh $DAEMON_OPTS ExecStart=/usr/local/sbin/sslh -F /etc/sslh/sslh.cfg KillMode=process [Install] WantedBy=multi-user.target ``` Save it and then… ``` # systemctl daemon-reload ``` Start it again to test… ``` # systemctl start sslh ```   Configure `sslh` ---------------- First stop `sslh` then open the config file and replace with below, save and start `sslh` again ``` # systemctl stop sslh # nano /etc/sslh/sslh.cfg # systemctl start sslh ``` ``` verbose: true; foreground: true; inetd: false; numeric: true; transparent: true; timeout: 2; user: "sslh"; pidfile: "/var/run/sslh.pid"; chroot: "/var/empty"; # You must have a port forward rule in the router: external port 443 <-> internal port 4433 # Local ip address of PiA is: 192.168.1.124, sslh and stunnel4 are running on this Pi # Local ip address of PiB is: 192.168.1.123, http server and ssh server on this Pi listen: ( { host: "192.168.1.124"; port: "4433"; } ); # sslh demultiplexes based on the Protocol and Hostname protocols: ( { name: "tls"; sni_hostnames: [ "www.example.com" ]; host: "192.168.1.124"; port: "443"; log_level: 1; }, # This probe is for tls encrypted ssh. SSLH forwards it to stunnel on port 4480 which decrypts it and sends it to the ssh server on PiA port 1022 { name: "tls"; sni_hostnames: [ "ssh.example.com" ]; host: "192.168.1.124"; port: "4480"; log_level: 1; }, { name: "http"; host: "192.168.1.123"; port: "8000"; log_level: 1; }, { name: "ssh"; host: "192.168.1.123"; port: "1022"; log_level: 1; } ); ```   Configure `stunnel` ------------------- First stop `stunnel` then open the config file and replace with below, save and start `stunnel` again ``` # systemctl stop stunnel4 # nano /etc/stunnel/stunnel.conf # systemctl start stunnel4 ``` ``` # Debugging stuff (may be useful for troubleshooting) foreground = yes #debug = 5 # this is the default debug = 7 output = /var/log/stunnel4/stunnel.log pid = /var/run/stunnel4/stunnel.pid fips = no cert = /etc/letsencrypt/live/example.com/fullchain.pem key = /etc/letsencrypt/live/example.com/privkey.pem [ssh] accept = 192.168.1.124:4480 connect = 192.168.1.124:1022 TIMEOUTclose = 0 ```   Configure iptables for Pi A -------------------------- The `_add.sh` script creates the rules, the `_rm.sh` script removes the rules. They will be lost if you reboot but there are ways to make them load again on start-up.. ``` # nano /usr/local/sbin/piA_tproxy_add.sh ``` ``` piA_tproxy_add.sh iptables -t mangle -N SSLH iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH iptables -t mangle -A SSLH --jump MARK --set-mark 0x1 iptables -t mangle -A SSLH --jump ACCEPT ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 ``` ``` # nano /usr/local/sbin/piA_tproxy_rm.sh ``` ``` piA_tproxy_rm.sh iptables -t mangle -D PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -D OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443,4480 --jump SSLH iptables -t mangle -D SSLH --jump MARK --set-mark 0x1 iptables -t mangle -D SSLH --jump ACCEPT iptables -t mangle -X SSLH ip rule del fwmark 0x1 lookup 100 ip route del local 0.0.0.0/0 dev lo table 100 ``` Make them executable.. ``` # chmod +rx piA_tproxy_add.sh # chmod +rx piA_tproxy_rm.sh ``` Now run the "add" script on Pi A! ``` # piA_tproxy_add.sh # piA_tproxy_rm.sh ``` Configure iptables for Pi B -------------------------- ``` # nano /usr/local/sbin/piB_tproxy_add.sh ``` ``` piB_tproxy_add.sh iptables -t mangle -N SSLHSSL iptables -t mangle -A OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL iptables -t mangle -A SSLHSSL --jump MARK --set-mark 0x1 iptables -t mangle -A SSLHSSL --jump ACCEPT ip rule add fwmark 0x1 lookup 100 ip route add default via 192.168.1.124 table 100 ip route flush cache ``` ``` # nano /usr/local/sbin/piB_tproxy_rm.sh ``` ``` iptables -t mangle -D OUTPUT -o eth0 -p tcp -m multiport --sport 1022,8000 -j SSLHSSL iptables -t mangle -D SSLHSSL --jump MARK --set-mark 0x1 iptables -t mangle -D SSLHSSL --jump ACCEPT iptables -t mangle -X SSLHSSL ip rule del fwmark 0x1 lookup 100 ip route del default via 192.168.1.124 table 100 ip route flush cache ``` Make them executable.. ``` # chmod +rx piB_tproxy_add.sh # chmod +rx piB_tproxy_rm.sh ``` Now run the "add" script on Pi B! ``` # piB_tproxy_add.sh # piB_tproxy_rm.sh ```   Testing ------- * Getting to sshd on PiA I did this test using 4G from my phone (outside the LAN) To simulate this I use `proxytunnel`. External port 443 is forwarded by my router to 4433. I need to arrive at `sslh` (port 4433) with ssh encrypted as TLS (hence I use the -e switch) and the `sni_hostname` set to ssh.example.com so that `sslh` will demultiplex to `stunnel` (port 4480) which will decrypt and forward to ssh server on PiA… see `sslh.cfg` and `stunnel.conf`. The first IP:port is just a free HTTPS proxy I found on https://free-proxy-list.net I execute this command from a terminal window.. ``` # proxytunnel -v -e -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443 ``` * Getting to sshd on PiB I did this test using 4G from my phone (outside the LAN) My smartphone telecom provider blocks ssh over port 443 so I need to use `proxytunnel` to encrypt. Use the Proxytunnel `-X` switch to encrypt from local proxy to destination only so by the time we get to the destination it is unencrypted and `sslh` will see the ssh protocol and demultiplex to PiB as per `sslh.cfg`. ``` # proxytunnel -v -X -C root.pem -p 78.141.192.198:8080 -d ssh.example.com:443 ``` Now when you test it all look at the output in daemon.log like this: ``` # grep -i 'ssl' /var/log/daemon.log ``` You should see that the IP address and port from the “connection from” and “forwarded from” fields are the same. Special thanks and appreciation to Michael Yelsukov without whom I would never have got this working. Any feedback or corrections very welcome! sslh-1.22c/doc/tproxy.svg000066400000000000000000000775221411244446000154220ustar00rootroot00000000000000 image/svg+xml Client Internet Router Local Area Network 443 443 443 4433 4433 SSLH 192.168.1.124 (Pi A) 192.168.1.123 (Pi B) Web Server SSH Server http 8000 8000 ssh 1022 1022 Web Server STUNNEL SSH Server 443 443 tls 4480 tls 1022 ssh sslh-1.22c/echosrv-conf.c000066400000000000000000001151551411244446000153220ustar00rootroot00000000000000/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) * on Fri Aug 13 18:03:20 2021. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2021 Yves Rutschle # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. */ #define _GNU_SOURCE #include #ifdef LIBCONFIG # include #endif #include #include #include #include "echosrv-conf.h" #include "argtable3.h" #ifdef LIBPCRE #define PCRE2_CODE_UNIT_WIDTH 8 #include typedef struct { PCRE2_SIZE rm_so; PCRE2_SIZE rm_eo; } regmatch_t; #else #include #endif /* This gets included in the output .c file */ /* Libconfig 1.4.9 is still used by major distributions * (e.g. CentOS7) and had a different name for * config_setting_lookup */ #if LIBCONFIG_VER_MAJOR == 1 #if LIBCONFIG_VER_MINOR == 4 #if LIBCONFIG_VER_REVISION == 9 #define config_setting_lookup config_lookup_from #endif #endif #endif /* config_type, lookup_fns, type2str are related, keep them together */ typedef enum { CFG_BOOL, CFG_INT, CFG_INT64, CFG_FLOAT, CFG_STRING, CFG_GROUP, CFG_ARRAY, CFG_LIST, } config_type; /* /config_type */ const char* type2str[] = { "boolean", "int", "int64", "float", "string", "group", "array", "list", }; typedef union { int def_bool; int def_int; long long def_int64; double def_float; char* def_string; } any_val; struct config_desc { const char* name; int type; struct config_desc * sub_group; /* Table for compound types (list and group) */ void* arg_cl; /* command-line argument for this setting */ void* base_addr; /* Base of the structure (filled at runtime). Probably not useable for list elements */ size_t offset; /* Offset of setting in the structure */ size_t offset_len; /* Offset of *_len field, for arrays and lists */ size_t offset_present; /* offset of *_is_present field, for optional settings */ size_t size; /* Size of element, or size of group for groups and lists */ int array_type; /* type of array elements, when type == CFG_ARRAY */ int mandatory; int optional; any_val default_val; }; #ifndef LIBCONFIG /* Stubs in case you don't want libconfig */ typedef void config_setting_t; typedef int config_t; #define CONFIG_TRUE 1 #define CONFIG_FALSE 0 #define make_config_setting_lookup(type) \ int config_setting_lookup_##type(const config_setting_t* a, const char* b, void* c) { \ return 0; \ } #define make_config_setting_get(type, ret_type) \ ret_type config_setting_get_##type(const config_setting_t* a) { \ return 0; \ } enum { CONFIG_TYPE_INT, CONFIG_TYPE_BOOL, CONFIG_TYPE_INT64, CONFIG_TYPE_FLOAT, CONFIG_TYPE_STRING }; make_config_setting_lookup(bool); make_config_setting_lookup(int); make_config_setting_lookup(int64); make_config_setting_lookup(float); make_config_setting_lookup(string); make_config_setting_get(bool, int); make_config_setting_get(int, int); make_config_setting_get(int64, long long int); make_config_setting_get(float, double); make_config_setting_get(string, char*); config_setting_t* config_root_setting(config_t* c) { return NULL; } config_setting_t* config_lookup(config_t* c, const char* b) { return NULL; } void config_init(config_t* c) { return; } char* config_setting_name(config_setting_t* c) { return NULL; } int config_setting_is_list(config_setting_t* c) { return 0; } int config_setting_is_array(config_setting_t* c) { return 0; } int config_setting_is_scalar(config_setting_t* c) { return 0; } int config_setting_index(const config_setting_t *setting) { return 0; } config_setting_t* config_setting_lookup(config_setting_t* a, char* b) { return NULL; } int config_setting_remove(config_setting_t* cfg, char* name) { return 0; } int config_setting_type(config_setting_t* s) { return -1; } int config_setting_length(config_setting_t* a) { return 0; } config_setting_t* config_setting_get_elem(config_setting_t* a, int i) { return NULL; } int config_read_file(config_t* a, const char* b) { return CONFIG_TRUE; } int config_error_line(config_t* c) { return 0; } char* config_error_text(config_t* c) { return NULL; } #endif /* This is the same as config_setting_lookup_string() except it allocates a new string which belongs to the caller */ static int myconfig_setting_lookup_stringcpy( const config_setting_t* setting, const char* name, char** value) { const char* str; *value = NULL; if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) { asprintf(value, "%s", str); return CONFIG_TRUE; } else { return CONFIG_FALSE; } } typedef int (*lookup_fn)(const config_setting_t*, const char*, void*); lookup_fn lookup_fns[] = { (lookup_fn)config_setting_lookup_bool, (lookup_fn)config_setting_lookup_int, (lookup_fn)config_setting_lookup_int64, (lookup_fn)config_setting_lookup_float, (lookup_fn)myconfig_setting_lookup_stringcpy, NULL, /* CFG_GROUP */ NULL, /* CFG_ARRAY */ NULL, /* CFG_LIST */ }; /* Copy an any_val to arbitrary memory location */ /* 0: success * <0: error */ static int any_valcpy(config_type type, void* target, any_val val) { switch(type) { case CFG_BOOL: *(int*)target = val.def_bool; break; case CFG_INT: *(int*)target = val.def_int; break; case CFG_INT64: *(long long*)target = val.def_int64; break; case CFG_FLOAT: *(double*)target = val.def_float; break; case CFG_STRING: *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 1; } /* Copy the value of a setting to an arbitrary memory that * must be large enough */ /* 0: success * <0: error */ static int settingcpy(config_type type, void* target, const config_setting_t* setting) { any_val val; char* str; switch(type) { case CFG_BOOL: val.def_bool = config_setting_get_bool(setting); *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = config_setting_get_int(setting); *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = config_setting_get_int64(setting); *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = config_setting_get_float(setting); *(double*)target = val.def_int64; break; case CFG_STRING: asprintf(&str, "%s", config_setting_get_string(setting)); val.def_string = str; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Copy the value of a command line arg to arbitrary memory * that must be large enough for the type */ /* 0: success * <0: error */ static int clcpy(config_type type, void* target, const void* cl_arg) { any_val val; char* str; switch(type) { case CFG_BOOL: val.def_bool = (*(struct arg_lit**)cl_arg)->count; *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = (*(struct arg_int**)cl_arg)->ival[0]; *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = (*(struct arg_int**)cl_arg)->ival[0]; *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = (*(struct arg_dbl**)cl_arg)->dval[0]; *(double*)target = val.def_float; break; case CFG_STRING: asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]); val.def_string = str; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Copy the value of a string argument to arbitary memory * location that must be large enough, converting on the way * (i.e. CFG_INT gets atoi() and so on) */ /* 0: success * <0: error */ static int stringcpy(config_type type, void* target, char* from) { any_val val; switch(type) { case CFG_BOOL: val.def_bool = (*from != '0'); *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = strtol(from, NULL, 10); *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = strtoll(from, NULL, 10); *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = strtod(from, NULL); *(double*)target = val.def_float; break; case CFG_STRING: val.def_string = from; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Element to describe the target of a compound element * element: which config entry is being changed * match: if >0, index in pmatch to set * if 0, don't match but init with value * value: constant if not matching */ struct compound_cl_target { struct config_desc * element; int match; any_val value; }; /* Element to describe one compound command line argument * An argument is string that gets matched against a regex, * then match-groups get evaluated to each targets[]. * For lists, base_entry points to the config_setting so we * can append to it */ struct compound_cl_arg { const char* regex; struct arg_str** arg_cl; /* arg_str entry for this compound option */ struct config_desc * base_entry; struct compound_cl_target* targets; /* If override_desc is set, it points to the descriptor of the element in the group which will be checked for override. Then, override_matchindex indicates the command-line parameter match used to compare against override_desc to know if this group is overridden. If override_matchindex is 0, we don't match from the command-line but from a constant stored in override_const instead */ struct config_desc * override_desc; int override_matchindex; char* override_const; }; struct arg_file* echocfg_conffile; struct arg_lit* echocfg_udp; struct arg_str* echocfg_prefix; struct arg_str* echocfg_listen_host; struct arg_str* echocfg_listen_port; struct arg_str* echocfg_listen; struct arg_end* echocfg_end; static struct config_desc table_echocfg_listen[] = { { /* name */ "host", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & echocfg_listen_host, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_listen_item, host), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "port", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & echocfg_listen_port, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_listen_item, port), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { 0 } }; static struct config_desc table_echocfg[] = { { /* name */ "udp", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & echocfg_udp, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_item, udp), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "prefix", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & echocfg_prefix, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_item, prefix), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "listen", /* type */ CFG_LIST, /* sub_group*/ table_echocfg_listen, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct echocfg_item, listen), /* offset_len */ offsetof(struct echocfg_item, listen_len), /* offset_present */ 0, /* size */ sizeof(struct echocfg_listen_item), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { 0 } }; static struct compound_cl_target echocfg_listen_targets [] = { { & table_echocfg_listen[0], 1, .value.def_string = "0" }, { & table_echocfg_listen[1], 2, .value.def_string = "0" }, { 0 } }; static struct compound_cl_arg compound_cl_args[] = { { /* arg: listen */ .regex = "(.+):(\\w+)", .arg_cl = & echocfg_listen, .base_entry = & table_echocfg [2], .targets = echocfg_listen_targets, .override_desc = NULL, .override_matchindex = 0, .override_const = NULL, }, { 0 } }; /* Enable debug to follow the parsing of tables */ #if 0 #define TRACE_READ(x) printf x #define TRACE_READ_PRINT_SETTING 1 #else #define TRACE_READ(x) #define TRACE_READ_PRINT_SETTING 0 #endif /* Enable debug to follow the parsing of compound options */ #if 0 #define TRACE_CMPD(x) printf x #define TRACE_CMPD_PRINT_SETTING 1 #else #define TRACE_CMPD(x) #define TRACE_CMPD_PRINT_SETTING 0 #endif static void print_setting(config_type type, void* val) { if (TRACE_READ_PRINT_SETTING || TRACE_CMPD_PRINT_SETTING) { switch(type) { case CFG_BOOL: case CFG_INT: printf("%d", *(int*)val); break; case CFG_INT64: printf("%lld", *(long long*)val); break; case CFG_FLOAT: printf("%f", *(double*)val); break; case CFG_STRING: printf("`%s'", *(char**)val); break; case CFG_GROUP: case CFG_LIST: case CFG_ARRAY: break; } } } /* Changes all dashes to underscores in a string of * vice-versa */ static void strswap_ud(const char target, char* str) { char* c; for (c = str; *c; c++) if (*c == (target == '_' ? '-' : '_')) *c = (target == '_' ? '_' : '-'); } /* Same as config_setting_lookup() but looks up with dash or * underscore so `my_setting` and `my-setting` match the same */ static config_setting_t* config_setting_lookup_ud(config_setting_t* cfg, struct config_desc* desc) { config_setting_t* setting; char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); setting = config_setting_lookup(cfg, name); if (setting) return setting; strswap_ud('-', name); setting = config_setting_lookup(cfg, name); return setting; } static int lookup_typed_ud(config_setting_t* cfg, void* target, struct config_desc *desc) { lookup_fn lookup_fn = lookup_fns[desc->type]; char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); if (lookup_fn(cfg, name, ((char*)target) + desc->offset) == CONFIG_TRUE) return CONFIG_TRUE; strswap_ud('-', name); return lookup_fn(cfg, name, ((char*)target) + desc->offset); } /* Removes a setting, trying both underscores and dashes as * name (so deleting 'my-setting' deletes both 'my_setting' * and 'my-setting') */ static int setting_delete_ud(config_setting_t* cfg, struct config_desc *desc) { char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); if (config_setting_remove(cfg, name) == CONFIG_TRUE) return CONFIG_TRUE; strswap_ud('-', name); return config_setting_remove(cfg, name); } /* When traversing configuration, allocate memory for plural * types, init for scalars */ static void read_block_init(void* target, config_setting_t* cfg, struct config_desc* desc) { size_t len = 0; void* block; config_setting_t* setting; switch (desc->type) { case CFG_LIST: case CFG_ARRAY: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); } block = calloc(len, desc->size); *(size_t*)(((char*)target) + desc->offset_len) = len; *(void**)(((char*)target) + desc->offset) = block; TRACE_READ((" sizing for %zu elems ", len)); break; case CFG_GROUP: block = calloc(1, desc->size); *(void**)(((char*)target) + desc->offset) = block; TRACE_READ((" sizing for %zu elems ", len)); break; default: /* scalar types: copy default */ memcpy(((char*)target) + desc->offset, &desc->default_val, desc->size); TRACE_READ(("setting %s to default ", desc->name)); print_setting(desc->type,(char*)target + desc->offset); break; } } static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg); /* When traversing configuration, set value from config * file, or command line * return: 0 if not set, 1 if set somehow */ static int read_block_setval(void* target, config_setting_t* cfg, struct config_desc* desc, char** errmsg) { int i; size_t len = 0; void* block; int in_cfg = 0, in_cl = 0; /* Present in config file? present on command line? */ config_setting_t* setting = NULL; switch (desc->type) { case CFG_LIST: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); block = *(void**)(((char*)target) + desc->offset); for (i = 0; i < len; i++) { config_setting_t* elem = config_setting_get_elem(setting, i); if (!read_block(elem, (char*)block + desc->size * i, desc->sub_group, errmsg)) return 0; } } break; case CFG_ARRAY: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); block = *(void**)(((char*)target) + desc->offset); for (i = 0; i < len; i++) { config_setting_t* elem = config_setting_get_elem(setting, i); settingcpy(desc->array_type, (char*)block + desc->size * i, elem); TRACE_READ(("[%d] = ", i)); print_setting(desc->array_type, (char*)block + desc->size *i); TRACE_READ(("\n")); } setting_delete_ud(cfg, desc); } break; case CFG_GROUP: if (cfg) setting = config_setting_lookup_ud(cfg, desc); block = *(void**)(((char*)target) + desc->offset); if (!read_block(setting, block, desc->sub_group, errmsg)) return 0; break; default: /* scalar types */ TRACE_READ((" `%s'", desc->name)); if (cfg && config_setting_lookup_ud(cfg, desc)) { TRACE_READ((" in config file: ")); /* setting is present in cfg, look it up */ if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) { TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type])); asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n", desc->name, type2str[desc->type]); return 0; } print_setting(desc->type, (((char*)target) + desc->offset)); setting_delete_ud(cfg, desc); in_cfg = 1; } else { TRACE_READ((" not in config file")); } if (desc->arg_cl && (*(struct arg_int**)desc->arg_cl)->count) { clcpy(desc->type, ((char*)target) + desc->offset, desc->arg_cl); TRACE_READ((", command line sets to ")); print_setting(desc->type, (((char*)target) + desc->offset)); in_cl = 1; } else { TRACE_READ((", not in command line")); } if (!(in_cfg || in_cl)) { TRACE_READ(("\n")); return 0; } TRACE_READ(("\n")); break; } return 1; } /* Set *_is_present flag for target */ static void target_is_present(void* target, struct config_desc* desc, int val) { if (desc->optional) { /* _is_present only exists in target for optional settings */ TRACE_READ((" mark as set")); *(int*)((char*)target + desc->offset_present) = val; } } /* traverses the configuration; allocates memory if needed, * set to default if exists, * fill from configuration file if present, overrides or set from * command line if present, verifies mandatory options have * been set * target: base address of the group being processed */ static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg) { int set; for (; desc->name; desc++) { TRACE_READ(("reading %s%s%s: ", desc->optional ? "optional " : "", desc->mandatory ? "mandatory " : "", desc->name)); desc->base_addr = target; read_block_init(target, cfg, desc); set = read_block_setval(target, cfg, desc, errmsg); if (!set && desc->mandatory) { asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name); return 0; } if (desc->optional) target_is_present(target, desc, set && desc->optional); } return 1; } /* Copy regex match into newly allocated string * out: newly allocated string (caller has to free it) * in: string into which the match was made * pmatch: the match to extract */ static void pmatchcpy(char** out, const char* in, regmatch_t* pmatch) { int len = pmatch->rm_eo - pmatch->rm_so; *out = calloc(len+1, 1); memcpy(*out, in + pmatch->rm_so, len); } /* Processes a list of targets within one element, setting * the values in the target setting * target: where to put the data * arg: CL arg containing the target fields * clval: command line parameter * pmatch: regex match array into clval */ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, const char* clval, regmatch_t* pmatch) { int pmatch_cnt = 1; struct compound_cl_target* target; for (target = arg->targets; target->element; target++) { struct config_desc * element = target->element; if (target->match) { TRACE_CMPD((" match %d rm_so %d rm_eo %d type %d\n", pmatch_cnt, pmatch[pmatch_cnt].rm_so, pmatch[pmatch_cnt].rm_eo, element->type )); if (pmatch[pmatch_cnt].rm_so == -1) { /* This should not happen as regexec() did * match before, unless there is a * discrepency between the regex and the * number of backreferences */ return 0; } char* str; pmatchcpy(&str, clval, &pmatch[pmatch_cnt]); stringcpy(element->type, (char*)target_addr + element->offset, str); TRACE_CMPD(("setting %p+%zu to : ", target_addr , element->offset)); print_setting(element->type , (char*)target_addr + element->offset); TRACE_CMPD(("\n")); /* str is temporary buffer for type conversion, except for strings which we * need to keep around so don't free them */ if (element->type != CFG_STRING) free(str); pmatch_cnt++; } else { /* don't use matching, set constant */ any_valcpy(element->type, (char*)target_addr + element->offset, target->value); } } return 1; } /* Goes over a list, finds if a group matches the specified string and overwrite * it if it does. */ static int override_on_str(struct compound_cl_arg* arg, const char* str, regmatch_t* pmatch) { struct config_desc * desc = arg->base_entry; void* list_base = *(void**)(desc->base_addr + desc->offset); size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len); size_t elem_size = desc->size; int i; for (i = 0; i < list_len; i++) { char* group_base = ((char*)list_base + i * elem_size); char* cfg_member = *(char**)(group_base + arg->override_desc->offset); if (!strcmp(str, cfg_member)) { memset(group_base, 0, elem_size); struct arg_str* arg_cl = *arg->arg_cl; if (!set_target_fields(group_base, arg, arg_cl->sval[0], pmatch)) return 0; return 1; } } return 0; } /* Goes over a list and override group if needed */ static int override_elem(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) { char* str; int allocated = 0; int res; if (arg->override_matchindex) { struct arg_str* arg_cl = *arg->arg_cl; pmatchcpy(&str, arg_cl->sval[arg_index], &pmatch[arg->override_matchindex]); allocated = 1; } else { str = arg->override_const; } res = override_on_str(arg, str, pmatch); if (allocated) free(str); return res; } /* Add an argument to a list, overriding if required or * appending otherwise */ static int add_arg_to_list(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) { struct config_desc * desc = arg->base_entry; void* list_base = *(void**)(desc->base_addr + desc->offset); size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len); size_t elem_size = desc->size; /* are we overriding an existing group? */ if (arg->override_desc) if (override_elem(arg, arg_index, pmatch)) return 1; /* override not found or no override, append element and * zero it out */ list_len++; list_base = realloc(list_base, list_len * elem_size); *(size_t*)(desc->base_addr + desc->offset_len) = list_len; *(void**)(desc->base_addr + desc->offset) = list_base; memset(list_base + (list_len - 1) * elem_size, 0, elem_size); struct arg_str* arg_cl = *arg->arg_cl; if (!set_target_fields((char*)list_base + (list_len - 1) * elem_size, arg, arg_cl->sval[arg_index], pmatch)) { return 0; } return 1; } /* TODO: pass pmatch size as parameter or something */ #define MAX_MATCH 10 #ifndef LIBPCRE static int regcompmatch_posix( regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { char* regerr; struct arg_str* arg_cl = *arg->arg_cl; regex_t preg; int res = regcomp(&preg, arg->regex, REG_EXTENDED); if (res) { int errlen = regerror(res, &preg, NULL, 0); regerr = malloc(errlen); regerror(res, &preg, regerr, errlen); asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr); free(regerr); return 0; } res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0); if (res) { asprintf(errmsg, "--%s %s: Illegal argument", arg_cl->hdr.longopts, arg->regex); return 0; } return 1; } #endif #ifdef LIBPCRE static int regcompmatch_pcre2( regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { int i, error; pcre2_code* pcre; PCRE2_UCHAR8 err_str[120]; /* ample, according to pcre2api(3) */ pcre2_match_data* matches; PCRE2_SIZE error_offset; struct arg_str* arg_cl = *arg->arg_cl; pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL); if (!pcre) { pcre2_get_error_message(error, err_str, sizeof(err_str)); asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n", arg->regex, error, err_str, error_offset); return 0; } matches = pcre2_match_data_create(MAX_MATCH, NULL); int res = pcre2_match(pcre, (PCRE2_SPTR8)arg_cl->sval[arg_index], PCRE2_ZERO_TERMINATED, 0, 0, matches, NULL); if (res < 0) { pcre2_get_error_message(res, err_str, sizeof(err_str)); asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n", arg_cl->sval[arg_index], arg->regex, res, err_str); return 0; } PCRE2_SIZE *ovec = pcre2_get_ovector_pointer(matches); if (res > MAX_MATCH) res = MAX_MATCH; /* From pcre2posix.c */ for (i = 0; i < res; i++) { pmatch[i].rm_so = (ovec[i*2] == PCRE2_UNSET) ? -1 : ovec[i*2]; pmatch[i].rm_eo = (ovec[i*2+1] == PCRE2_UNSET) ? -1 : ovec[i*2+1]; } for (; i < MAX_MATCH; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; pcre2_match_data_free(matches); return 1; } #endif /* Regex fiddling: uses info in arg to fill pmatch * arg: description of the command line argument * arg_index: occurence of this argument on the command line */ static int regcompmatch(regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { #if LIBPCRE return regcompmatch_pcre2(pmatch, arg, arg_index, errmsg); #else return regcompmatch_posix(pmatch, arg, arg_index, errmsg); #endif } /* Read compound options described in `arg`, from `cfg`, to `setting` */ static int read_compounds(config_setting_t* cfg, void* setting, struct compound_cl_arg* arg, char** errmsg) { int arg_i; struct arg_str* arg_cl; regmatch_t pmatch[MAX_MATCH]; for (; arg->regex; arg++) { arg_cl = *arg->arg_cl; TRACE_CMPD(("Compound %s occurs %d : ", arg_cl->hdr.longopts, arg_cl->count)); for (arg_i = 0; arg_i < arg_cl->count; arg_i++) { if (!regcompmatch(&pmatch[0], arg, arg_i, errmsg)) return 0; TRACE_CMPD(("`%s' matched\n", arg_cl->sval[arg_i])); switch (arg->base_entry->type) { case CFG_LIST: /* In a list, find the end or the element to override */ if (!add_arg_to_list(arg, arg_i, pmatch)) { return 0; } break; /* Semantics for CFG_ARRAY TBD */ case CFG_GROUP: if (!set_target_fields( /* base_addr is the same for all elements in the group */ arg->targets[0].element->base_addr, arg, arg_cl->sval[arg_i], pmatch)) return 0; default: TRACE_CMPD(("error, compound on type %d\n", arg->base_entry->type)); break; } } TRACE_CMPD(("done %s\n", arg_cl->hdr.longopts)); } return 1; } /* read config file `filename` into `c` */ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg) { /* Read config file */ if (config_read_file(c, filename) == CONFIG_FALSE) { if (config_error_line(c) != 0) { asprintf(errmsg, "%s:%d:%s", filename, config_error_line(c), config_error_text(c)); return 0; } asprintf(errmsg, "%s:%s", filename, config_error_text(c)); return 0; } return 1; } /* Allocates a new string that represents the setting value, which must be a scalar */ static void scalar_to_string(char** strp, config_setting_t* s) { switch(config_setting_type(s)) { case CONFIG_TYPE_INT: asprintf(strp, "%d\n", config_setting_get_int(s)); break; case CONFIG_TYPE_BOOL: asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" ); break; case CONFIG_TYPE_INT64: asprintf(strp, "%lld\n", config_setting_get_int64(s)); break; case CONFIG_TYPE_FLOAT: asprintf(strp, "%lf\n", config_setting_get_float(s)); break; case CONFIG_TYPE_STRING: asprintf(strp, "%s\n", config_setting_get_string(s)); break; default: /* This means a bug */ fprintf(stderr, "Unexpected type %d\n", config_setting_type(s)); exit(1); } } /* Typesets all the settings in a configuration as a * newly-allocated string. The string management is caller's * responsability. * Returns the number of scalars in the configuration */ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp) { int i, len, res = 0; config_setting_t* child; char* subpath, *value, *old; const char* name; len = config_setting_length(parent); for (i = 0; i < len; i++) { child = config_setting_get_elem(parent, i); name = config_setting_name(child); if (!name) name = ""; if(config_setting_is_list(parent) || config_setting_is_array(parent)) { asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name); } else { asprintf(&subpath, "%s/%s", path, name); } if (config_setting_is_scalar(child)) { scalar_to_string(&value, child); /* Add value to the output string */ if (*strp) { asprintf(&old, "%s", *strp); free(*strp); } else { asprintf(&old, "%s", ""); } asprintf(strp, "%s%s:%s", old, subpath, value); free(value); free(old); res++; /* At least one scalar was found */ } else { /* It's an aggregate -- descend into it */ res += cfg_as_string(child, subpath, strp); } free(subpath); } return res; } /* 0: success <0: error */ int echocfg_cl_parse(int argc, char* argv[], struct echocfg_item* cfg) { int nerrors, res; config_t c; char* errmsg; config_setting_t* s; void* argtable[] = { #ifdef LIBCONFIG echocfg_conffile = arg_filen("F", "config", "", 0, 1, "Specify configuration file"), #endif echocfg_udp = arg_litn(NULL, "udp", 0, 1, ""), echocfg_prefix = arg_strn(NULL, "prefix", "", 0, 1, ""), echocfg_listen_host = arg_strn(NULL, "listen-host", "", 0, 10, ""), echocfg_listen_port = arg_strn(NULL, "listen-port", "", 0, 10, ""), echocfg_listen = arg_strn("p", "listen", "", 0, 10, "Listen on host:port"), echocfg_end = arg_end(10) }; /* Parse command line */ nerrors = arg_parse(argc, argv, argtable); if (nerrors) { arg_print_errors(stdout, echocfg_end, "echocfg"); arg_print_syntax(stdout, argtable, "\n"); arg_print_glossary(stdout, argtable, " %-25s\t%s\n"); return -1; } config_init(&c); if (echocfg_conffile && echocfg_conffile->count) { if (!c2s_parse_file(echocfg_conffile->filename[0], &c, &errmsg)) { fprintf(stderr, "%s\n", errmsg); return -1; } } s = config_root_setting(&c); res = read_block(s, cfg, table_echocfg, &errmsg); if (!res) { fprintf(stderr, "%s\n", errmsg); return -1; } res = read_compounds(s, cfg, compound_cl_args, &errmsg); if (!res) { fprintf(stderr, "%s\n", errmsg); return -1; } errmsg = NULL; res = cfg_as_string(s, "", &errmsg); if (res) fprintf(stderr, "Unknown settings:\n%s\n", errmsg); return 0; } static void indent(FILE* out, int depth) { int i; for (i = 0; i < depth; i++) fprintf(out, " "); } static void echocfg_listen_fprint( FILE* out, struct echocfg_listen_item* echocfg_listen, int depth) { indent(out, depth); fprintf(out, "host: %s", echocfg_listen->host); fprintf(out, "\n"); indent(out, depth); fprintf(out, "port: %s", echocfg_listen->port); fprintf(out, "\n"); } void echocfg_fprint( FILE* out, struct echocfg_item* echocfg, int depth) { int i; indent(out, depth); fprintf(out, "udp: %d", echocfg->udp); fprintf(out, "\n"); indent(out, depth); fprintf(out, "prefix: %s", echocfg->prefix); fprintf(out, "\n"); indent(out, depth); fprintf(out, "listen [%zu]:\n", echocfg->listen_len); for (i = 0; i < echocfg->listen_len; i++) { echocfg_listen_fprint(out, &echocfg->listen[i], depth+1); } } sslh-1.22c/echosrv-conf.h000066400000000000000000000040651411244446000153240ustar00rootroot00000000000000/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) * on Fri Aug 13 18:03:20 2021. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2021 Yves Rutschle # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. */ #ifndef C2S_ECHOCFG_H #define C2S_ECHOCFG_H #ifdef LIBCONFIG # include #endif struct echocfg_listen_item { char* host; char* port; }; struct echocfg_item { int udp; char* prefix; size_t listen_len; struct echocfg_listen_item* listen; }; int echocfg_parse_file( const char* filename, struct echocfg_item* echocfg, const char** errmsg); void echocfg_fprint( FILE* out, struct echocfg_item *echocfg, int depth); int echocfg_cl_parse( int argc, char* argv[], struct echocfg_item *echocfg); #endif sslh-1.22c/echosrv.c000066400000000000000000000216021411244446000143700ustar00rootroot00000000000000/* echosrv: a simple line echo server with optional prefix adding. * * echsrv --listen localhost6:1234 --prefix "ssl: " * * This will bind to 1234, and echo every line pre-pending "ssl: ". This is * used for testing: we create several such servers with different prefixes, * then we connect test clients that can then check they get the proper data * back (thus testing that shoveling works both ways) with the correct prefix * (thus testing it connected to the expected service). * **/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define cfg sslhcfg #include "common.h" #undef cfg #include "echosrv-conf.h" /* Added to make the code compilable under CYGWIN * */ #ifndef SA_NOCLDWAIT #define SA_NOCLDWAIT 0 #endif struct echocfg_item cfg; void check_res_dump(int res, struct addrinfo *addr, char* syscall) { char buf[NI_MAXHOST]; if (res == -1) { if (addr) fprintf(stderr, "error %s:%s: %s\n", sprintaddr(buf, sizeof(buf), addr), syscall, strerror(errno)); else fprintf(stderr, "Dying just because\n"); exit(1); } } void start_echo(int fd) { int res; char buffer[1 << 20]; int ret, prefix_len; int first = 1; prefix_len = strlen(cfg.prefix); memset(buffer, 0, sizeof(buffer)); strcpy(buffer, cfg.prefix); while (1) { ret = read(fd, buffer + prefix_len, sizeof(buffer) - prefix_len); if (ret <= 0) { fprintf(stderr, "%s", strerror(errno)); return; } if (first) { res = write(fd, buffer, ret + prefix_len); first = 0; if (write(1, buffer, ret + prefix_len) < 0) { fprintf(stderr, "%s", strerror(errno)); } } else { res = write(fd, buffer + prefix_len, ret); } if (res < 0) { fprintf(stderr, "%s", strerror(errno)); return; } } } /* TCP echo server: accepts connections to an endpoint, forks an echo for each * connection, forever. Prefix is added at start of response stream */ void tcp_echo(struct listen_endpoint* listen_socket) { while (1) { int in_socket = accept(listen_socket->socketfd, 0, 0); CHECK_RES_DIE(in_socket, "accept"); if (!fork()) { close(listen_socket->socketfd); start_echo(in_socket); exit(0); } close(in_socket); } } void print_udp_xchange(int sockfd, struct sockaddr* addr, socklen_t addrlen) { struct addrinfo src_addrinfo, to_addrinfo; char str_addr[NI_MAXHOST+1+NI_MAXSERV+1]; char str_addr2[NI_MAXHOST+1+NI_MAXSERV+1]; struct sockaddr_storage ss; src_addrinfo.ai_addr = (struct sockaddr*)&ss; src_addrinfo.ai_addrlen = sizeof(ss); getsockname(sockfd, src_addrinfo.ai_addr, &src_addrinfo.ai_addrlen); to_addrinfo.ai_addr = addr; to_addrinfo.ai_addrlen = sizeof(*addr); fprintf(stderr, "UDP local %s remote %s\n", sprintaddr(str_addr, sizeof(str_addr), &src_addrinfo), sprintaddr(str_addr2, sizeof(str_addr2), &to_addrinfo) ); } /* UDP echo server: receive packets, return them, forever. * Prefix is added at each packet */ void udp_echo(struct listen_endpoint* listen_socket) { char data[65536]; struct sockaddr src_addr; socklen_t addrlen; memset(data, 0, sizeof(data)); size_t prefix_len = strlen(cfg.prefix); memcpy(data, cfg.prefix, prefix_len); while (1) { addrlen = sizeof(src_addr); size_t len = recvfrom(listen_socket->socketfd, data + prefix_len, sizeof(data) - prefix_len, 0, &src_addr, &addrlen); if (len < 0) { perror("recvfrom"); } *(data + prefix_len + len) = 0; fprintf(stderr, "%ld: %s\n", len, data + prefix_len); print_udp_xchange(listen_socket->socketfd, &src_addr, addrlen); int res = sendto(listen_socket->socketfd, data, len + prefix_len, 0, &src_addr, addrlen); if (res < 0) { perror("sendto"); } } } void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) { int i; for (i = 0; i < num_addr_listen; i++) { if (!fork()) { if (cfg.udp) { udp_echo(&listen_sockets[i]); } else { tcp_echo(&listen_sockets[i]); } } } wait(NULL); } /* Following is a number of utility functions copied from common.c: linking * against common.o directly means echosrv has to work with sslh config struct, * which makes it all too awkward */ /* simplified from common.c */ char* sprintaddr(char* buf, size_t size, struct addrinfo *a) { char host[NI_MAXHOST], serv[NI_MAXSERV]; int res; res = getnameinfo(a->ai_addr, a->ai_addrlen, host, sizeof(host), serv, sizeof(serv), 0 ); if (res) { /* Name resolution failed: do it numerically instead */ res = getnameinfo(a->ai_addr, a->ai_addrlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); /* should not fail but... */ if (res) { strcpy(host, "?"); strcpy(serv, "?"); } } snprintf(buf, size, "%s:%s", host, serv); return buf; } /* simplified from common.c */ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp) { struct sockaddr_storage *saddr; int sockfd, one, res; saddr = (struct sockaddr_storage*)addr->ai_addr; sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0); check_res_dump(sockfd, addr, "socket"); one = 1; res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); check_res_dump(res, addr, "setsockopt(SO_REUSEADDR)"); if (addr->ai_addr->sa_family == AF_INET6) { res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one)); check_res_dump(res, addr, "setsockopt(IPV6_V6ONLY)"); } res = bind(sockfd, addr->ai_addr, addr->ai_addrlen); check_res_dump(res, addr, "bind"); if (!udp) { res = listen (sockfd, 50); check_res_dump(res, addr, "listen"); } return sockfd; } /* simplified from common.c */ int resolve_split_name(struct addrinfo **out, char* host, char* serv) { struct addrinfo hint; char *end; int res; memset(&hint, 0, sizeof(hint)); hint.ai_family = PF_UNSPEC; hint.ai_socktype = SOCK_STREAM; /* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets * around IP address */ if (host[0] == '[') { end = strrchr(host, ']'); if (!end) { fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host); return -1; } host++; /* skip first bracket */ *end = 0; /* remove last bracket */ } res = getaddrinfo(host, serv, &hint, out); if (res) fprintf(stderr, "%s `%s:%s'\n", gai_strerror(res), host, serv); return res; } int start_listen_sockets(struct listen_endpoint *sockfd[]) { struct addrinfo *addr, *start_addr; char buf[NI_MAXHOST]; int i, res; int num_addr = 0, keepalive = 0, udp = 0; *sockfd = NULL; fprintf(stderr, "Listening to:\n"); for (i = 0; i < cfg.listen_len; i++) { udp = cfg.udp; res = resolve_split_name(&start_addr, cfg.listen[i].host, cfg.listen[i].port); if (res) exit(4); for (addr = start_addr; addr; addr = addr->ai_next) { num_addr++; *sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd)); (*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, keepalive, udp); (*sockfd)[num_addr-1].type = udp ? SOCK_DGRAM : SOCK_STREAM; fprintf(stderr, "%d:\t%s\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr)); } freeaddrinfo(start_addr); } return num_addr; } int main(int argc, char *argv[]) { extern char *optarg; extern int optind; int num_addr_listen; struct listen_endpoint *listen_sockets; memset(&cfg, 0, sizeof(cfg)); if (echocfg_cl_parse(argc, argv, &cfg)) exit(1); echocfg_fprint(stdout, &cfg, 0); num_addr_listen = start_listen_sockets(&listen_sockets); main_loop(listen_sockets, num_addr_listen); return 0; } sslh-1.22c/echosrv.cfg000066400000000000000000000017271411244446000147130ustar00rootroot00000000000000# conf2struct for echosrv header: "echosrv-conf.h"; parser: "echosrv-conf.c"; printer: true; conffile_option: ("F", "config"); config: { name: "echocfg", type: "list", items: ( {name: "udp", type: "bool"; default: false; }, {name: "prefix", type: "string"; }, { name: "listen", type: "list", items: ( { name: "host"; type: "string"; var: true; }, { name: "port"; type: "string"; var: true; } ) } ) } cl_groups: ( { name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port"; short: "p"; argdesc: ""; list: "listen"; # no override, this just adds to the list (and thus can be specified several times) targets: ( { path: "host"; value: "$1" }, { path: "port"; value: "$2" } ); } ) sslh-1.22c/example.cfg000066400000000000000000000123461411244446000146740ustar00rootroot00000000000000# This file is provided as documentation to show what is # possible. It should not be used as-is, and probably should # not be used as a starting point for a working # configuration. Instead use basic.cfg. verbose: 0; foreground: true; inetd: false; numeric: false; transparent: false; timeout: 2; user: "nobody"; pidfile: "/var/run/sslh.pid"; chroot: "/var/empty"; # Specify which syslog facility to use (names for your # system are usually defined in /usr/include/*/sys/syslog.h # or equivalent) # Default is "auth" # "none" disables use of syslog syslog_facility: "auth"; # List of interfaces on which we should listen # Options: listen: ( { host: "thelonious"; port: "443"; }, { host: "thelonious"; port: "8080"; keepalive: true; }, { host: "thelonious"; is_udp: true; port: "443" } ); # List of protocols # # Each protocol entry consists of: # name: name of the probe. These are listed on the command # line (ssh -?), plus 'regex' and 'timeout'. # service: (optional) libwrap service name (see hosts_access(5)) # host, port: where to connect when this probe succeeds # log_level: 0 to turn off logging # 1 to log each incoming connection # keepalive: Should TCP keepalive be on or off for that # connection (default is off) # fork: Should a new process be forked for this protocol? # (only useful for sslh-select) # tfo_ok: Set to true if the server supports TCP FAST OPEN # transparent: Set to true to proxy this protocol # transparently (server sees the remote client IP # address). Same as the global option, but per-protocol # # Probe-specific options: # (sslh will try each probe in order they are declared, and # connect to the first that matches.) # # tls: # sni_hostnames: list of FQDN for that target # alpn_protocols: list of ALPN protocols for that target, see: # https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids # # if both sni_hostnames AND alpn_protocols are specified, both must match # # if neither are set, it is just checked whether this is the TLS protocol or not # # Obviously set the most specific probes # first, and if you use TLS with no ALPN/SNI # set it as the last TLS probe # regex: # regex_patterns: list of patterns to match for # that target. # # You can specify several of 'regex' and 'tls'. protocols: ( { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; keepalive: true; fork: true; tfo_ok: true }, { name: "http"; host: "localhost"; port: "80"; }, # match BOTH ALPN/SNI { name: "tls"; host: "localhost"; port: "5223"; alpn_protocols: [ "xmpp-client" ]; sni_hostnames: [ "im.somethingelse.net" ]; log_level: 0; tfo_ok: true }, # just match ALPN { name: "tls"; host: "localhost"; port: "443"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; log_level: 0; tfo_ok: true }, { name: "tls"; host: "localhost"; port: "xmpp-client"; alpn_protocols: [ "xmpp-client" ]; log_level: 0; tfo_ok: true }, # just match SNI { name: "tls"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; log_level: 0; tfo_ok: true }, { name: "tls"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; log_level: 0; tfo_ok: true }, # Let's Encrypt (tls-alpn-* challenges) { name: "tls"; host: "localhost"; port: "letsencrypt-client"; alpn_protocols: [ "acme-tls/1" ]; log_level: 0;}, # catch anything else TLS { name: "tls"; host: "localhost"; port: "443"; tfo_ok: true }, # Forward UDP { name: "regex"; host: "localhost"; is_udp: true; port: "123"; udp_timeout: 20; # Time after which the "connection" is forgotten regex_patterns: [ "hello" ]; }, # Forward Teamspeak3 (Voice only) { name: "regex"; host: "localhost"; is_udp: true; port: "9987"; regex_patterns: [ "TS3INIT1" ]; }, # Forward IETF QUIC-50 ("Q050" -> "\x51\x30\x35\x30") # Remember that the regex needs to be adjusted for every supported QUIC version. { name: "regex"; host: "localhost"; is_udp: true; port: "4433"; regex_patterns: [ "\x51\x30\x35\x30" ]; }, # Regex examples -- better use the built-in probes for real-world use! # OpenVPN { name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; }, # Jabber { name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ]; minlength: 60; # Won't even try to match the regex if we don't have that many bytes }, # Catch-all (but better use 'anyprot') { name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; }, # Where to connect in case of timeout (defaults to ssh) { name: "timeout"; service: "daytime"; host: "localhost"; port: "daytime"; } ); # Optionally, specify to which protocol to connect in case # of timeout (defaults to "ssh"). # You can timeout to any arbitrary address by setting an # entry in 'protocols' named "timeout". # This enables you to set a tcpd service name for this # protocol too. on-timeout: "timeout"; sslh-1.22c/gap.c000066400000000000000000000056031411244446000134710ustar00rootroot00000000000000/* gap.c: gap, a simple, dynamically-growing array of pointers that never shrinks # Copyright (C) 2021 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #include #include #include #include #include "sslh-conf.h" #include "gap.h" typedef struct gap_array { int len; /* Number of elements in array */ void** array; } gap_array; /* Allocate one page-worth of elements */ static int gap_len_alloc(int elem_size) { return getpagesize() / elem_size; } /* Creates a new gap at least `len` big, all pointers are initialised at NULL */ gap_array* gap_init(int len) { gap_array* gap = malloc(sizeof(*gap)); if (!gap) return NULL; memset(gap, 0, sizeof(*gap)); int elem_size = sizeof(gap->array[0]); gap->len = gap_len_alloc(elem_size); if (gap->len < len) gap->len = len; gap->array = malloc(gap->len * elem_size); if (!gap->array) return NULL; for (int i = 0; i < gap->len; i++) gap->array[i] = NULL; return gap; } void* gap_get(gap_array* gap, int index) { return gap->array[index]; } static int gap_extend(gap_array* gap) { int elem_size = sizeof(gap->array[0]); int new_length = gap->len + gap_len_alloc(elem_size); void** new = realloc(gap->array, new_length * elem_size); if (!new) return -1; gap->array = new; for (int i = gap->len; i < new_length; i++) { gap->array[i] = NULL; } gap->len = new_length; return 0; } int gap_set(gap_array* gap, int index, void* ptr) { while (index >= gap->len) { int res = gap_extend(gap); if (res == -1) return -1; } gap->array[index] = ptr; return 0; } void gap_destroy(gap_array* gap) { free(gap->array); free(gap); } /* In gap, find element pointing to ptr, then shift the rest of the array that * is considered len elements long. * A poor man's list, if you will. Currently only used to remove probing * connections, so it only copies a few pointers at most. * Returns -1 if ptr was not found */ int gap_remove_ptr(gap_array* gap, void* ptr, int len) { int start, i; for (i = 0; i < len; i++) if (gap->array[i] == ptr) break; if (i < len) start = i; else return -1; for (i = start; i < len; i++) { gap->array[i] = gap->array[i+1]; } return 0; } sslh-1.22c/gap.h000066400000000000000000000004401411244446000134700ustar00rootroot00000000000000#ifndef GAP_H #define GAP_H typedef struct gap_array gap_array; gap_array* gap_init(int len); void* gap_get(gap_array* gap, int index); int gap_set(gap_array* gap, int index, void* ptr); void gap_destroy(gap_array* gap); int gap_remove_ptr(gap_array* gap, void* ptr, int len); #endif sslh-1.22c/genver.sh000077500000000000000000000027121411244446000144010ustar00rootroot00000000000000#! /bin/sh if [ ${#} -eq 1 ] && [ "x$1" = "x-r" ]; then # release text only QUIET=1 else QUIET=0 fi if [ ! -d .git ] || ! `(git status | grep -q "On branch") 2> /dev/null`; then # If we don't have git, we can't work out what # version this is. It must have been downloaded as a # zip file. # If downloaded from the release page, the directory # has the version number. release=`pwd | sed s/.*sslh-// | grep "[[:digit:]]"` if [ "x$release" = "x" ]; then # If downloaded from the head, Github creates the # zip file with all files dated from the last # change: use the Makefile's modification time as a # release number release=head-`perl -MPOSIX -e 'print strftime "%Y-%m-%d",localtime((stat "Makefile")[9])'` fi fi if [ -d .git ] && head=`git rev-parse --verify HEAD 2>/dev/null`; then # generate the version info based on the tag release=`(git describe --tags || git --describe || git describe --all --long) \ 2>/dev/null | tr -d '\n'` # Are there uncommitted changes? git update-index --refresh --unmerged > /dev/null if git diff-index --name-only HEAD | grep -v "^scripts/package" \ | read dummy; then release="$release-dirty" fi fi if [ $QUIET -ne 1 ]; then printf "#ifndef VERSION_H \n" printf "#define VERSION_H \n\n" printf "#define VERSION \"$release\"\n" printf "#endif\n" else printf "$release\n" fi sslh-1.22c/probe.c000066400000000000000000000341501411244446000140300ustar00rootroot00000000000000/* # probe.c: Code for probing protocols # # Copyright (C) 2007-2019 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #define _GNU_SOURCE #include #ifdef ENABLE_REGEX #define PCRE2_CODE_UNIT_WIDTH 8 #include #endif #include #include "probe.h" static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_openvpn_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_tinc_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_xmpp_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_socks5_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item*); static int is_true(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { return 1; } /* Table of protocols that have a built-in probe */ static struct protocol_probe_desc builtins[] = { /* description probe */ { "ssh", is_ssh_protocol}, { "openvpn", is_openvpn_protocol }, { "tinc", is_tinc_protocol }, { "xmpp", is_xmpp_protocol }, { "http", is_http_protocol }, { "tls", is_tls_protocol }, { "adb", is_adb_protocol }, { "socks5", is_socks5_protocol }, { "syslog", is_syslog_protocol }, { "anyprot", is_true } }; /* TODO I think this has to go */ struct protocol_probe_desc* get_builtins(void) { return builtins; } int get_num_builtins(void) { return ARRAY_SIZE(builtins); } /* Returns the protocol to connect to in case of timeout; * if not found, return the first protocol specified */ struct sslhcfg_protocols_item* timeout_protocol(void) { int i; for (i = 0; i < cfg.protocols_len; i++) { if (!strcmp(cfg.protocols[i].name, cfg.on_timeout)) return &cfg.protocols[i]; } return &cfg.protocols[0]; } /* From http://grapsus.net/blog/post/Hexadecimal-dump-in-C */ #define HEXDUMP_COLS 16 void hexdump(const char *mem, unsigned int len) { unsigned int i, j; for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) { /* print offset */ if(i % HEXDUMP_COLS == 0) fprintf(stderr, "0x%06x: ", i); /* print hex data */ if(i < len) fprintf(stderr, "%02x ", 0xFF & mem[i]); else /* end of block, just aligning for ASCII dump */ fprintf(stderr, " "); /* print ASCII dump */ if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) { for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) { if(j >= len) /* end of block, not really printing */ fputc(' ', stderr); else if(isprint(mem[j])) /* printable char */ fputc(0xFF & mem[j], stderr); else /* other char */ fputc('.', stderr); } fputc('\n', stderr); } } } /* Is the buffer the beginning of an SSH connection? */ static int is_ssh_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { if (len < 4) return PROBE_AGAIN; return !strncmp(p, "SSH-", 4); } /* Is the buffer the beginning of an OpenVPN connection? * * Code inspired from OpenVPN port-share option; however, OpenVPN code is * wrong: users using pre-shared secrets have non-initialised key_id fields so * p[3] & 7 should not be looked at, and also the key_method can be specified * to 1 which changes the opcode to P_CONTROL_HARD_RESET_CLIENT_V1. * See: * http://www.fengnet.com/book/vpns%20illustrated%20tunnels%20%20vpnsand%20ipsec/ch08lev1sec5.html * and OpenVPN ssl.c, ssl.h and options.c */ static int is_openvpn_protocol (const char*p,ssize_t len, struct sslhcfg_protocols_item* proto) { int packet_len; if (len < 2) return PROBE_AGAIN; packet_len = ntohs(*(uint16_t*)p); return packet_len == len - 2; } /* Is the buffer the beginning of a tinc connections? * Protocol is documented here: http://www.tinc-vpn.org/documentation/tinc.pdf * First connection starts with "0 " in 1.0.15) * */ static int is_tinc_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { if (len < 2) return PROBE_AGAIN; return !strncmp(p, "0 ", 2); } /* Is the buffer the beginning of a jabber (XMPP) connections? * (Protocol is documented (http://tools.ietf.org/html/rfc6120) but for lazy * clients, just checking first frame containing "jabber" in xml entity) * */ static int is_xmpp_protocol( const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { if (memmem(p, len, "jabber", 6)) return PROBE_MATCH; /* sometimes the word 'jabber' shows up late in the initial string, sometimes after a newline. this makes sure we snarf the entire preamble and detect it. (fixed for adium/pidgin) */ if (len < 50) return PROBE_AGAIN; return PROBE_NEXT; } static int probe_http_method(const char *p, int len, const char *opt) { if (len < strlen(opt)) return PROBE_AGAIN; return !strncmp(p, opt, strlen(opt)); } /* Is the buffer the beginning of an HTTP connection? */ static int is_http_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { int res; /* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */ if (memmem(p, len, "HTTP", 4)) return PROBE_MATCH; #define PROBE_HTTP_METHOD(opt) if ((res = probe_http_method(p, len, opt)) != PROBE_NEXT) return res /* Otherwise it could be HTTP/1.0 without version: check if it's got an * HTTP method (RFC2616 5.1.1) */ PROBE_HTTP_METHOD("OPTIONS"); PROBE_HTTP_METHOD("GET"); PROBE_HTTP_METHOD("HEAD"); PROBE_HTTP_METHOD("POST"); PROBE_HTTP_METHOD("PUT"); PROBE_HTTP_METHOD("DELETE"); PROBE_HTTP_METHOD("TRACE"); PROBE_HTTP_METHOD("CONNECT"); #undef PROBE_HTTP_METHOD return PROBE_NEXT; } /* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */ static int is_tls_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { switch (parse_tls_header(proto->data, p, len)) { case TLS_MATCH: return PROBE_MATCH; case TLS_NOMATCH: return PROBE_NEXT; case TLS_ELENGTH: return PROBE_AGAIN; default: return PROBE_NEXT; } } static int probe_adb_cnxn_message(const char *p) { /* The initial ADB host->device packet has a command type of CNXN, and a * data payload starting with "host:". Note that current versions of the * client hardcode "host::" (with empty serialno and banner fields) but * other clients may populate those fields. */ return !memcmp(&p[0], "CNXN", 4) && !memcmp(&p[24], "host:", 5); } static int is_adb_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { /* amessage.data_length is not being checked, under the assumption that * a packet >= 30 bytes will have "something" in the payload field. * * 24 bytes for the message header and 5 bytes for the "host:" tag. * * ADB protocol: * https://android.googlesource.com/platform/system/adb/+/master/protocol.txt */ static const unsigned int min_data_packet_size = 30; if (len < min_data_packet_size) return PROBE_AGAIN; if (probe_adb_cnxn_message(&p[0]) == PROBE_MATCH) return PROBE_MATCH; /* In ADB v26.0.0 rc1-4321094, the initial host->device packet sends an * empty message before sending the CNXN command type. This was an * unintended side effect introduced in * https://android-review.googlesource.com/c/342653, and will be reverted for * a future release. */ static const unsigned char empty_message[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff }; if (len < min_data_packet_size + sizeof(empty_message)) return PROBE_AGAIN; if (memcmp(&p[0], empty_message, sizeof(empty_message))) return PROBE_NEXT; return probe_adb_cnxn_message(&p[sizeof(empty_message)]); } static int is_socks5_protocol(const char *p_in, ssize_t len, struct sslhcfg_protocols_item* proto) { unsigned char* p = (unsigned char*)p_in; int i; if (len < 2) return PROBE_AGAIN; /* First byte should be socks protocol version */ if (p[0] != 5) return PROBE_NEXT; /* Second byte should be number of supported * authentication methods, assuming maximum of 10, * as defined in https://www.iana.org/assignments/socks-methods/socks-methods.xhtml */ char m_count = p[1]; if (m_count < 1 || m_count > 10) return PROBE_NEXT; if (len < 2 + m_count) return PROBE_AGAIN; /* Each authentication method number should be in range 0..9 * (https://www.iana.org/assignments/socks-methods/socks-methods.xhtml) */ for (i = 0; i < m_count; i++) { if (p[2 + i] > 9) return PROBE_NEXT; } return PROBE_MATCH; } static int is_syslog_protocol(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { int res, i, j; res = sscanf(p, "<%d>", &i); if (res == 1) return 1; res = sscanf(p, "%d <%d>", &i, &j); if (res == 2) return 1; return 0; } static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item* proto) { #ifdef ENABLE_REGEX pcre2_code**probe = (pcre2_code**)proto->data; pcre2_match_data* matches; matches = pcre2_match_data_create(1, NULL); for (; *probe; probe++) { int res = pcre2_match(*probe, (PCRE2_SPTR8)p, len, 0, 0, matches, NULL); if (res >= 0) return 1; } return 0; #else /* Should never happen as we check when loading config file */ fprintf(stderr, "FATAL: regex probe called but not built in\n"); exit(5); #endif } /* Run all the probes on a buffer * Returns * PROBE_AGAIN if not enough data, and set *proto to NULL * PROBE_MATCH if protocol is identified, in which case *proto is set to * point to the appropriate protocol * */ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto) { struct sslhcfg_protocols_item* p; int i, res, again = 0; if (cfg.verbose > 1) { fprintf(stderr, "hexdump of incoming packet:\n"); hexdump(buf, len); } *proto = NULL; for (i = 0; i < cfg.protocols_len; i++) { char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"}; p = &cfg.protocols[i]; if (! p->probe) continue; if (cfg.verbose) fprintf(stderr, "probing for %s\n", p->name); /* Don't probe last protocol if it is anyprot (and store last protocol) */ if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot"))) break; if (p->minlength_is_present && (len < p->minlength )) { fprintf(stderr, "input too short, %d bytes but need %d\n", len , p->minlength); again++; continue; } res = p->probe(buf, len, p); if (cfg.verbose) fprintf(stderr, "probed for %s: %s\n", p->name, probe_str[res]); if (res == PROBE_MATCH) { *proto = p; return PROBE_MATCH; } if (res == PROBE_AGAIN) again++; } if (again) return PROBE_AGAIN; /* Everything failed: match the last one */ *proto = &cfg.protocols[cfg.protocols_len-1]; return PROBE_MATCH; } /* * Read the beginning of data coming from the client connection and check if * it's a known protocol. * Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in * which case cnx->proto is set to the appropriate protocol. */ int probe_client_protocol(struct connection *cnx) { char buffer[BUFSIZ]; ssize_t n; n = read(cnx->q[0].fd, buffer, sizeof(buffer)); /* It's possible that read() returns an error, e.g. if the client * disconnected between the previous call to select() and now. If that * happens, we just connect to the default protocol so the caller of this * function does not have to deal with a specific failure condition (the * connection will just fail later normally). */ if (n > 0) { defer_write(&cnx->q[1], buffer, n); return probe_buffer(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, &cnx->proto); } /* read() returned an error, so just connect to the last protocol to die */ cnx->proto = &cfg.protocols[cfg.protocols_len-1]; return PROBE_MATCH; } /* Returns the probe for specified protocol: * parameter is the description in builtins[], or "regex" * */ T_PROBE* get_probe(const char* description) { int i; for (i = 0; i < ARRAY_SIZE(builtins); i++) { if (!strcmp(builtins[i].name, description)) { return builtins[i].probe; } } /* Special case of "regex" probe (we don't want to set it in builtins * because builtins is also used to build the command-line options and * regexp is not legal on the command line)*/ if (!strcmp(description, "regex")) return regex_probe; /* Special case of "timeout" is allowed as a probe name in the * configuration file even though it's not really a probe */ if (!strcmp(description, "timeout")) return is_true; return NULL; } sslh-1.22c/probe.h000066400000000000000000000033331411244446000140340ustar00rootroot00000000000000/* API for probe.c */ #ifndef PROBE_H #define PROBE_H #include "common.h" #include "tls.h" typedef enum { PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */ PROBE_MATCH, /* Enough data, probe successful -- it's the current protocol */ PROBE_AGAIN, /* Not enough data for this probe, try again with more data */ } probe_result; struct sslhcfg_protocols_item; typedef int T_PROBE(const char*, ssize_t, struct sslhcfg_protocols_item*); struct protocol_probe_desc { const char* name; T_PROBE* probe; }; #include "sslh-conf.h" /* Returns a pointer to the array of builtin protocols */ struct protocol_probe_desc* get_builtins(void); /* Returns the number of builtin protocols */ int get_num_builtins(void); /* Returns the probe for specified protocol */ T_PROBE* get_probe(const char* description); /* Returns the head of the configured protocols */ struct sslhcfg_protocols_item* get_first_protocol(void); /* Set the list of configured protocols */ void set_protocol_list(struct sslhcfg_protocols_item*); /* probe_client_protocol * * Read the beginning of data coming from the client connection and check if * it's a known protocol. Then leave the data on the deferred * write buffer of the connection and returns a pointer to the protocol * structure */ int probe_client_protocol(struct connection *cnx); /* Probe, but on a buffer */ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto); /* set the protocol to connect to in case of timeout */ void set_ontimeout(const char* name); /* timeout_protocol * * Returns the protocol to connect to in case of timeout */ struct sslhcfg_protocols_item* timeout_protocol(void); void hexdump(const char*, unsigned int); #endif sslh-1.22c/scripts/000077500000000000000000000000001411244446000142415ustar00rootroot00000000000000sslh-1.22c/scripts/etc.init.d.sslh000077500000000000000000000017711411244446000171040ustar00rootroot00000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: sslh # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 1 # Short-Description: sslh proxy ssl & ssh connections ### END INIT INFO set -e tag=sslh facility=user.info # /etc/init.d/sslh: start and stop the sslh proxy daemon if test -f /etc/default/sslh; then . /etc/default/sslh fi # The prefix is normally filled by make install. If # installing by hand, fill it in yourself! PREFIX= DAEMON=$PREFIX/sbin/sslh start() { echo "Start services: sslh" $DAEMON -F/etc/sslh.cfg logger -t ${tag} -p ${facility} -i 'Started sslh' } stop() { echo "Stop services: sslh" killall $DAEMON logger -t ${tag} -p ${facility} -i 'Stopped sslh' } case "$1" in start) start ;; stop) stop ;; restart) stop sleep 5 start ;; *) echo "Usage: /etc/init.d/sslh {start|stop|restart}" >&2 ;; esac exit 0 sslh-1.22c/scripts/etc.rc.d.init.d.sslh.centos000077500000000000000000000032551411244446000212220ustar00rootroot00000000000000#!/bin/bash # # sslh Startup script for the SSL/SSH multiplexer # # chkconfig: - 13 87 # description: Sslh accepts connections on specified ports, and forwards # them further based on tests performed on the first data # packet sent by the remote client. # processname: sslh # config: /etc/sslh.cfg # config: /etc/sysconfig/sslh # pidfile: /var/run/sslh/sslh.pid # # Authors: # Andre Krajnik akrajnik@gmail.com - 2010-03-20 # Julien Thomas julthomas@free.fr - 2013-08-25 # Source function library. . /etc/init.d/functions if [ -f /etc/sysconfig/sslh ]; then . /etc/sysconfig/sslh fi PROGNAME=sslh SSLH=${SSLH:-/usr/sbin/sslh-select} SSLH_LANG=${SSLH_LANG:-C} CONFIG=${CONFIG:-/etc/sslh.cfg} PIDFILE=${PIDFILE:-/var/run/sslh/sslh.pid} LOCKFILE=${LOCKFILE:-/var/lock/subsys/sslh} STOP_TIMEOUT=${STOP_TIMEOUT:-10} RETVAL=0 start() { echo -n "Starting $PROGNAME: " LANG=$SSLH_LANG daemon --pidfile="$PIDFILE" \ ${SSLH_USER:+--user="${SSLH_USER}"} \ "$SSLH" ${CONFIG:+-F "$CONFIG"} "$OPTIONS" RETVAL=$? echo [ $RETVAL = 0 ] && touch "$LOCKFILE" return $RETVAL } stop() { echo -n "Stopping $PROGNAME: " killproc -p "$PIDFILE" -d "$STOP_TIMEOUT" "$SSLH" RETVAL=$? echo [ $RETVAL = 0 ] && rm -f "$LOCKFILE" "$PIDFILE" } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status -p "$PIDFILE" "$SSLH" RETVAL=$? ;; restart) stop start ;; *) echo "Usage: $PROGNAME {start|stop|status|restart}" RETVAL=2 ;; esac exit $RETVAL sslh-1.22c/scripts/etc.sysconfig.sslh000066400000000000000000000013121411244446000177070ustar00rootroot00000000000000# # The default processing model uses select # A fork model is also available # #SSLH=/usr/sbin/sslh-select # # If transparent mode is enabled, the following # is needed in order to run as sslh user # #SSLH_USER=sslh #setcap cap_net_bind_service,cap_net_raw=+ep $SSLH # # Configuration file for sslh # Set empty to disable configuration file support # #CONFIG=/etc/sslh.cfg # # Extra option to pass on comand line # Those can supersede configuration file settings # #OPTIONS= # # The sslh process is started by default with the C # locale, it can be changed here # #SSLH_LANG=C # # If an alternate location is specified in configuration # file, it needs to be reported here # #PIDFILE=/var/run/sslh/sslh.pid sslh-1.22c/scripts/fail2ban/000077500000000000000000000000001411244446000157175ustar00rootroot00000000000000sslh-1.22c/scripts/fail2ban/jail.conf000066400000000000000000000003571411244446000175120ustar00rootroot00000000000000# Add the following to your fail2ban jail.conf # In Debian you'd append it to /etc/fail2ban/jail.local [sslh-ssh] enabled = true filter = sslh-ssh action = iptables-multiport[name=sslh,port="443"] logpath = /var/log/messages maxretry = 5 sslh-1.22c/scripts/fail2ban/sslh-ssh.conf000066400000000000000000000007641411244446000203410ustar00rootroot00000000000000# Add the following to you fail2ban configuration file # In Debian it'd go in /etc/fail2ban/filter.d/sslh-ssh.conf # Fail2Ban filter for sslh demultiplexed ssh # # Doesn't (and cannot) detect auth errors, # but many connection attempts from the same # origin is reason enough to block. # # Verion: 2014-03-28 [INCLUDES] # no includes [Definition] failregex = ^.+ sslh\[.+\]: connection from :.+ to .+ forwarded from .+ to .+:ssh\s*$ ignoreregex = # Author: Evert Mouw sslh-1.22c/scripts/systemd.sslh.service000066400000000000000000000011511411244446000202610ustar00rootroot00000000000000[Unit] Description=SSL/SSH multiplexer After=network.target [Service] EnvironmentFile=/etc/conf.d/sslh ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS KillMode=process #Hardening PrivateTmp=true CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE SecureBits=noroot-locked ProtectSystem=strict ProtectHome=true ProtectKernelModules=true ProtectKernelTunables=true ProtectControlGroups=true MountFlags=private NoNewPrivileges=true PrivateDevices=true RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX MemoryDenyWriteExecute=true DynamicUser=true [Install] WantedBy=multi-user.target sslh-1.22c/sslh-conf.c000066400000000000000000002151531411244446000146210ustar00rootroot00000000000000/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) * on Tue Aug 24 13:53:04 2021. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2021 Yves Rutschle # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. */ #define _GNU_SOURCE #include #ifdef LIBCONFIG # include #endif #include #include #include #include "sslh-conf.h" #include "argtable3.h" #ifdef LIBPCRE #define PCRE2_CODE_UNIT_WIDTH 8 #include typedef struct { PCRE2_SIZE rm_so; PCRE2_SIZE rm_eo; } regmatch_t; #else #include #endif /* This gets included in the output .c file */ /* Libconfig 1.4.9 is still used by major distributions * (e.g. CentOS7) and had a different name for * config_setting_lookup */ #if LIBCONFIG_VER_MAJOR == 1 #if LIBCONFIG_VER_MINOR == 4 #if LIBCONFIG_VER_REVISION == 9 #define config_setting_lookup config_lookup_from #endif #endif #endif /* config_type, lookup_fns, type2str are related, keep them together */ typedef enum { CFG_BOOL, CFG_INT, CFG_INT64, CFG_FLOAT, CFG_STRING, CFG_GROUP, CFG_ARRAY, CFG_LIST, } config_type; /* /config_type */ const char* type2str[] = { "boolean", "int", "int64", "float", "string", "group", "array", "list", }; typedef union { int def_bool; int def_int; long long def_int64; double def_float; char* def_string; } any_val; struct config_desc { const char* name; int type; struct config_desc * sub_group; /* Table for compound types (list and group) */ void* arg_cl; /* command-line argument for this setting */ void* base_addr; /* Base of the structure (filled at runtime). Probably not useable for list elements */ size_t offset; /* Offset of setting in the structure */ size_t offset_len; /* Offset of *_len field, for arrays and lists */ size_t offset_present; /* offset of *_is_present field, for optional settings */ size_t size; /* Size of element, or size of group for groups and lists */ int array_type; /* type of array elements, when type == CFG_ARRAY */ int mandatory; int optional; any_val default_val; }; #ifndef LIBCONFIG /* Stubs in case you don't want libconfig */ typedef void config_setting_t; typedef int config_t; #define CONFIG_TRUE 1 #define CONFIG_FALSE 0 #define make_config_setting_lookup(type) \ int config_setting_lookup_##type(const config_setting_t* a, const char* b, void* c) { \ return 0; \ } #define make_config_setting_get(type, ret_type) \ ret_type config_setting_get_##type(const config_setting_t* a) { \ return 0; \ } enum { CONFIG_TYPE_INT, CONFIG_TYPE_BOOL, CONFIG_TYPE_INT64, CONFIG_TYPE_FLOAT, CONFIG_TYPE_STRING }; make_config_setting_lookup(bool); make_config_setting_lookup(int); make_config_setting_lookup(int64); make_config_setting_lookup(float); make_config_setting_lookup(string); make_config_setting_get(bool, int); make_config_setting_get(int, int); make_config_setting_get(int64, long long int); make_config_setting_get(float, double); make_config_setting_get(string, char*); config_setting_t* config_root_setting(config_t* c) { return NULL; } config_setting_t* config_lookup(config_t* c, const char* b) { return NULL; } void config_init(config_t* c) { return; } char* config_setting_name(config_setting_t* c) { return NULL; } int config_setting_is_list(config_setting_t* c) { return 0; } int config_setting_is_array(config_setting_t* c) { return 0; } int config_setting_is_scalar(config_setting_t* c) { return 0; } int config_setting_index(const config_setting_t *setting) { return 0; } config_setting_t* config_setting_lookup(config_setting_t* a, char* b) { return NULL; } int config_setting_remove(config_setting_t* cfg, char* name) { return 0; } int config_setting_type(config_setting_t* s) { return -1; } int config_setting_length(config_setting_t* a) { return 0; } config_setting_t* config_setting_get_elem(config_setting_t* a, int i) { return NULL; } int config_read_file(config_t* a, const char* b) { return CONFIG_TRUE; } int config_error_line(config_t* c) { return 0; } char* config_error_text(config_t* c) { return NULL; } #endif /* This is the same as config_setting_lookup_string() except it allocates a new string which belongs to the caller */ static int myconfig_setting_lookup_stringcpy( const config_setting_t* setting, const char* name, char** value) { const char* str; *value = NULL; if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) { asprintf(value, "%s", str); return CONFIG_TRUE; } else { return CONFIG_FALSE; } } typedef int (*lookup_fn)(const config_setting_t*, const char*, void*); lookup_fn lookup_fns[] = { (lookup_fn)config_setting_lookup_bool, (lookup_fn)config_setting_lookup_int, (lookup_fn)config_setting_lookup_int64, (lookup_fn)config_setting_lookup_float, (lookup_fn)myconfig_setting_lookup_stringcpy, NULL, /* CFG_GROUP */ NULL, /* CFG_ARRAY */ NULL, /* CFG_LIST */ }; /* Copy an any_val to arbitrary memory location */ /* 0: success * <0: error */ static int any_valcpy(config_type type, void* target, any_val val) { switch(type) { case CFG_BOOL: *(int*)target = val.def_bool; break; case CFG_INT: *(int*)target = val.def_int; break; case CFG_INT64: *(long long*)target = val.def_int64; break; case CFG_FLOAT: *(double*)target = val.def_float; break; case CFG_STRING: *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 1; } /* Copy the value of a setting to an arbitrary memory that * must be large enough */ /* 0: success * <0: error */ static int settingcpy(config_type type, void* target, const config_setting_t* setting) { any_val val; char* str; switch(type) { case CFG_BOOL: val.def_bool = config_setting_get_bool(setting); *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = config_setting_get_int(setting); *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = config_setting_get_int64(setting); *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = config_setting_get_float(setting); *(double*)target = val.def_int64; break; case CFG_STRING: asprintf(&str, "%s", config_setting_get_string(setting)); val.def_string = str; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Copy the value of a command line arg to arbitrary memory * that must be large enough for the type */ /* 0: success * <0: error */ static int clcpy(config_type type, void* target, const void* cl_arg) { any_val val; char* str; switch(type) { case CFG_BOOL: val.def_bool = (*(struct arg_lit**)cl_arg)->count; *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = (*(struct arg_int**)cl_arg)->ival[0]; *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = (*(struct arg_int**)cl_arg)->ival[0]; *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = (*(struct arg_dbl**)cl_arg)->dval[0]; *(double*)target = val.def_float; break; case CFG_STRING: asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]); val.def_string = str; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Copy the value of a string argument to arbitary memory * location that must be large enough, converting on the way * (i.e. CFG_INT gets atoi() and so on) */ /* 0: success * <0: error */ static int stringcpy(config_type type, void* target, char* from) { any_val val; switch(type) { case CFG_BOOL: val.def_bool = (*from != '0'); *(int*)target = val.def_bool; break; case CFG_INT: val.def_int = strtol(from, NULL, 10); *(int*)target = val.def_int; break; case CFG_INT64: val.def_int64 = strtoll(from, NULL, 10); *(long long*)target = val.def_int64; break; case CFG_FLOAT: val.def_float = strtod(from, NULL); *(double*)target = val.def_float; break; case CFG_STRING: val.def_string = from; *(char**)target = val.def_string; break; default: fprintf(stderr, "Unknown type specification %d\n", type); return -1; } return 0; } /* Element to describe the target of a compound element * element: which config entry is being changed * match: if >0, index in pmatch to set * if 0, don't match but init with value * value: constant if not matching */ struct compound_cl_target { struct config_desc * element; int match; any_val value; }; /* Element to describe one compound command line argument * An argument is string that gets matched against a regex, * then match-groups get evaluated to each targets[]. * For lists, base_entry points to the config_setting so we * can append to it */ struct compound_cl_arg { const char* regex; struct arg_str** arg_cl; /* arg_str entry for this compound option */ struct config_desc * base_entry; struct compound_cl_target* targets; /* If override_desc is set, it points to the descriptor of the element in the group which will be checked for override. Then, override_matchindex indicates the command-line parameter match used to compare against override_desc to know if this group is overridden. If override_matchindex is 0, we don't match from the command-line but from a constant stored in override_const instead */ struct config_desc * override_desc; int override_matchindex; char* override_const; }; struct arg_file* sslhcfg_conffile; struct arg_int* sslhcfg_verbose; struct arg_lit* sslhcfg_version; struct arg_lit* sslhcfg_foreground; struct arg_lit* sslhcfg_inetd; struct arg_lit* sslhcfg_numeric; struct arg_lit* sslhcfg_transparent; struct arg_int* sslhcfg_timeout; struct arg_str* sslhcfg_user; struct arg_str* sslhcfg_pidfile; struct arg_str* sslhcfg_chroot; struct arg_str* sslhcfg_syslog_facility; struct arg_str* sslhcfg_on_timeout; struct arg_str* sslhcfg_prefix; struct arg_str* sslhcfg_listen; struct arg_str* sslhcfg_ssh; struct arg_str* sslhcfg_tls; struct arg_str* sslhcfg_openvpn; struct arg_str* sslhcfg_tinc; struct arg_str* sslhcfg_xmpp; struct arg_str* sslhcfg_http; struct arg_str* sslhcfg_adb; struct arg_str* sslhcfg_socks5; struct arg_str* sslhcfg_syslog; struct arg_str* sslhcfg_anyprot; struct arg_end* sslhcfg_end; static struct config_desc table_sslhcfg_protocols[] = { { /* name */ "name", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, name), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "host", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, host), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "port", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, port), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "service", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, service), /* offset_len */ 0, /* offset_present */ offsetof(struct sslhcfg_protocols_item, service_is_present), /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 1, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "is_udp", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, is_udp), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "udp_timeout", /* type */ CFG_INT, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, udp_timeout), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_int = 60 }, { /* name */ "fork", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, fork), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "tfo_ok", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, tfo_ok), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "transparent", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, transparent), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "log_level", /* type */ CFG_INT, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, log_level), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_int = 1 }, { /* name */ "keepalive", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, keepalive), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "sni_hostnames", /* type */ CFG_ARRAY, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, sni_hostnames), /* offset_len */ offsetof(struct sslhcfg_protocols_item, sni_hostnames_len), /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ CFG_STRING, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { /* name */ "alpn_protocols", /* type */ CFG_ARRAY, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, alpn_protocols), /* offset_len */ offsetof(struct sslhcfg_protocols_item, alpn_protocols_len), /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ CFG_STRING, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { /* name */ "regex_patterns", /* type */ CFG_ARRAY, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, regex_patterns), /* offset_len */ offsetof(struct sslhcfg_protocols_item, regex_patterns_len), /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ CFG_STRING, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { /* name */ "minlength", /* type */ CFG_INT, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_protocols_item, minlength), /* offset_len */ 0, /* offset_present */ offsetof(struct sslhcfg_protocols_item, minlength_is_present), /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 1, /* default_val*/ .default_val.def_int = 0 }, { 0 } }; static struct config_desc table_sslhcfg_listen[] = { { /* name */ "host", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_listen_item, host), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "port", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_listen_item, port), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "is_udp", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_listen_item, is_udp), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "keepalive", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_listen_item, keepalive), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { 0 } }; static struct config_desc table_sslhcfg[] = { { /* name */ "verbose", /* type */ CFG_INT, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_verbose, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, verbose), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { /* name */ "version", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_version, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, version), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "foreground", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_foreground, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, foreground), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "inetd", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_inetd, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, inetd), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "numeric", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_numeric, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, numeric), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "transparent", /* type */ CFG_BOOL, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_transparent, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, transparent), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_bool = 0 }, { /* name */ "timeout", /* type */ CFG_INT, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_timeout, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, timeout), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(int), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_int = 5 }, { /* name */ "user", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_user, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, user), /* offset_len */ 0, /* offset_present */ offsetof(struct sslhcfg_item, user_is_present), /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 1, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "pidfile", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_pidfile, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, pidfile), /* offset_len */ 0, /* offset_present */ offsetof(struct sslhcfg_item, pidfile_is_present), /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 1, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "chroot", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_chroot, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, chroot), /* offset_len */ 0, /* offset_present */ offsetof(struct sslhcfg_item, chroot_is_present), /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 1, /* default_val*/ .default_val.def_string = NULL }, { /* name */ "syslog_facility", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_syslog_facility, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, syslog_facility), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_string = "auth" }, { /* name */ "on_timeout", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_on_timeout, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, on_timeout), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_string = "ssh" }, { /* name */ "prefix", /* type */ CFG_STRING, /* sub_group*/ NULL, /* arg_cl */ & sslhcfg_prefix, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, prefix), /* offset_len */ 0, /* offset_present */ 0, /* size */ sizeof(char*), /* array_type */ -1, /* mandatory */ 0, /* optional */ 0, /* default_val*/ .default_val.def_string = "" }, { /* name */ "listen", /* type */ CFG_LIST, /* sub_group*/ table_sslhcfg_listen, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, listen), /* offset_len */ offsetof(struct sslhcfg_item, listen_len), /* offset_present */ 0, /* size */ sizeof(struct sslhcfg_listen_item), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { /* name */ "protocols", /* type */ CFG_LIST, /* sub_group*/ table_sslhcfg_protocols, /* arg_cl */ NULL, /* base_addr */ NULL, /* offset */ offsetof(struct sslhcfg_item, protocols), /* offset_len */ offsetof(struct sslhcfg_item, protocols_len), /* offset_present */ 0, /* size */ sizeof(struct sslhcfg_protocols_item), /* array_type */ -1, /* mandatory */ 1, /* optional */ 0, /* default_val*/ .default_val.def_int = 0 }, { 0 } }; static struct compound_cl_target sslhcfg_anyprot_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_syslog_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_socks5_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_adb_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_http_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "http" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_xmpp_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_tinc_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_openvpn_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_tls_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_ssh_targets [] = { { & table_sslhcfg_protocols[0], 0, .value.def_string = "ssh" }, { & table_sslhcfg_protocols[1], 1, .value.def_string = "0" }, { & table_sslhcfg_protocols[2], 2, .value.def_string = "0" }, { & table_sslhcfg_protocols[6], 0, .value.def_bool = 1 }, { & table_sslhcfg_protocols[9], 0, .value.def_int = 1 }, { & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 }, { 0 } }; static struct compound_cl_target sslhcfg_listen_targets [] = { { & table_sslhcfg_listen[0], 1, .value.def_string = "0" }, { & table_sslhcfg_listen[1], 2, .value.def_string = "0" }, { 0 } }; static struct compound_cl_arg compound_cl_args[] = { { /* arg: listen */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_listen, .base_entry = & table_sslhcfg [13], .targets = sslhcfg_listen_targets, .override_desc = NULL, .override_matchindex = 0, .override_const = NULL, }, { /* arg: ssh */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_ssh, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_ssh_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "ssh", }, { /* arg: tls */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_tls, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_tls_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "tls", }, { /* arg: openvpn */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_openvpn, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_openvpn_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "openvpn", }, { /* arg: tinc */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_tinc, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_tinc_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "tinc", }, { /* arg: xmpp */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_xmpp, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_xmpp_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "xmpp", }, { /* arg: http */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_http, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_http_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "http", }, { /* arg: adb */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_adb, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_adb_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "adb", }, { /* arg: socks5 */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_socks5, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_socks5_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "socks5", }, { /* arg: syslog */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_syslog, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_syslog_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "syslog", }, { /* arg: anyprot */ .regex = "(.+):(\\w+)", .arg_cl = & sslhcfg_anyprot, .base_entry = & table_sslhcfg [14], .targets = sslhcfg_anyprot_targets, .override_desc = & table_sslhcfg_protocols [0], .override_matchindex = 0, .override_const = "anyprot", }, { 0 } }; /* Enable debug to follow the parsing of tables */ #if 0 #define TRACE_READ(x) printf x #define TRACE_READ_PRINT_SETTING 1 #else #define TRACE_READ(x) #define TRACE_READ_PRINT_SETTING 0 #endif /* Enable debug to follow the parsing of compound options */ #if 0 #define TRACE_CMPD(x) printf x #define TRACE_CMPD_PRINT_SETTING 1 #else #define TRACE_CMPD(x) #define TRACE_CMPD_PRINT_SETTING 0 #endif static void print_setting(config_type type, void* val) { if (TRACE_READ_PRINT_SETTING || TRACE_CMPD_PRINT_SETTING) { switch(type) { case CFG_BOOL: case CFG_INT: printf("%d", *(int*)val); break; case CFG_INT64: printf("%lld", *(long long*)val); break; case CFG_FLOAT: printf("%f", *(double*)val); break; case CFG_STRING: printf("`%s'", *(char**)val); break; case CFG_GROUP: case CFG_LIST: case CFG_ARRAY: break; } } } /* Changes all dashes to underscores in a string of * vice-versa */ static void strswap_ud(const char target, char* str) { char* c; for (c = str; *c; c++) if (*c == (target == '_' ? '-' : '_')) *c = (target == '_' ? '_' : '-'); } /* Same as config_setting_lookup() but looks up with dash or * underscore so `my_setting` and `my-setting` match the same */ static config_setting_t* config_setting_lookup_ud(config_setting_t* cfg, struct config_desc* desc) { config_setting_t* setting; char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); setting = config_setting_lookup(cfg, name); if (setting) return setting; strswap_ud('-', name); setting = config_setting_lookup(cfg, name); return setting; } static int lookup_typed_ud(config_setting_t* cfg, void* target, struct config_desc *desc) { lookup_fn lookup_fn = lookup_fns[desc->type]; char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); if (lookup_fn(cfg, name, ((char*)target) + desc->offset) == CONFIG_TRUE) return CONFIG_TRUE; strswap_ud('-', name); return lookup_fn(cfg, name, ((char*)target) + desc->offset); } /* Removes a setting, trying both underscores and dashes as * name (so deleting 'my-setting' deletes both 'my_setting' * and 'my-setting') */ static int setting_delete_ud(config_setting_t* cfg, struct config_desc *desc) { char name[strlen(desc->name)+1];; strcpy(name, desc->name); strswap_ud('_', name); if (config_setting_remove(cfg, name) == CONFIG_TRUE) return CONFIG_TRUE; strswap_ud('-', name); return config_setting_remove(cfg, name); } /* When traversing configuration, allocate memory for plural * types, init for scalars */ static void read_block_init(void* target, config_setting_t* cfg, struct config_desc* desc) { size_t len = 0; void* block; config_setting_t* setting; switch (desc->type) { case CFG_LIST: case CFG_ARRAY: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); } block = calloc(len, desc->size); *(size_t*)(((char*)target) + desc->offset_len) = len; *(void**)(((char*)target) + desc->offset) = block; TRACE_READ((" sizing for %zu elems ", len)); break; case CFG_GROUP: block = calloc(1, desc->size); *(void**)(((char*)target) + desc->offset) = block; TRACE_READ((" sizing for %zu elems ", len)); break; default: /* scalar types: copy default */ memcpy(((char*)target) + desc->offset, &desc->default_val, desc->size); TRACE_READ(("setting %s to default ", desc->name)); print_setting(desc->type,(char*)target + desc->offset); break; } } static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg); /* When traversing configuration, set value from config * file, or command line * return: 0 if not set, 1 if set somehow */ static int read_block_setval(void* target, config_setting_t* cfg, struct config_desc* desc, char** errmsg) { int i; size_t len = 0; void* block; int in_cfg = 0, in_cl = 0; /* Present in config file? present on command line? */ config_setting_t* setting = NULL; switch (desc->type) { case CFG_LIST: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); block = *(void**)(((char*)target) + desc->offset); for (i = 0; i < len; i++) { config_setting_t* elem = config_setting_get_elem(setting, i); if (!read_block(elem, (char*)block + desc->size * i, desc->sub_group, errmsg)) return 0; } } break; case CFG_ARRAY: if (cfg) { setting = config_setting_lookup_ud(cfg, desc); if (setting) len = config_setting_length(setting); block = *(void**)(((char*)target) + desc->offset); for (i = 0; i < len; i++) { config_setting_t* elem = config_setting_get_elem(setting, i); settingcpy(desc->array_type, (char*)block + desc->size * i, elem); TRACE_READ(("[%d] = ", i)); print_setting(desc->array_type, (char*)block + desc->size *i); TRACE_READ(("\n")); } setting_delete_ud(cfg, desc); } break; case CFG_GROUP: if (cfg) setting = config_setting_lookup_ud(cfg, desc); block = *(void**)(((char*)target) + desc->offset); if (!read_block(setting, block, desc->sub_group, errmsg)) return 0; break; default: /* scalar types */ TRACE_READ((" `%s'", desc->name)); if (cfg && config_setting_lookup_ud(cfg, desc)) { TRACE_READ((" in config file: ")); /* setting is present in cfg, look it up */ if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) { TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type])); asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n", desc->name, type2str[desc->type]); return 0; } print_setting(desc->type, (((char*)target) + desc->offset)); setting_delete_ud(cfg, desc); in_cfg = 1; } else { TRACE_READ((" not in config file")); } if (desc->arg_cl && (*(struct arg_int**)desc->arg_cl)->count) { clcpy(desc->type, ((char*)target) + desc->offset, desc->arg_cl); TRACE_READ((", command line sets to ")); print_setting(desc->type, (((char*)target) + desc->offset)); in_cl = 1; } else { TRACE_READ((", not in command line")); } if (!(in_cfg || in_cl)) { TRACE_READ(("\n")); return 0; } TRACE_READ(("\n")); break; } return 1; } /* Set *_is_present flag for target */ static void target_is_present(void* target, struct config_desc* desc, int val) { if (desc->optional) { /* _is_present only exists in target for optional settings */ TRACE_READ((" mark as set")); *(int*)((char*)target + desc->offset_present) = val; } } /* traverses the configuration; allocates memory if needed, * set to default if exists, * fill from configuration file if present, overrides or set from * command line if present, verifies mandatory options have * been set * target: base address of the group being processed */ static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg) { int set; for (; desc->name; desc++) { TRACE_READ(("reading %s%s%s: ", desc->optional ? "optional " : "", desc->mandatory ? "mandatory " : "", desc->name)); desc->base_addr = target; read_block_init(target, cfg, desc); set = read_block_setval(target, cfg, desc, errmsg); if (!set && desc->mandatory) { asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name); return 0; } if (desc->optional) target_is_present(target, desc, set && desc->optional); } return 1; } /* Copy regex match into newly allocated string * out: newly allocated string (caller has to free it) * in: string into which the match was made * pmatch: the match to extract */ static void pmatchcpy(char** out, const char* in, regmatch_t* pmatch) { int len = pmatch->rm_eo - pmatch->rm_so; *out = calloc(len+1, 1); memcpy(*out, in + pmatch->rm_so, len); } /* Processes a list of targets within one element, setting * the values in the target setting * target: where to put the data * arg: CL arg containing the target fields * clval: command line parameter * pmatch: regex match array into clval */ static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, const char* clval, regmatch_t* pmatch) { int pmatch_cnt = 1; struct compound_cl_target* target; for (target = arg->targets; target->element; target++) { struct config_desc * element = target->element; if (target->match) { TRACE_CMPD((" match %d rm_so %d rm_eo %d type %d\n", pmatch_cnt, pmatch[pmatch_cnt].rm_so, pmatch[pmatch_cnt].rm_eo, element->type )); if (pmatch[pmatch_cnt].rm_so == -1) { /* This should not happen as regexec() did * match before, unless there is a * discrepency between the regex and the * number of backreferences */ return 0; } char* str; pmatchcpy(&str, clval, &pmatch[pmatch_cnt]); stringcpy(element->type, (char*)target_addr + element->offset, str); TRACE_CMPD(("setting %p+%zu to : ", target_addr , element->offset)); print_setting(element->type , (char*)target_addr + element->offset); TRACE_CMPD(("\n")); /* str is temporary buffer for type conversion, except for strings which we * need to keep around so don't free them */ if (element->type != CFG_STRING) free(str); pmatch_cnt++; } else { /* don't use matching, set constant */ any_valcpy(element->type, (char*)target_addr + element->offset, target->value); } } return 1; } /* Goes over a list, finds if a group matches the specified string and overwrite * it if it does. */ static int override_on_str(struct compound_cl_arg* arg, const char* str, regmatch_t* pmatch) { struct config_desc * desc = arg->base_entry; void* list_base = *(void**)(desc->base_addr + desc->offset); size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len); size_t elem_size = desc->size; int i; for (i = 0; i < list_len; i++) { char* group_base = ((char*)list_base + i * elem_size); char* cfg_member = *(char**)(group_base + arg->override_desc->offset); if (!strcmp(str, cfg_member)) { memset(group_base, 0, elem_size); struct arg_str* arg_cl = *arg->arg_cl; if (!set_target_fields(group_base, arg, arg_cl->sval[0], pmatch)) return 0; return 1; } } return 0; } /* Goes over a list and override group if needed */ static int override_elem(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) { char* str; int allocated = 0; int res; if (arg->override_matchindex) { struct arg_str* arg_cl = *arg->arg_cl; pmatchcpy(&str, arg_cl->sval[arg_index], &pmatch[arg->override_matchindex]); allocated = 1; } else { str = arg->override_const; } res = override_on_str(arg, str, pmatch); if (allocated) free(str); return res; } /* Add an argument to a list, overriding if required or * appending otherwise */ static int add_arg_to_list(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) { struct config_desc * desc = arg->base_entry; void* list_base = *(void**)(desc->base_addr + desc->offset); size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len); size_t elem_size = desc->size; /* are we overriding an existing group? */ if (arg->override_desc) if (override_elem(arg, arg_index, pmatch)) return 1; /* override not found or no override, append element and * zero it out */ list_len++; list_base = realloc(list_base, list_len * elem_size); *(size_t*)(desc->base_addr + desc->offset_len) = list_len; *(void**)(desc->base_addr + desc->offset) = list_base; memset(list_base + (list_len - 1) * elem_size, 0, elem_size); struct arg_str* arg_cl = *arg->arg_cl; if (!set_target_fields((char*)list_base + (list_len - 1) * elem_size, arg, arg_cl->sval[arg_index], pmatch)) { return 0; } return 1; } /* TODO: pass pmatch size as parameter or something */ #define MAX_MATCH 10 #ifndef LIBPCRE static int regcompmatch_posix( regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { char* regerr; struct arg_str* arg_cl = *arg->arg_cl; regex_t preg; int res = regcomp(&preg, arg->regex, REG_EXTENDED); if (res) { int errlen = regerror(res, &preg, NULL, 0); regerr = malloc(errlen); regerror(res, &preg, regerr, errlen); asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr); free(regerr); return 0; } res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0); if (res) { asprintf(errmsg, "--%s %s: Illegal argument", arg_cl->hdr.longopts, arg->regex); return 0; } return 1; } #endif #ifdef LIBPCRE static int regcompmatch_pcre2( regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { int i, error; pcre2_code* pcre; PCRE2_UCHAR8 err_str[120]; /* ample, according to pcre2api(3) */ pcre2_match_data* matches; PCRE2_SIZE error_offset; struct arg_str* arg_cl = *arg->arg_cl; pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL); if (!pcre) { pcre2_get_error_message(error, err_str, sizeof(err_str)); asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n", arg->regex, error, err_str, error_offset); return 0; } matches = pcre2_match_data_create(MAX_MATCH, NULL); int res = pcre2_match(pcre, (PCRE2_SPTR8)arg_cl->sval[arg_index], PCRE2_ZERO_TERMINATED, 0, 0, matches, NULL); if (res < 0) { pcre2_get_error_message(res, err_str, sizeof(err_str)); asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n", arg_cl->sval[arg_index], arg->regex, res, err_str); return 0; } PCRE2_SIZE *ovec = pcre2_get_ovector_pointer(matches); if (res > MAX_MATCH) res = MAX_MATCH; /* From pcre2posix.c */ for (i = 0; i < res; i++) { pmatch[i].rm_so = (ovec[i*2] == PCRE2_UNSET) ? -1 : ovec[i*2]; pmatch[i].rm_eo = (ovec[i*2+1] == PCRE2_UNSET) ? -1 : ovec[i*2+1]; } for (; i < MAX_MATCH; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; pcre2_match_data_free(matches); return 1; } #endif /* Regex fiddling: uses info in arg to fill pmatch * arg: description of the command line argument * arg_index: occurence of this argument on the command line */ static int regcompmatch(regmatch_t* pmatch, struct compound_cl_arg* arg, int arg_index, char** errmsg) { #if LIBPCRE return regcompmatch_pcre2(pmatch, arg, arg_index, errmsg); #else return regcompmatch_posix(pmatch, arg, arg_index, errmsg); #endif } /* Read compound options described in `arg`, from `cfg`, to `setting` */ static int read_compounds(config_setting_t* cfg, void* setting, struct compound_cl_arg* arg, char** errmsg) { int arg_i; struct arg_str* arg_cl; regmatch_t pmatch[MAX_MATCH]; for (; arg->regex; arg++) { arg_cl = *arg->arg_cl; TRACE_CMPD(("Compound %s occurs %d : ", arg_cl->hdr.longopts, arg_cl->count)); for (arg_i = 0; arg_i < arg_cl->count; arg_i++) { if (!regcompmatch(&pmatch[0], arg, arg_i, errmsg)) return 0; TRACE_CMPD(("`%s' matched\n", arg_cl->sval[arg_i])); switch (arg->base_entry->type) { case CFG_LIST: /* In a list, find the end or the element to override */ if (!add_arg_to_list(arg, arg_i, pmatch)) { return 0; } break; /* Semantics for CFG_ARRAY TBD */ case CFG_GROUP: if (!set_target_fields( /* base_addr is the same for all elements in the group */ arg->targets[0].element->base_addr, arg, arg_cl->sval[arg_i], pmatch)) return 0; default: TRACE_CMPD(("error, compound on type %d\n", arg->base_entry->type)); break; } } TRACE_CMPD(("done %s\n", arg_cl->hdr.longopts)); } return 1; } /* read config file `filename` into `c` */ static int c2s_parse_file(const char* filename, config_t* c, char**errmsg) { /* Read config file */ if (config_read_file(c, filename) == CONFIG_FALSE) { if (config_error_line(c) != 0) { asprintf(errmsg, "%s:%d:%s", filename, config_error_line(c), config_error_text(c)); return 0; } asprintf(errmsg, "%s:%s", filename, config_error_text(c)); return 0; } return 1; } /* Allocates a new string that represents the setting value, which must be a scalar */ static void scalar_to_string(char** strp, config_setting_t* s) { switch(config_setting_type(s)) { case CONFIG_TYPE_INT: asprintf(strp, "%d\n", config_setting_get_int(s)); break; case CONFIG_TYPE_BOOL: asprintf(strp, "%s\n", config_setting_get_bool(s) ? "[true]" : "[false]" ); break; case CONFIG_TYPE_INT64: asprintf(strp, "%lld\n", config_setting_get_int64(s)); break; case CONFIG_TYPE_FLOAT: asprintf(strp, "%lf\n", config_setting_get_float(s)); break; case CONFIG_TYPE_STRING: asprintf(strp, "%s\n", config_setting_get_string(s)); break; default: /* This means a bug */ fprintf(stderr, "Unexpected type %d\n", config_setting_type(s)); exit(1); } } /* Typesets all the settings in a configuration as a * newly-allocated string. The string management is caller's * responsability. * Returns the number of scalars in the configuration */ static int cfg_as_string(config_setting_t* parent, const char* path, char** strp) { int i, len, res = 0; config_setting_t* child; char* subpath, *value, *old; const char* name; len = config_setting_length(parent); for (i = 0; i < len; i++) { child = config_setting_get_elem(parent, i); name = config_setting_name(child); if (!name) name = ""; if(config_setting_is_list(parent) || config_setting_is_array(parent)) { asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name); } else { asprintf(&subpath, "%s/%s", path, name); } if (config_setting_is_scalar(child)) { scalar_to_string(&value, child); /* Add value to the output string */ if (*strp) { asprintf(&old, "%s", *strp); free(*strp); } else { asprintf(&old, "%s", ""); } asprintf(strp, "%s%s:%s", old, subpath, value); free(value); free(old); res++; /* At least one scalar was found */ } else { /* It's an aggregate -- descend into it */ res += cfg_as_string(child, subpath, strp); } free(subpath); } return res; } /* 0: success <0: error */ int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg) { int nerrors, res; config_t c; char* errmsg; config_setting_t* s; void* argtable[] = { #ifdef LIBCONFIG sslhcfg_conffile = arg_filen("F", "config", "", 0, 1, "Specify configuration file"), #endif sslhcfg_verbose = arg_intn("v", "verbose", "", 0, 1, ""), sslhcfg_version = arg_litn("V", "version", 0, 1, "Print version information and exit"), sslhcfg_foreground = arg_litn("f", "foreground", 0, 1, "Run in foreground instead of as a daemon"), sslhcfg_inetd = arg_litn("i", "inetd", 0, 1, "Run in inetd mode: use stdin/stdout instead of network listen"), sslhcfg_numeric = arg_litn("n", "numeric", 0, 1, "Print IP addresses and ports as numbers"), sslhcfg_transparent = arg_litn(NULL, "transparent", 0, 1, "Set up as a transparent proxy"), sslhcfg_timeout = arg_intn("t", "timeout", "", 0, 1, "Set up timeout before connecting to default target"), sslhcfg_user = arg_strn("u", "user", "", 0, 1, "Username to change to after set-up"), sslhcfg_pidfile = arg_strn("P", "pidfile", "", 0, 1, "Path to file to store PID of current instance"), sslhcfg_chroot = arg_strn("C", "chroot", "", 0, 1, "Root to change to after set-up"), sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "", 0, 1, "Facility to syslog to"), sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "", 0, 1, "Target to connect to when timing out"), sslhcfg_prefix = arg_strn(NULL, "prefix", "", 0, 1, "Reserved for testing"), sslhcfg_listen = arg_strn("p", "listen", "", 0, 10, "Listen on host:port"), sslhcfg_ssh = arg_strn(NULL, "ssh", "", 0, 10, "Set up ssh target"), sslhcfg_tls = arg_strn(NULL, "tls", "", 0, 10, "Set up TLS/SSL target"), sslhcfg_openvpn = arg_strn(NULL, "openvpn", "", 0, 10, "Set up OpenVPN target"), sslhcfg_tinc = arg_strn(NULL, "tinc", "", 0, 10, "Set up tinc target"), sslhcfg_xmpp = arg_strn(NULL, "xmpp", "", 0, 10, "Set up XMPP target"), sslhcfg_http = arg_strn(NULL, "http", "", 0, 10, "Set up HTTP (plain) target"), sslhcfg_adb = arg_strn(NULL, "adb", "", 0, 10, "Set up ADB (Android Debug) target"), sslhcfg_socks5 = arg_strn(NULL, "socks5", "", 0, 10, "Set up socks5 target"), sslhcfg_syslog = arg_strn(NULL, "syslog", "", 0, 10, "Set up syslog target"), sslhcfg_anyprot = arg_strn(NULL, "anyprot", "", 0, 10, "Set up default target"), sslhcfg_end = arg_end(10) }; /* Parse command line */ nerrors = arg_parse(argc, argv, argtable); if (nerrors) { arg_print_errors(stdout, sslhcfg_end, "sslhcfg"); arg_print_syntax(stdout, argtable, "\n"); arg_print_glossary(stdout, argtable, " %-25s\t%s\n"); return -1; } config_init(&c); if (sslhcfg_conffile && sslhcfg_conffile->count) { if (!c2s_parse_file(sslhcfg_conffile->filename[0], &c, &errmsg)) { fprintf(stderr, "%s\n", errmsg); return -1; } } s = config_root_setting(&c); res = read_block(s, cfg, table_sslhcfg, &errmsg); if (!res) { fprintf(stderr, "%s\n", errmsg); return -1; } res = read_compounds(s, cfg, compound_cl_args, &errmsg); if (!res) { fprintf(stderr, "%s\n", errmsg); return -1; } errmsg = NULL; res = cfg_as_string(s, "", &errmsg); if (res) fprintf(stderr, "Unknown settings:\n%s\n", errmsg); return 0; } static void indent(FILE* out, int depth) { int i; for (i = 0; i < depth; i++) fprintf(out, " "); } static void sslhcfg_protocols_fprint( FILE* out, struct sslhcfg_protocols_item* sslhcfg_protocols, int depth) { int i; indent(out, depth); fprintf(out, "name: %s", sslhcfg_protocols->name); fprintf(out, "\n"); indent(out, depth); fprintf(out, "host: %s", sslhcfg_protocols->host); fprintf(out, "\n"); indent(out, depth); fprintf(out, "port: %s", sslhcfg_protocols->port); fprintf(out, "\n"); indent(out, depth); fprintf(out, "service: %s", sslhcfg_protocols->service); if (! sslhcfg_protocols->service_is_present) fprintf(out, " "); fprintf(out, "\n"); indent(out, depth); fprintf(out, "is_udp: %d", sslhcfg_protocols->is_udp); fprintf(out, "\n"); indent(out, depth); fprintf(out, "udp_timeout: %d", sslhcfg_protocols->udp_timeout); fprintf(out, "\n"); indent(out, depth); fprintf(out, "fork: %d", sslhcfg_protocols->fork); fprintf(out, "\n"); indent(out, depth); fprintf(out, "tfo_ok: %d", sslhcfg_protocols->tfo_ok); fprintf(out, "\n"); indent(out, depth); fprintf(out, "transparent: %d", sslhcfg_protocols->transparent); fprintf(out, "\n"); indent(out, depth); fprintf(out, "log_level: %d", sslhcfg_protocols->log_level); fprintf(out, "\n"); indent(out, depth); fprintf(out, "keepalive: %d", sslhcfg_protocols->keepalive); fprintf(out, "\n"); indent(out, depth); fprintf(out, "sni_hostnames [%zu]:\n", sslhcfg_protocols->sni_hostnames_len); for (i = 0; i < sslhcfg_protocols->sni_hostnames_len; i++) { indent(out, depth+1); fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->sni_hostnames[i]); } indent(out, depth); fprintf(out, "alpn_protocols [%zu]:\n", sslhcfg_protocols->alpn_protocols_len); for (i = 0; i < sslhcfg_protocols->alpn_protocols_len; i++) { indent(out, depth+1); fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->alpn_protocols[i]); } indent(out, depth); fprintf(out, "regex_patterns [%zu]:\n", sslhcfg_protocols->regex_patterns_len); for (i = 0; i < sslhcfg_protocols->regex_patterns_len; i++) { indent(out, depth+1); fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->regex_patterns[i]); } indent(out, depth); fprintf(out, "minlength: %d", sslhcfg_protocols->minlength); if (! sslhcfg_protocols->minlength_is_present) fprintf(out, " "); fprintf(out, "\n"); } static void sslhcfg_listen_fprint( FILE* out, struct sslhcfg_listen_item* sslhcfg_listen, int depth) { indent(out, depth); fprintf(out, "host: %s", sslhcfg_listen->host); fprintf(out, "\n"); indent(out, depth); fprintf(out, "port: %s", sslhcfg_listen->port); fprintf(out, "\n"); indent(out, depth); fprintf(out, "is_udp: %d", sslhcfg_listen->is_udp); fprintf(out, "\n"); indent(out, depth); fprintf(out, "keepalive: %d", sslhcfg_listen->keepalive); fprintf(out, "\n"); } void sslhcfg_fprint( FILE* out, struct sslhcfg_item* sslhcfg, int depth) { int i; indent(out, depth); fprintf(out, "verbose: %d", sslhcfg->verbose); fprintf(out, "\n"); indent(out, depth); fprintf(out, "version: %d", sslhcfg->version); fprintf(out, "\n"); indent(out, depth); fprintf(out, "foreground: %d", sslhcfg->foreground); fprintf(out, "\n"); indent(out, depth); fprintf(out, "inetd: %d", sslhcfg->inetd); fprintf(out, "\n"); indent(out, depth); fprintf(out, "numeric: %d", sslhcfg->numeric); fprintf(out, "\n"); indent(out, depth); fprintf(out, "transparent: %d", sslhcfg->transparent); fprintf(out, "\n"); indent(out, depth); fprintf(out, "timeout: %d", sslhcfg->timeout); fprintf(out, "\n"); indent(out, depth); fprintf(out, "user: %s", sslhcfg->user); if (! sslhcfg->user_is_present) fprintf(out, " "); fprintf(out, "\n"); indent(out, depth); fprintf(out, "pidfile: %s", sslhcfg->pidfile); if (! sslhcfg->pidfile_is_present) fprintf(out, " "); fprintf(out, "\n"); indent(out, depth); fprintf(out, "chroot: %s", sslhcfg->chroot); if (! sslhcfg->chroot_is_present) fprintf(out, " "); fprintf(out, "\n"); indent(out, depth); fprintf(out, "syslog_facility: %s", sslhcfg->syslog_facility); fprintf(out, "\n"); indent(out, depth); fprintf(out, "on_timeout: %s", sslhcfg->on_timeout); fprintf(out, "\n"); indent(out, depth); fprintf(out, "prefix: %s", sslhcfg->prefix); fprintf(out, "\n"); indent(out, depth); fprintf(out, "listen [%zu]:\n", sslhcfg->listen_len); for (i = 0; i < sslhcfg->listen_len; i++) { sslhcfg_listen_fprint(out, &sslhcfg->listen[i], depth+1); } indent(out, depth); fprintf(out, "protocols [%zu]:\n", sslhcfg->protocols_len); for (i = 0; i < sslhcfg->protocols_len; i++) { sslhcfg_protocols_fprint(out, &sslhcfg->protocols[i], depth+1); } } sslh-1.22c/sslh-conf.h000066400000000000000000000057001411244446000146210ustar00rootroot00000000000000/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README) * on Tue Aug 24 13:53:04 2021. # conf2struct: generate libconf parsers that read to structs # Copyright (C) 2018-2021 Yves Rutschle # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. */ #ifndef C2S_SSLHCFG_H #define C2S_SSLHCFG_H #ifdef LIBCONFIG # include #endif #include "probe.h" #include #include #include struct sslhcfg_listen_item { char* host; char* port; int is_udp; int keepalive; }; struct sslhcfg_protocols_item { char* name; char* host; char* port; int service_is_present; char* service; int is_udp; int udp_timeout; int fork; int tfo_ok; int transparent; int log_level; int keepalive; size_t sni_hostnames_len; char** sni_hostnames; size_t alpn_protocols_len; char** alpn_protocols; size_t regex_patterns_len; char** regex_patterns; int minlength_is_present; int minlength; T_PROBE* probe; struct addrinfo* saddr; void* data; }; struct sslhcfg_item { int verbose; int version; int foreground; int inetd; int numeric; int transparent; int timeout; int user_is_present; char* user; int pidfile_is_present; char* pidfile; int chroot_is_present; char* chroot; char* syslog_facility; char* on_timeout; char* prefix; size_t listen_len; struct sslhcfg_listen_item* listen; size_t protocols_len; struct sslhcfg_protocols_item* protocols; }; int sslhcfg_parse_file( const char* filename, struct sslhcfg_item* sslhcfg, const char** errmsg); void sslhcfg_fprint( FILE* out, struct sslhcfg_item *sslhcfg, int depth); int sslhcfg_cl_parse( int argc, char* argv[], struct sslhcfg_item *sslhcfg); #endif sslh-1.22c/sslh-fork.c000066400000000000000000000151431411244446000146320ustar00rootroot00000000000000/* sslh-fork: forking server # Copyright (C) 2007-2021 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #include "common.h" #include "probe.h" #include "sslh-conf.h" #include "udp-listener.h" #ifdef LIBBSD #include #endif const char* server_type = "sslh-fork"; /* shovels data from one fd to the other and vice-versa returns after one socket closed */ int shovel(struct connection *cnx) { fd_set fds; int res, i; int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1; FD_ZERO(&fds); while (1) { FD_SET(cnx->q[0].fd, &fds); FD_SET(cnx->q[1].fd, &fds); res = select( max_fd, &fds, NULL, NULL, NULL ); CHECK_RES_DIE(res, "select"); for (i = 0; i < 2; i++) { if (FD_ISSET(cnx->q[i].fd, &fds)) { res = fd2fd(&cnx->q[1-i], &cnx->q[i]); if (res == FD_CNXCLOSED) { if (cfg.verbose) fprintf(stderr, "%s %s", i ? "client" : "server", "socket closed\n"); return res; } } } } } /* Child process that finds out what to connect to and proxies */ void start_shoveler(int in_socket) { fd_set fds; struct timeval tv; int res = PROBE_AGAIN; int out_socket; struct connection cnx; struct connection_desc desc; init_cnx(&cnx); cnx.q[0].fd = in_socket; FD_ZERO(&fds); FD_SET(in_socket, &fds); memset(&tv, 0, sizeof(tv)); tv.tv_sec = cfg.timeout; while (res == PROBE_AGAIN) { /* POSIX does not guarantee that tv will be updated, but the client can * only postpone the inevitable for so long */ res = select(in_socket + 1, &fds, NULL, NULL, &tv); if (res == -1) perror("select"); if (FD_ISSET(in_socket, &fds)) { /* Received data: figure out what protocol it is */ res = probe_client_protocol(&cnx); } else { /* Timed out: it's necessarily SSH */ cnx.proto = timeout_protocol(); if (cfg.verbose) log_message(LOG_INFO, "timed out, connect to %s\n", cnx.proto->name); break; } } if (cnx.proto->service && check_access_rights(in_socket, cnx.proto->service)) { exit(0); } /* Connect the target socket */ out_socket = connect_addr(&cnx, in_socket, BLOCKING); CHECK_RES_DIE(out_socket, "connect"); set_capabilities(0); cnx.q[1].fd = out_socket; get_connection_desc(&desc, &cnx); log_connection(&desc, &cnx); set_proctitle_shovel(&desc, &cnx); flush_deferred(&cnx.q[1]); shovel(&cnx); close(in_socket); close(out_socket); if (cfg.verbose) fprintf(stderr, "connection closed down\n"); exit(0); } static pid_t *listener_pid; static int listener_pid_number = 0; void stop_listeners(int sig) { int i; for (i = 0; i < listener_pid_number; i++) { kill(listener_pid[i], sig); } } void set_listen_procname(struct listen_endpoint *listen_socket) { #ifdef LIBBSD int res; struct addrinfo addr; struct sockaddr_storage ss; char listen_addr[NI_MAXHOST+1+NI_MAXSERV+1]; addr.ai_addr = (struct sockaddr*)&ss; addr.ai_addrlen = sizeof(ss); res = getsockname(listen_socket->socketfd, addr.ai_addr, &addr.ai_addrlen); if (res != -1) { sprintaddr(listen_addr, sizeof(listen_addr), &addr); setproctitle("listener %s", listen_addr); } #endif } /* TCP listener: connections, fork a child for each new connection * IN: * endpoint: array of listening endpoint objects * num_endpoints: size of endpoint array * active_endpoint: which endpoint is this listener working on * Does not return * */ void tcp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint) { int i, in_socket; while (1) { in_socket = accept(endpoint[active_endpoint].socketfd, 0, 0); if (cfg.verbose) fprintf(stderr, "accepted fd %d\n", in_socket); switch(fork()) { case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno)); break; case 0: /* In child process */ /* Shoveler processes don't need to hog file descriptors */ for (i = 0; i < num_endpoints; i++) close(endpoint[i].socketfd); start_shoveler(in_socket); exit(0); default: /* In parent process */ break; } close(in_socket); } } void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) { int i, res; struct sigaction action; listener_pid_number = num_addr_listen; listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0])); CHECK_ALLOC(listener_pid, "malloc"); /* Start one process for each listening address */ for (i = 0; i < num_addr_listen; i++) { listener_pid[i] = fork(); switch(listener_pid[i]) { /* Log if fork() fails for some reason */ case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno)); break; /* We're in the child, we have work to do */ case 0: set_listen_procname(&listen_sockets[i]); if (listen_sockets[i].type == SOCK_DGRAM) log_message(LOG_ERR, "UDP not (yet?) supported in sslh-fork\n"); else tcp_listener(listen_sockets, num_addr_listen, i); break; /* We're in the parent, we don't need to do anything */ default: break; } } /* Set SIGTERM to "stop_listeners" which further kills all listener * processes. Note this won't kill processes that listeners forked, which * means active connections remain active. */ memset(&action, 0, sizeof(action)); action.sa_handler = stop_listeners; res = sigaction(SIGTERM, &action, NULL); CHECK_RES_DIE(res, "sigaction"); wait(NULL); } /* The actual main is in common.c: it's the same for both version of * the server */ sslh-1.22c/sslh-main.c000066400000000000000000000144411411244446000146150ustar00rootroot00000000000000/* # main: processing of config file, command line options and start the main # loop. # # Copyright (C) 2007-2018 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #define _GNU_SOURCE #ifdef LIBCONFIG #include #endif #ifdef ENABLE_REGEX #define PCRE2_CODE_UNIT_WIDTH 8 #include #endif #ifdef LIBBSD #include #endif #include "common.h" #include "probe.h" /* Constants for options that have no one-character shorthand */ #define OPT_ONTIMEOUT 257 static void printcaps(void) { #ifdef LIBCAP cap_t caps; char* desc; ssize_t len; caps = cap_get_proc(); desc = cap_to_text(caps, &len); fprintf(stderr, "capabilities: %s\n", desc); cap_free(caps); cap_free(desc); #endif } static void printsettings(void) { char buf[NI_MAXHOST]; int i; struct sslhcfg_protocols_item *p; for (i = 0; i < cfg.protocols_len; i++ ) { p = &cfg.protocols[i]; fprintf(stderr, "%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s] [%s]\n", p->name, sprintaddr(buf, sizeof(buf), p->saddr), p->service, p->log_level, p->saddr->ai_family, p->saddr->ai_addr->sa_family, p->keepalive ? "keepalive" : "", p->fork ? "fork" : "", p->transparent ? "transparent" : "" ); } fprintf(stderr, "timeout: %d\non-timeout: %s\n", cfg.timeout, timeout_protocol()->name); } static void setup_regex_probe(struct sslhcfg_protocols_item *p) #ifdef ENABLE_REGEX { int num_patterns, i, error; pcre2_code** pattern_list; PCRE2_SIZE error_offset; PCRE2_UCHAR8 err_str[120]; num_patterns = p->regex_patterns_len; pattern_list = calloc(num_patterns + 1, sizeof(*pattern_list)); CHECK_ALLOC(pattern_list, "calloc"); p->data = (void*)pattern_list; for (i = 0; i < num_patterns; i++) { pattern_list[i] = pcre2_compile((PCRE2_SPTR8)p->regex_patterns[i], PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL); if (!pattern_list[i]) { pcre2_get_error_message(error, err_str, sizeof(err_str)); fprintf(stderr, "compiling pattern /%s/:%d:%s at offset %ld\n", p->regex_patterns[i], error, err_str, error_offset); exit(1); } } } #else { return; } #endif /* For each protocol in the configuration, resolve address and set up protocol * options if required */ static void config_protocols() { int i; for (i = 0; i < cfg.protocols_len; i++) { struct sslhcfg_protocols_item* p = &(cfg.protocols[i]); if (resolve_split_name(&(p->saddr), p->host, p->port)) { fprintf(stderr, "cannot resolve %s:%s\n", p->host, p->port); exit(4); } p->probe = get_probe(p->name); if (!p->probe) { fprintf(stderr, "%s: probe unknown\n", p->name); exit(1); } if (!strcmp(cfg.protocols[i].name, "regex")) { setup_regex_probe(&cfg.protocols[i]); } if (!strcmp(cfg.protocols[i].name, "tls")) { cfg.protocols[i].data = (void*)new_tls_data(); if (cfg.protocols[i].sni_hostnames_len) tls_data_set_list(cfg.protocols[i].data, 0, (const char**) cfg.protocols[i].sni_hostnames, cfg.protocols[i].sni_hostnames_len); if (cfg.protocols[i].alpn_protocols_len) tls_data_set_list(cfg.protocols[i].data, 1, (const char**) cfg.protocols[i].alpn_protocols, cfg.protocols[i].alpn_protocols_len); } } } void config_sanity_check(struct sslhcfg_item* cfg) { if (!cfg->protocols_len) { fprintf(stderr, "At least one target protocol must be specified.\n"); exit(2); } /* If compiling with systemd socket support no need to require listen address */ #ifndef SYSTEMD if (!cfg->listen_len && !cfg->inetd) { fprintf(stderr, "No listening address specified; use at least one -p option\n"); exit(1); } #endif } int main(int argc, char *argv[], char* envp[]) { extern char *optarg; extern int optind; int res, num_addr_listen; struct listen_endpoint *listen_sockets; #ifdef LIBBSD setproctitle_init(argc, argv, envp); #endif memset(&cfg, 0, sizeof(cfg)); res = sslhcfg_cl_parse(argc, argv, &cfg); if (res) exit(6); if (cfg.verbose > 3) sslhcfg_fprint(stderr, &cfg, 0); if (cfg.version) { printf("%s %s\n", server_type, VERSION); exit(0); } config_protocols(); config_sanity_check(&cfg); if (cfg.inetd) { cfg.verbose = 0; start_shoveler(0); exit(0); } if (cfg.verbose) printsettings(); num_addr_listen = start_listen_sockets(&listen_sockets); #ifdef SYSTEMD if (num_addr_listen < 1) { fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n"); exit(1); } #endif if (!cfg.foreground) { if (fork() > 0) exit(0); /* Detach */ /* New session -- become group leader */ if (getuid() == 0) { res = setsid(); CHECK_RES_DIE(res, "setsid: already process leader"); } } setup_signals(); if (cfg.pidfile) write_pid_file(cfg.pidfile); /* Open syslog connection before we drop privs/chroot */ setup_syslog(argv[0]); if (cfg.user || cfg.chroot) drop_privileges(cfg.user, cfg.chroot); if (cfg.verbose) printcaps(); main_loop(listen_sockets, num_addr_listen); return 0; } sslh-1.22c/sslh-select.c000066400000000000000000000447571411244446000151650ustar00rootroot00000000000000/* sslh-select: mono-processus server # Copyright (C) 2007-2021 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ /* Why use select(2) rather than poll(2)? * No real reason except that's how it was written at first. This article: * https://daniel.haxx.se/docs/poll-vs-select.html suggests that over a few * hundred file descriptors, both become very slow, so there is little * incentive to move to poll() to support more than FD_SETSIZE (which is 1024 * on many Linux. To support large numbers of descriptors, either use the fork * version, or we'll have to write a new version based on libev. */ #define __LINUX__ #include #include "common.h" #include "probe.h" #include "udp-listener.h" #include "collection.h" #include "gap.h" static int debug = 0; const char* server_type = "sslh-select"; /* Global state for a select() loop */ struct select_info { int max_fd; /* Highest fd number to pass to select() */ int num_probing; /* Number of connections currently probing * We use this to know if we need to time out of * select() */ gap_array* probing_list; /* Pointers to cnx that are in probing mode */ fd_set fds_r, fds_w; /* reference fd sets (used to init working copies) */ cnx_collection* collection; /* Collection of connections linked to this loop */ time_t next_timeout; /* time at which next UDP connection times out */ }; static int tidy_connection(struct connection *cnx, struct select_info* fd_info) { int i; fd_set* fds = &fd_info->fds_r; fd_set* fds2 = &fd_info->fds_w; for (i = 0; i < 2; i++) { if (cnx->q[i].fd != -1) { if (cfg.verbose) fprintf(stderr, "closing fd %d\n", cnx->q[i].fd); FD_CLR(cnx->q[i].fd, fds); FD_CLR(cnx->q[i].fd, fds2); close(cnx->q[i].fd); if (cnx->q[i].deferred_data) free(cnx->q[i].deferred_data); } } collection_remove_cnx(fd_info->collection, cnx); return 0; } /* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET * and FD_CLR. Need to drop connections if we go above that limit */ static int fd_is_in_range(int fd) { if (fd >= FD_SETSIZE) { log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n"); return 0; } return 1; } /* Accepts a connection from the main socket and assigns it to an empty slot. * If no slots are available, allocate another few. If that fails, drop the * connexion */ static struct connection* accept_new_connection(int listen_socket, struct cnx_collection *collection) { int in_socket, res; if (cfg.verbose) fprintf(stderr, "accepting from %d\n", listen_socket); in_socket = accept(listen_socket, 0, 0); CHECK_RES_RETURN(in_socket, "accept", NULL); if (!fd_is_in_range(in_socket)) { close(in_socket); return NULL; } res = set_nonblock(in_socket); if (res == -1) { close(in_socket); return NULL; } struct connection* cnx = collection_alloc_cnx_from_fd(collection, in_socket); if (!cnx) { close(in_socket); return NULL; } return cnx; } /* Connect queue 1 of connection to SSL; returns new file descriptor */ static int connect_queue(struct connection* cnx, struct select_info* fd_info) { struct queue *q = &cnx->q[1]; q->fd = connect_addr(cnx, cnx->q[0].fd, NON_BLOCKING); if ((q->fd != -1) && fd_is_in_range(q->fd)) { log_connection(NULL, cnx); flush_deferred(q); if (q->deferred_data) { FD_SET(q->fd, &fd_info->fds_w); FD_CLR(cnx->q[0].fd, &fd_info->fds_r); } FD_SET(q->fd, &fd_info->fds_r); collection_add_fd(fd_info->collection, cnx, q->fd); return q->fd; } else { tidy_connection(cnx, fd_info); return -1; } } /* shovels data from active fd to the other returns after one socket closed or operation would block */ static void shovel(struct connection *cnx, int active_fd, struct select_info* fd_info) { struct queue *read_q, *write_q; read_q = &cnx->q[active_fd]; write_q = &cnx->q[1-active_fd]; if (cfg.verbose) fprintf(stderr, "activity on fd%d\n", read_q->fd); switch(fd2fd(write_q, read_q)) { case -1: case FD_CNXCLOSED: tidy_connection(cnx, fd_info); break; case FD_STALLED: FD_SET(write_q->fd, &fd_info->fds_w); FD_CLR(read_q->fd, &fd_info->fds_r); break; default: /* Nothing */ break; } } /* shovels data from one fd to the other and vice-versa returns after one socket closed */ static void shovel_single(struct connection *cnx) { fd_set fds_r, fds_w; int res, i; int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1; FD_ZERO(&fds_r); FD_ZERO(&fds_w); while (1) { for (i = 0; i < 2; i++) { if (cnx->q[i].deferred_data_size) { FD_SET(cnx->q[i].fd, &fds_w); FD_CLR(cnx->q[1-i].fd, &fds_r); } else { FD_CLR(cnx->q[i].fd, &fds_w); FD_SET(cnx->q[1-i].fd, &fds_r); } } res = select( max_fd, &fds_r, &fds_w, NULL, NULL ); CHECK_RES_DIE(res, "select"); for (i = 0; i < 2; i++) { if (FD_ISSET(cnx->q[i].fd, &fds_w)) { res = flush_deferred(&cnx->q[i]); if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) { if (cfg.verbose) fprintf(stderr, "%s socket closed\n", i ? "server" : "client"); return; } } if (FD_ISSET(cnx->q[i].fd, &fds_r)) { res = fd2fd(&cnx->q[1-i], &cnx->q[i]); if (!res) { if (cfg.verbose) fprintf(stderr, "socket closed\n"); return; } } } } } /* Child process that makes internal connection and proxies */ static void connect_proxy(struct connection *cnx) { int in_socket; int out_socket; /* Minimize the file descriptor value to help select() */ in_socket = dup(cnx->q[0].fd); if (in_socket == -1) { in_socket = cnx->q[0].fd; } else { close(cnx->q[0].fd); cnx->q[0].fd = in_socket; } /* Connect the target socket */ out_socket = connect_addr(cnx, in_socket, BLOCKING); CHECK_RES_DIE(out_socket, "connect"); cnx->q[1].fd = out_socket; log_connection(NULL, cnx); shovel_single(cnx); close(in_socket); close(out_socket); if (cfg.verbose) fprintf(stderr, "connection closed down\n"); exit(0); } /* Removes cnx from probing list */ static void remove_probing_cnx(struct select_info* fd_info, struct connection* cnx) { gap_remove_ptr(fd_info->probing_list, cnx, fd_info->num_probing); fd_info->num_probing--; } static void add_probing_cnx(struct select_info* fd_info, struct connection* cnx) { gap_set(fd_info->probing_list, fd_info->num_probing, cnx); fd_info->num_probing++; } /* Process read activity on a socket in probe state * IN/OUT cnx: connection data, updated if connected * IN/OUT info: updated if connected * */ static void probing_read_process(struct connection* cnx, struct select_info* fd_info) { int res; /* If timed out it's SSH, otherwise the client sent * data so probe the protocol */ if ((cnx->probe_timeout < time(NULL))) { cnx->proto = timeout_protocol(); if (cfg.verbose) log_message(LOG_INFO, "timed out, connect to %s\n", cnx->proto->name); } else { res = probe_client_protocol(cnx); if (res == PROBE_AGAIN) return; } remove_probing_cnx(fd_info, cnx); cnx->state = ST_SHOVELING; /* libwrap check if required for this protocol */ if (cnx->proto->service && check_access_rights(cnx->q[0].fd, cnx->proto->service)) { tidy_connection(cnx, fd_info); res = -1; } else if (cnx->proto->fork) { switch (fork()) { case 0: /* child */ /* TODO: close all file descriptors except 2 */ /* free(cnx); */ connect_proxy(cnx); exit(0); case -1: log_message(LOG_ERR, "fork failed: err %d: %s\n", errno, strerror(errno)); break; default: /* parent */ break; } tidy_connection(cnx, fd_info); res = -1; } else { res = connect_queue(cnx, fd_info); } if (res >= fd_info->max_fd) fd_info->max_fd = res + 1;; } /* Returns the queue index that contains the specified file descriptor */ int active_queue(struct connection* cnx, int fd) { if (cnx->q[0].fd == fd) return 0; if (cnx->q[1].fd == fd) return 1; log_message(LOG_ERR, "file descriptor %d not found in connection object\n", fd); return -1; } /* Process a connection that is active in read */ static void tcp_read_process(struct select_info* fd_info, int fd) { if (debug) fprintf(stderr, "cnx_read_process fd %d\n", fd); cnx_collection* collection = fd_info->collection; struct connection* cnx = collection_get_cnx_from_fd(collection, fd); /* Determine active queue (0 or 1): if fd is that of q[1], active_q = 1, * otherwise it's 0 */ int active_q = active_queue(cnx, fd); switch (cnx->state) { case ST_PROBING: if (active_q == 1) { fprintf(stderr, "Activity on fd2 while probing, impossible\n"); dump_connection(cnx); exit(1); } probing_read_process(cnx, fd_info); break; case ST_SHOVELING: shovel(cnx, active_q, fd_info); break; default: /* illegal */ log_message(LOG_ERR, "Illegal connection state %d\n", cnx->state); dump_connection(cnx); exit(1); } } static void cnx_read_process(struct select_info* fd_info, int fd) { cnx_collection* collection = fd_info->collection; struct connection* cnx = collection_get_cnx_from_fd(collection, fd); switch (cnx->type) { case SOCK_STREAM: tcp_read_process(fd_info, fd); break; case SOCK_DGRAM: udp_s2c_forward(cnx); break; default: log_message(LOG_ERR, "cnx_read_process: Illegal connection type %d\n", cnx->type); dump_connection(cnx); exit(1); } } /* Process a connection that is active in write */ static void cnx_write_process(struct select_info* fd_info, int fd) { if (debug) fprintf(stderr, "cnx_write_process fd %d\n", fd); struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, fd); int res; int queue = active_queue(cnx, fd); res = flush_deferred(&cnx->q[queue]); if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) { if (cnx->state == ST_PROBING) remove_probing_cnx(fd_info, cnx); tidy_connection(cnx, fd_info); } else { /* If no deferred data is left, stop monitoring the fd * for write, and restart monitoring the other one for reads*/ if (!cnx->q[queue].deferred_data_size) { FD_CLR(cnx->q[queue].fd, &fd_info->fds_w); FD_SET(cnx->q[1-queue].fd, &fd_info->fds_r); } } } /* Process a connection that accepts a socket * (For UDP, this means all traffic coming from remote clients) * */ void cnx_accept_process(struct select_info* fd_info, struct listen_endpoint* listen_socket) { int fd = listen_socket->socketfd; int type = listen_socket->type; struct connection* cnx; int new_fd; if (debug) fprintf(stderr, "cnx_accept_process fd %d\n", fd); switch (type) { case SOCK_STREAM: cnx = accept_new_connection(fd, fd_info->collection); if (cnx) { add_probing_cnx(fd_info, cnx); new_fd = cnx->q[0].fd; } break; case SOCK_DGRAM: new_fd = udp_c2s_forward(fd, fd_info->collection, fd_info->max_fd); fprintf(stderr, "new_fd %d\n", new_fd); if (new_fd == -1) return; break; default: log_message(LOG_ERR, "Inconsistent cnx type: %d\n", type); exit(1); return; } FD_SET(new_fd, &fd_info->fds_r); if (new_fd >= fd_info->max_fd) fd_info->max_fd = new_fd + 1; } /* Check all connections to see if a UDP connections has timed out, then free * it. At the same time, keep track of the closest, next timeout. Only do the * search through connections if that timeout actually happened. If the * connection that would have timed out has had activity, it doesn't matter: we * go through connections to find the next timeout, which was needed anyway. */ static void udp_timeouts(struct select_info* fd_info) { time_t now = time(NULL); if (now < fd_info->next_timeout) return; time_t next_timeout = INT_MAX; for (int i = 0; i < fd_info->max_fd; i++) { /* if it's either in read or write set, there is a connection * behind that file descriptor */ if (FD_ISSET(i, &fd_info->fds_r) || FD_ISSET(i, &fd_info->fds_w)) { struct connection* cnx = collection_get_cnx_from_fd(fd_info->collection, i); if (cnx) { time_t timeout = udp_timeout(cnx); if (!timeout) continue; /* Not a UDP connection */ if (cnx && (timeout <= now)) { if (cfg.verbose > 3) fprintf(stderr, "timed out UDP %d\n", cnx->target_sock); close(cnx->target_sock); FD_CLR(i, &fd_info->fds_r); FD_CLR(i, &fd_info->fds_w); collection_remove_cnx(fd_info->collection, cnx); } else { if (timeout < next_timeout) next_timeout = timeout; } } } } if (next_timeout != INT_MAX) fd_info->next_timeout = next_timeout; } /* Main loop: the idea is as follow: * - fds_r and fds_w contain the file descriptors to monitor in read and write * - When a file descriptor goes off, process it: read from it, write the data * to its corresponding pair. * - When a file descriptor blocks when writing, remove the read fd from fds_r, * move the data to a deferred buffer, and add the write fd to fds_w. Deferred * buffer is allocated dynamically. * - When we can write to a file descriptor that has deferred data, we try to * write as much as we can. Once all data is written, remove the fd from fds_w * and add its corresponding pair to fds_r, free the buffer. * * That way, each pair of file descriptor (read from one, write to the other) * is monitored either for read or for write, but never for both. */ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) { struct select_info fd_info = {0}; fd_set readfds, writefds; /* working read and write fd sets */ struct timeval tv; int i, res; fd_info.num_probing = 0; FD_ZERO(&fd_info.fds_r); FD_ZERO(&fd_info.fds_w); fd_info.probing_list = gap_init(0); for (i = 0; i < num_addr_listen; i++) { FD_SET(listen_sockets[i].socketfd, &fd_info.fds_r); set_nonblock(listen_sockets[i].socketfd); } fd_info.max_fd = listen_sockets[num_addr_listen-1].socketfd + 1; fd_info.collection = collection_init(fd_info.max_fd); while (1) { memset(&tv, 0, sizeof(tv)); tv.tv_sec = cfg.timeout; memcpy(&readfds, &fd_info.fds_r, sizeof(readfds)); memcpy(&writefds, &fd_info.fds_w, sizeof(writefds)); if (cfg.verbose) fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", fd_info.max_fd, fd_info.num_probing); res = select(fd_info.max_fd, &readfds, &writefds, NULL, fd_info.num_probing ? &tv : NULL); if (res < 0) perror("select"); /* UDP timeouts: clear out connections after some idle time */ udp_timeouts(&fd_info); /* Check main socket for new connections */ for (i = 0; i < num_addr_listen; i++) { if (FD_ISSET(listen_sockets[i].socketfd, &readfds)) { cnx_accept_process(&fd_info, &listen_sockets[i]); /* don't also process it as a read socket */ FD_CLR(listen_sockets[i].socketfd, &readfds); } } /* Check all sockets for write activity */ for (i = 0; i < fd_info.max_fd; i++) { if (FD_ISSET(i, &writefds)) { cnx_write_process(&fd_info, i); } } /* Check sockets in probing state for timeouts */ for (i = 0; i < fd_info.num_probing; i++) { struct connection* cnx = gap_get(fd_info.probing_list, i); if (!cnx || cnx->state != ST_PROBING) { log_message(LOG_ERR, "Inconsistent probing: cnx=%0xp\n", cnx); if (cnx) log_message(LOG_ERR, "Inconsistent probing: state=%d\n", cnx); exit(1); } if (cnx->probe_timeout < time(NULL)) { if (cfg.verbose) fprintf(stderr, "timeout slot %d\n", i); probing_read_process(cnx, &fd_info); } } /* Check all sockets for read activity */ for (i = 0; i < fd_info.max_fd; i++) { /* Check if it's active AND currently monitored (if a connection * died, it gets tidied, which closes both sockets, but readfs does * not know about that */ if (FD_ISSET(i, &readfds) && FD_ISSET(i, &fd_info.fds_r)) { cnx_read_process(&fd_info, i); } } } } void start_shoveler(int listen_socket) { fprintf(stderr, "inetd mode is not supported in select mode\n"); exit(1); } /* The actual main is in common.c: it's the same for both version of * the server */ sslh-1.22c/sslh.pod000066400000000000000000000170721411244446000142360ustar00rootroot00000000000000# I'm just not gonna write troff :-) =head1 NAME sslh - protocol demultiplexer =head1 SYNOPSIS sslh [B<-F>I] [B<-t> I] [B<--transparent>] [B<-p> I [B<-p> I ...] [B<--tls> I] [B<--ssh> I] [B<--openvpn> I] [B<--http> I] [B<--xmpp> I] [B<--tinc> I] [B<--anyprot> I] [B<--on-timeout> I] [B<-u> I] [B<-C> I] [B<-P> I] [-v] [-i] [-V] [-f] [-n] =head1 DESCRIPTION B accepts connections on specified ports, and forwards them further based on tests performed on the first data packet sent by the remote client. Probes for HTTP, SSL, SSH, OpenVPN, tinc, XMPP are implemented, and any other protocol that can be tested using a regular expression, can be recognised. A typical use case is to allow serving several services on port 443 (e.g. to connect to ssh from inside a corporate firewall, which almost never block port 443) while still serving HTTPS on that port. Hence B acts as a protocol demultiplexer, or a switchboard. Its name comes from its original function to serve SSH and HTTPS on the same port. =head2 Libwrap support One drawback of B is that the servers do not see the original IP address of the client anymore, as the connection is forwarded through B. For this reason, B can be compiled with B to check accesses defined in F and F. Libwrap services can be defined using the configuration file. =head2 Configuration file A configuration file can be supplied to B. Command line arguments override file settings. B uses B to parse the configuration file, so the general file format is indicated in L. Please refer to the example configuration file provided with B for the specific format (Options have the same names as on the command line, except for the list of listen ports and the list of protocols). The configuration file makes it possible to specify protocols using regular expressions: a list of regular expressions is given as the I parameter, and if the first packet received from the client matches any of these expressions, B connects to that protocol. =head2 Probing protocols When receiving an incoming connection, B will read the first bytes sent by the connecting client. It will then probe for the protocol in the order specified on the command line (or the configuration file). Therefore B<--anyprot> should alway be used last, as it always succeeds and further protocols will never be tried. If no data is sent by the client, B will eventually time out and connect to the protocol specified with B<--on-timeout>, or I if none is specified. =head2 Logging As a security/authorization program, B logs to the LOG_AUTH facility, with priority LOG_INFO for normal connections and LOG_ERR for failures. =head1 OPTIONS =over 4 =item B<-F>I, B<--config> I Uses I as configuration file. If other command-line options are specified, they will override the configuration file's settings. =item B<-t> I, B<--timeout> I Timeout before forwarding the connection to the timeout protocol (which should usually be SSH). Default is 2s. =item B<--on-timeout> I Name of the protocol to connect to after the timeout period is over. Default is to forward to the first specified protocol. It usually makes sense to specify 'ssh' as the timeout protocol, as the SSH specification does not tell who is supposed to speak first and a large number of SSH clients wait for the server to send its banner. =item B<--transparent> Makes B behave as a transparent proxy, i.e. the receiving service sees the original client's IP address. This works on Linux only and involves B settings. Refer to the README for more information. =item B<-p> I, B<--listen> I Interface and port on which to listen, e.g. I, where I is the name of an interface (typically the IP address on which the Internet connection ends up). This can be specified several times to bind B to several addresses. =item B<--tls> I Interface and port on which to forward SSL connection, typically I. Note that you can set B to listen on I and B to listen on I: this allows clients inside your network to just connect directly to B. Also, B probes for SSLv3 (or TLSv1) handshake and will reject connections from clients requesting SSLv2. This is compliant with RFC6176 which prohibits the usage of SSLv2. If you wish to accept SSLv2, use B<--anyprot> instead. =item B<--ssh> I Interface and port on which to forward SSH connections, typically I. =item B<--openvpn> I Interface and port on which to forward OpenVPN connections, typically I. =item B<--xmpp> I Interface and port on which to forward XMPP connections, typically I. =item B<--http> I Interface and port on which to forward HTTP connections, typically I. =item B<--tinc> I Interface and port on which to forward tinc connections, typically I. This is experimental. If you use this feature, please report the results (even if it works!) =item B<--anyprot> I Interface and port on which to forward if no other protocol has been found. Because B tries protocols in the order specified on the command line, this should be specified last. If no default is specified, B will forward unknown protocols to the first protocol specified. =item B<-v>, B<--verbose> Increase verboseness. =item B<-n>, B<--numeric> Do not attempt to resolve hostnames: logs will contain IP addresses. This is mostly useful if the system's DNS is slow and running the I variant, as DNS requests will hang all connections. =item B<-V> Prints B version. =item B<-u> I, B<--user> I Requires to run under the specified username. =item B<-C> I, B<--chroot> I Requires to run under the specified chroot. =item B<-P> I, B<--pidfile> I Specifies a file in which to write the PID of the main server. =item B<-i>, B<--inetd> Runs as an I server. Options B<-P> (PID file), B<-p> (listen address), B<-u> (user) are ignored. =item B<-f>, B<--foreground> Runs in foreground. The server will not fork and will remain connected to the terminal. Messages normally sent to B will also be sent to I. =item B<--background> Runs in background. This overrides B if set in the configuration file (or on the command line, but there is no point setting both on the command line unless you have a personality disorder). =back =head1 FILES =over 4 =item F Start-up script. The standard actions B, B and B are supported. =item F Server configuration. These are environment variables loaded by the start-up script and passed to B as command-line arguments. Refer to the OPTIONS section for a detailed explanation of the variables used by B. =back =head1 SEE ALSO The latest version is available from L, and can be tracked from L. =head1 AUTHOR Written by Yves Rutschle. sslh-1.22c/sslhconf.cfg000066400000000000000000000230561411244446000150600ustar00rootroot00000000000000# This is a configuration file for the configuration # generator: it is used by # conf2struct(http://www.rutschle.net/tech/conf2struct/README.html) # to generate the config file and command line parser in # sslh-conf.c header: "sslh-conf.h"; parser: "sslh-conf.c"; printer: true; conffile_option: ("F", "config"); # List of includes to define runtime types # (bug in libconfig? if swallows the brackets if they start # the string) includes: ( "probe.h", " ", " ", " " ); config: { name : "sslhcfg", type: "list", items: ( { name: "verbose"; type: "int"; default: 0; short: "v"; }, { name: "version"; type: "bool"; default: false; short: "V"; description: "Print version information and exit"; }, { name: "foreground"; type: "bool"; default: false; short: "f"; description: "Run in foreground instead of as a daemon"; }, { name: "inetd"; type: "bool"; default: false; short: "i"; description: "Run in inetd mode: use stdin/stdout instead of network listen"; }, { name: "numeric"; type: "bool"; default: false; short: "n"; description: "Print IP addresses and ports as numbers"; }, { name: "transparent"; type: "bool"; default: false; description: "Set up as a transparent proxy"; }, { name: "timeout"; type: "int"; default: 5; short: "t"; description: "Set up timeout before connecting to default target"; }, { name: "user"; type: "string"; optional: true; short: "u"; description: "Username to change to after set-up"; }, { name: "pidfile"; type: "string"; optional: true; short: "P"; argdesc: ""; description: "Path to file to store PID of current instance"; }, { name: "chroot"; type: "string"; optional: true; short: "C"; argdesc: ""; description: "Root to change to after set-up"; }, { name: "syslog_facility"; type: "string"; default: "auth"; description: "Facility to syslog to"; }, { name: "on-timeout"; type: "string"; default: "ssh"; description: "Target to connect to when timing out"; }, { name: "prefix"; type: "string"; default: ""; description: "Reserved for testing" }, # For echosrv only, not sslh { name: "listen", no_cl_accessors: true; # disable generation of individual cl options for each group element (we create a specific --listen option further below) type: "list", items: ( { name: "host"; type: "string"; var: true; }, { name: "port"; type: "string"; var: true; }, { name: "is_udp"; type: "bool"; default: false }, { name: "keepalive"; type: "bool"; default: false; } ) }, { name: "protocols", no_cl_accessors: true; type: "list", items: ( { name: "name"; type: "string"; }, { name: "host"; type: "string"; var: true; }, { name: "port"; type: "string"; var: true; }, { name: "service"; type: "string"; optional: true; }, { name: "is_udp"; type: "bool"; default: false }, { name: "udp_timeout"; type: "int"; default: 60 }, { name: "fork"; type: "bool"; default: false }, { name: "tfo_ok"; type: "bool"; default: false; description: "Set to true if this protocol supports TCP FAST OPEN" }, { name: "transparent"; type: "bool"; default: false; description: "Set to proxy this protocol transparently" }, { name: "log_level"; type: "int"; default: 1 }, { name: "keepalive"; type: "bool"; default: false }, { name: "sni_hostnames", type: "array", element_type: "string" }, { name: "alpn_protocols", type: "array", element_type: "string" }, { name: "regex_patterns", type: "array", element_type: "string" }, { name: "minlength"; type: "int"; optional: true }, # Runtime data { name: "probe"; type: "runtime"; c_type: "T_PROBE*" }, { name: "saddr"; type: "runtime"; c_type: "struct addrinfo*" }, { name: "data"; type: "runtime"; c_type: "void*" } ) } ) } # Command line for list settings: additional options that # can set up several settings at once. Each option will # create a new group setting entry if required (with # defaults set up) # This only works with string targets # This may not be the right abstraction at all and way too # sslh-centric cl_groups: ( { name: "listen"; pattern: "(.+):(\w+)"; description: "Listen on host:port"; short: "p"; argdesc: ""; list: "listen"; # no override, this just adds to the list (and thus can be specified several times) targets: ( { path: "host"; value: "$1" }, { path: "port"; value: "$2" } ); }, { name: "ssh"; pattern: "(.+):(\w+)"; description: "Set up ssh target"; list: "protocols"; # List name that we're defining with this command line option override: "name"; # Field in the group to override. If not found in list, add an item # (it's mandatory to have that field as one of the targets # below) argdesc: ""; targets: ( { path: "name"; value: "ssh" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "fork"; value: 1 }, { path: "log_level"; value: 1 }, { path: "tfo_ok"; value: 1 } ); }, { name: "tls"; pattern: "(.+):(\w+)"; description: "Set up TLS/SSL target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "tls" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 }, { path: "tfo_ok"; value: 1 } ); }, { name: "openvpn"; pattern: "(.+):(\w+)"; description: "Set up OpenVPN target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "openvpn" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 }, { path: "tfo_ok"; value: 1 } ); }, { name: "tinc"; pattern: "(.+):(\w+)"; description: "Set up tinc target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "tinc" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 }, { path: "tfo_ok"; value: 1 } ); }, { name: "xmpp"; pattern: "(.+):(\w+)"; description: "Set up XMPP target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "xmpp" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 } ); }, { name: "http"; pattern: "(.+):(\w+)"; description: "Set up HTTP (plain) target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "http" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 } ); }, { name: "adb"; pattern: "(.+):(\w+)"; description: "Set up ADB (Android Debug) target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "adb" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 } ); }, { name: "socks5"; pattern: "(.+):(\w+)"; description: "Set up socks5 target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "socks5" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 } ); }, { name: "syslog"; pattern: "(.+):(\w+)"; description: "Set up syslog target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "syslog" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 } ); }, { name: "anyprot"; pattern: "(.+):(\w+)"; description: "Set up default target"; list: "protocols"; override: "name"; argdesc: ""; targets: ( { path: "name"; value: "anyprot" }, { path: "host"; value: "$1" }, { path: "port"; value: "$2" }, { path: "log_level"; value: 1 } ); } ) sslh-1.22c/systemd-sslh-generator.c000066400000000000000000000105701411244446000173440ustar00rootroot00000000000000#include #include #include #include #include "common.h" static char* resolve_listen(const char *hostname, const char *port) { /* Need room in the strcat for \0 and : * the format in the socket unit file is hostname:port */ char *conn = malloc(strlen(hostname)+strlen(port)+2); CHECK_ALLOC(conn, "malloc"); strcpy(conn, hostname); strcat(conn, ":"); strcat(conn, port); return conn; } static int get_listen_from_conf(const char *filename, char **listen[]) { config_t config; config_setting_t *setting, *addr; const char *hostname, *port; int len = 0; /* look up the listen stanzas in the config file so these * can be used in the socket file generated */ config_init(&config); if (config_read_file(&config, filename) == CONFIG_FALSE) { /* we don't care if file is missing, skip it */ if (config_error_line(&config) != 0) { fprintf(stderr, "%s:%d:%s\n", filename, config_error_line(&config), config_error_text(&config)); return -1; } } else { setting = config_lookup(&config, "listen"); if (setting) { int i; len = config_setting_length(setting); *listen = malloc(len * sizeof(**listen)); CHECK_ALLOC(*listen, "malloc"); for (i = 0; i < len; i++) { addr = config_setting_get_elem(setting, i); if (! (config_setting_lookup_string(addr, "host", &hostname) && config_setting_lookup_string(addr, "port", &port))) { fprintf(stderr, "line %d:Incomplete specification (hostname and port required)\n", config_setting_source_line(addr)); return -1; } else { (*listen)[i] = malloc(strlen(resolve_listen(hostname, port))); CHECK_ALLOC((*listen)[i], "malloc"); strcpy((*listen)[i], resolve_listen(hostname, port)); } } } } return len; } static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) { int i; fprintf(socket, "# Automatically generated by systemd-sslh-generator\n\n" "[Unit]\n" "Before=sslh.service\n" "SourcePath=%s\n" "Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n" "[Socket]\n" "FreeBind=true\n", source); for (i = 0; i < num_addr; i++) { fprintf(socket, "ListenStream=%s\n", listen[i]); } return 0; } static int gen_sslh_config(char *runtime_unit_dir) { char *sslh_conf; int num_addr; FILE *config; char **listen; FILE *runtime_conf_fd = stdout; const char *unit_file; /* There are two default locations so check both with first given preference */ sslh_conf = "/etc/sslh.cfg"; config = fopen(sslh_conf, "r"); if (config == NULL) { sslh_conf="/etc/sslh/sslh.cfg"; config = fopen(sslh_conf, "r"); if (config == NULL) { return -1; } } fclose(config); num_addr = get_listen_from_conf(sslh_conf, &listen); if (num_addr < 0) return -1; /* If this is run by systemd directly write to the location told to * otherwise write to standard out so that it's trivial to check what * will be written */ if (runtime_unit_dir && *runtime_unit_dir) { unit_file = "/sslh.socket"; size_t uf_len = strlen(unit_file); size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1; char *runtime_conf = malloc(runtime_len); CHECK_ALLOC(runtime_conf, "malloc"); strcpy(runtime_conf, runtime_unit_dir); strcat(runtime_conf, unit_file); runtime_conf_fd = fopen(runtime_conf, "w"); } return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf); } int main(int argc, char *argv[]){ int r = 0; int k; char *runtime_unit_dest = ""; if (argc > 1 && (argc != 4) ) { printf("This program takes three or no arguments.\n"); return -1; } if (argc > 1) runtime_unit_dest = argv[1]; k = gen_sslh_config(runtime_unit_dest); if (k < 0) r = k; return r < 0 ? -1 : 0; } sslh-1.22c/t000077500000000000000000000445161411244446000127550ustar00rootroot00000000000000#! /usr/bin/perl -w # Test script for sslh # Uses Conf::Libconfig to read sslh config file: install # with: # cpan Conf::Libconfig use strict; use IO::Socket::INET6; use Test::More qw/no_plan/; use Conf::Libconfig; my $conf = new Conf::Libconfig; $conf->read_file("test.cfg"); my $no_listen = 8083; # Port on which no-one listens my $pidfile = $conf->lookup_value("pidfile"); my $sslh_port = $conf->fetch_array("listen")->[0]->{port}; my $user = (getpwuid $<)[0]; # Run under current username # Which tests do we run my $SSH_SHY_CNX = 0; my $PROBES_NOFRAG = 1; my $PROBES_AGAIN = 1; my $SSL_MIX_SSH = 1; my $SSH_MIX_SSL = 1; my $DROP_CNX = 1; # Robustness tests. These are mostly to achieve full test # coverage, but do not necessarily result in an actual test # (e.g. some tests need to be run with valgrind to check all # memory management code). my $RB_CNX_NOSERVER = 0; my $RB_PARAM_NOHOST = 0; my $RB_WRONG_USERNAME = 0; my $RB_OPEN_PID_FILE = 0; my $RB_RESOLVE_ADDRESS = 0; my $RB_CL_PARAMS = 0; `lcov --directory . --zerocounters`; sub verbose_exec { my ($cmd) = @_; warn "$cmd\n"; if (!fork) { exec $cmd; } } # We want to keep track of tests to print a report at the # end, so we centralise all calls to Test::More::is here my $cnt = 1; # test counter my @results; sub my_is { my ($a, $b, $desc) = @_; my $res = is($a, $b, $desc); push @results, [$cnt++, $desc, $res]; } # For SNI/ALPN, build a protocol name as such: # tls:sni1,sni2,...;alpn1,alpn2,... # input: a protocol entry from Libconfig sub make_sni_alpn_name { my ($prot) = @_; return "tls:" . (join ",", @{$prot->{sni_hostnames} // []}) . ";" . (join ",", @{$prot->{alpn_protocols} // [] }); } # Tests one probe: given input data, connect, verify we get # the expected server, verify shoveling works # Named options: # data: what to write # expected: expected protocol prefix # no_frag: don't print byte-per-byte sub test_probe { my (%opts) = @_; my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx; return unless $cnx; my $pattern = $opts{data}; if ($opts{no_frag}) { syswrite $cnx, $pattern; } else { while (length $pattern) { syswrite $cnx, (substr $pattern, 0, 1, ''); select undef, undef, undef, .01; } } my $data; my $n = sysread $cnx, $data, 1024; $data =~ /^(.*?): /; my $prefix = $1; $data =~ s/$prefix: //g; print "Received $n bytes: protocol $prefix data [$data]\n"; close $cnx; $opts{expected} =~ s/^ssl/tls/; # to remove in 1.21 my_is($prefix, $opts{expected}, "$opts{binary}:$opts{expected}: probe connected correctly"); my_is($data, $opts{data}, "$opts{binary}:$opts{expected}: data shoveled correctly"); } # Test all probes, with or without fragmentation # options: # no_frag: write test patterns all at once (also # available per-protocol as some probes don't support # fragmentation) sub test_probes { my (%in_opts) = @_; my @probes = @{$conf->fetch_array("protocols")}; foreach my $p (@probes) { my %protocols = ( 'ssh' => { data => "SSH-2.0 tester" }, 'socks5' => { data => "\x05\x04\x01\x02\x03\x04" }, 'http' => { data => "GET index.html HTTP/1.1", no_frag => 1 }, 'tls' => { # Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`) data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn", # Packet with SNI alone data_sni => "\x16\x03\x01\x00\xb8\x01\x00\x00\xb4\x03\x03\x97\xe4\xe9\xad\x86\xe1\x21\xfd\xc4\x5b\x27\x0e\xad\x4b\x55\xc2\x50\xe4\x1c\x86\x2f\x37\x25\xde\xe8\x9c\x59\xfc\x1b\xa9\x37\x32\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x53\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello sni", # packet with ALPN alone data_alpn => "\x16\x03\x01\x00\xb7\x01\x00\x00\xb3\x03\x03\xe2\x90\xa2\x29\x03\x31\xad\x98\x44\x51\x54\x90\x5b\xd9\x51\x0e\x66\xb5\x3f\xe8\x8b\x09\xc9\xe4\x2b\x97\x24\xef\xad\x56\x06\xc9\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x52\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello alpn", # packet with no SNI, no ALPN data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone" }, 'openvpn' => { data => "\x00\x00" }, 'syslog' => { data => "<42> My syslog message" }, 'tinc' => { data => "0 hello" }, 'xmpp' => {data => "I should get a real jabber connection initialisation here" }, 'adb' => { data => "CNXN....................host:..." }, 'anyprot' => {data => "hello anyprot this needs to be longer than xmpp and adb which expect about 50 characters, which I all have to write before the timeout!" }, ); my $pattern = $protocols{$p->{name}}->{data}; my %opts = %in_opts; $opts{no_frag} = 1 if $protocols{$p->{name}}->{no_frag}; if ($p->{sni_hostnames} or $p->{alpn_protocols}) { my $pname = make_sni_alpn_name($p); my @sni = @{$p->{sni_hostnames} // [""] }; my @alpn = @{$p->{alpn_protocols} // [""] }; foreach my $sni ( @sni ) { foreach my $alpn ( @alpn ) { print "sni: $sni\nalpn: $alpn\n"; $pattern = $protocols{tls}->{ "data". ($sni ? "_sni" : "") . ($alpn ? "_alpn": "") }; $pattern =~ s/(\$\w+)/$1/eeg; test_probe( data => $pattern, expected => $pname, %opts ); } } } elsif ($p->{name} eq 'regex') { foreach my $test (@{$p->{test_patterns}}) { test_probe( data => $test->{pattern}, expected => $test->{result}, %opts ); } } else { test_probe( data => $pattern, expected => $p->{name}, %opts ); } } } # Start an echoserver for each service foreach my $s (@{$conf->fetch_array("protocols")}) { my $prefix = $s->{name}; $prefix =~ s/^ssl/tls/; # To remove in 1.21 if ($s->{sni_hostnames} or $s->{alpn_protocols}) { $prefix = make_sni_alpn_name($s); } verbose_exec "./echosrv --listen $s->{host}:$s->{port} --prefix '$prefix: '"; } #my @binaries = ('sslh-select', 'sslh-fork'); my @binaries = ('sslh-select'); for my $binary (@binaries) { warn "Testing $binary\n"; # Start sslh with the right plumbing my ($sslh_pid, $valgrind); if (!($sslh_pid = fork)) { my $user = (getpwuid $<)[0]; # Run under current username my $cmd = "./$binary -v 4 -f -u $user -F test.cfg"; #$valgrind = 1; #$cmd = "valgrind --leak-check=full $cmd"; verbose_exec $cmd; exit 0; } warn "spawned $sslh_pid\n"; sleep 1; # Give everyone some time to start sleep 5 if $valgrind; # valgrind can be heavy -- wait 5 seconds my $test_data = "hello world\n"; my $ssl_test_data = "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone"; # Test: Shy SSH connection if ($SSH_SHY_CNX) { print "***Test: Shy SSH connection\n"; my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_h; if (defined $cnx_h) { sleep 13; print $cnx_h $test_data; my $data = <$cnx_h>; my_is($data, "ssh: $test_data", "$binary: Shy SSH connection"); } } # Test: One SSL half-started then one SSH if ($SSL_MIX_SSH) { print "***Test: One SSL half-started then one SSH\n"; my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_l; if (defined $cnx_l) { print $cnx_l $ssl_test_data; my $cnx_h= new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_h; if (defined $cnx_h) { sleep 3; print $cnx_h $test_data; my $data_h = <$cnx_h>; my_is($data_h, "ssh: $test_data", "$binary: SSH during SSL being established"); } my $data; my $n = sysread $cnx_l, $data, 1024; my_is($data, "tls: $ssl_test_data", "$binary: SSL connection interrupted by SSH"); } } # Test: One SSH half-started then one SSL if ($SSH_MIX_SSL) { print "***Test: One SSH half-started then one SSL\n"; my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_h; if (defined $cnx_h) { sleep 3; my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_l; if (defined $cnx_l) { print $cnx_l $ssl_test_data; my $data; my $n = sysread $cnx_l, $data, 1024; my_is($data, "tls: $ssl_test_data", "$binary: SSL during SSH being established"); } print $cnx_h $test_data; my $data = <$cnx_h>; my_is($data, "ssh: $test_data", "$binary: SSH connection interrupted by SSL"); } } # Test: Drop connection without writing anything if ($DROP_CNX) { print "***Test: Connect but don't write anything\n"; my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_h; if ($cnx_h) { close $cnx_h; my_is(1, "$binary: Connect and write nothing"); # The goal of the test is to check sslh doesn't # crash } } if ($PROBES_NOFRAG) { test_probes(no_frag => 1, binary => $binary); } if ($PROBES_AGAIN) { test_probes(binary => $binary); } my $pid = `cat $pidfile`; warn "killing $pid\n"; kill TERM => $pid or warn "kill process: $!\n"; sleep 1; } # Robustness: Connecting to non-existant server if ($RB_CNX_NOSERVER) { print "***Test: Connecting to non-existant server\n"; my $sslh_pid; if (!($sslh_pid = fork)) { exec "./sslh-select -v 3 -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --tls localhost:$no_listen -P $pidfile"; } warn "spawned $sslh_pid\n"; sleep 1; my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); warn "$!\n" unless $cnx_h; if (defined $cnx_h) { sleep 1; my $test_data = "hello"; print $cnx_h $test_data; } # Ideally we should check a log is emitted. kill TERM => `cat $pidfile` or warn "kill: $!\n"; sleep 1; } my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0]; my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port}; # Use the last TLS echoserv (no SNI/ALPN) my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1]; my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port}; # Robustness: No hostname in address if ($RB_PARAM_NOHOST) { print "***Test: No hostname in address\n"; my $sslh_pid; if (!($sslh_pid = fork)) { exec "./sslh-select -v 3 -f -u $user --listen $sslh_port --ssh $ssh_address --tls $ssl_address -P $pidfile"; } warn "spawned $sslh_pid\n"; waitpid $sslh_pid, 0; my $code = $? >> 8; warn "exited with $code\n"; my_is($code, 6, "Exit status on illegal option"); } # Robustness: User does not exist if ($RB_WRONG_USERNAME) { print "***Test: Changing to non-existant username\n"; my $sslh_pid; if (!($sslh_pid = fork)) { exec "./sslh-select -v 3 -f -u ${user}_doesnt_exist --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address -P $pidfile"; } warn "spawned $sslh_pid\n"; waitpid $sslh_pid, 0; my $code = $? >> 8; warn "exited with $code\n"; my_is($code, 2, "Exit status on non-existant username"); } # Robustness: Can't open PID file if ($RB_OPEN_PID_FILE) { print "***Test: Can't open PID file\n"; my $sslh_pid; if (!($sslh_pid = fork)) { exec "./sslh-select -v 3 -f -u $user --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address -P /dont_exist/$pidfile"; # You don't have a /dont_exist/ directory, do you?! } warn "spawned $sslh_pid\n"; waitpid $sslh_pid, 0; my $code = $? >> 8; warn "exited with $code\n"; my_is($code, 3, "Exit status if can't open PID file"); } # Robustness: Can't resolve address if ($RB_RESOLVE_ADDRESS) { print "***Test: Can't resolve address\n"; my $sslh_pid; if (!($sslh_pid = fork)) { my $user = (getpwuid $<)[0]; # Run under current username exec "./sslh-select -v 3 -f -u $user --listen blahblah.dontexist:9000 --ssh $ssh_address --tls $ssl_address -P $pidfile"; } warn "spawned $sslh_pid\n"; waitpid $sslh_pid, 0; my $code = $? >> 8; warn "exited with $code\n"; my_is($code, 4, "Exit status if can't resolve address"); } # Robustness: verify all command line options work if ($RB_CL_PARAMS) { print "***Test: Command line parameters\n"; my $sslh_pid; if (!($sslh_pid = fork)) { my $user = (getpwuid $<)[0]; # Run under current username # This doesn't test --inetd exec "./sslh-select -v 3 -f -u $user -P $pidfile". " -n --transparent --timeout 10 -C /tmp". " --syslog-facility auth --on-timeout ssh". " --listen localhost:$no_listen --ssh $ssh_address --tls $ssl_address". " --openvpn localhost:$no_listen". " --tinc localhost:$no_listen". " --xmpp localhost:$no_listen". " --http localhost:$no_listen". " --adb localhost:$no_listen". " --socks5 localhost:$no_listen". " --anyprot localhost:$no_listen"; exit 0; } warn "spawned $sslh_pid\n"; # It will die soon because $user cannot chroot (you # don't test as root, do you?) waitpid $sslh_pid, 0; my $code = $? >> 8; warn "exited with $code\n"; my_is($code, 1, "Command line arguments"); print "***Test: Bad command line parameters\n"; my $sslh_pid; if (!($sslh_pid = fork)) { my $user = (getpwuid $<)[0]; # Run under current username # This doesn't test --inetd exec "./sslh-select -v 3 -f -u $user -P $pidfile". " -n --transparent --timeout 10 -C /tmp". " --fakeoption". " --anyprot localhost:$no_listen"; exit 0; } warn "spawned $sslh_pid\n"; waitpid $sslh_pid, 0; my $code = $? >> 8; warn "exited with $code\n"; my_is($code, 6, "Bad command line parameters"); } `lcov --directory . --capture --output-file sslh_cov.info`; `genhtml sslh_cov.info`; `killall echosrv`; format test_results_top = ID | Description | Status ----+-------------------------------------------------------------------+------- . format test_results = @>> | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @>> $_->[0], $_->[1], $_->[2] ? "OK" : "NOK" . format_name STDOUT "test_results"; format_top_name STDOUT "test_results_top"; map { write; } @results; sslh-1.22c/t_load000077500000000000000000000174321411244446000137510ustar00rootroot00000000000000#! /usr/bin/perl -w # Test script for sslh -- mass communication # This creates many clients that perform concurrent # connections, disconnect at any time, and try to generally # behave as badly as possible. # It can be used to test sslh behaves properly with many # clients, however its main use is to get an idea of how # much load it can take on your system before things start # to go wrong. use strict; use IO::Socket::INET6; use Data::Dumper; use Conf::Libconfig; ## BEGIN TEST CONFIG # How many total clients to we start? Each client will pick # a new protocol among what's in test.cfg. my $NUM_CNX = 50; # Delay between starting new processes when starting up. If # you start 200 processes in under a second, things go wrong # and it's not sslh's fault (typically the echosrv won't be # forking fast enough). my $start_time_delay = .5; # Max times we repeat the test string: allows to test for # large messages. my $block_rpt = 5; # Probability to stop a client after a message (e.g. with # .01 a client will send an average of 100 messages before # disconnecting). my $stop_client_probability = .001; ##END CONFIG my $conf = new Conf::Libconfig; $conf->read_file("test.cfg"); # Pick one address for TCP and one for UDP my @listen = @{$conf->fetch_array("listen")}; my ($sslh_tcp_address, $sslh_udp_address); foreach my $l (@listen) { if ($l->{is_udp}) { $sslh_udp_address //= "$l->{host}:$l->{port}"; } else { $sslh_tcp_address //= "$l->{host}:$l->{port}"; } last if defined $sslh_tcp_address and defined $sslh_udp_address; } # code snippets to connect to each protocol my %connect_params = ( regex => { is_udp => 1, sleep => 0, test_data => "foo bar", resp_len => 12, }, ssh => { sleep => 20, # So it times out 50% of connections test_data => "SSH-2.0 hello", resp_len => 18, # length "ssh: SSH-2.0 hello" => 18 }, tinc => { sleep => 0, test_data => "0 ", resp_len => 8, # length "tinc: 0 " => 10 }, openvpn => { sleep => 0, test_data => "\x00\x00", resp_len => 11, # length "openvpn: \x0\x0" => 11 }, ); sub connect_service { my ($cnx, $service) = @_; my $params = $connect_params{$service}; sleep rand $params->{sleep}; my $test_data = $params->{test_data}; syswrite $cnx, $test_data; sleep 1; sysread $cnx, my $r, $params->{resp_len}; my $expected = "$service: $test_data"; return ($r eq $expected); } sub client { my ($protocol, $client_id, $fd_out) = @_; my $service = $protocol->{name}; while (1) { my $r; #warn "$client_id: connect $sslh_tcp_address\n"; my $cnx = new IO::Socket::INET(PeerHost => $sslh_tcp_address); die "$@\n" if (!$cnx); my $cnt = 0; #warn "$client_id: connecting $service\n"; if (not connect_service($cnx, $service)) { print $fd_out "$client_id\t0\tC\n"; warn "$client_id: connecting failed\n"; exit; } #warn "$client_id: shoveling $service\n"; while (1) { my $test_data = "$service $cnt" x int(rand($block_rpt)+1) . "\n"; print $cnx $test_data; $r = <$cnx>; my $expected= "$test_data"; my $r_l = length $r; my $e_l = length $expected; $fd_out->autoflush; my $error = ""; $error = "E" if $r ne $expected; print $fd_out ("$client_id\t$r_l\t$error\n"); ($? = 1, die "$service got [$r] expected [$expected]\n") if ($r ne $expected); if (rand(1) < $stop_client_probability) { print $fd_out ("$client_id\t$r_l\tD\n"); last; } $cnt++; } } exit 0; } # For now, a simple regex client sub udp_client { my ($protocol, $client_id, $fd_out) = @_; warn "UDP client starts\n"; while (1) { my $cnx = new IO::Socket::INET(Proto => 'udp', PeerHost => $sslh_udp_address); # my $cnx; socket $cnx, PF_INET, SOCK_DGRAM, 0 or die "socket: $!\n"; die "$@\n" if (!$cnx); my $cnt = 0; while (1) { my $test_data = "foo udp $cnt"x int(rand($block_rpt)+1). "\n"; my $ipaddr = inet_aton("localhost"); my $portaddr = sockaddr_in(8086, $ipaddr); my $res = send($cnx, $test_data, 0, $portaddr); if ($res != length($test_data)) { die "cannot sendto: $!"; } my $expected= "$protocol->{name}: $test_data"; my $r; defined(recv($cnx, $r, length $expected, 0)) or die "recv: $!\n"; my $r_l = length $r; my $e_l = length $expected; $fd_out->autoflush; my $error = ""; $error = "M" if $r ne $expected; print $fd_out ("$client_id\t$r_l\t$error\n"); ($? = 1, die "udp got [$r] expected [$expected]\n") if ($r ne $expected); if (rand(1) < $stop_client_probability) { print $fd_out ("$client_id\t$r_l\tD\n"); last; } $cnt++; } } } foreach my $p (@{$conf->fetch_array("protocols")}) { if (!fork) { my $udp = $p->{is_udp} ? "--udp" : ""; my $cmd = "./echosrv $udp -p $p->{host}:$p->{port} --prefix '$p->{name}: ' 2> /dev/null"; warn "$cmd\n"; exec $cmd; exit; } } warn "Don't forget to run sslh -F test.cfg!\n"; sleep 2; # Let echosrv's and sslh start my ($c_in, $c_out); pipe $c_in, $c_out; my @protocols = @{$conf->fetch_array("protocols")}; if (!fork) { # Process that starts all the clients for my $client_num (1 .. $NUM_CNX) { if (!fork) { my @supported_protocols = keys %connect_params; my $p_name = $supported_protocols[rand @supported_protocols]; my @p = grep { $_->{name} eq $p_name } @protocols; my $p = shift @p; if ($p->{is_udp}) { udp_client($p, "$p->{name}$client_num", $c_out); } else { client($p, "$p->{name}$client_num", $c_out); } exit; } # Give a little time so we don't overrun the # listen(2) backlog. select undef, undef, undef, $start_time_delay; } exit; } else { my %data; # The condition here selects between pretty output or # raw output if (1) { my $CLEAR_LINE = "\033[2K"; my $CURSOR_HOME = "\033[1;1H"; my $CLEAR_SCREEN = "\033[2J"; # Process that retrieves client output to pretty print print $CLEAR_SCREEN; # Clear screen while (<$c_in>) { chop; my ($client_id, $r_l, $error, @rest) = split /\t/, $_; $data{$client_id} = [ 0, ""] if not exists $data{$client_id}; my ($curr_rcv) = ${$data{$client_id}}[0] + $r_l;; $error //= ""; my ($curr_error) = "${$data{$client_id}}[1]$error"; # Consolidate into... roman numerals! $curr_error =~ s/D{10}/X/; $curr_error =~ s/X{10}/C/; $curr_error =~ s/C{10}/M/; $data{$client_id} = [ $r_l + $curr_rcv, "$curr_error$error" ]; $client_id =~ /(\d+)/; my $i = $1; # print $CURSOR_HOME; print "\033[$i;1H$CLEAR_LINE$client_id\t$curr_rcv\t$curr_error\n"; #foreach my $i (sort keys %data) { # ($r_l, $error) = @{$data{$i}}; # print "$CLEAR_LINE$i\t$r_l\t$error\n"; } } else { # Just print the client outputs while (<$c_in>) { print; } } } warn "waiting\n"; wait; warn "finished waiting\n"; `killall echosrv`; sslh-1.22c/test.cfg000066400000000000000000000042561411244446000142210ustar00rootroot00000000000000# Configuration file for testing (use both by sslh under # test and the test script `t`) verbose: 4; foreground: true; inetd: false; numeric: true; transparent: false; timeout: 10; # Probe test writes slowly pidfile: "/tmp/sslh_test.pid"; syslog_facility: "auth"; # List of interfaces on which we should listen # Options: listen: ( { host: "localhost"; port: "8080"; keepalive: true; }, { host: "localhost"; port: "8081"; keepalive: true; }, { host: "ip4-localhost"; is_udp: true; port: "8086"; } ); protocols: ( { name: "ssh"; host: "localhost"; port: "9000"; fork: true; transparent: true; }, { name: "socks5"; host: "localhost"; port: "9001"; }, { name: "http"; host: "localhost"; port: "9002"; }, { name: "tinc"; host: "localhost"; port: "9003"; }, { name: "openvpn"; host: "localhost"; port: "9004"; }, { name: "xmpp"; host: "localhost"; port: "9009"; }, { name: "adb"; host: "localhost"; port: "9010"; }, { name: "syslog"; host: "localhost"; port: "9013"; }, { name: "regex"; host: "ip4-localhost"; is_udp: true; port: "9020"; udp_timeout: 30; regex_patterns: [ "^foo" ]; }, { name: "regex"; host: "localhost"; port: "9011"; regex_patterns: [ "^foo", "^bar" ]; minlength: 4; test_patterns: ( # this is used by the test script, not by sslh { pattern: "foo"; result: "ssh"; }, # After timeout { pattern: "fooo"; result: "regex"; }, { pattern: "bar"; result: "ssh"; }, { pattern: "barr"; result: "regex"; }, { pattern: "barrrr"; result: "regex"; } ); }, { name: "tls"; host: "localhost"; port: "9021"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni1" ]; }, { name: "tls"; host: "localhost"; port: "9022"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni2", "sni3" ]; }, { name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; }, { name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; }, { name: "tls"; host: "localhost"; port: "9025"; }, { name: "anyprot"; host: "localhost"; port: "9099"; } ); on_timeout: "ssh"; sslh-1.22c/tls.c000066400000000000000000000234561411244446000135320ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * This is a minimal TLS implementation intended only to parse the server name * extension. This was created based primarily on Wireshark dissection of a * TLS handshake and RFC4366. */ #include #include /* malloc() */ #include /* fnmatch() */ #include "tls.h" #include "sslh-conf.h" #define TLS_HEADER_LEN 5 #define TLS_HANDSHAKE_CONTENT_TYPE 0x16 #define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01 #ifndef MIN #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #endif typedef struct { int tls_match_sni : 1; int tls_match_alpn : 1; } TLS_MATCHMODE; struct TLSProtocol { TLS_MATCHMODE match_mode; int sni_list_len; const char** sni_hostname_list; int alpn_list_len; const char** alpn_protocol_list; }; static int parse_extensions(const struct TLSProtocol *, const char *, size_t); static int parse_server_name_extension(const struct TLSProtocol *, const char *, size_t); static int parse_alpn_extension(const struct TLSProtocol *, const char *, size_t); static int has_match(const char**, size_t, const char*, size_t); /* Parse a TLS packet for the Server Name Indication and ALPN extension in the client * hello handshake, returning a status code * * Returns: * 0: no match * 1: match * < 0: error code (see tls.h) */ int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { char tls_content_type; char tls_version_major; char tls_version_minor; size_t pos = TLS_HEADER_LEN; size_t len; /* Check that our TCP payload is at least large enough for a TLS header */ if (data_len < TLS_HEADER_LEN) return TLS_ELENGTH; tls_content_type = data[0]; if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) { if (cfg.verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n"); return TLS_EPROTOCOL; } tls_version_major = data[1]; tls_version_minor = data[2]; if (tls_version_major < 3) { if (cfg.verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n", tls_version_major, tls_version_minor); return TLS_EVERSION; } /* TLS record length */ len = ((unsigned char)data[3] << 8) + (unsigned char)data[4] + TLS_HEADER_LEN; data_len = MIN(data_len, len); /* Check we received entire TLS record length */ if (data_len < len) return TLS_ELENGTH; /* * Handshake */ if (pos + 1 > data_len) { return TLS_EPROTOCOL; } if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { if (cfg.verbose) fprintf(stderr, "Not a client hello\n"); return TLS_EPROTOCOL; } /* Skip past fixed length records: 1 Handshake Type 3 Length 2 Version (again) 32 Random to Session ID Length */ pos += 38; /* Session ID */ if (pos + 1 > data_len) return TLS_EPROTOCOL; len = (unsigned char)data[pos]; pos += 1 + len; /* Cipher Suites */ if (pos + 2 > data_len) return TLS_EPROTOCOL; len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; pos += 2 + len; /* Compression Methods */ if (pos + 1 > data_len) return TLS_EPROTOCOL; len = (unsigned char)data[pos]; pos += 1 + len; if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) { if (cfg.verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n"); return TLS_EVERSION; } /* Extensions */ if (pos + 2 > data_len) return TLS_EPROTOCOL; len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1]; pos += 2; if (pos + len > data_len) return TLS_EPROTOCOL; /* By now we know it's TLS. if SNI or ALPN is set, parse extensions to see if * they match. Otherwise, it's a match already */ if (tls_data && (tls_data->match_mode.tls_match_alpn || tls_data->match_mode.tls_match_sni)) { return parse_extensions(tls_data, data + pos, len); } else { return TLS_MATCH; } } static int parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { size_t pos = 0; size_t len; int sni_match = 0, alpn_match = 0; if (tls_data == NULL) return TLS_EINVAL; /* Parse each 4 bytes for the extension header */ while (pos + 4 <= data_len) { /* Extension Length */ len = ((unsigned char) data[pos + 2] << 8) + (unsigned char) data[pos + 3]; if (pos + 4 + len > data_len) return TLS_EPROTOCOL; size_t extension_type = ((unsigned char) data[pos] << 8) + (unsigned char) data[pos + 1]; if (extension_type == 0x00 && tls_data->match_mode.tls_match_sni) { /* Server Name */ sni_match = parse_server_name_extension(tls_data, data + pos + 4, len); if (sni_match < 0) return sni_match; } else if (extension_type == 0x10 && tls_data->match_mode.tls_match_alpn) { /* ALPN */ alpn_match = parse_alpn_extension(tls_data, data + pos + 4, len); if (alpn_match < 0) return alpn_match; } pos += 4 + len; /* Advance to the next extension header */ } /* Check we ended where we expected to */ if (pos != data_len) return TLS_EPROTOCOL; return (sni_match && alpn_match) || (!tls_data->match_mode.tls_match_sni && alpn_match) || (!tls_data->match_mode.tls_match_alpn && sni_match); } static int parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { size_t pos = 2; /* skip server name list length */ size_t len; while (pos + 3 < data_len) { len = ((unsigned char)data[pos + 1] << 8) + (unsigned char)data[pos + 2]; if (pos + 3 + len > data_len) return TLS_EPROTOCOL; switch (data[pos]) { /* name type */ case 0x00: /* host_name */ if(has_match(tls_data->sni_hostname_list, tls_data->sni_list_len, data + pos + 3, len)) { return len; } else { return TLS_ENOEXT; } default: if (cfg.verbose) fprintf(stderr, "Unknown server name extension name type: %d\n", data[pos]); } pos += 3 + len; } /* Check we ended where we expected to */ if (pos != data_len) return TLS_EPROTOCOL; return TLS_ENOEXT; } static int parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_t data_len) { size_t pos = 2; size_t len; while (pos + 1 < data_len) { len = (unsigned char)data[pos]; if (pos + 1 + len > data_len) return TLS_EPROTOCOL; if (len > 0 && has_match(tls_data->alpn_protocol_list, tls_data->alpn_list_len, data + pos + 1, len)) { return len; } else if (len > 0) { if (cfg.verbose) fprintf(stderr, "Unknown ALPN name: %.*s\n", (int)len, data + pos + 1); } pos += 1 + len; } /* Check we ended where we expected to */ if (pos != data_len) return TLS_EPROTOCOL; return TLS_ENOEXT; } static int has_match(const char** list, size_t list_len, const char* name, size_t name_len) { const char **item; int i; char *name_nullterminated = malloc(name_len+1); CHECK_ALLOC(name_nullterminated, "malloc"); memcpy(name_nullterminated, name, name_len); name_nullterminated[name_len]='\0'; for (i = 0; i < list_len; i++) { item = &list[i]; if (cfg.verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item); if(!fnmatch(*item, name_nullterminated, 0)) { free(name_nullterminated); return 1; } } free(name_nullterminated); return 0; } struct TLSProtocol * new_tls_data() { struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol)); CHECK_ALLOC(tls_data, "malloc"); memset(tls_data, 0, sizeof(*tls_data)); return tls_data; } struct TLSProtocol * tls_data_set_list(struct TLSProtocol *tls_data, int alpn, const char** list, size_t list_len) { if (alpn) { tls_data->alpn_protocol_list = list; tls_data->alpn_list_len = list_len; tls_data->match_mode.tls_match_alpn = 1; } else { tls_data->sni_hostname_list = list; tls_data->sni_list_len = list_len; tls_data->match_mode.tls_match_sni = 1; } return tls_data; } sslh-1.22c/tls.h000066400000000000000000000040151411244446000135250ustar00rootroot00000000000000/* * Copyright (c) 2011 and 2012, Dustin Lundquist * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef TLS_H #define TLS_H #include "common.h" struct TLSProtocol; int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len); struct TLSProtocol *new_tls_data(); struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, const char**, size_t); #define TLS_MATCH 1 #define TLS_NOMATCH 0 #define TLS_EINVAL -1 /* Invalid parameter (NULL data pointer) */ #define TLS_ELENGTH -2 /* Incomplete request */ #define TLS_EVERSION -3 /* TLS version that cannot be parsed */ #define TLS_ENOEXT -4 /* No ALPN or SNI extension found */ #define TLS_EPROTOCOL -5 /* Protocol error */ #endif sslh-1.22c/udp-listener.c000066400000000000000000000112001411244446000153230ustar00rootroot00000000000000/* udp-listener.c: handles demultplexing UDP protocols # Copyright (C) 2020-2021 Yves Rutschle # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be # useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more # details. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #include "common.h" #include "probe.h" #include "sslh-conf.h" #include "udp-listener.h" /* Find if the specified source has been seen before. -1 if not found * * TODO This is linear search and needs to be changed to something better for * production if we have more than a dozen sources * Also, this assumes src_addr from recvfrom() are repeatable for a specific * source... * */ static int known_source(cnx_collection* collection, int max_fd, struct sockaddr* addr, socklen_t addrlen) { int i; for (i = 0; i < max_fd; i++) { struct connection* cnx = collection_get_cnx_from_fd(collection, i); if (cnx && (cnx->type == SOCK_DGRAM) && cnx->target_sock) { if (!memcmp(&cnx->client_addr, addr, addrlen)) { return i; } } } return -1; } /* Process UDP coming from outside (client towards server) * If it's a new source, probe; otherwise, forward to previous target * Returns: >= 0 sockfd of newly allocated socket, for new connections * -1 otherwise * */ int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd) { char addr_str[NI_MAXHOST+1+NI_MAXSERV+1]; struct sockaddr src_addr; struct addrinfo addrinfo; struct sslhcfg_protocols_item* proto; struct connection* cnx; ssize_t len; socklen_t addrlen; int res, target, out = -1; char data[65536]; /* Theoritical max is 65507 (https://en.wikipedia.org/wiki/User_Datagram_Protocol). This will do. Dynamic allocation is possible with the MSG_PEEK flag in recvfrom(2), but that'd imply malloc/free overhead for each packet, when really 64K is not that much */ addrlen = sizeof(src_addr); len = recvfrom(sockfd, data, sizeof(data), 0, &src_addr, &addrlen); if (len < 0) { perror("recvfrom"); return -1; } target = known_source(collection, max_fd, &src_addr, addrlen); addrinfo.ai_addr = &src_addr; addrinfo.ai_addrlen = addrlen; if (cfg.verbose) fprintf(stderr, "received %ld UDP from %d:%s\n", len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo)); if (target == -1) { res = probe_buffer(data, len, &proto); /* First version: if we can't work out the protocol from the first * packet, drop it. Conceivably, we could store several packets to * run probes on packet sets */ if (cfg.verbose) fprintf(stderr, "UDP probed: %d\n", res); if (res != PROBE_MATCH) { return -1; } out = socket(proto->saddr->ai_family, SOCK_DGRAM, 0); struct connection* cnx = collection_alloc_cnx_from_fd(collection, out); if (!cnx) return -1; target = out; cnx->target_sock = out; cnx->proto = proto; cnx->type = SOCK_DGRAM; cnx->client_addr = src_addr; cnx->addrlen = addrlen; cnx->local_endpoint = sockfd; } cnx = collection_get_cnx_from_fd(collection, target); /* at this point src is the UDP connection */ res = sendto(cnx->target_sock, data, len, 0, cnx->proto->saddr->ai_addr, cnx->proto->saddr->ai_addrlen); cnx->last_active = time(NULL); fprintf(stderr, "sending %d to %s\n", res, sprintaddr(data, sizeof(data), cnx->proto->saddr)); return out; } void udp_s2c_forward(struct connection* cnx) { int sockfd = cnx->target_sock; char data[65536]; int res; res = recvfrom(sockfd, data, sizeof(data), 0, NULL, NULL); fprintf(stderr, "recvfrom %d\n", res); CHECK_RES_DIE(res, "udp_listener/recvfrom"); res = sendto(cnx->local_endpoint, data, res, 0, &cnx->client_addr, cnx->addrlen); cnx->last_active = time(NULL); fprintf(stderr, "sendto %d to\n", res); } /* returns date at which this socket times out. */ int udp_timeout(struct connection* cnx) { if (cnx->type != SOCK_DGRAM) return 0; /* Not a UDP connection */ return cnx->proto->udp_timeout + cnx->last_active; } sslh-1.22c/udp-listener.h000066400000000000000000000015231411244446000153370ustar00rootroot00000000000000#ifndef UDPLISTENER_H #define UDPLISTENER_H #include "collection.h" /* UDP listener: upon incoming packet, find where it should go * This is run in its own process and never returns. */ void udp_listener(struct listen_endpoint* endpoint, int num_endpoints, int active_endpoint); /* Process UDP coming from outside (client towards server) * If it's a new source, probe; otherwise, forward to previous target * Returns: >= 0 sockfd of newly allocated socket, for new connections * -1 otherwise * */ int udp_c2s_forward(int sockfd, cnx_collection* collection, int max_fd); /* Process UDP coming from inside (server towards client) */ void udp_s2c_forward(struct connection* cnx); /* returns how many seconds before socket times out. Negative if timed out * already. */ int udp_timeout(struct connection* cnx); #endif /* UDPLISTENER_H */ sslh-1.22c/udp.cfg000066400000000000000000000015111411244446000140210ustar00rootroot00000000000000# Example for UDP protocols # Listen sockets get a `is_udp : true` option # Protocol entries work exactly the same as TCP, there is no # difference. Currently, if mix up TCP and UDP probes, sslh will # try them in order and forward a UDP packet to a TCP probe # that matches (but using UDP; meaning, sslh will happily # forward a UDP packet to udp:localhost:80 if the HTTP # probe is specified, and matches) verbose: 3; foreground: true; inetd: false; numeric: false; transparent: false; timeout: 10; pidfile: "/tmp/sslh_test.pid"; # List of interfaces on which we should listen # Options: listen: ( { host: "localhost"; is_udp: true; port: "4443"; } ); protocols: ( { name: "regex"; regex_patterns: [ "rutschle.net" ]; host: "ns1.rutschle.net"; port: "53"; }, { name: "anyprot"; host: "localhost"; port: "9099"; } );