-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 NotDashEscaped: You need GnuPG to verify this message ======================================================================== The following patch will turn an unchanged Motsognir v1.0.9 source tree into a Motsognir v1.0.9+logmore1.0 source tree. Put this file into the source directory, chdir to the same directory, then apply the patch with: patch -p1 < motsognir109+logmore1.0.diff You don't need to trim any text: the patch program knows what to skip. ------------------------------------------------------------------------ Vvv====P:A:T:C:H===F:O:L:L:O:W:S====**====T:H:I:S===S:I:D:E===U:P======= ======================================================================== --- motsognir/NEWS_logmore (nonexistent) +++ motsognir+logmore/NEWS_logmore 2018-08-14 10:24:17.355449379 +0200 @@ -0,0 +1,25 @@ +logmore1.0 +========== + +The logmore1.0 patch adds the following features to Motsognir v1.0.9: + ++ New command line option '--log' allows user to specify either a log + file, or a syslog local facility number. This option is also available + as new configuration file directive 'log'; + ++ a directory specified on the command line is taken to be the + Gopher root; + ++ standard options '--help', '--version'; plus '--license'. + +Motsognir v1.0.9+logmore1.0 was tested on the following systems: + ++ Void Linux (x86_64), kernel 4.17.9_1 SMP PREEMPT, + GNU libc 2.26, gcc 7.3.0 ++ CentOS Linux 7 (x86_64), kernel 3.10.0 SMP, + GNU libc 2.17, gcc 4.8.5 + +-- +Dario Niedermann , 2018-08-14 + +gopher://darioniedermann.it/ <> https://www.darioniedermann.it/ ======================================================================== --- motsognir/Makefile 2018-08-06 09:52:58.777357833 +0200 +++ motsognir+logmore/Makefile 2018-08-07 11:08:42.019332501 +0200 @@ -1,3 +1,6 @@ +# $Id: motsognir109_logmore1.0.diff 6 2019-04-01 16:30:00Z ndr $ + CC ?= gcc -CFLAGS += -Wall -Wextra -O3 -std=gnu89 -pedantic -Wformat-security +CFLAGS += -Wall -Wextra -O3 -std=c99 -pedantic -Wformat-security \ + -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE -D_BSD_SOURCE @@ -5,4 +8,4 @@ -motsognir: motsognir.o extmap.o - $(CC) motsognir.o extmap.o -o motsognir $(CFLAGS) +motsognir: motsognir.o extmap.o cli.o log.o + $(CC) motsognir.o extmap.o cli.o log.o -o motsognir $(CFLAGS) @@ -11,3 +14,3 @@ -motsognir.o: motsognir.c +motsognir.o: motsognir.c motsognir.h extmap.h binary.h log.h $(CC) -c motsognir.c -o motsognir.o $(CFLAGS) @@ -17,2 +20,6 @@ +cli.o: cli.c log.h motsognir.h + +log.o: log.c log.h motsognir.h + extmaptest: extmaptest.c extmap.o ======================================================================== --- motsognir/cli.c (nonexistent) +++ motsognir+logmore/cli.c 2018-08-06 08:53:02.747462626 +0200 @@ -0,0 +1,202 @@ +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * Motsognir - The mighty gopher server * + * * Copyright (C) 2008-2016 Mateusz Viste * + * * http://motsognir.sourceforge.net * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Some changes and additions: Copyright (C) 2018 Dario Niedermann + * $Id: motsognir109_logmore1.0.diff 6 2019-04-01 16:30:00Z ndr $ + * ---------------------------------------------------------------------- + * 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 . + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "motsognir.h" + +#define FALSE 0 +#define TRUE 1 + +/* declare the default config file location, if not already declared from CLI + * at compile-time - esp. useful for systems that store config files in other + * locations, like /usr/local/etc/ for FreeBSD... */ +#ifndef CONFIGFILE + #define CONFIGFILE "/etc/motsognir.conf" +#endif + + +static void print_version(void) +{ + printf("Motsognir v%s Copyright (C) Mateusz Viste et al. %s\n", + pVer, pDate); +} + + +static void print_usage(char *argv0) +{ + printf("Usage: %s [OPTIONS] [DIRECTORY]", basename(argv0)); + printf("\n\ +Start the Motsognir Gopher server, with DIRECTORY as the Gopher root\n\n\ +Options:\n\ + -c, --config=FILE read server configuration from FILE\n\ + -l, --log=FILE|0..7 log to FILE or syslog local facility #\n\ + -V, --version display version information and exit\n\ + -L, --licence\n\ + --license display license and exit\n\ + -h, --help display this help text and exit\n\n\ +Command line arguments override configuration file directives.\n\ +Default Gopher root: /var/gopher/\n\ +Default configuration file: /etc/motsognir.conf\n"); +} + + +static void about(void) { + print_version(); + + printf("\n\ +This program is free software: you can redistribute it and/or modify it under\n\ +the terms of the GNU General Public License as published by the Free Software\n\ +Foundation, either version 3 of the License, or (at your option) any later\n\ +version.\n"); + + printf("\ +This program is distributed in the hope that it will be useful, but WITHOUT ANY\n\ +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n\ +PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\n\ +Motsognir is a robust and reliable open-source gopher server for POSIX systems.\n\ +Motsognir is entirely written in ISO C99, without any external dependencies.\n\n"); + + printf("Homepage: %s\n", HOMEPAGE); +} + + +static int configLogging(char *dest, struct logParams *logConf) +{ + static char timesHere = 0; + int status = EXIT_SUCCESS; + + if (++timesHere == 1) { /* I'll only configure ONCE: from the cmd line + if specified, from config file otherwise. */ + if (dest[1] == '\0' && dest[0] <= '7' && dest[0] >= '0') { + /* destination is a syslog local facility number */ + logConf->loggingType = TO_SYSLOG; + /* reduce the 1st -and last- char in dest to a 0 to 7 value, use + it as an index to look up the facility value in the array */ + logConf->facility = logConf->localFacility[dest[0]-'0']; + } + else { /* option's arg is -arguably- a file name */ + logConf->loggingType = TO_FILE; + logConf->fileName = dest; + } + } + else status = EX_DATAERR; + + return status; +} + + +int parseCmdLine(char mode, int myArgc, char **myArgv, + struct MotsognirConfig *confStruct) +{ + #define DONE_PARSING -1 /* getopt_long() returns -1 when done */ + static struct option long_options[] = { + {"config", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {"licence", no_argument, NULL, 'L'}, + {"license", no_argument, NULL, 'L'}, + {"log", required_argument, NULL, 'l'}, + {"version", no_argument, NULL, 'V'}, + {0, 0, 0, 0 } + }; + static struct logParams logConf; + /* since LOG_LOCALn symbols might (in theory) have any value, let's put + them in order into an array, where we can refer to them by index */ + static int localFacilities[] = { LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, + LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, + LOG_LOCAL6, LOG_LOCAL7 }; + int c, option_index, this_option = 0, status = EXIT_SUCCESS; + + if (mode == FIRST_PASS) { /* Do some one-time setup: */ + confStruct->configfile = CONFIGFILE; + logConf.loggingType = TO_SYSLOG; + logConf.facility = LOG_DAEMON; + logConf.fileName = NULL; + logConf.localFacility = localFacilities; + } + + optind=1; while (status == EXIT_SUCCESS) { + this_option = optind ? optind : 1; + option_index = 0; + + c = getopt_long(myArgc, myArgv, ":c:hLl:V", long_options, &option_index); + if (c == DONE_PARSING) break; + + switch (c) { + case 'c': + confStruct->configfile = optarg; break; + + case 'h': + print_usage(myArgv[0]); status = OK_EXIT_NOW; break; + + case 'L': + about(); status = OK_EXIT_NOW; break; + + case 'l': + if (configLogging(optarg, &logConf)) + /* if configLogging is called >1 time, it'll error out */ + if (mode != LAST_PASS) + warnx("further logging destination '%s' ignored", optarg); + break; + case 'V': + print_version(); status = OK_EXIT_NOW; break; + + case '?': + warnx("unrecognized option '%s'", myArgv[this_option]); + print_usage(myArgv[0]); status = EX_USAGE; break; + + case ':': + warnx("option '%s' requires an argument", + myArgv[this_option]); + status = EX_USAGE; break; + + default: /* should never happen... */ + warnx("internal error while parsing command line, char code 0%o", c); + status = EX_SOFTWARE; + } + } + + /* init the logging functions, either with default values, + or values from the command line: */ + logme(INIT, NO_MSG, &logConf); + + /* non-option args at the end of cmd line? */ + if (mode == LAST_PASS) /* only deal with them the last time I'm called */ + for (c=0; optind < myArgc && c < 256; c++, optind++) { + if (!c) /* the 1st will be our gopher root */ + confStruct->gopherroot = myArgv[optind]; + else + warnx("ignoring trailing argument: '%s'", myArgv[optind]); + } + + return status; +} ======================================================================== --- motsognir/log.c (nonexistent) +++ motsognir+logmore/log.c 2018-08-05 17:37:50.420143737 +0200 @@ -0,0 +1,151 @@ +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * Motsognir - The mighty gopher server * + * * Copyright (C) 2008-2016 Mateusz Viste * + * * http://motsognir.sourceforge.net * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Some changes and additions: Copyright (C) 2018 Dario Niedermann + * $Id: motsognir109_logmore1.0.diff 6 2019-04-01 16:30:00Z ndr $ + * ---------------------------------------------------------------------- + * 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 . + * ---------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include "motsognir.h" +#include "log.h" + +#define MAX_MSGLEN 2048 /* max bytes in a single log entry */ + +struct logWritersParams { + int level, + facility; + char *fileName, + *prefix, + *msg; + FILE *filePtr; +}; + +static int logToFile(struct logWritersParams *params) +{ + int status; + + status = EXIT_SUCCESS; + if (params->filePtr == NULL) + if ((params->filePtr = fopen(params->fileName, "a")) == NULL) + return EX_CANTCREAT; + /* else... */ + if (params->prefix != NULL) + if (fprintf(params->filePtr, "%s ", params->prefix) < 1) + status = EX_IOERR; + + if ((fprintf(params->filePtr, "%s\n", params->msg) < 1) + || fflush(params->filePtr)) + status = EX_IOERR; + + if (status == EX_IOERR) fclose(params->filePtr); + return status; +} + + +static int logToSyslog(struct logWritersParams *params) +{ + assert(params->level >= LOG_EMERG && params->level <= LOG_DEBUG); + assert((params->facility >= LOG_LOCAL0 && params->facility <= LOG_LOCAL7) + || params->facility == LOG_DAEMON); + syslog(params->level | params->facility, "%s", params->msg); + return 0; +} + + +void logme(int level, const char *fmt, ...) +{ + va_list argPtr; /* points to the list of unnamed args */ + static struct logWritersParams logWritersSettings; + static char msg[MAX_MSGLEN], whereToLog; + static int (*writerFunc)() = NULL; + struct logParams *logSettings; + int msgLen; + char *clientIP; + + va_start(argPtr, fmt); /* make argPtr point to 1st unnamed arg */ + + switch (level) { + case INIT: + /* If I'm called with pseudo-level INIT, I won't log a message: instead + I'll regard 1st unnamed passed-in arg as a pointer to a logParams struct. + Spares the need of being passed a config struct by every func calling me + */ + assert(fmt==NO_MSG); /* or there must be collision with INIT's value */ + logWritersSettings.msg = msg; /* never to be changed */ + logSettings = va_arg(argPtr, struct logParams *); + /* mk our static copys of settings (passed-in struc may be destroyd) */ + whereToLog = logSettings->loggingType; + assert(whereToLog==TO_FILE||whereToLog==TO_SYSLOG); + logWritersSettings.facility = logSettings->facility; + /* no need to alloc -- points to somewhere in argv[] if it came from + a command line opt, or to a strdup'ed string if from config file: */ + logWritersSettings.fileName = logSettings->fileName; + logWritersSettings.filePtr = NULL; + + /* pick function according to message destination -- file or syslog */ + writerFunc = (whereToLog == TO_FILE) ? logToFile : logToSyslog; + break; + + case SET_PREFIX: + /* This pseudo-level means I'll set the common prefix to log entries. + The 1st unnamed passed-in arg must now be a connected client's IP, + because I only get called this way when a new client connects. + The 2nd unnamed arg points to a char array that will contain the prefix. + */ + assert(writerFunc != NULL); /* I must've been previously INIT'ed */ + clientIP = va_arg(argPtr, char *); + logWritersSettings.prefix = va_arg(argPtr, char *); + sprintf(logWritersSettings.prefix, fmt, clientIP); + if (whereToLog == TO_SYSLOG) { + /* set up the logging to log with PID and peer's IP address */ + openlog(logWritersSettings.prefix, LOG_PID, + logWritersSettings.facility); + } + else + assert(whereToLog == TO_FILE); + break; + + default: /* an actual syslog level */ + assert(level >= LOG_EMERG && level <= LOG_DEBUG); + logWritersSettings.level = level; + /* turn fmt and any following args into a fixed string in msg: */ + msgLen = vsnprintf(msg, MAX_MSGLEN, fmt, argPtr); + /* write msg to log: */ + while ((*writerFunc)(&logWritersSettings)) { + /* got error. From logToFile() 'cause logToSyslog() only returns 0 */ + whereToLog = TO_SYSLOG; + /* facility must still be as initially,since we were loggin'to file*/ + assert(logWritersSettings.facility == LOG_DAEMON); + writerFunc = logToSyslog; + LOG(ERR,"Could not write to log file '%s' -- reverting to syslog", + logWritersSettings.fileName); + } + if (msgLen >= MAX_MSGLEN) + LOG(WARNING,"previous log message was truncated"); + } + va_end(argPtr); /* clean up va_ stuff */ +} ======================================================================== --- motsognir/log.h (nonexistent) +++ motsognir+logmore/log.h 2018-08-05 17:37:50.423144093 +0200 @@ -0,0 +1,29 @@ +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * Motsognir - The mighty gopher server * + * * Copyright (C) 2008-2016 Mateusz Viste * + * * http://motsognir.sourceforge.net * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Some changes and additions: Copyright (C) 2018 Dario Niedermann + * $Id: motsognir109_logmore1.0.diff 6 2019-04-01 16:30:00Z ndr $ + * ---------------------------------------------------------------------- + * 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 . + * ---------------------------------------------------------------------- + */ + +#define LOG(level, ...) logme(LOG_##level, __VA_ARGS__) +#define NO_MSG NULL + +void logme(int level, const char *fmt, ...); ======================================================================== --- motsognir/motsognir.c 2018-08-06 09:52:59.080393386 +0200 +++ motsognir+logmore/motsognir.c 2018-08-05 17:37:50.430144924 +0200 @@ -7,2 +7,4 @@ * + * Some changes and additions: Copyright (C) 2018 Dario Niedermann + * $Id: motsognir109_logmore1.0.diff 6 2019-04-01 16:30:00Z ndr $ * ---------------------------------------------------------------------- @@ -29,3 +31,2 @@ #include -#include /* regcomp(), regexec()... */ #include @@ -48,47 +49,8 @@ #include "extmap.h" +#include "motsognir.h" +#include "log.h" -/* Constants */ -#define pVer "1.0.9" -#define pDate "2008-2016" -#define HOMEPAGE "http://motsognir.sourceforge.net" - -/* declare the default config file location, if not already declared from CLI - * at compile-time - esp. useful for systems that store config files in other - * locations, like /usr/local/etc/ for FreeBSD... */ -#ifndef CONFIGFILE - #define CONFIGFILE "/etc/motsognir.conf" -#endif - - -struct MotsognirConfig { - char *gopherroot; - char *userdir; - char **pubdirlist; - int gopherport; - char *gopherhostname; - char *defaultgophermap; - int verbosemode; - int capssupport; - char *capsservergeolocationstring; - char *capsserverarchitecture; - char *capsserverdescription; - char *capsserverdefaultencoding; - int cgisupport; - int phpsupport; - int subgophermaps; - int paranoidmode; - char *plugin; - regex_t *pluginfilter; - char *runasuser; - uid_t runasuser_uid; - gid_t runasuser_gid; - char *runasuser_home; - char *chroot; - char *httperrfile; - char *bind; - char *extmapfile; - struct extmap_t *extmap; - char securldelim; -}; - +/* implementation in 'cli.c': */ +int parseCmdLine(char mode, int argc, char **argv, + struct MotsognirConfig *confStruct); @@ -157,3 +119,3 @@ if (initgroups(config->runasuser, config->runasuser_gid) != 0 || setgid(config->runasuser_gid) != 0 || setuid(config->runasuser_uid) != 0) { - syslog(LOG_WARNING, "ERROR: Couldn't change to '%.32s' uid=%lu gid=%lu: %s", config->runasuser, (unsigned long)config->runasuser_uid, (unsigned long)config->runasuser_gid, strerror(errno)); + LOG(WARNING,"ERROR: Couldn't change to '%.32s' uid=%lu gid=%lu: %s", config->runasuser, (unsigned long)config->runasuser_uid, (unsigned long)config->runasuser_gid, strerror(errno)); return(-1); @@ -162,3 +124,3 @@ if (getuid() != config->runasuser_uid) { - syslog(LOG_WARNING, "ERROR: For some mysterious reasons Motsognir was unable to switch to user '%s'.", config->runasuser); + LOG(WARNING,"ERROR: For some mysterious reasons Motsognir was unable to switch to user '%s'.", config->runasuser); return(-1); @@ -369,3 +331,3 @@ if (dstlen + 4 >= dstmaxlen) { - syslog(LOG_WARNING, "WARNING: reached percent encoding length limit - aborting"); + LOG(WARNING,"WARNING: reached percent encoding length limit - aborting"); break; /* stop the work if we reached our limit */ @@ -450,3 +412,3 @@ string[x] = 0; - syslog(LOG_WARNING, "ERROR: detected invalid percent encoding"); + LOG(WARNING,"ERROR: detected invalid percent encoding"); return(-1); @@ -456,3 +418,3 @@ string[x] = 0; - syslog(LOG_WARNING, "ERROR: detected a dangerous percent encoding (%%00)"); + LOG(WARNING,"ERROR: detected a dangerous percent encoding (%%00)"); return(-1); @@ -464,3 +426,3 @@ string[x - 2] = 0; - syslog(LOG_WARNING, "ERROR: detected an invalid percent encoding"); + LOG(WARNING,"ERROR: detected an invalid percent encoding"); return(-1); @@ -516,23 +478,5 @@ -static void about(char *version, char *datestring, char *homepage) { - printf("Motsognir v%s Copyright (C) Mateusz Viste %s\n\n", version, datestring); - printf("This program is free software: you can redistribute it and/or modify it under\n" - "the terms of the GNU General Public License as published by the Free Software\n" - "Foundation, either version 3 of the License, or (at your option) any later\n" - "version.\n"); - printf("This program is distributed in the hope that it will be useful, but WITHOUT ANY\n" - "WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n" - "PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\n"); - printf("Motsognir is a robust and reliable open-source gopher server for POSIX systems.\n" - "Motsognir is entirely written in ANSI C, without any external dependencies.\n\n"); - printf("Available command-line parameters:\n" - " --config file.conf use a configuration file in a custom location\n" - "\n"); - printf("homepage: %s\n\n", homepage); -} - - static void sendbackhttperror(int sock, struct MotsognirConfig *config) { char txtline[1024], portstr[16]; - syslog(LOG_INFO, "HTTP request detected - a HTTP error message is returned"); + LOG(INFO,"HTTP request detected - a HTTP error message is returned"); sendline(sock, "HTTP/1.1 400 Bad request"); @@ -574,3 +518,3 @@ char txtline[1024]; - syslog(LOG_INFO, "GOPHER+ request detected - a gopher+ fake redirector is returned"); + LOG(INFO,"GOPHER+ request detected - a gopher+ fake redirector is returned"); sendline(sock, "+-1"); @@ -595,3 +539,3 @@ if (res == NULL) { - syslog(LOG_ERR, "ERROR: OUT OF MEMORY ON LINE #%d", __LINE__); + LOG(ERR,"ERROR: OUT OF MEMORY ON LINE #%d", __LINE__); return(res); @@ -618,3 +562,3 @@ -static int loadconfig(struct MotsognirConfig *config, char *configfile) { +static int loadconfig(struct MotsognirConfig *config) { FILE *fd; @@ -623,8 +567,6 @@ int valuebuffpos = 0; - int bytebuff; + int bytebuff, priv_argc; int state = 0; /* 0=reading token, 1=reading value, 2=reading comment */ struct passwd *pw; - - /* zero out the config structure, just in case */ - memset(config, 0, sizeof(*config)); + char *priv_argv[3]; @@ -659,5 +601,5 @@ - fd = fopen(configfile, "r"); + fd = fopen(config->configfile, "r"); if (fd == NULL) { - syslog(LOG_WARNING, "WARNING: Failed to open the configuration file at '%s'", configfile); + LOG(WARNING,"WARNING: Failed to open the configuration file at '%s'", config->configfile); return(-1); @@ -729,5 +671,5 @@ if (config->pluginfilter == NULL) { - syslog(LOG_ERR, "ERROR: Out of memory while trying to allocate regex space!"); + LOG(ERR,"ERROR: Out of memory while trying to allocate regex space!"); } else if (regcomp(config->pluginfilter, valuebuff, REG_EXTENDED | REG_NOSUB) != 0) { - syslog(LOG_ERR, "ERROR: Invalid PluginFilter regex!"); + LOG(ERR,"ERROR: Invalid PluginFilter regex!"); free(config->pluginfilter); @@ -743,3 +685,3 @@ config->httperrfile = readfiletomem(valuebuff); - if (config->httperrfile == NULL) syslog(LOG_WARNING, "WARNING: Failed to load custom http error file '%s'. Default content will be used instead.", valuebuff); + if (config->httperrfile == NULL) LOG(WARNING,"WARNING: Failed to load custom http error file '%s'. Default content will be used instead.", valuebuff); } else if (strcasecmp(tokenbuff, "ExtMapFile") == 0) { @@ -748,2 +690,6 @@ config->securldelim = atoi(valuebuff); + } else if (strcasecmp(tokenbuff, "log") == 0) { + /* simulate a '-l ARG' given on command line */ + priv_argc=3; priv_argv[1]="-l"; priv_argv[2]=strdup(valuebuff); + parseCmdLine(FROM_FILE, priv_argc, priv_argv, config); } @@ -765,3 +711,3 @@ if (config->verbosemode < 0) { - syslog(LOG_ERR, "ERROR: Invalid verbose level found in the configuration file (%d)", config->verbosemode); + LOG(ERR,"ERROR: Invalid verbose level found in the configuration file (%d)", config->verbosemode); return(-1); @@ -770,3 +716,3 @@ if (config->gopherport < 1) { - syslog(LOG_ERR, "ERROR: Invalid gopher port found in the configuration file (%d)", config->gopherport); + LOG(ERR,"ERROR: Invalid gopher port found in the configuration file (%d)", config->gopherport); return(-1); @@ -775,3 +721,3 @@ if (config->gopherroot[0] == 0) { - syslog(LOG_ERR, "ERROR: Missing gopher root path in the configuration file. Please add a valid 'GopherRoot=' directive"); + LOG(ERR,"ERROR: Missing gopher root path in the configuration file. Please add a valid 'GopherRoot=' directive"); return(-1); @@ -782,3 +728,3 @@ if ((config->userdir[0] != '/') || (strstr(config->userdir, "%s") == NULL)) { - syslog(LOG_ERR, "ERROR: The UserDir configuration is invalid. It shall be an absolute path (start by '/') and contain the '%%s' placeholder."); + LOG(ERR,"ERROR: The UserDir configuration is invalid. It shall be an absolute path (start by '/') and contain the '%%s' placeholder."); return(-1); @@ -788,3 +734,3 @@ if (config->gopherhostname == NULL) { - syslog(LOG_WARNING, "WARNING: Missing gopher hostname in the configuration file. The local IP address will be used instead. Please add a valid 'GopherHostname=' directive."); + LOG(WARNING,"WARNING: Missing gopher hostname in the configuration file. The local IP address will be used instead. Please add a valid 'GopherHostname=' directive."); } @@ -794,3 +740,3 @@ if (config->extmap == NULL) { - syslog(LOG_ERR, "ERROR: failed to load the extension mapping file '%s'", config->extmapfile); + LOG(ERR,"ERROR: failed to load the extension mapping file '%s'", config->extmapfile); return(-1); @@ -802,3 +748,3 @@ if (pw == NULL) { - syslog(LOG_ERR, "ERROR: Could not map the username '%s' to a valid uid", config->runasuser); + LOG(ERR,"ERROR: Could not map the username '%s' to a valid uid", config->runasuser); return(-1); @@ -849,3 +795,3 @@ /* Retrieve server-side parameters */ - syslog(LOG_INFO, "Got following server-side parameters: %s | %s", res[0], res[1]); + LOG(INFO,"Got following server-side parameters: %s | %s", res[0], res[1]); return(res); /* return the array with params */ @@ -893,3 +839,3 @@ if ((timeoutStartTime != NULL) && (time(NULL) - *timeoutStartTime >= 10)) { - syslog(LOG_INFO, "Request takes too long to come. Connection aborted."); + LOG(INFO,"Request takes too long to come. Connection aborted."); return(-1); @@ -931,3 +877,3 @@ char linebuff[1024]; - syslog(LOG_INFO, "The request is asking for a URL redirection - returned a html document redirecting to '%s'", rawurl); + LOG(INFO,"The request is asking for a URL redirection - returned a html document redirecting to '%s'", rawurl); sendline(sock, ""); @@ -977,3 +923,3 @@ if (dirptr == NULL) { - syslog(LOG_WARNING, "ERROR: Could not access directory '%s' (%s)", localfile, strerror(errno)); + LOG(WARNING,"ERROR: Could not access directory '%s' (%s)", localfile, strerror(errno)); sendline(sock, "3Error: could not access directory\tfake\tfake\t0"); @@ -985,3 +931,3 @@ if (direntriescount < 0) { - syslog(LOG_WARNING, "ERROR: Failed to scan the directory '%s': %s", localfile, strerror(errno)); + LOG(WARNING,"ERROR: Failed to scan the directory '%s': %s", localfile, strerror(errno)); return; @@ -989,3 +935,3 @@ - syslog(LOG_INFO, "Found %d items in '%s'", direntriescount, localfile); + LOG(INFO,"Found %d items in '%s'", direntriescount, localfile); @@ -1095,5 +1041,5 @@ if ((srvsideparams[0] != NULL) || (srvsideparams[1] != NULL)) { - syslog(LOG_INFO, "running server-side app '%s' with queries '%s' + '%s'", localfile, srvsideparams[0], srvsideparams[1]); + LOG(INFO,"running server-side app '%s' with queries '%s' + '%s'", localfile, srvsideparams[0], srvsideparams[1]); } else { - syslog(LOG_INFO, "running server-side app '%s'", localfile); + LOG(INFO,"running server-side app '%s'", localfile); } @@ -1127,3 +1073,3 @@ if (cgifd == NULL) { - syslog(LOG_WARNING, "ERROR: failed to run the server-side app '%s'", localfile); + LOG(WARNING,"ERROR: failed to run the server-side app '%s'", localfile); return(0); @@ -1147,3 +1093,3 @@ if (explodegophermapline(tmpstring, &itemtype, itemdesc, itemselector, itemserver, &itemport) != 0) { - syslog(LOG_WARNING, "ERROR: dynamic gophermap processing aborted due to failure to interpret its output as being a gophermap line (%s)", localfile); + LOG(WARNING,"ERROR: dynamic gophermap processing aborted due to failure to interpret its output as being a gophermap line (%s)", localfile); break; @@ -1166,5 +1112,5 @@ if (res == -1) { - syslog(LOG_WARNING, "WARNING: call to server-side app '%s' failed (%s)", localfile, strerror(errno)); + LOG(WARNING,"WARNING: call to server-side app '%s' failed (%s)", localfile, strerror(errno)); } else if (WEXITSTATUS(res) != 0) { - syslog(LOG_WARNING, "WARNING: server-side app '%s' terminated with a non-zero exit code (%d)", localfile, WEXITSTATUS(res)); + LOG(WARNING,"WARNING: server-side app '%s' terminated with a non-zero exit code (%d)", localfile, WEXITSTATUS(res)); } @@ -1194,6 +1140,6 @@ if (gophermapfd == NULL) { - syslog(LOG_WARNING, "ERROR: Failed to open the gophermap at '%s' (%s)", gophermapfile, strerror(errno)); + LOG(WARNING,"ERROR: Failed to open the gophermap at '%s' (%s)", gophermapfile, strerror(errno)); return; } - syslog(LOG_INFO, "Response=\"Return gophermap. (%s)", gophermapfile); + LOG(INFO,"Response=\"Return gophermap. (%s)", gophermapfile); @@ -1217,3 +1163,3 @@ if (realscriptname == NULL) { - syslog(LOG_WARNING, "WARNING: Failed to resolve the path to '%s'", itemdesc); + LOG(WARNING,"WARNING: Failed to resolve the path to '%s'", itemdesc); } else { @@ -1240,3 +1186,3 @@ char gophermapfile[1024]; - syslog(LOG_INFO, "The resource is a directory"); + LOG(INFO,"The resource is a directory"); if (lastcharofstring(localfile) != '/') strcat(localfile, "/"); @@ -1274,3 +1220,3 @@ /* no gophermap found, simply list files & directories */ - syslog(LOG_INFO, "No gophermap found. Listing directory content"); + LOG(INFO,"No gophermap found. Listing directory content"); outputdircontent(sock, config, localfile, directorytolist); @@ -1297,3 +1243,3 @@ if (sockmaster < 0) { - syslog(LOG_WARNING, "FATAL ERROR: socket could not be open (%s)", strerror(errno)); + LOG(WARNING,"FATAL ERROR: socket could not be open (%s)", strerror(errno)); return(-2); @@ -1302,3 +1248,3 @@ /* I set the socket to be reusable, to avoid having to wait for a longish time when the server is restarted */ - if (setsockopt(sockmaster, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) syslog(LOG_WARNING, "WARNING: failed to set REUSEADDR on main socket"); + if (setsockopt(sockmaster, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) LOG(WARNING,"WARNING: failed to set REUSEADDR on main socket"); @@ -1311,3 +1257,3 @@ if (inet_pton(AF_INET6, config->bind, &(serv_addr.sin6_addr)) != 1) { - syslog(LOG_WARNING, "FATAL ERROR: failed to parse the IP address bind value. Please check your 'bind' configuration."); + LOG(WARNING,"FATAL ERROR: failed to parse the IP address bind value. Please check your 'bind' configuration."); return(-2); @@ -1327,3 +1273,3 @@ if (bind(sockmaster, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { - syslog(LOG_WARNING, "FATAL ERROR: binding failed (%s)", strerror(errno)); + LOG(WARNING,"FATAL ERROR: binding failed (%s)", strerror(errno)); return(-2); @@ -1341,3 +1287,3 @@ - syslog(LOG_INFO, "motsognir v" pVer " process started"); + LOG(INFO,"motsognir v" pVer " process started"); @@ -1352,3 +1298,3 @@ close(sockmaster); - syslog(LOG_WARNING, "Failed to dameonize the motsognir process (%s)", strerror(errno)); + LOG(WARNING,"Failed to dameonize the motsognir process (%s)", strerror(errno)); return(-2); @@ -1365,3 +1311,3 @@ /* I want to be the pack master now (aka session leader) */ - if (setsid() == -1) syslog(LOG_WARNING, "WARNING: setsid() failed (%s)", strerror(errno)); + if (setsid() == -1) LOG(WARNING,"WARNING: setsid() failed (%s)", strerror(errno)); @@ -1371,3 +1317,3 @@ if (chroot(config->chroot) != 0) { - syslog(LOG_WARNING, "Failed to chroot(): %s", strerror(errno)); + LOG(WARNING,"Failed to chroot(): %s", strerror(errno)); return(-2); @@ -1377,3 +1323,3 @@ /* set the working directory to the root directory */ - if (chdir ("/") == -1) syslog(LOG_WARNING, "WARNING: failed to switch to / directory (%s)", strerror(errno)); + if (chdir ("/") == -1) LOG(WARNING,"WARNING: failed to switch to / directory (%s)", strerror(errno)); @@ -1385,3 +1331,3 @@ if (getuid() != 0) { - syslog(LOG_WARNING, "A 'RunAsUser' directive has been configured, but the process has not been launched under root account. The 'RunAsUser' directive is therefore ignored."); + LOG(WARNING,"A 'RunAsUser' directive has been configured, but the process has not been launched under root account. The 'RunAsUser' directive is therefore ignored."); } else { /* if I'm root, drop off privileges */ @@ -1390,3 +1336,3 @@ } else { - syslog(LOG_WARNING, "Successfully dropped root privileges. Motsognir runs as user '%s' now.", config->runasuser); + LOG(WARNING,"Successfully dropped root privileges. Motsognir runs as user '%s' now.", config->runasuser); } @@ -1399,3 +1345,3 @@ if (sockslave < 0) { - syslog(LOG_WARNING, "FATAL ERROR: accepting connection failed (%s)", strerror(errno)); + LOG(WARNING,"FATAL ERROR: accepting connection failed (%s)", strerror(errno)); close(sockmaster); @@ -1410,3 +1356,3 @@ if (inet_ntop(cli_addr.sin6_family, &cli_addr.sin6_addr, clientipaddrstr, clientipaddrstr_maxlen) == NULL) { - syslog(LOG_WARNING, "Failed to fetch client's IP address: %s", strerror(errno)); + LOG(WARNING,"Failed to fetch client's IP address: %s", strerror(errno)); sprintf(clientipaddrstr, "UNKNOWN"); @@ -1416,3 +1362,3 @@ if ((getsockname(sockslave, (struct sockaddr *) &serv_addr, &clilen) < 0) || (inet_ntop(serv_addr.sin6_family, &serv_addr.sin6_addr, serveripaddrstr, serveripaddrstr_maxlen) == NULL)) { - syslog(LOG_WARNING, "Failed to fetch server's IP address: %s", strerror(errno)); + LOG(WARNING,"Failed to fetch server's IP address: %s", strerror(errno)); sprintf(serveripaddrstr, "UNKNOWN"); @@ -1423,5 +1369,4 @@ /* set logprefix to contain the client's address */ - sprintf(logprefix, "motsognir [%s]", clientipaddrstr); - openlog(logprefix, LOG_PID, LOG_DAEMON); /* set up the logging to log with PID and peer's IP address */ - syslog(LOG_INFO, "new connection to %s", serveripaddrstr); + logme(SET_PREFIX, "motsognir [%s]", clientipaddrstr, logprefix); + LOG(INFO,"new connection to %s", serveripaddrstr); /* if no gopher hostname was set, use the server's address */ @@ -1435,3 +1380,3 @@ } else { /* error condition */ - syslog(LOG_WARNING, "FATAL ERROR: fork() failed!"); + LOG(WARNING,"FATAL ERROR: fork() failed!"); close(sockslave); @@ -1452,3 +1397,3 @@ if (linebuff == NULL) { - syslog(LOG_WARNING, "ERROR: Out of memory while trying to allocate buffer for file"); + LOG(WARNING,"ERROR: Out of memory while trying to allocate buffer for file"); return; @@ -1457,3 +1402,3 @@ if (fd == NULL) { /* file could not be opened */ - syslog(LOG_WARNING, "ERROR: File '%s' could not be opened", filename); + LOG(WARNING,"ERROR: File '%s' could not be opened", filename); free(linebuff); @@ -1478,3 +1423,3 @@ if (buff == NULL) { - syslog(LOG_WARNING, "ERROR: Out of memory while trying to allocate buffer for file"); + LOG(WARNING,"ERROR: Out of memory while trying to allocate buffer for file"); return; @@ -1483,3 +1428,3 @@ if (fd == NULL) { /* file could not be opened */ - syslog(LOG_WARNING, "ERROR: File '%s' could not be opened", filename); + LOG(WARNING,"ERROR: File '%s' could not be opened", filename); free(buff); @@ -1526,3 +1471,3 @@ } - syslog(LOG_WARNING, "Evasion check: path '%s' (%s) seem to belong to neither '%s' nor any entry of the pubdir list", localfile, resolvedpath, gopherroot); + LOG(WARNING,"Evasion check: path '%s' (%s) seem to belong to neither '%s' nor any entry of the pubdir list", localfile, resolvedpath, gopherroot); return(1); @@ -1618,3 +1563,3 @@ if ((curdir == NULL) || (chdir(curdir) == -1)) { - syslog(LOG_WARNING, "WARNING: failed to switch current directory to %s (%s), original resource: %s", curdir, strerror(errno), s); + LOG(WARNING,"WARNING: failed to switch current directory to %s (%s), original resource: %s", curdir, strerror(errno), s); res = -1; @@ -1642,4 +1587,3 @@ char gophertype; - char *configfile = CONFIGFILE; - int sock; + int sock, status; struct MotsognirConfig config; @@ -1647,17 +1591,11 @@ - if (argc > 1) { - int x; - for (x = 1; x < argc; x++) { - if (strcmp(argv[x], "--config") == 0) { - x++; - if (x < argc) configfile = argv[x]; - } else { /* unknown command line */ - about(pVer, pDate, HOMEPAGE); - return(1); - } - } - } + /* zero out the config structure, just in case */ + memset(&config, 0, sizeof(config)); + + if ((status = parseCmdLine(FIRST_PASS, argc, argv, &config)) != EXIT_SUCCESS) + return (status==OK_EXIT_NOW) ? EXIT_SUCCESS : status; + /* else... */ /* load motsognir's configuration from file */ - if (loadconfig(&config, configfile) != 0) { + if (loadconfig(&config) != 0) { puts("ERROR: A configuration error has been detected. Check the logs for details."); @@ -1665,2 +1603,7 @@ } + /* Again?! Yep... Command line args must override config file + directives. But we also needed a 1st pass to possibly learn + where the config file was... */ + if ((status = parseCmdLine(LAST_PASS, argc, argv, &config)) != EXIT_SUCCESS) + return (status==OK_EXIT_NOW) ? EXIT_SUCCESS : status; @@ -1676,3 +1619,3 @@ if (sockreadline(sock, directorytolist, sizeof(directorytolist), &StartTime) < 0) { - syslog(LOG_WARNING, "Error during selector receiving phase. Connection aborted."); + LOG(WARNING,"Error during selector receiving phase. Connection aborted."); close(sock); @@ -1680,3 +1623,3 @@ } - syslog(LOG_INFO, "Query='%s'", directorytolist); + LOG(INFO,"Query='%s'", directorytolist); if (directorytolist[0] == 0) { /* Empty request means "gimme the root listing" */ @@ -1698,3 +1641,3 @@ if (res > 0) { - syslog(LOG_INFO, "Query handled by plugin (%s)", config.plugin); + LOG(INFO,"Query handled by plugin (%s)", config.plugin); drainsock(sock); /* read whatever request the peer sent us, to drain the socket before closing it (otherwise the tcp stack would trigger a ugly RST) */ @@ -1742,3 +1685,3 @@ if (percdecode(directorytolist) != 0) { - syslog(LOG_WARNING, "Percent decoding on request failed. Query aborted."); + LOG(WARNING,"Percent decoding on request failed. Query aborted."); return(0); @@ -1749,3 +1692,3 @@ if (securitycheckresult != NULL) { - syslog(LOG_INFO, "The gopher security module has detected a suspect condition. The query won't be processed. Reason: %s", securitycheckresult); + LOG(INFO,"The gopher security module has detected a suspect condition. The query won't be processed. Reason: %s", securitycheckresult); close(sock); @@ -1761,6 +1704,6 @@ - syslog(LOG_INFO, "Requested resource: %s / Local resource: %s", directorytolist, localfile); + LOG(INFO,"Requested resource: %s / Local resource: %s", directorytolist, localfile); if (checkforevasion(rootdir, config.pubdirlist, localfile) != 0) { - syslog(LOG_INFO, "Evasion attempt. Forbidden!"); + LOG(INFO,"Evasion attempt. Forbidden!"); sendline(sock, "iForbidden!\tfake\tfake\t0"); @@ -1772,3 +1715,3 @@ if (is_it_a_directory(localfile) != 0) { - if (chdir(localfile) != 0) syslog(LOG_WARNING, "WARNING: failed to switch to directory '%s'", localfile); + if (chdir(localfile) != 0) LOG(WARNING,"WARNING: failed to switch to directory '%s'", localfile); outputdir(sock, &config, localfile, directorytolist, remoteclientaddr, srvsideparams); @@ -1782,3 +1725,3 @@ if (changedir(localfile) != 0) { - syslog(LOG_INFO, "ERROR: changedir() failure for '%s'", localfile); + LOG(INFO,"ERROR: changedir() failure for '%s'", localfile); sendline(sock, "iForbidden!\tfake\tfake\t0"); @@ -1790,3 +1733,3 @@ if ((strcmp(directorytolist, "/caps.txt") == 0) && (config.capssupport != 0)) { /* If asking for /caps.txt, return it. */ - syslog(LOG_INFO, "Returned caps.txt data"); + LOG(INFO,"Returned caps.txt data"); printcapstxt(sock, &config, pVer); @@ -1800,3 +1743,3 @@ if ((fexist(localfile) == 0) || (islocalfileagophermap(localfile) != 0)) { - syslog(LOG_INFO, "FileExists check: the file doesn't exists"); + LOG(INFO,"FileExists check: the file doesn't exists"); sendline(sock, "3The selected resource doesn't exist!\tfake\tfake\t0"); @@ -1813,3 +1756,3 @@ /* error while reading attributes */ - syslog(LOG_INFO, "stat() failed: %s", strerror(errno)); + LOG(INFO,"stat() failed: %s", strerror(errno)); sendline(sock, "3Internal error\tfake\tfake\t0"); @@ -1821,3 +1764,3 @@ /* not world-readable */ - syslog(LOG_INFO, "Paranoid mode check failed: file is not world-readable"); + LOG(INFO,"Paranoid mode check failed: file is not world-readable"); sendline(sock, "3Permission denied\tfake\tfake\t0"); @@ -1845,3 +1788,3 @@ /* we want a normal file's content */ - syslog(LOG_INFO, "Returning file '%s'", localfile); + LOG(INFO,"Returning file '%s'", localfile); gophertype = DetectGopherType(localfile, config.extmap); @@ -1860,3 +1803,3 @@ close(sock); - syslog(LOG_INFO, "connection closed. duration: %us", (unsigned int)(time(NULL) - StartTime)); + LOG(INFO,"connection closed. duration: %us", (unsigned int)(time(NULL) - StartTime)); return(0); ======================================================================== --- motsognir/motsognir.h (nonexistent) +++ motsognir+logmore/motsognir.h 2018-08-05 19:41:31.004474968 +0200 @@ -0,0 +1,83 @@ +/* + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * Motsognir - The mighty gopher server * + * * Copyright (C) 2008-2016 Mateusz Viste * + * * http://motsognir.sourceforge.net * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Some changes and additions: Copyright (C) 2018 Dario Niedermann + * $Id: motsognir109_logmore1.0.diff 6 2019-04-01 16:30:00Z ndr $ + * ---------------------------------------------------------------------- + * 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 . + * ---------------------------------------------------------------------- + */ + +#include /* regcomp(), regexec()... */ + +/* Constants */ +#define pVer "1.0.9+logmore1.0" +#define pDate "2008-2018" +#define HOMEPAGE "http://motsognir.sourceforge.net" +#define OK_EXIT_NOW -1 + + /* logging: */ +#define TO_SYSLOG 0 +#define TO_FILE 1 +#define INIT 65536 /* just needs to be a # not used in syslog.h */ +#define SET_PREFIX 65537 /* just needs to be a # not used in syslog.h */ + + /* parseCmdLine() operating modes: */ +#define FIRST_PASS 0 +#define FROM_FILE 1 +#define LAST_PASS 2 + +struct MotsognirConfig { + char *gopherroot; + char *userdir; + char **pubdirlist; + int gopherport; + char *gopherhostname; + char *defaultgophermap; + int verbosemode; + int capssupport; + char *capsservergeolocationstring; + char *capsserverarchitecture; + char *capsserverdescription; + char *capsserverdefaultencoding; + int cgisupport; + int phpsupport; + int subgophermaps; + int paranoidmode; + char *plugin; + regex_t *pluginfilter; + char *runasuser; + uid_t runasuser_uid; + gid_t runasuser_gid; + char *runasuser_home; + char *chroot; + char *httperrfile; + char *bind; + char *extmapfile; + struct extmap_t *extmap; + char *configfile; + char securldelim; +}; + +struct logParams { + char loggingType; + int facility; + char *fileName; + int *localFacility; /* upon struct creation, point me to an array of + syslog facility values. Like in parseCmdLine() */ +}; ==================================================END==OF==PATCH=*=*=*** -----BEGIN PGP SIGNATURE----- iQIcBAEBAgAGBQJbcpi8AAoJEPJFaEEGrTgGxtgP/A4x/ff8d+WhbFL3EZeRvFSk 7sSdGObjo3NdhmJavYMcSB6EFuQ4rWH801LDlM5PeJeMJ6BNJlrfu4kQniSECoeK GbLrA7osydjYd5EJfiUVx+UTtfF3OzIj9MyYW4MM4vaewuoXPTmS+M0f235rOuv3 ebo02yHI1f5sw6um9DfRf9lL9j2s4RAGdVjgawZSFa8ydOpHr9iiHKCtAKwZWsCY 2bv+LFbZT73asa3O7/REqmYlFkkGOdagSobdg6spAtPc8cEveAMJPOy3cHzAo08I IDWshlYojynDfNBQ+SXMaxnqblFqegjOqv1ZX4LBz4p6KBcyjHD2e0njyk9CrCtN LMS4WhVEfoFh5bpbbvv2PuEkXg9R4S5pdTXFPAarthNFQeSiqbc+yNQNhDmiwSFD vmY9wjEayo03HFjp7b9xxJnzwGLmqfz5frJGXyWPEAfzdv332y+cXmCWsU9xb9FC pMJLMvn5i3gRD4/fh8ppBqUsHrIbpCRj70+GQSxPSB4lPOuUbIonGafT3UZ5c7Xn i7o2Kt56nhSDuiDLnEE1UqsaqkAirkI1j3nEBsEV+YyhaFLiV+DxMTqymY7CM29q XjseWpdJx8FOxANlXBOVO+m15wntQknsdlWIqhNmKIWjY0W0HIMyE6z0eRNW9u+L prIZZ5HQUndPIst9O/nA =cu5F -----END PGP SIGNATURE-----