diff --git a/doc/METADATA.ini b/doc/METADATA.ini index 8177da6d332..ff8cc068cd6 100644 --- a/doc/METADATA.ini +++ b/doc/METADATA.ini @@ -558,6 +558,21 @@ usedby/plugin= network ipaddr description= check if value resolves correctly to an ipaddr empty value uses either ipv4 or ipv6 +[check/port] +type= string +status = implemented +usedby/plugin= network +description= check if the given port is either a service under /etc/services or + is between 0 - 65535 (both inclusive) + +[check/port/listen] +type= string +status = implemented +usedby/plugin= network +description= check if the given port is either a service under /etc/services or + is between 0 - 65535 (both inclusive) and is unused so that potential + applications can start with that port + [check/format] type= string status= idea diff --git a/doc/news/_preparation_next_release.md b/doc/news/_preparation_next_release.md index bc7416d6c45..29586bfca96 100644 --- a/doc/news/_preparation_next_release.md +++ b/doc/news/_preparation_next_release.md @@ -81,12 +81,31 @@ Take a look at the [README](/src/libs/highlevel/README.md) for more infos. The following section lists news about the [modules](https://www.libelektra.org/plugins/readme) we updated in this release. -### <> +### Process -- <> -- <> -- <> +- There is also a new plugin called [process](https://libelektra.org/plugins/process). + This plugin utilizes the pluginprocess library in order to execute arbitrary other + plugins in an own process, acting as a proxy itself. Therefore it is not required + to explicitly change a plugin's implementation if it shall be executed in an own + process. This plugin is not completely finished yet, as currently there is no way + for it to mimic the proxied plugin's contract in Elektra. It can be used with simple + plugins like `dump` however, check the limitations in the readme for more details. *(Armin Wurzinger)* + +### gpgme + +- The `gpgme` plugin was brought into existence to provide cryptographic functions using GnuGP via the `libgpgme` library. See [#896] *(Peter Nirschl)* +### YAMBi + +This new plugin parses a subset of YAML using a parser generated by [Bison](https://www.gnu.org/software/bison). *(René Schwaiger)* + +### network + +The `network` plugin now also allows for non-numerical hosts (i.e. "localhost") to be set and tries to +resolve it via DNS. *(Michael Zronek)* + +The `network` plugin also supports port declarations to check if a portnumber is valid +or if the port is available to use. *(Michael Zronek)* ### <> @@ -96,7 +115,6 @@ The following section lists news about the [modules](https://www.libelektra.org/ - <> - ### <> - <> diff --git a/src/error/specification b/src/error/specification index 21e164f452b..0adab3b87b0 100644 --- a/src/error/specification +++ b/src/error/specification @@ -1286,3 +1286,9 @@ severity:error ingroup:plugin module:reference macro:REFERENCE_NOT_FOUND + +number:205 +description:Connection Error occurred +severity:error +ingroup:plugin +module:network diff --git a/src/plugins/network/README.md b/src/plugins/network/README.md index 8162775ae25..66c5f8e29d1 100644 --- a/src/plugins/network/README.md +++ b/src/plugins/network/README.md @@ -5,7 +5,7 @@ - infos/needs = - infos/placements = presetstorage - infos/status = maintained unittest nodep libc nodoc -- infos/metadata = check/ipaddr +- infos/metadata = check/ipaddr check/port check/port/listen - infos/description = Checks keys if they contain a valid ip address ## Introduction @@ -14,6 +14,10 @@ This plugin is a check plugin that checks if a key contains a valid ip address. It uses the `POSIX.1-2001` interface `getaddrinfo()` in order to check if an ip address is valid. +Furthermore `getaddrinfo()` is used in `check/port` to resolve a port by its service name +which is defined under `/etc/services`. The portname is translated to the respective portnumber. +The plugin can be used to check for valid port numbers and if the set port is free to use. + ## Purpose While, in theory, a regular expression can express if a string is a @@ -36,7 +40,15 @@ it to implement this plugin. Every key tagged with the metakey `check/ipaddr` will be checked using `getaddrinfo()`. If additionally the values `ipv4` or `ipv6` -are supplied, the address family will be specified. If supplied only -numerical hosts are allowed. If left empty, the plugin will resolve -domain names and look if it is reachable (i.e. "localhost" should most -likely work on any system) +are supplied, the address family will be specified. + +If `check/port` is specified on a given key, the plugin will validate if the port is a +correct number between 1 and 65535. + +If `check/port/listen` is specified, the plugin will check if the application can be started +and listen on the given port. + +## Future Work + +`check/port/connect` to check if the port can be pinged/reached (usually for clients). +If not reachable, users receive a warning. A correct timeout setting will be problematic though. \ No newline at end of file diff --git a/src/plugins/network/network.c b/src/plugins/network/network.c index 474f592384d..947b1e796bb 100644 --- a/src/plugins/network/network.c +++ b/src/plugins/network/network.c @@ -9,7 +9,9 @@ #include "network.h" #ifndef HAVE_KDBCONFIG + #include "kdbconfig.h" + #endif /* Obtain address(es) matching host/port */ @@ -50,6 +52,93 @@ int elektraNetworkAddrInfo (Key * toCheck) return 0; } +int elektraPortInfo (Key * toCheck, Key * parentKey) +{ + const Key * meta = keyGetMeta (toCheck, "check/port"); + const Key * listenMeta = keyGetMeta (toCheck, "check/port/listen"); + if (!meta && !listenMeta) return 0; /* No check to do */ + char * endptr = NULL; + long portNumber = strtol (keyString (toCheck), &endptr, 10); + int portNumberNetworkByteOrder; + + if (*endptr == '\0') + { + if (portNumber < 0 || portNumber > 65535) + { + ELEKTRA_SET_ERRORF (171, parentKey, "Port %ld on key %s was not within 0 - 65535", portNumber, keyName (toCheck)); + return -1; + } + portNumberNetworkByteOrder = htons (portNumber); + } + else + { + struct servent * service; + service = getservbyname (keyString (toCheck), NULL); // NULL means we accept both tcp and udp + if (service == NULL) + { + ELEKTRA_SET_ERRORF (205, parentKey, "Could not find service with name %s on key %s", keyString (toCheck), + keyName (toCheck)); + return -1; + } + portNumberNetworkByteOrder = service->s_port; + } + + if (!listenMeta) return 0; /* No check to do */ + + char * hostname = "localhost"; + + int sockfd; + struct sockaddr_in serv_addr; + struct hostent * server; + sockfd = socket (AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + { + ELEKTRA_SET_ERRORF (205, parentKey, "Could not open a socket: %s", strerror (errno)); + } + + server = gethostbyname (hostname); + if (server == NULL) + { + if (errno == HOST_NOT_FOUND) + { + ELEKTRA_SET_ERRORF (205, parentKey, "Could not connect to %s: No such host", hostname); + return -1; + } + else + { + ELEKTRA_SET_ERRORF (205, parentKey, "There was an error when trying to connect to host %s . errno: %s", hostname, + strerror (errno)); + return -1; + } + // TODO: Maybe consider errno == TRY_AGAIN seperately and try to reconnect + } + + + bzero ((char *) &serv_addr, sizeof (serv_addr)); + serv_addr.sin_family = AF_INET; + bcopy ((char *) server->h_addr, (char *) &serv_addr.sin_addr.s_addr, server->h_length); + + serv_addr.sin_port = (in_port_t) portNumberNetworkByteOrder; + if (bind (sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0) + { + close (sockfd); + if (errno == EADDRINUSE) + { + ELEKTRA_SET_ERRORF (205, parentKey, "Port %s is already in use which was specified on key %s", keyString (toCheck), + keyName (toCheck)); + } + else + { + ELEKTRA_SET_ERRORF (205, parentKey, "Could not bind to port %s which was specified on key %s. Reason: %s", + keyString (toCheck), keyName (toCheck), strerror (errno)); + } + return -1; + } + close (sockfd); + + return 0; +} + int elektraNetworkGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey ELEKTRA_UNUSED) { /* configuration only */ @@ -61,7 +150,10 @@ int elektraNetworkGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * keyNew ("system/elektra/modules/network/exports/set", KEY_FUNC, elektraNetworkSet, KEY_END), keyNew ("system/elektra/modules/network/exports/elektraNetworkAddrInfo", KEY_FUNC, elektraNetworkAddrInfo, KEY_END), + keyNew ("system/elektra/modules/network/exports/elektraPortInfo", KEY_FUNC, elektraNetworkAddrInfo, KEY_END), + #include "readme_network.c" + keyNew ("system/elektra/modules/network/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END)); ksDel (n); @@ -72,7 +164,6 @@ int elektraNetworkSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * { /* check all keys */ Key * cur; - ksRewind (returned); while ((cur = ksNext (returned)) != 0) { @@ -92,6 +183,11 @@ int elektraNetworkSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * elektraFree (errmsg); return -1; } + int p = elektraPortInfo (cur, parentKey); + if (p != 0) + { + return -1; + } } return 1; /* success */ @@ -100,9 +196,9 @@ int elektraNetworkSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * Plugin * ELEKTRA_PLUGIN_EXPORT (network) { // clang-format off - return elektraPluginExport("network", - ELEKTRA_PLUGIN_GET, &elektraNetworkGet, - ELEKTRA_PLUGIN_SET, &elektraNetworkSet, - ELEKTRA_PLUGIN_END); + return elektraPluginExport ("network", + ELEKTRA_PLUGIN_GET, &elektraNetworkGet, + ELEKTRA_PLUGIN_SET, &elektraNetworkSet, + ELEKTRA_PLUGIN_END); } diff --git a/src/plugins/network/network.h b/src/plugins/network/network.h index 3877fb28ad4..2edd2d1f16e 100644 --- a/src/plugins/network/network.h +++ b/src/plugins/network/network.h @@ -22,7 +22,10 @@ int elektraNetworkAddrInfo (Key * toCheck); +int elektraPortInfo (Key * toCheck, Key * parentKey); + int elektraNetworkGet (Plugin * handle, KeySet * ks, Key * parentKey); + int elektraNetworkSet (Plugin * handle, KeySet * ks, Key * parentKey); Plugin * ELEKTRA_PLUGIN_EXPORT (network); diff --git a/src/plugins/network/testmod_network.c b/src/plugins/network/testmod_network.c index b9ea33d3490..4b25da2c9de 100644 --- a/src/plugins/network/testmod_network.c +++ b/src/plugins/network/testmod_network.c @@ -7,7 +7,9 @@ */ #ifdef HAVE_KDBCONFIG_H + #include "kdbconfig.h" + #endif #include @@ -17,6 +19,9 @@ #include #define PLUGIN_NAME "network" + +static void testPorts (void); + #include "../ipaddr/test_ipaddr.h" int main (int argc, char ** argv) @@ -27,8 +32,54 @@ int main (int argc, char ** argv) init (argc, argv); testIPAll (); + testPorts (); print_result ("testmod_network"); return nbError; } + +static void testPort (char const * const port, const int ret, char const * const version, char const * const metaName) +{ + Key * parentKey = keyNew ("user/tests/port", KEY_VALUE, "", KEY_END); + KeySet * conf = ksNew (0, KS_END); + KeySet * ks = ksNew (10, keyNew ("user/test/port/totest", KEY_VALUE, port, KEY_META, metaName, version, KEY_END), KS_END); + PLUGIN_OPEN (PLUGIN_NAME); + const int pluginStatus = plugin->kdbSet (plugin, ks, parentKey); + char message[200]; + (void) snprintf (message, 200, "validation of %s “%s” returned %d instead of %d", version[0] == '\0' ? "Port" : version, port, + pluginStatus, ret); + succeed_if (pluginStatus == ret, message); + ksDel (ks); + keyDel (parentKey); + PLUGIN_CLOSE (); +} + +static inline void testPortAny (char const * const port, int ret) +{ + testPort (port, ret, "", "check/port"); +} + +static inline void testListenPortAny (char const * const port, int ret) +{ + testPort (port, ret, "", "check/port/listen"); +} + +static void testPorts (void) +{ + testPortAny ("0", 1); + testPortAny ("1234", 1); + testPortAny ("65535", 1); + testPortAny ("ssh", 1); + testPortAny ("https", 1); + + testPortAny ("65536", -1); + testPortAny ("-1", -1); + testPortAny ("22d", -1); + testPortAny ("myInvalidServiceName", -1); + + // Tests for ListenPort are not portable, even system ports in a range from 1-1000 can some short time be reachable + // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers + // Use such tests only locally if you are certain that some ports are actually in use + // testListenPortAny ("22", 1); +}