openfortivpn-1.23.1/0000775000175000017500000000000014753334500014241 5ustar epsilonepsilonopenfortivpn-1.23.1/configure.ac0000664000175000017500000002717014753334500016536 0ustar epsilonepsilon# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.63]) AC_INIT([openfortivpn], [1.23.1]) AC_CONFIG_SRCDIR([src/main.c]) AM_INIT_AUTOMAKE([foreign subdir-objects]) # Checks for programs. AC_PROG_CC AC_PROG_MKDIR_P AC_PROG_SED AC_PROG_INSTALL AC_USE_SYSTEM_EXTENSIONS m4_ifndef([PKG_PROG_PKG_CONFIG], [m4_fatal([Please install pkg-config.])]) PKG_PROG_PKG_CONFIG REVISION="" AS_IF([test -d .git], [ AC_PATH_PROG(GIT, [git], [""], "$PATH:/sbin:/usr/sbin") AS_IF([test "x$GIT" != "x"], [ REVISION=`$GIT describe --tags | sed -e 's/-/+git/;y/-/./'` ]) ]) AS_IF([test "x$REVISION" = "x"], [ REVISION="unavailable" ]) AC_SUBST(REVISION) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) # Checks for libraries. PKG_CHECK_MODULES(OPENSSL, [libssl >= 1.0.2 libcrypto >= 1.0.2], [], [AC_MSG_ERROR([Cannot find OpenSSL 1.0.2 or higher.])]) AC_CHECK_LIB([pthread], [pthread_create], [], [AC_MSG_ERROR([Cannot find libpthread.])]) AC_CHECK_LIB([util], [forkpty], [], [AC_MSG_ERROR([Cannot find libutil.])]) PKG_CHECK_MODULES(LIBSYSTEMD, [libsystemd], [AC_DEFINE(HAVE_SYSTEMD)], [AC_MSG_RESULT([libsystemd not present])]) # we assume presence of the following C standard headers # and omit them in the following header checks # # assert.h # ctype.h # errno.h # limits.h # signal.h # stdarg.h # stddef.h # stdint.h # stdio.h # stdlib.h # string.h # Checks for required header files. AC_CHECK_HEADERS([ \ arpa/inet.h \ fcntl.h \ getopt.h \ ifaddrs.h \ netdb.h \ netinet/in.h \ netinet/tcp.h \ pthread.h \ strings.h \ sys/ioctl.h \ syslog.h \ sys/select.h \ sys/socket.h \ sys/stat.h \ sys/types.h \ sys/wait.h \ termios.h \ unistd.h \ ], [], AC_MSG_ERROR([Required header not found])) # Checks for header files with prerequisites of other headers. AC_CHECK_HEADERS([net/if.h], [], AC_MSG_ERROR([Required header not found]), [#include ]) AC_CHECK_HEADERS([net/route.h], [], AC_MSG_ERROR([Required header not found]), [#include ], [#include ]) # Checks for optional header files. AC_CHECK_HEADERS([ \ libutil.h \ mach/mach.h \ pty.h \ semaphore.h \ util.h \ ]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_INLINE AC_C_VOLATILE AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT8_T AC_CHECK_TYPES([struct termios], [], [], [#include ]) # Checks for library functions. AC_CHECK_FUNCS([ \ access \ close \ connect \ execv \ _exit \ fcntl \ fileno \ forkpty \ freeaddrinfo \ freeifaddrs \ gai_strerror \ getaddrinfo \ geteuid \ getifaddrs \ getopt_long \ htons \ inet_addr \ inet_ntoa \ ioctl \ isatty \ memmem \ ntohs \ open \ openlog \ pclose \ popen \ pthread_cancel \ pthread_cond_init \ pthread_cond_signal \ pthread_cond_wait \ pthread_create \ pthread_join \ pthread_mutexattr_init \ pthread_mutex_destroy \ pthread_mutex_init \ pthread_mutex_lock \ pthread_mutex_unlock \ pthread_self \ pthread_sigmask \ read \ select \ sem_destroy \ sem_init \ sem_post \ sem_wait \ setenv \ setsockopt \ sigaddset \ sigemptyset \ sleep \ socket \ strcasecmp \ strdup \ strncasecmp \ strsignal \ strtok_r \ syslog \ tcgetattr \ tcsetattr \ usleep \ vsyslog \ waitpid \ write \ ], [], AC_MSG_ERROR([Required function not found])) # Checks for optional functions. AC_CHECK_FUNCS([ \ pthread_mutexattr_setrobust \ vdprintf \ ]) # Use PKG_CHECK_MODULES compiler/linker flags save_openssl_CPPFLAGS="${CPPFLAGS}" save_openssl_LIBS="${LIBS}" CPPFLAGS="${OPENSSL_CFLAGS} ${CPPFLAGS}" LIBS="${OPENSSL_LIBS} ${LIBS}" CPPFLAGS="${save_openssl_CPPFLAGS}" LIBS="${save_openssl_LIBS}" # Specialised checks for particular features. # When cross compile, don't run the tests. AC_ARG_WITH([rt_dst], AS_HELP_STRING([--with-rt_dst], [disable rtentry with rt_dst testing (for linux target when cross compile)]), AS_IF([test "x$with_rt_dst" = "xyes"],[ AC_MSG_NOTICE([HAVE_RT_ENTRY_WITH_RT_DST... 1]) AC_DEFINE(HAVE_RT_ENTRY_WITH_RT_DST) ]) ) AS_IF([test "x$with_rt_dst" = "x"], [ AC_MSG_CHECKING(whether rtentry is available and has rt_dst) AC_LANG(C) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ #include #include #include static inline struct sockaddr_in *cast_addr(struct sockaddr *addr) { return (struct sockaddr_in *) addr; } ],[ struct rtentry route; cast_addr(&(&route)->rt_dst)->sin_family = AF_INET; ])],[ AC_DEFINE(HAVE_RT_ENTRY_WITH_RT_DST) AC_MSG_RESULT(yes) ], AC_MSG_RESULT(no)) ]) NETSTAT_PATH="" PPP_PATH="" RESOLVCONF_PATH="" # prepare possibility to override default locations AC_ARG_WITH([netstat], AS_HELP_STRING([--with-netstat], [set the path to the netstat executable on MacOS or FreeBSD]), NETSTAT_PATH="$withval" ) # this is for the pppd daemon executable AC_ARG_WITH([pppd], AS_HELP_STRING([--with-pppd], [set the path to the pppd daemon executable]), AS_IF([test ! "x$with_pppd" = "xno" -a ! "x$with_pppd" = "xyes"],[ PPP_PATH="$withval" with_pppd="yes" with_ppp="no" ]) ) # support pppd < 2.5.0 by default instead of pppd >= 2.5.0 AC_ARG_ENABLE([legacy_pppd], AS_HELP_STRING([--enable-legacy-pppd], [support pppd < 2.5.0 by default instead of pppd >= 2.5.0])) # this is for the ppp user space client on FreeBSD AC_ARG_WITH([ppp], AS_HELP_STRING([--with-ppp], [set the path to the ppp userspace client on FreeBSD]), AS_IF([test ! "x$with_ppp" = "xno" -a ! "x$with_ppp" = "xyes"],[ PPP_PATH="$withval" with_ppp="yes" with_pppd="no" ]) ) # override for /proc/net/route detection AC_ARG_ENABLE([proc], AS_HELP_STRING([--enable-proc], [enable route manipulations directly via /proc/net/route \ when cross-compiling, use --disable-proc for the opposite])) # check for netstat if not cross-compiling AS_IF([test "x$enable_proc" = "x"], [ AC_CHECK_FILE([/proc/net/route],[ AS_IF([test "x$enable_proc" = "x"], [ enable_proc="yes" ]) ],[ AS_IF([test "x$NETSTAT_PATH" = "x"], [ AC_CHECK_FILE([/usr/sbin/netstat],[ NETSTAT_PATH="/usr/sbin/netstat" ],[ AC_CHECK_FILE([/usr/bin/netstat],[ NETSTAT_PATH="/usr/bin/netstat" ],[]) ]) ]) ]) ]) # check for ppp if not specified AC_PATH_PROG(PPP, [ppp], [/usr/sbin/ppp], "$PATH:/sbin:/usr/sbin") AS_IF([test "x$PPP_PATH" = "x"], [ AC_CHECK_FILE([$PPP], [ AS_IF([test "x$PPP_PATH" = "x"], [ PPP_PATH="$PPP" ]) AS_IF([test "x$with_ppp" = "x"], [ with_ppp="yes" ]) ],[]) ]) # check for pppd if not specified AC_PATH_PROG(PPPD, [pppd], [/usr/sbin/pppd], "$PATH:/sbin:/usr/sbin") AS_IF([test "x$PPP_PATH" = "x"], [ AC_CHECK_FILE([$PPPD], [ AS_IF([test "x$PPP_PATH" = "x"], [ PPP_PATH="$PPPD" ]) AS_IF([test "x$with_pppd" = "x"], [ with_pppd="yes" ]) ],[]) ]) # when neither ppp nor pppd are enabled fall back to a sensible choice for the platform AS_IF([test "x$with_ppp" = "x" -a "x$with_pppd" = "x"], [ AS_IF([test "x$(uname)" = "xFreeBSD"], [ with_ppp="yes" PPP_PATH="/usr/sbin/ppp" ], [ with_pppd="yes" PPP_PATH="/usr/sbin/pppd" ]) ]) # when both are enabled, give pppd the higher priority (we can only use one of them) AS_IF([test "x$with_ppp" = "xyes" -a "x$with_pppd" = "xyes"], [ with_ppp="no" ]) # replace empty settings with "no" AS_IF([test "x$with_pppd" = "x"], [ with_pppd="no" ]) AS_IF([test "x$with_ppp" = "x"], [ with_ppp="no" ]) AS_IF([test "x$with_ppp" = "xyes"], [ AC_DEFINE(HAVE_USR_SBIN_PPP, 1) AC_MSG_NOTICE([HAVE_USR_SBIN_PPP... 1]) ],[ AC_DEFINE(HAVE_USR_SBIN_PPP, 0) AC_MSG_NOTICE([HAVE_USR_SBIN_PPP... 0]) ]) AS_IF([test "x$with_pppd" = "xyes"], [ AC_DEFINE(HAVE_USR_SBIN_PPPD, 1) AC_MSG_NOTICE([HAVE_USR_SBIN_PPPD... 1]) ],[ AC_DEFINE(HAVE_USR_SBIN_PPPD, 0) AC_MSG_NOTICE([HAVE_USR_SBIN_PPPD... 0]) ]) AS_IF([test "x$enable_legacy_pppd" = "xyes"], [ AC_DEFINE(LEGACY_PPPD, 1) AC_MSG_NOTICE([LEGACY_PPPD... 1]) ],[ AC_DEFINE(LEGACY_PPPD, 0) AC_MSG_NOTICE([LEGACY_PPPD... 0]) ]) AS_IF([test "x$enable_proc" = "xyes"], [ AC_DEFINE(HAVE_PROC_NET_ROUTE, 1) AC_MSG_NOTICE([HAVE_PROC_NET_ROUTE... 1]) ],[ AC_DEFINE(HAVE_PROC_NET_ROUTE, 0) AC_MSG_NOTICE([HAVE_PROC_NET_ROUTE... 0]) ]) AC_SUBST(PPP_PATH) AC_MSG_NOTICE([PPP_PATH...] $PPP_PATH) AC_SUBST(NETSTAT_PATH) AS_IF([test "x$NETSTAT_PATH" != "x"], [ AC_MSG_NOTICE([NETSTAT_PATH...] $NETSTAT_PATH) ]) # use resolvconf if present AC_PATH_PROG(RESOLVCONF_PATH, [resolvconf], [DISABLED], "$PATH:/sbin:/usr/sbin") # allow override at configure time AC_ARG_WITH([resolvconf], AS_HELP_STRING([--with-resolvconf], [set the path to the resolvconf executable, \ with special value "DISABLED" fully disabling \ resolvconf support at build-time]), RESOLVCONF_PATH="$withval" ) AC_SUBST(RESOLVCONF_PATH) AS_IF([test "x$RESOLVCONF_PATH" != "x"], [ AC_MSG_NOTICE([RESOLVCONF_PATH...] $RESOLVCONF_PATH) ]) AS_IF([test "x$RESOLVCONF_PATH" = "xDISABLED"], [ AC_DEFINE(HAVE_RESOLVCONF, 0) AC_MSG_NOTICE([HAVE_RESOLVCONF... 0]) ],[ AC_DEFINE(HAVE_RESOLVCONF, 1) AC_MSG_NOTICE([HAVE_RESOLVCONF... 1]) ]) # the default for the --use-resolvconf runtime command line option AC_ARG_ENABLE([resolvconf], AS_HELP_STRING([--enable-resolvconf], [enable usage of resolvconf at runtime by default \ (please note that resolvconf support will still \ be compiled in with --disable-resolvconf but \ disabled unless explicitly enabled at runtime)])) # Determine how resolvconf works at build-time if it is installed: # * openresolv supports option -l that lists active configurations and returns 0 # * resolvconf in Ubuntu/Debian does not support listing but returns 99 # if invoked without parameters # * skip resolvectl which does not work as expected when invoked as resolveconf AS_IF([test "x$enable_resolvconf" = "x" -a "x$RESOLVCONF_PATH" != "xDISABLED"], [ AC_CHECK_FILE([$RESOLVCONF_PATH],[ AS_IF([$RESOLVCONF_PATH -l >/dev/null 2>/dev/null], [ enable_resolvconf="yes" ],[ AS_IF([$RESOLVCONF_PATH >/dev/null 2>/dev/null ; test $? -eq 99], [ enable_resolvconf="yes" ],[ enable_resolvconf="no" ]) ]) ],[ enable_resolvconf="no" ]) ]) AS_IF([test "x$enable_resolvconf" = "xyes"], [ AC_DEFINE(USE_RESOLVCONF, 1) AC_MSG_NOTICE([USE_RESOLVCONF... 1]) ],[ AC_DEFINE(USE_RESOLVCONF, 0) AC_MSG_NOTICE([USE_RESOLVCONF... 0]) ]) # install systemd service file AC_ARG_WITH([systemdsystemunitdir], [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [directory for systemd service files])],, [with_systemdsystemunitdir=auto]) AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) AS_IF([test "x$def_systemdsystemunitdir" = "x"], [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) with_systemdsystemunitdir=no], [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) AC_MSG_NOTICE([systemdsystemunitdir... $systemdsystemunitdir]) AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include int main(int argc, char **argv){ int ret, handle; handle = socket(AF_INET, SOCK_STREAM, 0); ret = setsockopt(handle, SOL_SOCKET, SO_BINDTODEVICE,"lo",3); } ])], [ AC_DEFINE(HAVE_SO_BINDTODEVICE, 1) AC_MSG_NOTICE([HAVE_SO_BINDTODEVICE... 1]) ],[ AC_DEFINE(HAVE_SO_BINDTODEVICE, 0) AC_MSG_NOTICE([HAVE_SO_BINDTODEVICE... 0]) ]) AC_CONFIG_COMMANDS([timestamp], [touch src/.dirstamp]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT openfortivpn-1.23.1/etc/0000775000175000017500000000000014753334500015014 5ustar epsilonepsilonopenfortivpn-1.23.1/etc/ppp/0000775000175000017500000000000014753334500015613 5ustar epsilonepsilonopenfortivpn-1.23.1/etc/ppp/peers/0000775000175000017500000000000014753334500016731 5ustar epsilonepsilonopenfortivpn-1.23.1/etc/ppp/peers/openfortivpn0000664000175000017500000000021014753334500021376 0ustar epsilonepsilon38400 :192.0.2.1 noipdefault noaccomp noauth default-asyncmap nopcomp receive-all nodefaultroute nodetach lcp-max-configure 40 mru 1354 openfortivpn-1.23.1/etc/ppp/ip-up.local.example0000775000175000017500000000116214753334500021316 0ustar epsilonepsilon#!/usr/bin/env bash case "$PPP_IPPARAM" in openfortivpn*) rconf=/etc/resolv.conf routes=$(echo "$PPP_IPPARAM" | tr , ' ') for r in $routes; do [[ $r = "openfortivpn" ]] && continue com="ip route add ${r%/*} via ${r##*/}" echo "$com" $com done cp -pv $rconf $rconf.openfortivpn if [[ "$DNS1" ]]; then echo nameserver "$DNS1" > $rconf [[ "$DNS2" ]] && [[ "$DNS1" != "$DNS2" ]] && echo nameserver "$DNS2" >> $rconf fi exit 0 ;; esac 2>&1 | logger -p daemon.debug -i -t "$0" true openfortivpn-1.23.1/etc/ppp/ppp.conf.example0000664000175000017500000000043614753334500020716 0ustar epsilonepsilon# Example configuration for a ppp client # for more examples see # https://github.com/freebsd/freebsd-base-graphics/blob/master/share/examples/ppp/ppp.conf.sample vpn-client: set dial set speed 38400 set mru 1354 set login set timeout 0 disable deflate pred1 deny deflate pred1 openfortivpn-1.23.1/etc/ppp/ip-down.local.example0000775000175000017500000000036614753334500021646 0ustar epsilonepsilon#!/usr/bin/env bash case "$PPP_IPPARAM" in openfortivpn*) rconf=/etc/resolv.conf [[ -f $rconf.openfortivpn ]] && cp -pv $rconf.openfortivpn $rconf exit 0 ;; esac 2>&1 | logger -p daemon.debug -i -t "$0" true openfortivpn-1.23.1/etc/openfortivpn/0000775000175000017500000000000014753334500017545 5ustar epsilonepsilonopenfortivpn-1.23.1/etc/openfortivpn/config.template0000664000175000017500000000022214753334500022543 0ustar epsilonepsilon### configuration file for openfortivpn, see man openfortivpn(1) ### host = vpn.example.org port = 443 username = vpnuser password = VPNpassw0rd openfortivpn-1.23.1/Makefile.am0000664000175000017500000000524414753334500016302 0ustar epsilonepsilon# http://mij.oltrelinux.com/devel/autoconf-automake/ bin_PROGRAMS = openfortivpn openfortivpn_SOURCES = src/config.c src/config.h src/hdlc.c src/hdlc.h \ src/http.c src/http.h src/io.c src/io.h \ src/http_server.c src/ipv4.c \ src/ipv4.h src/log.c src/log.h src/tunnel.c \ src/tunnel.h src/main.c src/ssl.h src/xml.c \ src/xml.h src/userinput.c src/userinput.h openfortivpn_CPPFLAGS = -DSYSCONFDIR=\"$(sysconfdir)\" \ -DPPP_PATH=\"@PPP_PATH@\" \ -DNETSTAT_PATH=\"@NETSTAT_PATH@\" \ -DRESOLVCONF_PATH=\"@RESOLVCONF_PATH@\" \ -DREVISION=\"@REVISION@\" \ $(OPENSSL_CFLAGS) $(LIBSYSTEMD_CFLAGS) openfortivpn_CFLAGS = -Wall -pedantic openfortivpn_LDADD = $(OPENSSL_LIBS) $(LIBSYSTEMD_LIBS) PATHFILES = CLEAN_LOCALS = EXTRA_DIST = \ autogen.sh \ CHANGELOG.md \ LICENSE \ LICENSE.OpenSSL \ README.md DISTCHECK_CONFIGURE_FLAGS = \ CFLAGS=-Werror \ --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) # configuration file template datadir=$(prefix)/share/@PACKAGE@ data_DATA=etc/openfortivpn/config.template EXTRA_DIST += $(data_DATA) # initial configuration file confdir=$(sysconfdir)/@PACKAGE@ etc/openfortivpn/config: $(srcdir)/etc/openfortivpn/config.template @$(MKDIR_P) etc/openfortivpn $(AM_V_GEN)$(SED) -e '/^#/n;/^\s*$$/n;s/^/# /' $(srcdir)/etc/openfortivpn/config.template >$@ install-data-hook: etc/openfortivpn/config if ! test -f $(DESTDIR)$(confdir)/config ; then \ $(MKDIR_P) $(DESTDIR)$(confdir) ; \ $(INSTALL) -m 600 etc/openfortivpn/config \ $(DESTDIR)$(confdir)/config ; \ fi clean-local-config: -rm -f $(top_builddir)/etc/openfortivpn/config CLEAN_LOCALS += clean-local-config # systemd service file PATHFILES += lib/systemd/system/openfortivpn@.service if HAVE_SYSTEMD lib/systemd/system/openfortivpn@.service: $(srcdir)/lib/systemd/system/openfortivpn@.service.in @$(MKDIR_P) lib/systemd/system $(AM_V_GEN)$(SED) -e 's|[@]BINDIR[@]|$(bindir)|g;s|[@]SYSCONFDIR[@]|$(sysconfdir)|g' $(srcdir)/lib/systemd/system/openfortivpn@.service.in >$@ systemdsystemunit_DATA = lib/systemd/system/openfortivpn@.service clean-local-systemd: -rm -f $(top_builddir)/lib/systemd/system/openfortivpn@.service CLEAN_LOCALS += clean-local-systemd endif # man page PATHFILES += doc/openfortivpn.1 dist_man_MANS = doc/openfortivpn.1 doc/openfortivpn.1: $(srcdir)/doc/openfortivpn.1.in @$(MKDIR_P) doc $(AM_V_GEN)$(SED) -e 's|[@]SYSCONFDIR[@]|$(sysconfdir)|g;s|[@]DATADIR[@]|$(datadir)|g' $(srcdir)/doc/openfortivpn.1.in >$@ clean-local-man: -rm -f $(top_builddir)/doc/openfortivpn.1 CLEAN_LOCALS += clean-local-man EXTRA_DIST += $(PATHFILES:=.in) all-local: etc/openfortivpn/config clean-local: $(CLEAN_LOCALS) openfortivpn-1.23.1/doc/0000775000175000017500000000000014753334500015006 5ustar epsilonepsilonopenfortivpn-1.23.1/doc/openfortivpn.1.in0000664000175000017500000003235314753334500020234 0ustar epsilonepsilon.TH OPENFORTIVPN 1 "May 4, 2020" "" .SH NAME openfortivpn \- Client for PPP+TLS VPN tunnel services .SH SYNOPSIS .B openfortivpn [\fI\fR[:\fI\fR]] [\fB\-u\fR \fI\fR] [\fB\-p\fR \fI\fR] [\fB\-\-cookie=\fI\fR] [\fB\-\-cookie\-on\-stdin\fR] [\fB\-\-saml\-login[=\fI\fR]] [\fB\-\-pinentry=\fI\fR] [\fB\-\-otp=\fI\fR] [\fB\-\-otp\-prompt=\fI\fR] [\fB\-\-otp\-delay=\fI\fR] [\fB\-\-no\-ftm\-push\fR] [\fB\-\-realm=\fI\fR] [\fB\-\-ifname=\fI\fR] [\fB\-\-set\-routes=\fI\fR] [\fB\-\-no\-routes\fR] [\fB\-\-set\-dns=\fI\fR] [\fB\-\-no\-dns\fR] [\fB\-\-half\-internet\-routes=\fI\fR] [\fB\-\-ca\-file=\fI\fR] [\fB\-\-user\-cert=\fI\fR] [\fB\-\-user-cert=\fIpkcs11:\fR] [\fB\-\-user\-key=\fI\fR] [\fB\-\-use\-syslog\fR] [\fB\-\-trusted\-cert=\fI\fR] [\fB\-\-insecure\-ssl\fR] [\fB\-\-cipher\-list=\fI\fR] [\fB\-\-min\-tls=\fI\fR] [\fB\-\-seclevel\-1\fR] [\fB\-\-pppd\-use\-peerdns=\fI\fR] [\fB\-\-pppd\-no\-peerdns\fR] [\fB\-\-pppd\-log=\fI\fR] [\fB\-\-pppd\-plugin=\fI\fR] [\fB\-\-pppd\-ipparam=\fI\fR] [\fB\-\-pppd\-ifname=\fI\fR] [\fB\-\-pppd\-call=\fI\fR] [\fB\-\-pppd\-accept\-remote=\fI\fR] [\fB\-\-ppp\-system=\fI\fR] [\fB\-\-use\-resolvconf=\fI\fR] [\fB\-\-persistent=\fI\fR] [\fB\-c\fR \fI\fR] [\fB\-v|\-q\fR] .br .B openfortivpn \-\-help .br .B openfortivpn \-\-version .SH DESCRIPTION .B openfortivpn connects to a VPN by setting up a tunnel to the gateway at \fI\fR:\fI\fR. .SH OPTIONS .TP \fB\-\-help\fR Show the help message and exit. .TP \fB\-\-version\fR Show version and exit. .TP \fB\-c \fI\fR, \fB\-\-config=\fI\fR Specify a custom configuration file (default: @SYSCONFDIR@/openfortivpn/config). .TP \fB\-u \fI\fR, \fB\-\-username=\fI\fR VPN account username. .TP \fB\-p \fI\fR, \fB\-\-password=\fI\fR VPN account password in plain text. For a secure alternative, use pinentry or let openfortivpn prompt for the password. .TP \fB\-\-cookie=\fI\fR A valid cookie (SVPNCOOKIE) to use in place of username and password. .TP \fB\-\-cookie\-on\-stdin\fR Read the cookie (SVPNCOOKIE) from standard input. .TP \fB\-\-saml\-login[=\fI\fR] Create a temporary web server to receive a local SAML redirect operation. To login using SAML you just have to open `/remote/saml/start?redirect=1' and follow the login steps. At the end of the login process, the page will be redirected to `http://127.0.0.1:8020/?id='. The actual URL to use for the login, including the optional realm, is printed to the terminal when the web server it started. .TP \fB\-\-pinentry=\fI\fR The pinentry program to use. Allows supplying the password in a secure manner. For example: pinentry-gnome3 on Linux, or pinentry-mac on macOS. .TP \fB\-o \fI\fR, \fB\-\-otp=\fI\fR One-Time-Password. .TP \fB\-\-otp\-prompt=\fI\fR Search for the OTP password prompt starting with the string \fI\fR. .TP \fB\-\-otp\-delay\=\fI\fR Set the amount of time to wait before sending the One-Time-Password. The delay time must be specified in seconds, where 0 means no wait (this is the default). .TP \fB\-\-no\-ftm\-push\fR Do not use FTM push if the server provides the option. The server may be configured to allow two factor authentication through a push notification to the mobile application. If this option is provided, authentication based on OTP will be used instead. .TP \fB\-\-realm=\fI\fR Connect to the specified authentication realm. Defaults to empty, which is usually what you want. .TP \fB\-\-ifname=\fI\fR Bind the connection to the specified network interface. .TP \fB\-\-set\-routes=\fI\fR, \fB\-\-no-routes\fR Set if openfortivpn should try to configure IP routes through the VPN when tunnel is up. If used multiple times, the last one takes priority. \fB\-\-no\-routes\fR is the same as \fB\-\-set-routes=\fI0\fR. .TP \fB\-\-half\-internet\-routes=\fI\fR Set if openfortivpn should add two 0.0.0.0/1 and 128.0.0.0/1 routes with higher priority instead of replacing the default route. .TP \fB\-\-set\-dns=\fI\fR, \fB\-\-no\-dns\fR Set if openfortivpn should add DNS name servers in /etc/resolv.conf when tunnel is up. Also a dns\-suffix may be received from the peer and added to /etc/resolv.conf in the turn of adding the name servers. resolvconf is instructed to do the update of the resolv.conf file if it is installed and \-\-use\-resolvconf is activated, otherwise openfortivpn prepends its changes to the existing content of the resolv.conf file. Note that there may be other mechanisms to update /etc/resolv.conf, e.g., \fB\-\-pppd\-use\-peerdns\fR in conjunction with an ip-up-script, which may require that openfortivpn is called with \fB\-\-no\-dns\fR. \fB\-\-no\-dns\fR is the same as \fB\-\-set\-dns=\fI0\fR. .TP \fB\-\-use\-resolvconf=\fI\fR Set if openfortivpn should use resolvconf to add DNS name servers in /etc/resolv.conf. If it is set to false, the builtin fallback mechanism is used even if resolvconf is available. .TP \fB\-\-ca\-file=\fI\fR Use specified PEM-encoded certificate bundle instead of system-wide store to verify the gateway certificate. .TP \fB\-\-user\-cert=\fI\fR Use specified PEM-encoded certificate if the server requires authentication with a certificate. .TP \fB\-\-user-cert=\fIpkcs11:\fR Use at least the string pkcs11: for using a smartcard. It takes the full or a partial PKCS11-URI (p11tool --list-token-urls) --user-cert = pkcs11: --user-cert = pkcs11:token=someuser --user-cert = pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=012345678;token=someuser \fBThis feature requires the OpenSSL PKCS engine! .TP \fB\-\-user\-key=\fI\fR Use specified PEM-encoded key if the server requires authentication with a certificate. .TP \fB\-\-pem-passphrase=\fI\fR Pass phrase for the PEM-encoded key. .TP \fB\-\-use\-syslog\fR Log to syslog instead of terminal. .TP \fB\-\-trusted\-cert=\fI\fR Trust a given gateway. If classical TLS certificate validation fails, the gateway certificate will be matched against this value. \fI\fR is the X509 certificate's sha256 sum. The certificate has to be encoded in DER form. This option can be used multiple times to trust several certificates. .TP \fB\-\-insecure\-ssl\fR Do not disable insecure TLS protocols/ciphers. If your server requires a specific cipher, consider using \fB\-\-cipher\-list\fR instead. .TP \fB\-\-cipher\-list=\fI\fR OpenSSL ciphers to use. If default does not work, you can try alternatives such as HIGH:!MD5:!RC4 or as suggested by the Cipher: line in the output of \fBopenssl\fP(1) (e.g. AES256-GCM-SHA384): $ openssl s_client -connect \fI\fR (default: HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4) \fBApplies to TLS v1.2 or lower only, not to be used with TLS v1.3 ciphers.\fR .TP \fB\-\-min\-tls=\fI\fR Use minimum TLS version instead of system default. Valid values are 1.0, 1.1, 1.2, 1.3. .TP \fB\-\-seclevel\-1\fR If \fB\-\-cipher-list\fR is not specified, add @SECLEVEL=1 to the list of ciphers. This lowers limits on dh key. \fBApplies to TLS v1.2 or lower only.\fR .TP \fB\-\-pppd\-use\-peerdns=\fI\fR, \fB\-\-pppd\-no\-peerdns\fR Whether to ask peer ppp server for DNS server addresses and let pppd rewrite /etc/resolv.conf. There is no mechanism to tell the dns\-suffix to pppd. If the DNS server addresses are requested, also \fB\-\-set\-dns=\fI1\fR may race with the mechanisms in pppd. \fB\-\-pppd\-no\-peerdns\fR is the same as \fB\-\-pppd\-use\-peerdns=\fI0\fR. .TP \fB\-\-pppd\-log=\fI\fR Set pppd in debug mode and save its logs into \fI\fR. .TP \fB\-\-pppd\-plugin=\fI\fR Use specified pppd plugin instead of configuring the resolver and routes directly. .TP \fB\-\-pppd\-ipparam=\fI\fR Provides an extra parameter to the ip\-up, ip\-pre\-up and ip\-down scripts. See man .BR pppd(8) for further details .TP \fB\-\-pppd\-ifname=\fI\fR Set the ppp interface name. Only if supported by pppd. Patched versions of pppd implement this option but may not be available on your platform. .TP \fB\-\-pppd\-call=\fI\fR Drop usual arguments from pppd command line and add `call ' instead. This can be useful on Debian and Ubuntu, where unprivileged users in group `dip' can invoke `pppd call ' to make pppd read and apply options from /etc/ppp/peers/ (including privileged ones). .TP \fB\-\-pppd\-accept\-remote=\fI\fR Whether to invoke pppd with `ipcp-accept-remote'. Enabling this option breaks pppd < 2.5.0 but is required by newer pppd versions. .TP \fB\-\-ppp\-system=\fI\fR Only available if compiled for ppp user space client (e.g. on FreeBSD). Connect to the specified system as defined in /etc/ppp/ppp.conf .TP \fB\-\-persistent\=\fI\fR Run the VPN persistently in an endless loop and try to reconnect forever. The reconnect interval may be specified in seconds, where 0 means no reconnect is done (this is the default). .TP \fB\-v\fR Increase verbosity. Can be used multiple times to be even more verbose. .TP \fB\-q\fR Decrease verbosity. Can be used multiple times to be even less verbose. .SH ENVIRONMENT and proxy support .B openfortivpn can be run behind an HTTP proxy that supports the HTTP connect command. It checks if one of the environment variables .B https_proxy HTTPS_PROXY all_proxy ALL_PROXY is set which are supposed to contain a string of the format .br .B http://[host]:[port] .br where .B [host] is the ip or the fully qualified host name of the proxy server .B [port] is the TCP port number where the proxy is listening for incoming connections. If one of these variables is defined, .B openfortivpn tries to first establish a TCP connection to this proxy (plain HTTP, not encrypted), and then makes a request to connect to the VPN host as given on the command line or in the configuration file. The proxy is supposed to forward any subsequent packets transparently to the VPN host, so that the TLS layer of the connection effectively is established between the client and the VPN host, and the proxy just acts as a forwarding instance on the lower level of the TCP connection. The following environment variables are set by .B openfortivpn and .BR pppd(8) or its scripts can obtain information this way: .br VPN_GATEWAY the ip of the gateway host .br and for each route three variables are set up, where an integer number is appended to the variable names, denoting the number of the current route: .br VPN_ROUTE_DEST_... the destination network of the route .br VPN_ROUTE_MASK_... the network mask for this route .br VPN_ROUTE_GATEWAY_... the gateway for the current route entry If not compiled for pppd the pppd options and features that rely on them are not available. On FreeBSD \fB\-\-ppp\-system\fR is available instead. .SH CONFIGURATION Options can be taken from a configuration file. Options passed in the command line will override those from the configuration file, though. The default configuration file is @SYSCONFDIR@/openfortivpn/config, but this can be set using the \fB\-c\fR option. An empty template for the configuration file is installed to @DATADIR@/config.template .TP A configuration file looks like: # this is a comment .br host = vpn\-gateway .br port = 443 .br username = foo .br # Password in plain text. .br # For a secure alternative, use pinentry or let openfortivpn prompt for the password. .br # password = bar .br # The pinentry program to use. Allows supplying the password in a secure manner. .br # pinentry = pinentry-mac .br # realm = some-realm .br # useful for a gui that passes a configuration file to openfortivpn .br # otp = 123456 .br # otp\-delay = 0 .br # otp\-prompt = Please .br # This would disable FTM push notification support, and use OTP instead .br # no\-ftm\-push = 1 .br user\-cert = @SYSCONFDIR@/openfortivpn/user\-cert.pem .br # user\-cert = pkcs1: # use smartcard as client certificate .br user\-key = @SYSCONFDIR@/openfortivpn/user\-key.pem .br pem\-passphrase = baz .br # the sha256 digest of the trusted host certs obtained by .br # openssl dgst -sha256 server\-cert.crt: .br trusted\-cert = certificatedigest4daa8c5fe6c... .br trusted\-cert = othercertificatedigest6631bf... .br # This would specify a ca bundle instead of system-wide store .br # ca\-file = @SYSCONFDIR@/openfortivpn/ca\-bundle.pem .br set\-dns = 0 .br use\-resolvconf = 1 .br set\-routes = 1 .br half\-internet\-routes = 0 .br pppd\-use\-peerdns = 1 .br # alternatively, use a specific pppd plugin instead .br # pppd\-plugin = /usr/lib/pppd/default/some\-plugin.so .br # for debugging pppd write logs here .br # pppd\-log = /var/log/pppd.log .br # pass ppp interface name to pppd (if supported by a patched pppd) .br # pppd\-ifname = ppp1 .br # pass an ipparam string to pppd, e.g. the device name (a similar use case) .br # pppd\-ipparam = 'device=$DEVICE' .br # instruct pppd to call a script instead of passing arguments (if pppd supports it) .br # pppd\-call = script .br # use\-syslog = 0 .br insecure\-ssl = 0 .br cipher\-list = HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4 .br persistent = 0 .br seclevel-1 = 0 .SH SEE ALSO The \fBopenfortivpn\fR home page (\fIhttps://github.com/adrienverge/openfortivpn\fR) provides a short introduction in the \fBREADME\fR file and additional information under the \fBWiki\fR tab. openfortivpn-1.23.1/autogen.sh0000775000175000017500000000060114753334500016237 0ustar epsilonepsilon#!/bin/sh set -exu if ! type autoconf >/dev/null 2>&1 ; then echo "autoconf not found - please install it" >&2 exit 1 fi if ! type automake >/dev/null 2>&1 ; then echo "automake not found - please install it" >&2 exit 1 fi if type aclocal >/dev/null 2>&1 ; then aclocal fi autoconf automake --add-missing echo "now you can run ./configure && make to build openfortivpn" openfortivpn-1.23.1/LICENSE0000664000175000017500000010606514753334500015256 0ustar epsilonepsilon GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read . Exception In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library under certain conditions as described in each individual source file, and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify file(s) with this exception, you may extend this exception to your version of the file(s), but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. openfortivpn-1.23.1/CHANGELOG.md0000664000175000017500000004031014753334500016050 0ustar epsilonepsilonOpenfortivpn Changelog ====================== Legend ------ * [+] new feature or improvement * [-] bug fix * [~] change in behavior Releases -------- This high level changelog is usually updated when a release is tagged. On the master branch there may be changes that are not (yet) described here. ### 1.23.1 * [-] fix a few coverity warnings ### 1.23.0 * [-] Support older mac0S versions that lack vdprintf() * [-] Patch certificate login error for FortiOS 7.4.4 * [-] Clear OTP after run * [+] Support SAML login authentication ### 1.22.1 * [-] do not advertise we talk compressed HTTP - we don't ### 1.22.0 * [-] make sure Homebrew packages for macOS are built with --enable-legacy-pppd * [~] do not print TLS socket options in log (revert change from 1.16.0) * [+] add option to specify SNI * [~] change most occurrences of "SSL" to "TLS" in user-visible text ### 1.21.0 * [~] fix "Peer refused to agree to his IP address" message, again * [~] deprecate option --plugin * [-] better masking of password in logs * [-] break on reading 0 from ppp pty, for non-Linux systems ### 1.20.5 * [-] revert previous fix from 1.20.4, make it optional ### 1.20.4 * [-] fix "Peer refused to agree to his IP address" message ### 1.20.3 * [~] minor change in a warning message * [+] documentation improvement * [+] minor changes in build and test files ### 1.20.2 * [-] fix regression: do attempt to apply duplicate routes, log INFO instead of WARN * [-] minor changes in log messages ### 1.20.1 * [-] fix version string in configure.ac ### 1.20.0 * [-] fix incorrect empty HDLC frame detection causing connection drops * [+] increase the inbound HTTP buffer capacity * [-] fix a few log messages * [-] fix innocuous memory leaks when parsing options * [+] prepend "SVPNCOOKIE=" to the cookie if missing * [~] drop support for openssl < 1.0.2 ### 1.19.0 * [-] fix "Peer refused to agree to our IP address" message * [+] avoid setting duplicate routes * [~] remove obsolete code that reads non-XML config from FortiOS 4 * [-] improve warning message when reading options from config file ### 1.18.0 * [+] add new options to delegate the authentication to external programs * [-] minor fixes in documentation ### 1.17.3 * [-] fix regression: spurious warning message after reading config ### 1.17.2 * [-] fix memory leak when reading user input * [-] improve calls to getsockopt() and associated debug output * [+] allow reading config from process substitution * [-] work around CodeQL false positives, improving code at the same time * [~] change type of systemd.service from simple to notify ### 1.17.1 * [-] fix regression: enable OpenSSL engines by default * [-] fix typos found by codespell * [-] fix LGTM alerts ### 1.17.0 * [-] make OpenSSL engines optional * [+] document and favor --pinentry over plain text password in configuration file * [-] fix buffer overflow and other errors in URI espcaping for --pinentry * [~] use different --pinentry hints for different hosts, usernames and realms * [-] fix memory management errors related to --user-agent option ### 1.16.0 * [+] support for user key pass phrase * [~] add a space at the end of the OTP prompt * [-] improve tunnel speed on macOS * [-] modify memory allocation in the tunnel configuration structure * [+] openfortivpn returns the PPP exit status * [+] print TLS socket options in log ### 1.15.0 * [-] fix issue sending pin codes * [+] add command line option to bind to specific interface * [+] use different hints for OTP and 2FA * [+] remove password from /proc/#/cmd * [+] extend OTP to allow FTM push * [+] add preliminary support for host checks * [-] don't accept route to the vpn gateway * [-] fix byte counter in pppd_write ### 1.14.1 * [-] fix out of bounds array access ### 1.14.0 * [+] add git commit id in debug output * [-] do not use interface ip for routing on linux * [-] avoid extra hop on interface for default route * [+] clean up, updates and improvements in the build system * [+] increase the inbound HTTP buffer capacity when needed * [+] print domain search list to output * [+] add systemd service file * [+] add systemd notification when stopping * [+] allow logging with both smartcard and username * [+] fix GCC 9 and clang warnings * [+] bump default minimal TLS version from TLSv1.0 to TLSv1.2 * [-] fix a couple coverity warnings ### 1.13.3 * [-] fix a coverity warning * [-] cross-compile: do not check resolvconf on the host system ### 1.13.2 * [-] properly build on FreeBSD, even if ppp is not installed at configure time ### 1.13.1 * [-] build in the absence of resolvconf ### 1.13.0 * [-] avoid unsupported versions of resolvconf * [~] add configure and command line option for resolvconf * [-] increase BUFSIZ * [-] reinitialize static variables with the --persistent option * [-] fix Makefile incompatibility with BSD sed * [-] fix a memory leak in ipv4_add_nameservers_to_resolv_conf ### 1.12.0 * [-] fix CVE-2020-7043: TLS Certificate CommonName NULL Byte Vulnerability * [-] fix CVE-2020-7042: use of uninitialized memory in X509_check_host * [-] fix CVE-2020-7041: incorrect use of X509_check_host (regarding return value). * [-] always hide cleartest password in -vv output * [+] add a clear warning about sensitive information in the debug output * [+] add a hint in debug output when password is read from configuration file * [-] fix segfault when connecting with empty password * [+] use resolvconf if available to update resolv.conf file * [~] replace semicolon by space in dns-suffix string ### 1.11.0 * [+] allow to connect with empty password (and with smartcard instead of username) * [~] properly handle manipulations of resolv.conf * [+] support dns-suffix feature * [-] several codacy fixes * [+] Add smartcard support with openssl-engine * [-] correctly shift masks for cidr notation on MAC * [-] one-byte fix to build with lcc compiler * [-] pass space character as %20 instead of encoding it as '+' ### 1.10.0 * [-] fix openssl 1.1.x compatibility issues * [~] Connect to old TLSv1.0 software - override new openssl defaults. * [~] suppress cleartext password in debug detail output / add new verbosity level * [~] increase speed setting for pppd * [-] work around EAGAIN issue on FreeBSD * [~] configure.ac: rt_dst: don't run tests when option is passed * [~] configure.ac: don't check file path if --with/--disable specified * [+] userinput: pass a hint to the pinentry program * [-] tunnel: make pppd default to logging to stderr * [-] tunnel: pass our stderr to the pppd slave ### 1.9.0 * [+] update of the man page, especially about the dns settings * [+] improved configure output: show detected paths for use at runtime * [-] correctly convert parsed values, fix for an issue e.g. on Raspbian * [+] make search string for the otp-prompt configurable * [+] add an option to specify a configurable delay during otp authentication * [~] make the options that control usepeerdns more consistent ### 1.8.1 * [~] Support longer passwords by allocation of a larger buffer * [-] With version 1.8.0 /etc/resolv.conf was not updated anymore in some situations. To avoid this regression the change "Rationalize DNS options" has been reverted again to restore the behavior of versions up to 1.7.1. * [-] Correctly use realm together with two factor authentication * [~] If no port is specified use standard https port similar as vendor client * [-] Fix value of Accept-Encoding request header * [-] Bugfix in url_encode for non alphanumerical characters * [-] HTML URL Encoding with uppercase characters * [-] Honor Cipher-list option * [~] Improved detection of pppd/ppp client during configure stage ### 1.8.0 * [-] On Mac OSX and FreeBSD correctly use interface name for routing * [~] On Mac OSX and FreeBSD moved netstat parsing output to higher debug level * [~] When logging traffic also show http traffic (not only tunneled traffic) * [~] Improve error message in case of login failure * [~] Require root privileges for running. They are needed at various places. Previously, just a warning was issued, but in later stage things have failed. * [-] On Mac OSX the protection of the route to the vpn gateway may have failed * [~] Invert order of ssl libraries (this may help linking on some platforms) * [+] Add FreeBSD support and redesigned the autoconf mechanism * [+] Support building with gcc 8 * [-] Prioritize command line arguments over configuration file parameters * [~] Dynamically allocate routing buffer and therefore allow larger routing table * [+] Support systemd notification upon tunnel up * [+] Support building in a separate directory * [~] Change the way to read passwords such that backspace etc. should work as usual * [~] Rationalize DNS options: pppd and openfortivpn were updating /etc/resolv.conf. Check man page and help output for the documentation of the current behavior. ### 1.7.1 * [~] Be more tolerant about white space in configuration file * [~] Make better usage of pkg-config * [~] Rework linking against OpenSSL * [-] Build again on Mac OSX where pthread_mutexattr_setrobust is not available ### 1.7.0 * [~] Correctly set up route to vpn gateway (add support for some particular situations) * [+] Support two factor authentication with configuration file (for NM-plugin) * [~] Change the ip address in the pppd call parameters by a rfc3330 test-net address * [-] Correctly report the exit status codes of pppd * [+] Add --pppd-call option * [~] Use X509_check_host instead of explicit CN match * [+] Add --persistent option * [~] Improve autoconf (check for pkg-conf before using, improve error messages, etc.) ### 1.6.0 * [-] Fix possible buffer overflow in long requests * [~] Code improvements in terms of header inclusion and some other coverity warnings * [+] Add proxy support * [~] Use the compiled-in fixed full path to pppd * [+] Support pppd ifname option * [+] Print a clear error message at runtime if pppd does not exist * [+] Print clear text error messages of pppd upon failure * [~] Existing configuration file is not overwritten anymore at installation time * [~] Increase the accepted cookie size and align the error behavior according to RFCs * [-] More gracefully handle unexpected content of resolv.conf * [~] Dynamically allocate memory for split routes and thus support larger numbers of routes ### 1.5.0 * [~] Improve error handling around the call of pppd * [+] Add half-internet-routes option * [-] realm was not recognized in the configuration file * [~] Switch from no-routes and no-dns to set-routes and set-dns option * [+] Add pppd-no-peerdns and pppd-log option * [~] Allow passing the otp via the configuration file for use with NetworkManager plugin * [-] Fix issues initializing memory and with build system * [+] Support building against Openssl 1.1 * [~] use pkg-config for configuration of openssl instead of configure option * [-] Fix string handling of the command line arguments ### 1.4.0 * [+] Allow to specify openssl location via configure option * [+] Introduce autotools build script autogen.sh * [~] Further increase possible number of split routes * [-] Fix locking issues on Mac OS X * [~] Rework signal handling: Handle SIGTERM as SIGINT and ignore SIGHUP ### 1.3.1 * [~] When calling pppd allow passing an ipparam for use in pppd ip-up/down scripts * [-] Command line option -o was not recognized before * [~] Improve Mac OSX support and parse netstat output to obtain routing information * [-] Fix segmentation fault when a gateway route is added on Mac OSX * [-] Fix buffer overflow for name server entries * [~] Increase possible number of split routes * [-] Do not remove route to vpn gateway if it has existed before connecting * [~] Load OS trusted certificate stores * [~} When setting up routes protect the route to the vpn gateway * [-] Add gateway flag to routes that may not be reachable directly at the tunnel end * [-] Correctly detect if pushed routes have a gateway * [-] Correctly mark the route to the vpn gateway as a host route * [-] Clean up routing table upon termination ### 1.3.0 * [+] Support vpn connections over an already existing ppp connection * [-] Fix for diagnostic message colors invisible on light background * [-] Bugfix for building with clang * [+] Add token-based one-time password support * [+] Add Mac OSX support * [+] Support logging via syslog * [-] Honor sysconfdir during runtime, i.e. when loading default configuration * [~] Disable insecure openssl default protocols/ciphers ### 1.2.0 * [+] Support login with client certificate, key, and ca-file specified in configuration file * [~] Use more meaningful error codes when loading config fails * [-] Correctly report errors of hostname lookup * [+] Add an option not to ask ppp peer for dns servers * [-] Fix array bounds error for trusted cert string * [-] Fix compiler warning about type cast around getchar * [-] Properly initialize memory for tunnel structure to avoid undeterministic behavior * [-] Properly initialize pointer in auth_log_in to avoid crash on http_request * [-] Fix buffer overflow in parse_config ### 1.1.4 * [-] Fix new GCC 6 strict-aliasing errors * [-] For split routes use interface if no gateway address is assigned in received route * [-] Fix rewrite of resolv.conf with non null-terminated buffer * [~] Perform two factor authentication also with zero-length tokeninfo ### 1.1.3 * [~] Support set-dns and set-routes flag from configuration file as well * [-] Properly URL-encode values sent in http requests * [+] Add support for realm authentication * [+] Add support for two factor authentication ### 1.1.2 * [-] Fix incompatible-pointer-types compiler error for x86 architectures * [~] Increase COOKIE_SIZE (again) ### 1.1.1 * [~] Update of Makefile to treat all warnings as errors * [~] Increase COOKIE_SIZE to 240 as the SVPNCOOKIE is longer in newer FortiOS versions ### 1.1.0 * [~] Rename --plugin to --pppd-plugin for consistency with other pppd-related options * [-] NUL terminate the config buffer * [-] Fix an off-by-one error when reading a quad-dotted attribute from xml * [+] Add support for client keys and certificates * [~] Extend the split VPN support with older FortiOS servers * [+] Add a config parser to handle received non-xml content * [~] Allow omitting the gateway for split routes * [~] Allow omitting DNS servers * [-] Fix a memory leak in auth_get_config * [+] Support split routes * [+] Export the configuration of routes and gateway to environment * [~] Several improvements around establishing the tunnel connection and http traffic * [+] Allow using a custom CA * [-] Turn on TLS verification, check the hostname at least for the CN * [+] Add --plugin option * [-] Fix a format string warning in do_log_packet * [~] Improved debugging output * [~] Restore default route ### 1.0.1 * [~] Better error messages in /etc/resolv.conf helpers * [~] Use better colors for warnings and error messages and only if output is a tty * [-] Fix parsing of "trusted-cert" in configuration file * [~] Add --pedantic to CFLAGS * [+] Add ability to type password interactively * [+] Verify gateway's X509 certificate * [-] Don't delete nameservers at tear down if they were here before * [~] Set /etc/openfortivpn/config not readable by other users * [+] Add ability to use a configuration file ### 1.0.0 * Start tracking openfortivpn - in this version with the following features: ``` Usage: openfortivpn : -u -p [--no-routes] [--no-dns] [--pppd-log=] [-v|-q] openfortivpn --help openfortivpn --version ``` ### Details of the changes This is a high level changelog meant to provide a rough overview about the version history of openfortivpn. Please see the Github [commit history](https://github.com/adrienverge/openfortivpn/commits) for more details of the individual changes listed here, and for a complete list of the internal code changes. More Information ---------------- * For a list of known issues please check the [issues list](https://github.com/adrienverge/openfortivpn/issues) on GitHub. * We try to avoid backwards incompatible changes, but sometimes it is not avoidable. When we are aware of compatibility issues, then we recommend to check the documentation in the above changelog. When changes turn out to break things for some specific configurations after we have tagged a new release, the issues list is the right place to report it, so that we can add a hint in the changelog for the subsequent release. openfortivpn-1.23.1/tests/0000775000175000017500000000000014753334500015403 5ustar epsilonepsilonopenfortivpn-1.23.1/tests/lint/0000775000175000017500000000000014753334500016351 5ustar epsilonepsilonopenfortivpn-1.23.1/tests/lint/eol-at-eof.sh0000775000175000017500000000047714753334500020650 0ustar epsilonepsilon#!/usr/bin/env bash # Copyright (c) 2015 Adrien Vergé rc=0 for file in "$@"; do if [ "$(sed -n '$p' "$file")" = "" ]; then echo "$file: too many newlines at end of file" >&2 rc=1 fi if [ "$(tail -c 1 "$file")" != "" ]; then echo "$file: no newline at end of file" >&2 rc=1 fi done exit $rc openfortivpn-1.23.1/tests/lint/line_length.py0000775000175000017500000000345514753334500021225 0ustar epsilonepsilon#!/usr/bin/env python3 # Copyright (c) 2015 Adrien Vergé """Enforce maximum line length in openfortivpn C source code. Example ------- Pass the list of files to check as arguments to the script:: $ line_length.py file1.c file2.c file3.c Notes ----- This script has been working so far for openfortivpn. It has not been widely tested. It may not work on any C source file. """ import sys # Guidelines say 80, let's tolerate a bit more MAX = 90 def endswithstring(line): """Detect lines from C source code ending with a string. This function has not been widely tested. Parameters ---------- line : str Line of C source code. Returns ------- bool True if line ends with string, False otherwise. """ return any(line.endswith(end) for end in ('"', '",', '");', '";', '" \\', "];")) def main(): """Check each file provided as a command line parameter. Returns ------- int 1 if a line in one of the files exceeds the expected length, else 0. """ exit_status = 0 for arg in sys.argv[1:]: with open(arg) as source_file: for i, line in enumerate(source_file, start=1): line = line.rstrip() # Lines that end with a string are exempted if endswithstring(line): continue # Replace tabs with 8 spaces line = line.replace("\t", " ") # Lines longer than MAX are reported as an error if len(line) > MAX: print( f"{arg}: {i}: line too long ({len(line)} characters)", file=sys.stderr, ) exit_status = 1 sys.exit(exit_status) if __name__ == "__main__": main() openfortivpn-1.23.1/tests/lint/run.sh0000775000175000017500000000051114753334500017511 0ustar epsilonepsilon#!/usr/bin/env bash # Copyright (c) 2015 Adrien Vergé rc=0 ./tests/lint/eol-at-eof.sh $(git ls-files | grep -v LICENSE.OpenSSL) || rc=1 ./tests/lint/line_length.py $(git ls-files '*.[ch]') || rc=1 ./tests/lint/astyle.sh $(git ls-files '*.[ch]') || rc=1 ./tests/lint/checkpatch.sh $(git ls-files '*.[ch]') || rc=1 exit $rc openfortivpn-1.23.1/tests/lint/checkpatch.sh0000775000175000017500000000122014753334500021000 0ustar epsilonepsilon#!/usr/bin/env bash # Copyright (c) 2020 Dimitri Papadopoulos # Path to checkpatch.pl script_dir=$(dirname "${BASH_SOURCE[0]}") checkpatch_path=$(realpath "${script_dir}/../ci/checkpatch/checkpatch.pl") rc=0 for file in "$@"; do tmp=$(mktemp) "$checkpatch_path" --no-tree --terse \ --ignore MACRO_ARG_UNUSED,LEADING_SPACE,SPDX_LICENSE_TAG,CODE_INDENT,NAKED_SSCANF,VOLATILE,NEW_TYPEDEFS,LONG_LINE,LONG_LINE_STRING,QUOTED_WHITESPACE_BEFORE_NEWLINE,STRCPY,STRLCPY,STRNCPY \ -f "$file" | tee "$tmp" if [ -s "$tmp" ]; then echo "error: $file does not comply with Linux kernel coding style" >&2 rc=1 fi rm "$tmp" done exit $rc openfortivpn-1.23.1/tests/lint/astyle.sh0000775000175000017500000000104414753334500020210 0ustar epsilonepsilon#!/usr/bin/env bash # Copyright (c) 2015 Adrien Vergé # Check that astyle is installed if ! command -v astyle &>/dev/null; then echo "error: astyle is not installed" >&2 exit 255 fi rc=0 for file in "$@"; do tmp=$(mktemp) astyle \ --style=linux \ --indent=tab=8 \ --pad-header \ --align-reference=type \ <"$file" >"$tmp" if ! cmp -s "$file" "$tmp"; then echo "error: $file does not comply with coding style" >&2 git --no-pager diff --no-index -U0 "$file" "$tmp" rc=1 fi rm "$tmp" done exit $rc openfortivpn-1.23.1/tests/ci/0000775000175000017500000000000014753334500015776 5ustar epsilonepsilonopenfortivpn-1.23.1/tests/ci/checkpatch/0000775000175000017500000000000014753334500020073 5ustar epsilonepsilonopenfortivpn-1.23.1/tests/ci/checkpatch/const_structs.checkpatch0000664000175000017500000000314014753334500025025 0ustar epsilonepsilonacpi_dock_ops address_space_operations backlight_ops block_device_operations bus_type clk_ops comedi_lrange component_ops ctl_table dentry_operations dev_pm_ops device_type dma_map_ops driver_info drm_connector_funcs drm_encoder_funcs drm_encoder_helper_funcs dvb_frontend_ops dvb_tuner_ops ethtool_ops extent_io_ops fb_ops file_lock_operations file_operations hv_ops hwmon_ops ib_device_ops ide_dma_ops ide_port_ops ieee80211_ops iio_buffer_setup_ops inode_operations intel_dvo_dev_ops irq_domain_ops item_operations iwl_cfg iwl_ops kernel_param_ops kgdb_arch kgdb_io kobj_type kset_uevent_ops lcd_ops lock_manager_operations machine_desc microcode_ops mlxsw_reg_info mtd_ooblayout_ops mtrr_ops nand_controller_ops neigh_ops net_device_ops nft_expr_ops nlmsvc_binding nvkm_device_chip of_device_id pci_raw_ops phy_ops pinconf_ops pinctrl_ops pinmux_ops pipe_buf_operations platform_hibernation_ops platform_suspend_ops proc_ops proto_ops pwm_ops reg_default reg_field reg_sequence regmap_access_table regmap_bus regmap_config regmap_irq regmap_irq_chip regmap_irq_sub_irq_map regmap_range regmap_range_cfg regulator_ops reset_control_ops rpc_pipe_ops rtc_class_ops sd_desc sdhci_ops seq_operations sirfsoc_padmux snd_ac97_build_ops snd_pcm_ops snd_rawmidi_ops snd_soc_component_driver snd_soc_dai_ops snd_soc_ops snd_soc_tplg_ops soc_pcmcia_socket_ops stacktrace_ops sysfs_ops tty_operations uart_ops usb_mon_operations v4l2_ctrl_ops v4l2_ioctl_ops v4l2_subdev_core_ops v4l2_subdev_internal_ops v4l2_subdev_ops v4l2_subdev_pad_ops v4l2_subdev_video_ops vb2_ops vm_operations_struct wacom_features watchdog_ops wd_ops xattr_handler openfortivpn-1.23.1/tests/ci/checkpatch/spelling.txt0000664000175000017500000010722214753334500022455 0ustar epsilonepsilon# Originally from Debian's Lintian tool. Various false positives have been # removed, and various additions have been made as they've been discovered # in the kernel source. # # License: GPLv2 # # The format of each line is: # mistake||correction # abandonning||abandoning abigious||ambiguous abitrary||arbitrary abitrate||arbitrate abnornally||abnormally abnrormal||abnormal abord||abort aboslute||absolute abov||above abreviated||abbreviated absense||absence absolut||absolute absoulte||absolute acccess||access acceess||access accelaration||acceleration accelearion||acceleration acceleratoin||acceleration accelleration||acceleration accelrometer||accelerometer accesing||accessing accesnt||accent accessable||accessible accesss||access accidentaly||accidentally accidentually||accidentally acclerated||accelerated accoding||according accomodate||accommodate accomodates||accommodates accordign||according accoring||according accout||account accquire||acquire accquired||acquired accross||across accumalate||accumulate accumalator||accumulator acessable||accessible acess||access acessing||accessing achitecture||architecture acient||ancient acitions||actions acitve||active acknowldegement||acknowledgment acknowledgement||acknowledgment ackowledge||acknowledge ackowledged||acknowledged acording||according activete||activate actived||activated actualy||actually actvie||active acumulating||accumulating acumulative||accumulative acumulator||accumulator acutally||actually adapater||adapter adderted||asserted addional||additional additionaly||additionally additonal||additional addres||address adddress||address addreses||addresses addresss||address addrress||address aditional||additional aditionally||additionally aditionaly||additionally adminstrative||administrative adress||address adresses||addresses adrresses||addresses advertisment||advertisement adviced||advised afecting||affecting againt||against agaist||against aggreataon||aggregation aggreation||aggregation ajust||adjust albumns||albums alegorical||allegorical algined||aligned algorith||algorithm algorithmical||algorithmically algoritm||algorithm algoritms||algorithms algorithmn||algorithm algorrithm||algorithm algorritm||algorithm aligment||alignment alignement||alignment allign||align alligned||aligned alllocate||allocate alloated||allocated allocatote||allocate allocatrd||allocated allocte||allocate allocted||allocated allpication||application alocate||allocate alogirhtms||algorithms alogrithm||algorithm alot||a lot alow||allow alows||allows alreay||already alredy||already altough||although alue||value ambigious||ambiguous ambigous||ambiguous amoung||among amount of times||number of times amout||amount amplifer||amplifier amplifyer||amplifier an union||a union an user||a user an userspace||a userspace an one||a one analysator||analyzer ang||and anniversery||anniversary annoucement||announcement anomolies||anomalies anomoly||anomaly anonynous||anonymous anway||anyway aplication||application apeared||appeared appearence||appearance applicaion||application appliction||application applictions||applications applys||applies appplications||applications appropiate||appropriate appropriatly||appropriately approriate||appropriate approriately||appropriately apropriate||appropriate aquainted||acquainted aquired||acquired aquisition||acquisition aquires||acquires arbitary||arbitrary architechture||architecture archtecture||architecture arguement||argument arguements||arguments arithmatic||arithmetic aritmetic||arithmetic arne't||aren't arraival||arrival artifical||artificial artillary||artillery asign||assign asser||assert assertation||assertion assertting||asserting assgined||assigned assiged||assigned assigment||assignment assigments||assignments assistent||assistant assocaited||associated assocated||associated assocating||associating assocation||association assocative||associative associcated||associated assotiated||associated asssert||assert assum||assume assumtpion||assumption asume||assume asuming||assuming asycronous||asynchronous asychronous||asynchronous asynchnous||asynchronous asynchrnous||asynchronous asynchronus||asynchronous asynchromous||asynchronous asymetric||asymmetric asymmeric||asymmetric atleast||at least atomatically||automatically atomicly||atomically atempt||attempt atrributes||attributes attachement||attachment attatch||attach attched||attached attemp||attempt attemps||attempts attemping||attempting attepmpt||attempt attnetion||attention attruibutes||attributes authentification||authentication authenicated||authenticated automaticaly||automatically automaticly||automatically automatize||automate automatized||automated automatizes||automates autonymous||autonomous auxillary||auxiliary auxilliary||auxiliary avaiable||available avaialable||available avaible||available availabe||available availabled||available availablity||availability availaible||available availale||available availavility||availability availble||available availiable||available availible||available avalable||available avaliable||available aysnc||async backgroud||background backword||backward backwords||backwards bahavior||behavior bakup||backup baloon||balloon baloons||balloons bandwith||bandwidth banlance||balance batery||battery battey||battery beacuse||because becasue||because becomming||becoming becuase||because beeing||being befor||before begining||beginning beter||better betweeen||between bianries||binaries bitmast||bitmask bitwiedh||bitwidth boardcast||broadcast borad||board boundry||boundary brievely||briefly brigde||bridge broadcase||broadcast broadcat||broadcast bufer||buffer bufferred||buffered bufferur||buffer bufufer||buffer cacluated||calculated caculate||calculate caculation||calculation cadidate||candidate cahces||caches calcluate||calculate calender||calendar calescing||coalescing calibraiton||calibration calle||called callibration||calibration callled||called callser||caller calucate||calculate calulate||calculate cancelation||cancellation cancle||cancel cant||can't cant'||can't canot||cannot cann't||can't cannnot||cannot capabiity||capability capabilites||capabilities capabilties||capabilities capabilty||capability capabitilies||capabilities capablity||capability capatibilities||capabilities capapbilities||capabilities captuer||capture caputure||capture carefuly||carefully cariage||carriage casued||caused catagory||category cehck||check challange||challenge challanges||challenges chache||cache chanell||channel changable||changeable chanined||chained channle||channel channnel||channel charachter||character charachters||characters charactor||character charater||character charaters||characters charcter||character chcek||check chck||check checksumed||checksummed checksuming||checksumming childern||children childs||children chiled||child chked||checked chnage||change chnages||changes chnange||change chnnel||channel choosen||chosen chouse||chose circumvernt||circumvent claread||cleared clared||cleared clearify||clarify closeing||closing clustred||clustered cnfiguration||configuration coexistance||coexistence colescing||coalescing collapsable||collapsible colorfull||colorful comand||command comit||commit commerical||commercial comming||coming comminucation||communication commited||committed commiting||committing committ||commit commmand||command commnunication||communication commoditiy||commodity comsume||consume comsumer||consumer comsuming||consuming comaptible||compatible compability||compatibility compaibility||compatibility comparsion||comparison compatability||compatibility compatable||compatible compatibililty||compatibility compatibiliy||compatibility compatibilty||compatibility compatiblity||compatibility competion||completion compilant||compliant compleatly||completely completition||completion completly||completely complient||compliant componnents||components compoment||component comppatible||compatible compres||compress compresion||compression compresser||compressor comression||compression comsumed||consumed comunicate||communicate comunication||communication conbination||combination concurent||concurrent conditionaly||conditionally conditon||condition condtion||condition condtional||conditional conected||connected conector||connector configed||configured configration||configuration configred||configured configuartion||configuration configuation||configuration configued||configured configuratoin||configuration configuraton||configuration configuretion||configuration configutation||configuration congiuration||configuration conider||consider conjuction||conjunction connction||connection connecetd||connected connectinos||connections connetor||connector connnection||connection connnections||connections consistancy||consistency consistant||consistent consits||consists constructred||constructed containes||contains containts||contains contaisn||contains contant||contact contence||contents contiguos||contiguous continious||continuous continous||continuous continously||continuously continueing||continuing contiuous||continuous contraints||constraints contruct||construct contol||control contoller||controller controled||controlled controler||controller controll||control contruction||construction contry||country conuntry||country convertion||conversion convertor||converter convienient||convenient convinient||convenient corected||corrected correponding||corresponding correponds||corresponds correspoding||corresponding cotrol||control cound||could couter||counter coutner||counter creationg||creating cryptocraphic||cryptographic cummulative||cumulative cunter||counter curent||current curently||currently cylic||cyclic dafault||default deactive||deactivate deafult||default deamon||daemon debouce||debounce decendant||descendant decendants||descendants decompres||decompress decsribed||described decrese||decrease decription||description detault||default dectected||detected defailt||default deferal||deferral deffered||deferred defferred||deferred definate||definite definately||definitely definiation||definition definiton||definition defintion||definition defintions||definitions defualt||default defult||default deintializing||deinitializing deintialize||deinitialize deintialized||deinitialized deivce||device delared||declared delare||declare delares||declares delaring||declaring delemiter||delimiter deley||delay delibrately||deliberately delievered||delivered demodualtor||demodulator demension||dimension dependancies||dependencies dependancy||dependency dependant||dependent dependend||dependent depreacted||deprecated depreacte||deprecate desactivate||deactivate desciptor||descriptor desciptors||descriptors descritpor||descriptor descripto||descriptor descripton||description descrition||description descritptor||descriptor desctiptor||descriptor desriptor||descriptor desriptors||descriptors desination||destination destionation||destination destoried||destroyed destory||destroy destoryed||destroyed destorys||destroys destroied||destroyed detabase||database deteced||detected detecion||detection detectt||detect detroyed||destroyed develope||develop developement||development developped||developed developpement||development developper||developer developpment||development deveolpment||development devided||divided deviece||device devision||division diable||disable diabled||disabled dicline||decline dictionnary||dictionary didnt||didn't diferent||different differrence||difference diffrent||different differenciate||differentiate diffreential||differential diffrentiate||differentiate difinition||definition digial||digital dimention||dimension dimesions||dimensions diconnected||disconnected disabed||disabled disasembler||disassembler disble||disable disgest||digest disired||desired dispalying||displaying dissable||disable dissapeared||disappeared diplay||display directon||direction direcly||directly direectly||directly diregard||disregard disassocation||disassociation disassocative||disassociative disapear||disappear disapeared||disappeared disappared||disappeared disbale||disable disbaled||disabled disble||disable disbled||disabled disconnet||disconnect discontinous||discontinuous disharge||discharge disnabled||disabled dispertion||dispersion dissapears||disappears dissconect||disconnect distiction||distinction divisable||divisible divsiors||divisors dsiabled||disabled docuentation||documentation documantation||documentation documentaion||documentation documment||document doesnt||doesn't donwload||download donwloading||downloading dorp||drop dosen||doesn downlad||download downlads||downloads droped||dropped droput||dropout druing||during dyanmic||dynamic dynmaic||dynamic eanable||enable eanble||enable easilly||easily ecspecially||especially edditable||editable editting||editing efective||effective effectivness||effectiveness efficently||efficiently ehther||ether eigth||eight elementry||elementary eletronic||electronic embeded||embedded emtpy||empty enabledi||enabled enbale||enable enble||enable enchanced||enhanced encorporating||incorporating encrupted||encrypted encrypiton||encryption encryped||encrypted encryptio||encryption endianess||endianness enpoint||endpoint enhaced||enhanced enlightnment||enlightenment enqueing||enqueuing entires||entries entites||entities entrys||entries enocded||encoded enought||enough enterily||entirely enviroiment||environment enviroment||environment environement||environment environent||environment eqivalent||equivalent equiped||equipped equivelant||equivalent equivilant||equivalent eror||error errorr||error errror||error estbalishment||establishment etsablishment||establishment etsbalishment||establishment evalute||evaluate evalutes||evaluates evalution||evaluation evaulated||evaluated excecutable||executable excceed||exceed exceded||exceeded exceds||exceeds exceeed||exceed excellant||excellent exchnage||exchange execeeded||exceeded execeeds||exceeds exeed||exceed exeeds||exceeds exeuction||execution existance||existence existant||existent exixt||exist exsits||exists exlcude||exclude exlcuding||excluding exlcusive||exclusive exlusive||exclusive exlicitly||explicitly exmaple||example expecially||especially experies||expires explicite||explicit explicity||explicitly explicitely||explicitly explict||explicit explictely||explicitly explictly||explicitly expresion||expression exprienced||experienced exprimental||experimental extened||extended exteneded||extended extensability||extensibility extention||extension extenstion||extension extracter||extractor faied||failed faield||failed faild||failed failded||failed failer||failure faill||fail failied||failed faillure||failure failue||failure failuer||failure failng||failing faireness||fairness falied||failed faliure||failure fallbck||fallback familar||familiar fatser||faster feauture||feature feautures||features fetaure||feature fetaures||features fetcing||fetching fileystem||filesystem fimrware||firmware fimware||firmware firmare||firmware firmaware||firmware firtly||firstly firware||firmware firwmare||firmware finanize||finalize findn||find finilizes||finalizes finsih||finish fliter||filter flusing||flushing folloing||following followign||following followings||following follwing||following fonud||found forcebly||forcibly forseeable||foreseeable forse||force fortan||fortran forwardig||forwarding forwared||forwarded frambuffer||framebuffer framming||framing framwork||framework frequence||frequency frequncy||frequency frequancy||frequency frome||from fronend||frontend fucntion||function fuction||function fuctions||functions fullill||fulfill funcation||function funcion||function functionallity||functionality functionaly||functionally functionnality||functionality functonality||functionality funtion||function funtions||functions furthur||further futhermore||furthermore futrue||future gatable||gateable gateing||gating gauage||gauge gaurenteed||guaranteed generiously||generously genereate||generate genereted||generated genric||generic gerenal||general geting||getting globel||global grabing||grabbing grahical||graphical grahpical||graphical granularty||granularity grapic||graphic grranted||granted grups||groups guage||gauge guarenteed||guaranteed guarentee||guarantee halfs||halves hander||handler handfull||handful hanlde||handle hanled||handled happend||happened hardare||hardware harware||hardware hardward||hardware havind||having heigth||height heirarchically||hierarchically heirarchy||hierarchy heirachy||hierarchy helpfull||helpful hearbeat||heartbeat heterogenous||heterogeneous hexdecimal||hexadecimal hybernate||hibernate hiearchy||hierarchy hierachy||hierarchy hierarchie||hierarchy homogenous||homogeneous horizental||horizontal howver||however hsould||should hypervior||hypervisor hypter||hyper idel||idle identidier||identifier iligal||illegal illigal||illegal illgal||illegal iomaped||iomapped imblance||imbalance immeadiately||immediately immedaite||immediate immedate||immediate immediatelly||immediately immediatly||immediately immidiate||immediate immutible||immutable impelentation||implementation impementated||implemented implemantation||implementation implemenation||implementation implementaiton||implementation implementated||implemented implemention||implementation implementd||implemented implemetation||implementation implemntation||implementation implentation||implementation implmentation||implementation implmenting||implementing incative||inactive incomming||incoming incompaitiblity||incompatibility incompatabilities||incompatibilities incompatable||incompatible incompatble||incompatible inconsistant||inconsistent increas||increase incremeted||incremented incrment||increment incuding||including inculde||include indendation||indentation indended||intended independant||independent independantly||independently independed||independent indiate||indicate indicat||indicate inexpect||inexpected infalte||inflate inferface||interface infinit||infinite infomation||information informatiom||information informations||information informtion||information infromation||information ingore||ignore inheritence||inheritance inital||initial initalized||initialized initalised||initialized initalise||initialize initalize||initialize initation||initiation initators||initiators initialiazation||initialization initializationg||initialization initializiation||initialization initializtion||initialization initialze||initialize initialzed||initialized initialzing||initializing initilization||initialization initilize||initialize initliaze||initialize initilized||initialized inofficial||unofficial inrerface||interface insititute||institute instace||instance instal||install instanciate||instantiate instanciated||instantiated instuments||instruments insufficent||insufficient intead||instead inteface||interface integreated||integrated integrety||integrity integrey||integrity intendet||intended intented||intended interal||internal interanl||internal interchangable||interchangeable interferring||interfering interger||integer intergrated||integrated intermittant||intermittent internel||internal interoprability||interoperability interuupt||interrupt interupt||interrupt interupts||interrupts interurpt||interrupt interrface||interface interrrupt||interrupt interrup||interrupt interrups||interrupts interruptted||interrupted interupted||interrupted intiailized||initialized intial||initial intialisation||initialisation intialised||initialised intialise||initialise intialization||initialization intialized||initialized intialize||initialize intregral||integral intrerrupt||interrupt intrrupt||interrupt intterrupt||interrupt intuative||intuitive inavlid||invalid invaid||invalid invaild||invalid invailid||invalid invald||invalid invalde||invalid invalide||invalid invalidiate||invalidate invalud||invalid invididual||individual invokation||invocation invokations||invocations ireelevant||irrelevant irrelevent||irrelevant isnt||isn't isssue||issue issus||issues iteraions||iterations iternations||iterations itertation||iteration itslef||itself ivalid||invalid jave||java jeffies||jiffies jumpimng||jumping juse||just jus||just kown||known lable||label langage||language langauage||language langauge||language langugage||language lauch||launch layed||laid legnth||length leightweight||lightweight lengh||length lenght||length lenth||length lesstiff||lesstif libaries||libraries libary||library librairies||libraries libraris||libraries licenceing||licencing limted||limited logaritmic||logarithmic loggging||logging loggin||login logile||logfile loobpack||loopback loosing||losing losted||lost maangement||management machinary||machinery maibox||mailbox maintainance||maintenance maintainence||maintenance maintan||maintain makeing||making mailformed||malformed malplaced||misplaced malplace||misplace managable||manageable managament||management managment||management mangement||management manger||manager manoeuvering||maneuvering manufaucturing||manufacturing mappping||mapping maping||mapping matchs||matches mathimatical||mathematical mathimatic||mathematic mathimatics||mathematics maxmium||maximum maximium||maximum maxium||maximum mechamism||mechanism mechanim||mechanism meetign||meeting memeory||memory memmber||member memoery||memory memroy||memory ment||meant mergable||mergeable mesage||message mesages||messages messags||messages messgaes||messages messsage||message messsages||messages metdata||metadata micropone||microphone microprocesspr||microprocessor migrateable||migratable miliseconds||milliseconds millenium||millennium milliseonds||milliseconds minimim||minimum minium||minimum minimam||minimum minimun||minimum miniumum||minimum minumum||minimum misalinged||misaligned miscelleneous||miscellaneous misformed||malformed mispelled||misspelled mispelt||misspelt mising||missing mismactch||mismatch missign||missing missmanaged||mismanaged missmatch||mismatch misssing||missing miximum||maximum mmnemonic||mnemonic mnay||many modfiy||modify modifer||modifier modul||module modulues||modules momery||memory memomry||memory monitring||monitoring monochorome||monochrome monochromo||monochrome monocrome||monochrome mopdule||module mroe||more mulitplied||multiplied muliple||multiple multipler||multiplier multidimensionnal||multidimensional multipe||multiple multple||multiple mumber||number muticast||multicast mutilcast||multicast mutiple||multiple mutli||multi nams||names navagating||navigating nead||need neccecary||necessary neccesary||necessary neccessary||necessary necesary||necessary neded||needed negaive||negative negoitation||negotiation negotation||negotiation nerver||never nescessary||necessary nessessary||necessary none existent||non-existent noticable||noticeable notication||notification notications||notifications notifcations||notifications notifed||notified notity||notify notfify||notify nubmer||number numebr||number numer||number numner||number nunber||number obtaion||obtain obusing||abusing occassionally||occasionally occationally||occasionally occurance||occurrence occurances||occurrences occurd||occurred occured||occurred occurence||occurrence occure||occurred occuring||occurring ocurrence||occurrence offser||offset offet||offset offlaod||offload offloded||offloaded offseting||offsetting oflload||offload omited||omitted omiting||omitting omitt||omit ommiting||omitting ommitted||omitted onself||oneself onthe||on the ony||only openning||opening operatione||operation opertaions||operations opportunies||opportunities optionnal||optional optmizations||optimizations orientatied||orientated orientied||oriented orignal||original originial||original orphanded||orphaned otherise||otherwise ouput||output oustanding||outstanding overaall||overall overhread||overhead overlaping||overlapping oveflow||overflow overflw||overflow overlfow||overflow overide||override overrided||overridden overriden||overridden overrrun||overrun overun||overrun overwritting||overwriting overwriten||overwritten pacakge||package pachage||package packacge||package packege||package packge||package packtes||packets pakage||package paket||packet pallette||palette paln||plan palne||plane paramameters||parameters paramaters||parameters paramater||parameter paramenters||parameters parametes||parameters parametised||parametrised paramter||parameter paramters||parameters parmaters||parameters particuarly||particularly particularily||particularly partion||partition partions||partitions partiton||partition pased||passed passin||passing pathes||paths pattrns||patterns pecularities||peculiarities peformance||performance peforming||performing peice||piece pendantic||pedantic peprocessor||preprocessor perfomance||performance perfoming||performing perfomring||performing periperal||peripheral peripherial||peripheral permissons||permissions permited||permitted peroid||period persistance||persistence persistant||persistent phoneticly||phonetically pipline||pipeline plaform||platform plalform||platform platfoem||platform platfomr||platform platfrom||platform plattform||platform pleaes||please ploting||plotting plugable||pluggable poinnter||pointer pointeur||pointer poiter||pointer posible||possible positon||position possibilites||possibilities postion||position potocol||protocol powerfull||powerful pramater||parameter preambule||preamble preamle||preamble preample||preamble preapre||prepare preceeded||preceded preceeding||preceding preceed||precede precendence||precedence precission||precision predicition||prediction preemptable||preemptible prefered||preferred prefferably||preferably prefitler||prefilter preform||perform premption||preemption prepaired||prepared prepate||prepare preperation||preparation preprare||prepare pressre||pressure presuambly||presumably previosuly||previously previsously||previously primative||primitive princliple||principle priorty||priority priting||printing privilaged||privileged privilage||privilege priviledge||privilege priviledged||privileged priviledges||privileges privleges||privileges probaly||probably probabalistic||probabilistic procceed||proceed proccesors||processors procesed||processed proces||process procesing||processing processessing||processing processess||processes processpr||processor processsed||processed processsing||processing procteted||protected prodecure||procedure progamming||programming progams||programs progess||progress programable||programmable programers||programmers programm||program programms||programs progres||progress progresss||progress prohibitted||prohibited prohibitting||prohibiting promiscous||promiscuous promps||prompts pronnounced||pronounced prononciation||pronunciation pronouce||pronounce pronunce||pronounce propery||property propigate||propagate propigation||propagation propogation||propagation propogate||propagate prosess||process protable||portable protcol||protocol protecion||protection protedcted||protected protocoll||protocol promixity||proximity psudo||pseudo psuedo||pseudo psychadelic||psychedelic purgable||purgeable pwoer||power queing||queuing quering||querying querrying||querying queus||queues randomally||randomly raoming||roaming readyness||readiness reasearcher||researcher reasearchers||researchers reasearch||research recalcualte||recalculate receieve||receive recepient||recipient recevied||received receving||receiving recievd||received recieved||received recieve||receive reciever||receiver recieves||receives recieving||receiving recogniced||recognised recognizeable||recognizable recompte||recompute recommanded||recommended recyle||recycle redect||reject redircet||redirect redirectrion||redirection redundacy||redundancy reename||rename refcounf||refcount refence||reference refered||referred referencce||reference referenace||reference refererence||reference refering||referring refernces||references refernnce||reference refrence||reference regiser||register registed||registered registerd||registered registeration||registration registeresd||registered registerred||registered registes||registers registraration||registration regsiter||register regster||register regualar||regular reguator||regulator regulamentations||regulations reigstration||registration releated||related relevent||relevant reloade||reload remoote||remote remore||remote removeable||removable repective||respective repectively||respectively replacable||replaceable replacments||replacements replys||replies reponse||response representaion||representation repsonse||response reqested||requested reqeust||request reqister||register requed||requeued requestied||requested requiere||require requieres||requires requirment||requirement requred||required requried||required requst||request requsted||requested reregisteration||reregistration reseting||resetting reseved||reserved reseverd||reserved resizeable||resizable resonable||reasonable resotre||restore resouce||resource resouces||resources resoures||resources responce||response resrouce||resource ressizes||resizes ressource||resource ressources||resources restesting||retesting resumbmitting||resubmitting retransmited||retransmitted retreived||retrieved retreive||retrieve retreiving||retrieving retrive||retrieve retrived||retrieved retrun||return retun||return retuned||returned reudce||reduce reuest||request reuqest||request reutnred||returned revsion||revision rewritting||rewriting rmeoved||removed rmeove||remove rmeoves||removes rountine||routine routins||routines rquest||request runing||running runned||ran runnnig||running runnning||running runtine||runtime sacrifying||sacrificing safly||safely safty||safety satify||satisfy satisifed||satisfied savable||saveable scaleing||scaling scaned||scanned scaning||scanning scarch||search schdule||schedule seach||search searchs||searches secion||section secquence||sequence secund||second segement||segment seleted||selected semaphone||semaphore senario||scenario senarios||scenarios sentivite||sensitive separatly||separately sepcify||specify seperated||separated seperately||separately seperate||separate seperatly||separately seperator||separator sepperate||separate seqeunce||sequence seqeuncer||sequencer seqeuencer||sequencer sequece||sequence sequemce||sequence sequencial||sequential serivce||service serveral||several servive||service sesion||session setts||sets settting||setting shapshot||snapshot shoft||shift shotdown||shutdown shoud||should shouldnt||shouldn't shoule||should shrinked||shrunk siginificantly||significantly signabl||signal significanly||significantly similary||similarly similiar||similar simlar||similar simliar||similar simpified||simplified simultaneusly||simultaneously simultanous||simultaneous singaled||signaled singal||signal singed||signed slect||select sleeped||slept sliped||slipped softwade||software softwares||software soley||solely soluation||solution souce||source speach||speech specfic||specific specfication||specification specfield||specified speciefied||specified specifc||specific specifed||specified specificatin||specification specificaton||specification specificed||specified specifing||specifying specifiy||specify specifiying||specifying speficied||specified speicify||specify speling||spelling spinlcok||spinlock spinock||spinlock splitted||split spreaded||spread spurrious||spurious sructure||structure stablilization||stabilization staically||statically staion||station standardss||standards standartization||standardization standart||standard standy||standby stardard||standard staticly||statically statisitcs||statistics statuss||status stoped||stopped stoping||stopping stoppped||stopped straming||streaming struc||struct structres||structures stuct||struct strucuture||structure stucture||structure sturcture||structure subdirectoires||subdirectories suble||subtle substract||subtract submited||submitted submition||submission succeded||succeeded suceed||succeed succesfuly||successfully succesfully||successfully succesful||successful successed||succeeded successfull||successful successfuly||successfully sucessfully||successfully sucessful||successful sucess||success superflous||superfluous superseeded||superseded suplied||supplied suported||supported suport||support supportet||supported suppored||supported supporing||supporting supportin||supporting suppoted||supported suppported||supported suppport||support supprot||support supress||suppress surpressed||suppressed surpresses||suppresses susbsystem||subsystem suspeneded||suspended suspsend||suspend suspicously||suspiciously swaping||swapping switchs||switches swith||switch swithable||switchable swithc||switch swithced||switched swithcing||switching swithed||switched swithing||switching swtich||switch syfs||sysfs symetric||symmetric synax||syntax synchonized||synchronized sychronization||synchronization sychronously||synchronously synchronuously||synchronously syncronize||synchronize syncronized||synchronized syncronizing||synchronizing syncronus||synchronous syste||system sytem||system sythesis||synthesis tagert||target taht||that tained||tainted tarffic||traffic tansmit||transmit targetted||targeted targetting||targeting taskelt||tasklet teh||the temeprature||temperature temorary||temporary temproarily||temporarily temperture||temperature theads||threads therfore||therefore thier||their threds||threads threee||three threshhold||threshold thresold||threshold throtting||throttling throught||through tansition||transition trackling||tracking troughput||throughput trys||tries thses||these tiggers||triggers tiggered||triggered tiggerring||triggering tipically||typically timeing||timing timming||timing timout||timeout tmis||this tolarance||tolerance toogle||toggle torerable||tolerable torlence||tolerance traget||target traking||tracking tramsmitted||transmitted tramsmit||transmit tranasction||transaction tranceiver||transceiver tranfer||transfer tranmission||transmission tranport||transport transcevier||transceiver transciever||transceiver transferd||transferred transfered||transferred transfering||transferring transision||transition transistioned||transitioned transmittd||transmitted transormed||transformed trasaction||transaction trasfer||transfer trasmission||transmission trasmitter||transmitter treshold||threshold trigged||triggered triggerd||triggered trigerred||triggered trigerring||triggering trun||turn tunning||tuning ture||true tyep||type udpate||update updtes||updates uesd||used unknwon||unknown uknown||unknown usccess||success uncommited||uncommitted uncompatible||incompatible uncomressed||uncompressed unconditionaly||unconditionally undeflow||underflow undelying||underlying underun||underrun unecessary||unnecessary unexecpted||unexpected unexepected||unexpected unexpcted||unexpected unexpectd||unexpected unexpeted||unexpected unexpexted||unexpected unfortunatelly||unfortunately unifiy||unify uniterrupted||uninterrupted uninterruptable||uninterruptible unintialized||uninitialized unitialized||uninitialized unkmown||unknown unknonw||unknown unknouwn||unknown unknow||unknown unkown||unknown unamed||unnamed uneeded||unneeded unneded||unneeded unneccecary||unnecessary unneccesary||unnecessary unneccessary||unnecessary unnecesary||unnecessary unneedingly||unnecessarily unnsupported||unsupported unuspported||unsupported unmached||unmatched unprecise||imprecise unpriviledged||unprivileged unpriviliged||unprivileged unregester||unregister unresgister||unregister unrgesiter||unregister unsinged||unsigned unstabel||unstable unsolicted||unsolicited unsolicitied||unsolicited unsuccessfull||unsuccessful unsuported||unsupported untill||until ununsed||unused unuseful||useless unvalid||invalid upate||update upsupported||unsupported upto||up to useable||usable usefule||useful usefull||useful usege||usage usera||users usualy||usually usupported||unsupported utilites||utilities utillities||utilities utilties||utilities utiltity||utility utitity||utility utitlty||utility vaid||valid vaild||valid validationg||validating valide||valid variantions||variations varible||variable varient||variant vaule||value verbse||verbose veify||verify verfication||verification veriosn||version versoin||version verisons||versions verison||version veritical||vertical verson||version vicefersa||vice-versa virtal||virtual virtaul||virtual virtiual||virtual visiters||visitors vitual||virtual vunerable||vulnerable wakeus||wakeups was't||wasn't wathdog||watchdog wating||waiting wiat||wait wether||whether whataver||whatever whcih||which whenver||whenever wheter||whether whe||when wierd||weird wihout||without wiil||will wirte||write withing||within wnat||want wont||won't workarould||workaround writeing||writing writting||writing wtih||with zombe||zombie zomebie||zombie openfortivpn-1.23.1/tests/ci/checkpatch/checkpatch.pl0000775000175000017500000072216314753334500022543 0ustar epsilonepsilon#!/usr/bin/env perl # SPDX-License-Identifier: GPL-2.0 # # (c) 2001, Dave Jones. (the file handling bit) # (c) 2005, Joel Schopp (the ugly bit) # (c) 2007,2008, Andy Whitcroft (new conditions, test suite) # (c) 2008-2010 Andy Whitcroft # (c) 2010-2018 Joe Perches use strict; use warnings; use POSIX; use File::Basename; use Cwd 'abs_path'; use Term::ANSIColor qw(:constants); use Encode qw(decode encode); my $P = $0; my $D = dirname(abs_path($P)); my $V = '0.32'; use Getopt::Long qw(:config no_auto_abbrev); my $quiet = 0; my $verbose = 0; my %verbose_messages = (); my %verbose_emitted = (); my $tree = 1; my $chk_signoff = 1; my $chk_fixes_tag = 1; my $chk_patch = 1; my $tst_only; my $emacs = 0; my $terse = 0; my $showfile = 0; my $file = 0; my $git = 0; my %git_commits = (); my $check = 0; my $check_orig = 0; my $summary = 1; my $mailback = 0; my $summary_file = 0; my $show_types = 0; my $list_types = 0; my $fix = 0; my $fix_inplace = 0; my $root; my $gitroot = $ENV{'GIT_DIR'}; $gitroot = ".git" if !defined($gitroot); my %debug; my %camelcase = (); my %use_type = (); my @use = (); my %ignore_type = (); my @ignore = (); my $help = 0; my $configuration_file = ".checkpatch.conf"; my $max_line_length = 100; my $ignore_perl_version = 0; my $minimum_perl_version = 5.10.0; my $min_conf_desc_length = 4; my $spelling_file = "$D/spelling.txt"; my $codespell = 0; my $codespellfile = "/usr/share/codespell/dictionary.txt"; my $user_codespellfile = ""; my $conststructsfile = "$D/const_structs.checkpatch"; my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; my $typedefsfile; my $color = "auto"; my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE # git output parsing needs US English output, so first set backtick child process LANGUAGE my $git_command ='export LANGUAGE=en_US.UTF-8; git'; my $tabsize = 8; my ${CONFIG_} = "CONFIG_"; my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h sub help { my ($exitcode) = @_; print << "EOM"; Usage: $P [OPTION]... [FILE]... Version: $V Options: -q, --quiet quiet -v, --verbose verbose mode --no-tree run without a kernel tree --no-signoff do not check for 'Signed-off-by' line --no-fixes-tag do not check for 'Fixes:' tag --patch treat FILE as patchfile (default) --emacs emacs compile window format --terse one line per report --showfile emit diffed file position, not input file position -g, --git treat FILE as a single commit or git revision range single git commit with: ^ ~n multiple git commits with: .. ... - git merges are ignored -f, --file treat FILE as regular source file --subjective, --strict enable more subjective tests --list-types list the possible message types --types TYPE(,TYPE2...) show only these comma separated message types --ignore TYPE(,TYPE2...) ignore various comma separated message types --show-types show the specific message type in the output --max-line-length=n set the maximum line length, (default $max_line_length) if exceeded, warn on patches requires --strict for use with --file --min-conf-desc-length=n set the min description length, if shorter, warn --tab-size=n set the number of spaces for tab (default $tabsize) --root=PATH PATH to the kernel tree root --no-summary suppress the per-file summary --mailback only produce a report in case of warnings/errors --summary-file include the filename in summary --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of 'values', 'possible', 'type', and 'attr' (default is all off) --test-only=WORD report only warnings/errors containing WORD literally --fix EXPERIMENTAL - may create horrible results If correctable single-line errors exist, create ".EXPERIMENTAL-checkpatch-fixes" with potential errors corrected to the preferred checkpatch style --fix-inplace EXPERIMENTAL - may create horrible results Is the same as --fix, but overwrites the input file. It's your fault if there's no backup or git --ignore-perl-version override checking of perl version. expect runtime errors. --codespell Use the codespell dictionary for spelling/typos (default:$codespellfile) --codespellfile Use this codespell dictionary --typedefsfile Read additional types from this file --color[=WHEN] Use colors 'always', 'never', or only when output is a terminal ('auto'). Default is 'auto'. --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default ${CONFIG_}) -h, --help, --version display this help and exit When FILE is - read standard input. EOM exit($exitcode); } sub uniq { my %seen; return grep { !$seen{$_}++ } @_; } sub list_types { my ($exitcode) = @_; my $count = 0; local $/ = undef; open(my $script, '<', abs_path($P)) or die "$P: Can't read '$P' $!\n"; my $text = <$script>; close($script); my %types = (); # Also catch when type or level is passed through a variable while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { if (defined($1)) { if (exists($types{$2})) { $types{$2} .= ",$1" if ($types{$2} ne $1); } else { $types{$2} = $1; } } else { $types{$2} = "UNDETERMINED"; } } print("#\tMessage type\n\n"); if ($color) { print(" ( Color coding: "); print(RED . "ERROR" . RESET); print(" | "); print(YELLOW . "WARNING" . RESET); print(" | "); print(GREEN . "CHECK" . RESET); print(" | "); print("Multiple levels / Undetermined"); print(" )\n\n"); } foreach my $type (sort keys %types) { my $orig_type = $type; if ($color) { my $level = $types{$type}; if ($level eq "ERROR") { $type = RED . $type . RESET; } elsif ($level eq "WARN") { $type = YELLOW . $type . RESET; } elsif ($level eq "CHK") { $type = GREEN . $type . RESET; } } print(++$count . "\t" . $type . "\n"); if ($verbose && exists($verbose_messages{$orig_type})) { my $message = $verbose_messages{$orig_type}; $message =~ s/\n/\n\t/g; print("\t" . $message . "\n\n"); } } exit($exitcode); } my $conf = which_conf($configuration_file); if (-f $conf) { my @conf_args; open(my $conffile, '<', "$conf") or warn "$P: Can't find a readable $configuration_file file $!\n"; while (<$conffile>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; $line =~ s/\s+/ /g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); my @words = split(" ", $line); foreach my $word (@words) { last if ($word =~ m/^#/); push (@conf_args, $word); } } close($conffile); unshift(@ARGV, @conf_args) if @conf_args; } sub load_docs { open(my $docs, '<', "$docsfile") or warn "$P: Can't read the documentation file $docsfile $!\n"; my $type = ''; my $desc = ''; my $in_desc = 0; while (<$docs>) { chomp; my $line = $_; $line =~ s/\s+$//; if ($line =~ /^\s*\*\*(.+)\*\*$/) { if ($desc ne '') { $verbose_messages{$type} = trim($desc); } $type = $1; $desc = ''; $in_desc = 1; } elsif ($in_desc) { if ($line =~ /^(?:\s{4,}|$)/) { $line =~ s/^\s{4}//; $desc .= $line; $desc .= "\n"; } else { $verbose_messages{$type} = trim($desc); $type = ''; $desc = ''; $in_desc = 0; } } } if ($desc ne '') { $verbose_messages{$type} = trim($desc); } close($docs); } # Perl's Getopt::Long allows options to take optional arguments after a space. # Prevent --color by itself from consuming other arguments foreach (@ARGV) { if ($_ eq "--color" || $_ eq "-color") { $_ = "--color=$color"; } } GetOptions( 'q|quiet+' => \$quiet, 'v|verbose!' => \$verbose, 'tree!' => \$tree, 'signoff!' => \$chk_signoff, 'fixes-tag!' => \$chk_fixes_tag, 'patch!' => \$chk_patch, 'emacs!' => \$emacs, 'terse!' => \$terse, 'showfile!' => \$showfile, 'f|file!' => \$file, 'g|git!' => \$git, 'subjective!' => \$check, 'strict!' => \$check, 'ignore=s' => \@ignore, 'types=s' => \@use, 'show-types!' => \$show_types, 'list-types!' => \$list_types, 'max-line-length=i' => \$max_line_length, 'min-conf-desc-length=i' => \$min_conf_desc_length, 'tab-size=i' => \$tabsize, 'root=s' => \$root, 'summary!' => \$summary, 'mailback!' => \$mailback, 'summary-file!' => \$summary_file, 'fix!' => \$fix, 'fix-inplace!' => \$fix_inplace, 'ignore-perl-version!' => \$ignore_perl_version, 'debug=s' => \%debug, 'test-only=s' => \$tst_only, 'codespell!' => \$codespell, 'codespellfile=s' => \$user_codespellfile, 'typedefsfile=s' => \$typedefsfile, 'color=s' => \$color, 'no-color' => \$color, #keep old behaviors of -nocolor 'nocolor' => \$color, #keep old behaviors of -nocolor 'kconfig-prefix=s' => \${CONFIG_}, 'h|help' => \$help, 'version' => \$help ) or $help = 2; if ($user_codespellfile) { # Use the user provided codespell file unconditionally $codespellfile = $user_codespellfile; } elsif (!(-f $codespellfile)) { # If /usr/share/codespell/dictionary.txt is not present, try to find it # under codespell's install directory: /data/dictionary.txt if (($codespell || $help) && which("python3") ne "") { my $python_codespell_dict = << "EOF"; import os.path as op import codespell_lib codespell_dir = op.dirname(codespell_lib.__file__) codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') print(codespell_file, end='') EOF my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`; $codespellfile = $codespell_dict if (-f $codespell_dict); } } # $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 # $help is 2 if invalid option is passed - exitcode: 1 help($help - 1) if ($help); die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); if ($color =~ /^[01]$/) { $color = !$color; } elsif ($color =~ /^always$/i) { $color = 1; } elsif ($color =~ /^never$/i) { $color = 0; } elsif ($color =~ /^auto$/i) { $color = (-t STDOUT); } else { die "$P: Invalid color mode: $color\n"; } load_docs() if ($verbose); list_types(0) if ($list_types); $fix = 1 if ($fix_inplace); $check_orig = $check; my $exit = 0; my $perl_version_ok = 1; if ($^V && $^V lt $minimum_perl_version) { $perl_version_ok = 0; printf "$P: requires at least perl version %vd\n", $minimum_perl_version; exit(1) if (!$ignore_perl_version); } #if no filenames are given, push '-' to read patch from stdin if ($#ARGV < 0) { push(@ARGV, '-'); } # skip TAB size 1 to avoid additional checks on $tabsize - 1 die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); sub hash_save_array_words { my ($hashRef, $arrayRef) = @_; my @array = split(/,/, join(',', @$arrayRef)); foreach my $word (@array) { $word =~ s/\s*\n?$//g; $word =~ s/^\s*//g; $word =~ s/\s+/ /g; $word =~ tr/[a-z]/[A-Z]/; next if ($word =~ m/^\s*#/); next if ($word =~ m/^\s*$/); $hashRef->{$word}++; } } sub hash_show_words { my ($hashRef, $prefix) = @_; if (keys %$hashRef) { print "\nNOTE: $prefix message types:"; foreach my $word (sort keys %$hashRef) { print " $word"; } print "\n"; } } hash_save_array_words(\%ignore_type, \@ignore); hash_save_array_words(\%use_type, \@use); my $dbg_values = 0; my $dbg_possible = 0; my $dbg_type = 0; my $dbg_attr = 0; for my $key (keys %debug) { ## no critic eval "\${dbg_$key} = '$debug{$key}';"; die "$@" if ($@); } my $rpt_cleaners = 0; if ($terse) { $emacs = 1; $quiet++; } if ($tree) { if (defined $root) { if (!top_of_kernel_tree($root)) { die "$P: $root: --root does not point at a valid tree\n"; } } else { if (top_of_kernel_tree('.')) { $root = '.'; } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && top_of_kernel_tree($1)) { $root = $1; } } if (!defined $root) { print "Must be run from the top-level dir. of a kernel tree\n"; exit(2); } } my $emitted_corrupt = 0; our $Ident = qr{ [A-Za-z_][A-Za-z\d_]* (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* }x; our $Storage = qr{extern|static|asmlinkage}; our $Sparse = qr{ __user| __kernel| __force| __iomem| __must_check| __kprobes| __ref| __refconst| __refdata| __rcu| __private }x; our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; # Notes to $Attribute: # We need \b after 'init' otherwise 'initconst' will cause a false positive in a check our $Attribute = qr{ const| volatile| __percpu| __nocast| __safe| __bitwise| __packed__| __packed2__| __naked| __maybe_unused| __always_unused| __noreturn| __used| __cold| __pure| __noclone| __deprecated| __read_mostly| __ro_after_init| __kprobes| $InitAttribute| __aligned\s*\(.*\)| ____cacheline_aligned| ____cacheline_aligned_in_smp| ____cacheline_internodealigned_in_smp| __weak| __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) }x; our $Modifier; our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; our $Lval = qr{$Ident(?:$Member)*}; our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; our $Binary = qr{(?i)0b[01]+$Int_type?}; our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; our $Int = qr{[0-9]+$Int_type?}; our $Octal = qr{0[0-7]+$Int_type?}; our $String = qr{(?:\b[Lu])?"[X\t]*"}; our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; our $Float = qr{$Float_hex|$Float_dec|$Float_int}; our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; our $Compare = qr{<=|>=|==|!=|<|(?}; our $Arithmetic = qr{\+|-|\*|\/|%}; our $Operators = qr{ <=|>=|==|!=| =>|->|<<|>>|<|>|!|~| &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic }x; our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; our $BasicType; our $NonptrType; our $NonptrTypeMisordered; our $NonptrTypeWithAttr; our $Type; our $TypeMisordered; our $Declare; our $DeclareMisordered; our $NON_ASCII_UTF8 = qr{ [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 }x; our $UTF8 = qr{ [\x09\x0A\x0D\x20-\x7E] # ASCII | $NON_ASCII_UTF8 }x; our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; our $typeOtherOSTypedefs = qr{(?x: u_(?:char|short|int|long) | # bsd u(?:nchar|short|int|long) # sysv )}; our $typeKernelTypedefs = qr{(?x: (?:__)?(?:u|s|be|le)(?:8|16|32|64)| atomic_t )}; our $typeStdioTypedefs = qr{(?x: FILE )}; our $typeTypedefs = qr{(?x: $typeC99Typedefs\b| $typeOtherOSTypedefs\b| $typeKernelTypedefs\b| $typeStdioTypedefs\b )}; our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; our $logFunctions = qr{(?x: printk(?:_ratelimited|_once|_deferred_once|_deferred|)| (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| TP_printk| WARN(?:_RATELIMIT|_ONCE|)| panic| MODULE_[A-Z_]+| seq_vprintf|seq_printf|seq_puts )}; our $allocFunctions = qr{(?x: (?:(?:devm_)? (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | kstrdup(?:_const)? | kmemdup(?:_nul)?) | (?:\w+)?alloc_skb(?:_ip_align)? | # dev_alloc_skb/netdev_alloc_skb, et al dma_alloc_coherent )}; our $signature_tags = qr{(?xi: Signed-off-by:| Co-developed-by:| Acked-by:| Tested-by:| Reviewed-by:| Reported-by:| Suggested-by:| To:| Cc: )}; our @link_tags = qw(Link Closes); #Create a search and print patterns for all these strings to be used directly below our $link_tags_search = ""; our $link_tags_print = ""; foreach my $entry (@link_tags) { if ($link_tags_search ne "") { $link_tags_search .= '|'; $link_tags_print .= ' or '; } $entry .= ':'; $link_tags_search .= $entry; $link_tags_print .= "'$entry'"; } $link_tags_search = "(?:${link_tags_search})"; our $tracing_logging_tags = qr{(?xi: [=-]*> | <[=-]* | \[ | \] | start | called | entered | entry | enter | in | inside | here | begin | exit | end | done | leave | completed | out | return | [\.\!:\s]* )}; sub edit_distance_min { my (@arr) = @_; my $len = scalar @arr; if ((scalar @arr) < 1) { # if underflow, return return; } my $min = $arr[0]; for my $i (0 .. ($len-1)) { if ($arr[$i] < $min) { $min = $arr[$i]; } } return $min; } sub get_edit_distance { my ($str1, $str2) = @_; $str1 = lc($str1); $str2 = lc($str2); $str1 =~ s/-//g; $str2 =~ s/-//g; my $len1 = length($str1); my $len2 = length($str2); # two dimensional array storing minimum edit distance my @distance; for my $i (0 .. $len1) { for my $j (0 .. $len2) { if ($i == 0) { $distance[$i][$j] = $j; } elsif ($j == 0) { $distance[$i][$j] = $i; } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { $distance[$i][$j] = $distance[$i - 1][$j - 1]; } else { my $dist1 = $distance[$i][$j - 1]; #insert distance my $dist2 = $distance[$i - 1][$j]; # remove my $dist3 = $distance[$i - 1][$j - 1]; #replace $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); } } } return $distance[$len1][$len2]; } sub find_standard_signature { my ($sign_off) = @_; my @standard_signature_tags = ( 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' ); foreach my $signature (@standard_signature_tags) { return $signature if (get_edit_distance($sign_off, $signature) <= 2); } return ""; } our $obsolete_archives = qr{(?xi: \Qfreedesktop.org/archives/dri-devel\E | \Qlists.infradead.org\E | \Qlkml.org\E | \Qmail-archive.com\E | \Qmailman.alsa-project.org/pipermail\E | \Qmarc.info\E | \Qozlabs.org/pipermail\E | \Qspinics.net\E )}; our @typeListMisordered = ( qr{char\s+(?:un)?signed}, qr{int\s+(?:(?:un)?signed\s+)?short\s}, qr{int\s+short(?:\s+(?:un)?signed)}, qr{short\s+int(?:\s+(?:un)?signed)}, qr{(?:un)?signed\s+int\s+short}, qr{short\s+(?:un)?signed}, qr{long\s+int\s+(?:un)?signed}, qr{int\s+long\s+(?:un)?signed}, qr{long\s+(?:un)?signed\s+int}, qr{int\s+(?:un)?signed\s+long}, qr{int\s+(?:un)?signed}, qr{int\s+long\s+long\s+(?:un)?signed}, qr{long\s+long\s+int\s+(?:un)?signed}, qr{long\s+long\s+(?:un)?signed\s+int}, qr{long\s+long\s+(?:un)?signed}, qr{long\s+(?:un)?signed}, ); our @typeList = ( qr{void}, qr{(?:(?:un)?signed\s+)?char}, qr{(?:(?:un)?signed\s+)?short\s+int}, qr{(?:(?:un)?signed\s+)?short}, qr{(?:(?:un)?signed\s+)?int}, qr{(?:(?:un)?signed\s+)?long\s+int}, qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, qr{(?:(?:un)?signed\s+)?long\s+long}, qr{(?:(?:un)?signed\s+)?long}, qr{(?:un)?signed}, qr{float}, qr{double}, qr{bool}, qr{struct\s+$Ident}, qr{union\s+$Ident}, qr{enum\s+$Ident}, qr{${Ident}_t}, qr{${Ident}_handler}, qr{${Ident}_handler_fn}, @typeListMisordered, ); our $C90_int_types = qr{(?x: long\s+long\s+int\s+(?:un)?signed| long\s+long\s+(?:un)?signed\s+int| long\s+long\s+(?:un)?signed| (?:(?:un)?signed\s+)?long\s+long\s+int| (?:(?:un)?signed\s+)?long\s+long| int\s+long\s+long\s+(?:un)?signed| int\s+(?:(?:un)?signed\s+)?long\s+long| long\s+int\s+(?:un)?signed| long\s+(?:un)?signed\s+int| long\s+(?:un)?signed| (?:(?:un)?signed\s+)?long\s+int| (?:(?:un)?signed\s+)?long| int\s+long\s+(?:un)?signed| int\s+(?:(?:un)?signed\s+)?long| int\s+(?:un)?signed| (?:(?:un)?signed\s+)?int )}; our @typeListFile = (); our @typeListWithAttr = ( @typeList, qr{struct\s+$InitAttribute\s+$Ident}, qr{union\s+$InitAttribute\s+$Ident}, ); our @modifierList = ( qr{fastcall}, ); our @modifierListFile = (); our @mode_permission_funcs = ( ["module_param", 3], ["module_param_(?:array|named|string)", 4], ["module_param_array_named", 5], ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], ["proc_create(?:_data|)", 2], ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], ["IIO_DEV_ATTR_[A-Z_]+", 1], ["SENSOR_(?:DEVICE_|)ATTR_2", 2], ["SENSOR_TEMPLATE(?:_2|)", 3], ["__ATTR", 2], ); my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; #Create a search pattern for all these functions to speed up a loop below our $mode_perms_search = ""; foreach my $entry (@mode_permission_funcs) { $mode_perms_search .= '|' if ($mode_perms_search ne ""); $mode_perms_search .= $entry->[0]; } $mode_perms_search = "(?:${mode_perms_search})"; our %deprecated_apis = ( "kmap" => "kmap_local_page", "kunmap" => "kunmap_local", "kmap_atomic" => "kmap_local_page", "kunmap_atomic" => "kunmap_local", ); #Create a search pattern for all these strings to speed up a loop below our $deprecated_apis_search = ""; foreach my $entry (keys %deprecated_apis) { $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); $deprecated_apis_search .= $entry; } $deprecated_apis_search = "(?:${deprecated_apis_search})"; our $mode_perms_world_writable = qr{ S_IWUGO | S_IWOTH | S_IRWXUGO | S_IALLUGO | 0[0-7][0-7][2367] }x; our %mode_permission_string_types = ( "S_IRWXU" => 0700, "S_IRUSR" => 0400, "S_IWUSR" => 0200, "S_IXUSR" => 0100, "S_IRWXG" => 0070, "S_IRGRP" => 0040, "S_IWGRP" => 0020, "S_IXGRP" => 0010, "S_IRWXO" => 0007, "S_IROTH" => 0004, "S_IWOTH" => 0002, "S_IXOTH" => 0001, "S_IRWXUGO" => 0777, "S_IRUGO" => 0444, "S_IWUGO" => 0222, "S_IXUGO" => 0111, ); #Create a search pattern for all these strings to speed up a loop below our $mode_perms_string_search = ""; foreach my $entry (keys %mode_permission_string_types) { $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); $mode_perms_string_search .= $entry; } our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; our $multi_mode_perms_string_search = qr{ ${single_mode_perms_string_search} (?:\s*\|\s*${single_mode_perms_string_search})* }x; sub perms_to_octal { my ($string) = @_; return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); my $val = ""; my $oval = ""; my $to = 0; my $curpos = 0; my $lastpos = 0; while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { $curpos = pos($string); my $match = $2; my $omatch = $1; last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); $lastpos = $curpos; $to |= $mode_permission_string_types{$match}; $val .= '\s*\|\s*' if ($val ne ""); $val .= $match; $oval .= $omatch; } $oval =~ s/^\s*\|\s*//; $oval =~ s/\s*\|\s*$//; return sprintf("%04o", $to); } our $allowed_asm_includes = qr{(?x: irq| memory| time| reboot )}; # memory.h: ARM has a custom one # Load common spelling mistakes and build regular expression list. my $misspellings; my %spelling_fix; if (open(my $spelling, '<', $spelling_file)) { while (<$spelling>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); my ($suspect, $fix) = split(/\|\|/, $line); $spelling_fix{$suspect} = $fix; } close($spelling); } else { warn "No typos will be found - file '$spelling_file': $!\n"; } if ($codespell) { if (open(my $spelling, '<', $codespellfile)) { while (<$spelling>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); next if ($line =~ m/, disabled/i); $line =~ s/,.*$//; my ($suspect, $fix) = split(/->/, $line); $spelling_fix{$suspect} = $fix; } close($spelling); } else { warn "No codespell typos will be found - file '$codespellfile': $!\n"; } } $misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; sub read_words { my ($wordsRef, $file) = @_; if (open(my $words, '<', $file)) { while (<$words>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); if ($line =~ /\s/) { print("$file: '$line' invalid - ignored\n"); next; } $$wordsRef .= '|' if (defined $$wordsRef); $$wordsRef .= $line; } close($file); return 1; } return 0; } my $const_structs; if (show_type("CONST_STRUCT")) { read_words(\$const_structs, $conststructsfile) or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; } if (defined($typedefsfile)) { my $typeOtherTypedefs; read_words(\$typeOtherTypedefs, $typedefsfile) or warn "No additional types will be considered - file '$typedefsfile': $!\n"; $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); } sub build_types { my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; $BasicType = qr{ (?:$typeTypedefs\b)| (?:${all}\b) }x; $NonptrType = qr{ (?:$Modifier\s+|const\s+)* (?: (?:typeof|__typeof__)\s*\([^\)]*\)| (?:$typeTypedefs\b)| (?:${all}\b) ) (?:\s+$Modifier|\s+const)* }x; $NonptrTypeMisordered = qr{ (?:$Modifier\s+|const\s+)* (?: (?:${Misordered}\b) ) (?:\s+$Modifier|\s+const)* }x; $NonptrTypeWithAttr = qr{ (?:$Modifier\s+|const\s+)* (?: (?:typeof|__typeof__)\s*\([^\)]*\)| (?:$typeTypedefs\b)| (?:${allWithAttr}\b) ) (?:\s+$Modifier|\s+const)* }x; $Type = qr{ $NonptrType (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} (?:\s+$Inline|\s+$Modifier)* }x; $TypeMisordered = qr{ $NonptrTypeMisordered (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} (?:\s+$Inline|\s+$Modifier)* }x; $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; } build_types(); our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; # Using $balanced_parens, $LvalOrFunc, or $FuncArg # requires at least perl version v5.10.0 # Any use must be runtime checked with $^V our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; our $declaration_macros = qr{(?x: (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(| (?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\( )}; our %allow_repeated_words = ( add => '', added => '', bad => '', be => '', ); sub deparenthesize { my ($string) = @_; return "" if (!defined($string)); while ($string =~ /^\s*\(.*\)\s*$/) { $string =~ s@^\s*\(\s*@@; $string =~ s@\s*\)\s*$@@; } $string =~ s@\s+@ @g; return $string; } sub seed_camelcase_file { my ($file) = @_; return if (!(-f $file)); local $/; open(my $include_file, '<', "$file") or warn "$P: Can't read '$file' $!\n"; my $text = <$include_file>; close($include_file); my @lines = split('\n', $text); foreach my $line (@lines) { next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { $camelcase{$1} = 1; } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { $camelcase{$1} = 1; } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { $camelcase{$1} = 1; } } } our %maintained_status = (); sub is_maintained_obsolete { my ($filename) = @_; return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); if (!exists($maintained_status{$filename})) { $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; } return $maintained_status{$filename} =~ /obsolete/i; } sub is_SPDX_License_valid { my ($license) = @_; return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); my $root_path = abs_path($root); my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; return 0 if ($status ne ""); return 1; } my $camelcase_seeded = 0; sub seed_camelcase_includes { return if ($camelcase_seeded); my $files; my $camelcase_cache = ""; my @include_files = (); $camelcase_seeded = 1; if (-e "$gitroot") { my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; chomp $git_last_include_commit; $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; } else { my $last_mod_date = 0; $files = `find $root/include -name "*.h"`; @include_files = split('\n', $files); foreach my $file (@include_files) { my $date = POSIX::strftime("%Y%m%d%H%M", localtime((stat $file)[9])); $last_mod_date = $date if ($last_mod_date < $date); } $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; } if ($camelcase_cache ne "" && -f $camelcase_cache) { open(my $camelcase_file, '<', "$camelcase_cache") or warn "$P: Can't read '$camelcase_cache' $!\n"; while (<$camelcase_file>) { chomp; $camelcase{$_} = 1; } close($camelcase_file); return; } if (-e "$gitroot") { $files = `${git_command} ls-files "include/*.h"`; @include_files = split('\n', $files); } foreach my $file (@include_files) { seed_camelcase_file($file); } if ($camelcase_cache ne "") { unlink glob ".checkpatch-camelcase.*"; open(my $camelcase_file, '>', "$camelcase_cache") or warn "$P: Can't write '$camelcase_cache' $!\n"; foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { print $camelcase_file ("$_\n"); } close($camelcase_file); } } sub git_is_single_file { my ($filename) = @_; return 0 if ((which("git") eq "") || !(-e "$gitroot")); my $output = `${git_command} ls-files -- $filename 2>/dev/null`; my $count = $output =~ tr/\n//; return $count eq 1 && $output =~ m{^${filename}$}; } sub git_commit_info { my ($commit, $id, $desc) = @_; return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; $output =~ s/^\s*//gm; my @lines = split("\n", $output); return ($id, $desc) if ($#lines < 0); if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { # Maybe one day convert this block of bash into something that returns # all matching commit ids, but it's very slow... # # echo "checking commits $1..." # git rev-list --remotes | grep -i "^$1" | # while read line ; do # git log --format='%H %s' -1 $line | # echo "commit $(cut -c 1-12,41-)" # done } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || $lines[0] =~ /^fatal: bad object $commit/) { $id = undef; } else { $id = substr($lines[0], 0, 12); $desc = substr($lines[0], 41); } return ($id, $desc); } $chk_signoff = 0 if ($file); $chk_fixes_tag = 0 if ($file); my @rawlines = (); my @lines = (); my @fixed = (); my @fixed_inserted = (); my @fixed_deleted = (); my $fixlinenr = -1; # If input is git commits, extract all commits from the commit expressions. # For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. die "$P: No git repository found\n" if ($git && !-e "$gitroot"); if ($git) { my @commits = (); foreach my $commit_expr (@ARGV) { my $git_range; if ($commit_expr =~ m/^(.*)-(\d+)$/) { $git_range = "-$2 $1"; } elsif ($commit_expr =~ m/\.\./) { $git_range = "$commit_expr"; } else { $git_range = "-1 $commit_expr"; } my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; foreach my $line (split(/\n/, $lines)) { $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; next if (!defined($1) || !defined($2)); my $sha1 = $1; my $subject = $2; unshift(@commits, $sha1); $git_commits{$sha1} = $subject; } } die "$P: no git commits after extraction!\n" if (@commits == 0); @ARGV = @commits; } my $vname; $allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; for my $filename (@ARGV) { my $FILE; my $is_git_file = git_is_single_file($filename); my $oldfile = $file; $file = 1 if ($is_git_file); if ($git) { open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || die "$P: $filename: git format-patch failed - $!\n"; } elsif ($file) { open($FILE, '-|', "diff -u /dev/null $filename") || die "$P: $filename: diff failed - $!\n"; } elsif ($filename eq '-') { open($FILE, '<&STDIN'); } else { open($FILE, '<', "$filename") || die "$P: $filename: open failed - $!\n"; } if ($filename eq '-') { $vname = 'Your patch'; } elsif ($git) { $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; } else { $vname = $filename; } while (<$FILE>) { chomp; push(@rawlines, $_); $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); } close($FILE); if ($#ARGV > 0 && $quiet == 0) { print '-' x length($vname) . "\n"; print "$vname\n"; print '-' x length($vname) . "\n"; } if (!process($filename)) { $exit = 1; } @rawlines = (); @lines = (); @fixed = (); @fixed_inserted = (); @fixed_deleted = (); $fixlinenr = -1; @modifierListFile = (); @typeListFile = (); build_types(); $file = $oldfile if ($is_git_file); } if (!$quiet) { hash_show_words(\%use_type, "Used"); hash_show_words(\%ignore_type, "Ignored"); if (!$perl_version_ok) { print << "EOM" NOTE: perl $^V is not modern enough to detect all possible issues. An upgrade to at least perl $minimum_perl_version is suggested. EOM } if ($exit) { print << "EOM" NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. EOM } } exit($exit); sub top_of_kernel_tree { my ($root) = @_; my @tree_check = ( "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", "README", "Documentation", "arch", "include", "drivers", "fs", "init", "ipc", "kernel", "lib", "scripts", ); foreach my $check (@tree_check) { if (! -e $root . '/' . $check) { return 0; } } return 1; } sub parse_email { my ($formatted_email) = @_; my $name = ""; my $quoted = ""; my $name_comment = ""; my $address = ""; my $comment = ""; if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { $name = $1; $address = $2; $comment = $3 if defined $3; } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { $address = $1; $comment = $2 if defined $2; } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { $address = $1; $comment = $2 if defined $2; $formatted_email =~ s/\Q$address\E.*$//; $name = $formatted_email; $name = trim($name); $name =~ s/^\"|\"$//g; # If there's a name left after stripping spaces and # leading quotes, and the address doesn't have both # leading and trailing angle brackets, the address # is invalid. ie: # "joe smith joe@smith.com" bad # "joe smith ]+>$/) { $name = ""; $address = ""; $comment = ""; } } # Extract comments from names excluding quoted parts # "John D. (Doe)" - Do not extract if ($name =~ s/\"(.+)\"//) { $quoted = $1; } while ($name =~ s/\s*($balanced_parens)\s*/ /) { $name_comment .= trim($1); } $name =~ s/^[ \"]+|[ \"]+$//g; $name = trim("$quoted $name"); $address = trim($address); $address =~ s/^\<|\>$//g; $comment = trim($comment); if ($name =~ /[^\w \-]/i) { ##has "must quote" chars $name =~ s/(?"; } $formatted_email .= "$comment"; return $formatted_email; } sub reformat_email { my ($email) = @_; my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); return format_email($email_name, $name_comment, $email_address, $comment); } sub same_email_addresses { my ($email1, $email2) = @_; my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); return $email1_name eq $email2_name && $email1_address eq $email2_address && $name1_comment eq $name2_comment && $comment1 eq $comment2; } sub which { my ($bin) = @_; foreach my $path (split(/:/, $ENV{PATH})) { if (-e "$path/$bin") { return "$path/$bin"; } } return ""; } sub which_conf { my ($conf) = @_; foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { if (-e "$path/$conf") { return "$path/$conf"; } } return ""; } sub expand_tabs { my ($str) = @_; my $res = ''; my $n = 0; for my $c (split(//, $str)) { if ($c eq "\t") { $res .= ' '; $n++; for (; ($n % $tabsize) != 0; $n++) { $res .= ' '; } next; } $res .= $c; $n++; } return $res; } sub copy_spacing { (my $res = shift) =~ tr/\t/ /c; return $res; } sub line_stats { my ($line) = @_; # Drop the diff line leader and expand tabs $line =~ s/^.//; $line = expand_tabs($line); # Pick the indent from the front of the line. my ($white) = ($line =~ /^(\s*)/); return (length($line), length($white)); } my $sanitise_quote = ''; sub sanitise_line_reset { my ($in_comment) = @_; if ($in_comment) { $sanitise_quote = '*/'; } else { $sanitise_quote = ''; } } sub sanitise_line { my ($line) = @_; my $res = ''; my $l = ''; my $qlen = 0; my $off = 0; my $c; # Always copy over the diff marker. $res = substr($line, 0, 1); for ($off = 1; $off < length($line); $off++) { $c = substr($line, $off, 1); # Comments we are whacking completely including the begin # and end, all to $;. if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { $sanitise_quote = '*/'; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { $sanitise_quote = ''; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { $sanitise_quote = '//'; substr($res, $off, 2, $sanitise_quote); $off++; next; } # A \ in a string means ignore the next character. if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && $c eq "\\") { substr($res, $off, 2, 'XX'); $off++; next; } # Regular quotes. if ($c eq "'" || $c eq '"') { if ($sanitise_quote eq '') { $sanitise_quote = $c; substr($res, $off, 1, $c); next; } elsif ($sanitise_quote eq $c) { $sanitise_quote = ''; } } #print "c<$c> SQ<$sanitise_quote>\n"; if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { substr($res, $off, 1, 'X'); } else { substr($res, $off, 1, $c); } } if ($sanitise_quote eq '//') { $sanitise_quote = ''; } # The pathname on a #include may be surrounded by '<' and '>'. if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { my $clean = 'X' x length($1); $res =~ s@\<.*\>@<$clean>@; # The whole of a #error is a string. } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { my $clean = 'X' x length($1); $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; } if ($allow_c99_comments && $res =~ m@(//.*$)@) { my $match = $1; $res =~ s/\Q$match\E/"$;" x length($match)/e; } return $res; } sub get_quoted_string { my ($line, $rawline) = @_; return "" if (!defined($line) || !defined($rawline)); return "" if ($line !~ m/($String)/g); return substr($rawline, $-[0], $+[0] - $-[0]); } sub ctx_statement_block { my ($linenr, $remain, $off) = @_; my $line = $linenr - 1; my $blk = ''; my $soff = $off; my $coff = $off - 1; my $coff_set = 0; my $loff = 0; my $type = ''; my $level = 0; my @stack = (); my $p; my $c; my $len = 0; my $remainder; while (1) { @stack = (['', 0]) if ($#stack == -1); #warn "CSB: blk<$blk> remain<$remain>\n"; # If we are about to drop off the end, pull in more # context. if ($off >= $len) { for (; $remain > 0; $line++) { last if (!defined $lines[$line]); next if ($lines[$line] =~ /^-/); $remain--; $loff = $len; $blk .= $lines[$line] . "\n"; $len = length($blk); $line++; last; } # Bail if there is no further context. #warn "CSB: blk<$blk> off<$off> len<$len>\n"; if ($off >= $len) { last; } if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { $level++; $type = '#'; } } $p = $c; $c = substr($blk, $off, 1); $remainder = substr($blk, $off); #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; # Handle nested #if/#else. if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, [ $type, $level ]); } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { ($type, $level) = @{$stack[$#stack - 1]}; } elsif ($remainder =~ /^#\s*endif\b/) { ($type, $level) = @{pop(@stack)}; } # Statement ends at the ';' or a close '}' at the # outermost level. if ($level == 0 && $c eq ';') { last; } # An else is really a conditional as long as its not else if if ($level == 0 && $coff_set == 0 && (!defined($p) || $p =~ /(?:\s|\}|\+)/) && $remainder =~ /^(else)(?:\s|{)/ && $remainder !~ /^else\s+if\b/) { $coff = $off + length($1) - 1; $coff_set = 1; #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; } if (($type eq '' || $type eq '(') && $c eq '(') { $level++; $type = '('; } if ($type eq '(' && $c eq ')') { $level--; $type = ($level != 0)? '(' : ''; if ($level == 0 && $coff < $soff) { $coff = $off; $coff_set = 1; #warn "CSB: mark coff<$coff>\n"; } } if (($type eq '' || $type eq '{') && $c eq '{') { $level++; $type = '{'; } if ($type eq '{' && $c eq '}') { $level--; $type = ($level != 0)? '{' : ''; if ($level == 0) { if (substr($blk, $off + 1, 1) eq ';') { $off++; } last; } } # Preprocessor commands end at the newline unless escaped. if ($type eq '#' && $c eq "\n" && $p ne "\\") { $level--; $type = ''; $off++; last; } $off++; } # We are truly at the end, so shuffle to the next line. if ($off == $len) { $loff = $len + 1; $line++; $remain--; } my $statement = substr($blk, $soff, $off - $soff + 1); my $condition = substr($blk, $soff, $coff - $soff + 1); #warn "STATEMENT<$statement>\n"; #warn "CONDITION<$condition>\n"; #print "coff<$coff> soff<$off> loff<$loff>\n"; return ($statement, $condition, $line, $remain + 1, $off - $loff + 1, $level); } sub statement_lines { my ($stmt) = @_; # Strip the diff line prefixes and rip blank lines at start and end. $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_rawlines { my ($stmt) = @_; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_block_size { my ($stmt) = @_; $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*{//; $stmt =~ s/}\s*$//; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); my @stmt_statements = ($stmt =~ /;/g); my $stmt_lines = $#stmt_lines + 2; my $stmt_statements = $#stmt_statements + 1; if ($stmt_lines > $stmt_statements) { return $stmt_lines; } else { return $stmt_statements; } } sub ctx_statement_full { my ($linenr, $remain, $off) = @_; my ($statement, $condition, $level); my (@chunks); # Grab the first conditional/block pair. ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "F: c<$condition> s<$statement> remain<$remain>\n"; push(@chunks, [ $condition, $statement ]); if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { return ($level, $linenr, @chunks); } # Pull in the following conditional/block pairs and see if they # could continue the statement. for (;;) { ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "C: c<$condition> s<$statement> remain<$remain>\n"; last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); #print "C: push\n"; push(@chunks, [ $condition, $statement ]); } return ($level, $linenr, @chunks); } sub ctx_block_get { my ($linenr, $remain, $outer, $open, $close, $off) = @_; my $line; my $start = $linenr - 1; my $blk = ''; my @o; my @c; my @res = (); my $level = 0; my @stack = ($level); for ($line = $start; $remain > 0; $line++) { next if ($rawlines[$line] =~ /^-/); $remain--; $blk .= $rawlines[$line]; # Handle nested #if/#else. if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, $level); } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { $level = $stack[$#stack - 1]; } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { $level = pop(@stack); } foreach my $c (split(//, $lines[$line])) { ##print "C<$c>L<$level><$open$close>O<$off>\n"; if ($off > 0) { $off--; next; } if ($c eq $close && $level > 0) { $level--; last if ($level == 0); } elsif ($c eq $open) { $level++; } } if (!$outer || $level <= 1) { push(@res, $rawlines[$line]); } last if ($level == 0); } return ($level, @res); } sub ctx_block_outer { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); return @r; } sub ctx_block { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); return @r; } sub ctx_statement { my ($linenr, $remain, $off) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); return @r; } sub ctx_block_level { my ($linenr, $remain) = @_; return ctx_block_get($linenr, $remain, 0, '{', '}', 0); } sub ctx_statement_level { my ($linenr, $remain, $off) = @_; return ctx_block_get($linenr, $remain, 0, '(', ')', $off); } sub ctx_locate_comment { my ($first_line, $end_line) = @_; # If c99 comment on the current line, or the line before or after my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); return $current_comment if (defined $current_comment); ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); return $current_comment if (defined $current_comment); ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); return $current_comment if (defined $current_comment); # Catch a comment on the end of the line itself. ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); return $current_comment if (defined $current_comment); # Look through the context and try and figure out if there is a # comment. my $in_comment = 0; $current_comment = ''; for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { my $line = $rawlines[$linenr - 1]; #warn " $line\n"; if ($linenr == $first_line and $line =~ m@^.\s*\*@) { $in_comment = 1; } if ($line =~ m@/\*@) { $in_comment = 1; } if (!$in_comment && $current_comment ne '') { $current_comment = ''; } $current_comment .= $line . "\n" if ($in_comment); if ($line =~ m@\*/@) { $in_comment = 0; } } chomp($current_comment); return($current_comment); } sub ctx_has_comment { my ($first_line, $end_line) = @_; my $cmt = ctx_locate_comment($first_line, $end_line); ##print "LINE: $rawlines[$end_line - 1 ]\n"; ##print "CMMT: $cmt\n"; return ($cmt ne ''); } sub raw_line { my ($linenr, $cnt) = @_; my $offset = $linenr - 1; $cnt++; my $line; while ($cnt) { $line = $rawlines[$offset++]; next if (defined($line) && $line =~ /^-/); $cnt--; } return $line; } sub get_stat_real { my ($linenr, $lc) = @_; my $stat_real = raw_line($linenr, 0); for (my $count = $linenr + 1; $count <= $lc; $count++) { $stat_real = $stat_real . "\n" . raw_line($count, 0); } return $stat_real; } sub get_stat_here { my ($linenr, $cnt, $here) = @_; my $herectx = $here . "\n"; for (my $n = 0; $n < $cnt; $n++) { $herectx .= raw_line($linenr, $n) . "\n"; } return $herectx; } sub cat_vet { my ($vet) = @_; my ($res, $coded); $res = ''; while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { $res .= $1; if ($2 ne '') { $coded = sprintf("^%c", unpack('C', $2) + 64); $res .= $coded; } } $res =~ s/$/\$/; return $res; } my $av_preprocessor = 0; my $av_pending; my @av_paren_type; my $av_pend_colon; sub annotate_reset { $av_preprocessor = 0; $av_pending = '_'; @av_paren_type = ('E'); $av_pend_colon = 'O'; } sub annotate_values { my ($stream, $type) = @_; my $res; my $var = '_' x length($stream); my $cur = $stream; print "$stream\n" if ($dbg_values > 1); while (length($cur)) { @av_paren_type = ('E') if ($#av_paren_type < 0); print " <" . join('', @av_paren_type) . "> <$type> <$av_pending>" if ($dbg_values > 1); if ($cur =~ /^(\s+)/o) { print "WS($1)\n" if ($dbg_values > 1); if ($1 =~ /\n/ && $av_preprocessor) { $type = pop(@av_paren_type); $av_preprocessor = 0; } } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { print "CAST($1)\n" if ($dbg_values > 1); push(@av_paren_type, $type); $type = 'c'; } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { print "DECLARE($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^($Modifier)\s*/) { print "MODIFIER($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { print "DEFINE($1,$2)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); if ($2 ne '') { $av_pending = 'N'; } $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { print "UNDEF($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { print "PRE_START($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { print "PRE_RESTART($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $av_paren_type[$#av_paren_type]); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:endif))/o) { print "PRE_END($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; # Assume all arms of the conditional end as this # one does, and continue as if the #endif was not here. pop(@av_paren_type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\\\n)/o) { print "PRECONT($1)\n" if ($dbg_values > 1); } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { print "ATTR($1)\n" if ($dbg_values > 1); $av_pending = $type; $type = 'N'; } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { print "SIZEOF($1)\n" if ($dbg_values > 1); if (defined $2) { $av_pending = 'V'; } $type = 'N'; } elsif ($cur =~ /^(if|while|for)\b/o) { print "COND($1)\n" if ($dbg_values > 1); $av_pending = 'E'; $type = 'N'; } elsif ($cur =~/^(case)/o) { print "CASE($1)\n" if ($dbg_values > 1); $av_pend_colon = 'C'; $type = 'N'; } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { print "KEYWORD($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(\()/o) { print "PAREN('$1')\n" if ($dbg_values > 1); push(@av_paren_type, $av_pending); $av_pending = '_'; $type = 'N'; } elsif ($cur =~ /^(\))/o) { my $new_type = pop(@av_paren_type); if ($new_type ne '_') { $type = $new_type; print "PAREN('$1') -> $type\n" if ($dbg_values > 1); } else { print "PAREN('$1')\n" if ($dbg_values > 1); } } elsif ($cur =~ /^($Ident)\s*\(/o) { print "FUNC($1)\n" if ($dbg_values > 1); $type = 'V'; $av_pending = 'V'; } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { if (defined $2 && $type eq 'C' || $type eq 'T') { $av_pend_colon = 'B'; } elsif ($type eq 'E') { $av_pend_colon = 'L'; } print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Ident|$Constant)/o) { print "IDENT($1)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Assignment)/o) { print "ASSIGN($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~/^(;|{|})/) { print "END($1)\n" if ($dbg_values > 1); $type = 'E'; $av_pend_colon = 'O'; } elsif ($cur =~/^(,)/) { print "COMMA($1)\n" if ($dbg_values > 1); $type = 'C'; } elsif ($cur =~ /^(\?)/o) { print "QUESTION($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(:)/o) { print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); substr($var, length($res), 1, $av_pend_colon); if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { $type = 'E'; } else { $type = 'N'; } $av_pend_colon = 'O'; } elsif ($cur =~ /^(\[)/o) { print "CLOSE($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { my $variant; print "OPV($1)\n" if ($dbg_values > 1); if ($type eq 'V') { $variant = 'B'; } else { $variant = 'U'; } substr($var, length($res), 1, $variant); $type = 'N'; } elsif ($cur =~ /^($Operators)/o) { print "OP($1)\n" if ($dbg_values > 1); if ($1 ne '++' && $1 ne '--') { $type = 'N'; } } elsif ($cur =~ /(^.)/o) { print "C($1)\n" if ($dbg_values > 1); } if (defined $1) { $cur = substr($cur, length($1)); $res .= $type x length($1); } } return ($res, $var); } sub possible { my ($possible, $line) = @_; my $notPermitted = qr{(?: ^(?: $Modifier| $Storage| $Type| DEFINE_\S+ )$| ^(?: goto| return| case| else| asm|__asm__| do| \#| \#\#| )(?:\s|$)| ^(?:typedef|struct|enum)\b )}x; warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); if ($possible !~ $notPermitted) { # Check for modifiers. $possible =~ s/\s*$Storage\s*//g; $possible =~ s/\s*$Sparse\s*//g; if ($possible =~ /^\s*$/) { } elsif ($possible =~ /\s/) { $possible =~ s/\s*$Type\s*//g; for my $modifier (split(' ', $possible)) { if ($modifier !~ $notPermitted) { warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); push(@modifierListFile, $modifier); } } } else { warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); push(@typeListFile, $possible); } build_types(); } else { warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); } } my $prefix = ''; sub show_type { my ($type) = @_; $type =~ tr/[a-z]/[A-Z]/; return defined $use_type{$type} if (scalar keys %use_type > 0); return !defined $ignore_type{$type}; } sub report { my ($level, $type, $msg) = @_; if (!show_type($type) || (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { return 0; } my $output = ''; if ($color) { if ($level eq 'ERROR') { $output .= RED; } elsif ($level eq 'WARNING') { $output .= YELLOW; } else { $output .= GREEN; } } $output .= $prefix . $level . ':'; if ($show_types) { $output .= BLUE if ($color); $output .= "$type:"; } $output .= RESET if ($color); $output .= ' ' . $msg . "\n"; if ($showfile) { my @lines = split("\n", $output, -1); splice(@lines, 1, 1); $output = join("\n", @lines); } if ($terse) { $output = (split('\n', $output))[0] . "\n"; } if ($verbose && exists($verbose_messages{$type}) && !exists($verbose_emitted{$type})) { $output .= $verbose_messages{$type} . "\n\n"; $verbose_emitted{$type} = 1; } push(our @report, $output); return 1; } sub report_dump { our @report; } sub fixup_current_range { my ($lineRef, $offset, $length) = @_; if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { my $o = $1; my $l = $2; my $no = $o + $offset; my $nl = $l + $length; $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; } } sub fix_inserted_deleted_lines { my ($linesRef, $insertedRef, $deletedRef) = @_; my $range_last_linenr = 0; my $delta_offset = 0; my $old_linenr = 0; my $new_linenr = 0; my $next_insert = 0; my $next_delete = 0; my @lines = (); my $inserted = @{$insertedRef}[$next_insert++]; my $deleted = @{$deletedRef}[$next_delete++]; foreach my $old_line (@{$linesRef}) { my $save_line = 1; my $line = $old_line; #don't modify the array if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename $delta_offset = 0; } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk $range_last_linenr = $new_linenr; fixup_current_range(\$line, $delta_offset, 0); } while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { $deleted = @{$deletedRef}[$next_delete++]; $save_line = 0; fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); } while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { push(@lines, ${$inserted}{'LINE'}); $inserted = @{$insertedRef}[$next_insert++]; $new_linenr++; fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); } if ($save_line) { push(@lines, $line); $new_linenr++; } $old_linenr++; } return @lines; } sub fix_insert_line { my ($linenr, $line) = @_; my $inserted = { LINENR => $linenr, LINE => $line, }; push(@fixed_inserted, $inserted); } sub fix_delete_line { my ($linenr, $line) = @_; my $deleted = { LINENR => $linenr, LINE => $line, }; push(@fixed_deleted, $deleted); } sub ERROR { my ($type, $msg) = @_; if (report("ERROR", $type, $msg)) { our $clean = 0; our $cnt_error++; return 1; } return 0; } sub WARN { my ($type, $msg) = @_; if (report("WARNING", $type, $msg)) { our $clean = 0; our $cnt_warn++; return 1; } return 0; } sub CHK { my ($type, $msg) = @_; if ($check && report("CHECK", $type, $msg)) { our $clean = 0; our $cnt_chk++; return 1; } return 0; } sub check_absolute_file { my ($absolute, $herecurr) = @_; my $file = $absolute; ##print "absolute<$absolute>\n"; # See if any suffix of this path is a path within the tree. while ($file =~ s@^[^/]*/@@) { if (-f "$root/$file") { ##print "file<$file>\n"; last; } } if (! -f _) { return 0; } # It is, so see if the prefix is acceptable. my $prefix = $absolute; substr($prefix, -length($file)) = ''; ##print "prefix<$prefix>\n"; if ($prefix ne ".../") { WARN("USE_RELATIVE_PATH", "use relative pathname instead of absolute in changelog text\n" . $herecurr); } } sub trim { my ($string) = @_; $string =~ s/^\s+|\s+$//g; return $string; } sub ltrim { my ($string) = @_; $string =~ s/^\s+//; return $string; } sub rtrim { my ($string) = @_; $string =~ s/\s+$//; return $string; } sub string_find_replace { my ($string, $find, $replace) = @_; $string =~ s/$find/$replace/g; return $string; } sub tabify { my ($leading) = @_; my $source_indent = $tabsize; my $max_spaces_before_tab = $source_indent - 1; my $spaces_to_tab = " " x $source_indent; #convert leading spaces to tabs 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; #Remove spaces before a tab 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; return "$leading"; } sub pos_last_openparen { my ($line) = @_; my $pos = 0; my $opens = $line =~ tr/\(/\(/; my $closes = $line =~ tr/\)/\)/; my $last_openparen = 0; if (($opens == 0) || ($closes >= $opens)) { return -1; } my $len = length($line); for ($pos = 0; $pos < $len; $pos++) { my $string = substr($line, $pos); if ($string =~ /^($FuncArg|$balanced_parens)/) { $pos += length($1) - 1; } elsif (substr($line, $pos, 1) eq '(') { $last_openparen = $pos; } elsif (index($string, '(') == -1) { last; } } return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; } sub get_raw_comment { my ($line, $rawline) = @_; my $comment = ''; for my $i (0 .. (length($line) - 1)) { if (substr($line, $i, 1) eq "$;") { $comment .= substr($rawline, $i, 1); } } return $comment; } sub exclude_global_initialisers { my ($realfile) = @_; # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || $realfile =~ m@^samples/bpf/.*_kern\.c$@ || $realfile =~ m@/bpf/.*\.bpf\.c$@; } sub process { my $filename = shift; my $linenr=0; my $prevline=""; my $prevrawline=""; my $stashline=""; my $stashrawline=""; my $length; my $indent; my $previndent=0; my $stashindent=0; our $clean = 1; my $signoff = 0; my $fixes_tag = 0; my $is_revert = 0; my $needs_fixes_tag = ""; my $author = ''; my $authorsignoff = 0; my $author_sob = ''; my $is_patch = 0; my $is_binding_patch = -1; my $in_header_lines = $file ? 0 : 1; my $in_commit_log = 0; #Scanning lines before patch my $has_patch_separator = 0; #Found a --- line my $has_commit_log = 0; #Encountered lines before patch my $commit_log_lines = 0; #Number of commit log lines my $commit_log_possible_stack_dump = 0; my $commit_log_long_line = 0; my $commit_log_has_diff = 0; my $reported_maintainer_file = 0; my $non_utf8_charset = 0; my $last_git_commit_id_linenr = -1; my $last_blank_line = 0; my $last_coalesced_string_linenr = -1; our @report = (); our $cnt_lines = 0; our $cnt_error = 0; our $cnt_warn = 0; our $cnt_chk = 0; # Trace the real file/line as we go. my $realfile = ''; my $realline = 0; my $realcnt = 0; my $here = ''; my $context_function; #undef'd unless there's a known function my $in_comment = 0; my $comment_edge = 0; my $first_line = 0; my $p1_prefix = ''; my $prev_values = 'E'; # suppression flags my %suppress_ifbraces; my %suppress_whiletrailers; my %suppress_export; my $suppress_statement = 0; my %signatures = (); # Pre-scan the patch sanitizing the lines. # Pre-scan the patch looking for any __setup documentation. # my @setup_docs = (); my $setup_docs = 0; my $camelcase_file_seeded = 0; my $checklicenseline = 1; sanitise_line_reset(); my $line; foreach my $rawline (@rawlines) { $linenr++; $line = $rawline; push(@fixed, $rawline) if ($fix); if ($rawline=~/^\+\+\+\s+(\S+)/) { $setup_docs = 0; if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { $setup_docs = 1; } #next; } if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } $in_comment = 0; # Guestimate if this is a continuing comment. Run # the context looking for a comment "edge". If this # edge is a close comment then we must be in a comment # at context start. my $edge; my $cnt = $realcnt; for (my $ln = $linenr + 1; $cnt > 0; $ln++) { next if (defined $rawlines[$ln - 1] && $rawlines[$ln - 1] =~ /^-/); $cnt--; #print "RAW<$rawlines[$ln - 1]>\n"; last if (!defined $rawlines[$ln - 1]); if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { ($edge) = $1; last; } } if (defined $edge && $edge eq '*/') { $in_comment = 1; } # Guestimate if this is a continuing comment. If this # is the start of a diff block and this line starts # ' *' then it is very likely a comment. if (!defined $edge && $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) { $in_comment = 1; } ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; sanitise_line_reset($in_comment); } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { # Standardise the strings and chars within the input to # simplify matching -- only bother with positive lines. $line = sanitise_line($rawline); } push(@lines, $line); if ($realcnt > 1) { $realcnt-- if ($line =~ /^(?:\+| |$)/); } else { $realcnt = 0; } #print "==>$rawline\n"; #print "-->$line\n"; if ($setup_docs && $line =~ /^\+/) { push(@setup_docs, $line); } } $prefix = ''; $realcnt = 0; $linenr = 0; $fixlinenr = -1; foreach my $line (@lines) { $linenr++; $fixlinenr++; my $sline = $line; #copy of $line $sline =~ s/$;/ /g; #with comments as spaces my $rawline = $rawlines[$linenr - 1]; my $raw_comment = get_raw_comment($line, $rawline); # check if it's a mode change, rename or start of a patch if (!$in_commit_log && ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || ($line =~ /^rename (?:from|to) \S+\s*$/ || $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { $is_patch = 1; } #extract the line range in the file after the patch is applied if (!$in_commit_log && $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { my $context = $4; $is_patch = 1; $first_line = $linenr + 1; $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } annotate_reset(); $prev_values = 'E'; %suppress_ifbraces = (); %suppress_whiletrailers = (); %suppress_export = (); $suppress_statement = 0; if ($context =~ /\b(\w+)\s*\(/) { $context_function = $1; } else { undef $context_function; } next; # track the line number as we move through the hunk, note that # new versions of GNU diff omit the leading space on completely # blank context lines so we need to count that too. } elsif ($line =~ /^( |\+|$)/) { $realline++; $realcnt-- if ($realcnt != 0); # Measure the line length and indent. ($length, $indent) = line_stats($rawline); # Track the previous line. ($prevline, $stashline) = ($stashline, $line); ($previndent, $stashindent) = ($stashindent, $indent); ($prevrawline, $stashrawline) = ($stashrawline, $rawline); #warn "line<$line>\n"; } elsif ($realcnt == 1) { $realcnt--; } my $hunk_line = ($realcnt != 0); $here = "#$linenr: " if (!$file); $here = "#$realline: " if ($file); my $found_file = 0; # extract the filename as it passes if ($line =~ /^diff --git.*?(\S+)$/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@ if (!$file); $in_commit_log = 0; $found_file = 1; } elsif ($line =~ /^\+\+\+\s+(\S+)/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@ if (!$file); $in_commit_log = 0; $p1_prefix = $1; if (!$file && $tree && $p1_prefix ne '' && -e "$root/$p1_prefix") { WARN("PATCH_PREFIX", "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); } if ($realfile =~ m@^include/asm/@) { ERROR("MODIFIED_INCLUDE_ASM", "do not modify files in include/asm, change architecture specific files in arch//include/asm\n" . "$here$rawline\n"); } $found_file = 1; } #make up the handle for any error we report on this line if ($showfile) { $prefix = "$realfile:$realline: " } elsif ($emacs) { if ($file) { $prefix = "$filename:$realline: "; } else { $prefix = "$filename:$linenr: "; } } if ($found_file) { if (is_maintained_obsolete($realfile)) { WARN("OBSOLETE", "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); } if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { $check = 1; } else { $check = $check_orig; } $checklicenseline = 1; if ($realfile !~ /^MAINTAINERS/) { my $last_binding_patch = $is_binding_patch; $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; if (($last_binding_patch != -1) && ($last_binding_patch ^ $is_binding_patch)) { WARN("DT_SPLIT_BINDING_PATCH", "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); } } next; } $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); my $hereline = "$here\n$rawline\n"; my $herecurr = "$here\n$rawline\n"; my $hereprev = "$here\n$prevrawline\n$rawline\n"; $cnt_lines++ if ($realcnt != 0); # Verify the existence of a commit log if appropriate # 2 is used because a $signature is counted in $commit_log_lines if ($in_commit_log) { if ($line !~ /^\s*$/) { $commit_log_lines++; #could be a $signature } } elsif ($has_commit_log && $commit_log_lines < 2) { WARN("COMMIT_MESSAGE", "Missing commit description - Add an appropriate one\n"); $commit_log_lines = 2; #warn only once } # Check if the commit log has what seems like a diff which can confuse patch if ($in_commit_log && !$commit_log_has_diff && (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { ERROR("DIFF_IN_COMMIT_MSG", "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); $commit_log_has_diff = 1; } # Check for incorrect file permissions if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { my $permhere = $here . "FILE: $realfile\n"; if ($realfile !~ m@scripts/@ && $realfile !~ /\.(py|pl|awk|sh)$/) { ERROR("EXECUTE_PERMISSIONS", "do not set execute permissions for source files\n" . $permhere); } } # Check the patch for a From: if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { $author = $1; my $curline = $linenr; while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { $author .= $1; } $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); $author =~ s/"//g; $author = reformat_email($author); } # Check the patch for a signoff: if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { $signoff++; $in_commit_log = 0; if ($author ne '' && $authorsignoff != 1) { if (same_email_addresses($1, $author)) { $authorsignoff = 1; } else { my $ctx = $1; my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); if (lc $email_address eq lc $author_address && $email_name eq $author_name) { $author_sob = $ctx; $authorsignoff = 2; } elsif (lc $email_address eq lc $author_address) { $author_sob = $ctx; $authorsignoff = 3; } elsif ($email_name eq $author_name) { $author_sob = $ctx; $authorsignoff = 4; my $address1 = $email_address; my $address2 = $author_address; if ($address1 =~ /(\S+)\+\S+(\@.*)/) { $address1 = "$1$2"; } if ($address2 =~ /(\S+)\+\S+(\@.*)/) { $address2 = "$1$2"; } if ($address1 eq $address2) { $authorsignoff = 5; } } } } } # Check for patch separator if ($line =~ /^---$/) { $has_patch_separator = 1; $in_commit_log = 0; } # Check if MAINTAINERS is being updated. If so, there's probably no need to # emit the "does MAINTAINERS need updating?" message on file add/move/delete if ($line =~ /^\s*MAINTAINERS\s*\|/) { $reported_maintainer_file = 1; } # Check signature styles if (!$in_header_lines && $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { my $space_before = $1; my $sign_off = $2; my $space_after = $3; my $email = $4; my $ucfirst_sign_off = ucfirst(lc($sign_off)); if ($sign_off !~ /$signature_tags/) { my $suggested_signature = find_standard_signature($sign_off); if ($suggested_signature eq "") { WARN("BAD_SIGN_OFF", "Non-standard signature: $sign_off\n" . $herecurr); } else { if (WARN("BAD_SIGN_OFF", "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; } } } if (defined $space_before && $space_before ne "") { if (WARN("BAD_SIGN_OFF", "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "$ucfirst_sign_off $email"; } } if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { if (WARN("BAD_SIGN_OFF", "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "$ucfirst_sign_off $email"; } } if (!defined $space_after || $space_after ne " ") { if (WARN("BAD_SIGN_OFF", "Use a single space after $ucfirst_sign_off\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "$ucfirst_sign_off $email"; } } my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); if ($suggested_email eq "") { ERROR("BAD_SIGN_OFF", "Unrecognized email address: '$email'\n" . $herecurr); } else { my $dequoted = $suggested_email; $dequoted =~ s/^"//; $dequoted =~ s/" 1) { WARN("BAD_SIGN_OFF", "Use a single name comment in email: '$email'\n" . $herecurr); } # stable@vger.kernel.org or stable@kernel.org shouldn't # have an email name. In addition comments should strictly # begin with a # if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { if (($comment ne "" && $comment !~ /^#.+/) || ($email_name ne "")) { my $cur_name = $email_name; my $new_comment = $comment; $cur_name =~ s/[a-zA-Z\s\-\"]+//g; # Remove brackets enclosing comment text # and # from start of comments to get comment text $new_comment =~ s/^\((.*)\)$/$1/; $new_comment =~ s/^\[(.*)\]$/$1/; $new_comment =~ s/^[\s\#]+|\s+$//g; $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); $new_comment = " # $new_comment" if ($new_comment ne ""); my $new_email = "$email_address$new_comment"; if (WARN("BAD_STABLE_ADDRESS_STYLE", "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; } } } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { my $new_comment = $comment; # Extract comment text from within brackets or # c89 style /*...*/ comments $new_comment =~ s/^\[(.*)\]$/$1/; $new_comment =~ s/^\/\*(.*)\*\/$/$1/; $new_comment = trim($new_comment); $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo $new_comment = "($new_comment)" if ($new_comment ne ""); my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); if (WARN("BAD_SIGN_OFF", "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; } } } # Check for duplicate signatures my $sig_nospace = $line; $sig_nospace =~ s/\s//g; $sig_nospace = lc($sig_nospace); if (defined $signatures{$sig_nospace}) { WARN("BAD_SIGN_OFF", "Duplicate signature\n" . $herecurr); } else { $signatures{$sig_nospace} = 1; } # Check Co-developed-by: immediately followed by Signed-off-by: with same name and email if ($sign_off =~ /^co-developed-by:$/i) { if ($email eq $author) { WARN("BAD_SIGN_OFF", "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr); } if (!defined $lines[$linenr]) { WARN("BAD_SIGN_OFF", "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr); } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) { WARN("BAD_SIGN_OFF", "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n"); } elsif ($1 ne $email) { WARN("BAD_SIGN_OFF", "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n"); } } # check if Reported-by: is followed by a Closes: tag if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) { if (!defined $lines[$linenr]) { WARN("BAD_REPORTED_BY_LINK", "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . "\n"); } elsif ($rawlines[$linenr] !~ /^closes:\s*/i) { WARN("BAD_REPORTED_BY_LINK", "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n"); } } } # These indicate a bug fix if (!$in_header_lines && !$is_patch && $line =~ /^This reverts commit/) { $is_revert = 1; } if (!$in_header_lines && !$is_patch && $line =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) { $needs_fixes_tag = $1; } # Check Fixes: styles is correct if (!$in_header_lines && $line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) { my $tag = $1; my $orig_commit = $2; my $title; my $title_has_quotes = 0; $fixes_tag = 1; if (defined $3) { # Always strip leading/trailing parens then double quotes if existing $title = substr($3, 1, -1); if ($title =~ /^".*"$/) { $title = substr($title, 1, -1); $title_has_quotes = 1; } } else { $title = "commit title" } my $tag_case = not ($tag eq "Fixes:"); my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i); my $id_length = not ($orig_commit =~ /^[0-9a-f]{12,40}$/i); my $id_case = not ($orig_commit !~ /[A-F]/); my $id = "0123456789ab"; my ($cid, $ctitle) = git_commit_info($orig_commit, $id, $title); if (defined($cid) && ($ctitle ne $title || $tag_case || $tag_space || $id_length || $id_case || !$title_has_quotes)) { my $fixed = "Fixes: $cid (\"$ctitle\")"; if (WARN("BAD_FIXES_TAG", "Please use correct Fixes: style 'Fixes: <12+ chars of sha1> (\"\")' - ie: '$fixed'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = $fixed; } } } # Check email subject for common tools that don't need to be mentioned if ($in_header_lines && $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { WARN("EMAIL_SUBJECT", "A patch subject line should describe the change not the tool that found it\n" . $herecurr); } # Check for Gerrit Change-Ids not in any patch context if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { if (ERROR("GERRIT_CHANGE_ID", "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr, $rawline); } } # Check if the commit log is in a possible stack dump if ($in_commit_log && !$commit_log_possible_stack_dump && ($line =~ /^\s*(?:WARNING:|BUG:)/ || $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || # timestamp $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { # stack dump address styles $commit_log_possible_stack_dump = 1; } # Check for line lengths > 75 in commit log, warn once if ($in_commit_log && !$commit_log_long_line && length($line) > 75 && !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || # file delta changes $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || # filename then : $line =~ /^\s*(?:Fixes:|$link_tags_search|$signature_tags)/i || # A Fixes:, link or signature tag line $commit_log_possible_stack_dump)) { WARN("COMMIT_LOG_LONG_LINE", "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr); $commit_log_long_line = 1; } # Reset possible stack dump if a blank line is found if ($in_commit_log && $commit_log_possible_stack_dump && $line =~ /^\s*$/) { $commit_log_possible_stack_dump = 0; } # Check for odd tags before a URI/URL if ($in_commit_log && $line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) { if ($1 =~ /^v(?:ersion)?\d+/i) { WARN("COMMIT_LOG_VERSIONING", "Patch version information should be after the --- line\n" . $herecurr); } else { WARN("COMMIT_LOG_USE_LINK", "Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr); } } # Check for misuse of the link tags if ($in_commit_log && $line =~ /^\s*(\w+:)\s*(\S+)/) { my $tag = $1; my $value = $2; if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) { WARN("COMMIT_LOG_WRONG_LINK", "'$tag' should be followed by a public http(s) link\n" . $herecurr); } } # Check for lines starting with a # if ($in_commit_log && $line =~ /^#/) { if (WARN("COMMIT_COMMENT_SYMBOL", "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^/ /; } } # Check for git id commit length and improperly formed commit descriptions # A correctly formed commit description is: # commit <SHA-1 hash length 12+ chars> ("Complete commit subject") # with the commit subject '("' prefix and '")' suffix # This is a fairly compilicated block as it tests for what appears to be # bare SHA-1 hash with minimum length of 5. It also avoids several types of # possible SHA-1 matches. # A commit match can span multiple lines so this block attempts to find a # complete typical commit on a maximum of 3 lines if ($perl_version_ok && $in_commit_log && !$commit_log_possible_stack_dump && $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && $line !~ /^This reverts commit [0-9a-f]{7,40}/ && (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { my $init_char = "c"; my $orig_commit = ""; my $short = 1; my $long = 0; my $case = 1; my $space = 1; my $id = '0123456789ab'; my $orig_desc = "commit description"; my $description = ""; my $herectx = $herecurr; my $has_parens = 0; my $has_quotes = 0; my $input = $line; if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { for (my $n = 0; $n < 2; $n++) { if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { $orig_desc = $1; $has_parens = 1; # Always strip leading/trailing parens then double quotes if existing $orig_desc = substr($orig_desc, 1, -1); if ($orig_desc =~ /^".*"$/) { $orig_desc = substr($orig_desc, 1, -1); $has_quotes = 1; } last; } last if ($#lines < $linenr + $n); $input .= " " . trim($rawlines[$linenr + $n]); $herectx .= "$rawlines[$linenr + $n]\n"; } $herectx = $herecurr if (!$has_parens); } if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { $init_char = $1; $orig_commit = lc($2); $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { $orig_commit = lc($1); } ($id, $description) = git_commit_info($orig_commit, $id, $orig_desc); if (defined($id) && ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && $last_git_commit_id_linenr != $linenr - 1) { ERROR("GIT_COMMIT_ID", "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); } #don't report the next line if this line ends in commit and the sha1 hash is the next line $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); } # Check for mailing list archives other than lore.kernel.org if ($rawline =~ m{http.*\b$obsolete_archives}) { WARN("PREFER_LORE_ARCHIVE", "Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr); } # Check for added, moved or deleted files if (!$reported_maintainer_file && !$in_commit_log && ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && (defined($1) || defined($2))))) { $is_patch = 1; $reported_maintainer_file = 1; WARN("FILE_PATH_CHANGES", "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); } # Check for adding new DT bindings not in schema format if (!$in_commit_log && ($line =~ /^new file mode\s*\d+\s*$/) && ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { WARN("DT_SCHEMA_BINDING_PATCH", "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); } # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("CORRUPTED_PATCH", "patch seems to be corrupt (line wrapped?)\n" . $herecurr) if (!$emitted_corrupt++); } # UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php if (($realfile =~ /^$/ || $line =~ /^\+/) && $rawline !~ m/^$UTF8*$/) { my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); my $blank = copy_spacing($rawline); my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; my $hereptr = "$hereline$ptr\n"; CHK("INVALID_UTF8", "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); } # Check if it's the start of a commit log # (not a header line and we haven't seen the patch filename) if ($in_header_lines && $realfile =~ /^$/ && !($rawline =~ /^\s+(?:\S|$)/ || $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { $in_header_lines = 0; $in_commit_log = 1; $has_commit_log = 1; } # Check if there is UTF-8 in a commit log when a mail header has explicitly # declined it, i.e defined some charset where it is missing. if ($in_header_lines && $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && $1 !~ /utf-8/i) { $non_utf8_charset = 1; } if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && $rawline =~ /$NON_ASCII_UTF8/) { WARN("UTF8_BEFORE_PATCH", "8-bit UTF-8 used in possible commit log\n" . $herecurr); } # Check for absolute kernel paths in commit message if ($tree && $in_commit_log) { while ($line =~ m{(?:^|\s)(/\S*)}g) { my $file = $1; if ($file =~ m{^(.*?)(?::\d+)+:?$} && check_absolute_file($1, $herecurr)) { # } else { check_absolute_file($file, $herecurr); } } } # Check for various typo / spelling mistakes if (defined($misspellings) && ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { my $typo = $1; my $blank = copy_spacing($rawline); my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); my $hereptr = "$hereline$ptr\n"; my $typo_fix = $spelling_fix{lc($typo)}; $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); my $msg_level = \&WARN; $msg_level = \&CHK if ($file); if (&{$msg_level}("TYPO_SPELLING", "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && $fix) { $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; } } } # check for invalid commit id if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { my $id; my $description; ($id, $description) = git_commit_info($2, undef, undef); if (!defined($id)) { WARN("UNKNOWN_COMMIT_ID", "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); } } # check for repeated words separated by a single space # avoid false positive from list command eg, '-rw-r--r-- 1 root root' if (($rawline =~ /^\+/ || $in_commit_log) && $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { pos($rawline) = 1 if (!$in_commit_log); while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { my $first = $1; my $second = $2; my $start_pos = $-[1]; my $end_pos = $+[2]; if ($first =~ /(?:struct|union|enum)/) { pos($rawline) += length($first) + length($second) + 1; next; } next if (lc($first) ne lc($second)); next if ($first eq 'long'); # check for character before and after the word matches my $start_char = ''; my $end_char = ''; $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); next if ($start_char =~ /^\S$/); next if (index(" \t.,;?!", $end_char) == -1); # avoid repeating hex occurrences like 'ff ff fe 09 ...' if ($first =~ /\b[0-9a-f]{2,}\b/i) { next if (!exists($allow_repeated_words{lc($first)})); } if (WARN("REPEATED_WORD", "Possible repeated word: '$first'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; } } # if it's a repeated word on consecutive lines in a comment block if ($prevline =~ /$;+\s*$/ && $prevrawline =~ /($word_pattern)\s*$/) { my $last_word = $1; if ($rawline =~ /^\+\s*\*\s*$last_word /) { if (WARN("REPEATED_WORD", "Possible repeated word: '$last_word'\n" . $hereprev) && $fix) { $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; } } } } # ignore non-hunk lines and lines being removed next if (!$hunk_line || $line =~ /^-/); #trailing whitespace if ($line =~ /^\+.*\015/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (ERROR("DOS_LINE_ENDINGS", "DOS line endings\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/[\s\015]+$//; } } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (ERROR("TRAILING_WHITESPACE", "trailing whitespace\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/\s+$//; } $rpt_cleaners = 1; } # Check for FSF mailing addresses. if ($rawline =~ /\bwrite to the Free/i || $rawline =~ /\b675\s+Mass\s+Ave/i || $rawline =~ /\b59\s+Temple\s+Pl/i || $rawline =~ /\b51\s+Franklin\s+St/i) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; my $msg_level = \&ERROR; $msg_level = \&CHK if ($file); &{$msg_level}("FSF_MAILING_ADDRESS", "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) } # check for Kconfig help text having a real description # Only applies when adding the entry originally, after that we do not have # sufficient context to determine whether it is indeed long enough. if ($realfile =~ /Kconfig/ && # 'choice' is usually the last thing on the line (though # Kconfig supports named choices), so use a word boundary # (\b) rather than a whitespace character (\s) $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { my $ln = $linenr; my $needs_help = 0; my $has_help = 0; my $help_length = 0; while (defined $lines[$ln]) { my $f = $lines[$ln++]; next if ($f =~ /^-/); last if ($f !~ /^[\+ ]/); # !patch context if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { $needs_help = 1; next; } if ($f =~ /^\+\s*help\s*$/) { $has_help = 1; next; } $f =~ s/^.//; # strip patch context [+ ] $f =~ s/#.*//; # strip # directives $f =~ s/^\s+//; # strip leading blanks next if ($f =~ /^$/); # skip blank lines # At the end of this Kconfig block: # This only checks context lines in the patch # and so hopefully shouldn't trigger false # positives, even though some of these are # common words in help texts if ($f =~ /^(?:config|menuconfig|choice|endchoice| if|endif|menu|endmenu|source)\b/x) { last; } $help_length++ if ($has_help); } if ($needs_help && $help_length < $min_conf_desc_length) { my $stat_real = get_stat_real($linenr, $ln - 1); WARN("CONFIG_DESCRIPTION", "please write a help paragraph that fully describes the config symbol\n" . "$here\n$stat_real\n"); } } # check MAINTAINERS entries if ($realfile =~ /^MAINTAINERS$/) { # check MAINTAINERS entries for the right form if ($rawline =~ /^\+[A-Z]:/ && $rawline !~ /^\+[A-Z]:\t\S/) { if (WARN("MAINTAINERS_STYLE", "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; } } # check MAINTAINERS entries for the right ordering too my $preferred_order = 'MRLSWQBCPTFXNK'; if ($rawline =~ /^\+[A-Z]:/ && $prevrawline =~ /^[\+ ][A-Z]:/) { $rawline =~ /^\+([A-Z]):\s*(.*)/; my $cur = $1; my $curval = $2; $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; my $prev = $1; my $prevval = $2; my $curindex = index($preferred_order, $cur); my $previndex = index($preferred_order, $prev); if ($curindex < 0) { WARN("MAINTAINERS_STYLE", "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); } else { if ($previndex >= 0 && $curindex < $previndex) { WARN("MAINTAINERS_STYLE", "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); } elsif ((($prev eq 'F' && $cur eq 'F') || ($prev eq 'X' && $cur eq 'X')) && ($prevval cmp $curval) > 0) { WARN("MAINTAINERS_STYLE", "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); } } } } if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { my $flag = $1; my $replacement = { 'EXTRA_AFLAGS' => 'asflags-y', 'EXTRA_CFLAGS' => 'ccflags-y', 'EXTRA_CPPFLAGS' => 'cppflags-y', 'EXTRA_LDFLAGS' => 'ldflags-y', }; WARN("DEPRECATED_VARIABLE", "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); } # check for DT compatible documentation if (defined $root && (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; my $dt_path = $root . "/Documentation/devicetree/bindings/"; my $vp_file = $dt_path . "vendor-prefixes.yaml"; foreach my $compat (@compats) { my $compat2 = $compat; $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; my $compat3 = $compat; $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; `grep -Erq "$compat|$compat2|$compat3" $dt_path`; if ( $? >> 8 ) { WARN("UNDOCUMENTED_DT_STRING", "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); } next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; my $vendor = $1; `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; if ( $? >> 8 ) { WARN("UNDOCUMENTED_DT_STRING", "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); } } } # check for using SPDX license tag at beginning of files if ($realline == $checklicenseline) { if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { $checklicenseline = 2; } elsif ($rawline =~ /^\+/) { my $comment = ""; if ($realfile =~ /\.(h|s|S)$/) { $comment = '/*'; } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) { $comment = '//'; } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { $comment = '#'; } elsif ($realfile =~ /\.rst$/) { $comment = '..'; } # check SPDX comment style for .[chsS] files if ($realfile =~ /\.[chsS]$/ && $rawline =~ /SPDX-License-Identifier:/ && $rawline !~ m@^\+\s*\Q$comment\E\s*@) { WARN("SPDX_LICENSE_TAG", "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); } if ($comment !~ /^$/ && $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { WARN("SPDX_LICENSE_TAG", "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { my $spdx_license = $1; if (!is_SPDX_License_valid($spdx_license)) { WARN("SPDX_LICENSE_TAG", "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); } if ($realfile =~ m@^Documentation/devicetree/bindings/@ && $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); if (&{$msg_level}("SPDX_LICENSE_TAG", "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; } } if ($realfile =~ m@^include/dt-bindings/@ && $spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) { WARN("SPDX_LICENSE_TAG", "DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr); } } } } # check for embedded filenames if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) { WARN("EMBEDDED_FILENAME", "It's generally not useful to have the filename in the file\n" . $herecurr); } # check we are in a valid source file if not then ignore this hunk next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/); # check for using SPDX-License-Identifier on the wrong line number if ($realline != $checklicenseline && $rawline =~ /\bSPDX-License-Identifier:/ && substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { WARN("SPDX_LICENSE_TAG", "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); } # line length limit (with some exclusions) # # There are a few types of lines that may extend beyond $max_line_length: # logging functions like pr_info that end in a string # lines with a single string # #defines that are a single string # lines with an RFC3986 like URL # # There are 3 different line length message types: # LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length # LONG_LINE_STRING a string starts before but extends beyond $max_line_length # LONG_LINE all other lines longer than $max_line_length # # if LONG_LINE is ignored, the other 2 types are also ignored # if ($line =~ /^\+/ && $length > $max_line_length) { my $msg_type = "LONG_LINE"; # Check the allowed long line types first # logging functions that end in a string that starts # before $max_line_length if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { $msg_type = ""; # lines with only strings (w/ possible termination) # #defines with only strings } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { $msg_type = ""; # More special cases } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { $msg_type = ""; # URL ($rawline is used in case the URL is in a comment) } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { $msg_type = ""; # Otherwise set the alternate message types # a comment starts before $max_line_length } elsif ($line =~ /($;[\s$;]*)$/ && length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { $msg_type = "LONG_LINE_COMMENT" # a quoted string starts before $max_line_length } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { $msg_type = "LONG_LINE_STRING" } if ($msg_type ne "" && show_type("LONG_LINE") && show_type($msg_type)) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}($msg_type, "line length of $length exceeds $max_line_length columns\n" . $herecurr); } } # check for adding lines without a newline. if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { if (WARN("MISSING_EOF_NEWLINE", "adding a line without newline at end of file\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr+1, "No newline at end of file"); } } # check for .L prefix local symbols in .S files if ($realfile =~ /\.S$/ && $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { WARN("AVOID_L_PREFIX", "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr); } # check we are in a valid source file C or perl if not then ignore this hunk next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); # at the beginning of a line any tabs must come first and anything # more than $tabsize must use tabs. if ($rawline =~ /^\+\s* \t\s*\S/ || $rawline =~ /^\+\s* \s*/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; $rpt_cleaners = 1; if (ERROR("CODE_INDENT", "code indent should use tabs where possible\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; } } # check for space before tabs. if ($rawline =~ /^\+/ && $rawline =~ / \t/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (WARN("SPACE_BEFORE_TAB", "please, no space before tabs\n" . $herevet) && $fix) { while ($fixed[$fixlinenr] =~ s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} while ($fixed[$fixlinenr] =~ s/(^\+.*) +\t/$1\t/) {} } } # check for assignments on the start of a line if ($sline =~ /^\+\s+($Assignment)[^=]/) { my $operator = $1; if (CHK("ASSIGNMENT_CONTINUATIONS", "Assignment operator '$1' should be on the previous line\n" . $hereprev) && $fix && $prevrawline =~ /^\+/) { # add assignment operator to the previous line, remove from current line $fixed[$fixlinenr - 1] .= " $operator"; $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; } } # check for && or || at the start of a line if ($rawline =~ /^\+\s*(&&|\|\|)/) { my $operator = $1; if (CHK("LOGICAL_CONTINUATIONS", "Logical continuations should be on the previous line\n" . $hereprev) && $fix && $prevrawline =~ /^\+/) { # insert logical operator at last non-comment, non-whitepsace char on previous line $prevline =~ /[\s$;]*$/; my $line_end = substr($prevrawline, $-[0]); $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; } } # check indentation starts on a tab stop if ($perl_version_ok && $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { my $indent = length($1); if ($indent % $tabsize) { if (WARN("TABSTOP", "Statements should start on a tabstop\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; } } } # check multi-line statement indentation matches previous line if ($perl_version_ok && $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { $prevline =~ /^\+(\t*)(.*)$/; my $oldindent = $1; my $rest = $2; my $pos = pos_last_openparen($rest); if ($pos >= 0) { $line =~ /^(\+| )([ \t]*)/; my $newindent = $2; my $goodtabindent = $oldindent . "\t" x ($pos / $tabsize) . " " x ($pos % $tabsize); my $goodspaceindent = $oldindent . " " x $pos; if ($newindent ne $goodtabindent && $newindent ne $goodspaceindent) { if (CHK("PARENTHESIS_ALIGNMENT", "Alignment should match open parenthesis\n" . $hereprev) && $fix && $line =~ /^\+/) { $fixed[$fixlinenr] =~ s/^\+[ \t]*/\+$goodtabindent/; } } } } # check for space after cast like "(int) foo" or "(struct foo) bar" # avoid checking a few false positives: # "sizeof(<type>)" or "__alignof__(<type>)" # function pointer declarations like "(*foo)(int) = bar;" # structure definitions like "(struct foo) { 0 };" # multiline macros that define functions # known attributes or the __attribute__ keyword if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { if (CHK("SPACING", "No space is necessary after a cast\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\(\s*$Type\s*\))[ \t]+/$1/; } } # Block comments use * on subsequent lines if ($prevline =~ /$;[ \t]*$/ && #ends in comment $prevrawline =~ /^\+.*?\/\*/ && #starting /* $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ $rawline =~ /^\+/ && #line is new $rawline !~ /^\+[ \t]*\*/) { #no leading * WARN("BLOCK_COMMENT_STYLE", "Block comments use * on subsequent lines\n" . $hereprev); } # Block comments use */ on trailing lines if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ WARN("BLOCK_COMMENT_STYLE", "Block comments use a trailing */ on a separate line\n" . $herecurr); } # Block comment * alignment if ($prevline =~ /$;[ \t]*$/ && #ends in comment $line =~ /^\+[ \t]*$;/ && #leading comment $rawline =~ /^\+[ \t]*\*/ && #leading * (($prevrawline =~ /^\+.*?\/\*/ && #leading /* $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ $prevrawline =~ /^\+[ \t]*\*/)) { #leading * my $oldindent; $prevrawline =~ m@^\+([ \t]*/?)\*@; if (defined($1)) { $oldindent = expand_tabs($1); } else { $prevrawline =~ m@^\+(.*/?)\*@; $oldindent = expand_tabs($1); } $rawline =~ m@^\+([ \t]*)\*@; my $newindent = $1; $newindent = expand_tabs($newindent); if (length($oldindent) ne length($newindent)) { WARN("BLOCK_COMMENT_STYLE", "Block comments should align the * on each line\n" . $hereprev); } } # check for missing blank lines after struct/union declarations # with exceptions for various attributes and macros if ($prevline =~ /^[\+ ]};?\s*$/ && $line =~ /^\+/ && !($line =~ /^\+\s*$/ || $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ || $line =~ /^\+\s*MODULE_/i || $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || $line =~ /^\+[a-z_]*init/ || $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || $line =~ /^\+\s*DECLARE/ || $line =~ /^\+\s*builtin_[\w_]*driver/ || $line =~ /^\+\s*__setup/)) { if (CHK("LINE_SPACING", "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && $fix) { fix_insert_line($fixlinenr, "\+"); } } # check for multiple consecutive blank lines if ($prevline =~ /^[\+ ]\s*$/ && $line =~ /^\+\s*$/ && $last_blank_line != ($linenr - 1)) { if (CHK("LINE_SPACING", "Please don't use multiple blank lines\n" . $hereprev) && $fix) { fix_delete_line($fixlinenr, $rawline); } $last_blank_line = $linenr; } # check for missing blank lines after declarations # (declarations must have the same indentation and not be at the start of line) if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { # use temporaries my $sl = $sline; my $pl = $prevline; # remove $Attribute/$Sparse uses to simplify comparisons $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || # function pointer declarations $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || # foo bar; where foo is some local typedef or #define $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || # known declaration macros $pl =~ /^\+\s+$declaration_macros/) && # for "else if" which can look like "$Ident $Ident" !($pl =~ /^\+\s+$c90_Keywords\b/ || # other possible extensions of declaration lines $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || # not starting a section or a macro "\" extended line $pl =~ /(?:\{\s*|\\)$/) && # looks like a declaration !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || # function pointer declarations $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || # foo bar; where foo is some local typedef or #define $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || # known declaration macros $sl =~ /^\+\s+$declaration_macros/ || # start of struct or union or enum $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || # start or end of block or continuation of declaration $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || # bitfield continuation $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || # other possible extensions of declaration lines $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { if (WARN("LINE_SPACING", "Missing a blank line after declarations\n" . $hereprev) && $fix) { fix_insert_line($fixlinenr, "\+"); } } } # check for spaces at the beginning of a line. # Exceptions: # 1) within comments # 2) indented preprocessor commands # 3) hanging labels if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (WARN("LEADING_SPACE", "please, no spaces at the start of a line\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; } } # check we are in a valid C source file if not then ignore this hunk next if ($realfile !~ /\.(h|c)$/); # check for unusual line ending [ or ( if ($line =~ /^\+.*([\[\(])\s*$/) { CHK("OPEN_ENDED_LINE", "Lines should not end with a '$1'\n" . $herecurr); } # check if this appears to be the start function declaration, save the name if ($sline =~ /^\+\{\s*$/ && $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { $context_function = $1; } # check if this appears to be the end of function declaration if ($sline =~ /^\+\}\s*$/) { undef $context_function; } # check indentation of any line with a bare else # (but not if it is a multiple line "if (foo) return bar; else return baz;") # if the previous line is a break or return and is indented 1 tab more... if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { my $tabs = length($1) + 1; if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && defined $lines[$linenr] && $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { WARN("UNNECESSARY_ELSE", "else is not generally useful after a break or return\n" . $hereprev); } } # check indentation of a line with a break; # if the previous line is a goto, return or break # and is indented the same # of tabs if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { my $tabs = $1; if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { if (WARN("UNNECESSARY_BREAK", "break is not useful after a $1\n" . $hereprev) && $fix) { fix_delete_line($fixlinenr, $rawline); } } } # check for RCS/CVS revision markers if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { WARN("CVS_KEYWORD", "CVS style keyword markers, these will _not_ be updated\n". $herecurr); } # check for old HOTPLUG __dev<foo> section markings if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { WARN("HOTPLUG_SECTION", "Using $1 is unnecessary\n" . $herecurr); } # Check for potential 'bare' types my ($stat, $cond, $line_nr_next, $remain_next, $off_next, $realline_next); #print "LINE<$line>\n"; if ($linenr > $suppress_statement && $realcnt && $sline =~ /.\s*\S/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0); $stat =~ s/\n./\n /g; $cond =~ s/\n./\n /g; #print "linenr<$linenr> <$stat>\n"; # If this statement has no statement boundaries within # it there is no point in retrying a statement scan # until we hit end of it. my $frag = $stat; $frag =~ s/;+\s*$//; if ($frag !~ /(?:{|;)/) { #print "skip<$line_nr_next>\n"; $suppress_statement = $line_nr_next; } # Find the real next line. $realline_next = $line_nr_next; if (defined $realline_next && (!defined $lines[$realline_next - 1] || substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { $realline_next++; } my $s = $stat; $s =~ s/{.*$//s; # Ignore goto labels. if ($s =~ /$Ident:\*$/s) { # Ignore functions being called } elsif ($s =~ /^.\s*$Ident\s*\(/s) { } elsif ($s =~ /^.\s*else\b/s) { # declarations always start with types } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { my $type = $1; $type =~ s/\s+/ /g; possible($type, "A:" . $s); # definitions in global scope can only start with types } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { possible($1, "B:" . $s); } # any (foo ... *) is a pointer cast, and foo is a type while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { possible($1, "C:" . $s); } # Check for any sort of function declaration. # int foo(something bar, other baz); # void (*store_gdt)(x86_descr_ptr *); if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { my ($name_len) = length($1); my $ctx = $s; substr($ctx, 0, $name_len + 1, ''); $ctx =~ s/\)[^\)]*$//; for my $arg (split(/\s*,\s*/, $ctx)) { if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { possible($1, "D:" . $s); } } } } # # Checks which may be anchored in the context. # # Check for switch () and associated case and default # statements should be at the same indent. if ($line=~/\bswitch\s*\(.*\)/) { my $err = ''; my $sep = ''; my @ctx = ctx_block_outer($linenr, $realcnt); shift(@ctx); for my $ctx (@ctx) { my ($clen, $cindent) = line_stats($ctx); if ($ctx =~ /^\+\s*(case\s+|default:)/ && $indent != $cindent) { $err .= "$sep$ctx\n"; $sep = ''; } else { $sep = "[...]\n"; } } if ($err ne '') { ERROR("SWITCH_CASE_INDENT_LEVEL", "switch and case should be at the same indent\n$hereline$err"); } } # if/while/etc brace do not go on next line, unless defining a do while loop, # or if that brace on the next line is for something else if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { my $pre_ctx = "$1$2"; my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); if ($line =~ /^\+\t{6,}/) { WARN("DEEP_INDENTATION", "Too many leading tabs - consider code refactoring\n" . $herecurr); } my $ctx_cnt = $realcnt - $#ctx - 1; my $ctx = join("\n", @ctx); my $ctx_ln = $linenr; my $ctx_skip = $realcnt; while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && defined $lines[$ctx_ln - 1] && $lines[$ctx_ln - 1] =~ /^-/)) { ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); $ctx_ln++; } #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { ERROR("OPEN_BRACE", "that open brace { should be on the previous line\n" . "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); } if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && $ctx =~ /\)\s*\;\s*$/ && defined $lines[$ctx_ln - 1]) { my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); if ($nindent > $indent) { WARN("TRAILING_SEMICOLON", "trailing semicolon indicates no statements, indent implies otherwise\n" . "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); } } } # Check relative indent for conditionals and blocks. if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0) if (!defined $stat); my ($s, $c) = ($stat, $cond); substr($s, 0, length($c), ''); # remove inline comments $s =~ s/$;/ /g; $c =~ s/$;/ /g; # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; # Make sure we remove the line prefixes as we have # none on the first line, and are going to readd them # where necessary. $s =~ s/\n./\n/gs; while ($s =~ /\n\s+\\\n/) { $cond_lines += $s =~ s/\n\s+\\\n/\n/g; } # We want to check the first line inside the block # starting at the end of the conditional, so remove: # 1) any blank line termination # 2) any opening brace { on end of the line # 3) any do (...) { my $continuation = 0; my $check = 0; $s =~ s/^.*\bdo\b//; $s =~ s/^\s*{//; if ($s =~ s/^\s*\\//) { $continuation = 1; } if ($s =~ s/^\s*?\n//) { $check = 1; $cond_lines++; } # Also ignore a loop construct at the end of a # preprocessor statement. if (($prevline =~ /^.\s*#\s*define\s/ || $prevline =~ /\\\s*$/) && $continuation == 0) { $check = 0; } my $cond_ptr = -1; $continuation = 0; while ($cond_ptr != $cond_lines) { $cond_ptr = $cond_lines; # If we see an #else/#elif then the code # is not linear. if ($s =~ /^\s*\#\s*(?:else|elif)/) { $check = 0; } # Ignore: # 1) blank lines, they should be at 0, # 2) preprocessor lines, and # 3) labels. if ($continuation || $s =~ /^\s*?\n/ || $s =~ /^\s*#\s*?/ || $s =~ /^\s*$Ident\s*:/) { $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; if ($s =~ s/^.*?\n//) { $cond_lines++; } } } my (undef, $sindent) = line_stats("+" . $s); my $stat_real = raw_line($linenr, $cond_lines); # Check if either of these lines are modified, else # this is not this patch's fault. if (!defined($stat_real) || $stat !~ /^\+/ && $stat_real !~ /^\+/) { $check = 0; } if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; if ($check && $s ne '' && (($sindent % $tabsize) != 0 || ($sindent < $indent) || ($sindent == $indent && ($s !~ /^\s*(?:\}|\{|else\b)/)) || ($sindent > $indent + $tabsize))) { WARN("SUSPECT_CODE_INDENT", "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); } } # Track the 'values' across context and added lines. my $opline = $line; $opline =~ s/^./ /; my ($curr_values, $curr_vars) = annotate_values($opline . "\n", $prev_values); $curr_values = $prev_values . $curr_values; if ($dbg_values) { my $outline = $opline; $outline =~ s/\t/ /g; print "$linenr > .$outline\n"; print "$linenr > $curr_values\n"; print "$linenr > $curr_vars\n"; } $prev_values = substr($curr_values, -1); #ignore lines not being added next if ($line =~ /^[^\+]/); # check for self assignments used to avoid compiler warnings # e.g.: int foo = foo, *bar = NULL; # struct foo bar = *(&(bar)); if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { my $var = $1; if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { WARN("SELF_ASSIGNMENT", "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); } } # check for dereferences that span multiple lines if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { $prevline =~ /($Lval\s*(?:\.|->))\s*$/; my $ref = $1; $line =~ /^.\s*($Lval)/; $ref .= $1; $ref =~ s/\s//g; WARN("MULTILINE_DEREFERENCE", "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); } # check for declarations of signed or unsigned without int while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { my $type = $1; my $var = $2; $var = "" if (!defined $var); if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { my $sign = $1; my $pointer = $2; $pointer = "" if (!defined $pointer); if (WARN("UNSPECIFIED_INT", "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && $fix) { my $decl = trim($sign) . " int "; my $comp_pointer = $pointer; $comp_pointer =~ s/\s//g; $decl .= $comp_pointer; $decl = rtrim($decl) if ($var eq ""); $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; } } } # TEST: allow direct testing of the type matcher. if ($dbg_type) { if ($line =~ /^.\s*$Declare\s*$/) { ERROR("TEST_TYPE", "TEST: is type\n" . $herecurr); } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { ERROR("TEST_NOT_TYPE", "TEST: is not type ($1 is)\n". $herecurr); } next; } # TEST: allow direct testing of the attribute matcher. if ($dbg_attr) { if ($line =~ /^.\s*$Modifier\s*$/) { ERROR("TEST_ATTR", "TEST: is attr\n" . $herecurr); } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { ERROR("TEST_NOT_ATTR", "TEST: is not attr ($1 is)\n". $herecurr); } next; } # check for initialisation to aggregates open brace on the next line if ($line =~ /^.\s*{/ && $prevline =~ /(?:^|[^=])=\s*$/) { if (ERROR("OPEN_BRACE", "that open brace { should be on the previous line\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; $fixedline =~ s/\s*=\s*$/ = {/; fix_insert_line($fixlinenr, $fixedline); $fixedline = $line; $fixedline =~ s/^(.\s*)\{\s*/$1/; fix_insert_line($fixlinenr, $fixedline); } } # # Checks which are anchored on the added line. # # check for malformed paths in #include statements (uses RAW line) if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { my $path = $1; if ($path =~ m{//}) { ERROR("MALFORMED_INCLUDE", "malformed #include filename\n" . $herecurr); } if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { ERROR("UAPI_INCLUDE", "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); } } # no C99 // comments if ($line =~ m{//}) { if (ERROR("C99_COMMENTS", "do not use C99 // comments\n" . $herecurr) && $fix) { my $line = $fixed[$fixlinenr]; if ($line =~ /\/\/(.*)$/) { my $comment = trim($1); $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; } } } # Remove C99 comments. $line =~ s@//.*@@; $opline =~ s@//.*@@; # EXPORT_SYMBOL should immediately follow the thing it is exporting, consider # the whole statement. #print "APW <$lines[$realline_next - 1]>\n"; if (defined $realline_next && exists $lines[$realline_next - 1] && !defined $suppress_export{$realline_next} && ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { # Handle definitions which produce identifiers with # a prefix: # XXX(foo); # EXPORT_SYMBOL(something_foo); my $name = $1; $name =~ s/^\s*($Ident).*/$1/; if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && $name =~ /^${Ident}_$2/) { #print "FOO C name<$name>\n"; $suppress_export{$realline_next} = 1; } elsif ($stat !~ /(?: \n.}\s*$| ^.DEFINE_$Ident\(\Q$name\E\)| ^.DECLARE_$Ident\(\Q$name\E\)| ^.LIST_HEAD\(\Q$name\E\)| ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() )/x) { #print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; $suppress_export{$realline_next} = 2; } else { $suppress_export{$realline_next} = 1; } } if (!defined $suppress_export{$linenr} && $prevline =~ /^.\s*$/ && ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { #print "FOO B <$lines[$linenr - 1]>\n"; $suppress_export{$linenr} = 2; } if (defined $suppress_export{$linenr} && $suppress_export{$linenr} == 2) { WARN("EXPORT_SYMBOL", "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); } # check for global initialisers. if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && !exclude_global_initialisers($realfile)) { if (ERROR("GLOBAL_INITIALISERS", "do not initialise globals to $1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; } } # check for static initialisers. if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { if (ERROR("INITIALISED_STATIC", "do not initialise statics to $1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; } } # check for misordered declarations of char/short/int/long with signed/unsigned while ($sline =~ m{(\b$TypeMisordered\b)}g) { my $tmp = trim($1); WARN("MISORDERED_TYPE", "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); } # check for unnecessary <signed> int declarations of short/long/long long while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { my $type = trim($1); next if ($type !~ /\bint\b/); next if ($type !~ /\b(?:short|long\s+long|long)\b/); my $new_type = $type; $new_type =~ s/\b\s*int\s*\b/ /; $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; $new_type =~ s/^const\s+//; $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); $new_type = "const $new_type" if ($type =~ /^const\b/); $new_type =~ s/\s+/ /g; $new_type = trim($new_type); if (WARN("UNNECESSARY_INT", "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; } } # check for static const char * arrays. if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { WARN("STATIC_CONST_CHAR_ARRAY", "static const char * array should probably be static const char * const\n" . $herecurr); } # check for initialized const char arrays that should be static const if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { if (WARN("STATIC_CONST_CHAR_ARRAY", "const array should probably be static const\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; } } # check for static char foo[] = "bar" declarations. if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { WARN("STATIC_CONST_CHAR_ARRAY", "static char array declaration should probably be static const char\n" . $herecurr); } # check for const <foo> const where <foo> is not a pointer or array type if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { my $found = $1; if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { WARN("CONST_CONST", "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { WARN("CONST_CONST", "'const $found const' should probably be 'const $found'\n" . $herecurr); } } # check for const static or static <non ptr type> const declarations # prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { if (WARN("STATIC_CONST", "Move const after static - use 'static const $1'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; } } # check for non-global char *foo[] = {"bar", ...} declarations. if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { WARN("STATIC_CONST_CHAR_ARRAY", "char * array declaration might be better as static const\n" . $herecurr); } # check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { my $array = $1; if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { my $array_div = $1; if (WARN("ARRAY_SIZE", "Prefer ARRAY_SIZE($array)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; } } } # check for function declarations without arguments like "int foo()" if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { if (ERROR("FUNCTION_WITHOUT_ARGS", "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; } } # check for new typedefs, only function parameters and sparse annotations # make sense. if ($line =~ /\btypedef\s/ && $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && $line !~ /\b$typeTypedefs\b/ && $line !~ /\b__bitwise\b/) { WARN("NEW_TYPEDEFS", "do not add new typedefs\n" . $herecurr); } # * goes on variable not on type # (char*[ const]) while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { #print "AA<$1>\n"; my ($ident, $from, $to) = ($1, $2, $2); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } ## print "1: from<$from> to<$to> ident<$ident>\n"; if ($from ne $to) { if (ERROR("POINTER_LOCATION", "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && $fix) { my $sub_from = $ident; my $sub_to = $ident; $sub_to =~ s/\Q$from\E/$to/; $fixed[$fixlinenr] =~ s@\Q$sub_from\E@$sub_to@; } } } while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { #print "BB<$1>\n"; my ($match, $from, $to, $ident) = ($1, $2, $2, $3); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } # Modifiers should have spaces. $to =~ s/(\b$Modifier$)/$1 /; ## print "2: from<$from> to<$to> ident<$ident>\n"; if ($from ne $to && $ident !~ /^$Modifier$/) { if (ERROR("POINTER_LOCATION", "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && $fix) { my $sub_from = $match; my $sub_to = $match; $sub_to =~ s/\Q$from\E/$to/; $fixed[$fixlinenr] =~ s@\Q$sub_from\E@$sub_to@; } } } # do not use BUG() or variants if ($line =~ /\b(?!AA_|BUILD_|DCCP_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}("AVOID_BUG", "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr); } # avoid LINUX_VERSION_CODE if ($line =~ /\bLINUX_VERSION_CODE\b/) { WARN("LINUX_VERSION_CODE", "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); } # check for uses of printk_ratelimit if ($line =~ /\bprintk_ratelimit\s*\(/) { WARN("PRINTK_RATELIMITED", "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); } # printk should use KERN_* levels if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { WARN("PRINTK_WITHOUT_KERN_LEVEL", "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); } # prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { my $printk = $1; my $modifier = $2; my $orig = $3; $modifier = "" if (!defined($modifier)); my $level = lc($orig); $level = "warn" if ($level eq "warning"); my $level2 = $level; $level2 = "dbg" if ($level eq "debug"); $level .= $modifier; $level2 .= $modifier; WARN("PREFER_PR_LEVEL", "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); } # prefer dev_<level> to dev_printk(KERN_<LEVEL> if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { my $orig = $1; my $level = lc($orig); $level = "warn" if ($level eq "warning"); $level = "dbg" if ($level eq "debug"); WARN("PREFER_DEV_LEVEL", "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); } # trace_printk should not be used in production code. if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { WARN("TRACE_PRINTK", "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); } # ENOSYS means "bad syscall nr" and nothing else. This will have a small # number of false positives, but assembly files are not checked, so at # least the arch entry code will not trigger this warning. if ($line =~ /\bENOSYS\b/) { WARN("ENOSYS", "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); } # ENOTSUPP is not a standard error code and should be avoided in new patches. # Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. # Similarly to ENOSYS warning a small number of false positives is expected. if (!$file && $line =~ /\bENOTSUPP\b/) { if (WARN("ENOTSUPP", "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; } } # function brace can't be on same line, except for #defines of do while, # or if closed on same line if ($perl_version_ok && $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && $sline !~ /\#\s*define\b.*do\s*\{/ && $sline !~ /}/) { if (ERROR("OPEN_BRACE", "open brace '{' following function definitions go on the next line\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr, $rawline); my $fixed_line = $rawline; $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; my $line1 = $1; my $line2 = $2; fix_insert_line($fixlinenr, ltrim($line1)); fix_insert_line($fixlinenr, "\+{"); if ($line2 !~ /^\s*$/) { fix_insert_line($fixlinenr, "\+\t" . trim($line2)); } } } # open braces for enum, union and struct go on the same line. if ($line =~ /^.\s*{/ && $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { if (ERROR("OPEN_BRACE", "open brace '{' following $1 go on the same line\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = rtrim($prevrawline) . " {"; fix_insert_line($fixlinenr, $fixedline); $fixedline = $rawline; $fixedline =~ s/^(.\s*)\{\s*/$1\t/; if ($fixedline !~ /^\+\s*$/) { fix_insert_line($fixlinenr, $fixedline); } } } # missing space after union, struct or enum definition if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { if (WARN("SPACING", "missing space after $1 definition\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; } } # Function pointer declarations # check spacing between type, funcptr, and args # canonical declaration is "type (*funcptr)(args...)" if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { my $declare = $1; my $pre_pointer_space = $2; my $post_pointer_space = $3; my $funcname = $4; my $post_funcname_space = $5; my $pre_args_space = $6; # the $Declare variable will capture all spaces after the type # so check it for a missing trailing missing space but pointer return types # don't need a space so don't warn for those. my $post_declare_space = ""; if ($declare =~ /(\s+)$/) { $post_declare_space = $1; $declare = rtrim($declare); } if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { WARN("SPACING", "missing space after return type\n" . $herecurr); $post_declare_space = " "; } # unnecessary space "type (*funcptr)(args...)" # This test is not currently implemented because these declarations are # equivalent to # int foo(int bar, ...) # and this is form shouldn't/doesn't generate a checkpatch warning. # # elsif ($declare =~ /\s{2,}$/) { # WARN("SPACING", # "Multiple spaces after return type\n" . $herecurr); # } # unnecessary space "type ( *funcptr)(args...)" if (defined $pre_pointer_space && $pre_pointer_space =~ /^\s/) { WARN("SPACING", "Unnecessary space after function pointer open parenthesis\n" . $herecurr); } # unnecessary space "type (* funcptr)(args...)" if (defined $post_pointer_space && $post_pointer_space =~ /^\s/) { WARN("SPACING", "Unnecessary space before function pointer name\n" . $herecurr); } # unnecessary space "type (*funcptr )(args...)" if (defined $post_funcname_space && $post_funcname_space =~ /^\s/) { WARN("SPACING", "Unnecessary space after function pointer name\n" . $herecurr); } # unnecessary space "type (*funcptr) (args...)" if (defined $pre_args_space && $pre_args_space =~ /^\s/) { WARN("SPACING", "Unnecessary space before function pointer arguments\n" . $herecurr); } if (show_type("SPACING") && $fix) { $fixed[$fixlinenr] =~ s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; } } # check for spacing round square brackets; allowed: # 1. with a type on the left -- int [] a; # 2. at the beginning of a line for slice initialisers -- [0...10] = 5, # 3. inside a curly brace -- = { [0...10] = 5 } while ($line =~ /(.*?\s)\[/g) { my ($where, $prefix) = ($-[1], $1); if ($prefix !~ /$Type\s+$/ && ($where != 0 || $prefix !~ /^.\s+$/) && $prefix !~ /[{,:]\s+$/) { if (ERROR("BRACKET_SPACE", "space prohibited before open square bracket '['\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+.*?)\s+\[/$1\[/; } } } # check for spaces between functions and their parentheses. while ($line =~ /($Ident)\s+\(/g) { my $name = $1; my $ctx_before = substr($line, 0, $-[1]); my $ctx = "$ctx_before$name"; # Ignore those directives where spaces _are_ permitted. if ($name =~ /^(?: if|for|while|switch|return|case| volatile|__volatile__| __attribute__|format|__extension__| asm|__asm__|scoped_guard)$/x) { # cpp #define statements have non-optional spaces, ie # if there is a space between the name and the open # parenthesis it is simply not a parameter group. } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { # cpp #elif statement condition may start with a ( } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { # If this whole things ends with a type its most # likely a typedef for a function. } elsif ($ctx =~ /$Type$/) { } else { if (WARN("SPACING", "space prohibited between function name and open parenthesis '('\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$name\s+\(/$name\(/; } } } # Check operator spacing. if (!($line=~/\#\s*include/)) { my $fixed_line = ""; my $line_fixed = 0; my $ops = qr{ <<=|>>=|<=|>=|==|!=| \+=|-=|\*=|\/=|%=|\^=|\|=|&=| =>|->|<<|>>|<|>|=|!|~| &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| \?:|\?|: }x; my @elements = split(/($ops|;)/, $opline); ## print("element count: <" . $#elements . ">\n"); ## foreach my $el (@elements) { ## print("el: <$el>\n"); ## } my @fix_elements = (); my $off = 0; foreach my $el (@elements) { push(@fix_elements, substr($rawline, $off, length($el))); $off += length($el); } $off = 0; my $blank = copy_spacing($opline); my $last_after = -1; for (my $n = 0; $n < $#elements; $n += 2) { my $good = $fix_elements[$n] . $fix_elements[$n + 1]; ## print("n: <$n> good: <$good>\n"); $off += length($elements[$n]); # Pick up the preceding and succeeding characters. my $ca = substr($opline, 0, $off); my $cc = ''; if (length($opline) >= ($off + length($elements[$n + 1]))) { $cc = substr($opline, $off + length($elements[$n + 1])); } my $cb = "$ca$;$cc"; my $a = ''; $a = 'V' if ($elements[$n] ne ''); $a = 'W' if ($elements[$n] =~ /\s$/); $a = 'C' if ($elements[$n] =~ /$;$/); $a = 'B' if ($elements[$n] =~ /(\[|\()$/); $a = 'O' if ($elements[$n] eq ''); $a = 'E' if ($ca =~ /^\s*$/); my $op = $elements[$n + 1]; my $c = ''; if (defined $elements[$n + 2]) { $c = 'V' if ($elements[$n + 2] ne ''); $c = 'W' if ($elements[$n + 2] =~ /^\s/); $c = 'C' if ($elements[$n + 2] =~ /^$;/); $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); $c = 'O' if ($elements[$n + 2] eq ''); $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); } else { $c = 'E'; } my $ctx = "${a}x${c}"; my $at = "(ctx:$ctx)"; my $ptr = substr($blank, 0, $off) . "^"; my $hereptr = "$hereline$ptr\n"; # Pull out the value of this operator. my $op_type = substr($curr_values, $off + 1, 1); # Get the full operator variant. my $opv = $op . substr($curr_vars, $off, 1); # Ignore operators passed as parameters. if ($op_type ne 'V' && $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { # # Ignore comments # } elsif ($op =~ /^$;+$/) { # ; should have either the end of line or a space or \ after it } elsif ($op eq ';') { if ($ctx !~ /.x[WEBC]/ && $cc !~ /^\\/ && $cc !~ /^;/) { if (ERROR("SPACING", "space required after that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; $line_fixed = 1; } } # // is a comment } elsif ($op eq '//') { # : when part of a bitfield } elsif ($opv eq ':B') { # skip the bitfield test for now # No spaces for: # -> } elsif ($op eq '->') { if ($ctx =~ /Wx.|.xW/) { if (ERROR("SPACING", "spaces prohibited around that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # , must not have a space before and must have a space on the right. } elsif ($op eq ',') { my $rtrim_before = 0; my $space_after = 0; if ($ctx =~ /Wx./) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $line_fixed = 1; $rtrim_before = 1; } } if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { if (ERROR("SPACING", "space required after that '$op' $at\n" . $hereptr)) { $line_fixed = 1; $last_after = $n; $space_after = 1; } } if ($rtrim_before || $space_after) { if ($rtrim_before) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); } else { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); } if ($space_after) { $good .= " "; } } # '*' as part of a type definition -- reported already. } elsif ($opv eq '*_') { #warn "'*' is part of type\n"; # unary operators should have a space before and # none after. May be left adjacent to another # unary operator, or a cast } elsif ($op eq '!' || $op eq '~' || $opv eq '*U' || $opv eq '-U' || $opv eq '&U' || $opv eq '&&U') { if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { if (ERROR("SPACING", "space required before that '$op' $at\n" . $hereptr)) { if ($n != $last_after + 2) { $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); $line_fixed = 1; } } } if ($op eq '*' && $cc =~/\s*$Modifier\b/) { # A unary '*' may be const } elsif ($ctx =~ /.xW/) { if (ERROR("SPACING", "space prohibited after that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # unary ++ and unary -- are allowed no space on one side. } elsif ($op eq '++' or $op eq '--') { if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { if (ERROR("SPACING", "space required one side of that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; $line_fixed = 1; } } if ($ctx =~ /Wx[BE]/ || ($ctx =~ /Wx./ && $cc =~ /^;/)) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); $line_fixed = 1; } } if ($ctx =~ /ExW/) { if (ERROR("SPACING", "space prohibited after that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # << and >> may either have or not have spaces both sides } elsif ($op eq '<<' or $op eq '>>' or $op eq '&' or $op eq '^' or $op eq '|' or $op eq '+' or $op eq '-' or $op eq '*' or $op eq '/' or $op eq '%') { if ($check) { if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { if (CHK("SPACING", "spaces preferred around that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; $fix_elements[$n + 2] =~ s/^\s+//; $line_fixed = 1; } } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { if (CHK("SPACING", "space preferred before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); $line_fixed = 1; } } } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { if (ERROR("SPACING", "need consistent spacing around '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # A colon needs no spaces before when it is # terminating a case value or a label. } elsif ($opv eq ':C' || $opv eq ':L') { if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); $line_fixed = 1; } } # All the others need spaces both sides. } elsif ($ctx !~ /[EWC]x[CWE]/) { my $ok = 0; # Ignore email addresses <foo@bar> if (($op eq '<' && $cc =~ /^\S+\@\S+>/) || ($op eq '>' && $ca =~ /<\S+\@\S+$/)) { $ok = 1; } # for asm volatile statements # ignore a colon with another # colon immediately before or after if (($op eq ':') && ($ca =~ /:$/ || $cc =~ /^:/)) { $ok = 1; } # messages are ERROR, but ?: are CHK if ($ok == 0) { my $msg_level = \&ERROR; $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); if (&{$msg_level}("SPACING", "spaces required around that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } } $off += length($elements[$n + 1]); ## print("n: <$n> GOOD: <$good>\n"); $fixed_line = $fixed_line . $good; } if (($#elements % 2) == 0) { $fixed_line = $fixed_line . $fix_elements[$#elements]; } if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { $fixed[$fixlinenr] = $fixed_line; } } # check for whitespace before a non-naked semicolon if ($line =~ /^\+.*\S\s+;\s*$/) { if (WARN("SPACING", "space prohibited before semicolon\n" . $herecurr) && $fix) { 1 while $fixed[$fixlinenr] =~ s/^(\+.*\S)\s+;/$1;/; } } # check for multiple assignments if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { CHK("MULTIPLE_ASSIGNMENTS", "multiple assignments should be avoided\n" . $herecurr); } ## # check for multiple declarations, allowing for a function declaration ## # continuation. ## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && ## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { ## ## # Remove any bracketed sections to ensure we do not ## # falsely report the parameters of functions. ## my $ln = $line; ## while ($ln =~ s/\([^\(\)]*\)//g) { ## } ## if ($ln =~ /,/) { ## WARN("MULTIPLE_DECLARATION", ## "declaring multiple variables together should be avoided\n" . $herecurr); ## } ## } #need space before brace following if, while, etc if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || $line =~ /\b(?:else|do)\{/) { if (ERROR("SPACING", "space required before the open brace '{'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; } } ## # check for blank lines before declarations ## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && ## $prevrawline =~ /^.\s*$/) { ## WARN("SPACING", ## "No blank lines before declarations\n" . $hereprev); ## } ## # closing brace should have a space following it when it has anything # on the line if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { if (ERROR("SPACING", "space required after that close brace '}'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/}((?!(?:,|;|\)))\S)/} $1/; } } # check spacing on square brackets if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { if (ERROR("SPACING", "space prohibited after that open square bracket '['\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\[\s+/\[/; } } if ($line =~ /\s\]/) { if (ERROR("SPACING", "space prohibited before that close square bracket ']'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+\]/\]/; } } # check spacing on parentheses if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && $line !~ /for\s*\(\s+;/) { if (ERROR("SPACING", "space prohibited after that open parenthesis '('\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\(\s+/\(/; } } if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && $line !~ /for\s*\(.*;\s+\)/ && $line !~ /:\s+\)/) { if (ERROR("SPACING", "space prohibited before that close parenthesis ')'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+\)/\)/; } } # check unnecessary parentheses around addressof/dereference single $Lvals # ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { my $var = $1; if (CHK("UNNECESSARY_PARENTHESES", "Unnecessary parentheses around $var\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; } } # check for unnecessary parentheses around function pointer uses # ie: (foo->bar)(); should be foo->bar(); # but not "if (foo->bar) (" to avoid some false positives if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { my $var = $2; if (CHK("UNNECESSARY_PARENTHESES", "Unnecessary parentheses around function pointer $var\n" . $herecurr) && $fix) { my $var2 = deparenthesize($var); $var2 =~ s/\s//g; $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; } } # check for unnecessary parentheses around comparisons # except in drivers/staging if (($realfile !~ m@^(?:drivers/staging/)@) && $perl_version_ok && defined($stat) && $stat =~ /(^.\s*if\s*($balanced_parens))/) { my $if_stat = $1; my $test = substr($2, 1, -1); my $herectx; while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { my $match = $1; # avoid parentheses around potential macro args next if ($match =~ /^\s*\w+\s*$/); if (!defined($herectx)) { $herectx = $here . "\n"; my $cnt = statement_rawlines($if_stat); for (my $n = 0; $n < $cnt; $n++) { my $rl = raw_line($linenr, $n); $herectx .= $rl . "\n"; last if $rl =~ /^[ \+].*\{/; } } CHK("UNNECESSARY_PARENTHESES", "Unnecessary parentheses around '$match'\n" . $herectx); } } # check that goto labels aren't indented (allow a single space indentation) # and ignore bitfield definitions like foo:1 # Strictly, labels can have whitespace after the identifier and before the : # but this is not allowed here as many ?: uses would appear to be labels if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && $sline !~ /^.\s+default:/) { if (WARN("INDENTED_LABEL", "labels should not be indented\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(.)\s+/$1/; } } # check if a statement with a comma should be two statements like: # foo = bar(), /* comma should be semicolon */ # bar = baz(); if (defined($stat) && $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { my $cnt = statement_rawlines($stat); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("SUSPECT_COMMA_SEMICOLON", "Possible comma where semicolon could be used\n" . $herectx); } # return is not a function if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { my $spacing = $1; if ($perl_version_ok && $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { my $value = $1; $value = deparenthesize($value); if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { ERROR("RETURN_PARENTHESES", "return is not a function, parentheses are not required\n" . $herecurr); } } elsif ($spacing !~ /\s+/) { ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr); } } # unnecessary return in a void function # at end-of-function, with the previous line a single leading tab, then return; # and the line before that not a goto label target like "out:" if ($sline =~ /^[ \+]}\s*$/ && $prevline =~ /^\+\treturn\s*;\s*$/ && $linenr >= 3 && $lines[$linenr - 3] =~ /^[ +]/ && $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { WARN("RETURN_VOID", "void function return statements are not generally useful\n" . $hereprev); } # if statements using unnecessary parentheses - ie: if ((foo == bar)) if ($perl_version_ok && $line =~ /\bif\s*((?:\(\s*){2,})/) { my $openparens = $1; my $count = $openparens =~ tr@\(@\(@; my $msg = ""; if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { my $comp = $4; #Not $1 because of $LvalOrFunc $msg = " - maybe == should be = ?" if ($comp eq "=="); WARN("UNNECESSARY_PARENTHESES", "Unnecessary parentheses$msg\n" . $herecurr); } } # comparisons with a constant or upper case identifier on the left # avoid cases like "foo + BAR < baz" # only fix matches surrounded by parentheses to avoid incorrect # conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" if ($perl_version_ok && $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { my $lead = $1; my $const = $2; my $comp = $3; my $to = $4; my $newcomp = $comp; if ($lead !~ /(?:$Operators|\.)\s*$/ && $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && WARN("CONSTANT_COMPARISON", "Comparisons should place the constant on the right side of the test\n" . $herecurr) && $fix) { if ($comp eq "<") { $newcomp = ">"; } elsif ($comp eq "<=") { $newcomp = ">="; } elsif ($comp eq ">") { $newcomp = "<"; } elsif ($comp eq ">=") { $newcomp = "<="; } $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; } } # Return of what appears to be an errno should normally be negative if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { my $name = $1; if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { WARN("USE_NEGATIVE_ERRNO", "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); } } # Need a space before open parenthesis after if, while etc if ($line =~ /\b(if|while|for|switch)\(/) { if (ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b(if|while|for|switch)\(/$1 \(/; } } # Check for illegal assignment in if conditional -- and check for trailing # statements after the conditional. if ($line =~ /do\s*(?!{)/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0) if (!defined $stat); my ($stat_next) = ctx_statement_block($line_nr_next, $remain_next, $off_next); $stat_next =~ s/\n./\n /g; ##print "stat<$stat> stat_next<$stat_next>\n"; if ($stat_next =~ /^\s*while\b/) { # If the statement carries leading newlines, # then count those as offsets. my ($whitespace) = ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $suppress_whiletrailers{$line_nr_next + $offset} = 1; } } if (!defined $suppress_whiletrailers{$linenr} && defined($stat) && defined($cond) && $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { my ($s, $c) = ($stat, $cond); my $fixed_assign_in_if = 0; if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { if (ERROR("ASSIGN_IN_IF", "do not use assignment in if condition\n" . $herecurr) && $fix && $perl_version_ok) { if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { my $space = $1; my $not = $2; my $statement = $3; my $assigned = $4; my $test = $8; my $against = $9; my $brace = $15; fix_delete_line($fixlinenr, $rawline); fix_insert_line($fixlinenr, "$space$statement;"); my $newline = "${space}if ("; $newline .= '!' if defined($not); $newline .= '(' if (defined $not && defined($test) && defined($against)); $newline .= "$assigned"; $newline .= " $test $against" if (defined($test) && defined($against)); $newline .= ')' if (defined $not && defined($test) && defined($against)); $newline .= ')'; $newline .= " {" if (defined($brace)); fix_insert_line($fixlinenr + 1, $newline); $fixed_assign_in_if = 1; } } } # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; $s =~ s/$;//g; # Remove any comments if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && $c !~ /}\s*while\s*/) { # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; my $stat_real = ''; $stat_real = raw_line($linenr, $cond_lines) . "\n" if ($cond_lines); if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } if (ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr . $stat_real) && !$fixed_assign_in_if && $cond_lines == 0 && $fix && $perl_version_ok && $fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) { my $indent = $1; my $test = $2; my $rest = rtrim($4); if ($rest =~ /;$/) { $fixed[$fixlinenr] = "\+$indent$test"; fix_insert_line($fixlinenr + 1, "$indent\t$rest"); } } } } # Check for bitwise tests written as boolean if ($line =~ / (?: (?:\[|\(|\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\|) | (?:\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\||\)|\]) )/x) { WARN("HEXADECIMAL_BOOLEAN_TEST", "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); } # if and else should not have general statements after it if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { my $s = $1; $s =~ s/$;//g; # Remove any comments if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } } # if should not continue a brace if ($line =~ /}\s*if\b/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line (or did you mean 'else if'?)\n" . $herecurr); } # case and default should not have general statements after them if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && $line !~ /\G(?: (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| \s*return\s+ )/xg) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } # Check for }<nl>else {, these must be at the same # indent level to be relevant to each other. if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && $previndent == $indent) { if (ERROR("ELSE_AFTER_BRACE", "else should follow close brace '}'\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; $fixedline =~ s/}\s*$//; if ($fixedline !~ /^\+\s*$/) { fix_insert_line($fixlinenr, $fixedline); } $fixedline = $rawline; $fixedline =~ s/^(.\s*)else/$1} else/; fix_insert_line($fixlinenr, $fixedline); } } if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && $previndent == $indent) { my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; if ($s =~ /^\s*;/) { if (ERROR("WHILE_AFTER_BRACE", "while should follow close brace '}'\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; my $trailing = $rawline; $trailing =~ s/^\+//; $trailing = trim($trailing); $fixedline =~ s/}\s*$/} $trailing/; fix_insert_line($fixlinenr, $fixedline); } } } #Specific variable tests while ($line =~ m{($Constant|$Lval)}g) { my $var = $1; #CamelCase if ($var !~ /^$Constant$/ && $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && #Ignore C keywords $var !~ /^_Generic$/ && #Ignore some autogenerated defines and enum values $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && #Ignore Page<foo> variants $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && #Ignore ETHTOOL_LINK_MODE_<foo> variants $var !~ /^ETHTOOL_LINK_MODE_/ && #Ignore SI style variants like nS, mV and dB #(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && #Ignore some three character SI units explicitly, like MiB and KHz $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { while ($var =~ m{\b($Ident)}g) { my $word = $1; next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); if ($check) { seed_camelcase_includes(); if (!$file && !$camelcase_file_seeded) { seed_camelcase_file($realfile); $camelcase_file_seeded = 1; } } if (!defined $camelcase{$word}) { $camelcase{$word} = 1; CHK("CAMELCASE", "Avoid CamelCase: <$word>\n" . $herecurr); } } } } #no spaces allowed after \ in define if ($line =~ /\#\s*define.*\\\s+$/) { if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", "Whitespace after \\ makes next lines useless\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+$//; } } # warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes # itself <asm/foo.h> (uses RAW line) if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { my $file = "$1.h"; my $checkfile = "include/linux/$file"; if (-f "$root/$checkfile" && $realfile ne $checkfile && $1 !~ /$allowed_asm_includes/) { my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; if ($asminclude > 0) { if ($realfile =~ m{^arch/}) { CHK("ARCH_INCLUDE_LINUX", "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); } else { WARN("INCLUDE_LINUX", "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); } } } } # multi-statement macros should be enclosed in a do while loop, grab the # first statement and ensure its the whole macro if its not enclosed # in a known good container if ($realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { my $ln = $linenr; my $cnt = $realcnt; my ($off, $dstat, $dcond, $rest); my $ctx = ''; my $has_flow_statement = 0; my $has_arg_concat = 0; ($dstat, $dcond, $ln, $cnt, $off) = ctx_statement_block($linenr, $realcnt, 0); $ctx = $dstat; #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; my $define_args = $1; my $define_stmt = $dstat; my @def_args = (); if (defined $define_args && $define_args ne "") { $define_args = substr($define_args, 1, length($define_args) - 2); $define_args =~ s/\s*//g; $define_args =~ s/\\\+?//g; @def_args = split(",", $define_args); } $dstat =~ s/$;//g; $dstat =~ s/\\\n.//g; $dstat =~ s/^\s*//s; $dstat =~ s/\s*$//s; # Flatten any parentheses and braces while ($dstat =~ s/\([^\(\)]*\)/1u/ || $dstat =~ s/\{[^\{\}]*\}/1u/ || $dstat =~ s/.\[[^\[\]]*\]/1u/) { } # Flatten any obvious string concatenation. while ($dstat =~ s/($String)\s*$Ident/$1/ || $dstat =~ s/$Ident\s*($String)/$1/) { } # Make asm volatile uses seem like a generic function $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; my $exceptions = qr{ $Declare| module_param_named| MODULE_PARM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| __typeof__\(| union| struct| \.$Ident\s*=\s*| ^\"|\"$| ^\[ }x; #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; $ctx =~ s/\n*$//; my $stmt_cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $stmt_cnt, $here); if ($dstat ne '' && $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants $dstat !~ /$exceptions/ && $dstat !~ /^\.$Ident\s*=/ && # .foo = $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo $dstat !~ /^case\b/ && # case ... $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} $dstat !~ /^for\s*$Constant$/ && # for (...) $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() $dstat !~ /^do\s*{/ && # do {... $dstat !~ /^\(\{/ && # ({... $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) { if ($dstat =~ /^\s*if\b/) { ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); } elsif ($dstat =~ /;/) { ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); } else { ERROR("COMPLEX_MACRO", "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); } } # Make $define_stmt single line, comment-free, etc my @stmt_array = split('\n', $define_stmt); my $first = 1; $define_stmt = ""; foreach my $l (@stmt_array) { $l =~ s/\\$//; if ($first) { $define_stmt = $l; $first = 0; } elsif ($l =~ /^[\+ ]/) { $define_stmt .= substr($l, 1); } } $define_stmt =~ s/$;//g; $define_stmt =~ s/\s+/ /g; $define_stmt = trim($define_stmt); # check if any macro arguments are reused (ignore '...' and 'type') foreach my $arg (@def_args) { next if ($arg =~ /\.\.\./); next if ($arg =~ /^type$/i); my $tmp_stmt = $define_stmt; $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; $tmp_stmt =~ s/\#+\s*$arg\b//g; $tmp_stmt =~ s/\b$arg\s*\#\#//g; my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; if ($use_cnt > 1) { CHK("MACRO_ARG_REUSE", "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); } # check if any macro arguments may have other precedence issues if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && ((defined($1) && $1 ne ',') || (defined($2) && $2 ne ','))) { CHK("MACRO_ARG_PRECEDENCE", "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); } # check if this is an unused argument if ($define_stmt !~ /\b$arg\b/) { WARN("MACRO_ARG_UNUSED", "Argument '$arg' is not used in function-like macro\n" . "$herectx"); } } # check for macros with flow control, but without ## concatenation # ## concatenation is commonly a macro that defines a function so ignore those if ($has_flow_statement && !$has_arg_concat) { my $cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("MACRO_WITH_FLOW_CONTROL", "Macros with flow control statements should be avoided\n" . "$herectx"); } # check for line continuations outside of #defines, preprocessor #, and asm } elsif ($realfile =~ m@/vmlinux.lds.h$@) { $line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge; #print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol; } else { if ($prevline !~ /^..*\\$/ && $line !~ /^\+\s*\#.*\\$/ && # preprocessor $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm $line =~ /^\+.*\\$/) { WARN("LINE_CONTINUATIONS", "Avoid unnecessary line continuations\n" . $herecurr); } } # do {} while (0) macro tests: # single-statement macros do not need to be enclosed in do while (0) loop, # macro should not end with a semicolon if ($perl_version_ok && $realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { my $ln = $linenr; my $cnt = $realcnt; my ($off, $dstat, $dcond, $rest); my $ctx = ''; ($dstat, $dcond, $ln, $cnt, $off) = ctx_statement_block($linenr, $realcnt, 0); $ctx = $dstat; $dstat =~ s/\\\n.//g; $dstat =~ s/$;/ /g; if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { my $stmts = $2; my $semis = $3; $ctx =~ s/\n*$//; my $cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $cnt, $here); if (($stmts =~ tr/;/;/) == 1 && $stmts !~ /^\s*(if|while|for|switch)\b/) { WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); } if (defined $semis && $semis ne "") { WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); } } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { $ctx =~ s/\n*$//; my $cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("TRAILING_SEMICOLON", "macros should not use a trailing semicolon\n" . "$herectx"); } } # check for redundant bracing round if etc if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, 1); #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; if ($#chunks > 0 && $level == 0) { my @allowed = (); my $allow = 0; my $seen = 0; my $herectx = $here . "\n"; my $ln = $linenr - 1; for my $chunk (@chunks) { my ($cond, $block) = @{$chunk}; # If the condition carries leading newlines, then count those as offsets. my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $allowed[$allow] = 0; #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; # We have looked at and allowed this specific line. $suppress_ifbraces{$ln + $offset} = 1; $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; $ln += statement_rawlines($block) - 1; substr($block, 0, length($cond), ''); $seen++ if ($block =~ /^\s*{/); #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed[$allow] = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed[$allow] = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed[$allow] = 1; } $allow++; } if ($seen) { my $sum_allowed = 0; foreach (@allowed) { $sum_allowed += $_; } if ($sum_allowed == 0) { WARN("BRACES", "braces {} are not necessary for any arm of this statement\n" . $herectx); } elsif ($sum_allowed != $allow && $seen != $allow) { CHK("BRACES", "braces {} should be used on all arms of this statement\n" . $herectx); } } } } if (!defined $suppress_ifbraces{$linenr - 1} && $line =~ /\b(if|while|for|else)\b/) { my $allowed = 0; # Check the pre-context. if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { #print "APW: ALLOWED: pre<$1>\n"; $allowed = 1; } my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, $-[0]); # Check the condition. my ($cond, $block) = @{$chunks[0]}; #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; if (defined $cond) { substr($block, 0, length($cond), ''); } if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed = 1; } # Check the post-context. if (defined $chunks[1]) { my ($cond, $block) = @{$chunks[1]}; if (defined $cond) { substr($block, 0, length($cond), ''); } if ($block =~ /^\s*\{/) { #print "APW: ALLOWED: chunk-1 block<$block>\n"; $allowed = 1; } } if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { my $cnt = statement_rawlines($block); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("BRACES", "braces {} are not necessary for single statement blocks\n" . $herectx); } } # check for single line unbalanced braces if ($sline =~ /^.\s*\}\s*else\s*$/ || $sline =~ /^.\s*else\s*\{\s*$/) { CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); } # check for unnecessary blank lines around braces if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { if (CHK("BRACES", "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && $fix && $prevrawline =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); } } if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { if (CHK("BRACES", "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && $fix) { fix_delete_line($fixlinenr, $rawline); } } # no volatiles please my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { WARN("VOLATILE", "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); } # Check for user-visible strings broken across lines, which breaks the ability # to grep for the string. Make exceptions when the previous string ends in a # newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' # (common in inline assembly) or is a octal \123 or hexadecimal \xaf value if ($line =~ /^\+\s*$String/ && $prevline =~ /"\s*$/ && $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { if (WARN("SPLIT_STRING", "quoted string split across lines\n" . $hereprev) && $fix && $prevrawline =~ /^\+.*"\s*$/ && $last_coalesced_string_linenr != $linenr - 1) { my $extracted_string = get_quoted_string($line, $rawline); my $comma_close = ""; if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { $comma_close = $1; } fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; $fixedline =~ s/"\s*$//; $fixedline .= substr($extracted_string, 1) . trim($comma_close); fix_insert_line($fixlinenr - 1, $fixedline); $fixedline = $rawline; $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; if ($fixedline !~ /\+\s*$/) { fix_insert_line($fixlinenr, $fixedline); } $last_coalesced_string_linenr = $linenr; } } # check for missing a space in a string concatenation if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { WARN('MISSING_SPACE', "break quoted strings at a space character\n" . $hereprev); } # check for an embedded function name in a string when the function is known # This does not work very well for -f --file checking as it depends on patch # context providing the function name or a single line form for in-file # function declarations if ($line =~ /^\+.*$String/ && defined($context_function) && get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { WARN("EMBEDDED_FUNCTION_NAME", "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); } # check for unnecessary function tracing like uses # This does not use $logFunctions because there are many instances like # 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { if (WARN("TRACING_LOGGING", "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr, $rawline); } } # check for spaces before a quoted newline if ($rawline =~ /^.*\".*\s\\n/) { if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", "unnecessary whitespace before a quoted newline\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; } } # concatenated string without spaces between elements if ($line =~ /$String[A-Z_]/ || ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { if (CHK("CONCATENATED_STRING", "Concatenated strings should use spaces between elements\n" . $herecurr) && $fix) { while ($line =~ /($String)/g) { my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; } } } # uncoalesced string fragments if ($line =~ /$String\s*[Lu]?"/) { if (WARN("STRING_FRAGMENTS", "Consecutive strings are generally better as a single string\n" . $herecurr) && $fix) { while ($line =~ /($String)(?=\s*")/g) { my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; } } } # check for non-standard and hex prefixed decimal printf formats my $show_L = 1; #don't show the same defect twice my $show_Z = 1; while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { my $string = substr($rawline, $-[1], $+[1] - $-[1]); $string =~ s/%%/__/g; # check for %L if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { WARN("PRINTF_L", "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); $show_L = 0; } # check for %Z if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { WARN("PRINTF_Z", "%Z$1 is non-standard C, use %z$1\n" . $herecurr); $show_Z = 0; } # check for 0x<decimal> if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { ERROR("PRINTF_0XDECIMAL", "Prefixing 0x with decimal output is defective\n" . $herecurr); } } # check for line continuations in quoted strings with odd counts of " if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { WARN("LINE_CONTINUATIONS", "Avoid line continuations in quoted strings\n" . $herecurr); } # warn about #if 0 if ($line =~ /^.\s*\#\s*if\s+0\b/) { WARN("IF_0", "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); } # warn about #if 1 if ($line =~ /^.\s*\#\s*if\s+1\b/) { WARN("IF_1", "Consider removing the #if 1 and its #endif\n" . $herecurr); } # check for needless "if (<foo>) fn(<foo>)" uses if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { my $tested = quotemeta($1); my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { my $func = $1; if (WARN('NEEDLESS_IF', "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && $fix) { my $do_fix = 1; my $leading_tabs = ""; my $new_leading_tabs = ""; if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { $leading_tabs = $1; } else { $do_fix = 0; } if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { $new_leading_tabs = $1; if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { $do_fix = 0; } } else { $do_fix = 0; } if ($do_fix) { fix_delete_line($fixlinenr - 1, $prevrawline); $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; } } } } # check for unnecessary "Out of Memory" messages if ($line =~ /^\+.*\b$logFunctions\s*\(/ && $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && (defined $1 || defined $3) && $linenr > 3) { my $testval = $2; my $testline = $lines[$linenr - 3]; my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); # print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && $s !~ /\b__GFP_NOWARN\b/ ) { WARN("OOM_MESSAGE", "Possible unnecessary 'out of memory' message\n" . $hereprev); } } # check for logging functions with KERN_<LEVEL> if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { my $level = $1; if (WARN("UNNECESSARY_KERN_LEVEL", "Possible unnecessary $level\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s*$level\s*//; } } # check for logging continuations if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { WARN("LOGGING_CONTINUATION", "Avoid logging continuation uses where feasible\n" . $herecurr); } # check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions if (defined $stat && $line =~ /\b$logFunctions\s*\(/ && index($stat, '"') >= 0) { my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); pos($stat_real) = index($stat_real, '"'); while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { my $pspec = $1; my $h = $2; my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; if (WARN("UNNECESSARY_MODIFIER", "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { my $nspec = $pspec; $nspec =~ s/h//g; $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; } } } # check for mask then right shift without a parentheses if ($perl_version_ok && $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so WARN("MASK_THEN_SHIFT", "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); } # check for pointer comparisons to NULL if ($perl_version_ok) { while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { my $val = $1; my $equal = "!"; $equal = "" if ($4 eq "!="); if (CHK("COMPARISON_TO_NULL", "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; } } } # check for bad placement of section $InitAttribute (e.g.: __initdata) if ($line =~ /(\b$InitAttribute\b)/) { my $attr = $1; if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { my $ptr = $1; my $var = $2; if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && ERROR("MISPLACED_INIT", "$attr should be placed after $var\n" . $herecurr)) || ($ptr !~ /\b(union|struct)\s+$attr\b/ && WARN("MISPLACED_INIT", "$attr should be placed after $var\n" . $herecurr))) && $fix) { $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; } } } # check for $InitAttributeData (ie: __initdata) with const if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { my $attr = $1; $attr =~ /($InitAttributePrefix)(.*)/; my $attr_prefix = $1; my $attr_type = $2; if (ERROR("INIT_ATTRIBUTE", "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/$InitAttributeData/${attr_prefix}initconst/; } } # check for $InitAttributeConst (ie: __initconst) without const if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { my $attr = $1; if (ERROR("INIT_ATTRIBUTE", "Use of $attr requires a separate use of const\n" . $herecurr) && $fix) { my $lead = $fixed[$fixlinenr] =~ /(^\+\s*(?:static\s+))/; $lead = rtrim($1); $lead = "$lead " if ($lead !~ /^\+$/); $lead = "${lead}const "; $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; } } # check for __read_mostly with const non-pointer (should just be const) if ($line =~ /\b__read_mostly\b/ && $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { if (ERROR("CONST_READ_MOSTLY", "Invalid use of __read_mostly with const type\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; } } # don't use __constant_<foo> functions outside of include/uapi/ if ($realfile !~ m@^include/uapi/@ && $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { my $constant_func = $1; my $func = $constant_func; $func =~ s/^__constant_//; if (WARN("CONSTANT_CONVERSION", "$constant_func should be $func\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; } } # prefer usleep_range over udelay if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { my $delay = $1; # ignore udelay's < 10, however if (! ($delay < 10) ) { CHK("USLEEP_RANGE", "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr); } if ($delay > 2000) { WARN("LONG_UDELAY", "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr); } } # warn about unexpectedly long msleep's if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("MSLEEP", "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); } } # check for comparisons of jiffies if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { WARN("JIFFIES_COMPARISON", "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); } # check for comparisons of get_jiffies_64() if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { WARN("JIFFIES_COMPARISON", "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); } # warn about #ifdefs in C files # if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { # print "#ifdef in C files should be avoided\n"; # print "$herecurr"; # $clean = 0; # } # warn about spacing in #ifdefs if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { if (ERROR("SPACING", "exactly one space required after that #$1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; } } # check for spinlock_t definitions without a comment. if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { my $which = $1; if (!ctx_has_comment($first_line, $linenr)) { CHK("UNCOMMENTED_DEFINITION", "$1 definition without comment\n" . $herecurr); } } # check for memory barriers without a comment. my $barriers = qr{ mb| rmb| wmb }x; my $barrier_stems = qr{ mb__before_atomic| mb__after_atomic| store_release| load_acquire| store_mb| (?:$barriers) }x; my $all_barriers = qr{ (?:$barriers)| smp_(?:$barrier_stems)| virt_(?:$barrier_stems) }x; if ($line =~ /\b(?:$all_barriers)\s*\(/) { if (!ctx_has_comment($first_line, $linenr)) { WARN("MEMORY_BARRIER", "memory barrier without comment\n" . $herecurr); } } my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; if ($realfile !~ m@^include/asm-generic/@ && $realfile !~ m@/barrier\.h$@ && $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { WARN("MEMORY_BARRIER", "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); } # check for waitqueue_active without a comment. if ($line =~ /\bwaitqueue_active\s*\(/) { if (!ctx_has_comment($first_line, $linenr)) { WARN("WAITQUEUE_ACTIVE", "waitqueue_active without comment\n" . $herecurr); } } # check for data_race without a comment. if ($line =~ /\bdata_race\s*\(/) { if (!ctx_has_comment($first_line, $linenr)) { WARN("DATA_RACE", "data_race without comment\n" . $herecurr); } } # check of hardware specific defines if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { CHK("ARCH_DEFINES", "architecture specific defines should be avoided\n" . $herecurr); } # check that the storage class is not after a type if ($line =~ /\b($Type)\s+($Storage)\b/) { WARN("STORAGE_CLASS", "storage class '$2' should be located before type '$1'\n" . $herecurr); } # Check that the storage class is at the beginning of a declaration if ($line =~ /\b$Storage\b/ && $line !~ /^.\s*$Storage/ && $line =~ /^.\s*(.+?)\$Storage\s/ && $1 !~ /[\,\)]\s*$/) { WARN("STORAGE_CLASS", "storage class should be at the beginning of the declaration\n" . $herecurr); } # check the location of the inline attribute, that it is between # storage class and type. if ($line =~ /\b$Type\s+$Inline\b/ || $line =~ /\b$Inline\s+$Storage\b/) { ERROR("INLINE_LOCATION", "inline keyword should sit between storage class and type\n" . $herecurr); } # Check for __inline__ and __inline, prefer inline if ($realfile !~ m@\binclude/uapi/@ && $line =~ /\b(__inline__|__inline)\b/) { if (WARN("INLINE", "plain inline is preferred over $1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; } } # Check for compiler attributes if ($realfile !~ m@\binclude/uapi/@ && $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { my $attr = $1; $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; my %attr_list = ( "alias" => "__alias", "aligned" => "__aligned", "always_inline" => "__always_inline", "assume_aligned" => "__assume_aligned", "cold" => "__cold", "const" => "__attribute_const__", "copy" => "__copy", "designated_init" => "__designated_init", "externally_visible" => "__visible", "format" => "printf|scanf", "gnu_inline" => "__gnu_inline", "malloc" => "__malloc", "mode" => "__mode", "no_caller_saved_registers" => "__no_caller_saved_registers", "noclone" => "__noclone", "noinline" => "noinline", "nonstring" => "__nonstring", "noreturn" => "__noreturn", "packed" => "__packed", "pure" => "__pure", "section" => "__section", "used" => "__used", "weak" => "__weak" ); while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { my $orig_attr = $1; my $params = ''; $params = $2 if defined($2); my $curr_attr = $orig_attr; $curr_attr =~ s/^[\s_]+|[\s_]+$//g; if (exists($attr_list{$curr_attr})) { my $new = $attr_list{$curr_attr}; if ($curr_attr eq "format" && $params) { $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; $new = "__$1\($2"; } else { $new = "$new$params"; } if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && $fix) { my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; $fixed[$fixlinenr] =~ s/$remove//; $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; } } } # Check for __attribute__ unused, prefer __always_unused or __maybe_unused if ($attr =~ /^_*unused/) { WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); } } # Check for __attribute__ weak, or __weak declarations (may have link issues) if ($perl_version_ok && $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || $line =~ /\b__weak\b/)) { ERROR("WEAK_DECLARATION", "Using weak declarations can have unintended link defects\n" . $herecurr); } # check for c99 types like uint8_t used outside of uapi/ and tools/ if ($realfile !~ m@\binclude/uapi/@ && $realfile !~ m@\btools/@ && $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { my $type = $1; if ($type =~ /\b($typeC99Typedefs)\b/) { $type = $1; my $kernel_type = 'u'; $kernel_type = 's' if ($type =~ /^_*[si]/); $type =~ /(\d+)/; $kernel_type .= $1; if (CHK("PREFER_KERNEL_TYPES", "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; } } } # check for cast of C90 native int or longer types constants if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { my $cast = $1; my $const = $2; my $suffix = ""; my $newconst = $const; $newconst =~ s/${Int_type}$//; $suffix .= 'U' if ($cast =~ /\bunsigned\b/); if ($cast =~ /\blong\s+long\b/) { $suffix .= 'LL'; } elsif ($cast =~ /\blong\b/) { $suffix .= 'L'; } if (WARN("TYPECAST_INT_CONSTANT", "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; } } # check for sizeof(&) if ($line =~ /\bsizeof\s*\(\s*\&/) { WARN("SIZEOF_ADDRESS", "sizeof(& should be avoided\n" . $herecurr); } # check for sizeof without parenthesis if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { if (WARN("SIZEOF_PARENTHESIS", "sizeof $1 should be sizeof($1)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; } } # check for struct spinlock declarations if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { WARN("USE_SPINLOCK_T", "struct spinlock should be spinlock_t\n" . $herecurr); } # check for seq_printf uses that could be seq_puts if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { my $fmt = get_quoted_string($line, $rawline); $fmt =~ s/%%//g; if ($fmt !~ /%/) { if (WARN("PREFER_SEQ_PUTS", "Prefer seq_puts to seq_printf\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; } } } # check for vsprintf extension %p<foo> misuses if ($perl_version_ok && defined $stat && $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && $1 !~ /^_*volatile_*$/) { my $stat_real; my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; for (my $count = $linenr; $count <= $lc; $count++) { my $specifier; my $extension; my $qualifier; my $bad_specifier = ""; my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); $fmt =~ s/%%//g; while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { $specifier = $1; $extension = $2; $qualifier = $3; if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || ($extension eq "f" && defined $qualifier && $qualifier !~ /^w/) || ($extension eq "4" && defined $qualifier && $qualifier !~ /^cc/)) { $bad_specifier = $specifier; last; } if ($extension eq "x" && !defined($stat_real)) { if (!defined($stat_real)) { $stat_real = get_stat_real($linenr, $lc); } WARN("VSPRINTF_SPECIFIER_PX", "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); } } if ($bad_specifier ne "") { my $stat_real = get_stat_real($linenr, $lc); my $msg_level = \&WARN; my $ext_type = "Invalid"; my $use = ""; if ($bad_specifier =~ /p[Ff]/) { $use = " - use %pS instead"; $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); } elsif ($bad_specifier =~ /pA/) { $use = " - '%pA' is only intended to be used from Rust code"; $msg_level = \&ERROR; } &{$msg_level}("VSPRINTF_POINTER_EXTENSION", "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); } } } # Check for misused memsets if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { my $ms_addr = $2; my $ms_val = $7; my $ms_size = $12; if ($ms_size =~ /^(0x|)0$/i) { ERROR("MEMSET", "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); } elsif ($ms_size =~ /^(0x|)1$/i) { WARN("MEMSET", "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); } } # Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) # if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # if (WARN("PREFER_ETHER_ADDR_COPY", # "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && # $fix) { # $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; # } # } # Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) # if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # WARN("PREFER_ETHER_ADDR_EQUAL", # "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") # } # check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr # check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr # if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # # my $ms_val = $7; # # if ($ms_val =~ /^(?:0x|)0+$/i) { # if (WARN("PREFER_ETH_ZERO_ADDR", # "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && # $fix) { # $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; # } # } elsif ($ms_val =~ /^(?:0xff|255)$/i) { # if (WARN("PREFER_ETH_BROADCAST_ADDR", # "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && # $fix) { # $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; # } # } # } # strcpy uses that should likely be strscpy if ($line =~ /\bstrcpy\s*\(/) { WARN("STRCPY", "Prefer strscpy over strcpy - see: https://github.com/KSPP/linux/issues/88\n" . $herecurr); } # strlcpy uses that should likely be strscpy if ($line =~ /\bstrlcpy\s*\(/) { WARN("STRLCPY", "Prefer strscpy over strlcpy - see: https://github.com/KSPP/linux/issues/89\n" . $herecurr); } # strncpy uses that should likely be strscpy or strscpy_pad if ($line =~ /\bstrncpy\s*\(/) { WARN("STRNCPY", "Prefer strscpy, strscpy_pad, or __nonstring over strncpy - see: https://github.com/KSPP/linux/issues/90\n" . $herecurr); } # ethtool_sprintf uses that should likely be ethtool_puts if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { if (WARN("PREFER_ETHTOOL_PUTS", "Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; } } # use $rawline because $line loses %s via sanitization and thus we can't match against it. if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) { if (WARN("PREFER_ETHTOOL_PUTS", "Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; } } # typecasts on min/max could be min_t/max_t if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { if (defined $2 || defined $7) { my $call = $1; my $cast1 = deparenthesize($2); my $arg1 = $3; my $cast2 = deparenthesize($7); my $arg2 = $8; my $cast; if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { $cast = "$cast1 or $cast2"; } elsif ($cast1 ne "") { $cast = $cast1; } else { $cast = $cast2; } WARN("MINMAX", "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); } } # check usleep_range arguments if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { my $min = $1; my $max = $7; if ($min eq $max) { WARN("USLEEP_RANGE", "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n"); } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && $min > $max) { WARN("USLEEP_RANGE", "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n"); } } # check for naked sscanf if ($perl_version_ok && defined $stat && $line =~ /\bsscanf\b/ && ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); WARN("NAKED_SSCANF", "unchecked sscanf return value\n" . "$here\n$stat_real\n"); } # check for simple sscanf that should be kstrto<foo> if ($perl_version_ok && defined $stat && $line =~ /\bsscanf\b/) { my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { my $format = $6; my $count = $format =~ tr@%@%@; if ($count == 1 && $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { WARN("SSCANF_TO_KSTRTO", "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); } } } # check for new externs in .h files. if ($realfile =~ /\.h$/ && $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { if (CHK("AVOID_EXTERNS", "extern prototypes should be avoided in .h files\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; } } # check for new externs in .c files. if ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) { my $function_name = $1; my $paren_space = $2; my $s = $stat; if (defined $cond) { substr($s, 0, length($cond), ''); } if ($s =~ /^\s*;/) { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); } if ($paren_space =~ /\n/) { WARN("FUNCTION_ARGUMENTS", "arguments for function declarations should follow identifier\n" . $herecurr); } } elsif ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/) { my ($st_type, $st_name) = ($1, $2); for my $s (keys %maybe_linker_symbol) { #print "Linker symbol? $st_name : $s\n"; goto LIKELY_LINKER_SYMBOL if $st_name =~ /$s/; } WARN("AVOID_EXTERNS", "found a file-scoped extern type:$st_type name:$st_name in .c file\n" . "is this a linker symbol ?\n" . $herecurr); LIKELY_LINKER_SYMBOL: } elsif ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*extern\s+/) { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); } # check for function declarations that have arguments without identifier names if (defined $stat && $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && $1 ne "void") { my $args = trim($1); while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { my $arg = trim($1); if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { WARN("FUNCTION_ARGUMENTS", "function definition argument '$arg' should also have an identifier name\n" . $herecurr); } } } # check for function definitions if ($perl_version_ok && defined $stat && $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { $context_function = $1; # check for multiline function definition with misplaced open brace my $ok = 0; my $cnt = statement_rawlines($stat); my $herectx = $here . "\n"; for (my $n = 0; $n < $cnt; $n++) { my $rl = raw_line($linenr, $n); $herectx .= $rl . "\n"; $ok = 1 if ($rl =~ /^[ \+]\{/); $ok = 1 if ($rl =~ /\{/ && $n == 0); last if $rl =~ /^[ \+].*\{/; } if (!$ok) { ERROR("OPEN_BRACE", "open brace '{' following function definitions go on the next line\n" . $herectx); } } # checks for new __setup's if ($rawline =~ /\b__setup\("([^"]*)"/) { my $name = $1; if (!grep(/$name/, @setup_docs)) { CHK("UNDOCUMENTED_SETUP", "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); } } # check for pointless casting of alloc functions if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { WARN("UNNECESSARY_CASTS", "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); } # alloc style # p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) if ($perl_version_ok && $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { CHK("ALLOC_SIZEOF_STRUCT", "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); } # check for (kv|k)[mz]alloc with multiplies that could be kmalloc_array/kvmalloc_array/kvcalloc/kcalloc if ($perl_version_ok && defined $stat && $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { my $oldfunc = $3; my $a1 = $4; my $a2 = $10; my $newfunc = "kmalloc_array"; $newfunc = "kvmalloc_array" if ($oldfunc eq "kvmalloc"); $newfunc = "kvcalloc" if ($oldfunc eq "kvzalloc"); $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); my $r1 = $a1; my $r2 = $a2; if ($a1 =~ /^sizeof\s*\S/) { $r1 = $a2; $r2 = $a1; } if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { my $cnt = statement_rawlines($stat); my $herectx = get_stat_here($linenr, $cnt, $here); if (WARN("ALLOC_WITH_MULTIPLY", "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && $cnt == 1 && $fix) { $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; } } } # check for krealloc arg reuse if ($perl_version_ok && $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && $1 eq $3) { WARN("KREALLOC_ARG_REUSE", "Reusing the krealloc arg is almost always a bug\n" . $herecurr); } # check for alloc argument mismatch if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) { WARN("ALLOC_ARRAY_ARGS", "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); } # check for multiple semicolons if ($line =~ /;\s*;\s*$/) { if (WARN("ONE_SEMICOLON", "Statements terminations use 1 semicolon\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; } } # check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi if ($realfile !~ m@^include/uapi/@ && $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { my $ull = ""; $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); if (CHK("BIT_MACRO", "Prefer using the BIT$ull macro\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; } } # check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { WARN("IS_ENABLED_CONFIG", "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); } # check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { my $config = $1; if (WARN("PREFER_IS_ENABLED", "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; } } # check for /* fallthrough */ like comment, prefer fallthrough; my @fallthroughs = ( 'fallthrough', '@fallthrough@', 'lint -fallthrough[ \t]*', 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', ); if ($raw_comment ne '') { foreach my $ft (@fallthroughs) { if ($raw_comment =~ /$ft/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}("PREFER_FALLTHROUGH", "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); last; } } } # check for switch/default statements without a break; if ($perl_version_ok && defined $stat && $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { my $cnt = statement_rawlines($stat); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("DEFAULT_NO_BREAK", "switch default: should use break\n" . $herectx); } # check for gcc specific __FUNCTION__ if ($line =~ /\b__FUNCTION__\b/) { if (WARN("USE_FUNC", "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; } } # check for uses of __DATE__, __TIME__, __TIMESTAMP__ while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { ERROR("DATE_TIME", "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); } # check for use of yield() if ($line =~ /\byield\s*\(\s*\)/) { WARN("YIELD", "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); } # check for comparisons against true and false if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { my $lead = $1; my $arg = $2; my $test = $3; my $otype = $4; my $trail = $5; my $op = "!"; ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); my $type = lc($otype); if ($type =~ /^(?:true|false)$/) { if (("$test" eq "==" && "$type" eq "true") || ("$test" eq "!=" && "$type" eq "false")) { $op = ""; } CHK("BOOL_COMPARISON", "Using comparison to $otype is error prone\n" . $herecurr); ## maybe suggesting a correct construct would better ## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); } } # check for semaphores initialized locked if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { WARN("CONSIDER_COMPLETION", "consider using a completion\n" . $herecurr); } # recommend kstrto* over simple_strto* and strict_strto* if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { WARN("CONSIDER_KSTRTO", "$1 is obsolete, use k$3 instead\n" . $herecurr); } # check for __initcall(), use device_initcall() explicitly or more appropriate function please if ($line =~ /^.\s*__initcall\s*\(/) { WARN("USE_DEVICE_INITCALL", "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); } # check for spin_is_locked(), suggest lockdep instead if ($line =~ /\bspin_is_locked\(/) { WARN("USE_LOCKDEP", "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); } # check for deprecated apis if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { my $deprecated_api = $1; my $new_api = $deprecated_apis{$deprecated_api}; WARN("DEPRECATED_API", "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); } # check for various structs that are normally const (ops, kgdb, device_tree) # and avoid what seem like struct definitions 'struct foo {' if (defined($const_structs) && $line !~ /\bconst\b/ && $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { WARN("CONST_STRUCT", "struct $1 should normally be const\n" . $herecurr); } # use of NR_CPUS is usually wrong # ignore definitions of NR_CPUS and usage to define arrays as likely right # ignore designated initializers using NR_CPUS if ($line =~ /\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) { WARN("NR_CPUS", "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); } # Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { ERROR("DEFINE_ARCH_HAS", "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); } # likely/unlikely comparisons similar to "(likely(foo) > 0)" if ($perl_version_ok && $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { WARN("LIKELY_MISUSE", "Using $1 should generally have parentheses around the comparison\n" . $herecurr); } # return sysfs_emit(foo, fmt, ...) fmt without newline if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { my $offset = $+[6] - 1; if (WARN("SYSFS_EMIT", "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && $fix) { substr($fixed[$fixlinenr], $offset, 0) = '\\n'; } } # check for array definition/declarations that should use flexible arrays instead if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ && $prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) { if (ERROR("FLEXIBLE_ARRAY", "Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) && $1 == '0' && $fix) { $fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/; } } # nested likely/unlikely calls if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { WARN("LIKELY_MISUSE", "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); } # whine mightly about in_atomic if ($line =~ /\bin_atomic\s*\(/) { if ($realfile =~ m@^drivers/@) { ERROR("IN_ATOMIC", "do not use in_atomic in drivers\n" . $herecurr); } elsif ($realfile !~ m@^kernel/@) { WARN("IN_ATOMIC", "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); } } # Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). our $rcu_trace_funcs = qr{(?x: rcu_read_lock_trace | rcu_read_lock_trace_held | rcu_read_unlock_trace | call_rcu_tasks_trace | synchronize_rcu_tasks_trace | rcu_barrier_tasks_trace | rcu_request_urgent_qs_task )}; our $rcu_trace_paths = qr{(?x: kernel/bpf/ | include/linux/bpf | net/bpf/ | kernel/rcu/ | include/linux/rcu )}; if ($line =~ /\b($rcu_trace_funcs)\s*\(/) { if ($realfile !~ m{^$rcu_trace_paths}) { WARN("RCU_TASKS_TRACE", "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr); } } # check for lockdep_set_novalidate_class if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || $line =~ /__lockdep_no_validate__\s*\)/ ) { if ($realfile !~ m@^kernel/lockdep@ && $realfile !~ m@^include/linux/lockdep@ && $realfile !~ m@^drivers/base/core@) { ERROR("LOCKDEP", "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); } } if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { WARN("EXPORTED_WORLD_WRITABLE", "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); } # check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> # and whether or not function naming is typical and if # DEVICE_ATTR permissions uses are unusual too if ($perl_version_ok && defined $stat && $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { my $var = $1; my $perms = $2; my $show = $3; my $store = $4; my $octal_perms = perms_to_octal($perms); if ($show =~ /^${var}_show$/ && $store =~ /^${var}_store$/ && $octal_perms eq "0644") { if (WARN("DEVICE_ATTR_RW", "Use DEVICE_ATTR_RW\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; } } elsif ($show =~ /^${var}_show$/ && $store =~ /^NULL$/ && $octal_perms eq "0444") { if (WARN("DEVICE_ATTR_RO", "Use DEVICE_ATTR_RO\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; } } elsif ($show =~ /^NULL$/ && $store =~ /^${var}_store$/ && $octal_perms eq "0200") { if (WARN("DEVICE_ATTR_WO", "Use DEVICE_ATTR_WO\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; } } elsif ($octal_perms eq "0644" || $octal_perms eq "0444" || $octal_perms eq "0200") { my $newshow = "$show"; $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); my $newstore = $store; $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); my $rename = ""; if ($show ne $newshow) { $rename .= " '$show' to '$newshow'"; } if ($store ne $newstore) { $rename .= " '$store' to '$newstore'"; } WARN("DEVICE_ATTR_FUNCTIONS", "Consider renaming function(s)$rename\n" . $herecurr); } else { WARN("DEVICE_ATTR_PERMS", "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); } } # Mode permission misuses where it seems decimal should be octal # This uses a shortcut match to avoid unnecessary uses of a slow foreach loop # o Ignore module_param*(...) uses with a decimal 0 permission as that has a # specific definition of not visible in sysfs. # o Ignore proc_create*(...) uses with a decimal 0 permission as that means # use the default permissions if ($perl_version_ok && defined $stat && $line =~ /$mode_perms_search/) { foreach my $entry (@mode_permission_funcs) { my $func = $entry->[0]; my $arg_pos = $entry->[1]; my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); my $skip_args = ""; if ($arg_pos > 1) { $arg_pos--; $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; } my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; if ($stat =~ /$test/) { my $val = $1; $val = $6 if ($skip_args ne ""); if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || ($val =~ /^$Octal$/ && length($val) ne 4))) { ERROR("NON_OCTAL_PERMISSIONS", "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); } if ($val =~ /^$Octal$/ && (oct($val) & 02)) { ERROR("EXPORTED_WORLD_WRITABLE", "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); } } } } # check for uses of S_<PERMS> that could be octal for readability while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { my $oval = $1; my $octal = perms_to_octal($oval); if (WARN("SYMBOLIC_PERMS", "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; } } # validate content of MODULE_LICENSE against list from include/linux/module.h if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { my $extracted_string = get_quoted_string($line, $rawline); my $valid_licenses = qr{ GPL| GPL\ v2| GPL\ and\ additional\ rights| Dual\ BSD/GPL| Dual\ MIT/GPL| Dual\ MPL/GPL| Proprietary }x; if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { WARN("MODULE_LICENSE", "unknown module license " . $extracted_string . "\n" . $herecurr); } if (!$file && $extracted_string eq '"GPL v2"') { if (WARN("MODULE_LICENSE", "Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/; } } } # check for sysctl duplicate constants if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { WARN("DUPLICATED_SYSCTL_CONST", "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); } } # If we have no input at all, then there is nothing to report on # so just keep quiet. if ($#rawlines == -1) { exit(0); } # In mailback mode only produce a report in the negative, for # things that appear to be patches. if ($mailback && ($clean == 1 || !$is_patch)) { exit(0); } # This is not a patch, and we are in 'no-patch' mode so # just keep quiet. if (!$chk_patch && !$is_patch) { exit(0); } if (!$is_patch && $filename !~ /cover-letter\.patch$/) { ERROR("NOT_UNIFIED_DIFF", "Does not appear to be a unified-diff format patch\n"); } if ($is_patch && $has_commit_log && $chk_fixes_tag) { if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) { WARN("MISSING_FIXES_TAG", "The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n"); } } if ($is_patch && $has_commit_log && $chk_signoff) { if ($signoff == 0) { ERROR("MISSING_SIGN_OFF", "Missing Signed-off-by: line(s)\n"); } elsif ($authorsignoff != 1) { # authorsignoff values: # 0 -> missing sign off # 1 -> sign off identical # 2 -> names and addresses match, comments mismatch # 3 -> addresses match, names different # 4 -> names match, addresses different # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; if ($authorsignoff == 0) { ERROR("NO_AUTHOR_SIGN_OFF", "Missing Signed-off-by: line by nominal patch author '$author'\n"); } elsif ($authorsignoff == 2) { CHK("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); } elsif ($authorsignoff == 3) { WARN("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email name mismatch: $sob_msg\n"); } elsif ($authorsignoff == 4) { WARN("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email address mismatch: $sob_msg\n"); } elsif ($authorsignoff == 5) { WARN("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); } } } print report_dump(); if ($summary && !($clean == 1 && $quiet == 1)) { print "$filename " if ($summary_file); print "total: $cnt_error errors, $cnt_warn warnings, " . (($check)? "$cnt_chk checks, " : "") . "$cnt_lines lines checked\n"; } if ($quiet == 0) { # If there were any defects found and not already fixing them if (!$clean and !$fix) { print << "EOM" NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. EOM } # If there were whitespace errors which cleanpatch can fix # then suggest that. if ($rpt_cleaners) { $rpt_cleaners = 0; print << "EOM" NOTE: Whitespace errors detected. You may wish to use scripts/cleanpatch or scripts/cleanfile EOM } } if ($clean == 0 && $fix && ("@rawlines" ne "@fixed" || $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { my $newfile = $filename; $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); my $linecount = 0; my $f; @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); open($f, '>', $newfile) or die "$P: Can't open $newfile for write\n"; foreach my $fixed_line (@fixed) { $linecount++; if ($file) { if ($linecount > 3) { $fixed_line =~ s/^\+//; print $f $fixed_line . "\n"; } } else { print $f $fixed_line . "\n"; } } close($f); if (!$quiet) { print << "EOM"; Wrote EXPERIMENTAL --fix correction(s) to '$newfile' Do _NOT_ trust the results written to this file. Do _NOT_ submit these changes without inspecting them for correctness. This EXPERIMENTAL file is simply a convenience to help rewrite patches. No warranties, expressed or implied... EOM } } if ($quiet == 0) { print "\n"; if ($clean == 1) { print "$vname has no obvious style problems and is ready for submission.\n"; } else { print "$vname has style problems, please review.\n"; } } return $clean; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/tests/ci/install_openssl.sh�����������������������������������������������������0000775�0001750�0001750�00000000476�14753334500�021555� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh set -e PREFIX="$1" [ -x "${PREFIX}/bin/openssl" ] && exit 0 VERSION=1.0.2u SRC="https://www.openssl.org/source/old/1.0.2/openssl-${VERSION}.tar.gz" wget -O openssl.tar.gz "$SRC" tar -xf openssl.tar.gz -C "$HOME" cd "${HOME}/openssl-${VERSION}" ./config --prefix="$PREFIX" shared -fPIC make make install ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/README.md�����������������������������������������������������������������������0000664�0001750�0001750�00000020143�14753334500�015520� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn ============ openfortivpn is a client for PPP+TLS VPN tunnel services. It spawns a pppd process and operates the communication between the gateway and this process. It is compatible with Fortinet VPNs. Usage ----- ```shell man openfortivpn ``` Examples -------- * Simply connect to a VPN: ```shell openfortivpn vpn-gateway:8443 --username=foo ``` * Connect to a VPN using an authentication realm: ```shell openfortivpn vpn-gateway:8443 --username=foo --realm=bar ``` * Store password securely with a pinentry program: ```shell openfortivpn vpn-gateway:8443 --username=foo --pinentry=pinentry-mac ``` * Connect with a user certificate and no password: ```shell openfortivpn vpn-gateway:8443 --username= --password= --user-cert=cert.pem --user-key=key.pem ``` * Connect using SAML login: ```shell openfortivpn vpn-gateway:8443 --saml-login ``` * Don't set IP routes and don't add VPN nameservers to `/etc/resolv.conf`: ```shell openfortivpn vpn-gateway:8443 -u foo --no-routes --no-dns --pppd-no-peerdns ``` * Using a configuration file: ```shell openfortivpn -c /etc/openfortivpn/my-config ``` With `/etc/openfortivpn/my-config` containing: ```ini host = vpn-gateway port = 8443 username = foo set-dns = 0 pppd-use-peerdns = 0 # X509 certificate sha256 sum, trust only this one! trusted-cert = e46d4aff08ba6914e64daa85bc6112a422fa7ce16631bff0b592a28556f993db ``` * For the full list of config options, see the `CONFIGURATION` section of ```shell man openfortivpn ``` Smartcard --------- Smartcard support needs `openssl pkcs engine` and `opensc` to be installed. The pkcs11-engine from libp11 needs to be compiled with p11-kit-devel installed. Check [#464](https://github.com/adrienverge/openfortivpn/issues/464) for a discussion of known issues in this area. To make use of your smartcard put at least `pkcs11:` to the user-cert config or commandline option. It takes the full or a partial PKCS#11 token URI. ```ini user-cert = pkcs11: user-cert = pkcs11:token=someuser user-cert = pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=012345678;token=someuser username = password = ``` In most cases `user-cert = pkcs11:` will do it, but if needed you can get the token-URI with `p11tool --list-token-urls`. Multiple readers are currently not supported. Smartcard support has been tested with Yubikey under Linux, but other PIV enabled smartcards may work too. On Mac OS X Mojave it is known that the pkcs engine-by-id is not found. Installing ---------- ### Installing existing packages Some Linux distributions provide `openfortivpn` packages: * [Fedora / CentOS](https://packages.fedoraproject.org/pkgs/openfortivpn) * [openSUSE / SLE](https://software.opensuse.org/package/openfortivpn) * [Gentoo](https://packages.gentoo.org/packages/net-vpn/openfortivpn) * [NixOS](https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/networking/openfortivpn) * [Arch Linux](https://archlinux.org/packages/extra/x86_64/openfortivpn) * [Debian](https://packages.debian.org/stable/openfortivpn) * [Ubuntu](https://packages.ubuntu.com/search?keywords=openfortivpn) * [Solus](https://dev.getsol.us/source/openfortivpn/) * [Alpine Linux](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/openfortivpn) On macOS both [Homebrew](https://formulae.brew.sh/formula/openfortivpn) and [MacPorts](https://ports.macports.org/port/openfortivpn) provide an `openfortivpn` package. Either [install Homebrew](https://brew.sh/) then install openfortivpn: ```shell # Install 'Homebrew' /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" # Install 'openfortivpn' brew install openfortivpn ``` or [install MacPorts](https://www.macports.org/install.php) then install openfortivpn: ```shell # Install 'openfortivpn' sudo port install openfortivpn ``` A more complete overview can be obtained from [repology](https://repology.org/project/openfortivpn/versions). ### Building and installing from source For other distros, you'll need to build and install from source: 1. Install build dependencies. * RHEL/CentOS/Fedora: `gcc` `automake` `autoconf` `openssl-devel` `make` `pkg-config` * Debian/Ubuntu: `gcc` `automake` `autoconf` `libssl-dev` `make` `pkg-config` * Arch Linux: `gcc` `automake` `autoconf` `openssl` `pkg-config` * Gentoo Linux: `net-dialup/ppp` `pkg-config` * openSUSE: `gcc` `automake` `autoconf` `libopenssl-devel` `pkg-config` * macOS (Homebrew): `automake` `autoconf` `openssl@1.1` `pkg-config` * FreeBSD: `automake` `autoconf` `libressl` `pkgconf` On Linux, if you manage your kernel yourself, ensure to compile those modules: ```text CONFIG_PPP=m CONFIG_PPP_ASYNC=m ``` On macOS, install 'Homebrew' to install the build dependencies: ```shell # Install 'Homebrew' /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" # Install Dependencies brew install automake autoconf openssl@1.1 pkg-config # You may need to make this openssl available to compilers and pkg-config export LDFLAGS="-L/usr/local/opt/openssl/lib $LDFLAGS" export CPPFLAGS="-I/usr/local/opt/openssl/include $CPPFLAGS" export PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH" ``` 2. Build and install. ```shell ./autogen.sh ./configure --prefix=/usr/local --sysconfdir=/etc make sudo make install ``` If targeting platforms with pppd < 2.5.0 such as current version of macOS, we suggest you configure with option --enable-legacy-pppd: ```shell ./autogen.sh ./configure --prefix=/usr/local --sysconfdir=/etc --enable-legacy-pppd make sudo make install ``` If you need to specify the openssl location you can set the `$PKG_CONFIG_PATH` environment variable. For fine-tuning check the available configure arguments with `./configure --help` especially when you are cross compiling. Finally, install runtime dependency `ppp` or `pppd`. Running as root? ---------------- openfortivpn needs elevated privileges at three steps during tunnel set up: * when spawning a `/usr/sbin/pppd` process; * when setting IP routes through VPN (when the tunnel is up); * when adding nameservers to `/etc/resolv.conf` (when the tunnel is up). For these reasons, you need to use `sudo openfortivpn`. If you need it to be usable by non-sudoer users, you might consider adding an entry in `/etc/sudoers` or a file under `/etc/sudoers.d`. For example: ```shell visudo -f /etc/sudoers.d/openfortivpn ``` ```text Cmnd_Alias OPENFORTIVPN = /usr/bin/openfortivpn %adm ALL = (ALL) OPENFORTIVPN ``` Adapt the above example by changing the `openfortivpn` path or choosing a group different from `adm` - such as a dedicated `openfortivpn` group. **Warning**: Make sure only trusted users can run openfortivpn as root! As described in [#54](https://github.com/adrienverge/openfortivpn/issues/54), a malicious user could use `--pppd-plugin` and `--pppd-log` options to divert the program's behaviour. SSO/SAML/2FA ------------ In some cases, the server may require the VPN client to load and interact with a web page containing JavaScript. Depending on the complexity of the web page, interpreting the web page might be beyond the reach of a command line program such as openfortivpn. In such cases, you may use an external program spawning a full-fledged web browser such as [openfortivpn-webview](https://github.com/gm-vm/openfortivpn-webview) to authenticate and retrieve a session cookie. This cookie can be fed to openfortivpn using option `--cookie-on-stdin`. Obviously, such a solution requires a graphic session. When started using `--saml-login` the program creates a web server that accepts SAML login requests. To login using SAML you just have to open `<your-vpn-domain>/remote/saml/start?redirect=1` and follow the login steps. At the end of the login process the page will be redirected to `http://127.0.0.1:8020/?id=<session-id>` Contributing ------------ Feel free to make pull requests! C coding style should follow the [Linux kernel coding style](https://www.kernel.org/doc/html/latest/process/coding-style.html). �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/LICENSE.OpenSSL�����������������������������������������������������������������0000664�0001750�0001750�00000013751�14753334500�016537� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� LICENSE ISSUES ============== The OpenSSL toolkit stays under a double license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts. OpenSSL License --------------- /* ==================================================================== * Copyright (c) 1998-2019 The OpenSSL Project. 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. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" * * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * openssl-core@openssl.org. * * 5. Products derived from this software may not be called "OpenSSL" * nor may "OpenSSL" appear in their names without prior written * permission of the OpenSSL Project. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit (http://www.openssl.org/)" * * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY * EXPRESSED 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 OpenSSL PROJECT OR * ITS 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 product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim * Hudson (tjh@cryptsoft.com). * */ Original SSLeay License ----------------------- /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) * All rights reserved. * * This package is an SSL implementation written * by Eric Young (eay@cryptsoft.com). * The implementation was written so as to conform with Netscapes SSL. * * This library is free for commercial and non-commercial use as long as * the following conditions are aheared to. The following conditions * apply to all code found in this distribution, be it the RC4, RSA, * lhash, DES, etc., code; not just the SSL code. The SSL documentation * included with this distribution is covered by the same copyright terms * except that the holder is Tim Hudson (tjh@cryptsoft.com). * * Copyright remains Eric Young's, and as such any Copyright notices in * the code are not to be removed. * If this package is used in a product, Eric Young should be given attribution * as the author of the parts of the library used. * This can be in the form of a textual message at program startup or * in documentation (online or textual) provided with the package. * * 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 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * "This product includes cryptographic software written by * Eric Young (eay@cryptsoft.com)" * The word 'cryptographic' can be left out if the rouines from the library * being used are not cryptographic related :-). * 4. If you include any Windows specific code (or a derivative thereof) from * the apps directory (application code) you must include an acknowledgement: * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" * * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. * * The licence and distribution terms for any publically available version or * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] */ �����������������������openfortivpn-1.23.1/lib/����������������������������������������������������������������������������0000775�0001750�0001750�00000000000�14753334500�015007� 5����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/lib/systemd/��������������������������������������������������������������������0000775�0001750�0001750�00000000000�14753334500�016477� 5����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/lib/systemd/system/�������������������������������������������������������������0000775�0001750�0001750�00000000000�14753334500�020023� 5����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/lib/systemd/system/openfortivpn@.service.in�������������������������������������0000664�0001750�0001750�00000000737�14753334500�024652� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=OpenFortiVPN for %I After=network-online.target Wants=network-online.target systemd-networkd-wait-online.service Documentation=man:openfortivpn(1) Documentation=https://github.com/adrienverge/openfortivpn#readme Documentation=https://github.com/adrienverge/openfortivpn/wiki [Service] Type=notify PrivateTmp=true ExecStart=@BINDIR@/openfortivpn -c @SYSCONFDIR@/openfortivpn/%I.conf Restart=on-failure OOMScoreAdjust=-100 [Install] WantedBy=multi-user.target ���������������������������������openfortivpn-1.23.1/src/����������������������������������������������������������������������������0000775�0001750�0001750�00000000000�14753334500�015030� 5����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/io.h������������������������������������������������������������������������0000664�0001750�0001750�00000003407�14753334500�015614� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_IO_H #define OPENFORTIVPN_IO_H #include <sys/types.h> #include <pthread.h> #include <stddef.h> #include <stdint.h> /* * For performance reasons, we store the 6-byte header used by the TLS * communication right in front of the real PPP packet data. This way, * SSL_write can be called directly on packet->content, instead of memcpy'ing * the header + data to a temporary buffer. */ struct ppp_packet { struct ppp_packet *next; size_t len; // length of data; actual length is 6 + len uint8_t content[]; }; #define pkt_header(packet) ((packet)->content) #define pkt_data(packet) ((packet)->content + 6) struct ppp_packet_pool { pthread_mutex_t mutex; pthread_cond_t new_data; struct ppp_packet *list_head; }; #define init_ppp_packet_pool(pool) \ do { \ pthread_mutex_init(&(pool)->mutex, NULL); \ pthread_cond_init(&(pool)->new_data, NULL); \ (pool)->list_head = NULL; \ } while (0) #define destroy_ppp_packet_pool(pool) \ pthread_mutex_destroy(&(pool)->mutex) struct tunnel; int io_loop(struct tunnel *tunnel); int get_sig_received(void); #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/http_server.h���������������������������������������������������������������0000664�0001750�0001750�00000001570�14753334500�017551� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2025 Rainer Keller * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_HTTP_SERVER_H #define OPENFORTIVPN_HTTP_SERVER_H #include "config.h" int wait_for_http_request(struct vpn_config *config); #endif // OPENFORTIVPN_HTTP_SERVER_H ����������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/hdlc.h����������������������������������������������������������������������0000664�0001750�0001750�00000003130�14753334500�016110� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_HDLC_H #define OPENFORTIVPN_HDLC_H #include <sys/types.h> #include <stddef.h> #include <stdint.h> #include <stdlib.h> #define ERR_HDLC_BUFFER_TOO_SMALL -1 #define ERR_HDLC_NO_FRAME_FOUND -2 #define ERR_HDLC_INVALID_FRAME -3 #define ERR_HDLC_BAD_CHECKSUM -4 /* * These macros are used to help decide how much to malloc before encoding or * decoding. They over-evaluate the output size so it is sure the output will * fit. */ #define estimated_encoded_size(ppp_pkt_len) (9 + 2 * (ppp_pkt_len)) #define estimated_decoded_size(hdlc_frm_len) (hdlc_frm_len) void init_hdlc(void); ssize_t hdlc_encode(uint8_t *frame, size_t frmsize, const uint8_t *packet, size_t pktsize); ssize_t hdlc_find_frame(const uint8_t *buffer, size_t bufsize, off_t *start); ssize_t hdlc_decode(const uint8_t *frame, size_t frmsize, uint8_t *packet, size_t pktsize); #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/userinput.h�����������������������������������������������������������������0000664�0001750�0001750�00000001713�14753334500�017241� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Davíð Steinn Geirsson * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_USERINPUT_H #define OPENFORTIVPN_USERINPUT_H #include <stddef.h> void read_password(const char *pinentry, const char *hint, const char *prompt, char *pass, size_t len); char *read_from_stdin(size_t count); #endif �����������������������������������������������������openfortivpn-1.23.1/src/io.c������������������������������������������������������������������������0000664�0001750�0001750�00000051031�14753334500�015603� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, * you may extend this exception to your version of the file(s), but you are * not obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from * all source files in the program, then also delete it here. */ #include "io.h" #include "hdlc.h" #include "ssl.h" #include "tunnel.h" #include "log.h" #include <unistd.h> #include <arpa/inet.h> #include <netinet/tcp.h> #include <errno.h> #include <signal.h> #include <string.h> #if HAVE_MACH_MACH_H /* this is typical for mach kernel used on Mac OS X */ #include <mach/mach.h> /* Mac OS X defines sem_init but actually does not implement them */ typedef semaphore_t os_semaphore_t; #define SEM_INIT(sem, x, value) semaphore_create(mach_task_self(), sem, \ SYNC_POLICY_FIFO, value) #define SEM_WAIT(sem) semaphore_wait(*(sem)) #define SEM_POST(sem) semaphore_signal(*(sem)) #define SEM_DESTROY(sem) semaphore_destroy(mach_task_self(), *(sem)) #else #include <semaphore.h> typedef sem_t os_semaphore_t; #define SEM_INIT(sem, x, value) sem_init(sem, x, value) #define SEM_WAIT(sem) sem_wait(sem) #define SEM_POST(sem) sem_post(sem) #define SEM_DESTROY(sem) sem_destroy(sem) #endif #define PKT_BUF_SZ 0x1000 #if OPENSSL_VERSION_NUMBER < 0x10100000L static pthread_mutex_t *lockarray; static void lock_callback(int mode, int type, const char *file, int line) { if (mode & CRYPTO_LOCK) pthread_mutex_lock(&lockarray[type]); else pthread_mutex_unlock(&lockarray[type]); } static unsigned long thread_id(void) { return (unsigned long) pthread_self(); } static void init_ssl_locks(void) { lockarray = (pthread_mutex_t *) OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); for (int i = 0; i < CRYPTO_num_locks(); i++) pthread_mutex_init(&lockarray[i], NULL); CRYPTO_set_id_callback((unsigned long (*)()) thread_id); CRYPTO_set_locking_callback((void (*)()) lock_callback); } static void destroy_ssl_locks(void) { CRYPTO_set_locking_callback(NULL); for (int i = 0; i < CRYPTO_num_locks(); i++) pthread_mutex_destroy(&lockarray[i]); OPENSSL_free(lockarray); lockarray = NULL; } #else static void init_ssl_locks(void) { } static void destroy_ssl_locks(void) { } #endif // global variable to pass signal out of its handler volatile sig_atomic_t sig_received; //static variables are initialized to zero in C99 int get_sig_received(void) { return (int)sig_received; } /* * Adds a new packet to a pool. * * Warning: for performance reasons, this function does not check if the packet * is already present in the list. If it is, there will be a loop in the list * and this will result in an unpredictable behavior. */ static void pool_push(struct ppp_packet_pool *pool, struct ppp_packet *new) { struct ppp_packet *current; pthread_mutex_lock(&pool->mutex); new->next = NULL; current = pool->list_head; if (current == NULL) { pool->list_head = new; } else { while (current->next != NULL) current = current->next; current->next = new; } pthread_cond_signal(&pool->new_data); pthread_mutex_unlock(&pool->mutex); } /* * Gets the first packet from a pool. */ static struct ppp_packet *pool_pop(struct ppp_packet_pool *pool) { struct ppp_packet *first; pthread_mutex_lock(&pool->mutex); while (pool->list_head == NULL) pthread_cond_wait(&pool->new_data, &pool->mutex); first = pool->list_head; pool->list_head = first->next; first->next = NULL; pthread_mutex_unlock(&pool->mutex); return first; } static os_semaphore_t sem_pppd_ready; static os_semaphore_t sem_if_config; static os_semaphore_t sem_stop_io; /* * Thread to read bytes from the pppd pty, convert them to ppp packets and add * them to the 'pty_to_ssl' pool. */ static void *pppd_read(void *arg) { struct tunnel *tunnel = (struct tunnel *) arg; uint8_t buf[PKT_BUF_SZ]; int first_time = 1; off_t off_r, off_w; fd_set read_fd; FD_ZERO(&read_fd); FD_SET(tunnel->pppd_pty, &read_fd); log_debug("%s thread\n", __func__); // Wait for pppd to be ready off_w = 0; while (1) { ssize_t n; int sel; sel = select(tunnel->pppd_pty + 1, &read_fd, NULL, NULL, NULL); if (sel == -1) { log_error("select: %s\n", strerror(errno)); break; } else if (sel == 0) { log_warn("select returned 0\n"); continue; } n = read(tunnel->pppd_pty, &buf[off_w], PKT_BUF_SZ - off_w); if (n == -1) { log_error("read: %s\n", strerror(errno)); break; } else if (n == 0) { log_warn("read returned %ld\n", n); break; } else if (first_time) { // pppd did talk, now we can write to it if we want SEM_POST(&sem_pppd_ready); first_time = 0; } off_w += n; // We have data in the buffer, there may be zero, one or many // packets inside. off_r = 0; while (1) { ssize_t frm_len, pktsize; struct ppp_packet *packet, *repacket; frm_len = hdlc_find_frame(buf, off_w, &off_r); if (frm_len == ERR_HDLC_NO_FRAME_FOUND) break; pktsize = estimated_decoded_size(frm_len); packet = malloc(sizeof(*packet) + 6 + pktsize); if (packet == NULL) { log_error("malloc: %s\n", strerror(errno)); break; } pktsize = hdlc_decode(&buf[off_r], frm_len, pkt_data(packet), pktsize); if (pktsize < 0) { log_error("Failed to decode PPP packet from HDLC frame (%s).\n", (pktsize == ERR_HDLC_BAD_CHECKSUM ? "bad checksum" : (pktsize == ERR_HDLC_INVALID_FRAME ? "invalid frame" : "unknown"))); goto exit; } // Reduce the malloc'ed area now that we know the // actual packet length repacket = realloc(packet, sizeof(*packet) + 6 + pktsize); if (repacket == NULL) { free(packet); goto exit; } packet = repacket; packet->len = pktsize; log_debug("%s ---> gateway (%lu bytes)\n", PPP_DAEMON, packet->len); #if HAVE_USR_SBIN_PPPD log_packet("pppd: ", packet->len, pkt_data(packet)); #else log_packet("ppp: ", packet->len, pkt_data(packet)); #endif pool_push(&tunnel->pty_to_ssl_pool, packet); off_r += frm_len; } // Do not discard remaining data if (off_r > 0 && off_r < off_w) memmove(buf, &buf[off_r], off_w - off_r); off_w = off_w - off_r; } exit: // Send message to main thread to stop other threads SEM_POST(&sem_stop_io); return NULL; } /* * Thread to pop packets from the 'ssl_to_pty' pool, and send them to the pppd * process through its pty. */ static void *pppd_write(void *arg) { struct tunnel *tunnel = (struct tunnel *) arg; fd_set write_fd; FD_ZERO(&write_fd); FD_SET(tunnel->pppd_pty, &write_fd); // Write for pppd to talk first, otherwise unpredictable SEM_WAIT(&sem_pppd_ready); log_debug("%s thread\n", __func__); while (1) { struct ppp_packet *packet; ssize_t hdlc_bufsize, len, n, written; uint8_t *hdlc_buffer; // This waits until a packet has arrived from the gateway packet = pool_pop(&tunnel->ssl_to_pty_pool); hdlc_bufsize = estimated_encoded_size(packet->len); hdlc_buffer = malloc(hdlc_bufsize); if (hdlc_buffer == NULL) { log_error("malloc: %s\n", strerror(errno)); break; } len = hdlc_encode(hdlc_buffer, hdlc_bufsize, pkt_data(packet), packet->len); if (len < 0) { log_error("Failed to encode PPP packet into HDLC frame.\n"); goto err_free_buf; } written = 0; while (written < len) { int sel; sel = select(tunnel->pppd_pty + 1, NULL, &write_fd, NULL, NULL); if (sel == -1) { log_error("select: %s\n", strerror(errno)); break; } else if (sel == 0) { log_warn("select returned 0\n"); continue; } n = write(tunnel->pppd_pty, &hdlc_buffer[written], len - written); // retry on repeatable failure if (n == -1) { if (errno == EAGAIN) { continue; } else { log_error("write: %s\n", strerror(errno)); goto err_free_buf; } } written += n; } free(hdlc_buffer); free(packet); continue; err_free_buf: free(hdlc_buffer); free(packet); break; } // Send message to main thread to stop other threads SEM_POST(&sem_stop_io); return NULL; } #define packet_is_ip_plus_dns(packet) \ ((packet)->len >= 12 \ && pkt_data(packet)[0] == 0x80 \ && pkt_data(packet)[1] == 0x21 \ && pkt_data(packet)[2] == 0x02 \ && pkt_data(packet)[6] == 0x03) #define packet_is_end_negociation(packet) \ (((packet)->len == 6 \ && pkt_data(packet)[0] == 0x80 \ && pkt_data(packet)[1] == 0x21 \ && pkt_data(packet)[2] == 0x01 \ && pkt_data(packet)[4] == 0x00 \ && pkt_data(packet)[5] == 0x04) || \ ((packet)->len >= 12 \ && pkt_data(packet)[0] == 0x80 \ && pkt_data(packet)[1] == 0x21 \ && pkt_data(packet)[2] == 0x02)) static inline void set_tunnel_ips(struct tunnel *tunnel, struct ppp_packet *packet) { memcpy(&tunnel->ipv4.ip_addr.s_addr, &pkt_data(packet)[8], sizeof(uint32_t)); if (packet->len >= 18 && pkt_data(packet)[12] == 0x81 && tunnel->config->pppd_use_peerdns) { memcpy(&tunnel->ipv4.ns1_addr.s_addr, &pkt_data(packet)[14], sizeof(uint32_t)); } if (packet->len >= 24 && pkt_data(packet)[18] == 0x83 && tunnel->config->pppd_use_peerdns) { memcpy(&tunnel->ipv4.ns2_addr.s_addr, &pkt_data(packet)[20], sizeof(uint32_t)); } } #define printable_char(c) \ (c == '\t' || c == '\n' || (c >= ' ' && c <= '~')) static void debug_bad_packet(struct tunnel *tunnel, uint8_t *header) { uint8_t buffer[256]; int size; memcpy(buffer, header, 6 * sizeof(uint8_t)); size = safe_ssl_read(tunnel->ssl_handle, &buffer[6], 256 - 6); if (size < 0) return; size += 6; // Print hex dump do_log_packet(" (hex) ", size, buffer); // then print the raw string, after escaping non-displayable chars for (int i = 0; i < size; i++) if (!printable_char((char) buffer[i])) buffer[i] = '.'; buffer[size] = buffer[256 - 1] = '\0'; printf(" (raw) %s\n", (const char *) buffer); } /* * Thread to read bytes from the TLS socket, convert them to ppp packets and add * them to the 'ssl_to_pty' pool. */ static void *ssl_read(void *arg) { struct tunnel *tunnel = (struct tunnel *) arg; //uint8_t buf[PKT_BUF_SZ]; log_debug("%s thread\n", __func__); while (1) { struct ppp_packet *packet; int ret; uint8_t header[6]; static const char http_header[6] = "HTTP/1"; uint16_t total, magic, size; ret = safe_ssl_read_all(tunnel->ssl_handle, header, 6); if (ret < 0) { log_debug("Error reading from TLS connection (%s).\n", err_ssl_str(ret)); break; } if (memcmp(header, http_header, 6) == 0) { /* * When the TLS-VPN portal has not been set up to allow * tunnel mode for VPN clients, while it allows web mode * for web browsers, it returns an HTTP error instead of * a PPP packet: * HTTP/1.1 403 Forbidden */ log_error("Could not authenticate to the gateway. Please make sure tunnel mode is allowed by the gateway, check the realm, etc.\n"); break; } total = (uint16_t)(header[0]) << 8 | header[1]; magic = (uint16_t)(header[2]) << 8 | header[3]; size = (uint16_t)(header[4]) << 8 | header[5]; if (magic != 0x5050 || total < 7 || total - 6 != size) { log_error("Received bad header from gateway:\n"); debug_bad_packet(tunnel, header); break; } packet = malloc(sizeof(struct ppp_packet) + 6 + size); if (packet == NULL) { log_error("malloc: %s\n", strerror(errno)); break; } memcpy(pkt_header(packet), header, 6); packet->len = size; ret = safe_ssl_read_all(tunnel->ssl_handle, pkt_data(packet), size); if (ret < 0) { log_debug("Error reading from TLS connection (%s).\n", err_ssl_str(ret)); free(packet); break; } log_debug("gateway ---> %s (%lu bytes)\n", PPP_DAEMON, packet->len); log_packet("gtw: ", packet->len, pkt_data(packet)); pool_push(&tunnel->ssl_to_pty_pool, packet); if (tunnel->state == STATE_CONNECTING) { if (packet_is_ip_plus_dns(packet)) { char line[ARRAY_SIZE("[xxx.xxx.xxx.xxx], ns [xxx.xxx.xxx.xxx, xxx.xxx.xxx.xxx], ns_suffix []") + MAX_DOMAIN_LENGTH]; set_tunnel_ips(tunnel, packet); strcpy(line, "["); strncat(line, inet_ntoa(tunnel->ipv4.ip_addr), 15); strcat(line, "], ns ["); strncat(line, inet_ntoa(tunnel->ipv4.ns1_addr), 15); strcat(line, ", "); strncat(line, inet_ntoa(tunnel->ipv4.ns2_addr), 15); if (tunnel->ipv4.dns_suffix) { strcat(line, "], ns_suffix ["); strncat(line, tunnel->ipv4.dns_suffix, MAX_DOMAIN_LENGTH); } strcat(line, "]"); log_info("Got addresses: %s\n", line); } if (packet_is_end_negociation(packet)) { log_info("Negotiation complete.\n"); SEM_POST(&sem_if_config); } } } // Send message to main thread to stop other threads SEM_POST(&sem_stop_io); return NULL; } /* * Thread to pop packets from the 'pty_to_ssl' pool, and write them to the TLS * socket. */ static void *ssl_write(void *arg) { struct tunnel *tunnel = (struct tunnel *) arg; log_debug("%s thread\n", __func__); while (1) { struct ppp_packet *packet; int ret; // This waits until a packet has arrived from pppd packet = pool_pop(&tunnel->pty_to_ssl_pool); pkt_header(packet)[0] = (6 + packet->len) >> 8; pkt_header(packet)[1] = (6 + packet->len) & 0xff; pkt_header(packet)[2] = 0x50; pkt_header(packet)[3] = 0x50; pkt_header(packet)[4] = packet->len >> 8; pkt_header(packet)[5] = packet->len & 0xff; do { ret = safe_ssl_write(tunnel->ssl_handle, packet->content, 6 + packet->len); } while (ret == 0); if (ret < 0) { log_debug("Error writing to TLS connection (%s).\n", err_ssl_str(ret)); free(packet); break; } free(packet); } // Send message to main thread to stop other threads SEM_POST(&sem_stop_io); return NULL; } /* * Thread to pop packets from the 'pty_to_ssl' pool, and write them to the TLS * socket. */ static void *if_config(void *arg) { struct tunnel *tunnel = (struct tunnel *) arg; int timeout = 60000000; // one minute log_debug("%s thread\n", __func__); // Wait for the right moment to configure IP interface SEM_WAIT(&sem_if_config); while (1) { if (ppp_interface_is_up(tunnel)) { if (tunnel->on_ppp_if_up != NULL) if (tunnel->on_ppp_if_up(tunnel)) goto error; tunnel->state = STATE_UP; break; } else if (timeout == 0) { log_error("Timed out waiting for the ppp interface to be UP.\n"); break; } log_debug("%s: not ready yet...\n", __func__); timeout -= 200000; usleep(200000); } if (tunnel->state != STATE_UP) goto error; return NULL; error: // Send message to main thread to stop other threads SEM_POST(&sem_stop_io); return NULL; } static void sig_handler(int signo) { sig_received = signo; if (signo == SIGINT || signo == SIGTERM) SEM_POST(&sem_stop_io); } int io_loop(struct tunnel *tunnel) { int tcp_nodelay_flag = 1; int ret = 0; // keep track of pthread_* return value int fatal = 0; // indicate a fatal error during pthread_* calls pthread_t pty_read_thread; pthread_t pty_write_thread; pthread_t ssl_read_thread; pthread_t ssl_write_thread; pthread_t if_config_thread; SEM_INIT(&sem_pppd_ready, 0, 0); SEM_INIT(&sem_if_config, 0, 0); SEM_INIT(&sem_stop_io, 0, 0); init_ppp_packet_pool(&tunnel->ssl_to_pty_pool); init_ppp_packet_pool(&tunnel->pty_to_ssl_pool); init_ssl_locks(); init_hdlc(); /* * I noticed that using TCP_NODELAY (i.e. disabling Nagle's algorithm) * gives much better performance. Probably because setting up the VPN * is sending and receiving many small packets. * A small benchmark gave these results: * - with TCP_NODELAY: ~ 4000 kbit/s * - without TCP_NODELAY: ~ 1200 kbit/s * - forticlientsslvpn from Fortinet: ~ 3700 kbit/s * - openfortivpn, Python version: ~ 2000 kbit/s * (with or without TCP_NODELAY) */ if (setsockopt(tunnel->ssl_socket, IPPROTO_TCP, TCP_NODELAY, (const char *) &tcp_nodelay_flag, sizeof(int))) { log_error("setsockopt TCP_NODELAY: %s\n", strerror(errno)); goto err_sockopt; } // on osx this prevents the program from being stopped with ctrl-c #if !HAVE_MACH_MACH_H // Disable SIGINT and SIGTERM for the future spawned threads sigset_t sigset, oldset; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGTERM); pthread_sigmask(SIG_BLOCK, &sigset, &oldset); #endif // Set signal handler if (signal(SIGINT, sig_handler) == SIG_ERR || signal(SIGTERM, sig_handler) == SIG_ERR) goto err_signal; // Ignore SIGHUP if (signal(SIGHUP, SIG_IGN) == SIG_ERR) goto err_signal; // create all workers, stop on first error and bail out ret = pthread_create(&pty_read_thread, NULL, pppd_read, tunnel); if (ret != 0) { log_debug("Error creating pty_read_thread: %s\n", strerror(ret)); goto err_thread; } ret = pthread_create(&pty_write_thread, NULL, pppd_write, tunnel); if (ret != 0) { log_debug("Error creating pty_write_thread: %s\n", strerror(ret)); goto err_thread; } ret = pthread_create(&ssl_read_thread, NULL, ssl_read, tunnel); if (ret != 0) { log_debug("Error creating ssl_read_thread: %s\n", strerror(ret)); goto err_thread; } ret = pthread_create(&ssl_write_thread, NULL, ssl_write, tunnel); if (ret != 0) { log_debug("Error creating ssl_write_thread: %s\n", strerror(ret)); goto err_thread; } ret = pthread_create(&if_config_thread, NULL, if_config, tunnel); if (ret != 0) { log_debug("Error creating if_config_thread: %s\n", strerror(ret)); goto err_thread; } #if !HAVE_MACH_MACH_H // Restore the signal for the main thread pthread_sigmask(SIG_SETMASK, &oldset, NULL); #endif // Wait for one of the thread to ask termination SEM_WAIT(&sem_stop_io); log_info("Cancelling threads...\n"); // no goto err_thread here, try to cancel all threads ret = pthread_cancel(if_config_thread); if (ret != 0) log_debug("Error canceling if_config_thread: %s\n", strerror(ret)); ret = pthread_cancel(ssl_write_thread); if (ret != 0) log_debug("Error canceling ssl_write_thread: %s\n", strerror(ret)); ret = pthread_cancel(ssl_read_thread); if (ret != 0) log_debug("Error canceling safe_ssl_read_thread: %s\n", strerror(ret)); ret = pthread_cancel(pty_write_thread); if (ret != 0) log_debug("Error canceling pty_write_thread: %s\n", strerror(ret)); ret = pthread_cancel(pty_read_thread); if (ret != 0) log_debug("Error canceling pty_read_thread: %s\n", strerror(ret)); log_info("Cleanup, joining threads...\n"); // failure to clean is a possible zombie thread, consider it fatal ret = pthread_join(if_config_thread, NULL); if (ret != 0) { log_debug("Error joining if_config_thread: %s\n", strerror(ret)); fatal = 1; } ret = pthread_join(ssl_write_thread, NULL); if (ret != 0) { log_debug("Error joining ssl_write_thread: %s\n", strerror(ret)); fatal = 1; } ret = pthread_join(ssl_read_thread, NULL); if (ret != 0) { log_debug("Error joining ssl_read_thread: %s\n", strerror(ret)); fatal = 1; } ret = pthread_join(pty_write_thread, NULL); if (ret != 0) { log_debug("Error joining pty_write_thread: %s\n", strerror(ret)); fatal = 1; } ret = pthread_join(pty_read_thread, NULL); if (ret != 0) { log_debug("Error joining pty_read_thread: %s\n", strerror(ret)); fatal = 1; } destroy_ssl_locks(); destroy_ppp_packet_pool(&tunnel->pty_to_ssl_pool); destroy_ppp_packet_pool(&tunnel->ssl_to_pty_pool); SEM_DESTROY(&sem_stop_io); SEM_DESTROY(&sem_if_config); SEM_DESTROY(&sem_pppd_ready); // should we have detected a fatal error if (fatal) goto err_thread; return 0; err_thread: err_signal: err_sockopt: return 1; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/log.h�����������������������������������������������������������������������0000664�0001750�0001750�00000003705�14753334500�015767� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_LOG_H #define OPENFORTIVPN_LOG_H #include <stddef.h> #include <stdint.h> // Assign enum values explicitly, we're using them in a lookup enum log_verbosity { OFV_LOG_MUTE = 0, OFV_LOG_ERROR = 1, OFV_LOG_WARN = 2, OFV_LOG_INFO = 3, OFV_LOG_DEBUG = 4, OFV_LOG_DEBUG_DETAILS = 5, OFV_LOG_DEBUG_ALL = 6 }; extern enum log_verbosity loglevel; void init_logging(void); void set_syslog(int do_syslog); void increase_verbosity(void); void decrease_verbosity(void); void do_log(int verbosity, const char *format, ...); #define log_level(verbosity, ...) \ do { \ if (loglevel >= verbosity) \ do_log(verbosity, __VA_ARGS__); \ } while (0) #define log_error(...) \ log_level(OFV_LOG_ERROR, __VA_ARGS__) #define log_warn(...) \ log_level(OFV_LOG_WARN, __VA_ARGS__) #define log_info(...) \ log_level(OFV_LOG_INFO, __VA_ARGS__) #define log_debug(...) \ log_level(OFV_LOG_DEBUG, __VA_ARGS__) #define log_debug_details(...) \ log_level(OFV_LOG_DEBUG_DETAILS, __VA_ARGS__) #define log_debug_all(...) \ log_level(OFV_LOG_DEBUG_ALL, __VA_ARGS__) #define log_packet(...) \ do { \ if (loglevel >= OFV_LOG_DEBUG_DETAILS) \ do_log_packet(__VA_ARGS__); \ } while (0) void do_log_packet(const char *prefix, size_t len, const uint8_t *packet); #endif �����������������������������������������������������������openfortivpn-1.23.1/src/xml.h�����������������������������������������������������������������������0000664�0001750�0001750�00000001552�14753334500�016004� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Lubomir Rintel * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_XML_H #define OPENFORTIVPN_XML_H const char *xml_find(char t, const char *tag, const char *buf, int nest); char *xml_get(const char *buf); #endif ������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/ipv4.h����������������������������������������������������������������������0000664�0001750�0001750�00000006732�14753334500�016073� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_IPV4_H #define OPENFORTIVPN_IPV4_H #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <net/route.h> #if !HAVE_RT_ENTRY_WITH_RT_DST /* * On Mac OS X and FreeBSD struct rtentry is not directly available. * On FreeBSD one could #define _WANT_RTENTRY but the struct does not * contain rt_dst for instance. The entries for mask and destination * are maintained in a separate radix_tree structure by the routing * table instance. We can not simply copy rtentry structures. */ /* This structure gets passed by the SIOCADDRT and SIOCDELRT calls. */ struct rtentry { unsigned long rt_hash; /* hash key for lookups */ struct sockaddr rt_dst; /* target address */ struct sockaddr rt_gateway; /* gateway addr (RTF_GATEWAY) */ struct sockaddr rt_genmask; /* target network mask (IP) */ short rt_flags; short rt_refcnt; unsigned long rt_use; struct ifnet *rt_ifp; short rt_metric; /* +1 for binary compatibility! */ char *rt_dev; /* forcing the device at add */ unsigned long rt_mss; /* per route MTU/Window */ unsigned long rt_mtu; /* compatibility */ unsigned long rt_window; /* Window clamping */ unsigned short rt_irtt; /* Initial RTT */ }; #endif #define ROUTE_IFACE_LEN 32 #define MAX_SPLIT_ROUTES 65535 #define STEP_SPLIT_ROUTES 32 struct ipv4_config { struct in_addr ip_addr; struct in_addr ns1_addr; struct in_addr ns2_addr; char *dns_suffix; int ns1_was_there; // were ns1 already in /etc/resolv.conf? int ns2_was_there; // were ns2 already in /etc/resolv.conf? int dns_suffix_was_there; // was the dns suffix already there? int split_routes; int route_to_vpn_is_added; struct rtentry def_rt; // default route struct rtentry gtw_rt; // route to access VPN gateway struct rtentry ppp_rt; // new default route through VPN struct rtentry *split_rt; // split VPN routes }; // Dummy function to make gcc 6 happy static inline struct sockaddr_in *cast_addr(struct sockaddr *addr) { return (struct sockaddr_in *) addr; } #define route_dest(route) (cast_addr(&(route)->rt_dst)->sin_addr) #define route_mask(route) (cast_addr(&(route)->rt_genmask)->sin_addr) #define route_gtw(route) (cast_addr(&(route)->rt_gateway)->sin_addr) #define route_iface(route) ((route)->rt_dev) struct tunnel; int ipv4_add_split_vpn_route(struct tunnel *tunnel, char *dest, char *mask, char *gateway); int ipv4_set_tunnel_routes(struct tunnel *tunnel); int ipv4_restore_routes(struct tunnel *tunnel); int ipv4_add_nameservers_to_resolv_conf(struct tunnel *tunnel); int ipv4_del_nameservers_from_resolv_conf(struct tunnel *tunnel); #endif ��������������������������������������openfortivpn-1.23.1/src/tunnel.h��������������������������������������������������������������������0000664�0001750�0001750�00000005225�14753334500�016512� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, * you may extend this exception to your version of the file(s), but you are * not obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from * all source files in the program, then also delete it here. */ #ifndef OPENFORTIVPN_TUNNEL_H #define OPENFORTIVPN_TUNNEL_H #include "config.h" #include "io.h" #include "ipv4.h" #include <openssl/ssl.h> #include <openssl/x509v3.h> #include <sys/types.h> #ifdef __clang__ /* * Get rid of Mac OS X 10.7 and greater deprecation warnings * see for instance https://wiki.openssl.org/index.php/Hostname_validation * this pragma selectively suppresses this type of warnings in clang */ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif enum tunnel_state { STATE_DOWN, STATE_CONNECTING, STATE_UP, STATE_DISCONNECTING }; struct tunnel { struct vpn_config *config; enum tunnel_state state; char cookie[COOKIE_SIZE + 1]; struct ppp_packet_pool ssl_to_pty_pool; struct ppp_packet_pool pty_to_ssl_pool; pid_t pppd_pid; pid_t pppd_pty; char ppp_iface[ROUTE_IFACE_LEN]; int ssl_socket; SSL_CTX *ssl_context; SSL *ssl_handle; struct ipv4_config ipv4; int (*on_ppp_if_up)(struct tunnel *tunnel); int (*on_ppp_if_down)(struct tunnel *tunnel); }; struct token { const char *uri; X509 *cert; }; int ppp_interface_is_up(struct tunnel *tunnel); int ssl_connect(struct tunnel *tunnel); int run_tunnel(struct vpn_config *config); #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/ipv4.c����������������������������������������������������������������������0000664�0001750�0001750�00000117255�14753334500�016071� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "ipv4.h" #include "tunnel.h" #include "config.h" #include "log.h" #include "xml.h" #include <unistd.h> #include <arpa/inet.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <assert.h> #define IPV4_GET_ROUTE_BUFFER_CHUNK_SIZE 65536 #define SHOW_ROUTE_BUFFER_SIZE 128 static char show_route_buffer[SHOW_ROUTE_BUFFER_SIZE]; #define ERR_IPV4_SEE_ERRNO -1 #define ERR_IPV4_NO_MEM -2 #define ERR_IPV4_PERMISSION -3 #define ERR_IPV4_NO_SUCH_ROUTE -4 #define ERR_IPV4_PROC_NET_ROUTE -5 static inline const char *err_ipv4_str(int code) { if (code == ERR_IPV4_SEE_ERRNO) return strerror(errno); else if (code == ERR_IPV4_NO_MEM) return "Not enough memory"; else if (code == ERR_IPV4_PERMISSION) return "Permission denied"; else if (code == ERR_IPV4_NO_SUCH_ROUTE) return "Route not found"; else if (code == ERR_IPV4_PROC_NET_ROUTE) return "Parsing /proc/net/route failed"; return "unknown"; } /* * Returns a string representation of the route, such as: * to 192.168.1.0/255.255.255.0 via 172.16.0.1 dev eth0 * * Warning: the returned buffer is static, so multiple calls will overwrite it. */ static char *ipv4_show_route(struct rtentry *route) { strcpy(show_route_buffer, "to "); strncat(show_route_buffer, inet_ntoa(route_dest(route)), 15); strcat(show_route_buffer, "/"); strncat(show_route_buffer, inet_ntoa(route_mask(route)), 15); if (route->rt_flags & RTF_GATEWAY) { strcat(show_route_buffer, " via "); strncat(show_route_buffer, inet_ntoa(route_gtw(route)), 15); } if (route_iface(route) != NULL) { strcat(show_route_buffer, " dev "); strncat(show_route_buffer, route_iface(route), SHOW_ROUTE_BUFFER_SIZE - strlen(show_route_buffer) - 1); } return show_route_buffer; } static inline int route_init(struct rtentry *route) { memset(route, 0, sizeof(*route)); cast_addr(&(route)->rt_dst)->sin_family = AF_INET; cast_addr(&(route)->rt_genmask)->sin_family = AF_INET; cast_addr(&(route)->rt_gateway)->sin_family = AF_INET; return 0; } static inline void route_destroy(struct rtentry *route) { free(route_iface(route)); route_iface(route) = NULL; } /* * Finds system IP route to a destination. * * The passed route must have dest and mask set. If the route is found, * the function searches for a match in the routing table and returns * that one. Note that dest and mask contain the network address and * the mask of the corresponding routing table entry after calling. * After calling ipv4_get_route it might be necessary to set dest * and mask again to the desired values for further processing. */ static int ipv4_get_route(struct rtentry *route) { size_t buffer_size = IPV4_GET_ROUTE_BUFFER_CHUNK_SIZE; char *buffer; char *realloc_buffer; int err = 0; char *start, *line; char *saveptr1 = NULL, *saveptr2 = NULL; uint32_t rtdest, rtmask, rtgtw; int rtfound = 0; /* * initialize the buffer with zeroes, aiming to address the * coverity issue "TAINTED_SCALAR passed to a tainted sink" * * Later on, the routing table is read into this buffer using * read() and therefore the content of the buffer is considered * tainted. strtok_r internally uses it in a loop boundary. * The theoretical problem is that the loop could iterate forever, * if the buffer contains a huge string which doesn't contain * the token character, which we are parsing for. * * We can declare this as a false positive, because * - the routing table is to some extent trusted input, * - it's not that large, * - and the loop in strtok_r increments the pointer in each * iteration until it reaches the area where we have ensured * that there is a delimiting '\0' character by proper * initialization. We ensure this also when growing the buffer. */ buffer = calloc(1, buffer_size); if (!buffer) { err = ERR_IPV4_SEE_ERRNO; goto end; } log_debug("ip route show %s\n", ipv4_show_route(route)); // store what we are looking for rtdest = route_dest(route).s_addr; rtmask = route_mask(route).s_addr; rtgtw = route_gtw(route).s_addr; // initialize the output record route_dest(route).s_addr = inet_addr("0.0.0.0"); route_mask(route).s_addr = inet_addr("0.0.0.0"); route_gtw(route).s_addr = inet_addr("0.0.0.0"); #if HAVE_PROC_NET_ROUTE /* this is not present on Mac OS X and FreeBSD */ int fd; uint32_t total_bytes_read = 0; // Cannot stat, mmap not lseek this special /proc file fd = open("/proc/net/route", O_RDONLY); if (fd == -1) { err = ERR_IPV4_SEE_ERRNO; goto end; } int bytes_read; while ((bytes_read = read(fd, buffer + total_bytes_read, buffer_size - total_bytes_read - 1)) > 0) { total_bytes_read += bytes_read; if ((buffer_size - total_bytes_read) < 1) { buffer_size += IPV4_GET_ROUTE_BUFFER_CHUNK_SIZE; realloc_buffer = realloc(buffer, buffer_size); if (realloc_buffer) { buffer = realloc_buffer; } else { err = ERR_IPV4_SEE_ERRNO; goto cleanup; } buffer[buffer_size-1] = '\0'; } } cleanup: if (close(fd)) log_warn("Could not close /proc/net/route (%s).\n", strerror(errno)); if (err) goto end; if (bytes_read < 0) { err = ERR_IPV4_SEE_ERRNO; goto end; } #else FILE *fp; uint32_t total_bytes_read = 0; char *saveptr3 = NULL; int have_ref = 0; int have_use = 0; static const char netstat_path[] = NETSTAT_PATH; if (access(netstat_path, F_OK) != 0) { log_error("%s: %s.\n", netstat_path, strerror(errno)); return 1; } log_debug("netstat_path: %s\n", netstat_path); // Open the command for reading fp = popen(NETSTAT_PATH " -f inet -rn", "r"); if (fp == NULL) { err = ERR_IPV4_SEE_ERRNO; goto end; } line = buffer; // Read the output a line at a time while (fgets(line, buffer_size - total_bytes_read - 1, fp) != NULL) { uint32_t bytes_read = strlen(line); total_bytes_read += bytes_read; if (bytes_read > 0 && line[bytes_read - 1] != '\n') { buffer_size += IPV4_GET_ROUTE_BUFFER_CHUNK_SIZE; realloc_buffer = realloc(buffer, buffer_size); if (realloc_buffer) { buffer = realloc_buffer; } else { err = ERR_IPV4_SEE_ERRNO; goto cleanup; } } line = buffer + total_bytes_read; } cleanup: if (pclose(fp)) log_warn("Could not close netstat pipe (%s).\n", strerror(errno)); if (err) goto end; // reserve enough memory (256 shorts) // to make sure not to access out of bounds later, // for ipv4 only unsigned short is allowed unsigned short flag_table[256] = { 0 }; /* * Fill the flag_table now. Unfortunately it is not easy * to do this in a more elegant way. The problem here * is that these are already preprocessor macros and * we can't use them as arguments for another macro which * would include the #ifdef statements. * * Also, not all flags might be allowed in the context * of ipv4, and the code depends on which ones are * actually implemented on the target platform, which * might also be varying between Mac OS X versions. * */ #ifdef RTF_PROTO1 // Protocol specific routing flag #1 flag_table['1'] = RTF_PROTO1 & USHRT_MAX; #endif #ifdef RTF_PROTO2 // Protocol specific routing flag #2 flag_table['2'] = RTF_PROTO2 & USHRT_MAX; #endif #ifdef RTF_PROTO3 // Protocol specific routing flag #3 flag_table['3'] = RTF_PROTO3 & USHRT_MAX; #endif #ifdef RTF_BLACKHOLE // Just discard packets (during updates) flag_table['B'] = RTF_BLACKHOLE & USHRT_MAX; #endif #ifdef RTF_BROADCAST // The route represents a broadcast address flag_table['b'] = RTF_BROADCAST & USHRT_MAX; #endif #ifdef RTF_CLONING // Generate new routes on use flag_table['C'] = RTF_CLONING & USHRT_MAX; #endif #ifdef RTF_PRCLONING // Protocol-specified generate new routes on use flag_table['c'] = RTF_PRCLONING & USHRT_MAX; #endif #ifdef RTF_DYNAMIC // Created dynamically (by redirect) flag_table['D'] = RTF_DYNAMIC & USHRT_MAX; #endif #ifdef RTF_GATEWAY // Destination requires forwarding by intermediary flag_table['G'] = RTF_GATEWAY & USHRT_MAX; #endif #ifdef RTF_HOST // Host entry (net otherwise) flag_table['H'] = RTF_HOST & USHRT_MAX; #endif #ifdef RTF_IFSCOPE // Route is associated with an interface scope flag_table['I'] = RTF_IFSCOPE & USHRT_MAX; #endif #ifdef RTF_IFREF // Route is holding a reference to the interface flag_table['i'] = RTF_IFREF & USHRT_MAX; #endif #ifdef RTF_LLINFO // Valid protocol to link address translation flag_table['L'] = RTF_LLINFO & USHRT_MAX; #endif #ifdef RTF_MODIFIED // Modified dynamically (by redirect) flag_table['M'] = RTF_MODIFIED & USHRT_MAX; #endif #ifdef RTF_MULTICAST // The route represents a multicast address flag_table['m'] = RTF_MULTICAST & USHRT_MAX; #endif #ifdef RTF_REJECT // Host or net unreachable flag_table['R'] = RTF_REJECT & USHRT_MAX; #endif #ifdef RTF_ROUTER // Host is a default router flag_table['r'] = RTF_ROUTER & USHRT_MAX; #endif #ifdef RTF_STATIC // Manually added flag_table['S'] = RTF_STATIC & USHRT_MAX; #endif #ifdef RTF_UP // Route usable flag_table['U'] = RTF_UP & USHRT_MAX; #endif #ifdef RTF_WASCLONED // Route was generated as a result of cloning flag_table['W'] = RTF_WASCLONED & USHRT_MAX; #endif #ifdef RTF_XRESOLVE // External daemon translates proto to link address flag_table['X'] = RTF_XRESOLVE & USHRT_MAX; #endif #ifdef RTF_PROXY // Proxying; cloned routes will not be scoped flag_table['Y'] = RTF_PROXY & USHRT_MAX; #endif #endif if (total_bytes_read == 0) { log_debug("Routing table is empty.\n"); err = ERR_IPV4_PROC_NET_ROUTE; goto end; } buffer[total_bytes_read] = '\0'; // Skip first line start = strchr(buffer, '\n'); if (start == NULL) { log_debug("Routing table is malformed.\n"); err = ERR_IPV4_PROC_NET_ROUTE; goto end; } start++; #if !HAVE_PROC_NET_ROUTE if (strstr(buffer, "Ref") != NULL) have_ref = 1; if (strstr(buffer, "Use") != NULL) have_use = 1; // Skip 3 more lines from netstat output on Mac OS X and on FreeBSD start = strchr(start, '\n'); start = strchr(++start, '\n'); start = strchr(++start, '\n'); if (start == NULL) { log_debug("Routing table is malformed.\n"); err = ERR_IPV4_PROC_NET_ROUTE; goto end; } #endif if (strchr(start, '\n') == NULL) { log_debug("Routing table is malformed.\n"); err = ERR_IPV4_PROC_NET_ROUTE; goto end; } // Look for the route line = strtok_r(start, "\n", &saveptr1); while (line != NULL) { char *iface; uint32_t dest, mask, gtw; unsigned short flags; #if HAVE_PROC_NET_ROUTE unsigned short irtt; short metric; unsigned long mtu, window; iface = strtok_r(line, "\t", &saveptr2); dest = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); gtw = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); flags = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); strtok_r(NULL, "\t", &saveptr2); // "RefCnt" strtok_r(NULL, "\t", &saveptr2); // "Use" metric = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); mask = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); mtu = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); window = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); irtt = strtoul(strtok_r(NULL, "\t", &saveptr2), NULL, 16); #else /* parse netstat output on Mac OS X and BSD */ char tmp_ip_string[16]; struct in_addr dstaddr; int pos; char *tmpstr; log_debug_details("\n"); log_debug_details("line: %s\n", line); saveptr3 = NULL; dest = UINT32_MAX; mask = UINT32_MAX; // "Destination" tmpstr = strtok_r(line, " ", &saveptr2); if (strncmp(tmpstr, "Internet6", 9) == 0) { // we have arrived at the end of ipv4 output goto end; } log_debug_details("- Destination: %s\n", tmpstr); // replace literal "default" route by IPV4 numbers-and-dots notation if (strncmp(tmpstr, "default", 7) == 0) { dest = 0; mask = 0; } else { int is_mask_set = 0; char *tmp_position; int dot_count = -1; if (strchr(tmpstr, '/') != NULL) { // 123.123.123.123/30 style // 123.123.123/24 style // 123.123/24 style // break CIDR up into address and mask part strcpy(tmp_ip_string, strtok_r(tmpstr, "/", &saveptr3)); mask = strtoul(saveptr3, NULL, 10); // convert from CIDR to ipv4 mask mask = 0xffffffff << (32-mask); is_mask_set = 1; } else if (inet_aton(tmpstr, &dstaddr)) { // 123.123.123.123 style // 123.123.123 style // 123.123 style strcpy(tmp_ip_string, tmpstr); is_mask_set = 0; } // Process Destination IP Expression tmp_position = tmp_ip_string; while (tmp_position != NULL) { ++dot_count; tmp_position = strchr(++tmp_position, '.'); } for (int i = dot_count; i < 3; i++) strcat(tmp_ip_string, ".0"); if (inet_aton(tmp_ip_string, &dstaddr)) dest = dstaddr.s_addr; if (!is_mask_set) { // convert from CIDR to ipv4 mask mask = 0xffffffff << (32-((dot_count + 1) * 8)); } // convert mask to reversed byte order mask = ((mask & 0xff000000) >> 24) | ((mask & 0xff0000) >> 8) | ((mask & 0xff00) << 8) | ((mask & 0xff) << 24); } log_debug_details("- Destination IP Hex: %x\n", dest); log_debug_details("- Destination Mask Hex: %x\n", mask); // "Gateway" gtw = 0; if (inet_aton(strtok_r(NULL, " ", &saveptr2), &dstaddr)) { gtw = dstaddr.s_addr; log_debug_details("- Gateway Mask Hex: %x\n", gtw); } // "Flags" tmpstr = strtok_r(NULL, " ", &saveptr2); flags = 0; // this is the reason for the 256 entries mentioned above for (pos = 0; pos < strlen(tmpstr); pos++) flags |= flag_table[(unsigned char)tmpstr[pos]]; if (have_ref) strtok_r(NULL, " ", &saveptr2); // "Ref" if (have_use) strtok_r(NULL, " ", &saveptr2); // "Use" iface = strtok_r(NULL, " ", &saveptr2); // "Netif" log_debug_details("- Interface: %s\n", iface); #endif /* * Now that we have parsed a routing entry, check if it * matches the current argument to the function call. * In rtentry we have integer representation, i.e. * the most significant byte corresponds to the last * number of dotted-number representation and vice versa. * In this representation ( address & mask ) is the network * address. * The routing algorithm does the following: * First, check if the network address we are looking for * falls into the network for the current route. * Therefore, calculate the network address for both, the * current route and for the destination we are searching. * If the destination is a smaller network (for instance a * single host), we have to mask again with the netmask of * the routing entry that we are checking in order to obtain * the network address in the context of the current route. * If both network addresses match, we have found a candidate * for a route. * However, there might be another route for a smaller network, * therefore repeat this and only store the resulting route * when the mask is at least as large as the one we may * have already found in a previous iteration (a larger * netmask corresponds to a smaller network in this * representation, and has a higher priority by default). * Also, only consider routing entries for which the * netmask is not larger than the netmask used in the * argument when calling the function - so that we can * distinguish between different routing entries for subnets * of different size but with the same network address. * For routing entries with the same destination and * the same netmask the metric can be used for adjusting * the priority (this is not supported on mac). * If the metric is larger than one found for this network * size, skip the current route (smaller numbers denote * less hops and therefore have a higher priority). */ if (((dest & mask) == (rtdest & rtmask & mask)) && (mask >= route_mask(route).s_addr) && (mask <= rtmask) && ((route_iface(route) == NULL) || (strcmp(iface, route_iface(route)) == 0) || (strlen(route_iface(route)) > 0 && route_iface(route)[0] == '!' && strcmp(iface, &route_iface(route)[1]) != 0) )) { #if HAVE_PROC_NET_ROUTE if (((mask == route_mask(route).s_addr) && (metric <= route->rt_metric)) || (rtfound == 0) || (mask > route_mask(route).s_addr)) { #endif rtfound = 1; // Requested route has been found route_dest(route).s_addr = dest; route_mask(route).s_addr = mask; route_gtw(route).s_addr = gtw; route->rt_flags = flags; free(route_iface(route)); route_iface(route) = strdup(iface); if (!route_iface(route)) { err = ERR_IPV4_NO_MEM; goto end; } #if HAVE_PROC_NET_ROUTE // we do not have these values from Mac OS X netstat, // so stay with defaults denoted by values of 0 route->rt_metric = metric; route->rt_mtu = mtu; route->rt_window = window; route->rt_irtt = irtt; } #else log_debug_details("- route matches\n"); #endif } line = strtok_r(NULL, "\n", &saveptr1); } end: free(buffer); if (err) return err; if (rtfound == 0) { // should not occur anymore unless there is no default route log_debug("Route not found.\n"); // at least restore input values route_dest(route).s_addr = rtdest; route_mask(route).s_addr = rtmask; route_gtw(route).s_addr = rtgtw; return ERR_IPV4_NO_SUCH_ROUTE; } return 0; } static int ipv4_set_route(struct rtentry *route) { #ifdef HAVE_RT_ENTRY_WITH_RT_DST /* we can copy rtentry struct directly between openfortivpn and kernel */ log_debug("ip route add %s\n", ipv4_show_route(route)); int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) return ERR_IPV4_SEE_ERRNO; if (ioctl(sockfd, SIOCADDRT, route) == -1) { if (close(sockfd)) log_warn("Could not close socket for setting route (%s).\n", strerror(errno)); return ERR_IPV4_SEE_ERRNO; } if (close(sockfd)) log_warn("Could not close socket for setting route: %s\n", strerror(errno)); #else /* we have to use the route command as tool for route manipulation */ char cmd[SHOW_ROUTE_BUFFER_SIZE]; if (access("/sbin/route", F_OK) != 0) { log_error("/sbin/route: %s.\n", strerror(errno)); return 1; } strcpy(cmd, "/sbin/route -n add "); if (route->rt_flags & RTF_HOST) strcat(cmd, "-host "); else strcat(cmd, "-net "); strncat(cmd, inet_ntoa(route_dest(route)), 15); if (!(route->rt_flags & RTF_HOST)) { strcat(cmd, " -netmask "); strncat(cmd, inet_ntoa(route_mask(route)), 15); } if (route->rt_flags & RTF_GATEWAY) { strcat(cmd, " "); strncat(cmd, inet_ntoa(route_gtw(route)), 15); } else { strcat(cmd, " -interface "); strncat(cmd, route_iface(route), SHOW_ROUTE_BUFFER_SIZE - strlen(cmd) - 1); } log_debug("%s\n", cmd); int res = system(cmd); if (res == -1) return ERR_IPV4_SEE_ERRNO; #endif return 0; } static int ipv4_del_route(struct rtentry *route) { #ifdef HAVE_RT_ENTRY_WITH_RT_DST /* we can copy rtentry struct directly between openfortivpn and kernel */ struct rtentry tmp; int sockfd; log_debug("ip route del %s\n", ipv4_show_route(route)); // Copy route to a temp variable to clear some of its properties memcpy(&tmp, route, sizeof(tmp)); tmp.rt_metric = 0; tmp.rt_mtu = 0; tmp.rt_window = 0; tmp.rt_irtt = 0; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) return ERR_IPV4_SEE_ERRNO; if (ioctl(sockfd, SIOCDELRT, &tmp) == -1) { if (close(sockfd)) log_warn("Could not close socket for deleting route (%s).\n", strerror(errno)); return ERR_IPV4_SEE_ERRNO; } if (close(sockfd)) log_warn("Could not close socket for deleting route (%s).\n", strerror(errno)); #else char cmd[SHOW_ROUTE_BUFFER_SIZE]; if (access("/sbin/route", F_OK) != 0) { log_error("/sbin/route: %s.\n", strerror(errno)); return 1; } strcpy(cmd, "/sbin/route -n delete "); if (route->rt_flags & RTF_HOST) strcat(cmd, "-host "); else strcat(cmd, "-net "); strncat(cmd, inet_ntoa(route_dest(route)), 15); if (!(route->rt_flags & RTF_HOST)) { strcat(cmd, " -netmask "); strncat(cmd, inet_ntoa(route_mask(route)), 15); } if (route->rt_flags & RTF_GATEWAY) { strcat(cmd, " "); strncat(cmd, inet_ntoa(route_gtw(route)), 15); } else { strcat(cmd, " -interface "); strncat(cmd, route_iface(route), SHOW_ROUTE_BUFFER_SIZE - strlen(cmd) - 1); } log_debug("%s\n", cmd); int res = system(cmd); if (res == -1) return ERR_IPV4_SEE_ERRNO; #endif return 0; } int ipv4_protect_tunnel_route(struct tunnel *tunnel) { struct rtentry *gtw_rt = &tunnel->ipv4.gtw_rt; struct rtentry *def_rt = &tunnel->ipv4.def_rt; int ret; route_init(def_rt); route_init(gtw_rt); // Back up default route route_dest(def_rt).s_addr = inet_addr("0.0.0.0"); route_mask(def_rt).s_addr = inet_addr("0.0.0.0"); route_iface(def_rt) = malloc(strlen(tunnel->ppp_iface) + 2); if (!route_iface(def_rt)) { log_error("malloc: %s\n", strerror(errno)); return ERR_IPV4_SEE_ERRNO; } sprintf(route_iface(def_rt), "!%s", tunnel->ppp_iface); ret = ipv4_get_route(def_rt); if (ret != 0) { log_warn("Could not get current default route (%s).\n", err_ipv4_str(ret)); log_warn("Protecting tunnel route has failed. But this can be working except for some cases.\n"); goto err_destroy_def_rt; } // Set the up a route to the tunnel gateway route_dest(gtw_rt).s_addr = tunnel->config->gateway_ip.s_addr; route_mask(gtw_rt).s_addr = inet_addr("255.255.255.255"); route_iface(gtw_rt) = malloc(strlen(tunnel->ppp_iface) + 2); if (!route_iface(gtw_rt)) { log_error("malloc: %s\n", strerror(errno)); return ERR_IPV4_SEE_ERRNO; } sprintf(route_iface(gtw_rt), "%s", tunnel->ppp_iface); ret = ipv4_get_route(gtw_rt); if ((ret == 0) && (route_dest(gtw_rt).s_addr == tunnel->config->gateway_ip.s_addr) && (route_mask(gtw_rt).s_addr == inet_addr("255.255.255.255"))) { log_debug("Removing wrong route to vpn server...\n"); log_debug("ip route show %s\n", ipv4_show_route(gtw_rt)); ipv4_del_route(gtw_rt); } sprintf(route_iface(gtw_rt), "!%s", tunnel->ppp_iface); ret = ipv4_get_route(gtw_rt); if (ret != 0) { log_warn("Could not get route to gateway (%s).\n", err_ipv4_str(ret)); log_warn("Protecting tunnel route has failed. But this can be working except for some cases.\n"); goto err_destroy_gtw_rt; } route_dest(gtw_rt).s_addr = tunnel->config->gateway_ip.s_addr; route_mask(gtw_rt).s_addr = inet_addr("255.255.255.255"); gtw_rt->rt_flags |= RTF_HOST; gtw_rt->rt_metric = 0; tunnel->ipv4.route_to_vpn_is_added = 1; log_debug("Setting route to vpn server...\n"); log_debug("ip route show %s\n", ipv4_show_route(gtw_rt)); ret = ipv4_set_route(gtw_rt); if (ret == ERR_IPV4_SEE_ERRNO && errno == EEXIST) { log_warn("Route to vpn server exists already.\n"); tunnel->ipv4.route_to_vpn_is_added = 0; } else if (ret != 0) log_warn("Could not set route to vpn server (%s).\n", err_ipv4_str(ret)); return 0; err_destroy_gtw_rt: route_destroy(gtw_rt); err_destroy_def_rt: route_destroy(def_rt); tunnel->ipv4.route_to_vpn_is_added = 0; return ret; } #if HAVE_USR_SBIN_PPPD static void add_text_route(struct tunnel *tunnel, const char *dest, const char *mask, const char *gw) { size_t l0, l1; static const char fmt[] = ",%s/%s/%s"; static const char trigger[] = "openfortivpn"; char **target = &tunnel->config->pppd_ipparam; char *ptr; if (*target == NULL || strncmp(*target, trigger, strlen(trigger))) return; if (!dest || !mask || !gw) return; log_info("Registering route %s/%s via %s\n", dest, mask, gw); l0 = strlen(*target); l1 = strlen(fmt) + strlen(dest) + strlen(mask) + strlen(gw) + 1; ptr = realloc(*target, l0 + l1); if (ptr) { *target = ptr; snprintf(*target + l0, l1, fmt, dest, mask, gw); } else { log_error("realloc: %s\n", strerror(errno)); } } #endif int ipv4_add_split_vpn_route(struct tunnel *tunnel, char *dest, char *mask, char *gateway) { struct rtentry *route; char env_var[24]; // strlen("VPN_ROUTE_GATEWAY_") + strlen("65535") + 1 #if HAVE_USR_SBIN_PPPD add_text_route(tunnel, dest, mask, gateway); #endif if (tunnel->ipv4.split_routes == MAX_SPLIT_ROUTES) return ERR_IPV4_NO_MEM; if ((tunnel->ipv4.split_rt == NULL) || ((tunnel->ipv4.split_routes % STEP_SPLIT_ROUTES) == 0)) { void *new_ptr = realloc(tunnel->ipv4.split_rt, (size_t) (tunnel->ipv4.split_routes + STEP_SPLIT_ROUTES) * sizeof(*(tunnel->ipv4.split_rt))); if (new_ptr == NULL) return ERR_IPV4_NO_MEM; tunnel->ipv4.split_rt = new_ptr; } assert(tunnel->ipv4.split_routes >= 0 && tunnel->ipv4.split_routes < MAX_SPLIT_ROUTES); sprintf(env_var, "VPN_ROUTE_DEST_%d", tunnel->ipv4.split_routes); setenv(env_var, dest, 0); sprintf(env_var, "VPN_ROUTE_MASK_%d", tunnel->ipv4.split_routes); setenv(env_var, mask, 0); if (gateway != NULL) { sprintf(env_var, "VPN_ROUTE_GATEWAY_%d", tunnel->ipv4.split_routes); setenv(env_var, gateway, 0); } route = &tunnel->ipv4.split_rt[tunnel->ipv4.split_routes++]; route_init(route); route_dest(route).s_addr = inet_addr(dest); route_mask(route).s_addr = inet_addr(mask); if (gateway != NULL) { route_gtw(route).s_addr = inet_addr(gateway); route->rt_flags |= RTF_GATEWAY; } else { free(route_iface(route)); route_iface(route) = strdup(tunnel->ppp_iface); if (!route_iface(route)) return ERR_IPV4_NO_MEM; } return 0; } static int ipv4_set_split_routes(struct tunnel *tunnel) { for (int i = 0; i < tunnel->ipv4.split_routes; i++) { struct rtentry *route; int ret; route = &tunnel->ipv4.split_rt[i]; // check if the route to be added is not the one to the gateway itself if (route_dest(route).s_addr == route_dest(&tunnel->ipv4.gtw_rt).s_addr) { log_debug("Skipping route to tunnel gateway (%s).\n", ipv4_show_route(route)); continue; } free(route_iface(route)); route_iface(route) = strdup(tunnel->ppp_iface); if (!route_iface(route)) return ERR_IPV4_NO_MEM; if (route_gtw(route).s_addr == tunnel->ipv4.ip_addr.s_addr) route_gtw(route).s_addr = 0; if (route_gtw(route).s_addr == 0) route->rt_flags &= ~RTF_GATEWAY; if (route_gtw(route).s_addr != 0) route->rt_flags |= RTF_GATEWAY; ret = ipv4_set_route(route); if (ret == ERR_IPV4_SEE_ERRNO && errno == EEXIST) log_info("Route to gateway exists already.\n"); else if (ret != 0) log_warn("Could not set route to tunnel gateway (%s).\n", err_ipv4_str(ret)); } return 0; } static int ipv4_set_default_routes(struct tunnel *tunnel) { int ret; struct rtentry *def_rt = &tunnel->ipv4.def_rt; struct rtentry *ppp_rt = &tunnel->ipv4.ppp_rt; struct vpn_config *cfg = tunnel->config; route_init(ppp_rt); if (cfg->half_internet_routes == 0) { // Delete the current default route log_debug("Deleting the current default route...\n"); ret = ipv4_del_route(def_rt); if (ret != 0) log_warn("Could not delete the current default route (%s).\n", err_ipv4_str(ret)); // Set the new default route // ip route add to 0/0 dev ppp0 route_dest(ppp_rt).s_addr = inet_addr("0.0.0.0"); route_mask(ppp_rt).s_addr = inet_addr("0.0.0.0"); route_gtw(ppp_rt).s_addr = inet_addr("0.0.0.0"); log_debug("Setting new default route...\n"); free(route_iface(ppp_rt)); route_iface(ppp_rt) = strdup(tunnel->ppp_iface); if (!route_iface(ppp_rt)) return ERR_IPV4_NO_MEM; if (route_gtw(ppp_rt).s_addr == tunnel->ipv4.ip_addr.s_addr) route_gtw(ppp_rt).s_addr = 0; if (route_gtw(ppp_rt).s_addr == 0) ppp_rt->rt_flags &= ~RTF_GATEWAY; if (route_gtw(ppp_rt).s_addr != 0) ppp_rt->rt_flags |= RTF_GATEWAY; ret = ipv4_set_route(ppp_rt); if (ret == ERR_IPV4_SEE_ERRNO && errno == EEXIST) { log_warn("Default route exists already.\n"); } else if (ret != 0) { log_warn("Could not set the new default route (%s).\n", err_ipv4_str(ret)); } } else { // Emulate default routes as two "half internet" routes // This allows for e.g. DHCP renewing default routes without // breaking the tunnel log_debug("Setting new half-internet routes...\n"); route_dest(ppp_rt).s_addr = inet_addr("0.0.0.0"); route_mask(ppp_rt).s_addr = inet_addr("128.0.0.0"); free(route_iface(ppp_rt)); route_iface(ppp_rt) = strdup(tunnel->ppp_iface); if (!route_iface(ppp_rt)) return ERR_IPV4_NO_MEM; if (route_gtw(ppp_rt).s_addr == tunnel->ipv4.ip_addr.s_addr) route_gtw(ppp_rt).s_addr = 0; if (route_gtw(ppp_rt).s_addr == 0) ppp_rt->rt_flags &= ~RTF_GATEWAY; if (route_gtw(ppp_rt).s_addr != 0) ppp_rt->rt_flags |= RTF_GATEWAY; ret = ipv4_set_route(ppp_rt); if (ret == ERR_IPV4_SEE_ERRNO && errno == EEXIST) { log_warn("0.0.0.0/1 route exists already.\n"); } else if (ret != 0) { log_warn("Could not set the new 0.0.0.0/1 route (%s).\n", err_ipv4_str(ret)); } route_dest(ppp_rt).s_addr = inet_addr("128.0.0.0"); ret = ipv4_set_route(ppp_rt); if (ret == ERR_IPV4_SEE_ERRNO && errno == EEXIST) { log_warn("128.0.0.0/1 route exists already.\n"); } else if (ret != 0) { log_warn("Could not set the new 128.0.0.0/1 route (%s).\n", err_ipv4_str(ret)); } } return 0; } int ipv4_set_tunnel_routes(struct tunnel *tunnel) { int ret = ipv4_protect_tunnel_route(tunnel); if (tunnel->ipv4.split_routes) // try even if ipv4_protect_tunnel_route has failed return ipv4_set_split_routes(tunnel); else if (ret == 0) return ipv4_set_default_routes(tunnel); else return ret; } int ipv4_restore_routes(struct tunnel *tunnel) { struct rtentry *def_rt = &tunnel->ipv4.def_rt; struct rtentry *gtw_rt = &tunnel->ipv4.gtw_rt; struct rtentry *ppp_rt = &tunnel->ipv4.ppp_rt; struct vpn_config *cfg = tunnel->config; if (tunnel->ipv4.route_to_vpn_is_added) { int ret; ret = ipv4_del_route(gtw_rt); if (ret != 0) log_warn("Could not delete route to vpn server (%s).\n", err_ipv4_str(ret)); if ((cfg->half_internet_routes == 0) && (tunnel->ipv4.split_routes == 0)) { ret = ipv4_del_route(ppp_rt); if (ret != 0) log_warn("Could not delete route through tunnel (%s).\n", err_ipv4_str(ret)); // Restore the default route. It seems not to be // automatically restored on all linux distributions ret = ipv4_set_route(def_rt); if (ret != 0) { log_warn("Could not restore default route (%s). Already restored?\n", err_ipv4_str(ret)); } } } else { log_debug("Route to vpn server was not added\n"); } route_destroy(ppp_rt); route_destroy(def_rt); route_destroy(gtw_rt); return 0; } static inline char *replace_char(char *str, char find, char replace) { for (size_t i = 0; i < strlen(str); i++) if (str[i] == find) str[i] = replace; return str; } int ipv4_add_nameservers_to_resolv_conf(struct tunnel *tunnel) { int ret = -1; FILE *file; struct stat stat; #define NS_SIZE ARRAY_SIZE("nameserver xxx.xxx.xxx.xxx\n") char ns1[NS_SIZE], ns2[NS_SIZE]; #undef NS_SIZE #define DNS_SUFFIX_SIZE (ARRAY_SIZE("search \n") + MAX_DOMAIN_LENGTH) char dns_suffix[DNS_SUFFIX_SIZE]; #undef DNS_SUFFIX_SIZE char *buffer = NULL; #if HAVE_RESOLVCONF int use_resolvconf = 0; #endif tunnel->ipv4.ns1_was_there = 0; tunnel->ipv4.ns2_was_there = 0; tunnel->ipv4.dns_suffix_was_there = 0; if (tunnel->ipv4.ns1_addr.s_addr == 0) tunnel->ipv4.ns1_was_there = -1; if (tunnel->ipv4.ns2_addr.s_addr == 0) tunnel->ipv4.ns2_was_there = -1; #if HAVE_RESOLVCONF if (tunnel->config->use_resolvconf && (access(RESOLVCONF_PATH, F_OK) == 0)) { int resolvconf_call_len; char *resolvconf_call; log_debug("Attempting to run %s.\n", RESOLVCONF_PATH); resolvconf_call_len = strlen(RESOLVCONF_PATH) + 20 + strlen(tunnel->ppp_iface); resolvconf_call = malloc(resolvconf_call_len); if (resolvconf_call == NULL) { log_warn("Could not create command to run resolvconf (%s).\n", strerror(errno)); return 1; } snprintf(resolvconf_call, resolvconf_call_len, "%s -a \"%s.openfortivpn\"", RESOLVCONF_PATH, tunnel->ppp_iface); use_resolvconf = 1; log_debug("resolvconf_call: %s\n", resolvconf_call); file = popen(resolvconf_call, "w"); if (file == NULL) { log_warn("Could not open pipe %s (%s).\n", resolvconf_call, strerror(errno)); free(resolvconf_call); return 1; } free(resolvconf_call); } else { #endif log_debug("Attempting to modify /etc/resolv.conf directly.\n"); file = fopen("/etc/resolv.conf", "r+"); if (file == NULL) { log_warn("Could not open /etc/resolv.conf (%s).\n", strerror(errno)); return 1; } if (fstat(fileno(file), &stat) == -1) { log_warn("Could not stat /etc/resolv.conf (%s).\n", strerror(errno)); goto err_close; } if (stat.st_size == 0) { log_warn("Could not read /etc/resolv.conf (%s).\n", "Empty file"); goto err_close; } buffer = malloc(stat.st_size + 1); if (buffer == NULL) { log_warn("Could not read /etc/resolv.conf (%s).\n", strerror(errno)); goto err_close; } // Copy all file contents at once if (fread(buffer, stat.st_size, 1, file) != 1) { log_warn("Could not read /etc/resolv.conf.\n"); goto err_free; } buffer[stat.st_size] = '\0'; #if HAVE_RESOLVCONF } #endif if (tunnel->ipv4.ns1_addr.s_addr != 0) { strcpy(ns1, "nameserver "); strncat(ns1, inet_ntoa(tunnel->ipv4.ns1_addr), 15); } else { ns1[0] = '\0'; } if (tunnel->ipv4.ns2_addr.s_addr != 0) { strcpy(ns2, "nameserver "); strncat(ns2, inet_ntoa(tunnel->ipv4.ns2_addr), 15); } else { ns2[0] = '\0'; } if (tunnel->ipv4.dns_suffix != NULL) { strcpy(dns_suffix, "search "); strncat(dns_suffix, tunnel->ipv4.dns_suffix, MAX_DOMAIN_LENGTH); replace_char(dns_suffix, ';', ' '); } else { dns_suffix[0] = '\0'; } #if HAVE_RESOLVCONF if (use_resolvconf == 0) { #endif char *saveptr = NULL; for (const char *line = strtok_r(buffer, "\n", &saveptr); line != NULL; line = strtok_r(NULL, "\n", &saveptr)) { if (strcmp(line, ns1) == 0) { tunnel->ipv4.ns1_was_there = 1; log_debug("ns1 already present in /etc/resolv.conf.\n"); } } if (tunnel->ipv4.ns1_was_there == 0) log_debug("Adding \"%s\", to /etc/resolv.conf.\n", ns1); for (const char *line = strtok_r(buffer, "\n", &saveptr); line != NULL; line = strtok_r(NULL, "\n", &saveptr)) { if (strcmp(line, ns2) == 0) { tunnel->ipv4.ns2_was_there = 1; log_debug("ns2 already present in /etc/resolv.conf.\n"); } } if (tunnel->ipv4.ns2_was_there == 0) log_debug("Adding \"%s\", to /etc/resolv.conf.\n", ns2); if (dns_suffix[0] == '\0') { tunnel->ipv4.dns_suffix_was_there = -1; } else { for (const char *line = strtok_r(buffer, "\n", &saveptr); line != NULL; line = strtok_r(NULL, "\n", &saveptr)) { if (dns_suffix[0] != '\0' && strcmp(line, dns_suffix) == 0) { tunnel->ipv4.dns_suffix_was_there = 1; log_debug("dns_suffix already present in /etc/resolv.conf.\n"); } } } if (tunnel->ipv4.dns_suffix_was_there == 0) log_debug("Adding \"%s\", to /etc/resolv.conf.\n", dns_suffix); rewind(file); if (fread(buffer, stat.st_size, 1, file) != 1) { log_warn("Could not read /etc/resolv.conf.\n"); goto err_free; } buffer[stat.st_size] = '\0'; rewind(file); #if HAVE_RESOLVCONF } #endif if (tunnel->ipv4.ns1_was_there == 0) { strcat(ns1, "\n"); fputs(ns1, file); } if (tunnel->ipv4.ns2_was_there == 0) { strcat(ns2, "\n"); fputs(ns2, file); } if (tunnel->ipv4.dns_suffix_was_there == 0) { strcat(dns_suffix, "\n"); fputs(dns_suffix, file); } #if HAVE_RESOLVCONF if (use_resolvconf == 0) #endif fwrite(buffer, stat.st_size, 1, file); ret = 0; err_free: free(buffer); err_close: #if HAVE_RESOLVCONF if (use_resolvconf == 0) { #endif if (fclose(file)) log_warn("Could not close /etc/resolv.conf: %s\n", strerror(errno)); #if HAVE_RESOLVCONF } else { if (pclose(file) == -1) log_warn("Could not close resolvconf pipe: %s\n", strerror(errno)); } #endif return ret; } int ipv4_del_nameservers_from_resolv_conf(struct tunnel *tunnel) { int ret = -1; FILE *file; struct stat stat; #define NS_SIZE ARRAY_SIZE("nameserver xxx.xxx.xxx.xxx") char ns1[NS_SIZE], ns2[NS_SIZE]; #undef NS_SIZE #define DNS_SUFFIX_SIZE (ARRAY_SIZE("search ") + MAX_DOMAIN_LENGTH) char dns_suffix[DNS_SUFFIX_SIZE]; #undef DNS_SUFFIX_SIZE char *buffer = NULL; char *saveptr = NULL; #if HAVE_RESOLVCONF if (tunnel->config->use_resolvconf && (access(RESOLVCONF_PATH, F_OK) == 0)) { int resolvconf_call_len; char *resolvconf_call; resolvconf_call_len = strlen(RESOLVCONF_PATH) + 20 + strlen(tunnel->ppp_iface); resolvconf_call = malloc(resolvconf_call_len); if (resolvconf_call == NULL) { log_warn("Could not create command to run resolvconf (%s).\n", strerror(errno)); return ERR_IPV4_SEE_ERRNO; } snprintf(resolvconf_call, resolvconf_call_len, "%s -d \"%s.openfortivpn\"", RESOLVCONF_PATH, tunnel->ppp_iface ); log_debug("resolvconf_call: %s\n", resolvconf_call); ret = system(resolvconf_call); free(resolvconf_call); if (ret == -1) return ERR_IPV4_SEE_ERRNO; return 0; } #endif file = fopen("/etc/resolv.conf", "r+"); if (file == NULL) { log_warn("Could not open /etc/resolv.conf (%s).\n", strerror(errno)); return 1; } if (fstat(fileno(file), &stat) == -1) { log_warn("Could not stat /etc/resolv.conf (%s).\n", strerror(errno)); goto err_close; } buffer = malloc(stat.st_size + 1); if (buffer == NULL) { log_warn("Could not read /etc/resolv.conf (%s).\n", strerror(errno)); goto err_close; } // Copy all file contents at once if (fread(buffer, stat.st_size, 1, file) != 1) { log_warn("Could not read /etc/resolv.conf.\n"); goto err_free; } buffer[stat.st_size] = '\0'; ns1[0] = '\0'; if (tunnel->ipv4.ns1_addr.s_addr != 0) { strcpy(ns1, "nameserver "); strncat(ns1, inet_ntoa(tunnel->ipv4.ns1_addr), 15); } ns2[0] = '\0'; if (tunnel->ipv4.ns2_addr.s_addr != 0) { strcpy(ns2, "nameserver "); strncat(ns2, inet_ntoa(tunnel->ipv4.ns2_addr), 15); } dns_suffix[0] = '\0'; if (tunnel->ipv4.dns_suffix != NULL && tunnel->ipv4.dns_suffix[0] != '\0') { strcpy(dns_suffix, "search "); strncat(dns_suffix, tunnel->ipv4.dns_suffix, MAX_DOMAIN_LENGTH); } file = freopen("/etc/resolv.conf", "w", file); if (file == NULL) { log_warn("Could not reopen /etc/resolv.conf (%s).\n", strerror(errno)); goto err_free; } for (const char *line = strtok_r(buffer, "\n", &saveptr); line != NULL; line = strtok_r(NULL, "\n", &saveptr)) { if (ns1[0] != '\0' && strcmp(line, ns1) == 0 && (tunnel->ipv4.ns1_was_there == 0)) { log_debug("Deleting \"%s\" from /etc/resolv.conf.\n", ns1); } else if (ns2[0] != '\0' && strcmp(line, ns2) == 0 && (tunnel->ipv4.ns2_was_there == 0)) { log_debug("Deleting \"%s\" from /etc/resolv.conf.\n", ns2); } else if (dns_suffix[0] != '\0' && strcmp(line, dns_suffix) == 0 && (tunnel->ipv4.dns_suffix_was_there == 0)) { log_debug("Deleting \"%s\" from /etc/resolv.conf.\n", dns_suffix); } else { fputs(line, file); fputs("\n", file); } } ret = 0; err_free: free(buffer); err_close: if (file && fclose(file)) log_warn("Could not close /etc/resolv.conf (%s).\n", strerror(errno)); return ret; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/hdlc.c����������������������������������������������������������������������0000664�0001750�0001750�00000022056�14753334500�016113� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "hdlc.h" /* * Encode and decode PPP packets from and into HDLC frames. * * RFC 1622 describes the use of HDLC-like framing for PPP encapsulated packets: * https://www.rfc-editor.org/info/rfc1662 */ #define in_sending_accm(byte) \ ((byte) < 0x20 || ((byte) & 0x7f) == 0x7d || ((byte) & 0x7f) == 0x7e) #define in_receiving_accm(byte) \ ((byte) < 0x20) /* * Lookup table used to calculate the FCS, as generated in RFC 1662. */ static const uint16_t fcs_tab[] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; /* * Calculates a new FCS from the current FCS and new data. * * @param[in] sum Current FCS value. * @param[in] seq Array of new data. * @param[in] length Length of the array of new data. * @return new FCS value. */ static uint16_t frame_checksum_16bit(uint16_t sum, const uint8_t *seq, size_t length) { while (length--) sum = (sum >> 8) ^ fcs_tab[(sum ^ *seq++) & 0xff]; return sum; } /* * Precalculated FCS for Address and Control fields. * * address_control_checksum = frame_checksum_16bit(0xffff, { 0xff, 0x03 }, 2); */ static const uint16_t address_control_checksum = 0x3de3; /* * Each frame begins with a Flag Sequence. * Only one Flag Sequence is required between two frames. * The first frame begins with a Flag Sequence. * Subsequent frames rely on the Flag Sequence that ends the previous frame. */ static int need_flag_sequence; /* * Upon connection, the first frame begins with a Flag Sequence. */ void init_hdlc(void) { need_flag_sequence = 1; } /* * Wraps a PPP packet into an HDLC frame and write it to a buffer. * * @param[out] frame The buffer to store the encoded frame. * @param[in] frmsize The output buffer size. * @param[in] packet The buffer containing the packet. * @param[in] pktsize The input packet size. * @return the number of bytes written to the buffer (i.e. the * HDLC-encoded frame length) or ERR_HDLC_BUFFER_TOO_SMALL * if the output buffer is too small */ ssize_t hdlc_encode(uint8_t *frame, size_t frmsize, const uint8_t *packet, size_t pktsize) { ssize_t written = 0; uint16_t checksum; const uint8_t address_control_fields[] = { 0xff, 0x03 }; uint8_t byte; if (frmsize < 7) return ERR_HDLC_BUFFER_TOO_SMALL; // In theory each frame begins with a Flag Sequence, but it is omitted // if the previous frame ends with a Flag Sequence. if (need_flag_sequence) frame[written++] = 0x7e; // Escape and write Frame Address and Control fields frame[written++] = address_control_fields[0]; frame[written++] = 0x7d; frame[written++] = address_control_fields[1] ^ 0x20; checksum = address_control_checksum; // Precalculated for Address Control for (int i = 0; i < pktsize; i++) { byte = packet[i]; if (frmsize < written + 2) return ERR_HDLC_BUFFER_TOO_SMALL; if (in_sending_accm(byte)) { frame[written++] = 0x7d; frame[written++] = byte ^ 0x20; } else { frame[written++] = byte; } } if (frmsize < written + 3) return ERR_HDLC_BUFFER_TOO_SMALL; checksum = frame_checksum_16bit(checksum, packet, pktsize); // Escape and write Frame Check Sequence field checksum ^= 0xffff; byte = checksum & 0x00ff; if (in_sending_accm(byte)) { frame[written++] = 0x7d; frame[written++] = byte ^ 0x20; } else { frame[written++] = byte; } byte = (checksum >> 8) & 0x00ff; if (in_sending_accm(byte)) { frame[written++] = 0x7d; frame[written++] = byte ^ 0x20; } else { frame[written++] = byte; } // Each frame ends with a Flag Sequence frame[written++] = 0x7e; need_flag_sequence = 0; return written; } /* * Finds the first frame in a buffer, starting search at start. * * @param[in] buffer The input buffer. * @param[in] bufsize The input buffer size. * @param[in,out] start Offset of the beginning of the first frame in the buffer. * @return the length of the first frame or ERR_HDLC_NO_FRAME_FOUND * if no frame is found. */ ssize_t hdlc_find_frame(const uint8_t *buffer, size_t bufsize, off_t *start) { int s = -1, e = -1; // Look for frame start for (size_t i = *start; i < bufsize; i++) { if (buffer[i] == 0x7e) { // Flag Sequence s = i + 1; break; } } if (s == -1) return ERR_HDLC_NO_FRAME_FOUND; // Discard empty frames while (s < bufsize && buffer[s] == 0x7e) // consecutive Flag Sequences s++; // Look for frame end for (size_t i = s; i < bufsize; i++) { if (buffer[i] == 0x7e) { // Flag Sequence e = i; break; } } if (e == -1) return ERR_HDLC_NO_FRAME_FOUND; *start = s; return e - s; } /* * Extracts the first PPP packet found in the input buffer. * * The frame should be passed without its surrounding Flag Sequence (0x7e) bytes. * * @param[in] frame The buffer containing the encoded frame. * @param[in] frmsize The input buffer size. * @param[out] packet The buffer to store the decoded packet. * @param[in] pktsize The output packet buffer size. * @return the number of bytes written to the output packet * buffer, or < 0 in case of error. */ ssize_t hdlc_decode(const uint8_t *frame, size_t frmsize, uint8_t *packet, size_t pktsize) { off_t start = 0; ssize_t written = 0; int has_address_control_fields = 0; int in_escape; uint16_t checksum; if (frmsize < 5) return ERR_HDLC_INVALID_FRAME; // Remove Address and escaped Control fields if (frame[0] == 0xff && frame[1] == 0x7d && frame[2] == (0x03 ^ 0x20)) { start += 3; has_address_control_fields = 1; } in_escape = 0; for (int i = start; i < frmsize; i++) { uint8_t byte = frame[i]; if (byte == 0x7d) { // Control Escape if (in_escape) return ERR_HDLC_INVALID_FRAME; in_escape = 1; continue; } else if (in_escape) { byte ^= 0x20; in_escape = 0; } else if (in_receiving_accm(byte)) { continue; // Drop characters possibly introduced by DCE } if (written >= pktsize) return ERR_HDLC_BUFFER_TOO_SMALL; packet[written++] = byte; } if (in_escape) return ERR_HDLC_INVALID_FRAME; if (written < 3) return ERR_HDLC_INVALID_FRAME; // Control Frame Check Sequence field validity and remove it if (has_address_control_fields) checksum = address_control_checksum; else checksum = 0xffff; checksum = frame_checksum_16bit(checksum, packet, written); if (checksum != 0xf0b8) return ERR_HDLC_BAD_CHECKSUM; written -= 2; return written; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/userinput.c�����������������������������������������������������������������0000664�0001750�0001750�00000020212�14753334500�017227� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Davíð Steinn Geirsson * Copyright (c) 2019 Lubomir Rintel <lkundrak@v3.sk> * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "userinput.h" #include "log.h" #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <termios.h> #include <ctype.h> #include <errno.h> #include <stdarg.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static char *uri_escape(const char *string) { char *escaped = NULL; int allocated_len = 0; int real_len = 0; while (*string != '\0') { if (allocated_len < real_len + 4) { allocated_len += 16; char *tmp = realloc(escaped, allocated_len); // bail out if realloc fails if (tmp == NULL) { free(escaped); escaped = NULL; break; } escaped = tmp; } if (isalnum(*string) || *string == '-' || *string == '_' || *string == '.' || *string == '~') escaped[real_len++] = *string; else real_len += sprintf(&escaped[real_len], "%%%02X", (unsigned char)*string); string++; } if (escaped) escaped[real_len] = '\0'; return escaped; } static char *uri_unescape(const char *string) { int escaped_len = strlen(string) + 1; char *unescaped = malloc(escaped_len); int real_len = 0; int i = 0; // bail out if malloc fails if (unescaped == NULL) return NULL; while (string[i]) { if (string[i] == '%' && isxdigit(string[i + 1]) && isxdigit(string[i + 2])) { sscanf(&string[i + 1], "%02hhx", (unsigned char *)&unescaped[real_len]); i += 3; } else if (string[i] == '%' && string[i + 1] == '%') { unescaped[real_len] = '%'; i += 2; } else { unescaped[real_len] = string[i]; i += 1; } real_len++; } unescaped[real_len] = '\0'; return unescaped; } static int pinentry_read(int from, char **retstr) { int bufsiz = 0; char *buf = NULL, *saveptr = NULL; int len = 0; int ret; do { if (bufsiz - len < 64) { bufsiz += 64; char *tmp = realloc(buf, bufsiz); // bail out if realloc fails if (tmp == NULL) { if (retstr) *retstr = strdup(strerror(errno)); free(buf); return -1; } buf = tmp; buf[bufsiz-1] = '\0'; } ret = read(from, &buf[len], bufsiz - len); if (ret == -1) { free(buf); if (retstr) *retstr = strdup(strerror(errno)); return -1; } if (ret == 0) { free(buf); if (retstr) *retstr = strdup("Short read"); return -1; } len += ret; } while (buf[len - 1] != '\n'); // overwrite the newline with a null character buf[len - 1] = '\0'; if (strcmp(buf, "OK") == 0 || strncmp(buf, "OK ", 3) == 0 || strncmp(buf, "D ", 2) == 0) { if (retstr) { *retstr = strchr(buf, ' '); *retstr = *retstr ? strtok_r(*retstr, "\n", &saveptr) : NULL; *retstr = *retstr ? uri_unescape(*retstr + 1) : NULL; } free(buf); return 0; } if (strncmp(buf, "ERR ", 4) == 0 || strncmp(buf, "S ERROR", 7) == 0) { ret = strtol(&buf[4], NULL, 10); if (!ret) ret = -1; if (retstr) { *retstr = strchr(&buf[4], ' '); *retstr = *retstr ? uri_unescape(*retstr + 1) : NULL; } free(buf); return ret; } free(buf); if (retstr) *retstr = strdup("pinentry protocol error"); return -1; } #ifndef HAVE_VDPRINTF static int vdprintf(int fd, const char *format, va_list ap) { char buffer[2049]; int size = vsnprintf(buffer, sizeof(buffer), format, ap); if (size < 0) return size; if (size >= sizeof(buffer)) // silently discard beyond the buffer size size = sizeof(buffer) - 1; return (int) write(fd, buffer, size); } #endif static int pinentry_exchange(int to, int from, char **retstr, const char *format, ...) { va_list ap; va_start(ap, format); if (vdprintf(to, format, ap) == 0) { if (retstr) *retstr = strdup(strerror(errno)); va_end(ap); return -1; } va_end(ap); return pinentry_read(from, retstr); } static void pinentry_read_password(const char *pinentry, const char *hint, const char *prompt, char *pass, size_t len) { int from_pinentry[2]; int to_pinentry[2]; int pinentry_status; pid_t pinentry_pid; char *escaped; char *retstr; int ret; *pass = '\0'; if (pipe(from_pinentry) == -1) { perror("pipe"); return; } if (pipe(to_pinentry) == -1) { perror("pipe"); close(from_pinentry[0]); close(from_pinentry[1]); return; } pinentry_pid = fork(); if (pinentry_pid == -1) { perror("fork"); return; } if (pinentry_pid == 0) { close(to_pinentry[1]); if (dup2(to_pinentry[0], STDIN_FILENO) == -1) { perror("dup2"); exit(EXIT_FAILURE); } close(to_pinentry[0]); close(from_pinentry[0]); if (dup2(from_pinentry[1], STDOUT_FILENO) == -1) { perror("dup2"); exit(EXIT_FAILURE); } close(from_pinentry[1]); execlp(pinentry, pinentry, NULL); perror(pinentry); exit(EXIT_FAILURE); } close(to_pinentry[0]); close(from_pinentry[1]); ret = pinentry_read(from_pinentry[0], &retstr); if (ret) log_error("Error: %s\n", retstr); free(retstr); retstr = NULL; if (ret) goto out; ret = pinentry_exchange(to_pinentry[1], from_pinentry[0], &retstr, "SETTITLE %s\n", "VPN Password"); if (ret) log_error("Failed to set title: %s\n", retstr); free(retstr); retstr = NULL; if (ret) goto out; ret = pinentry_exchange(to_pinentry[1], from_pinentry[0], &retstr, "SETDESC %s\n", "VPN Requires a Password"); if (ret) log_error("Failed to set description: %s\n", retstr); free(retstr); retstr = NULL; if (ret) goto out; escaped = uri_escape(hint); ret = pinentry_exchange(to_pinentry[1], from_pinentry[0], NULL, "SETKEYINFO %s\n", escaped); if (ret) log_error("Failed to set keyinfo\n"); free(escaped); escaped = NULL; if (ret) goto out; escaped = uri_escape(prompt); ret = pinentry_exchange(to_pinentry[1], from_pinentry[0], &retstr, "SETPROMPT %s\n", escaped); free(escaped); escaped = NULL; if (ret) log_error("Failed to set prompt: %s\n", retstr); free(retstr); retstr = NULL; if (ret) goto out; ret = pinentry_exchange(to_pinentry[1], from_pinentry[0], &retstr, "GETPIN\n"); if (ret) { log_error("Failed to get PIN: %s\n", retstr); free(retstr); retstr = NULL; goto out; } if (retstr) { strncpy(pass, retstr, len); free(retstr); retstr = NULL; } else { log_error("No password given\n"); } out: close(to_pinentry[1]); close(from_pinentry[0]); if (waitpid(pinentry_pid, &pinentry_status, 0) == -1) perror("waitpid"); } void read_password(const char *pinentry, const char *hint, const char *prompt, char *pass, size_t len) { int masked = 0; struct termios oldt, newt; size_t i; if (pinentry && *pinentry) { pinentry_read_password(pinentry, hint, prompt, pass, len); return; } printf("%s", prompt); fflush(stdout); // Try to hide user input if (tcgetattr(STDIN_FILENO, &oldt) == 0) { newt = oldt; newt.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &newt); masked = 1; } for (i = 0; i < len; i++) { int c = getchar(); if (c == '\n' || c == EOF) break; pass[i] = (char) c; } pass[i] = '\0'; if (masked) tcsetattr(STDIN_FILENO, TCSANOW, &oldt); printf("\n"); } char *read_from_stdin(size_t count) { char *buf; char *output; int bytes_read; buf = malloc(count + 1); if (buf == NULL) return NULL; bytes_read = read(STDIN_FILENO, buf, count); if (bytes_read == -1) { free(buf); return NULL; } buf[bytes_read] = '\0'; output = realloc(buf, bytes_read + 1); // Just keep using the larger buffer if realloc() fails. return output ? output : buf; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/ssl.h�����������������������������������������������������������������������0000664�0001750�0001750�00000012476�14753334500�016014� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, * you may extend this exception to your version of the file(s), but you are * not obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from * all source files in the program, then also delete it here. */ #ifndef OPENFORTIVPN_SSL_H #define OPENFORTIVPN_SSL_H #include <openssl/err.h> #include <openssl/ssl.h> #include <errno.h> #include <stdint.h> #include <string.h> #ifdef __clang__ /* * Get rid of Mac OS X 10.7 and greater deprecation warnings * see for instance https://wiki.openssl.org/index.php/Hostname_validation * this pragma selectively suppresses this type of warnings in clang */ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif #ifndef ERESTART /* * ERESTART is one of the recoverable errors which might be returned. * However, in Mac OS X and BSD this constant is not defined in errno.h * so we define a dummy value here. */ #define ERESTART -1 #endif #define ERR_SSL_AGAIN 0 // deprecated #define ERR_TLS_AGAIN 0 #define ERR_SSL_CLOSED -1 // deprecated #define ERR_TLS_CLOSED -1 #define ERR_SSL_CERT -2 // deprecated #define ERR_TLS_CERT -2 #define ERR_SSL_EOF -3 // deprecated #define ERR_TLS_EOF -3 #define ERR_SSL_PROTOCOL -4 // deprecated #define ERR_TLS_PROTOCOL -4 #define ERR_SSL_SEE_ERRNO -5 // deprecated #define ERR_TLS_SEE_ERRNO -5 #define ERR_SSL_SEE_TLSERR -6 // deprecated #define ERR_TLS_SEE_TLSERR -6 #define ERR_SSL_UNKNOWN -7 // deprecated #define ERR_TLS_UNKNOWN -7 static inline const char *err_ssl_str(int code) { if (code == ERR_TLS_AGAIN) return "Try again"; else if (code == ERR_TLS_CLOSED) return "Connection closed"; else if (code == ERR_TLS_CERT) return "Want X509 lookup"; else if (code == ERR_TLS_EOF) return "Protocol violation with EOF"; else if (code == ERR_TLS_PROTOCOL) return "Protocol error"; else if (code == ERR_TLS_SEE_ERRNO) return strerror(errno); else if (code == ERR_TLS_SEE_TLSERR) return ERR_reason_error_string(ERR_peek_last_error()); return "unknown"; } static inline int handle_ssl_error(SSL *ssl, int ret) { int code; if (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) return ERR_TLS_CLOSED; code = SSL_get_error(ssl, ret); if (code == SSL_ERROR_WANT_READ || code == SSL_ERROR_WANT_WRITE) return ERR_TLS_AGAIN; // The caller should try again if (code == SSL_ERROR_ZERO_RETURN) return ERR_TLS_CLOSED; if (code == SSL_ERROR_WANT_X509_LOOKUP) return ERR_TLS_CERT; if (code == SSL_ERROR_SYSCALL) { if (ERR_peek_last_error() != 0) return ERR_TLS_SEE_TLSERR; if (ret == 0) return ERR_TLS_EOF; if (errno == EAGAIN || errno == ERESTART || errno == EINTR) return ERR_TLS_AGAIN; // The caller should try again if (errno == EPIPE) return ERR_TLS_CLOSED; return ERR_TLS_SEE_ERRNO; } if (code == SSL_ERROR_SSL) return ERR_TLS_PROTOCOL; return ERR_TLS_UNKNOWN; } /* * Reads data from the TLS connection. * * @return > 0 in case of success (number of bytes transferred) * ERR_TLS_AGAIN if the caller should try again * < 0 in case of error */ static inline int safe_ssl_read(SSL *ssl, uint8_t *buf, int bufsize) { int ret = SSL_read(ssl, buf, bufsize); return (ret > 0) ? ret : handle_ssl_error(ssl, ret); } /* * Reads all data from the TLS connection. * * @return 1 in case of success * < 0 in case of error */ static inline int safe_ssl_read_all(SSL *ssl, uint8_t *buf, int bufsize) { for (int n = 0; n < bufsize; ) { int ret; ret = safe_ssl_read(ssl, &buf[n], bufsize - n); if (ret == ERR_TLS_AGAIN) continue; else if (ret < 0) return ret; n += ret; } return 1; } /* * Writes data to the TLS connection. * * Since SSL_MODE_ENABLE_PARTIAL_WRITE is not set by default (see man * SSL_get_mode), SSL_write() will only report success once the complete chunk * has been written. * * @return > 0 in case of success (number of bytes transferred) * ERR_TLS_AGAIN if the caller should try again * < 0 in case of error */ static inline int safe_ssl_write(SSL *ssl, const uint8_t *buf, int n) { int ret = SSL_write(ssl, buf, n); return (ret > 0) ? ret : handle_ssl_error(ssl, ret); } #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/http.c����������������������������������������������������������������������0000664�0001750�0001750�00000060222�14753334500�016155� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "http.h" #include "xml.h" #include "ssl.h" #include "ipv4.h" #include "userinput.h" #include "log.h" #include <unistd.h> #include <arpa/inet.h> #include <assert.h> #include <ctype.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> /* * Fixed size of the buffer for outgoing HTTP requests. * Initial size of the buffer for incoming HTTP responses. */ #define HTTP_BUFFER_SIZE 0x10000 /* * URL-encodes a string for HTTP requests. * * The dest buffer size MUST be at least strlen(str) * 3 + 1. * * @param[out] dest the buffer to write the URL-encoded string * @param[in] str the input string to be escaped */ void url_encode(char *dest, const char *str) { while (*str != '\0') { if (isalnum(*str) || *str == '-' || *str == '_' || *str == '.' || *str == '~') { *dest++ = *str; } else { static const char hex[] = "0123456789ABCDEF"; *dest++ = '%'; *dest++ = hex[(unsigned char)*str >> 4]; *dest++ = hex[(unsigned char)*str & 15]; } str++; } *dest = '\0'; } /* * Sends data to the HTTP server. * * @param[in] request the data to send to the server * @return 1 in case of success * < 0 in case of error */ int http_send(struct tunnel *tunnel, const char *request, ...) { va_list args; char buffer[HTTP_BUFFER_SIZE]; char logbuffer[HTTP_BUFFER_SIZE]; int length; int n = 0; va_start(args, request); length = vsnprintf(buffer, HTTP_BUFFER_SIZE, request, args); va_end(args); strcpy(logbuffer, buffer); if (loglevel <= OFV_LOG_DEBUG_DETAILS && tunnel->config->password[0] != '\0') { char *pwstart; char password[3 * PASSWORD_SIZE + 1]; url_encode(password, tunnel->config->password); while ((pwstart = strstr(logbuffer, password))) { int pos, pwlen; pos = pwstart - logbuffer; pwlen = strlen(password); for (int i = pos; i < pos + pwlen; i++) logbuffer[i] = '*'; } } if (length < 0) return ERR_HTTP_INVALID; else if (length >= HTTP_BUFFER_SIZE) return ERR_HTTP_TOO_LONG; log_debug_details("%s:\n%s\n", __func__, logbuffer); while (n == 0) n = safe_ssl_write(tunnel->ssl_handle, (uint8_t *) buffer, length); if (n < 0) { log_debug("Error writing to TLS connection (%s).\n", err_ssl_str(n)); return ERR_HTTP_TLS; } return 1; } static const char *find_header(const char *res, const char *header, uint32_t response_size) { const char *line = res; while (memcmp(line, "\r\n", 2)) { int line_len = (char *) memmem( line, response_size - (line - res), "\r\n", 2 ) - line; int head_len = strlen(header); if (line_len >= head_len && !strncasecmp(line, header, head_len)) return line + head_len; line += line_len + 2; } return NULL; } /* * Receives data from the HTTP server. * * @param[out] response if not NULL, this pointer is set to reference * the new allocated buffer containing the data * sent by the server * @return 1 in case of success * < 0 in case of error */ int http_receive(struct tunnel *tunnel, char **response, uint32_t *response_size) { uint32_t capacity = HTTP_BUFFER_SIZE; char *buffer; uint32_t bytes_read = 0; uint32_t header_size = 0; uint32_t content_size = 0; int chunked = 0; buffer = malloc(capacity + 1); // room for terminal '\0' if (buffer == NULL) return ERR_HTTP_NO_MEM; while (1) { int n; while ((n = safe_ssl_read(tunnel->ssl_handle, (uint8_t *) buffer + bytes_read, capacity - bytes_read)) == ERR_TLS_AGAIN) ; if (n < 0) { log_debug("Error reading from TLS connection (%s).\n", err_ssl_str(n)); free(buffer); return ERR_HTTP_TLS; } bytes_read += n; log_debug_details("%s:\n%s\n", __func__, buffer); if (!header_size) { /* Have we reached the end of the HTTP header? */ static const char EOH[4] = "\r\n\r\n"; const char *eoh = memmem(buffer, bytes_read, EOH, sizeof(EOH)); if (eoh) { header_size = eoh - buffer + sizeof(EOH); /* Get the body size. */ const char *header = find_header(buffer, "Content-Length: ", header_size); if (header) content_size = strtol(header, NULL, 10); if (find_header(buffer, "Transfer-Encoding: chunked", header_size)) chunked = 1; } } if (header_size) { /* Have we reached the end of the HTTP body? */ if (chunked) { static const char EOB[7] = "\r\n0\r\n\r\n"; /* Last chunk terminator. Done naively. */ if (bytes_read >= sizeof(EOB) && !memcmp(&buffer[bytes_read - sizeof(EOB)], EOB, sizeof(EOB))) break; } else { if (bytes_read >= header_size + content_size) break; } } /* expand the buffer if necessary */ if (bytes_read == capacity) { char *new_buffer; if ((UINT32_MAX - 1) / capacity < 2) { free(buffer); return ERR_HTTP_TOO_LONG; } capacity *= 2; new_buffer = realloc(buffer, capacity + 1); if (new_buffer == NULL) { free(buffer); return ERR_HTTP_NO_MEM; } buffer = new_buffer; } } if (memmem(&buffer[header_size], bytes_read - header_size, "<!--sslvpnerrmsgkey=sslvpn_login_permission_denied-->", 53) || memmem(buffer, header_size, "permission_denied denied", 24) || memmem(buffer, header_size, "Permission denied", 17)) { free(buffer); return ERR_HTTP_PERMISSION; } if (response == NULL) { free(buffer); } else { assert(bytes_read < capacity); buffer[bytes_read] = '\0'; *response = buffer; if (response_size != NULL) *response_size = bytes_read + 1; } return 1; } static int do_http_request(struct tunnel *tunnel, const char *method, const char *uri, const char *data, char **response, uint32_t *response_size ) { int ret; const char *template = ("%s %s HTTP/1.1\r\n" "Host: %s:%d\r\n" "User-Agent: %s\r\n" "Accept: */*\r\n" "Accept-Encoding: identity\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-store, no-cache, must-revalidate\r\n" "If-Modified-Since: Sat, 1 Jan 2000 00:00:00 GMT\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Cookie: %s\r\n" "Content-Length: %d\r\n" "\r\n%s"); ret = http_send(tunnel, template, method, uri, tunnel->config->gateway_host, tunnel->config->gateway_port, tunnel->config->user_agent, tunnel->cookie, strlen(data), data); if (ret != 1) return ret; return http_receive(tunnel, response, response_size); } /* * Sends and receives data from the HTTP server. * * @param[out] response if not NULL, this pointer is set to reference * the new allocated buffer containing the data * sent by the server * @return 1 in case of success * < 0 in case of error */ static int http_request(struct tunnel *tunnel, const char *method, const char *uri, const char *data, char **response, uint32_t *response_size ) { int ret; ret = do_http_request(tunnel, method, uri, data, response, response_size); if (ret == ERR_HTTP_TLS) { ssl_connect(tunnel); ret = do_http_request(tunnel, method, uri, data, response, response_size); } if (ret != 1) log_debug("Error issuing %s request\n", uri); return ret; } /* * Read value for key from a string like "key1=value1&key2=value2". * The `key` arg is supposed to contains the final "=". * * @return 1 in case of success * -1 key not found * -2 value too large for buffer * -3 if no memory */ static int get_value_from_response(const char *buf, const char *key, char *retbuf, size_t retbuflen) { int ret = -1; char *tokens; size_t keylen = strlen(key); char *saveptr = NULL; tokens = strdup(buf); if (tokens == NULL) { ret = -3; goto end; } for (const char *kv_pair = strtok_r(tokens, "&,\r\n", &saveptr); kv_pair != NULL; kv_pair = strtok_r(NULL, "&,\r\n", &saveptr)) { if (strncmp(key, kv_pair, keylen) == 0) { const char *val = &kv_pair[keylen]; if (strlen(val) > retbuflen - 1) { // value too long ret = -2; } else { strcpy(retbuf, val); ret = 1; } break; } } free(tokens); end: return ret; } static int get_action_url(const char *buf, const char *key, char *retbuf, size_t retbuflen) { int ret = -1; char *tokens; size_t keylen = strlen(key); char *saveptr = NULL; tokens = strdup(buf); if (tokens == NULL) { ret = -3; goto end; } for (const char *kv_pair = strtok_r(tokens, " \"\r\n", &saveptr); kv_pair != NULL; kv_pair = strtok_r(NULL, " \"\r\n", &saveptr)) { if (strncmp(key, kv_pair, keylen) == 0) { const char *val = strtok_r(NULL, "\"\r\n", &saveptr); if (strlen(val) > retbuflen - 1) { // value too long ret = -2; } else { strcpy(retbuf, val); ret = 1; } break; } } free(tokens); end: return ret; } static int auth_get_cookie(struct tunnel *tunnel, char *buf, uint32_t buffer_size) { const char *line; line = find_header(buf, "Set-Cookie: ", buffer_size); return auth_set_cookie(tunnel, line); } int auth_set_cookie(struct tunnel *tunnel, const char *line) { int ret = ERR_HTTP_NO_COOKIE; if (line) { const char *cookie_start; cookie_start = strstr(line, "SVPNCOOKIE="); if (cookie_start != NULL) { const char *cookie_end; size_t cookie_len; cookie_end = strpbrk(cookie_start, "\r\n;"); if (cookie_end) cookie_len = cookie_end - cookie_start; else cookie_len = strlen(cookie_start); if (cookie_len > COOKIE_SIZE) { log_error("Cookie larger than expected: %zu > %d\n", cookie_len, COOKIE_SIZE); } else { strncpy(tunnel->cookie, cookie_start, COOKIE_SIZE); tunnel->cookie[cookie_len] = '\0'; if (tunnel->cookie[11] == '\0') { log_debug("Empty cookie.\n"); } else { log_debug("Cookie: %s\n", tunnel->cookie); ret = 1; // success } } } else { log_debug("No cookie found\n"); } } return ret; } static void delay_otp(struct tunnel *tunnel) { if (tunnel->config->otp_delay > 0) { log_info("Delaying OTP by %d seconds...\n", tunnel->config->otp_delay); sleep(tunnel->config->otp_delay); } } static int try_otp_auth(struct tunnel *tunnel, const char *buffer, char **res, uint32_t *response_size) { char data[256]; char path[40]; char tmp[40]; char prompt[80]; const char *t = NULL, *n = NULL, *v = NULL, *e = NULL; const char *s = buffer; char *d = data; const char *p = NULL; int ret; /* Length-check for destination buffer */ #define SPACE_AVAILABLE(sz) (sizeof(data) - (d - data) >= (sz)) /* Get the form action */ s = strcasestr(s, "<FORM"); if (s == NULL) return -1; s = strcasestr(s + 5, "ACTION=\""); if (s == NULL) return -1; s += 8; e = strchr(s, '"'); if (e == NULL) return -1; if (e - s + 1 > sizeof(path)) return -1; strncpy(path, s, e - s); path[e - s] = '\0'; /* * Try to get password prompt, assume it starts with 'Please' * Fall back to default prompt if not found/parseable */ p = strstr(s, "Please"); if (tunnel->config->otp_prompt != NULL) p = strstr(s, tunnel->config->otp_prompt); if (p) { e = strchr(p, '<'); if (e != NULL) { if (e - p + 1 < sizeof(prompt)) { strncpy(prompt, p, e - p); prompt[e - p] = '\0'; p = prompt; } else { p = NULL; } } else { p = NULL; } } if (p == NULL) p = "Please enter one-time password: "; /* Search for all inputs */ while ((s = strcasestr(s, "<INPUT"))) { s += 6; /* * check if we found parameters for a later INPUT * during last round */ if (s < t || s < n || (v && s < v)) return -1; t = strcasestr(s, "TYPE=\""); n = strcasestr(s, "NAME=\""); v = strcasestr(s, "VALUE=\""); if (t == NULL) return -1; if (n == NULL) continue; n += 6; t += 6; if (strncmp(t, "hidden", 6) == 0 || strncmp(t, "password", 8) == 0) { /* * We try to be on the safe side * and URL-encode the variable name * * Append '&' if we found something in last round */ if (d > data) { if (!SPACE_AVAILABLE(1)) return -1; *d++ = '&'; } e = strchr(n, '"'); if (e == NULL) return -1; if (e - n + 1 > sizeof(tmp)) return -1; strncpy(tmp, n, e - n); tmp[e - n] = '\0'; if (!SPACE_AVAILABLE(3 * (e - n) + 1)) return -1; url_encode(d, tmp); d += strlen(d); if (!SPACE_AVAILABLE(1)) return -1; *d++ = '='; } if (strncmp(t, "hidden", 6) == 0) { /* Require value for hidden fields */ if (v == NULL) return -1; v += 7; e = strchr(v, '"'); if (e == NULL) return -1; if (e - v + 1 > sizeof(tmp)) return -1; strncpy(tmp, v, e - v); tmp[e - v] = '\0'; if (!SPACE_AVAILABLE(3 * (e - v) + 1)) return -1; url_encode(d, tmp); d += strlen(d); } else if (strncmp(t, "password", 8) == 0) { struct vpn_config *cfg = tunnel->config; size_t l; v = NULL; if (cfg->otp[0] == '\0') { // Interactively ask user for OTP char hint[USERNAME_SIZE + 1 + REALM_SIZE + 1 + GATEWAY_HOST_SIZE + 5]; sprintf(hint, "%s_%s_%s_otp", cfg->username, cfg->realm, cfg->gateway_host); read_password(cfg->pinentry, hint, p, cfg->otp, OTP_SIZE); if (cfg->otp[0] == '\0') { log_error("No OTP specified\n"); return 0; } } l = strlen(cfg->otp); if (!SPACE_AVAILABLE(3 * l + 1)) return -1; url_encode(d, cfg->otp); d += strlen(d); /* realm workaround */ if (cfg->realm[0] != '\0') { l = strlen(cfg->realm); if (!SPACE_AVAILABLE(3 * l + 8)) return -1; strcat(d, "&realm="); d += strlen(d); url_encode(d, cfg->realm); d += strlen(d); } } else if (strncmp(t, "submit", 6) == 0) { /* avoid adding another '&' */ n = v = e = NULL; } } if (!SPACE_AVAILABLE(1)) return -1; *d++ = '\0'; ret = http_request(tunnel, "POST", path, data, res, response_size); memset(tunnel->config->otp, '\0', OTP_SIZE + 1); // clear OTP for next run return ret; #undef SPACE_AVAILABLE } /* * Authenticates to gateway by sending username and password. * * @return 1 in case of success * < 0 in case of error */ int auth_log_in(struct tunnel *tunnel) { int ret; char username[3 * USERNAME_SIZE + 1]; char password[3 * PASSWORD_SIZE + 1]; char realm[3 * REALM_SIZE + 1]; char reqid[32] = { '\0' }; char polid[32] = { '\0' }; char group[128] = { '\0' }; char portal[64] = { '\0' }; char magic[32] = {'\0' }; char peer[32] = { '\0' }; #define OFV_MAX(a, b) ((a) > (b) ? a : b) char data[OFV_MAX( // username=%s&realm=%s&ajax=1&...&just_logged_in=1 sizeof("username=") + 3 * USERNAME_SIZE + sizeof("realm=") + 3 * REALM_SIZE + sizeof("ajax=1&redir=%%2Fremote%%2Findex&just_logged_in=1"), // "username=%s&credential=%s&realm=%s&ajax=1 sizeof("username=") + 3 * USERNAME_SIZE + sizeof("realm=") + 3 * REALM_SIZE + sizeof("credential=") + 3 * PASSWORD_SIZE + sizeof("ajax=1") )] = { '\0' }; #undef OFV_MAX char token[128], tokenresponse[256], tokenparams[320]; char action_url[1024] = { '\0' }; char *res = NULL; uint32_t response_size; url_encode(username, tunnel->config->username); url_encode(realm, tunnel->config->realm); tunnel->cookie[0] = '\0'; if (strlen(tunnel->config->saml_session_id) > 0) { // SAML login static const char uri_pattern[] = "/remote/saml/auth_id?id=%s"; int required_size = snprintf(NULL, 0, uri_pattern, tunnel->config->saml_session_id) + 1; char *uri = malloc(required_size); if (!uri) { ret = -1; log_error("malloc: %s\n", strerror(errno)); goto end; } snprintf(uri, required_size, uri_pattern, tunnel->config->saml_session_id); log_debug("Using SAML authentication URL %s\n", uri); ret = http_request(tunnel, "GET", uri, "", &res, &response_size); free(uri); } else if (username[0] == '\0' && tunnel->config->password[0] == '\0') { ret = http_request(tunnel, "GET", "/remote/login", data, &res, &response_size); } else { if (tunnel->config->password[0] == '\0') { snprintf(data, sizeof(data), "username=%s&realm=%s&ajax=1&redir=%%2Fremote%%2Findex&just_logged_in=1", username, realm); } else { url_encode(password, tunnel->config->password); snprintf(data, sizeof(data), "username=%s&credential=%s&realm=%s&ajax=1", username, password, realm); } ret = http_request(tunnel, "POST", "/remote/logincheck", data, &res, &response_size); } if (ret != 1) goto end; /* Probably one-time password required */ if (strncmp(res, "HTTP/1.1 401 Authorization Required\r\n", 37) == 0) { delay_otp(tunnel); ret = try_otp_auth(tunnel, res, &res, &response_size); if (ret != 1) goto end; } if (strncmp(res, "HTTP/1.1 200 OK\r\n", 17)) { char word[17]; if (sscanf(res, "%16s %d", word, &ret) < 2) ret = ERR_HTTP_BAD_RES_CODE; goto end; } ret = auth_get_cookie(tunnel, res, response_size); if (ret == ERR_HTTP_NO_COOKIE) { struct vpn_config *cfg = tunnel->config; /* * If the response body includes a tokeninfo= parameter, * it means the VPN gateway expects two-factor authentication. * It sends a one-time authentication credential for example * by email or SMS, and expects to receive it back in the * second authentication stage. No SVPNCOOKIE will be provided * until after the second call to /remote/logincheck. * * If we receive neither a tokeninfo= parameter nor an * SVPNCOOKIE, it means our authentication attempt was * rejected. */ ret = get_value_from_response(res, "tokeninfo=", token, 128); if (ret != 1) { // No SVPNCOOKIE and no tokeninfo, return error. ret = ERR_HTTP_NO_COOKIE; goto end; } // Two-factor authentication needed. get_value_from_response(res, "grp=", group, 128); get_value_from_response(res, "reqid=", reqid, 32); get_value_from_response(res, "polid=", polid, 32); get_value_from_response(res, "portal=", portal, 64); get_value_from_response(res, "magic=", magic, 32); get_value_from_response(res, "peer=", peer, 32); if (cfg->otp[0] == '\0' && strncmp(token, "ftm_push", 8) == 0 && cfg->no_ftm_push == 0) { /* * The server supports FTM push if `tokeninfo` is `ftm_push`, * but only try this if the OTP is not provided by the config * file or command line. */ snprintf(tokenparams, sizeof(tokenparams), "ftmpush=1"); } else { if (cfg->otp[0] == '\0') { // Interactively ask user for 2FA token char hint[USERNAME_SIZE + 1 + REALM_SIZE + 1 + GATEWAY_HOST_SIZE + 5]; sprintf(hint, "%s_%s_%s_2fa", cfg->username, cfg->realm, cfg->gateway_host); read_password(cfg->pinentry, hint, "Two-factor authentication token: ", cfg->otp, OTP_SIZE); if (cfg->otp[0] == '\0') { log_error("No token specified\n"); return 0; } } url_encode(tokenresponse, cfg->otp); snprintf(tokenparams, sizeof(tokenparams), "code=%s&code2=&magic=%s", tokenresponse, magic); } snprintf(data, sizeof(data), "username=%s&realm=%s&reqid=%s&polid=%s&grp=%s&portal=%s&peer=%s&%s", username, realm, reqid, polid, group, portal, peer, tokenparams); delay_otp(tunnel); ret = http_request(tunnel, "POST", "/remote/logincheck", data, &res, &response_size); if (ret != 1) goto end; if (strncmp(res, "HTTP/1.1 200 OK\r\n", 17)) { char word[17]; if (sscanf(res, "%16s %d", word, &ret) < 2) ret = ERR_HTTP_BAD_RES_CODE; goto end; } ret = auth_get_cookie(tunnel, res, response_size); } /* * If hostchecking enabled, get action url */ get_action_url(res, "action=", action_url, 1024); if (strlen(action_url) != 0) { snprintf(data, sizeof(data), "hostcheck=%s&check_virtual_desktop=%s", tunnel->config->hostcheck, tunnel->config->check_virtual_desktop); ret = http_request(tunnel, "POST", action_url, data, &res, &response_size); } end: free(res); return ret; } int auth_log_out(struct tunnel *tunnel) { return http_request(tunnel, "GET", "/remote/logout", "", NULL, NULL); } int auth_request_vpn_allocation(struct tunnel *tunnel) { int ret = http_request(tunnel, "GET", "/remote/index", "", NULL, NULL); if (ret != 1) return ret; return http_request(tunnel, "GET", "/remote/fortisslvpn", "", NULL, NULL); } static int parse_xml_config(struct tunnel *tunnel, const char *buffer) { const char *val; char *gateway; char *dns_server; int ret = 0; if (strncmp(buffer, "HTTP/1.1 200 OK\r\n", 17)) { char word[17]; if (sscanf(buffer, "%16s %d", word, &ret) < 2) ret = ERR_HTTP_BAD_RES_CODE; return ret; } // Skip the HTTP header buffer = strstr(buffer, "\r\n\r\n"); // The address of a local end of a router val = xml_find('<', "assigned-addr", buffer, 1); gateway = xml_get(xml_find(' ', "ipv4=", val, 1)); if (!gateway) log_warn("No gateway address, using interface for routing\n"); // The dns search string val = buffer; while ((val = xml_find('<', "dns", val, 2))) { if (xml_find(' ', "domain=", val, 1)) { tunnel->ipv4.dns_suffix = xml_get(xml_find(' ', "domain=", val, 1)); log_debug("Found dns suffix %s in xml config\n", tunnel->ipv4.dns_suffix); break; } } // The dns servers val = buffer; while ((val = xml_find('<', "dns", val, 2))) { if (xml_find(' ', "ip=", val, 1)) { dns_server = xml_get(xml_find(' ', "ip=", val, 1)); log_debug("Found dns server %s in xml config\n", dns_server); if (!tunnel->ipv4.ns1_addr.s_addr) tunnel->ipv4.ns1_addr.s_addr = inet_addr(dns_server); else if (!tunnel->ipv4.ns2_addr.s_addr) tunnel->ipv4.ns2_addr.s_addr = inet_addr(dns_server); free(dns_server); } } // Routes the tunnel wants to push val = xml_find('<', "split-tunnel-info", buffer, 1); while ((val = xml_find('<', "addr", val, 2))) { char *dest, *mask; dest = xml_get(xml_find(' ', "ip=", val, 1)); if (!dest) { log_warn("No ip address for a route\n"); continue; } mask = xml_get(xml_find(' ', "mask=", val, 1)); if (!mask) { log_warn("No mask for a route\n"); free(dest); continue; } ipv4_add_split_vpn_route(tunnel, dest, mask, gateway); free(dest); free(mask); } free(gateway); return 1; } int auth_get_config(struct tunnel *tunnel) { char *buffer; int ret; ret = http_request(tunnel, "GET", "/remote/fortisslvpn_xml", "", &buffer, NULL); if (ret == 1) { ret = parse_xml_config(tunnel, buffer); free(buffer); } return ret; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/log.c�����������������������������������������������������������������������0000664�0001750�0001750�00000007245�14753334500�015765� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "log.h" #include <unistd.h> #include <pthread.h> #include <syslog.h> #include <errno.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static pthread_mutex_t mutex; static int do_syslog; //static variables are initialized to zero in C99 enum log_verbosity loglevel; static int is_a_tty; // static variables are initialized to zero in C99 struct log_param_s { const char *prefix; const char *color_string; int syslog_prio; }; static const struct log_param_s log_params[OFV_LOG_DEBUG_ALL + 1] = { { " ", "", LOG_ERR}, { "ERROR: ", "\033[0;31m", LOG_ERR}, { "WARN: ", "\033[0;33m", LOG_WARNING}, { "INFO: ", "", LOG_INFO}, { "DEBUG: ", "\033[0;90m", LOG_DEBUG}, { "DEBUG: ", "\033[0;90m", LOG_DEBUG}, { "DEBUG: ", "\033[0;90m", LOG_DEBUG}, }; void init_logging(void) { pthread_mutexattr_t mutexattr; int e; loglevel = OFV_LOG_INFO; is_a_tty = isatty(STDOUT_FILENO); e = pthread_mutexattr_init(&mutexattr); if (e) fprintf(stderr, "ERROR: pthread_mutexattr_init: %s\n", strerror(e)); #ifdef HAVE_PTHREAD_MUTEXATTR_SETROBUST e = pthread_mutexattr_setrobust(&mutexattr, PTHREAD_MUTEX_ROBUST); if (e) fprintf(stderr, "ERROR: pthread_mutexattr_setrobust: %s\n", strerror(e)); #endif e = pthread_mutex_init(&mutex, &mutexattr); if (e) fprintf(stderr, "ERROR: pthread_mutex_init: %s\n", strerror(e)); } void set_syslog(int use_syslog) { if (!use_syslog) return; do_syslog = use_syslog; openlog("openfortivpn", LOG_PID, LOG_DAEMON); } void increase_verbosity(void) { if (loglevel < OFV_LOG_DEBUG_ALL) loglevel++; } void decrease_verbosity(void) { if (loglevel > OFV_LOG_MUTE) loglevel--; } void do_log(int verbosity, const char *format, ...) { va_list args; const struct log_param_s *lp = NULL; int e; e = pthread_mutex_lock(&mutex); if (e) fprintf(stderr, "ERROR: pthread_mutex_lock: %s\n", strerror(e)); // Use sane default if wrong verbosity specified if (verbosity > OFV_LOG_DEBUG_ALL || verbosity < 0) verbosity = OFV_LOG_MUTE; lp = &log_params[verbosity]; if (!do_syslog) printf("%s%s", is_a_tty ? lp->color_string : "", lp->prefix); va_start(args, format); if (do_syslog) vsyslog(lp->syslog_prio, format, args); else vprintf(format, args); va_end(args); if (!do_syslog) { if (is_a_tty) printf("\033[0;0m"); fflush(stdout); } e = pthread_mutex_unlock(&mutex); if (e) fprintf(stderr, "ERROR: pthread_mutex_unlock: %s\n", strerror(e)); } void do_log_packet(const char *prefix, size_t len, const uint8_t *packet) { char *str, *pos; size_t len_prefix = strlen(prefix); str = malloc(len_prefix + 3 * len + 1 + 1); if (str == NULL) { log_error("malloc: %s\n", strerror(errno)); return; } pos = strcpy(str, prefix); pos += len_prefix; for (size_t i = 0; i < len; i++) pos += sprintf(pos, "%02x ", packet[i]); strcpy(pos - 1, "\n"); if (do_syslog) syslog(LOG_DEBUG, "%s", str); else puts(str); free(str); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/xml.c�����������������������������������������������������������������������0000664�0001750�0001750�00000005426�14753334500�016003� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Lubomir Rintel * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "xml.h" #include "config.h" #include "log.h" #include <string.h> /* * Poor man's XML parser. Looks for given tag or attribute. Assumes * there's no CDATA or text content and does not recognize strings: * it would mess up the parsing if they contain '<' or '/'. * * @t '<' if we're looking for a tag, ' ' if we're looking * for an attribute. * @needle name of a tag or attribute (end and attribute with a * '=' to swallow it). * @buf a NUL terminated buffer to look in. * @nest do not escape more than @nest levels of nesting * (1 == look for kids, 2 == look for siblings). * @return a string immediately following the needle if found, * %NULL otherwise. */ const char *xml_find(char t, const char *needle, const char *buf, int nest) { if (!buf) return NULL; for (int i = 0; buf[i]; i++) { if (buf[i] == '<' && buf[i + 1] != '/') nest++; if (buf[i] == '/') nest--; if (!nest) return NULL; if (&buf[i+1] == strstr(&buf[i], needle) && buf[i] == t) { return &buf[i + 1 + strlen(needle)]; } } return NULL; } /* * Poor man's XML attribute parser. Takes the first string as a quoting * character and consumes characters until the next occurrence or an end * of the buffer. Doesn't return more than 15 characters (enough for an * IPv4 address textural form). * * @buf a NUL terminated buffer to look in. * @return %NULL in case of an error, a character string with * ownership passed upon success */ char *xml_get(const char *buf) { char val[MAX_DOMAIN_LENGTH]; // just enough to hold a domain search string char quote; int i; if (!buf) return NULL; quote = buf[0]; if (!quote) { log_warn("Short read while getting value in config XML\n"); return NULL; } for (i = 1; buf[i]; i++) { if (buf[i] == quote) break; if (i == MAX_DOMAIN_LENGTH) { log_warn("Value too long in config XML\n"); break; } val[i - 1] = buf[i]; } if (buf[i] != quote) { log_warn("Could not read out an attribute value in config XML\n"); return NULL; } val[i - 1] = '\0'; return strdup(val); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/config.c��������������������������������������������������������������������0000664�0001750�0001750�00000043157�14753334500�016453� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, * you may extend this exception to your version of the file(s), but you are * not obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from * all source files in the program, then also delete it here. */ #include "config.h" #include "log.h" #include <openssl/x509.h> /* work around OpenSSL bug: missing definition of STACK_OF */ #include <openssl/tls1.h> #include <ctype.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> const struct vpn_config invalid_cfg = { .gateway_host = {'\0'}, .gateway_port = 0, .username = {'\0'}, .password = {'\0'}, .password_set = 0, .cookie = NULL, .saml_port = 0, .saml_session_id = {'\0'}, .otp = {'\0'}, .otp_prompt = NULL, .otp_delay = -1, .no_ftm_push = -1, .pinentry = NULL, .realm = {'\0'}, .iface_name = {'\0'}, .sni = {'\0'}, .set_routes = -1, .set_dns = -1, .pppd_use_peerdns = -1, #if HAVE_RESOLVCONF .use_resolvconf = -1, #endif .use_syslog = -1, .half_internet_routes = -1, .persistent = -1, #if HAVE_USR_SBIN_PPPD .pppd_log = NULL, .pppd_plugin = NULL, .pppd_ipparam = NULL, .pppd_ifname = NULL, .pppd_call = NULL, .pppd_accept_remote = -1, #endif #if HAVE_USR_SBIN_PPP .ppp_system = NULL, #endif .ca_file = NULL, .user_cert = NULL, .user_key = NULL, .pem_passphrase = {'\0'}, .pem_passphrase_set = 0, .insecure_ssl = -1, .cipher_list = NULL, .min_tls = -1, .seclevel_1 = -1, .cert_whitelist = NULL, .use_engine = -1, .user_agent = NULL, .hostcheck = NULL, .check_virtual_desktop = NULL, }; /* * Adds a sha256 digest to the list of trusted certificates. */ int add_trusted_cert(struct vpn_config *cfg, const char *digest) { struct x509_digest *new; new = malloc(sizeof(struct x509_digest)); if (new == NULL) return ERR_CFG_NO_MEM; new->next = NULL; strncpy(new->data, digest, SHA256STRLEN - 1); new->data[SHA256STRLEN - 1] = '\0'; if (cfg->cert_whitelist == NULL) { cfg->cert_whitelist = new; } else { struct x509_digest *last; for (last = cfg->cert_whitelist; last->next != NULL; last = last->next) ; last->next = new; } return 0; } /* * Converts string to bool int * * @params[in] str the string to read from * @return 0 or 1 if successful, < 0 if unrecognized value */ int strtob(const char *str) { if (str[0] == '\0') return 0; else if (strcasecmp(str, "true") == 0) return 1; else if (strcasecmp(str, "false") == 0) return 0; else if (isdigit(str[0]) == 0) return -1; long i = strtol(str, NULL, 0); if (i < 0 || i > 1) return -1; return i; } /* * Converts string to TLS version * * @params[in] str the string to read from * @return OpenSSL version or -1 */ int parse_min_tls(const char *str) { if (str[0] != '1' || str[1] != '.' || str[2] == 0 || str[3] != 0) return -1; switch (str[2]) { #ifdef TLS1_VERSION case '0': return TLS1_VERSION; #endif #ifdef TLS1_1_VERSION case '1': return TLS1_1_VERSION; #endif #ifdef TLS1_2_VERSION case '2': return TLS1_2_VERSION; #endif #ifdef TLS1_3_VERSION case '3': return TLS1_3_VERSION; #endif default: return -1; } } /* * Reads filename contents and fill cfg with its values. * * @param[out] cfg the struct vpn_config to store configuration values * @param[in] filename the file to read values from * @return 0 if successful, or < 0 in case of error */ int load_config(struct vpn_config *cfg, const char *filename) { int ret = 0; FILE *file; char *line = NULL; size_t len = 0; ssize_t read; file = fopen(filename, "r"); if (file == NULL) { ret = ERR_CFG_SEE_ERRNO; goto err_return; } // Read line by line while ((read = getline(&line, &len, file)) != -1) { char *key, *equals, *val; // Ignore blank lines. We could argue that the string must be at least // 3 chars to be valid, eg. 'x=\n' but let the rest of the function // logic handle that. NOTE: getline includes the '\n' in the string, // which is removed later on. if (read < 2) continue; if (line[0] == '#') continue; // Expect something like: "key = value" equals = strchr(line, '='); if (equals == NULL) { log_warn("Bad line in configuration file: \"%s\".\n", line); continue; } equals[0] = '\0'; key = line; val = equals + 1; // Remove heading spaces while (isspace(key[0])) key++; while (isspace(val[0])) val++; // Remove trailing spaces for (int i = strlen(key) - 1; i > 0; i--) { if (isspace(key[i])) key[i] = '\0'; else break; } for (int i = strlen(val) - 1; i > 0; i--) { if (isspace(val[i])) val[i] = '\0'; else break; } if (strcmp(key, "host") == 0) { strncpy(cfg->gateway_host, val, GATEWAY_HOST_SIZE); cfg->gateway_host[GATEWAY_HOST_SIZE] = '\0'; } else if (strcmp(key, "port") == 0) { long port = strtol(val, NULL, 0); if (port < 1 || port > 65535) { log_warn("Bad port in configuration file: \"%ld\".\n", port); continue; } cfg->gateway_port = (uint16_t)port; } else if (strcmp(key, "username") == 0) { strncpy(cfg->username, val, USERNAME_SIZE); cfg->username[USERNAME_SIZE] = '\0'; } else if (strcmp(key, "password") == 0) { strncpy(cfg->password, val, PASSWORD_SIZE); cfg->password[PASSWORD_SIZE] = '\0'; cfg->password_set = 1; } else if (strcmp(key, "otp") == 0) { strncpy(cfg->otp, val, OTP_SIZE); cfg->otp[OTP_SIZE] = '\0'; } else if (strcmp(key, "otp-prompt") == 0) { free(cfg->otp_prompt); cfg->otp_prompt = strdup(val); } else if (strcmp(key, "otp-delay") == 0) { long otp_delay = strtol(val, NULL, 0); if (otp_delay < 0 || otp_delay > UINT_MAX) { log_warn("Bad value for otp-delay in configuration file: \"%s\".\n", val); continue; } cfg->otp_delay = otp_delay; } else if (strcmp(key, "cookie") == 0) { log_warn("Ignoring option \"%s\" in the config file.\n", key); } else if (strcmp(key, "cookie-on-stdin") == 0) { log_warn("Ignoring option \"%s\" in the config file.\n", key); } else if (strcmp(key, "no-ftm-push") == 0) { int no_ftm_push = strtob(val); if (no_ftm_push < 0) { log_warn("Bad no-ftm-push in configuration file: \"%s\".\n", val); continue; } cfg->no_ftm_push = no_ftm_push; } else if (strcmp(key, "pinentry") == 0) { free(cfg->pinentry); cfg->pinentry = strdup(val); } else if (strcmp(key, "realm") == 0) { strncpy(cfg->realm, val, REALM_SIZE); cfg->realm[REALM_SIZE] = '\0'; } else if (strcmp(key, "set-dns") == 0) { int set_dns = strtob(val); if (set_dns < 0) { log_warn("Bad set-dns in configuration file: \"%s\".\n", val); continue; } cfg->set_dns = set_dns; } else if (strcmp(key, "sni") == 0) { strncpy(cfg->sni, val, GATEWAY_HOST_SIZE); cfg->sni[GATEWAY_HOST_SIZE] = '\0'; } else if (strcmp(key, "set-routes") == 0) { int set_routes = strtob(val); if (set_routes < 0) { log_warn("Bad set-routes in configuration file: \"%s\".\n", val); continue; } cfg->set_routes = set_routes; } else if (strcmp(key, "half-internet-routes") == 0) { int half_internet_routes = strtob(val); if (half_internet_routes < 0) { log_warn("Bad half-internet-routes in configuration file: \"%s\".\n", val); continue; } cfg->half_internet_routes = half_internet_routes; } else if (strcmp(key, "persistent") == 0) { unsigned long persistent = strtoul(val, NULL, 0); if (persistent > UINT_MAX) { log_warn("Bad value for persistent in configuration file: \"%s\".\n", val); continue; } cfg->persistent = persistent; #if HAVE_USR_SBIN_PPPD } else if (strcmp(key, "pppd-use-peerdns") == 0) { int pppd_use_peerdns = strtob(val); if (pppd_use_peerdns < 0) { log_warn("Bad pppd-use-peerdns in configuration file: \"%s\".\n", val); continue; } cfg->pppd_use_peerdns = pppd_use_peerdns; } else if (strcmp(key, "pppd-log") == 0) { free(cfg->pppd_log); cfg->pppd_log = strdup(val); } else if (strcmp(key, "pppd-plugin") == 0) { free(cfg->pppd_plugin); cfg->pppd_plugin = strdup(val); } else if (strcmp(key, "pppd-ipparam") == 0) { free(cfg->pppd_ipparam); cfg->pppd_ipparam = strdup(val); } else if (strcmp(key, "pppd-ifname") == 0) { free(cfg->pppd_ifname); cfg->pppd_ifname = strdup(val); } else if (strcmp(key, "pppd-call") == 0) { free(cfg->pppd_call); cfg->pppd_call = strdup(val); } else if (strcmp(key, "pppd-accept-remote") == 0) { int pppd_accept_remote = strtob(val); if (pppd_accept_remote < 0) { log_warn("Bad pppd-accept-remote in configuration file: \"%s\".\n", val); continue; } cfg->pppd_accept_remote = pppd_accept_remote; #else } else if (strcmp(key, "pppd") == 0) { log_warn("Ignoring pppd option \"%s\" in the config file.\n", key); #endif } else if (strcmp(key, "ppp-system") == 0) { #if HAVE_USR_SBIN_PPP cfg->ppp_system = strdup(val); #else log_warn("Ignoring option \"%s\" in the config file.\n", key); #endif } else if (strcmp(key, "use-resolvconf") == 0) { #if HAVE_RESOLVCONF int use_resolvconf = strtob(val); if (use_resolvconf < 0) { log_warn("Bad value for use-resolvconf in configuration file: \"%s\".\n", val); continue; } cfg->use_resolvconf = use_resolvconf; #else log_warn("Ignoring option \"%s\" in the config file.\n", key); #endif } else if (strcmp(key, "use-syslog") == 0) { int use_syslog = strtob(val); if (use_syslog < 0) { log_warn("Bad use-syslog in configuration file: \"%s\".\n", val); continue; } cfg->use_syslog = use_syslog; } else if (strcmp(key, "trusted-cert") == 0) { if (strlen(val) != SHA256STRLEN - 1) { log_warn("Bad certificate sha256 digest in configuration file: \"%s\".\n", val); continue; } if (add_trusted_cert(cfg, val)) log_warn("Could not add certificate digest to whitelist.\n"); } else if (strcmp(key, "ca-file") == 0) { free(cfg->ca_file); cfg->ca_file = strdup(val); } else if (strcmp(key, "user-cert") == 0) { free(cfg->user_cert); cfg->user_cert = strdup(val); if (strncmp(cfg->user_cert, "pkcs11:", 7) == 0) cfg->use_engine = 1; } else if (strcmp(key, "saml-login") == 0) { long port = strtol(val, NULL, 0); if (port < 1 || port > 65535) { log_error("Bad SAML listen port: \"%s\".\n", val); goto err_free; } cfg->saml_port = (uint16_t)port; } else if (strcmp(key, "user-key") == 0) { free(cfg->user_key); cfg->user_key = strdup(val); } else if (strcmp(key, "pem-passphrase") == 0) { strncpy(cfg->pem_passphrase, val, PEM_PASSPHRASE_SIZE); cfg->pem_passphrase[PEM_PASSPHRASE_SIZE] = '\0'; cfg->pem_passphrase_set = 1; } else if (strcmp(key, "insecure-ssl") == 0) { int insecure_ssl = strtob(val); if (insecure_ssl < 0) { log_warn("Bad insecure-ssl in configuration file: \"%s\".\n", val); continue; } cfg->insecure_ssl = insecure_ssl; } else if (strcmp(key, "cipher-list") == 0) { free(cfg->cipher_list); cfg->cipher_list = strdup(val); #if OPENSSL_VERSION_NUMBER >= 0x10100000L } else if (strcmp(key, "min-tls") == 0) { int min_tls = parse_min_tls(val); if (min_tls == -1) { log_warn("Bad min-tls in configuration file: \"%s\".\n", val); continue; } else { cfg->min_tls = min_tls; } #endif } else if (strcmp(key, "seclevel-1") == 0) { int seclevel_1 = strtob(val); if (seclevel_1 < 0) { log_warn("Bad seclevel-1 in configuration file: \"%s\".\n", val); continue; } cfg->seclevel_1 = seclevel_1; } else if (strcmp(key, "user-agent") == 0) { free(cfg->user_agent); cfg->user_agent = strdup(val); } else if (strcmp(key, "hostcheck") == 0) { free(cfg->hostcheck); cfg->hostcheck = strdup(val); } else if (strcmp(key, "check-virtual-desktop") == 0) { free(cfg->check_virtual_desktop); cfg->check_virtual_desktop = strdup(val); } else { log_warn("Bad key in configuration file: \"%s\".\n", key); goto err_free; } } if (ferror(file)) ret = ERR_CFG_SEE_ERRNO; err_free: free(line); if (fclose(file)) log_warn("Could not close %s (%s).\n", filename, strerror(errno)); err_return: return ret; } void destroy_vpn_config(struct vpn_config *cfg) { free(cfg->otp_prompt); free(cfg->pinentry); free(cfg->cookie); #if HAVE_USR_SBIN_PPPD free(cfg->pppd_log); free(cfg->pppd_plugin); free(cfg->pppd_ipparam); free(cfg->pppd_ifname); free(cfg->pppd_call); #endif #if HAVE_USR_SBIN_PPP free(cfg->ppp_system); #endif free(cfg->ca_file); free(cfg->user_cert); free(cfg->user_key); free(cfg->cipher_list); free(cfg->user_agent); free(cfg->hostcheck); free(cfg->check_virtual_desktop); while (cfg->cert_whitelist != NULL) { struct x509_digest *tmp = cfg->cert_whitelist->next; free(cfg->cert_whitelist); cfg->cert_whitelist = tmp; } } void merge_config(struct vpn_config *dst, struct vpn_config *src) { if (src->gateway_host[0]) strcpy(dst->gateway_host, src->gateway_host); if (src->gateway_port != invalid_cfg.gateway_port) dst->gateway_port = src->gateway_port; if (src->username[0]) strcpy(dst->username, src->username); if (src->password_set) { strcpy(dst->password, src->password); dst->password_set = src->password_set; } if (src->otp[0]) strcpy(dst->otp, src->otp); if (src->otp_delay != invalid_cfg.otp_delay) dst->otp_delay = src->otp_delay; if (src->no_ftm_push != invalid_cfg.no_ftm_push) dst->no_ftm_push = src->no_ftm_push; if (src->cookie != invalid_cfg.cookie) { free(dst->cookie); dst->cookie = src->cookie; } if (src->saml_port != 0) dst->saml_port = src->saml_port; if (src->pinentry) { free(dst->pinentry); dst->pinentry = src->pinentry; } if (src->realm[0]) strcpy(dst->realm, src->realm); if (src->iface_name[0]) strcpy(dst->iface_name, src->iface_name); if (src->sni[0]) strcpy(dst->sni, src->sni); if (src->set_routes != invalid_cfg.set_routes) dst->set_routes = src->set_routes; if (src->set_dns != invalid_cfg.set_dns) dst->set_dns = src->set_dns; if (src->pppd_use_peerdns != invalid_cfg.pppd_use_peerdns) dst->pppd_use_peerdns = src->pppd_use_peerdns; #if HAVE_RESOLVCONF if (src->use_resolvconf != invalid_cfg.use_resolvconf) dst->use_resolvconf = src->use_resolvconf; #endif if (src->use_syslog != invalid_cfg.use_syslog) dst->use_syslog = src->use_syslog; if (src->half_internet_routes != invalid_cfg.half_internet_routes) dst->half_internet_routes = src->half_internet_routes; if (src->persistent != invalid_cfg.persistent) dst->persistent = src->persistent; #if HAVE_USR_SBIN_PPPD if (src->pppd_log) { free(dst->pppd_log); dst->pppd_log = src->pppd_log; } if (src->pppd_plugin) { free(dst->pppd_plugin); dst->pppd_plugin = src->pppd_plugin; } if (src->pppd_ipparam) { free(dst->pppd_ipparam); dst->pppd_ipparam = src->pppd_ipparam; } if (src->pppd_ifname) { free(dst->pppd_ifname); dst->pppd_ifname = src->pppd_ifname; } if (src->pppd_call) { free(dst->pppd_call); dst->pppd_call = src->pppd_call; } if (src->pppd_accept_remote != invalid_cfg.pppd_accept_remote) dst->pppd_accept_remote = src->pppd_accept_remote; #endif #if HAVE_USR_SBIN_PPP if (src->ppp_system) { free(dst->ppp_system); dst->ppp_system = src->ppp_system; } #endif if (src->ca_file) { free(dst->ca_file); dst->ca_file = src->ca_file; } if (src->user_cert) { free(dst->user_cert); if (strncmp(src->user_cert, "pkcs11:", 7) == 0) dst->use_engine = 1; dst->user_cert = src->user_cert; } if (src->user_key) { free(dst->user_key); dst->user_key = src->user_key; } if (src->pem_passphrase_set) { strcpy(dst->pem_passphrase, src->pem_passphrase); dst->pem_passphrase_set = src->pem_passphrase_set; } if (src->insecure_ssl != invalid_cfg.insecure_ssl) dst->insecure_ssl = src->insecure_ssl; if (src->cipher_list) { free(dst->cipher_list); dst->cipher_list = src->cipher_list; } if (src->min_tls > 0) dst->min_tls = src->min_tls; if (src->seclevel_1 != invalid_cfg.seclevel_1) dst->seclevel_1 = src->seclevel_1; if (src->cert_whitelist) { while (dst->cert_whitelist != NULL) { struct x509_digest *tmp = dst->cert_whitelist->next; free(dst->cert_whitelist); dst->cert_whitelist = tmp; } dst->cert_whitelist = src->cert_whitelist; } if (src->user_agent != invalid_cfg.user_agent) dst->user_agent = src->user_agent; if (src->hostcheck != invalid_cfg.hostcheck) dst->hostcheck = src->hostcheck; if (src->check_virtual_desktop != invalid_cfg.check_virtual_desktop) dst->check_virtual_desktop = src->check_virtual_desktop; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/tunnel.c��������������������������������������������������������������������0000664�0001750�0001750�00000116675�14753334500�016521� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. * You must obey the GNU General Public License in all respects for all of the * code used other than OpenSSL. If you modify file(s) with this exception, * you may extend this exception to your version of the file(s), but you are * not obligated to do so. If you do not wish to do so, delete this exception * statement from your version. If you delete this exception statement from * all source files in the program, then also delete it here. */ #include "tunnel.h" #include "http.h" #include "log.h" #include "userinput.h" #include <openssl/err.h> #ifndef OPENSSL_NO_ENGINE #include <openssl/engine.h> #endif #include <openssl/ui.h> #include <openssl/x509v3.h> #if HAVE_SYSTEMD #include <systemd/sd-daemon.h> #endif #include <unistd.h> #include <arpa/inet.h> #include <fcntl.h> #include <ifaddrs.h> #include <netdb.h> #if HAVE_PTY_H #include <pty.h> #elif HAVE_UTIL_H #include <util.h> #endif #include <sys/types.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <sys/wait.h> #include <sys/ioctl.h> #include <termios.h> #if HAVE_LIBUTIL_H #include <libutil.h> #endif #include <errno.h> #include <signal.h> #include <string.h> #include <assert.h> struct ofv_varr { unsigned int cap; // current capacity unsigned int off; // next slot to write, always < max(cap - 1, 1) const char **data; // NULL terminated }; static int ofv_append_varr(struct ofv_varr *p, const char *x) { if (p->off + 1 >= p->cap) { const char **ndata; unsigned int ncap = (p->off + 1) * 2; if (p->off + 1 >= ncap) { log_error("%s: ncap exceeded\n", __func__); return 1; }; ndata = realloc(p->data, ncap * sizeof(const char *)); if (ndata) { p->data = ndata; p->cap = ncap; } else { log_error("realloc: %s\n", strerror(errno)); return 1; } } if (p->data == NULL) { log_error("%s: NULL data\n", __func__); return 1; } if (p->off + 1 >= p->cap) { log_error("%s: cap exceeded in p\n", __func__); return 1; } p->data[p->off] = x; p->data[++p->off] = NULL; return 0; } static int on_ppp_if_up(struct tunnel *tunnel) { log_info("Interface %s is UP.\n", tunnel->ppp_iface); if (tunnel->config->set_routes) { int ret; log_info("Setting new routes...\n"); ret = ipv4_set_tunnel_routes(tunnel); if (ret != 0) log_warn("Adding route table is incomplete. Please check route table.\n"); } if (tunnel->config->set_dns) { log_info("Adding VPN nameservers...\n"); ipv4_add_nameservers_to_resolv_conf(tunnel); } log_info("Tunnel is up and running.\n"); #if HAVE_SYSTEMD sd_notify(0, "READY=1"); #endif return 0; } static int on_ppp_if_down(struct tunnel *tunnel) { #if HAVE_SYSTEMD sd_notify(0, "STOPPING=1"); #endif log_info("Setting %s interface down.\n", tunnel->ppp_iface); if (tunnel->config->set_routes) { log_info("Restoring routes...\n"); ipv4_restore_routes(tunnel); } if (tunnel->config->set_dns) { log_info("Removing VPN nameservers...\n"); ipv4_del_nameservers_from_resolv_conf(tunnel); } return 0; } static int pppd_run(struct tunnel *tunnel) { pid_t pid; int amaster; int slave_stderr; #ifdef HAVE_STRUCT_TERMIOS struct termios termp = { .c_cflag = B9600, .c_cc[VTIME] = 0, .c_cc[VMIN] = 1 }; #endif static const char ppp_path[] = PPP_PATH; if (access(ppp_path, F_OK) != 0) { log_error("%s: %s.\n", ppp_path, strerror(errno)); return 1; } log_debug("ppp_path: %s\n", ppp_path); slave_stderr = dup(STDERR_FILENO); if (slave_stderr < 0) { log_error("slave stderr: %s\n", strerror(errno)); return 1; } #ifdef HAVE_STRUCT_TERMIOS pid = forkpty(&amaster, NULL, &termp, NULL); #else pid = forkpty(&amaster, NULL, NULL, NULL); #endif if (pid == 0) { // child process struct ofv_varr pppd_args = { 0, 0, NULL }; dup2(slave_stderr, STDERR_FILENO); if (close(slave_stderr)) log_warn("Could not close slave stderr (%s).\n", strerror(errno)); #if HAVE_USR_SBIN_PPP /* * assume there is a default configuration to start. * Support for taking options from the command line * e.g. the name of the configuration or options * to send interactively to ppp will be added later */ static const char *const v[] = { ppp_path, "-direct" }; for (unsigned int i = 0; i < ARRAY_SIZE(v); i++) if (ofv_append_varr(&pppd_args, v[i])) { free(pppd_args.data); return 1; } #endif #if HAVE_USR_SBIN_PPPD if (tunnel->config->pppd_call) { if (ofv_append_varr(&pppd_args, ppp_path)) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, "call")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, tunnel->config->pppd_call)) { free(pppd_args.data); return 1; } } else { static const char *const v[] = { ppp_path, /* * On systems such as 4.4BSD and NetBSD, any speed can * be specified. Other systems (e.g. Linux, SunOS) only * support the commonly-used baud rates. */ "230400", /* * Set the local and/or remote interface IP addresses. * Either one may be omitted. The IP addresses can be * specified with a host name or in decimal dot notation * (e.g. 150.234.56.78). The default local address is * the (first) IP address of the system (unless the * noipdefault option is given). The remote address will * be obtained from the peer if not specified in any * option. * Thus, in simple cases, this option is not required. * If a local and/or remote IP address is specified with * this option, pppd will not accept a different value * from the peer in the IPCP negotiation, unless the * ipcp-accept-local and/or ipcp-accept-remote options * are given, respectively. */ ":169.254.2.1", /* * Disables the default behaviour when no local * IP address is specified, which is to determine * (if possible) the local IP address from the hostname. * With this option, the peer will have to supply the * local IP address during IPCP negotiation (unless * it specified explicitly on the command line or in * an options file). */ "noipdefault", /* * With this option, pppd will accept the peer's idea * of our local IP address, even if the local IP address * was specified in an option. * * pppd < 2.5.0 requires this option to avoid this error: * Peer refused to agree to our IP address * This doesn't make sense to me. I feel it should be the * default because: * 1. we do not specify a local IP address, * 2. we use option noipdefault to specifically ask the * peer to supply the local IP address. */ "ipcp-accept-local", "noaccomp", "noauth", "default-asyncmap", "nopcomp", "receive-all", "nodefaultroute", "nodetach", "lcp-max-configure", "40", "mru", "1354" }; for (unsigned int i = 0; i < ARRAY_SIZE(v); i++) if (ofv_append_varr(&pppd_args, v[i])) { free(pppd_args.data); return 1; } } if (tunnel->config->pppd_accept_remote) /* * With this option, pppd will accept the peer's idea of its * (remote) IP address, even if the remote IP address was * specified in an option. * * pppd ≥ 2.5.0 requires this option to avoid this error: * Peer refused to agree to his IP address * This makes sense. * * Unfortunately, pppd < 2.5.0 does not like this option. * Again, this doesn't make sense to me. */ if (ofv_append_varr(&pppd_args, "ipcp-accept-remote")) { free(pppd_args.data); return 1; } if (tunnel->config->pppd_use_peerdns) if (ofv_append_varr(&pppd_args, "usepeerdns")) { free(pppd_args.data); return 1; } if (tunnel->config->pppd_log) { if (ofv_append_varr(&pppd_args, "debug")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, "logfile")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, tunnel->config->pppd_log)) { free(pppd_args.data); return 1; } } else { /* * pppd defaults to logging to fd=1, clobbering the * actual PPP data */ if (ofv_append_varr(&pppd_args, "logfd")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, "2")) { free(pppd_args.data); return 1; } } if (tunnel->config->pppd_plugin) { if (ofv_append_varr(&pppd_args, "plugin")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, tunnel->config->pppd_plugin)) { free(pppd_args.data); return 1; } } if (tunnel->config->pppd_ipparam) { if (ofv_append_varr(&pppd_args, "ipparam")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, tunnel->config->pppd_ipparam)) { free(pppd_args.data); return 1; } } if (tunnel->config->pppd_ifname) { if (ofv_append_varr(&pppd_args, "ifname")) { free(pppd_args.data); return 1; } if (ofv_append_varr(&pppd_args, tunnel->config->pppd_ifname)) { free(pppd_args.data); return 1; } } #endif #if HAVE_USR_SBIN_PPP if (tunnel->config->ppp_system) { if (ofv_append_varr(&pppd_args, tunnel->config->ppp_system)) { free(pppd_args.data); return 1; } } #endif if (close(tunnel->ssl_socket)) log_warn("Could not close TLS socket (%s).\n", strerror(errno)); tunnel->ssl_socket = -1; execv(pppd_args.data[0], (char *const *)pppd_args.data); free(pppd_args.data); fprintf(stderr, "execv: %s\n", strerror(errno)); _exit(EXIT_FAILURE); } else { if (close(slave_stderr)) log_error("Could not close slave stderr (%s).\n", strerror(errno)); if (pid == -1) { log_error("forkpty: %s\n", strerror(errno)); return 1; } } // Set non-blocking int flags = fcntl(amaster, F_GETFL, 0); if (flags == -1) flags = 0; if (fcntl(amaster, F_SETFL, flags | O_NONBLOCK) == -1) { log_error("fcntl: %s\n", strerror(errno)); return 1; } tunnel->pppd_pid = pid; tunnel->pppd_pty = amaster; return 0; } static const char * const ppp_message[] = { #if HAVE_USR_SBIN_PPPD // pppd(8) - https://ppp.samba.org/pppd.html "Has detached, or otherwise the connection was successfully established and terminated at the peer's request.", "An immediately fatal error of some kind occurred, such as an essential system call failing, or running out of virtual memory.", "An error was detected in processing the options given, such as two mutually exclusive options being used.", "Is not setuid-root and the invoking user is not root.", "The kernel does not support PPP, for example, the PPP kernel driver is not included or cannot be loaded.", "Terminated because it was sent a SIGINT, SIGTERM or SIGHUP signal.", "The serial port could not be locked.", "The serial port could not be opened.", "The connect script failed (returned a non-zero exit status).", "The command specified as the argument to the pty option could not be run.", "The PPP negotiation failed, that is, it didn't reach the point where at least one network protocol (e.g. IP) was running.", "The peer system failed (or refused) to authenticate itself.", "The link was established successfully and terminated because it was idle.", "The link was established successfully and terminated because the connect time limit was reached.", "Callback was negotiated and an incoming call should arrive shortly.", "The link was terminated because the peer is not responding to echo requests.", "The link was terminated by the modem hanging up.", "The PPP negotiation failed because serial loopback was detected.", "The init script failed (returned a non-zero exit status).", "We failed to authenticate ourselves to the peer." #endif #if HAVE_USR_SBIN_PPP // sysexits(3) - https://www.freebsd.org/cgi/man.cgi?query=sysexits // EX_NORMAL = EX_OK (0) "Successful exit.", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 1-9 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 10-19 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 20-29 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 30-39 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 40-49 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 50-59 NULL, NULL, NULL, NULL, // 60-63 // EX_USAGE (64) "The command was used incorrectly, e.g., with the wrong number of arguments, a bad flag, a bad syntax in a parameter, or whatever.", NULL, NULL, NULL, NULL, // 65-68 // EX_UNAVAILABLE (69) "A service is unavailable. This can occur if a support program or file does not exist.", // EX_ERRDEAD = EX_SOFTWARE (70) "An internal software error has been detected.", NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 71-77 // EX_CONFIG (78) "Something was found in an unconfigured or misconfigured state.", NULL, // 79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 80-89 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // 90-98 NULL // EX_TERMINATE (99), PPP internal pseudo-code #endif }; static int pppd_terminate(struct tunnel *tunnel) { if (close(tunnel->pppd_pty)) log_warn("Could not close pppd pty (%s).\n", strerror(errno)); log_debug("Waiting for %s to exit...\n", PPP_DAEMON); int status; /* * Errors outside of the PPP process are returned as negative integers. */ if (waitpid(tunnel->pppd_pid, &status, 0) == -1) { log_error("waitpid: %s\n", strerror(errno)); return -1; } /* * Errors in the PPP process are returned as positive integers. */ if (WIFEXITED(status)) { int exit_status = WEXITSTATUS(status); /* * PPP exit status codes are positive integers. The way we interpret * their value is not straightforward: * - in the case of "normal" exit, the PPP process may return 0, * but also a strictly positive integer such as 16 in the case of * pppd, * - in the case of failure, the PPP process will return a strictly * positive integer. * For now we process PPP exit status codes as follows: * - exit status codes synonym of success are logged and then * translated to 0 before they are returned to the calling function, * - other exit status codes are considered synonyms of failure and * returned to the calling function as is. */ log_debug("waitpid: %s exit status code %d\n", PPP_DAEMON, exit_status); if (exit_status >= ARRAY_SIZE(ppp_message)) { log_error("%s: Returned an unknown exit status code: %d\n", PPP_DAEMON, exit_status); } else { switch (exit_status) { /* * PPP exit status codes considered as success */ case 0: log_debug("%s: %s\n", PPP_DAEMON, ppp_message[exit_status]); break; #if HAVE_USR_SBIN_PPPD case 16: // emitted by Ctrl+C or "kill -15" if (get_sig_received() == SIGINT || get_sig_received() == SIGTERM) { log_info("%s: %s\n", PPP_DAEMON, ppp_message[exit_status]); exit_status = 0; break; } #endif /* * PPP exit status codes considered as failure */ default: if (ppp_message[exit_status]) log_error("%s: %s\n", PPP_DAEMON, ppp_message[exit_status]); else log_error("%s: Returned an unexpected exit status code: %d\n", PPP_DAEMON, exit_status); break; } } return exit_status; } else if (WIFSIGNALED(status)) { int signal_number = WTERMSIG(status); /* * For now we do not consider interruption of the PPP process by * a signal as a failure. Should we? */ log_debug("waitpid: %s terminated by signal %d\n", PPP_DAEMON, signal_number); log_error("%s: terminated by signal: %s\n", PPP_DAEMON, strsignal(signal_number)); } return 0; } int ppp_interface_is_up(struct tunnel *tunnel) { struct ifaddrs *ifap; log_debug("Got Address: %s\n", inet_ntoa(tunnel->ipv4.ip_addr)); if (getifaddrs(&ifap)) { log_error("getifaddrs: %s\n", strerror(errno)); return 0; } for (struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { if ( #if HAVE_USR_SBIN_PPPD ((tunnel->config->pppd_ifname && strstr(ifa->ifa_name, tunnel->config->pppd_ifname) != NULL) || strstr(ifa->ifa_name, "ppp") != NULL) && #endif #if HAVE_USR_SBIN_PPP strstr(ifa->ifa_name, "tun") != NULL && #endif ifa->ifa_flags & IFF_UP) { if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { struct in_addr if_ip_addr = cast_addr(ifa->ifa_addr)->sin_addr; log_debug("Interface Name: %s\n", ifa->ifa_name); log_debug("Interface Addr: %s\n", inet_ntoa(if_ip_addr)); if (tunnel->ipv4.ip_addr.s_addr == if_ip_addr.s_addr) { strncpy(tunnel->ppp_iface, ifa->ifa_name, ROUTE_IFACE_LEN - 1); freeifaddrs(ifap); return 1; } } } } freeifaddrs(ifap); return 0; } static int get_gateway_host_ip(struct tunnel *tunnel) { const struct addrinfo hints = { .ai_family = AF_INET }; struct addrinfo *result = NULL; int ret = getaddrinfo(tunnel->config->gateway_host, NULL, &hints, &result); if (ret) { if (ret == EAI_SYSTEM) log_error("getaddrinfo: %s\n", strerror(errno)); else log_error("getaddrinfo: %s\n", gai_strerror(ret)); return 1; } tunnel->config->gateway_ip = ((struct sockaddr_in *) result->ai_addr)->sin_addr; freeaddrinfo(result); setenv("VPN_GATEWAY", inet_ntoa(tunnel->config->gateway_ip), 0); return 0; } /* * Establish a regular TCP connection. */ static int tcp_connect(struct tunnel *tunnel) { int ret, handle; struct sockaddr_in server; char *env_proxy; const int iface_len = strnlen(tunnel->config->iface_name, IF_NAMESIZE); handle = socket(AF_INET, SOCK_STREAM, 0); if (handle == -1) { log_error("socket: %s\n", strerror(errno)); goto err_socket; } if (iface_len == IF_NAMESIZE) { log_error("socket: Too long iface name\n"); goto err_post_socket; } if (iface_len > 0) { #if HAVE_SO_BINDTODEVICE ret = setsockopt(handle, SOL_SOCKET, SO_BINDTODEVICE, tunnel->config->iface_name, iface_len); #else struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (strlcpy(ifr.ifr_name, tunnel->config->iface_name, IF_NAMESIZE) >= IF_NAMESIZE) { log_error("interface name too long\n"); goto err_post_socket; } ifr.ifr_addr.sa_family = AF_INET; if (ioctl(handle, SIOCGIFADDR, &ifr) == -1) { log_error("ioctl(%d,SIOCGIFADDR,\"%s\") failed\n", handle, ifr.ifr_name); goto err_post_socket; } ret = bind(handle, &ifr.ifr_addr, ifr.ifr_addr.sa_len); #endif if (ret) { log_error("socket: setting interface name failed with error: %d\n", errno); goto err_post_socket; } } env_proxy = getenv("https_proxy"); if (env_proxy == NULL) env_proxy = getenv("HTTPS_PROXY"); if (env_proxy == NULL) env_proxy = getenv("all_proxy"); if (env_proxy == NULL) env_proxy = getenv("ALL_PROXY"); if (env_proxy != NULL) { char *proxy_host, *proxy_port; // protect the original environment from modifications env_proxy = strdup(env_proxy); if (env_proxy == NULL) { log_error("strdup: %s\n", strerror(errno)); goto err_strdup; } // get rid of a trailing slash if (*env_proxy && env_proxy[strlen(env_proxy) - 1] == '/') env_proxy[strlen(env_proxy) - 1] = '\0'; // get rid of a http(s):// prefix in env_proxy proxy_host = strstr(env_proxy, "://"); if (proxy_host == NULL) proxy_host = env_proxy; else proxy_host += 3; // split host and port proxy_port = strchr(proxy_host, ':'); if (proxy_port != NULL) { proxy_port[0] = '\0'; proxy_port++; server.sin_port = htons(strtoul(proxy_port, NULL, 10)); } else { server.sin_port = htons(tunnel->config->gateway_port); } // get rid of a trailing slash if (*proxy_host && proxy_host[strlen(proxy_host) - 1] == '/') proxy_host[strlen(proxy_host) - 1] = '\0'; log_debug("proxy_host: %s\n", proxy_host); log_debug("proxy_port: %s\n", proxy_port); server.sin_addr.s_addr = inet_addr(proxy_host); // if host is given as a FQDN we have to do a DNS lookup if (server.sin_addr.s_addr == INADDR_NONE) { const struct addrinfo hints = { .ai_family = AF_INET }; struct addrinfo *result = NULL; ret = getaddrinfo(proxy_host, NULL, &hints, &result); if (ret) { if (ret == EAI_SYSTEM) log_error("getaddrinfo: %s\n", strerror(errno)); else log_error("getaddrinfo: %s\n", gai_strerror(ret)); goto err_connect; } server.sin_addr = ((struct sockaddr_in *) result->ai_addr)->sin_addr; freeaddrinfo(result); } } else { server.sin_port = htons(tunnel->config->gateway_port); server.sin_addr = tunnel->config->gateway_ip; } log_debug("server_addr: %s\n", inet_ntoa(server.sin_addr)); log_debug("server_port: %u\n", ntohs(server.sin_port)); server.sin_family = AF_INET; memset(&(server.sin_zero), 0, sizeof(server.sin_zero)); log_debug("gateway_ip: %s\n", inet_ntoa(tunnel->config->gateway_ip)); log_debug("gateway_port: %u\n", tunnel->config->gateway_port); ret = connect(handle, (struct sockaddr *) &server, sizeof(server)); if (ret) { log_error("connect: %s\n", strerror(errno)); goto err_connect; } if (env_proxy != NULL) { char request[128]; // https://tools.ietf.org/html/rfc7231#section-4.3.6 sprintf(request, "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\n\r\n", inet_ntoa(tunnel->config->gateway_ip), tunnel->config->gateway_port, inet_ntoa(tunnel->config->gateway_ip), tunnel->config->gateway_port); ssize_t bytes_written = write(handle, request, strlen(request)); if (bytes_written != strlen(request)) { log_error("write error while talking to proxy: %s\n", strerror(errno)); goto err_connect; } // wait for a "200 OK" reply from the proxy, // be careful not to fetch too many bytes at once const char *response = NULL; memset(&(request), 0, sizeof(request)); for (size_t j = 0; response == NULL; j++) { if (j >= ARRAY_SIZE(request) - 1) { log_error("Proxy response is unexpectedly large and cannot fit in the %lu-bytes buffer.\n", ARRAY_SIZE(request)); goto err_proxy_response; } ssize_t bytes_read = read(handle, &(request[j]), 1); static const char HTTP_STATUS_200[] = "200"; // we have reached the end of the data sent by the proxy // and have not seen HTTP status code 200 if (bytes_read < 1) { log_error("Proxy response does not contain \"%s\" as expected.\n", HTTP_STATUS_200); goto err_proxy_response; } // detect "200" response = strstr(request, HTTP_STATUS_200); // detect end-of-line after "200" if (response != NULL) { /* * RFC 2616 states in section 2.2 Basic Rules: * CR = <US-ASCII CR, carriage return (13)> * LF = <US-ASCII LF, linefeed (10)> * HTTP/1.1 defines the sequence CR LF as the * end-of-line marker for all protocol elements * except the entity-body (see appendix 19.3 * for tolerant applications). * CRLF = CR LF * * RFC 2616 states in section 19.3 Tolerant Applications: * The line terminator for message-header fields * is the sequence CRLF. However, we recommend * that applications, when parsing such headers, * recognize a single LF as a line terminator * and ignore the leading CR. */ static const char *const HTTP_EOL[] = { "\r\n\r\n", "\n\n" }; const char *eol = NULL; for (size_t i = 0; (i < ARRAY_SIZE(HTTP_EOL)) && (eol == NULL); i++) eol = strstr(response, HTTP_EOL[i]); response = eol; } } free(env_proxy); // release memory allocated by strdup() } return handle; err_proxy_response: err_connect: free(env_proxy); // release memory allocated by strdup() err_strdup: err_post_socket: if (close(handle)) log_warn("Could not close socket (%s).\n", strerror(errno)); err_socket: return -1; } static int ssl_verify_cert(struct tunnel *tunnel) { int ret = -1; unsigned char digest[SHA256LEN]; unsigned int len; struct x509_digest *elem; char digest_str[SHA256STRLEN], *subject, *issuer; X509_NAME *subj; char *saveptr = NULL; SSL_set_verify(tunnel->ssl_handle, SSL_VERIFY_PEER, NULL); X509 *cert = SSL_get_peer_certificate(tunnel->ssl_handle); if (cert == NULL) { log_error("Unable to get gateway certificate.\n"); return 1; } // Validate certificate: // 1. Validate using local PKI // 2. Compare against gateway_host and correctly check return value // to fix prior incorrect use of X509_check_host if (SSL_get_verify_result(tunnel->ssl_handle) == X509_V_OK && X509_check_host(cert, tunnel->config->gateway_host, 0, 0, NULL) == 1) { log_debug("Gateway certificate validation succeeded.\n"); ret = 0; goto free_cert; } log_debug("Gateway certificate validation failed.\n"); // If validation failed, check if cert is in the white list if (X509_digest(cert, EVP_sha256(), digest, &len) <= 0 || len != SHA256LEN) { log_error("Could not compute certificate sha256 digest.\n"); goto free_cert; } // Encode digest in base16 for (int i = 0; i < SHA256LEN; i++) sprintf(&digest_str[2 * i], "%02x", digest[i]); digest_str[SHA256STRLEN - 1] = '\0'; // Is it in whitelist? for (elem = tunnel->config->cert_whitelist; elem != NULL; elem = elem->next) if (memcmp(digest_str, elem->data, SHA256STRLEN - 1) == 0) break; if (elem != NULL) { // break before end of loop log_debug("Gateway certificate digest found in white list.\n"); ret = 0; goto free_cert; } subj = X509_get_subject_name(cert); log_error("Gateway certificate validation failed, and the certificate digest is not in the local whitelist. If you trust it, rerun with:\n"); log_error(" --trusted-cert %s\n", digest_str); log_error("or add this line to your configuration file:\n"); log_error(" trusted-cert = %s\n", digest_str); log_error("Gateway certificate:\n"); log_error(" subject:\n"); subject = X509_NAME_oneline(subj, NULL, 0); if (subject) { for (char *line = strtok_r(subject, "/", &saveptr); line != NULL; line = strtok_r(NULL, "/", &saveptr)) log_error(" %s\n", line); free(subject); } log_error(" issuer:\n"); issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); if (issuer) { for (char *line = strtok_r(issuer, "/", &saveptr); line != NULL; line = strtok_r(NULL, "/", &saveptr)) log_error(" %s\n", line); free(issuer); } log_error(" sha256 digest:\n"); log_error(" %s\n", digest_str); free_cert: X509_free(cert); return ret; } /* * Destroy and free the TLS connection to the gateway. */ static void ssl_disconnect(struct tunnel *tunnel) { if (!tunnel->ssl_handle) return; SSL_shutdown(tunnel->ssl_handle); SSL_free(tunnel->ssl_handle); tunnel->ssl_handle = NULL; SSL_CTX_free(tunnel->ssl_context); tunnel->ssl_context = NULL; if (close(tunnel->ssl_socket)) log_warn("Could not close TLS socket (%s).\n", strerror(errno)); tunnel->ssl_socket = -1; } /* * Query for the pass phrase used for encrypted PEM structures * (normally only private keys). */ static int pem_passphrase_cb(char *buf, int size, int rwflag, void *u) { struct vpn_config *cfg = (struct vpn_config *)u; /* We expect to only read PEM pass phrases, not write them. */ if (rwflag == 0) { if (!cfg->pem_passphrase_set) { if (size > PEM_PASSPHRASE_SIZE) { read_password(NULL, NULL, "Enter PEM pass phrase: ", cfg->pem_passphrase, PEM_PASSPHRASE_SIZE + 1); cfg->pem_passphrase_set = 1; } else { log_error("Buffer too small for PEM pass phrase: %d.\n", size); } } if (cfg->pem_passphrase_set) { assert(strlen(cfg->pem_passphrase) < size); strncpy(buf, cfg->pem_passphrase, size); buf[size - 1] = '\0'; return strlen(buf); } } else { log_error("We refuse to write PEM pass phrases!\n"); } return -1; } /* * Connects to the gateway and initiate a TLS session. */ int ssl_connect(struct tunnel *tunnel) { ssl_disconnect(tunnel); tunnel->ssl_socket = tcp_connect(tunnel); if (tunnel->ssl_socket == -1) goto err_tcp_connect; // https://wiki.openssl.org/index.php/Library_Initialization #if OPENSSL_VERSION_NUMBER < 0x10100000L // Register the error strings for libcrypto & libssl SSL_load_error_strings(); // Register the available ciphers and digests SSL_library_init(); tunnel->ssl_context = SSL_CTX_new(SSLv23_client_method()); #else tunnel->ssl_context = SSL_CTX_new(TLS_client_method()); #endif if (tunnel->ssl_context == NULL) { log_error("SSL_CTX_new: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_socket; } /* Load the OS default CA files */ if (!SSL_CTX_set_default_verify_paths(tunnel->ssl_context)) log_error("Could not load OS OpenSSL files.\n"); if (tunnel->config->ca_file) { if (!SSL_CTX_load_verify_locations(tunnel->ssl_context, tunnel->config->ca_file, NULL)) { log_error("SSL_CTX_load_verify_locations: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } /* Disable vulnerable TLS protocols and ciphers by default*/ if (!tunnel->config->cipher_list) { if (!tunnel->config->insecure_ssl) { const char *cipher_list; if (tunnel->config->seclevel_1) cipher_list = "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4@SECLEVEL=1"; else cipher_list = "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"; tunnel->config->cipher_list = strdup(cipher_list); } else if (tunnel->config->seclevel_1) { static const char cipher_list[] = "DEFAULT@SECLEVEL=1"; tunnel->config->cipher_list = strdup(cipher_list); } } /* Set the password callback for PEM certificates with encryption. */ SSL_CTX_set_default_passwd_cb(tunnel->ssl_context, &pem_passphrase_cb); SSL_CTX_set_default_passwd_cb_userdata(tunnel->ssl_context, tunnel->config); if (tunnel->config->cipher_list) { log_debug("Setting cipher list to: %s\n", tunnel->config->cipher_list); if (!SSL_CTX_set_cipher_list(tunnel->ssl_context, tunnel->config->cipher_list)) { log_error("SSL_CTX_set_cipher_list failed: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } #if OPENSSL_VERSION_NUMBER >= 0x10100000L if (tunnel->config->min_tls > 0) { log_debug("Setting minimum protocol version to: 0x%x.\n", tunnel->config->min_tls); if (!SSL_CTX_set_min_proto_version(tunnel->ssl_context, tunnel->config->min_tls)) { log_error("Cannot set minimum protocol version (%s).\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } #else if (!tunnel->config->insecure_ssl || tunnel->config->min_tls > 0) { long sslctxopt = 0; long checkopt; if (!tunnel->config->insecure_ssl) sslctxopt |= SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; #ifdef TLS1_VERSION if (tunnel->config->min_tls > TLS1_VERSION) sslctxopt |= SSL_OP_NO_TLSv1; #endif #ifdef TLS1_1_VERSION if (tunnel->config->min_tls > TLS1_1_VERSION) sslctxopt |= SSL_OP_NO_TLSv1_1; #endif #ifdef TLS1_2_VERSION if (tunnel->config->min_tls > TLS1_2_VERSION) sslctxopt |= SSL_OP_NO_TLSv1_2; #endif checkopt = SSL_CTX_set_options(tunnel->ssl_context, sslctxopt); if ((checkopt & sslctxopt) != sslctxopt) { log_error("SSL_CTX_set_options didn't set opt: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } #endif #ifndef OPENSSL_NO_ENGINE /* Use PKCS11 engine for PIV if user-cert config starts with pkcs11 URI: */ if (tunnel->config->use_engine > 0) { ENGINE *e; ENGINE_load_builtin_engines(); e = ENGINE_by_id("pkcs11"); if (!e) { log_error("Could not load pkcs11 Engine: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } if (!ENGINE_init(e)) { log_error("Could not init pkcs11 Engine: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); ENGINE_free(e); goto err_ssl_context; } if (!ENGINE_set_default_RSA(e)) abort(); ENGINE_finish(e); ENGINE_free(e); struct token parms; parms.uri = tunnel->config->user_cert; parms.cert = NULL; if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &parms, NULL, 1)) { log_error("PKCS11 ENGINE_ctrl_cmd: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } if (!SSL_CTX_use_certificate(tunnel->ssl_context, parms.cert)) { log_error("PKCS11 SSL_CTX_use_certificate: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } EVP_PKEY * privkey = ENGINE_load_private_key( e, parms.uri, UI_OpenSSL(), NULL); if (!privkey) { log_error("PKCS11 ENGINE_load_private_key: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } if (!SSL_CTX_use_PrivateKey(tunnel->ssl_context, privkey)) { log_error("PKCS11 SSL_CTX_use_PrivateKey_file: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } if (!SSL_CTX_check_private_key(tunnel->ssl_context)) { log_error("PKCS11 SSL_CTX_check_private_key: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } else { /* end PKCS11 engine */ #endif if (tunnel->config->user_cert) { if (!SSL_CTX_use_certificate_chain_file( tunnel->ssl_context, tunnel->config->user_cert)) { log_error("SSL_CTX_use_certificate_chain_file: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } if (tunnel->config->user_key) { if (!SSL_CTX_use_PrivateKey_file(tunnel->ssl_context, tunnel->config->user_key, SSL_FILETYPE_PEM)) { log_error("SSL_CTX_use_PrivateKey_file: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } if (tunnel->config->user_cert && tunnel->config->user_key) { if (!SSL_CTX_check_private_key(tunnel->ssl_context)) { log_error("SSL_CTX_check_private_key: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } } #ifndef OPENSSL_NO_ENGINE } #endif tunnel->ssl_handle = SSL_new(tunnel->ssl_context); if (tunnel->ssl_handle == NULL) { log_error("SSL_new: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_context; } if (!SSL_set_fd(tunnel->ssl_handle, tunnel->ssl_socket)) { log_error("SSL_set_fd: %s\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_handle; } SSL_set_mode(tunnel->ssl_handle, SSL_MODE_AUTO_RETRY); // Set SNI for the session const char *sni = tunnel->config->sni[0] ? tunnel->config->sni : tunnel->config->gateway_host; if (SSL_set_tlsext_host_name(tunnel->ssl_handle, sni) != 1) log_warn("SSL_set_tlsext_host_name('%s'): %s\n", sni, ERR_error_string(ERR_peek_last_error(), NULL)); else log_debug("Set SNI for TLS handshake: %s\n", sni); // Initiate SSL handshake if (SSL_connect(tunnel->ssl_handle) != 1) { log_error("SSL_connect: %s\n" "You might want to try --insecure-ssl or specify a different --cipher-list\n", ERR_error_string(ERR_peek_last_error(), NULL)); goto err_ssl_handle; } SSL_set_mode(tunnel->ssl_handle, SSL_MODE_AUTO_RETRY); if (ssl_verify_cert(tunnel)) goto err_ssl_handle; // Disable SIGPIPE (occurs when trying to write to an already-closed // socket). signal(SIGPIPE, SIG_IGN); return 0; err_ssl_handle: SSL_shutdown(tunnel->ssl_handle); SSL_free(tunnel->ssl_handle); tunnel->ssl_handle = NULL; err_ssl_context: SSL_CTX_free(tunnel->ssl_context); tunnel->ssl_context = NULL; err_ssl_socket: if (close(tunnel->ssl_socket)) log_warn("Could not close TLS socket (%s).\n", strerror(errno)); tunnel->ssl_socket = -1; err_tcp_connect: return 1; } int run_tunnel(struct vpn_config *config) { int ret; struct tunnel tunnel = { .config = config, .state = STATE_DOWN, .ssl_socket = -1, .ssl_context = NULL, .ssl_handle = NULL, .ipv4.ns1_addr.s_addr = 0, .ipv4.ns2_addr.s_addr = 0, .ipv4.dns_suffix = NULL, .on_ppp_if_up = on_ppp_if_up, .on_ppp_if_down = on_ppp_if_down }; // Step 0: get gateway host IP log_debug("Resolving gateway host ip\n"); ret = get_gateway_host_ip(&tunnel); if (ret) goto err_tunnel; // Step 1: open a TLS connection to the gateway log_debug("Establishing TLS connection\n"); ret = ssl_connect(&tunnel); if (ret) goto err_tunnel; log_info("Connected to gateway.\n"); // Step 2: connect to the HTTP interface and authenticate to get a // cookie if (config->cookie) ret = auth_set_cookie(&tunnel, config->cookie); else ret = auth_log_in(&tunnel); if (ret != 1) { log_error("Could not authenticate to gateway. Please check the password, client certificate, etc.\n"); log_debug("%s (%d)\n", err_http_str(ret), ret); ret = 1; goto err_tunnel; } log_info("Authenticated.\n"); log_debug("Cookie: %s\n", tunnel.cookie); ret = auth_request_vpn_allocation(&tunnel); if (ret != 1) { log_error("VPN allocation request failed (%s).\n", err_http_str(ret)); ret = 1; goto err_tunnel; } log_info("Remote gateway has allocated a VPN.\n"); ret = ssl_connect(&tunnel); if (ret) goto err_tunnel; // Step 3: get configuration log_debug("Retrieving configuration\n"); ret = auth_get_config(&tunnel); if (ret != 1) { log_error("Could not get VPN configuration (%s).\n", err_http_str(ret)); ret = 1; goto err_tunnel; } // Step 4: run a pppd process log_debug("Establishing the tunnel\n"); ret = pppd_run(&tunnel); if (ret) goto err_tunnel; // Step 5: ask gateway to start tunneling log_debug("Switch to tunneling mode\n"); ret = http_send(&tunnel, "GET /remote/sslvpn-tunnel HTTP/1.1\r\n" "Host: sslvpn\r\n" "Cookie: %s\r\n\r\n", tunnel.cookie); if (ret != 1) { log_error("Could not start tunnel (%s).\n", err_http_str(ret)); goto err_start_tunnel; } tunnel.state = STATE_CONNECTING; // Step 6: perform io between pppd and the gateway, while tunnel is up log_debug("Starting IO through the tunnel\n"); io_loop(&tunnel); log_debug("Disconnecting\n"); if (tunnel.state == STATE_UP) if (tunnel.on_ppp_if_down != NULL) tunnel.on_ppp_if_down(&tunnel); tunnel.state = STATE_DISCONNECTING; err_start_tunnel: ret = pppd_terminate(&tunnel); log_info("Terminated %s.\n", PPP_DAEMON); err_tunnel: log_info("Closed connection to gateway.\n"); tunnel.state = STATE_DOWN; if (ssl_connect(&tunnel)) { log_info("Could not log out.\n"); } else { auth_log_out(&tunnel); log_info("Logged out.\n"); } // explicitly free the buffer allocated for split routes of the ipv4 configuration if (tunnel.ipv4.split_rt != NULL) { free(tunnel.ipv4.split_rt); tunnel.ipv4.split_rt = NULL; } return ret; } �������������������������������������������������������������������openfortivpn-1.23.1/src/http_server.c���������������������������������������������������������������0000664�0001750�0001750�00000023222�14753334500�017542� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2025 Rainer Keller * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "http_server.h" #include "config.h" #include "http.h" #include "log.h" #include "tunnel.h" #include <netinet/in.h> #include <netinet/tcp.h> #include <sys/select.h> #include <unistd.h> #include <ctype.h> #include <string.h> static void print_url(const struct vpn_config *cfg) { char *encoded_realm = NULL; char realm[] = "&realm="; char *url = NULL; // Desired string is // https://company.com:port/remote/saml/start?redirect=1(&realm=<str>) // with the realm being optional static const char uri_pattern[] = "https://%s:%d/remote/saml/start?redirect=1%s%s"; if (cfg->realm[0] != '\0') { // url_encode requires three times the size encoded_realm = malloc(strlen(cfg->realm) * 3 + 1); if (!encoded_realm) { log_error("malloc: %s\n", strerror(errno)); goto end; } url_encode(encoded_realm, cfg->realm); } else { encoded_realm = malloc(1); if (!encoded_realm) { log_error("malloc: %s\n", strerror(errno)); goto end; } encoded_realm[0] = 0; realm[0] = 0; // Make realm appear empty when printing as string } int required_size = 1 + snprintf(NULL, 0, uri_pattern, cfg->gateway_host, cfg->gateway_port, realm, encoded_realm); url = malloc(required_size); if (!url) { log_error("malloc: %s\n", strerror(errno)); goto end; } snprintf(url, required_size, uri_pattern, cfg->gateway_host, cfg->gateway_port, realm, encoded_realm); log_info("Authenticate at '%s'\n", url); end: free(url); free(encoded_realm); } // Convenience function to send a response with a user readable status message and the // request URL shown for debug purposes. The response is shown in the user's browser // after being redirected from the Fortinet Server. static void send_status_response(int socket, const char *userMessage) { static const char replyHeader[] = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Content-Length: %lu\r\n" "Connection: close\r\n" "\r\n"; static const char replyBody[] = "<!DOCTYPE html>\r\n" "<html><body>\r\n" "%s" // User readable message "</body></html>\r\n"; char *replyBodyBuffer = NULL; char *replyHeaderBuffer = NULL; size_t replyBodySize = snprintf(NULL, 0, replyBody, userMessage) + 1; replyBodyBuffer = malloc(replyBodySize); if (!replyBodyBuffer) { log_error("malloc: %s\n", strerror(errno)); goto end; } snprintf(replyBodyBuffer, replyBodySize, replyBody, userMessage); int replyHeaderSize = snprintf(NULL, 0, replyHeader, replyBodySize) + 1; replyHeaderBuffer = malloc(replyHeaderSize); if (!replyHeaderBuffer) { log_error("malloc: %s\n", strerror(errno)); goto end; } snprintf(replyHeaderBuffer, replyHeaderSize, replyHeader, strlen(replyBodyBuffer)); // Using two separate writes here to make the code not more complicated assembling // the buffers. if (write(socket, replyHeaderBuffer, strlen(replyHeaderBuffer)) < 0) log_warn("Failed to write: %s\n", strerror(errno)); if (write(socket, replyBodyBuffer, strlen(replyBodyBuffer)) < 0) log_warn("Failed to write: %s\n", strerror(errno)); end: free(replyBodyBuffer); free(replyHeaderBuffer); } static int process_request(int new_socket, char *id) { log_info("Processing HTTP SAML request\n"); int flag = 1; if (setsockopt(new_socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag))) { log_error("Failed to set socket options: %s\n", strerror(errno)); return -1; } // Read the request char request[1024]; // -1 : Save one place for termination in case the request is about to // fill the entire buffer. ssize_t read_result = read(new_socket, request, sizeof(request) - 1); // Check for '=id' in the response // If the received request from the server is larger than the buffer, // the result will not be null-terminated causing strlen to behave wrong. if (read_result < 0) { log_error("Bad request: %s\n", strerror(errno)); send_status_response(new_socket, "Invalid redirect response from Fortinet server. VPN could not be established."); return -1; } // Safety Null-terminate request[sizeof(request) - 1] = 0; request[read_result] = 0; static const char request_head[] = "GET /?id="; if (strncmp(request, request_head, strlen(request_head)) != 0) { log_error("Bad request\n"); send_status_response(new_socket, "Invalid redirect response from Fortinet server. VPN could not be established."); return -1; } // Extract the id static const char token_delimiter[] = " &\r\n"; // Use next_token because strsep does modify the input argument // and we don't want to loose our request pointer. char *next_token = request + strlen(request_head); char *id_start = strsep(&next_token, token_delimiter); if (next_token == NULL) { // In case not found, next_token was set to NULL // This should be invalid because we expect \r\n // at the end of the GET request line log_error("Bad request format\n"); send_status_response(new_socket, "Invalid formatting of Fortinet server redirect response. VPN could not be established."); return -1; } // strsep inserted a NULL at the location of the delimiter. int id_length = strlen(id_start); if (id_length == 0 || id_length >= MAX_SAML_SESSION_ID_LENGTH) { log_error("Bad request id\n"); send_status_response(new_socket, "Invalid SAML session id received from Fortinet server. VPN could not be established."); return -1; } strncpy(id, id_start, MAX_SAML_SESSION_ID_LENGTH); id[MAX_SAML_SESSION_ID_LENGTH] = 0; // Arrays in the structs are one byte extra for (int i = 0; i < id_length; i++) { if (isalnum(id[i]) || id[i] == '-') continue; log_error("Invalid id format\n"); send_status_response(new_socket, "Invalid SAML session id received from Fortinet server. VPN could not be established."); return -1; } send_status_response(new_socket, "SAML session id received from Fortinet server. VPN will be established...<br>\r\n" "You may close this browser tab now.<br>\r\n" "<script>\r\n" "window.setTimeout(() => { window.close(); }, 5000);\r\n" "document.write(\"<br>This window will close automatically in 5 seconds.\");\r\n" "</script>\r\n"); return 0; } /** * Run a http server to listen for SAML login requests * * @return 0 in case of success * < 0 in case of error */ int wait_for_http_request(struct vpn_config *config) { int server_fd, new_socket; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); // Creating socket file descriptor server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { log_error("Failed to create socket: %s\n", strerror(errno)); return -1; } // Forcefully attaching socket to the port if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { close(server_fd); log_error("Failed to set socket options: %s\n", strerror(errno)); return -1; } address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); address.sin_port = htons(config->saml_port); // Forcefully attaching socket to the port if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { close(server_fd); log_error("Failed to bind socket to port %u\n", config->saml_port); return -1; } if (listen(server_fd, 3) < 0) { close(server_fd); log_error("Failed to listen on socket: %s\n", strerror(errno)); return -1; } int max_tries = 5; fd_set readfds; struct timeval tv; log_info("Listening for SAML login on port %u\n", config->saml_port); print_url(config); while (max_tries > 0) { --max_tries; FD_ZERO(&readfds); FD_SET(server_fd, &readfds); // Wait up to ten seconds tv.tv_sec = 10; tv.tv_usec = 0; int retval = select(server_fd + 1, &readfds, NULL, NULL, &tv); if (retval == -1) { log_error("Failed to wait for connection: %s\n", strerror(errno)); break; } else if (retval > 0) { log_debug("Incoming HTTP connection\n"); new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen); if (new_socket < 0) { log_error("Failed to accept connection: %s\n", strerror(errno)); continue; } } else { log_debug("Timeout listening for incoming HTTP connection reached\n"); continue; } int result = process_request(new_socket, config->saml_session_id); close(new_socket); if (result == 0) break; log_warn("Failed to process request\n"); } close(server_fd); if (max_tries == 0 && strlen(config->saml_session_id) == 0) { log_error("Finally failed to retrieve SAML authentication token\n"); return -1; } return 0; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������openfortivpn-1.23.1/src/main.c����������������������������������������������������������������������0000664�0001750�0001750�00000065754�14753334500�016141� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include "config.h" #include "tunnel.h" #include "userinput.h" #include "log.h" #include "http_server.h" #include <openssl/ssl.h> #include <unistd.h> #include <getopt.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #if HAVE_USR_SBIN_PPPD && HAVE_USR_SBIN_PPP #error "Both HAVE_USR_SBIN_PPPD and HAVE_USR_SBIN_PPP have been defined." #elif HAVE_USR_SBIN_PPPD #define PPPD_USAGE \ " [--pppd-use-peerdns=<0|1>] [--pppd-log=<file>]\n" \ " [--pppd-ifname=<string>] [--pppd-ipparam=<string>]\n" \ " [--pppd-call=<name>] [--pppd-plugin=<file>]\n" \ " [--pppd-accept-remote=<0|1>]\n" #define PPPD_HELP \ " --pppd-use-peerdns=[01] Whether to ask peer ppp server for DNS server\n" \ " addresses and make pppd rewrite /etc/resolv.conf.\n" \ " --pppd-no-peerdns Same as --pppd-use-peerdns=0. pppd will not\n" \ " modify DNS resolution then.\n" \ " --pppd-log=<file> Set pppd in debug mode and save its logs into\n" \ " <file>.\n" \ " --pppd-plugin=<file> Use specified pppd plugin instead of configuring\n" \ " resolver and routes directly.\n" \ " --pppd-ifname=<string> Set the pppd interface name, if supported by pppd.\n" \ " --pppd-ipparam=<string> Provides an extra parameter to the ip-up, ip-pre-up\n" \ " and ip-down scripts. See man (8) pppd.\n" \ " --pppd-call=<name> Move most pppd options from pppd cmdline to\n" \ " /etc/ppp/peers/<name> and invoke pppd with\n" \ " 'call <name>'.\n" \ " --pppd-accept-remote=[01] Whether to invoke pppd with 'ipcp-accept-remote'.\n" \ " Disable for pppd < 2.5.0.\n" #elif HAVE_USR_SBIN_PPP #define PPPD_USAGE \ " [--ppp-system=<system>]\n" #define PPPD_HELP \ " --ppp-system=<system> Connect to the specified system as defined in\n" \ " /etc/ppp/ppp.conf.\n" #else #error "Neither HAVE_USR_SBIN_PPPD nor HAVE_USR_SBIN_PPP have been defined." #endif #if HAVE_RESOLVCONF #define RESOLVCONF_USAGE \ "[--use-resolvconf=<0|1>] " #define RESOLVCONF_HELP \ " --use-resolvconf=[01] If possible use resolvconf to update /etc/resolv.conf.\n" #else #define RESOLVCONF_USAGE "" #define RESOLVCONF_HELP "" #endif #define usage \ "Usage: openfortivpn [<host>[:<port>]] [-u <user>] [-p <pass>]\n" \ " [--cookie=<cookie>] [--cookie-on-stdin] [--saml-login]\n" \ " [--otp=<otp>] [--otp-delay=<delay>] [--otp-prompt=<prompt>]\n" \ " [--pinentry=<program>] [--realm=<realm>]\n" \ " [--ifname=<ifname>] [--set-routes=<0|1>]\n" \ " [--half-internet-routes=<0|1>] [--set-dns=<0|1>]\n" \ PPPD_USAGE \ " " RESOLVCONF_USAGE "[--ca-file=<file>]\n" \ " [--user-cert=<file>] [--user-key=<file>]\n" \ " [--use-syslog] [--trusted-cert=<digest>]\n" \ " [--persistent=<interval>] [-c <file>] [-v|-q]\n" \ " openfortivpn --help\n" \ " openfortivpn --version\n" \ "\n" #define summary \ "Client for PPP+TLS VPN tunnel services.\n" \ "openfortivpn connects to a VPN by setting up a tunnel to the gateway at\n" \ "<host>:<port>. It spawns a pppd process and operates the communication between\n" \ "the gateway and this process.\n" \ "\n" #ifdef TLS1_3_VERSION #define help_cipher_list " Applies to TLS v1.2 or\n" \ " lower only, not to be used with TLS v1.3 ciphers." #define help_seclevel_1 " Applies to TLS v1.2 or lower only." #else #define help_cipher_list "" #define help_seclevel_1 "" #endif #define help_options_part1 \ "Options:\n" \ " -h --help Show this help message and exit.\n" \ " --version Show version and exit.\n" \ " -c <file>, --config=<file> Specify a custom configuration file (default:\n" \ " " SYSCONFDIR "/openfortivpn/config).\n" \ " -u <user>, --username=<user> VPN account username.\n" \ " -p <pass>, --password=<pass> VPN account password.\n" \ " --cookie=<cookie> A valid session cookie (SVPNCOOKIE).\n" \ " --cookie-on-stdin Read the cookie (SVPNCOOKIE) from standard input.\n" \ " --saml-login[=port] Run a http server to handle SAML login requests\n" \ " -o <otp>, --otp=<otp> One-Time-Password.\n" \ " --otp-prompt=<prompt> Search for the OTP prompt starting with this string.\n" \ " --otp-delay=<delay> Wait <delay> seconds before sending the OTP.\n" \ " --no-ftm-push Do not use FTM push if the server provides the option.\n" \ " --pinentry=<program> Use the program to supply a secret instead of asking for it.\n" \ " --realm=<realm> Use specified authentication realm.\n" \ " --ifname=<interface> Bind to interface.\n" \ " --set-routes=[01] Set if openfortivpn should configure routes\n" \ " when tunnel is up.\n" \ " --sni=<sni> Specify a different server name (SNI) than the host argument\n" \ " during TLS handshake.\n" \ " --no-routes Do not configure routes, same as --set-routes=0.\n" \ " --half-internet-routes=[01] Add two 0.0.0.0/1 and 128.0.0.0/1 routes with higher\n" \ " priority instead of replacing the default route.\n" \ " --set-dns=[01] Set if openfortivpn should add DNS name servers\n" \ " and domain search list in /etc/resolv.conf.\n" \ " If installed resolvconf is used for the update.\n" \ " --no-dns Do not reconfigure DNS, same as --set-dns=0.\n" \ " --ca-file=<file> Use specified PEM-encoded certificate bundle\n" \ " instead of system-wide store to verify the gateway\n" \ " certificate.\n" \ " --user-cert=<file> Use specified PEM-encoded certificate if the server\n" \ " requires authentication with a certificate.\n" \ " --user-cert=pkcs11: Use smartcard. Takes also partial or full PKCS11-URI.\n" \ " --user-key=<file> Use specified PEM-encoded key if the server requires\n" \ " authentication with a certificate.\n" \ " --pem-passphrase=<pass> Pass phrase for the PEM-encoded key.\n" \ " --use-syslog Log to syslog instead of terminal.\n" \ " --trusted-cert=<digest> Trust a given gateway. If classical TLS\n" \ " certificate validation fails, the gateway\n" \ " certificate will be matched against this value.\n" \ " <digest> is the X509 certificate's sha256 sum.\n" \ " This option can be used multiple times to trust\n" \ " several certificates.\n" #define help_options_part2 \ " --insecure-ssl Do not disable insecure TLS protocols/ciphers.\n" \ " Also enable TLS v1.0 if applicable.\n" \ " If your server requires a specific cipher or protocol,\n" \ " consider using --cipher-list and/or --min-tls instead.\n" \ " --cipher-list=<ciphers> OpenSSL ciphers to use. If default does not work\n" \ " you can try with the cipher suggested in the output\n" \ " of 'openssl s_client -connect <host:port>'\n" \ " (e.g. AES256-GCM-SHA384)." help_cipher_list "\n" \ " --min-tls Use minimum TLS version instead of system default.\n" \ " Valid values are 1.0, 1.1, 1.2, 1.3.\n" \ " --seclevel-1 If --cipher-list is not specified, add @SECLEVEL=1 to\n" \ " (compiled in) list of ciphers. This lowers limits on\n" \ " dh key." help_seclevel_1 "\n" \ " --persistent=<interval> Run the vpn persistently in a loop and try to re-\n" \ " connect every <interval> seconds when dropping out.\n" \ " -v Increase verbosity. Can be used multiple times\n" \ " to be even more verbose.\n" \ " -q Decrease verbosity. Can be used multiple times\n" \ " to be even less verbose.\n" #define help_config \ "\n" \ "Configuration file:\n" \ " Options can be taken from a configuration file. Options passed in the\n" \ " command line will override those from the configuration file, though. The\n" \ " default configuration file is " SYSCONFDIR "/openfortivpn/config,\n" \ " but this can be set using the -c option.\n" \ " A simple configuration file example looks like:\n" \ " # this is a comment\n" \ " host = vpn-gateway\n" \ " port = 8443\n" \ " username = foo\n" \ " password = bar\n" \ " trusted-cert = certificatedigest4daa8c5fe6c...\n" \ " trusted-cert = othercertificatedigest6631bf...\n" \ " For a full-featured configuration see man openfortivpn(1).\n" /** * Returns the given "input" prefixed with "prefix" in a dynamically * allocated string. This behaves exactly like "strdup" if "input" already * starts with "prefix". */ static char *strdup_with_prefix(const char *input, const char *prefix) { size_t prefix_len = strlen(prefix); char *output; if (strncmp(prefix, input, prefix_len) == 0) return strdup(input); output = malloc(prefix_len + strlen(input) + 1); if (output) { strcpy(output, prefix); strcpy(output + prefix_len, input); } return output; } int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; const char *config_file = SYSCONFDIR "/openfortivpn/config"; const char *host; char *port_str; struct vpn_config cfg = { .gateway_host = {'\0'}, .gateway_port = 443, .username = {'\0'}, .password = {'\0'}, .password_set = 0, .cookie = NULL, .saml_port = 0, .saml_session_id = {'\0'}, .otp = {'\0'}, .otp_prompt = NULL, .otp_delay = 0, .no_ftm_push = 0, .pinentry = NULL, .realm = {'\0'}, .iface_name = {'\0'}, .sni = {'\0'}, .set_routes = 1, .set_dns = 1, .use_syslog = 0, .half_internet_routes = 0, .persistent = 0, #if HAVE_RESOLVCONF .use_resolvconf = USE_RESOLVCONF, #endif #if HAVE_USR_SBIN_PPPD .pppd_use_peerdns = 0, .pppd_log = NULL, .pppd_plugin = NULL, .pppd_ipparam = NULL, .pppd_ifname = NULL, .pppd_call = NULL, #if LEGACY_PPPD .pppd_accept_remote = 0, #else .pppd_accept_remote = 1, #endif #endif #if HAVE_USR_SBIN_PPP .ppp_system = NULL, #endif .ca_file = NULL, .user_cert = NULL, .user_key = NULL, .pem_passphrase = {'\0'}, .pem_passphrase_set = 0, .insecure_ssl = 0, #ifdef TLS1_2_VERSION .min_tls = TLS1_2_VERSION, #else .min_tls = 0, #endif .seclevel_1 = 0, .cipher_list = NULL, .cert_whitelist = NULL, .use_engine = 0, .user_agent = NULL, }; struct vpn_config cli_cfg = invalid_cfg; const struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 0}, {"config", required_argument, NULL, 'c'}, {"pinentry", required_argument, NULL, 0}, {"realm", required_argument, NULL, 0}, {"username", required_argument, NULL, 'u'}, {"password", required_argument, NULL, 'p'}, {"cookie", required_argument, NULL, 0}, {"cookie-on-stdin", no_argument, NULL, 0}, {"saml-login", optional_argument, NULL, 0}, {"otp", required_argument, NULL, 'o'}, {"otp-prompt", required_argument, NULL, 0}, {"otp-delay", required_argument, NULL, 0}, {"no-ftm-push", no_argument, &cli_cfg.no_ftm_push, 1}, {"ifname", required_argument, NULL, 0}, {"set-routes", required_argument, NULL, 0}, {"sni", required_argument, NULL, 0}, {"no-routes", no_argument, &cli_cfg.set_routes, 0}, {"half-internet-routes", required_argument, NULL, 0}, {"set-dns", required_argument, NULL, 0}, {"no-dns", no_argument, &cli_cfg.set_dns, 0}, {"use-syslog", no_argument, &cli_cfg.use_syslog, 1}, {"persistent", required_argument, NULL, 0}, {"ca-file", required_argument, NULL, 0}, {"user-cert", required_argument, NULL, 0}, {"user-key", required_argument, NULL, 0}, {"pem-passphrase", required_argument, NULL, 0}, {"trusted-cert", required_argument, NULL, 0}, {"insecure-ssl", no_argument, &cli_cfg.insecure_ssl, 1}, {"cipher-list", required_argument, NULL, 0}, {"min-tls", required_argument, NULL, 0}, {"seclevel-1", no_argument, &cli_cfg.seclevel_1, 1}, #if HAVE_USR_SBIN_PPPD {"pppd-use-peerdns", required_argument, NULL, 0}, {"pppd-no-peerdns", no_argument, &cli_cfg.pppd_use_peerdns, 0}, {"pppd-log", required_argument, NULL, 0}, {"pppd-plugin", required_argument, NULL, 0}, {"pppd-ipparam", required_argument, NULL, 0}, {"pppd-ifname", required_argument, NULL, 0}, {"pppd-call", required_argument, NULL, 0}, {"pppd-accept-remote", optional_argument, NULL, 0}, {"plugin", required_argument, NULL, 0}, // deprecated #endif #if HAVE_USR_SBIN_PPP {"ppp-system", required_argument, NULL, 0}, #endif #if HAVE_RESOLVCONF {"use-resolvconf", required_argument, NULL, 0}, #endif {NULL, 0, NULL, 0} }; init_logging(); while (1) { /* getopt_long stores the option index here. */ int c, option_index = 0; c = getopt_long(argc, argv, "hvqc:u:p:o:", long_options, &option_index); /* Detect the end of the options. */ if (c == -1) break; switch (c) { case 0: /* If this option set a flag, do nothing else now. */ if (long_options[option_index].flag != 0) break; if (strcmp(long_options[option_index].name, "version") == 0) { printf(VERSION "\n"); if (strcmp(&REVISION[1], VERSION)) log_debug("Revision " REVISION "\n"); ret = EXIT_SUCCESS; goto exit; } #if HAVE_USR_SBIN_PPPD if (strcmp(long_options[option_index].name, "pppd-use-peerdns") == 0) { int pppd_use_peerdns = strtob(optarg); if (pppd_use_peerdns < 0) { log_warn("Bad pppd-use-peerdns option: \"%s\"\n", optarg); break; } cli_cfg.pppd_use_peerdns = pppd_use_peerdns; break; } if (strcmp(long_options[option_index].name, "pppd-log") == 0) { free(cli_cfg.pppd_log); cli_cfg.pppd_log = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "pppd-plugin") == 0) { free(cli_cfg.pppd_plugin); cli_cfg.pppd_plugin = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "pppd-ifname") == 0) { free(cli_cfg.pppd_ifname); cli_cfg.pppd_ifname = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "pppd-ipparam") == 0) { free(cli_cfg.pppd_ipparam); cli_cfg.pppd_ipparam = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "pppd-call") == 0) { free(cli_cfg.pppd_call); cli_cfg.pppd_call = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "pppd-accept-remote") == 0) { if (optarg) { int pppd_accept_remote = strtob(optarg); if (pppd_accept_remote < 0) { log_warn("Bad pppd-accept-remote option: \"%s\"\n", optarg); break; } cli_cfg.pppd_accept_remote = pppd_accept_remote; } else { cli_cfg.pppd_accept_remote = 1; } break; } // --plugin is deprecated, use --pppd-plugin if (cli_cfg.pppd_plugin == NULL && strcmp(long_options[option_index].name, "plugin") == 0) { log_warn("Option --%s is deprecated, use --pppd-plugin\n", long_options[option_index].name); free(cli_cfg.pppd_plugin); cli_cfg.pppd_plugin = strdup(optarg); break; } #endif #if HAVE_USR_SBIN_PPP if (strcmp(long_options[option_index].name, "ppp-system") == 0) { free(cli_cfg.ppp_system); cfg.ppp_system = strdup(optarg); break; } #endif #if HAVE_RESOLVCONF if (strcmp(long_options[option_index].name, "use-resolvconf") == 0) { int use_resolvconf = strtob(optarg); if (use_resolvconf < 0) { log_warn("Bad use-resolvconf option: \"%s\"\n", optarg); break; } cli_cfg.use_resolvconf = use_resolvconf; break; } #endif if (strcmp(long_options[option_index].name, "ca-file") == 0) { free(cli_cfg.ca_file); cli_cfg.ca_file = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "user-cert") == 0) { free(cli_cfg.user_cert); cli_cfg.user_cert = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "user-key") == 0) { free(cli_cfg.user_key); cli_cfg.user_key = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "pem-passphrase") == 0) { strncpy(cli_cfg.pem_passphrase, optarg, PEM_PASSPHRASE_SIZE); cli_cfg.pem_passphrase[PEM_PASSPHRASE_SIZE] = '\0'; cli_cfg.pem_passphrase_set = 1; break; } if (strcmp(long_options[option_index].name, "pinentry") == 0) { free(cli_cfg.pinentry); cli_cfg.pinentry = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "realm") == 0) { strncpy(cli_cfg.realm, optarg, REALM_SIZE); cli_cfg.realm[REALM_SIZE] = '\0'; break; } if (strcmp(long_options[option_index].name, "trusted-cert") == 0) { if (add_trusted_cert(&cli_cfg, optarg)) log_warn("Could not add certificate digest to whitelist.\n"); break; } if (strcmp(long_options[option_index].name, "cipher-list") == 0) { free(cli_cfg.cipher_list); cli_cfg.cipher_list = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "min-tls") == 0) { int min_tls = parse_min_tls(optarg); if (min_tls == -1) { log_warn("Bad min-tls option: \"%s\"\n", optarg); } else { cli_cfg.min_tls = min_tls; } break; } if (strcmp(long_options[option_index].name, "otp-prompt") == 0) { free(cli_cfg.otp_prompt); cli_cfg.otp_prompt = strdup(optarg); break; } if (strcmp(long_options[option_index].name, "ifname") == 0) { strncpy(cli_cfg.iface_name, optarg, IF_NAMESIZE - 1); cli_cfg.iface_name[IF_NAMESIZE - 1] = '\0'; break; } if (strcmp(long_options[option_index].name, "sni") == 0) { strncpy(cli_cfg.sni, optarg, GATEWAY_HOST_SIZE); cli_cfg.sni[GATEWAY_HOST_SIZE] = '\0'; break; } if (strcmp(long_options[option_index].name, "set-routes") == 0) { int set_routes = strtob(optarg); if (set_routes < 0) { log_warn("Bad set-routes option: \"%s\"\n", optarg); break; } cli_cfg.set_routes = set_routes; break; } if (strcmp(long_options[option_index].name, "half-internet-routes") == 0) { int half_internet_routes = strtob(optarg); if (half_internet_routes < 0) { log_warn("Bad half-internet-routes option: \"%s\"\n", optarg); break; } cli_cfg.half_internet_routes = half_internet_routes; break; } if (strcmp(long_options[option_index].name, "otp-delay") == 0) { long otp_delay = strtol(optarg, NULL, 0); if (otp_delay < 0 || otp_delay > UINT_MAX) { log_warn("Bad otp-delay option: \"%s\"\n", optarg); break; } cli_cfg.otp_delay = otp_delay; break; } if (strcmp(long_options[option_index].name, "persistent") == 0) { long persistent = strtol(optarg, NULL, 0); if (persistent < 0 || persistent > UINT_MAX) { log_warn("Bad persistent option: \"%s\"\n", optarg); break; } cli_cfg.persistent = persistent; break; } if (strcmp(long_options[option_index].name, "set-dns") == 0) { int set_dns = strtob(optarg); if (set_dns < 0) { log_warn("Bad set-dns option: \"%s\"\n", optarg); break; } cli_cfg.set_dns = set_dns; break; } if (strcmp(long_options[option_index].name, "cookie") == 0) { free(cli_cfg.cookie); cli_cfg.cookie = strdup_with_prefix(optarg, "SVPNCOOKIE="); break; } if (strcmp(long_options[option_index].name, "cookie-on-stdin") == 0) { char *cookie = read_from_stdin(COOKIE_SIZE); if (cookie == NULL) { log_error("Could not read the cookie from stdin\n"); break; } free(cli_cfg.cookie); cli_cfg.cookie = strdup_with_prefix(cookie, "SVPNCOOKIE="); free(cookie); break; } if (strcmp(long_options[option_index].name, "saml-login") == 0) { long port = 8020; if (optarg != NULL) port = strtol(optarg, NULL, 0); if (port < 0 || port > 65535) { log_warn("Invalid saml listen port: %s! Default port is 8020 \n", optarg); break; } cli_cfg.saml_port = port; break; } goto user_error; case 'h': printf("%s%s%s%s%s%s%s", usage, summary, help_options_part1, help_options_part2, PPPD_HELP, RESOLVCONF_HELP, help_config); ret = EXIT_SUCCESS; goto exit; case 'v': increase_verbosity(); break; case 'q': decrease_verbosity(); break; case 'c': config_file = optarg; break; case 'u': strncpy(cli_cfg.username, optarg, USERNAME_SIZE); cli_cfg.username[USERNAME_SIZE] = '\0'; break; case 'p': strncpy(cli_cfg.password, optarg, PASSWORD_SIZE); cli_cfg.password[PASSWORD_SIZE] = '\0'; cli_cfg.password_set = 1; while (*optarg) *optarg++ = '*'; // nuke it break; case 'o': strncpy(cli_cfg.otp, optarg, OTP_SIZE); cli_cfg.otp[OTP_SIZE] = '\0'; break; default: goto user_error; } } if (optind < argc - 1 || optind > argc) goto user_error; if (cli_cfg.password[0] != '\0') log_warn("You should not pass the password on the command line. Type it interactively or use a configuration file instead.\n"); log_debug_all("ATTENTION: the output contains sensitive information such as the THE CLEAR TEXT PASSWORD.\n"); log_debug("openfortivpn " VERSION "\n"); if (strcmp(&REVISION[1], VERSION)) log_debug("revision " REVISION "\n"); // Load configuration file if (config_file[0] != '\0') { ret = load_config(&cfg, config_file); if (ret == 0) log_debug("Loaded configuration file \"%s\".\n", config_file); else log_warn("Could not load configuration file \"%s\" (%s).\n", config_file, err_cfg_str(ret)); } if (cli_cfg.password_set) { if (cli_cfg.password[0] == '\0') log_debug("Disabled password due to empty command-line option\n"); } else if (cfg.password_set) { if (cfg.password[0] == '\0') log_debug("Disabled password due to empty entry in configuration file \"%s\"\n", config_file); else log_debug("Loaded password from configuration file \"%s\"\n", config_file); } // Then apply CLI configuration merge_config(&cfg, &cli_cfg); set_syslog(cfg.use_syslog); // Set default UA if (cfg.user_agent == NULL) cfg.user_agent = strdup("Mozilla/5.0 SV1"); // Read host and port from the command line if (optind == argc - 1) { host = argv[optind++]; port_str = strchr(host, ':'); if (port_str != NULL) { long port; port_str[0] = '\0'; port_str++; port = strtol(port_str, NULL, 0); if (port < 1 || port > 65535) { log_error("Specify a valid port.\n"); goto user_error; } cfg.gateway_port = (uint16_t)port; } strncpy(cfg.gateway_host, host, GATEWAY_HOST_SIZE); cfg.gateway_host[GATEWAY_HOST_SIZE] = '\0'; } // Check host and port if (cfg.gateway_host[0] == '\0' || cfg.gateway_port == 0) { log_error("Specify a valid host:port couple.\n"); goto user_error; } // Check username if (cfg.username[0] == '\0' && !cfg.cookie && !cfg.saml_port) // Need either username or cert if (cfg.user_cert == NULL) { log_error("Specify a username.\n"); goto user_error; } // If username but no password given, interactively ask user if (!cfg.password_set && cfg.username[0] != '\0' && !cfg.cookie && !cfg.saml_port) { char hint[USERNAME_SIZE + 1 + REALM_SIZE + 1 + GATEWAY_HOST_SIZE + 10]; sprintf(hint, "%s_%s_%s_password", cfg.username, cfg.realm, cfg.gateway_host); read_password(cfg.pinentry, hint, "VPN account password: ", cfg.password, PASSWORD_SIZE); } log_debug("Configuration host = \"%s\"\n", cfg.gateway_host); log_debug("Configuration realm = \"%s\"\n", cfg.realm); log_debug("Configuration port = \"%d\"\n", cfg.gateway_port); if (cfg.username[0] != '\0') log_debug("Configuration username = \"%s\"\n", cfg.username); log_debug_all("Configuration password = \"%s\"\n", cfg.password); if (cfg.otp[0] != '\0') log_debug("One-time password = \"%s\"\n", cfg.otp); if (geteuid() != 0) { log_error("This process was not spawned with root privileges, which are required.\n"); ret = EXIT_FAILURE; goto exit; } if (cfg.saml_port != 0) { // Wait for the SAML token from the HTTP GET request if (wait_for_http_request(&cfg) != 0) { log_error("Failed to retrieve SAML cookie from HTTP\n"); ret = EXIT_FAILURE; goto exit; } if (strlen(cfg.saml_session_id) == 0) { log_error("Failed to receive SAML session id\n"); ret = EXIT_FAILURE; goto exit; } } do { if (run_tunnel(&cfg) != 0) ret = EXIT_FAILURE; else ret = EXIT_SUCCESS; if ((cfg.persistent > 0) && (get_sig_received() == 0)) sleep(cfg.persistent); } while ((get_sig_received() == 0) && (cfg.persistent != 0)); goto exit; user_error: fprintf(stderr, "%s", usage); exit: destroy_vpn_config(&cfg); exit(ret); } ��������������������openfortivpn-1.23.1/src/config.h��������������������������������������������������������������������0000664�0001750�0001750�00000007675�14753334500�016465� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_CONFIG_H #define OPENFORTIVPN_CONFIG_H #include <netinet/in.h> #include <net/if.h> #include <errno.h> #include <stdint.h> #include <string.h> #define ERR_CFG_UNKNOWN -1 #define ERR_CFG_SEE_ERRNO -2 #define ERR_CFG_EMPTY_FILE -3 #define ERR_CFG_NO_MEM -4 #define ERR_CFG_CANNOT_READ -5 static inline const char *err_cfg_str(int code) { if (code == ERR_CFG_SEE_ERRNO) return strerror(errno); else if (code == ERR_CFG_EMPTY_FILE) return "Empty file"; else if (code == ERR_CFG_NO_MEM) return "Not enough memory"; else if (code == ERR_CFG_CANNOT_READ) return "Cannot read file"; return "unknown"; } #if HAVE_USR_SBIN_PPPD #define PPP_DAEMON "pppd" #else #define PPP_DAEMON "ppp" #endif #define SHA256LEN (256 / 8) #define SHA256STRLEN (2 * SHA256LEN + 1) struct x509_digest { struct x509_digest *next; char data[SHA256STRLEN]; }; #define GATEWAY_HOST_SIZE 253 #define USERNAME_SIZE 64 #define PASSWORD_SIZE 256 #define OTP_SIZE 64 #define REALM_SIZE 63 #define PEM_PASSPHRASE_SIZE 31 /* * RFC 6265 does not limit the size of cookies: * https://www.rfc-editor.org/info/rfc6265 * * Yet browsers typically limit themselves to ~4K so we are on the safe side: * http://browsercookielimits.squawky.net/ */ #define COOKIE_SIZE 4096 /* * GNU libc used to limit the search list to 256 characters: * https://unix.stackexchange.com/questions/245849 * * We believe we are on the safe side using this value. */ #define MAX_DOMAIN_LENGTH 256 #define MAX_SAML_SESSION_ID_LENGTH 1024 struct vpn_config { char gateway_host[GATEWAY_HOST_SIZE + 1]; struct in_addr gateway_ip; uint16_t gateway_port; char username[USERNAME_SIZE + 1]; char password[PASSWORD_SIZE + 1]; int password_set; char otp[OTP_SIZE + 1]; char *cookie; uint16_t saml_port; char saml_session_id[MAX_SAML_SESSION_ID_LENGTH + 1]; char *otp_prompt; unsigned int otp_delay; int no_ftm_push; char *pinentry; char iface_name[IF_NAMESIZE]; char realm[REALM_SIZE + 1]; char sni[GATEWAY_HOST_SIZE + 1]; int set_routes; int set_dns; int pppd_use_peerdns; int use_syslog; #if HAVE_RESOLVCONF int use_resolvconf; #endif int half_internet_routes; unsigned int persistent; #if HAVE_USR_SBIN_PPPD char *pppd_log; char *pppd_plugin; char *pppd_ipparam; char *pppd_ifname; char *pppd_call; int pppd_accept_remote; #endif #if HAVE_USR_SBIN_PPP char *ppp_system; #endif char *ca_file; char *user_cert; char *user_key; char pem_passphrase[PEM_PASSPHRASE_SIZE + 1]; int pem_passphrase_set; int insecure_ssl; int min_tls; int seclevel_1; char *cipher_list; struct x509_digest *cert_whitelist; int use_engine; char *user_agent; char *hostcheck; char *check_virtual_desktop; }; int add_trusted_cert(struct vpn_config *cfg, const char *digest); int strtob(const char *str); int parse_min_tls(const char *str); int load_config(struct vpn_config *cfg, const char *filename); void destroy_vpn_config(struct vpn_config *cfg); /* * merge source config into dest * * memory allocated dynamically is transferred with this function * e.g. ownership goes to dest config */ void merge_config(struct vpn_config *dest, struct vpn_config *source); extern const struct vpn_config invalid_cfg; #endif �������������������������������������������������������������������openfortivpn-1.23.1/src/http.h����������������������������������������������������������������������0000664�0001750�0001750�00000004377�14753334500�016173� 0����������������������������������������������������������������������������������������������������ustar �epsilon�������������������������epsilon����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright (c) 2015 Adrien Vergé * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #ifndef OPENFORTIVPN_HTTP_H #define OPENFORTIVPN_HTTP_H #include "tunnel.h" #include <stdint.h> #define ERR_HTTP_INVALID -1 #define ERR_HTTP_TOO_LONG -2 #define ERR_HTTP_NO_MEM -3 #define ERR_HTTP_SSL -4 // deprecated #define ERR_HTTP_TLS -4 #define ERR_HTTP_BAD_RES_CODE -5 #define ERR_HTTP_PERMISSION -6 #define ERR_HTTP_NO_COOKIE -7 /* * URL-encodes a string for HTTP requests. * * The dest buffer size MUST be at least strlen(str) * 3 + 1. * * @param[out] dest the buffer to write the URL-encoded string * @param[in] str the input string to be escaped */ void url_encode(char *dest, const char *str); static inline const char *err_http_str(int code) { if (code > 0) return "HTTP status code"; else if (code == ERR_HTTP_INVALID) return "Invalid input"; else if (code == ERR_HTTP_TOO_LONG) return "Request too long"; else if (code == ERR_HTTP_NO_MEM) return "Not enough memory"; else if (code == ERR_HTTP_TLS) return "TLS error"; else if (code == ERR_HTTP_BAD_RES_CODE) return "Bad HTTP response code"; else if (code == ERR_HTTP_PERMISSION) return "Permission denied"; else if (code == ERR_HTTP_NO_COOKIE) return "No cookie given"; return "unknown"; } int http_send(struct tunnel *tunnel, const char *request, ...); int http_receive(struct tunnel *tunnel, char **response, uint32_t *response_size); int auth_log_in(struct tunnel *tunnel); int auth_log_out(struct tunnel *tunnel); int auth_request_vpn_allocation(struct tunnel *tunnel); int auth_get_config(struct tunnel *tunnel); int auth_set_cookie(struct tunnel *tunnel, const char *line); #endif �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������