OSSP CVS Repository

ossp - Check-in [1461]
Not logged in
[Honeypot]  [Browse]  [Home]  [Login]  [Reports
[Search]  [Ticket]  [Timeline
  [Patchset]  [Tagging/Branching

Check-in Number: 1461
Date: 2001-Dec-31 12:09:52 (local)
2001-Dec-31 11:09:52 (UTC)
User:thl
Branch:
Comment: Mega-Commit: Finally restructure the lmtp2nntp source tree in order to clean it up. We especially use a consistent prefix for all inlined sources.
Tickets:
Inspections:
Files:
ossp-pkg/lmtp2nntp/Makefile.in      1.30 -> 1.31     8 inserted, 16 deleted
ossp-pkg/lmtp2nntp/README      1.14 -> 1.15     1 inserted, 1 deleted
ossp-pkg/lmtp2nntp/argz.c      1.3->removed
ossp-pkg/lmtp2nntp/argz.h      1.2->removed
ossp-pkg/lmtp2nntp/autogen.sh      1.10 -> 1.11     12 inserted, 12 deleted
ossp-pkg/lmtp2nntp/configure.ac      1.16 -> 1.17     18 inserted, 18 deleted
ossp-pkg/lmtp2nntp/daemon.c      1.4->removed
ossp-pkg/lmtp2nntp/daemon.h      1.2->removed
ossp-pkg/lmtp2nntp/lmtp.c      1.23->removed
ossp-pkg/lmtp2nntp/lmtp.h      1.11->removed
ossp-pkg/lmtp2nntp/lmtp2nntp.c      1.100->removed
ossp-pkg/lmtp2nntp/lmtp2nntp.h      1.6->removed
ossp-pkg/lmtp2nntp/lmtp2nntp_argz.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_argz.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_daemon.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_daemon.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_dotconf.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_dotconf.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_global.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_lmtp.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_lmtp.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_main.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_msg.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_msg.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_nntp.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_nntp.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_shpat.c      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_shpat.h      added-> 1.1
ossp-pkg/lmtp2nntp/lmtp2nntp_version.c      added-> 1.1
ossp-pkg/lmtp2nntp/msg.c      1.22->removed
ossp-pkg/lmtp2nntp/msg.h      1.7->removed
ossp-pkg/lmtp2nntp/nntp.c      1.27->removed
ossp-pkg/lmtp2nntp/nntp.h      1.13->removed
ossp-pkg/lmtp2nntp/shpat_match.c      1.4->removed
ossp-pkg/lmtp2nntp/shpat_match.h      1.2->removed
ossp-pkg/lmtp2nntp/version.c      1.16->removed

ossp-pkg/lmtp2nntp/Makefile.in 1.30 -> 1.31

--- Makefile.in  2001/12/11 12:02:26     1.30
+++ Makefile.in  2001/12/31 11:09:52     1.31
@@ -44,9 +44,9 @@
 POD2MAN     = pod2man
 
 PROG = lmtp2nntp
-HDRS = lmtp2nntp.h daemon.h lmtp.h nntp.h argz.h shpat_match.h msg.h 
-SRCS = lmtp2nntp.c daemon.c lmtp.c nntp.c argz.c shpat_match.c msg.c version.c
-OBJS = lmtp2nntp.o daemon.o lmtp.o nntp.o argz.o shpat_match.o msg.o version.o
+HDRS = lmtp2nntp_global.h lmtp2nntp_daemon.h lmtp2nntp_lmtp.h lmtp2nntp_nntp.h lmtp2nntp_argz.h lmtp2nntp_shpat.h lmtp2nntp_msg.h 
+SRCS = lmtp2nntp_main.c lmtp2nntp_daemon.c lmtp2nntp_lmtp.c lmtp2nntp_nntp.c lmtp2nntp_argz.c lmtp2nntp_shpat.c lmtp2nntp_msg.c lmtp2nntp_version.c
+OBJS = lmtp2nntp_main.o lmtp2nntp_daemon.o lmtp2nntp_lmtp.o lmtp2nntp_nntp.o lmtp2nntp_argz.o lmtp2nntp_shpat.o lmtp2nntp_msg.o lmtp2nntp_version.o
 
 SUBDIRS = @SUBDIR_STR@ @SUBDIR_L2@ @SUBDIR_SA@ @SUBDIR_VAR@
 
@@ -77,8 +77,8 @@
         $(CC) $(LDFLAGS) -o $(PROG) $(OBJS) $(LIBS)
 
 lmtp2nntp.8: lmtp2nntp.pod
-        VS=`$(SHTOOL) version -lc -dshort version.c`; \
-        VL=`$(SHTOOL) version -lc -dlong version.c`; \
+        VS=`$(SHTOOL) version -lc -dshort lmtp2nntp_version.c`; \
+        VL=`$(SHTOOL) version -lc -dlong lmtp2nntp_version.c`; \
         $(POD2MAN) --section=8 --center="mail to news gateway" \
                --release="$$VS" --date="$$VL" \
                lmtp2nntp.pod >lmtp2nntp.8
@@ -111,13 +111,13 @@
         -rm -f shtool
 
 new-version:
-        @$(SHTOOL) version -lc -n lmtp2nntp -p lmtp2nntp_ -e version.c; \
-        V=`$(SHTOOL) version -lc -dlong version.c`; \
+        @$(SHTOOL) version -lc -n lmtp2nntp -p lmtp2nntp_ -e lmtp2nntp_version.c; \
+        V=`$(SHTOOL) version -lc -dlong lmtp2nntp_version.c`; \
         sed -e "s/Version .*(.*)/Version $$V/g" <README >README.n && mv README.n README
 
 dist: distclean
         @$(SHTOOL) fixperm -v .; \
-        V=`$(SHTOOL) version -lc -dshort version.c`; \
+        V=`$(SHTOOL) version -lc -dshort lmtp2nntp_version.c`; \
         $(SHTOOL) tarball -o lmtp2nntp-$${V}.tar.gz -d lmtp2nntp-$${V} -u ossp -g ossp \
                           -e 'CVS,\.cvsignore,\.[ao],^\.,/\.,autogen.sh,*.tar.gz,*.bak,TODO' -c 'gzip --best' .
 
@@ -138,11 +138,3 @@
 $(OBJS): Makefile
 
 #   AUTOMATICALLY GENERATED DEPENDENCY LIST - DO NOT EDIT
-lmtp2nntp.o: lmtp2nntp.c argz.h shpat_match.h daemon.h lmtp2nntp.h config.h lmtp.h nntp.h msg.h version.c
-daemon.o: daemon.c config.h daemon.h
-lmtp.o: lmtp.c lmtp.h config.h
-nntp.o: nntp.c nntp.h msg.h lmtp2nntp.h config.h
-argz.o: argz.c argz.h config.h
-shpat_match.o: shpat_match.c config.h shpat_match.h
-msg.o: msg.c msg.h lmtp2nntp.h config.h argz.h
-version.o: version.c version.c


ossp-pkg/lmtp2nntp/README 1.14 -> 1.15

--- README       2001/12/11 11:53:11     1.14
+++ README       2001/12/31 11:09:52     1.15
@@ -7,7 +7,7 @@
                   |_|                        |_|    
 
   OSSP lmtp2nntp - Mail to News Gateway
-  Version 1.2a1 (06-Dec-2001)
+  Version 1.2a2 (31-Dec-2001)
 
   ABSTRACT
 


ossp-pkg/lmtp2nntp/argz.c 1.3 -> 1.4



ossp-pkg/lmtp2nntp/argz.h 1.2 -> 1.3



ossp-pkg/lmtp2nntp/autogen.sh 1.10 -> 1.11

--- autogen.sh   2001/12/31 10:23:09     1.10
+++ autogen.sh   2001/12/31 11:09:52     1.11
@@ -64,16 +64,16 @@
 shtoolize -q all
 
 #   perform same operation in subdirs
-echo "===> str (autogen.sh)"
-(cd str && ./devtool autogen)
-echo "<=== str"
-echo "===> l2 (devtool)"
-(cd l2 && ./devtool autogen)
-echo "<=== l2"
-echo "===> sa (devtool)"
-(cd sa && ./devtool autogen)
-echo "<=== sa"
-echo "===> var (devtool)"
-(cd var && ./devtool autogen)
-echo "<=== var"
+echo "===> lib_str (autogen.sh)"
+(cd lib_str && ./devtool autogen)
+echo "<=== lib_str"
+echo "===> lib_l2 (devtool)"
+(cd lib_l2 && ./devtool autogen)
+echo "<=== lib_l2"
+echo "===> lib_sa (devtool)"
+(cd lib_sa && ./devtool autogen)
+echo "<=== lib_sa"
+echo "===> lib_var (devtool)"
+(cd lib_var && ./devtool autogen)
+echo "<=== lib_var"
 


ossp-pkg/lmtp2nntp/configure.ac 1.16 -> 1.17

--- configure.ac 2001/12/11 12:02:26     1.16
+++ configure.ac 2001/12/31 11:09:52     1.17
@@ -27,7 +27,7 @@
 dnl #   standard preamble
 AC_PREREQ(2.52)
 AC_REVISION(1.0)
-AC_INIT(lmtp2nntp.c)
+AC_INIT(lmtp2nntp_main.c)
 
 dnl #   announce our version
 AC_DIVERT_PUSH(NOTICE)
@@ -52,10 +52,10 @@
 AC_CHECK_EXTLIB([OSSP Str], 
                 str, str_parse, str.h, 
                 [SUBDIR_STR=""], 
-                [SUBDIR_STR="str"
-                 CPPFLAGS="$CPPFLAGS -Istr"
-                 CFLAGS="$CFLAGS -Istr"
-                 LDFLAGS="$LDFLAGS -Lstr/.libs"
+                [SUBDIR_STR="lib_str"
+                 CPPFLAGS="$CPPFLAGS -Ilib_str"
+                 CFLAGS="$CFLAGS -Ilib_str"
+                 LDFLAGS="$LDFLAGS -Llib_str/.libs"
                  LIBS_EXTRA="$LIBS_EXTRA -lstr"])
 AC_SUBST(SUBDIR_STR)
 
@@ -63,10 +63,10 @@
 AC_CHECK_EXTLIB([OSSP L2], 
                 l2, l2_stream_log, l2.h, 
                 [SUBDIR_L2=""], 
-                [SUBDIR_L2="l2"
-                 CPPFLAGS="$CPPFLAGS -Il2"
-                 CFLAGS="$CFLAGS -Il2"
-                 LDFLAGS="$LDFLAGS -Ll2/.libs"
+                [SUBDIR_L2="lib_l2"
+                 CPPFLAGS="$CPPFLAGS -Ilib_l2"
+                 CFLAGS="$CFLAGS -Ilib_l2"
+                 LDFLAGS="$LDFLAGS -Llib_l2/.libs"
                  LIBS_EXTRA="$LIBS_EXTRA -ll2"])
 AC_SUBST(SUBDIR_L2)
 
@@ -74,21 +74,21 @@
 AC_CHECK_EXTLIB([OSSP SA], 
                 sa, sa_create, sa.h, 
                 [SUBDIR_SA=""], 
-                [SUBDIR_SA="sa"
-                 CPPFLAGS="$CPPFLAGS -Isa"
-                 CFLAGS="$CFLAGS -Isa"
-                 LDFLAGS="$LDFLAGS -Lsa/.libs"
+                [SUBDIR_SA="lib_sa"
+                 CPPFLAGS="$CPPFLAGS -Ilib_sa"
+                 CFLAGS="$CFLAGS -Ilib_sa"
+                 LDFLAGS="$LDFLAGS -Llib_sa/.libs"
                  LIBS_EXTRA="$LIBS_EXTRA -lsa"])
 AC_SUBST(SUBDIR_SA)
 
 dnl #   check for VAR library
-AC_CHECK_EXTLIB([OSSP VAR], 
+AC_CHECK_EXTLIB([OSSP Var], 
                 var, var_expand, var.h, 
                 [SUBDIR_VAR=""], 
-                [SUBDIR_VAR="var"
-                 CPPFLAGS="$CPPFLAGS -Ivar"
-                 CFLAGS="$CFLAGS -Ivar"
-                 LDFLAGS="$LDFLAGS -Lvar/.libs"
+                [SUBDIR_VAR="lib_var"
+                 CPPFLAGS="$CPPFLAGS -Ilib_var"
+                 CFLAGS="$CFLAGS -Ilib_var"
+                 LDFLAGS="$LDFLAGS -Llib_var/.libs"
                  LIBS_EXTRA="$LIBS_EXTRA -lvar"])
 AC_SUBST(SUBDIR_VAR)
 


ossp-pkg/lmtp2nntp/daemon.c 1.4 -> 1.5



ossp-pkg/lmtp2nntp/daemon.h 1.2 -> 1.3



ossp-pkg/lmtp2nntp/lmtp.c 1.23 -> 1.24



ossp-pkg/lmtp2nntp/lmtp.h 1.11 -> 1.12



ossp-pkg/lmtp2nntp/lmtp2nntp.c 1.100 -> 1.101



ossp-pkg/lmtp2nntp/lmtp2nntp.h 1.6 -> 1.7



ossp-pkg/lmtp2nntp/lmtp2nntp_argz.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,429 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  argz.h: Zero-Terminated Argument Vector library
+ */
+ 
+ /* Routines for dealing with '\0' separated arg vectors.
+    Copyright (C) 1996, 1997 Free Software Foundation, Inc.
+    This file is part of the GNU C Library.
+    Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1996.
+ 
+    The GNU C Library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.
+ 
+    The GNU C Library 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
+    Library General Public License for more details.
+ 
+    You should have received a copy of the GNU Library General Public
+    License along with the GNU C Library; see the file COPYING.LIB.  If not,
+    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+    Boston, MA 02111-1307, USA.  */
+ 
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <string.h>
+ 
+ #include "lmtp2nntp_argz.h"
+ 
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ 
+ /* Find the length of STRING, but scan at most MAXLEN characters.
+    If no '\0' terminator is found in that many characters, return MAXLEN.  */
+ static size_t my_strnlen(const char *string, size_t maxlen)
+ {
+     const char *end = memchr(string, '\0', maxlen);
+     return end ? (size_t) (end - string) : maxlen;
+ }
+ 
+ static char *my_strndup(const char *s, size_t n)
+ {
+     size_t len = my_strnlen(s, n);
+     char *new = malloc(len + 1);
+ 
+     if (new == NULL)
+         return NULL;
+     new[len] = '\0';
+     return (char *)memcpy(new, s, len);
+ }
+ 
+ /* Copy SRC to DEST, returning the address of the terminating '\0' in DEST. */
+ static char *my_stpcpy(char *dest, const char *src)
+ {
+     register char *d = dest;
+     register const char *s = src;
+ 
+     do {
+       *d++ = *s;
+     } while (*s++ != '\0');
+     return d - 1;
+ }
+ 
+ int argz_add_sep(char **argz, size_t * argz_len, const char *string, int delim)
+ {
+        size_t nlen = strlen(string) + 1;
+ 
+        if (nlen > 1) {
+                const char *rp;
+                char *wp;
+ 
+                *argz = (char *)realloc(*argz, *argz_len + nlen);
+                if (*argz == NULL)
+                        return ENOMEM;
+ 
+                wp = *argz + *argz_len;
+                rp = string;
+                do {
+                        if (*rp == delim) {
+                                if (wp > *argz && wp[-1] != '\0')
+                                        *wp++ = '\0';
+                                else
+                                        --nlen;
+                        }
+                        else
+                                *wp++ = *rp;
+                } while (*rp++ != '\0');
+ 
+                *argz_len += nlen;
+        }
+ 
+        return 0;
+ }
+ 
+ /* Add BUF, of length BUF_LEN to the argz vector in ARGZ & ARGZ_LEN.  */
+ int argz_append(char **argz, size_t * argz_len, const char *buf, size_t buf_len)
+ {
+        size_t new_argz_len = *argz_len + buf_len;
+        char *new_argz = realloc(*argz, new_argz_len);
+        if (new_argz) {
+                memcpy(new_argz + *argz_len, buf, buf_len);
+                *argz = new_argz;
+                *argz_len = new_argz_len;
+                return 0;
+        }
+        else
+                return ENOMEM;
+ }
+ 
+ /* Add STR to the argz vector in ARGZ & ARGZ_LEN.  This should be moved into
+  * argz.c in libshouldbelibc.  */
+ int argz_add(char **argz, size_t * argz_len, const char *str)
+ {
+        return argz_append(argz, argz_len, str, strlen(str) + 1);
+ }
+ 
+ /* Returns the number of strings in ARGZ.  */
+ size_t argz_count(const char *argz, size_t len)
+ {
+        size_t count = 0;
+        while (len > 0) {
+                size_t part_len = strlen(argz);
+                argz += part_len + 1;
+                len -= part_len + 1;
+                count++;
+        }
+        return count;
+ }
+ 
+ /* Make a '\0' separated arg vector from a unix argv vector, returning it in
+  * ARGZ, and the total length in LEN.  If a memory allocation error occurs,
+  * ENOMEM is returned, otherwise 0.  */
+ int argz_create(char *const argv[], char **argz, size_t * len)
+ {
+        int argc;
+        size_t tlen = 0;
+        char *const *ap;
+        char *p;
+ 
+        for (argc = 0; argv[argc] != NULL; ++argc)
+                tlen += strlen(argv[argc]) + 1;
+ 
+        if (tlen == 0)
+                *argz = NULL;
+        else {
+                *argz = malloc(tlen);
+                if (*argz == NULL)
+                        return ENOMEM;
+ 
+                for (p = *argz, ap = argv; *ap; ++ap, ++p)
+                        p = my_stpcpy(p, *ap);
+        }
+        *len = tlen;
+ 
+        return 0;
+ }
+ 
+ int argz_create_sep(const char *string, int delim, char **argz, size_t * len)
+ {
+        size_t nlen = strlen(string) + 1;
+ 
+        if (nlen > 1) {
+                const char *rp;
+                char *wp;
+ 
+                *argz = (char *)malloc(nlen);
+                if (*argz == NULL)
+                        return ENOMEM;
+ 
+                rp = string;
+                wp = *argz;
+                do
+                        if (*rp == delim) {
+                                if (wp > *argz && wp[-1] != '\0')
+                                        *wp++ = '\0';
+                                else
+                                        --nlen;
+                        }
+                        else
+                                *wp++ = *rp;
+                while (*rp++ != '\0');
+ 
+                if (nlen == 0) {
+                        free(*argz);
+                        *argz = NULL;
+                        *len = 0;
+                }
+ 
+                *len = nlen;
+        }
+        else {
+                *argz = NULL;
+                *len = 0;
+        }
+ 
+        return 0;
+ }
+ 
+ /* Delete ENTRY from ARGZ & ARGZ_LEN, if any.  */
+ void argz_delete(char **argz, size_t * argz_len, char *entry)
+ {
+        if (entry)
+                /* Get rid of the old value for NAME.  */
+        {
+                size_t entry_len = strlen(entry) + 1;
+                *argz_len -= entry_len;
+                memmove(entry, entry + entry_len, *argz_len - (entry - *argz));
+                if (*argz_len == 0) {
+                        free(*argz);
+                        *argz = 0;
+                }
+        }
+ }
+ 
+ /* Puts pointers to each string in ARGZ, plus a terminating 0 element, into
+  * ARGV, which must be large enough to hold them all.  */
+ void argz_extract(const char *argz, size_t len, char **argv)
+ {
+        while (len > 0) {
+                size_t part_len = strlen(argz);
+                *argv++ = (char *)argz;
+                argz += part_len + 1;
+                len -= part_len + 1;
+        }
+        *argv = 0;
+ }
+ 
+ /* Insert ENTRY into ARGZ & ARGZ_LEN before BEFORE, which should be an
+  * existing entry in ARGZ; if BEFORE is NULL, ENTRY is appended to the end.
+  * Since ARGZ's first entry is the same as ARGZ, argz_insert (ARGZ, ARGZ_LEN,
+  * ARGZ, ENTRY) will insert ENTRY at the beginning of ARGZ.  If BEFORE is not
+  * in ARGZ, EINVAL is returned, else if memory can't be allocated for the new
+  * ARGZ, ENOMEM is returned, else 0.  */
+ int argz_insert(char **argz, size_t * argz_len, char *before, const char *entry)
+ {
+        if (!before)
+                return argz_add(argz, argz_len, entry);
+ 
+        if (before < *argz || before >= *argz + *argz_len)
+                return EINVAL;
+ 
+        if (before > *argz)
+                /* Make sure before is actually the beginning of an entry.  */
+                while (before[-1])
+                        before--;
+ 
+        {
+                size_t after_before = *argz_len - (before - *argz);
+                size_t entry_len = strlen(entry) + 1;
+                size_t new_argz_len = *argz_len + entry_len;
+                char *new_argz = realloc(*argz, new_argz_len);
+ 
+                if (new_argz) {
+                        before = new_argz + (before - *argz);
+                        memmove(before + entry_len, before, after_before);
+                        memmove(before, entry, entry_len);
+                        *argz = new_argz;
+                        *argz_len = new_argz_len;
+                        return 0;
+                }
+                else
+                        return ENOMEM;
+        }
+ }
+ 
+ char *argz_next(const char *argz, size_t argz_len, const char *entry)
+ {
+        if (entry) {
+                if (entry < argz + argz_len)
+                        entry = strchr(entry, '\0') + 1;
+ 
+                return entry >= argz + argz_len ? NULL : (char *)entry;
+        }
+        else if (argz_len > 0)
+                return (char *)argz;
+        else
+                return NULL;
+ }
+ 
+ /* Append BUF, of length BUF_LEN to *TO, of length *TO_LEN, reallocating and
+  * updating *TO & *TO_LEN appropriately.  If an allocation error occurs,
+  * *TO's old value is freed, and *TO is set to 0.  */
+ static void
+ str_append(char **to, size_t * to_len, const char *buf, const size_t buf_len)
+ {
+        size_t new_len = *to_len + buf_len;
+        char *new_to = realloc(*to, new_len + 1);
+ 
+        if (new_to) {
+                /* *((char *)__mempcpy(new_to + *to_len, buf, buf_len)) = '\0'; */
+                memcpy(new_to + *to_len, buf, buf_len);
+         *(new_to + *to_len + buf_len) = '\0';
+                *to = new_to;
+                *to_len = new_len;
+        }
+        else {
+                free(*to);
+                *to = 0;
+        }
+ }
+ 
+ /* Replace any occurrences of the string STR in ARGZ with WITH, reallocating
+  * ARGZ as necessary.  If REPLACE_COUNT is non-zero, *REPLACE_COUNT will be
+  * incremented by number of replacements performed.  */
+ int argz_replace(char **argz, size_t * argz_len, const char *str,
+                           const char *with, unsigned *replace_count)
+ {
+        int err = 0;
+ 
+        if (str && *str) {
+                char *arg = 0;
+                char *src = *argz;
+                size_t src_len = *argz_len;
+                char *dst = 0;
+                size_t dst_len = 0;
+                int delayed_copy = 1;              /* True while we've avoided copying
+                                                                            * anything.  */
+                size_t str_len = strlen(str), with_len = strlen(with);
+ 
+                while (!err && (arg = argz_next(src, src_len, arg))) {
+                        char *match = strstr(arg, str);
+                        if (match) {
+                                char *from = match + str_len;
+                                size_t to_len = match - arg;
+                                char *to = my_strndup(arg, to_len);
+ 
+                                while (to && from) {
+                                        str_append(&to, &to_len, with, with_len);
+                                        if (to) {
+                                                match = strstr(from, str);
+                                                if (match) {
+                                                        str_append(&to, &to_len, from, match - from);
+                                                        from = match + str_len;
+                                                }
+                                                else {
+                                                        str_append(&to, &to_len, from, strlen(from));
+                                                        from = 0;
+                                                }
+                                        }
+                                }
+ 
+                                if (to) {
+                                        if (delayed_copy)
+                                                /* We avoided copying SRC to DST until we found a
+                                                 * match; now that we've done so, copy everything
+                                                 * from the start of SRC.  */
+                                        {
+                                                if (arg > src)
+                                                        err =
+                                                                argz_append(&dst, &dst_len, src,
+                                                                                          (arg - src));
+                                                delayed_copy = 0;
+                                        }
+                                        if (!err)
+                                                err = argz_add(&dst, &dst_len, to);
+                                        free(to);
+                                }
+                                else
+                                        err = ENOMEM;
+ 
+                                if (replace_count)
+                                        (*replace_count)++;
+                        }
+                        else if (!delayed_copy)
+                                err = argz_add(&dst, &dst_len, arg);
+                }
+ 
+                if (!err) {
+                        if (!delayed_copy)
+                                /* We never found any instances of str.  */
+                        {
+                                if (src)
+                                        free(src);
+                                *argz = dst;
+                                *argz_len = dst_len;
+                        }
+                }
+                else if (dst_len > 0)
+                        free(dst);
+        }
+ 
+        return err;
+ }
+ 
+ /* Make '\0' separated arg vector ARGZ printable by converting all the '\0's
+  * except the last into the character SEP.  */
+ void argz_stringify(char *argz, size_t len, int sep)
+ {
+        if (len > 0) {
+                while (1) {
+                        size_t part_len = my_strnlen(argz, len);
+                        argz += part_len;
+                        len -= part_len;
+                        if (len-- <= 1)                    /* includes final '\0' we want to stop 
+                                                                            * at */
+                                break;
+                        *argz++ = sep;
+                }
+     }
+     return;
+ }
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_argz.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,66 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  argz.c: Zero-Terminated Argument Vector library (API)
+ */
+ 
+ /* Routines for dealing with '\0' separated arg vectors.
+    Copyright (C) 1996, 1997 Free Software Foundation, Inc.
+    This file is part of the GNU C Library.
+    Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1996.
+ 
+    The GNU C Library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.
+ 
+    The GNU C Library 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
+    Library General Public License for more details.
+ 
+    You should have received a copy of the GNU Library General Public
+    License along with the GNU C Library; see the file COPYING.LIB.  If not,
+    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+    Boston, MA 02111-1307, USA.  */
+ 
+ #ifndef _ARGZ_H_
+ #define _ARGZ_H_
+ 
+ #include <string.h>
+ 
+ extern int    argz_create    (char *const _argv[], char **_argz, size_t * _len);
+ extern int    argz_create_sep(const char *_string, int _sep, char **_argz, size_t * _len);
+ extern size_t argz_count     (const char *_argz, size_t _len);
+ extern void   argz_extract   (const char *_argz, size_t _len, char **_argv);
+ extern void   argz_stringify (char *_argz, size_t _len, int _sep);
+ extern int    argz_append    (char **_argz, size_t * _argz_len, const char *_buf, size_t _buf_len);
+ extern int    argz_add       (char **_argz, size_t * _argz_len, const char *_str);
+ extern int    argz_add_sep   (char **_argz, size_t * _argz_len, const char *_string, int _delim);
+ extern void   argz_delete    (char **_argz, size_t * _argz_len, char *_entry);
+ extern int    argz_insert    (char **_argz, size_t * _argz_len, char *_before, const char *_entry);
+ extern int    argz_replace   (char **_argz, size_t * _argz_len, const char *_str, const char *_with, unsigned int *_replace_count);
+ extern char  *argz_next      (const char *_argz, size_t _argz_len, const char *_entry);
+ 
+ #endif /* _ARGZ_H_ */
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_daemon.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,122 ----
+ /*
+ **  daemon.c -- daemonize current process 
+ **  Copyright (c) 1999-2000 Ralf S. Engelschall, All Rights Reserved. 
+ **
+ **  See "Unix Programming Frequently Asked Questions":
+ **  http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC10
+ */
+ 
+ #include "config.h"
+ 
+ #include <fcntl.h>
+ #include <unistd.h>
+ #include <signal.h>
+ #include <sys/stat.h>
+ #include <sys/ioctl.h>
+ #ifdef HAVE_TERMIOS_H
+ #include <sys/termios.h>
+ #endif
+ 
+ #include "lmtp2nntp_daemon.h"
+ 
+ int daemonize(void)
+ {
+     int fd;
+     int rc;
+ 
+     /* 
+      * if we are started from init, 
+      * no need to become daemon.
+      */
+     if (getppid() == 1)
+         return 0;
+ 
+     /*
+      * Ignore tty related signals
+      */
+ #ifdef  SIGTTOU
+     signal(SIGTTOU, SIG_IGN);
+ #endif
+ #ifdef  SIGTTIN
+     signal(SIGTTIN, SIG_IGN);
+ #endif
+ #ifdef  SIGTSTP
+     signal(SIGTSTP, SIG_IGN);
+ #endif
+ 
+     /*
+      * fork so the parent can exit, this returns control to the command line
+      * or shell invoking your program. This step is required so that the new
+      * process is guaranteed not to be a process group leader (The next step,
+      * setsid, would fail if you're a process group leader). 
+      */
+     rc = fork();
+     switch (rc) {
+         case -1: return -1;
+         case 0:  break;
+         default: _exit(0); /* exit original process */
+     }
+ 
+     /*
+      * setsid to become a process group and session group leader. Since a
+      * controlling terminal is associated with a session, and this new session
+      * has not yet acquired a controlling terminal our process now has no
+      * controlling terminal, which is a Good Thing for daemons. 
+      */
+ #ifdef HAVE_SETSID
+     if (setsid() == -1)
+         return -1;
+ #else
+     if (setpgid(0, getpid()) == -1)
+         return -1;
+ #ifndef _PATH_TTY
+ #define _PATH_TTY "/dev/tty"
+ #endif
+     if ((fd = open(_PATH_TTY, O_RDWR)) == -1) 
+         return -1;
+     ioctl(fd, TIOCNOTTY, NULL);
+     close(fd);
+ #endif
+ 
+     /*
+      * fork again so the parent, (the session group leader), can exit. This
+      * means that we, as a non-session group leader, can never regain a
+      * controlling terminal. 
+      */
+     rc = fork();
+     switch (rc) {
+         case -1: return -1;
+         case 0:  break;
+         default: _exit(0); /* exit original process */
+     }
+ 
+     /*
+      * chdir("/") to ensure that our process doesn't keep any directory in
+      * use. Failure to do this could make it so that an administrator couldn't
+      * unmount a filesystem, because it was our current directory.
+      * [Equivalently, we could change to any directory containing files
+      * important to the daemon's operation.] 
+      */
+     chdir("/");
+ 
+     /*
+      * give us complete control over the permissions of anything we write. We
+      * don't know what umask we may have inherited.  [This step is optional] 
+      */
+     umask(0);
+ 
+     /*
+      * close fds 0, 1, and 2. This releases the standard in, out, and error we
+      * inherited from our parent process. We have no way of knowing where
+      * these fds might have been redirected to. 
+      */
+     if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
+         dup2(fd, STDIN_FILENO);
+         dup2(fd, STDOUT_FILENO);
+         dup2(fd, STDERR_FILENO);
+         if (fd > 2)
+             close(fd);
+     }
+     return 0;
+ }
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_daemon.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,6 ----
+ #ifndef _DAEMON_H_
+ #define _DAEMON_H_
+ 
+ int daemonize(void);
+ 
+ #endif /* _DAEMON_H_ */


ossp-pkg/lmtp2nntp/lmtp2nntp_dotconf.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,1311 ----
+ /* dot.conf - configuration file parser library
+  * Copyright (C) 1999,2000,2001 Lukas Schroeder <lukas@azzit.de>
+  * 
+  * This library is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Lesser General Public
+  * License as published by the Free Software Foundation; either
+  * version 2.1 of the License.
+  * 
+  * This library 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
+  * Lesser General Public License for more details.
+  * 
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this library; if not, write to the
+  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+  * Boston, MA  02111-1307, USA.
+  *
+  */
+ 
+ /* -- dotconf.c - this code is responsible for the input, parsing and dispatching of options  */
+ 
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ 
+ /* Added by Stephen W. Boyer <sboyer@caldera.com> 
+  * for wildcard support in Include file paths
+  */
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <fcntl.h>
+ #include <sys/stat.h>
+ 
+ /* -- AIX 4.3 compile time fix
+  * by Eduardo Marcel Macan <macan@colband.com.br>
+  *
+  * modified by Stephen W. Boyer <sboyer@caldera.com>
+  * for Unixware and OpenServer
+  */
+ 
+ #if defined (_AIX43) || defined(UNIXWARE) || defined(OSR5)
+ #include <strings.h> 
+ #endif
+ 
+ #include <stdarg.h>
+ #include <time.h>
+ #include <sys/stat.h>
+ 
+ #ifndef WIN32
+ 
+ #include <dirent.h>
+ #include <unistd.h>
+ 
+ #else /* ndef WIN32 */
+ 
+ #include "readdir.h"                   /* WIN32 fix by Robert J. Buck */
+ 
+ #define strncasecmp strnicmp
+ typedef unsigned long ulong;
+ #define snprintf _snprintf
+ #define vsnprintf _vsnprintf
+ #endif /* !WIN32 */
+ 
+ #include <ctype.h>
+ #include "./dotconf.h"
+ 
+ static char name[CFG_MAX_OPTION + 1];  /* option name */
+ 
+ /*
+  * some 'magic' options that are predefined by dot.conf itself for
+  * advanced functionality
+  */
+ static DOTCONF_CB(dotconf_cb_include);         /* internal 'Include'     */
+ static DOTCONF_CB(dotconf_cb_includepath);     /* internal 'IncludePath' */
+ 
+ static configoption_t dotconf_options[] =
+ {
+        { "Include", ARG_STR, dotconf_cb_include, NULL, CTX_ALL },
+        { "IncludePath", ARG_STR, dotconf_cb_includepath, NULL, CTX_ALL },
+        LAST_CONTEXT_OPTION
+ };
+ 
+ char *dotconf_substitute_env(configfile_t *configfile, char *str)
+ {
+        char *cp1, *cp2, *cp3, *eos, *eob;
+        char *env_value;
+        char env_name[CFG_MAX_VALUE + 1];
+        char env_default[CFG_MAX_VALUE + 1];
+        char tmp_value[CFG_MAX_VALUE + 1];
+ 
+        memset(env_name, 0, CFG_MAX_VALUE + 1);
+        memset(env_default, 0, CFG_MAX_VALUE + 1);
+        memset(tmp_value, 0, CFG_MAX_VALUE + 1);
+ 
+        cp1 = str;
+        eob = cp1 + strlen(str) + 1;
+        cp2 = tmp_value;
+        eos = cp2 + CFG_MAX_VALUE + 1;
+ 
+        while ((cp1 < eob) && (cp2 < eos) && (*cp1 != '\0'))
+        {
+                /* substitution needed ?? */
+                if (*cp1 == '$' && *(cp1 + 1) == '{')
+                {
+                        cp1 += 2;                       /* skip ${ */
+                        cp3 = env_name;
+                        while ((cp1 < eob) && !(*cp1 == '}' || *cp1 == ':'))
+                                *cp3++ = *cp1++;
+                        *cp3 = '\0';            /* terminate */
+ 
+                        /* default substitution */
+                        if (*cp1 == ':' && *(cp1 + 1) == '-')
+                        {
+                                cp1 += 2;               /* skip :- */
+                                cp3 = env_default;
+                                while ((cp1 < eob) && (*cp1 != '}'))
+                                        *cp3++ = *cp1++;
+                                *cp3 = '\0';    /* terminate */
+                        }
+                        else
+                        {
+                                while ((cp1 < eob) && (*cp1 != '}'))
+                                        cp1++;
+                        }
+ 
+                        if (*cp1 != '}')
+                        {
+                                dotconf_warning(configfile, DCLOG_WARNING, ERR_PARSE_ERROR,
+                                        "Unbalanced '{'");
+                        }
+                        else
+                        {
+                                cp1++;                  /* skip } */
+                                if ((env_value = getenv(env_name)) != NULL)
+                                {
+                                        strncat(cp2, env_value, eos - cp2);
+                                        cp2 += strlen(env_value);
+                                }
+                                else
+                                {
+                                        strncat(cp2, env_default, eos - cp2);
+                                        cp2 += strlen(env_default);
+                                }
+                        }
+ 
+                }
+ 
+                *cp2++ = *cp1++;
+        }
+        *cp2 = '\0';                            /* terminate buffer */
+ 
+        free(str);
+        return strdup(tmp_value);
+ }
+ 
+ int dotconf_warning(configfile_t *configfile, int type, unsigned long errnum, const char *fmt, ...)
+ {
+        va_list args;
+        int retval = 0;
+ 
+        va_start(args, fmt);
+        if (configfile->errorhandler != 0)      /* an errorhandler is registered */
+        {
+                char msg[CFG_BUFSIZE];
+                vsnprintf(msg, CFG_BUFSIZE, fmt, args);
+                retval = configfile->errorhandler(configfile, type, errnum, msg);
+        }
+        else                                            /* no errorhandler, do-it-yourself */
+        {
+                retval = 0;
+                fprintf(stderr, "%s:%ld: ", configfile->filename, configfile->line);
+                vfprintf(stderr, fmt, args);
+                fprintf(stderr, "\n");
+        }
+        va_end(args);
+ 
+        return retval;
+ }
+ 
+ void dotconf_register_options(configfile_t *configfile, const configoption_t * options)
+ {
+        int num = configfile->config_option_count;
+ 
+ #define GROW_BY   10
+ 
+        /* resize memoryblock for options blockwise */
+        if (configfile->config_options == NULL)
+        {
+                configfile->config_options = malloc(sizeof(void *) * (GROW_BY + 1));
+        }
+        else
+        {
+                if ( !(num % GROW_BY) )
+                        configfile->config_options = realloc(configfile->config_options, 
+                                                                                         sizeof(void *) * (num + GROW_BY));
+        }
+ 
+ #undef GROW_BY
+ 
+        /* append new options */
+        configfile->config_options[configfile->config_option_count] = options;
+        configfile->config_options[ ++configfile->config_option_count ] = 0;
+ 
+ }
+ 
+ void dotconf_callback(configfile_t *configfile, callback_types type, dotconf_callback_t callback)
+ {
+        switch(type)
+        {
+                case ERROR_HANDLER:
+                        configfile->errorhandler = (dotconf_errorhandler_t) callback;
+                        break;
+                case CONTEXT_CHECKER:
+                        configfile->contextchecker = (dotconf_contextchecker_t) callback;
+                        break;
+                default:
+                        break;
+        }
+ }
+ 
+ int dotconf_continue_line(char *buffer, size_t length)
+ {
+        /* ------ match [^\\]\\[\r]\n ------------------------------ */
+        char *cp1 = buffer + length - 1;
+ 
+        if (length < 2)
+                return 0;
+ 
+        if (*cp1-- != '\n')
+                return 0;
+ 
+        if (*cp1 == '\r')
+                cp1--;
+ 
+        if (*cp1-- != '\\')
+                return 0;
+ 
+        cp1[1] = 0;                                             /* strip escape character and/or newline */
+        return (*cp1 != '\\');
+ }
+ 
+ int dotconf_get_next_line(char *buffer, size_t bufsize, configfile_t *configfile)
+ {
+        char *cp1, *cp2;
+        char buf2[CFG_BUFSIZE];
+        int length;
+ 
+        if (configfile->eof)
+                return 1;
+ 
+        cp1 = fgets(buffer, CFG_BUFSIZE, configfile->stream);
+ 
+        if (!cp1)
+        {
+                configfile->eof = 1;
+                return 1;
+        }
+ 
+        configfile->line++;
+        length = strlen(cp1);
+        while ( dotconf_continue_line(cp1, length) ) 
+        {
+                cp2 = fgets(buf2, CFG_BUFSIZE, configfile->stream);
+                if (!cp2)
+                {
+                        fprintf(stderr, "[dotconf] Parse error. Unexpected end of file at "
+                                        "line %ld in file %s\n", configfile->line, configfile->filename);
+                        configfile->eof = 1;
+                        return 1;
+                }
+                configfile->line++;
+                strcpy(cp1 + length - 2, cp2);
+                length = strlen(cp1);
+        }
+ 
+        return 0;
+ }
+ 
+ char *dotconf_get_here_document(configfile_t *configfile, const char *delimit)
+ {
+        /* it's a here-document: yeah, what a cool feature ;) */
+        unsigned int limit_len;
+        char here_string;
+        char buffer[CFG_BUFSIZE];
+        char *here_doc = 0;
+        char here_limit[9];                            /* max length for here-document delimiter: 8 */
+        struct stat finfo;
+        int offset = 0;
+ 
+        if (configfile->size <= 0)
+        {
+                if (stat(configfile->filename, &finfo))
+                {
+                        dotconf_warning(configfile, DCLOG_EMERG, ERR_NOACCESS, 
+                                                   "[emerg] could not stat currently read file (%s)\n", 
+                                                        configfile->filename);
+                        return NULL;
+                }
+                configfile->size = finfo.st_size;
+        }
+ 
+        /* 
+         * allocate a buffer of filesize bytes; should be enough to
+         * prevent buffer overflows
+         */
+        here_doc = malloc(configfile->size);    /* allocate buffer memory */
+        memset(here_doc, 0, configfile->size);
+ 
+        here_string = 1;
+        limit_len = snprintf(here_limit, 9, "%s", delimit);
+        while (!dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile))
+        {
+                if (!strncmp(here_limit, buffer, limit_len - 1))
+                {
+                        here_string = 0;
+                        break;
+                }
+                offset += snprintf( (here_doc + offset), configfile->size - offset - 1, "%s", buffer);
+        }
+        if (here_string)
+                dotconf_warning(configfile, DCLOG_WARNING, ERR_PARSE_ERROR, "Unterminated here-document!");
+ 
+        here_doc[offset-1] = '\0';      /* strip newline */
+ 
+        return (char *)realloc(here_doc, offset);
+ }
+ 
+ const char *dotconf_invoke_command(configfile_t *configfile, command_t *cmd)
+ {
+        const char *error = 0;
+ 
+        if ( !configfile->contextchecker
+                || !(error = configfile->contextchecker(cmd, cmd->option->context)) )
+        {
+                if (!error)
+                        error = cmd->option->callback(cmd, configfile->context);
+        }
+ 
+        return error;
+ }
+ 
+ configoption_t *dotconf_find_command(configfile_t *configfile, const char *command)
+ {
+        configoption_t *option;
+        int i = 0, mod = 0, done = 0;
+ 
+        for (option = 0, mod = 0; configfile->config_options[mod] && !done; mod++)
+                for (i = 0; configfile->config_options[mod][i].name[0]; i++)
+                {
+                        if (!configfile->cmp_func(name, 
+                                                                          configfile->config_options[mod][i].name, CFG_MAX_OPTION))
+                        {
+                                option = (configoption_t *) &configfile->config_options[mod][i];
+                                /* TODO: this could be flagged: option overwriting by modules */
+                                done = 1;
+                                break;          /* found it; break out */
+                        }
+                }
+ 
+        /* handle ARG_NAME fallback */
+        if ( (option && option->name[0] == 0) 
+                  || configfile->config_options[mod - 1][i].type == ARG_NAME)
+        {
+                option = (configoption_t *) &configfile->config_options[mod - 1][i];
+        }
+ 
+        return option;
+ }
+ 
+ char *dotconf_read_arg(configfile_t *configfile, char **line)
+ {
+        int sq = 0, dq = 0;                                                     /* single quote, double quote */
+        int done;
+        char *cp1 = *line;
+        char *cp2, *eos;
+        char buf[CFG_MAX_VALUE];
+ 
+        memset(buf, 0, CFG_MAX_VALUE);
+        done = 0;
+        cp2 = buf;
+        eos = cp2 + CFG_MAX_VALUE - 1;
+ 
+        if (*cp1 == '#' || !*cp1)
+                return NULL;
+ 
+        /* skip all whitespace between 2 arguments */
+        while ( isspace((int)cp1[0]) && (*cp1 != '\0'))
+                cp1++;
+ 
+        while ((*cp1 != '\0') && (cp2 != eos) && !done)
+        {
+                switch (*cp1)
+                {
+                        case '\'':                                      /* single quote */
+                                if (dq)
+                                        break;                                  /* already double quoting, break out */
+                                if (sq)
+                                        sq--;                                   /* already single quoting, clear state */
+                                else if (!sq)
+                                        sq++;                                   /* set state for single quoting */
+                                break;
+                        case '"':                                       /* double quote */
+                                if (sq)
+                                        break;                                  /* already single quoting, break out */
+                                if (dq)
+                                        dq--;                                   /* already double quoting, clear state */
+                                else if (!dq)
+                                        dq++;                                   /* set state for double quoting */
+                                break;
+                        case '\\':                                      /* protected chars */
+                                if (!cp1[1])                    /* dont protect NUL */
+                                        break;
+                                *cp2++ = *(++cp1);
+                                cp1++;                                  /* skip the protected one */
+                                continue;
+                                break;
+                        default:
+                                break;
+                }
+ 
+                /* unquoted space: start a new option argument */
+                if (isspace((int)*cp1) && !dq && !sq)
+                {
+                        *cp2 = '\0';    /* terminate current argument */
+                        break;
+                }
+                /* unquoted, unescaped comment-hash ; break out */
+                else if (*cp1 == '#' && !dq && !sq)
+                {
+                        *cp2 = '\0';
+                        break;
+                }
+                /* not space or quoted:eat it; dont take quote if quoting */
+                else if ( (!isspace((int)*cp1) && !dq && !sq && *cp1 != '"' && *cp1 != '\'')
+                                   || (dq && (*cp1 != '"')) || (sq && *cp1 != '\'') )
+                {
+                        *cp2++ = *cp1;
+                }
+                cp1++;
+        }
+ 
+        *line = cp1;
+ 
+        /* FIXME: escaping substitutes does not work 
+                Subst ${HOME} \$\{HOME\}
+                BOTH! will be substituted, which is somewhat wrong, ain't it ?? :-(
+        */
+        if ( (configfile->flags & DONT_SUBSTITUTE) == DONT_SUBSTITUTE )
+                return buf[0] ? strdup(buf) : NULL;
+        return buf[0] ? dotconf_substitute_env(configfile, strdup(buf)) : NULL;
+ }
+ 
+ const char *dotconf_handle_command(configfile_t *configfile, char *buffer)
+ {
+        char *cp1, *cp2;                                                        /* generic char pointer      */
+        char *eob;                                                                      /* end of buffer; end of string  */
+        int i;                                                                          /* generic counter, mod holds the module index */
+        int memory_cleanup;
+        const char *error;                                                      /* error message as return by callback */
+        command_t command;                                                      /* command structure */
+        configoption_t *option;                                         /* matched option from config_options */
+ 
+        i = memory_cleanup = 0;
+        error = 0;
+ 
+        /* TODO????: insert 'skipped_line' callback code */
+ 
+        /* clear fields */
+        memset(&command, 0, sizeof(command_t));
+        name[0] = 0;
+ 
+        /* initialize char pointer */
+        cp1 = buffer;
+        eob = cp1 + strlen(cp1);                                        /* calculate end of buffer */
+ 
+        /* skip any whitspace of indented lines */
+        while ((cp1 < eob) && (isspace((int)*cp1)))
+                cp1++;
+ 
+        /* ignore comments and empty lines */
+        if (!cp1 || !*cp1 || *cp1 == '#' || *cp1 == '\n' || *cp1 == EOF)
+                return NULL;
+ 
+        /* skip line if it only contains whitespace */
+        if (cp1 == eob)
+                return NULL;
+ 
+        /* get first token: read the name of a possible option */
+        cp2 = name;
+        while ((*cp1 != '\0') && (!isspace((int)*cp1)))
+                *cp2++ = *cp1++;
+        *cp2 = '\0';
+ 
+        option = dotconf_find_command(configfile, name);
+ 
+        if (!option || option->callback == 0)
+        {
+                dotconf_warning(configfile, DCLOG_INFO, ERR_UNKNOWN_OPTION, 
+                                                "Unknown Config-Option: '%s'", name);
+                return NULL;
+        }
+ 
+        /* fill in the command_t structure with values we already know */
+        command.name = option->type == ARG_NAME ? name : option->name;
+        command.option = option;
+        command.context = configfile->context;
+        command.configfile = configfile;
+        command.data.list = (char **)calloc(CFG_VALUES, sizeof(char *));
+ 
+        if (option->type == ARG_RAW)
+        {
+                /* if it is an ARG_RAW type, save some time and call the
+                   callback now */
+                command.data.str = cp1;
+        }
+        else if (option->type == ARG_STR)
+        {
+                /* check if it's a here-document and act accordingly */
+                char *cp3 = cp1;
+ 
+                /* skip whitespace */
+                while ((cp3 < eob) && (*cp3 != '\0') && (isspace((int)*cp3)))
+                        cp3++;
+ 
+                if (!strncmp("<<", cp3, 2))     /* here document magic bytes :) */
+                {
+                        command.data.str = dotconf_get_here_document(configfile, cp3 + 2);
+                        command.arg_count = 1;
+                        memory_cleanup = !!command.data.str;
+                }
+        }
+ 
+        if ( !(option->type == ARG_STR && command.data.str != 0) )
+        {
+ 
+                /* skip whitespace */
+                while ((cp1 < eob) && (*cp1 != '\0') && (isspace((int)*cp1)))
+                        cp1++;
+ 
+                command.arg_count = 0; 
+                while ( command.arg_count < (CFG_VALUES - 1)
+                                && (command.data.list[command.arg_count] = dotconf_read_arg(configfile, &cp1)) )
+                {
+                        command.arg_count++;
+                }
+ 
+                /* skip whitespace again */
+                while ((cp1 < eob) && (*cp1 != '\0') && (isspace((int)*cp1)))
+                        cp1++;
+ 
+                if (command.arg_count && command.data.list[command.arg_count-1] && *cp1)
+                        command.data.list[command.arg_count++] = strdup(cp1);
+ 
+                /* has an option entry been found before or do we have to use a fallback? */
+                if ((option->name && option->name[0] > 32) || option->type == ARG_NAME)
+                {
+                        /* found it, now check the type of args it wants */
+                        switch (option->type)
+                        {
+                                case ARG_TOGGLE:
+                                {
+                                        /* the value is true if the argument is Yes, On or 1 */
+                                        if (command.arg_count < 1)
+                                        {
+                                                dotconf_warning(configfile, DCLOG_WARNING, ERR_WRONG_ARG_COUNT, 
+                                                                                "Missing argument to option '%s'", name);
+                                                goto out;
+                                        }
+ 
+                                        command.data.value = CFG_TOGGLED(command.data.list[0]);
+                                        break;
+                                }
+                                case ARG_INT:
+                                {
+                                        if (command.arg_count < 1)
+                                        {
+                                                dotconf_warning(configfile, DCLOG_WARNING, ERR_WRONG_ARG_COUNT, 
+                                                                                "Missing argument to option '%s'", name);
+                                                goto out;
+                                        }
+ 
+                                        sscanf(command.data.list[0], "%li", &command.data.value);
+                                        break;
+                                }
+                                case ARG_STR:
+                                {
+                                        if (command.arg_count < 1)
+                                        {
+                                                dotconf_warning(configfile, DCLOG_WARNING, ERR_WRONG_ARG_COUNT, 
+                                                                                "Missing argument to option '%s'", name);
+                                                goto out;
+                                        }
+ 
+                                        command.data.str = command.data.list[0];
+                                        break;
+                                }
+                                case ARG_NAME:  /* fall through */
+                                case ARG_LIST:
+                                case ARG_NONE:
+                                case ARG_RAW:   /* this has been handled before */
+                                default:
+                                        break;  //win32 edit
+                        }
+                }
+        }
+ 
+        error = dotconf_invoke_command(configfile, &command);
+ 
+ out:
+        if (command.option->type == ARG_STR && memory_cleanup)
+                free(command.data.str);
+        else
+        {
+                for (i = 0; i < command.arg_count; i++)
+                        free(command.data.list[i]);
+        }
+ 
+        free(command.data.list);
+        return error;                                   
+ }
+ 
+ const char *dotconf_command_loop_until_error(configfile_t *configfile)
+ {
+        char buffer[CFG_BUFSIZE];
+ 
+        while ( !(dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile)) )
+        {
+                const char *error = dotconf_handle_command(configfile, buffer);
+                if ( error )
+                        return error;
+        }
+        return NULL;
+ }
+ 
+ int dotconf_command_loop(configfile_t *configfile)
+ {
+        /* ------ returns: 0 for failure -- !0 for success ------------------------------------------ */
+        char buffer[CFG_BUFSIZE];
+ 
+        while ( !(dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile)) )
+        {
+                const char *error = dotconf_handle_command(configfile, buffer);
+                if ( error != NULL )
+                {
+                        if ( dotconf_warning(configfile, DCLOG_ERR, 0, error) )
+                                return 0;
+                }
+        }
+        return 1;
+ }
+ 
+ configfile_t *dotconf_create(char *fname, const configoption_t * options, 
+                              context_t *context, unsigned long flags)
+ {
+        configfile_t *new = 0;
+        char *dc_env;
+ 
+        if (access(fname, R_OK))
+        {
+                fprintf(stderr, "Error opening configuration file '%s'\n", fname);
+                return NULL;
+        }
+ 
+        new = calloc(1, sizeof(configfile_t));
+        if (!(new->stream = fopen(fname, "r")))
+        {
+                fprintf(stderr, "Error opening configuration file '%s'\n", fname);
+                free(new);
+                return NULL;
+        }
+ 
+        new->flags = flags;
+        new->filename = strdup(fname);
+ 
+        new->includepath = malloc(CFG_MAX_FILENAME);
+        new->includepath[0] = 0x00;
+ 
+        /* take includepath from environment if present */
+        if ((dc_env = getenv(CFG_INCLUDEPATH_ENV)) != NULL)
+        {
+                snprintf(new->includepath, CFG_MAX_FILENAME, "%s", dc_env);
+        }
+ 
+        new->context = context;
+ 
+        dotconf_register_options(new, dotconf_options);                 
+        dotconf_register_options(new, options);                                 
+ 
+        if ( new->flags & CASE_INSENSITIVE )
+                new->cmp_func = strncasecmp;
+        else
+                new->cmp_func = strncmp;
+ 
+        return new;
+ }
+ 
+ void dotconf_cleanup(configfile_t *configfile)
+ {
+        if (configfile->stream)
+                fclose(configfile->stream);
+ 
+        if (configfile->filename)
+                free(configfile->filename);
+ 
+        if (configfile->config_options)
+                free(configfile->config_options);
+ 
+        if (configfile->includepath)
+                free(configfile->includepath);
+ 
+        free(configfile);
+ }
+ 
+ /* ------ internal utility function that verifies if a character is in the WILDCARDS list -- */
+ int dotconf_is_wild_card(char value)
+ {
+        int retval = 0;
+        int i;
+        int wildcards_len = strlen(WILDCARDS);
+ 
+        for (i=0;i<wildcards_len;i++)
+        {
+                if (value == WILDCARDS[i])
+                {
+                        retval = 1;
+                        break;
+                }
+        }
+        
+        return retval;
+ }
+ 
+ /* ------ internal utility function that calls the appropriate routine for the wildcard passed in -- */
+ int dotconf_handle_wild_card(command_t* cmd, char wild_card, char* path, char* pre, char* ext)
+ {
+        int retval = 0;
+ 
+        switch (wild_card)
+        {
+                case '*':
+ 
+                        retval = dotconf_handle_star(cmd,path,pre,ext);
+ 
+                break;
+ 
+                case '?':
+ 
+                        retval = dotconf_handle_question_mark(cmd,path,pre,ext);
+ 
+                break;
+ 
+                default:
+                        retval = -1;
+        }
+ 
+        return retval;
+ }
+ 
+ 
+ /* ------ internal utility function that frees allocated memory from dotcont_find_wild_card -- */
+ void dotconf_wild_card_cleanup(char* path, char* pre)
+ {
+ 
+        if (path != NULL)
+        {
+                free(path);
+        }
+ 
+        if (pre != NULL)
+        {
+                free(pre);
+        }
+ 
+ }
+ 
+ /* ------ internal utility function to check for wild cards in file path -- */
+ /* ------ path and pre must be freed by the developer ( dotconf_wild_card_cleanup) -- */ 
+ int dotconf_find_wild_card(char* filename, char* wildcard, char** path, char** pre, char** ext)
+ {
+        int retval = -1;
+        int prefix_len = 0;
+        int tmp_count = 0;
+        char* tmp = 0;
+        int found_path = 0;
+ 
+        int len = strlen(filename);
+ 
+        if (wildcard != NULL && len > 0 && path != NULL && pre != NULL && ext != NULL )
+        {       
+                prefix_len = strcspn(filename,WILDCARDS); /* find any wildcard in WILDCARDS */ 
+ 
+                if ( prefix_len < len ) /* Wild card found */ 
+                {
+                        tmp = filename + prefix_len;
+                        tmp_count = prefix_len + 1;
+ 
+                        while ( tmp != filename && *(tmp) != '/' )
+                        {
+                                tmp--;
+                                tmp_count--;
+                        }
+                        
+                        if ( *(tmp) == '/' )
+                        {
+                                *path = (char*)malloc(tmp_count+1);
+                                found_path = 1;
+ 
+                        } else 
+ 
+                                *path = (char*)malloc(1);
+ 
+                        *pre =  (char*)malloc((prefix_len-(tmp_count-(found_path?0:1)))+1);
+ 
+                        if ( *path && *pre )
+                        {
+                                if (found_path) 
+                                        strncpy(*path,filename,tmp_count);
+                                (*path)[tmp_count] = '\0';
+ 
+                                strncpy(*pre,(tmp+(found_path?1:0)),
+                                                (prefix_len-(tmp_count-(found_path?0:1))));
+                                (*pre)[(prefix_len-(tmp_count-(found_path?0:1)))] = '\0';
+ 
+                                *ext = filename + prefix_len;
+                                *wildcard = (**ext);
+                                (*ext)++;
+ 
+                                retval = prefix_len;
+ 
+                        }
+ 
+                }
+ 
+        }
+ 
+        return retval;
+ }
+ 
+ /* ------ internal utility function that compares two stings from back to front -- */
+ int dotconf_strcmp_from_back(const char* s1, const char* s2)
+ {
+        int retval = 0;
+        int i,j;
+        int len_1 = strlen(s1);
+        int len_2 = strlen(s2);
+ 
+        for (i=len_1,j=len_2;(i>=0 && j>=0);i--,j--)
+        {
+                if (s1[i] != s2[j])
+                {
+                        retval = -1;
+                        break;
+                }
+        }
+ 
+        return retval;
+ }
+ 
+ /* ------ internal utility function that determins if a string matches the '?' criteria -- */
+ int dotconf_question_mark_match(char* dir_name, char* pre, char* ext)
+ {
+        int retval = -1;
+        int dir_name_len = strlen(dir_name);
+        int pre_len = strlen(pre);
+        int ext_len = strlen(ext); 
+        int w_card_check = strcspn(ext,WILDCARDS);
+ 
+        if ( (w_card_check < ext_len) && (strncmp(dir_name,pre,pre_len) == 0)
+                 && (strcmp(dir_name,".") != 0 ) && (strcmp(dir_name,"..") != 0) )
+        {
+                retval = 1;    /* Another wildcard found */
+ 
+        } else {
+ 
+                if ((dir_name_len >= pre_len) && 
+                         (strncmp(dir_name,pre,pre_len) == 0) &&
+                         (strcmp(dir_name,".") != 0 ) &&
+                         (strcmp(dir_name,"..") != 0)) 
+                {
+                        retval = 0; /* Matches no other wildcards */
+                }
+ 
+        }
+ 
+        return retval;
+ }
+ 
+ /* ------ internal utility function that determins if a string matches the '*' criteria -- */
+ int dotconf_star_match(char* dir_name, char* pre, char* ext)
+ {
+        int retval = -1;
+        int dir_name_len = strlen(dir_name);
+        int pre_len = strlen(pre);
+        int ext_len = strlen(ext); 
+        int w_card_check = strcspn(ext,WILDCARDS);
+ 
+        if ( (w_card_check < ext_len) && (strncmp(dir_name,pre,pre_len) == 0)
+                 && (strcmp(dir_name,".") != 0 ) && (strcmp(dir_name,"..") != 0) )
+        {
+                retval = 1;    /* Another wildcard found */
+ 
+        } else {
+ 
+                if ((dir_name_len >= (ext_len + pre_len)) && 
+                         (dotconf_strcmp_from_back(dir_name,ext) == 0) &&
+                         (strncmp(dir_name,pre,pre_len) == 0) &&
+                         (strcmp(dir_name,".") != 0 ) &&
+                         (strcmp(dir_name,"..") != 0)) 
+                {
+                        retval = 0; /* Matches no other wildcards */
+                }
+ 
+        }
+ 
+        return retval;
+ }
+ 
+ /* ------ internal utility function that determins matches for filenames with   -- */
+ /* ------ a '?' in name and calls the Internal Include function on that filename -- */
+ int dotconf_handle_question_mark(command_t* cmd, char* path, char* pre, char* ext)
+ {
+        configfile_t *included; 
+        DIR* dh = 0;
+        struct dirent* dirptr = 0; 
+ 
+        char new_pre[CFG_MAX_FILENAME];
+        char already_matched[CFG_MAX_FILENAME];
+ 
+        char wc = '\0';
+ 
+        char* new_path = 0;
+        char* wc_path = 0;
+        char* wc_pre = 0;
+        char* wc_ext = 0;
+ 
+        int pre_len;
+        int new_path_len;
+        int name_len = 0;
+        int alloced = 0;         
+        int match_state = 0;
+ 
+        pre_len = strlen(pre);
+ 
+        if ((dh = opendir(path)) != NULL)
+        {
+                while ( (dirptr = readdir(dh)) != NULL )
+                {
+                        match_state = dotconf_question_mark_match(dirptr->d_name,pre,ext); 
+ 
+                        if (match_state >= 0) 
+                        {
+                                name_len = strlen(dirptr->d_name);
+                                new_path_len = strlen(path) + name_len + strlen(ext) + 1; 
+ 
+                                if ( !alloced )
+                                {
+                                        if ((new_path = (char*)malloc(new_path_len)) == NULL )
+                                        {
+                                                return -1;
+                                        }
+ 
+                                        alloced = new_path_len;
+ 
+                                } else {
+ 
+                                                if ( new_path_len > alloced )
+                                                {
+                                                        if ( realloc(new_path,new_path_len) == NULL )
+                                                        {
+                                                                free(new_path);
+                                                                return -1;
+                                                        }
+ 
+                                                }
+ 
+                                }
+ 
+                                if (match_state == 1)
+                                {
+ 
+                                        strncpy(new_pre,dirptr->d_name,(name_len > pre_len)?(pre_len+1):pre_len);
+                                        new_pre[(name_len > pre_len)?(pre_len+1):pre_len] = '\0';
+ 
+                                        sprintf(new_path,"%s%s%s",path,new_pre,ext);
+ 
+                                        if (strcmp(new_path,already_matched) == 0)
+                                        {
+                                                continue; /* Already searched this expression */
+ 
+                                        } else {
+ 
+                                                strcpy(already_matched,new_path);
+ 
+                                        }
+ 
+                                        if (dotconf_find_wild_card(new_path,&wc,&wc_path,&wc_pre,&wc_ext) >= 0)
+                                        {
+                                                if ( dotconf_handle_wild_card(cmd,wc,wc_path,wc_pre,wc_ext) < 0)
+                                                {
+                                                        dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                                                        "Error occured while processing wildcard %c\n"
+                                                                                        "Filename is '%s'\n", wc, new_path);
+ 
+                                                        free(new_path);
+                                                        dotconf_wild_card_cleanup(wc_path,wc_pre);
+                                                        return -1;
+                                                }
+ 
+                                                dotconf_wild_card_cleanup(wc_path,wc_pre);
+                                                continue;
+                                        }
+ 
+                                }
+ 
+                                sprintf(new_path,"%s%s",path,dirptr->d_name);
+ 
+                                if (access(new_path, R_OK))
+                                {
+                                        dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                                        "Cannot open %s for inclusion.\n"
+                                                                        "IncludePath is '%s'\n", new_path, cmd->configfile->includepath);
+                                        return -1;
+                                }
+ 
+                                included = dotconf_create(new_path, cmd->configfile->config_options[1], 
+                                                                                        cmd->configfile->context, cmd->configfile->flags);
+                                if (included)
+                                {
+                                        included->errorhandler = cmd->configfile->errorhandler;
+                                        included->contextchecker = cmd->configfile->contextchecker;
+                                        dotconf_command_loop(included);
+                                        dotconf_cleanup(included);
+                                }
+ 
+                        }
+ 
+                }
+ 
+                closedir(dh);
+                free(new_path);
+ 
+        }
+ 
+        return 0;
+ }
+ 
+ /* ------ internal utility function that determins matches for filenames with   --- */
+ /* ------ a '*' in name and calls the Internal Include function on that filename -- */
+ int dotconf_handle_star(command_t* cmd, char* path, char* pre, char* ext)
+ {
+        configfile_t *included; 
+        DIR* dh = 0;
+        struct dirent* dirptr = 0; 
+ 
+        char new_pre[CFG_MAX_FILENAME];
+        char new_ext[CFG_MAX_FILENAME];
+        char already_matched[CFG_MAX_FILENAME];
+ 
+        char wc = '\0';
+ 
+        char* new_path = 0;
+        char* s_ext = 0;
+        char* t_ext = 0;
+        char* sub = 0;
+        char* wc_path = 0;
+        char* wc_pre = 0;
+        char* wc_ext = 0;
+ 
+        int pre_len;
+        int new_path_len;
+        int name_len = 0;
+        int alloced = 0;         
+        int match_state = 0;
+        int t_ext_count = 0;
+        int sub_count = 0;
+ 
+        pre_len = strlen(pre);
+        memset(already_matched,0,CFG_MAX_FILENAME);
+        s_ext = ext;
+ 
+        while (dotconf_is_wild_card(*s_ext)) /* remove trailing wild-cards proceeded by * */
+        {
+                s_ext++;
+        }
+ 
+        t_ext = s_ext;
+ 
+        while(t_ext != NULL && !(dotconf_is_wild_card(*t_ext)) && *t_ext != '\0')
+        {
+                t_ext++;                                /* find non-wild-card string */
+                t_ext_count++;
+        }
+ 
+        strncpy(new_ext,s_ext,t_ext_count);
+        new_ext[t_ext_count] = '\0';
+ 
+        if ((dh = opendir(path)) != NULL)
+        {
+                while ( (dirptr = readdir(dh)) != NULL )
+                {
+                        sub_count = 0;
+                        t_ext_count = 0;
+ 
+                        match_state = dotconf_star_match(dirptr->d_name,pre,s_ext); 
+ 
+                        if (match_state >= 0) 
+                        {
+                                name_len = strlen(dirptr->d_name);
+                                new_path_len = strlen(path) + name_len + strlen(s_ext) + 1; 
+ 
+                                if ( !alloced )
+                                {
+                                        if ((new_path = (char*)malloc(new_path_len)) == NULL )
+                                        {
+                                                return -1;
+                                        }
+ 
+                                        alloced = new_path_len;
+ 
+                                } else {
+ 
+                                                if ( new_path_len > alloced )
+                                                {
+                                                        if ( realloc(new_path,new_path_len) == NULL )
+                                                        {
+                                                                free(new_path);
+                                                                return -1;
+                                                        }
+ 
+                                                }
+ 
+                                }
+ 
+                                if (match_state == 1)
+                                {
+ 
+                                        if ((sub = strstr((dirptr->d_name+pre_len),new_ext)) == NULL)
+                                        {
+                                                continue;
+                                        }
+ 
+                                        while (sub != dirptr->d_name)
+                                        {
+                                                sub--;
+                                                sub_count++;
+                                        }
+                                        
+                                        if (sub_count + t_ext_count > name_len)
+                                        {
+                                                continue;
+                                        }
+ 
+                                        strncpy(new_pre,dirptr->d_name,(sub_count+t_ext_count));
+                                        new_pre[sub_count+t_ext_count] = '\0';
+                                        strcat(new_pre,new_ext);
+ 
+                                        sprintf(new_path,"%s%s%s",path,new_pre,t_ext);
+ 
+                                        if (strcmp(new_path,already_matched) == 0)
+                                        {
+                                                continue; /* Already searched this expression */
+ 
+                                        } else {
+                                                
+                                                strcpy(already_matched,new_path);
+ 
+                                        }
+ 
+                                        if (dotconf_find_wild_card(new_path,&wc,&wc_path,&wc_pre,&wc_ext) >= 0)
+                                        {
+                                                if ( dotconf_handle_wild_card(cmd,wc,wc_path,wc_pre,wc_ext) < 0)
+                                                {
+                                                        dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                                                        "Error occured while processing wildcard %c\n"
+                                                                                        "Filename is '%s'\n", wc, new_path);
+ 
+                                                        free(new_path);
+                                                        dotconf_wild_card_cleanup(wc_path,wc_pre);
+                                                        return -1;
+                                                }
+ 
+                                                dotconf_wild_card_cleanup(wc_path,wc_pre);
+                                                continue;
+                                        }
+ 
+                                }
+ 
+                                sprintf(new_path,"%s%s",path,dirptr->d_name);
+ 
+                                if (access(new_path, R_OK))
+                                {
+                                        dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                                        "Cannot open %s for inclusion.\n"
+                                                                        "IncludePath is '%s'\n", new_path, cmd->configfile->includepath);
+                                        return -1;
+                                }
+ 
+                                included = dotconf_create(new_path, cmd->configfile->config_options[1], 
+                                                                                        cmd->configfile->context, cmd->configfile->flags);
+                                if (included)
+                                {
+                                        included->errorhandler = cmd->configfile->errorhandler;
+                                        included->contextchecker = cmd->configfile->contextchecker;
+                                        dotconf_command_loop(included);
+                                        dotconf_cleanup(included);
+                                }
+ 
+                        }
+ 
+                }
+ 
+                closedir(dh);
+                free(new_path);
+ 
+        }
+ 
+        return 0;
+ }
+ 
+ /* ------ callbacks of the internal option (Include, IncludePath) ------------------------------- */
+ DOTCONF_CB(dotconf_cb_include)
+ {
+        char *filename = 0;
+        configfile_t *included; 
+ 
+        char wild_card;
+        char* path = 0;
+        char* pre = 0;
+        char* ext = 0;  
+ 
+ 
+        if (cmd->configfile->includepath
+                && cmd->data.str[0] != '/' && cmd->configfile->includepath[0] != '\0')
+        {
+                /* relative file AND include path is used */
+                int len, inclen;
+                char *sl;
+ 
+                inclen = strlen(cmd->configfile->includepath);
+                if (( len = (strlen(cmd->data.str) + inclen + 1)) == CFG_MAX_FILENAME)
+                {
+                        dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                        "Absolute filename too long (>%d)", CFG_MAX_FILENAME);
+                        return NULL;
+                }
+ 
+                if (cmd->configfile->includepath[inclen - 1] == '/')
+                        sl = "";
+                else
+                {
+                        sl = "/";
+                        len++;
+                }
+ 
+                filename = malloc(len);
+                snprintf(filename, len, "%s%s%s", 
+                                 cmd->configfile->includepath, sl, cmd->data.str);
+        }
+        else                                            /* fully qualified, or no includepath */
+                filename = strdup(cmd->data.str);
+ 
+        /* Added wild card support here */
+        if (dotconf_find_wild_card(filename,&wild_card,&path,&pre,&ext) >= 0)
+        {
+                if ( dotconf_handle_wild_card(cmd,wild_card,path,pre,ext) < 0)
+                {
+                        dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                        "Error occured while attempting to process %s for inclusion.\n"
+                                                        "IncludePath is '%s'\n", filename, cmd->configfile->includepath);
+                }
+ 
+                dotconf_wild_card_cleanup(path,pre);
+                free(filename);
+                return NULL;
+        }
+ 
+        if (access(filename, R_OK))
+        {
+                dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
+                                                "Cannot open %s for inclusion.\n"
+                                                "IncludePath is '%s'\n", filename, cmd->configfile->includepath);
+                free(filename);
+                return NULL;
+        }
+ 
+        included = dotconf_create(filename, cmd->configfile->config_options[1], 
+                                                           cmd->configfile->context, cmd->configfile->flags);
+        if (included)
+        {
+                included->contextchecker = (dotconf_contextchecker_t) cmd->configfile->contextchecker;
+                included->errorhandler = (dotconf_errorhandler_t) cmd->configfile->errorhandler;
+ 
+                dotconf_command_loop(included);
+                dotconf_cleanup(included);
+        }
+ 
+        free(filename);
+        return NULL;
+ }
+ 
+ DOTCONF_CB(dotconf_cb_includepath)
+ {
+        char *env = getenv(CFG_INCLUDEPATH_ENV);
+        /* environment overrides configuration file setting */
+        if (!env)                                       
+                snprintf(cmd->configfile->includepath, CFG_MAX_FILENAME, "%s", cmd->data.str);
+        return NULL;
+ }
+ 
+ /* vim:set ts=4: */


ossp-pkg/lmtp2nntp/lmtp2nntp_dotconf.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,262 ----
+ #ifndef DOTCONF_H
+ #define DOTCONF_H
+ 
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ 
+ /* stdio.h should be included by the application - as the manual page says */
+ #ifndef _STDIO_H
+ #include <stdio.h>             /* needed for FILE* */
+ #endif
+ 
+ #ifdef WIN32
+ # ifndef R_OK
+ #define R_OK 0
+ # endif
+ #endif
+ 
+ /* some buffersize definitions */
+ #define CFG_BUFSIZE 4096       /* max length of one line */
+ #define CFG_MAX_OPTION 32      /* max length of any option name */
+ #define CFG_MAX_VALUE 4064     /* max length of any options value */
+ #define CFG_MAX_FILENAME 256   /* max length of a filename */
+ #define CFG_VALUES 16          /* max # of arguments an option takes */
+ 
+ #define CFG_INCLUDEPATH_ENV "DC_INCLUDEPATH"
+ #define WILDCARDS      "*?"            /* list of supported wild-card characters */
+ 
+ /* constants for type of option */
+ #define ARG_TOGGLE    0                /* TOGGLE on,off; yes,no; 1, 0; */
+ #define ARG_INT       1                /* callback wants an integer */
+ #define ARG_STR       2                /* callback expects a \0 terminated str */
+ #define ARG_LIST      3                /* wants list of strings */
+ #define ARG_NAME      4                /* wants option name plus ARG_LIST stuff */
+ #define ARG_RAW       5                /* wants raw argument data */
+ #define ARG_NONE      6                /* does not expect ANY args */
+ 
+ #define CTX_ALL       0                /* context: option can be used anywhere */
+ 
+ /* for convenience of terminating the dotconf_options list */
+ #define LAST_OPTION                                            { "", 0, NULL, NULL    }
+ #define LAST_CONTEXT_OPTION                            { "", 0, NULL, NULL, 0 }                
+ 
+ #define DOTCONF_CB(__name)     const char *__name(command_t *cmd,             \
+                                                   context_t *ctx)
+ #define FUNC_ERRORHANDLER(_name) int _name(configfile_t * configfile,            \
+                                                                                        int type, long dc_errno, const char *msg)
+ 
+ 
+ /* some flags that change the runtime behaviour of dotconf */
+ #define NONE 0
+ #define CASE_INSENSITIVE 1<<0  /* match option names case insensitive */
+ #define DONT_SUBSTITUTE 1<<1    /* do not call substitute_env after read_arg */
+ 
+ /* syslog style errors as suggested by Sander Steffann <sander@steffann.nl> */
+ #ifdef HAVE_SYSLOG
+ #include <syslog.h>
+ 
+ #define DCLOG_EMERG            LOG_EMERG                       /* system is unusable */
+ #define DCLOG_ALERT            LOG_ALERT                       /* action must be taken immediately */
+ #define DCLOG_CRIT             LOG_CRIT                        /* critical conditions */
+ #define DCLOG_ERR              LOG_ERR                         /* error conditions */
+ #define DCLOG_WARNING  LOG_WARNING                     /* warning conditions */
+ #define DCLOG_NOTICE   LOG_NOTICE                      /* normal but significant condition */
+ #define DCLOG_INFO             LOG_INFO                        /* informational */
+ #define DCLOG_DEBUG            LOG_DEBUG                       /* debug-level messages */
+ 
+ #define DCLOG_LEVELMASK        LOG_PRIMASK                     /* mask off the level value */
+ 
+ #else /* HAVE_SYSLOG */
+ 
+ #define DCLOG_EMERG     0       /* system is unusable */
+ #define DCLOG_ALERT     1       /* action must be taken immediately */
+ #define DCLOG_CRIT      2       /* critical conditions */
+ #define DCLOG_ERR       3       /* error conditions */
+ #define DCLOG_WARNING   4       /* warning conditions */
+ #define DCLOG_NOTICE    5       /* normal but significant condition */
+ #define DCLOG_INFO      6       /* informational */
+ #define DCLOG_DEBUG     7       /* debug-level messages */
+ 
+ #define DCLOG_LEVELMASK 7       /* mask off the level value */
+ 
+ #endif /* HAVE_SYSLOG */
+ 
+ /* callback types for dotconf_callback */
+ 
+ /* error constants */
+ #define ERR_NOERROR                0x0000
+ #define ERR_PARSE_ERROR            0x0001
+ #define ERR_UNKNOWN_OPTION         0x0002
+ #define ERR_WRONG_ARG_COUNT        0x0003
+ #define ERR_INCLUDE_ERROR          0x0004
+ #define ERR_NOACCESS               0x0005
+ #define ERR_USER                   0x1000   /* base for userdefined errno's */
+ 
+ /* i needed this to check an ARG_LIST entry if it's toggled in one of my apps; maybe you do too */
+ #define CFG_TOGGLED(_val) ( (_val[0] == 'Y'                                \
+                              || _val[0] == 'y')                            \
+                              || (_val[0] == '1')                           \
+                              || ((_val[0] == 'o'                           \
+                                  || _val[0] == 'O')                        \
+                                 && (_val[1] == 'n'                         \
+                                     || _val[1] == 'N')))
+ 
+ enum callback_types
+ {
+        ERROR_HANDLER = 1,
+        CONTEXT_CHECKER,
+ };
+ 
+ typedef enum callback_types                    callback_types;
+ typedef struct configfile_t                    configfile_t;
+ typedef struct configoption_t       configoption_t;
+ typedef struct configoption_t          ConfigOption;
+ typedef struct command_t               command_t;
+ typedef void                        context_t;
+ typedef void                        info_t;
+ 
+ typedef const char *(*dotconf_callback_t)(command_t *, context_t *);
+ typedef int (*dotconf_errorhandler_t)(configfile_t *, int, unsigned long, const char *);
+ typedef const char *(*dotconf_contextchecker_t)(command_t *, unsigned long);
+ 
+ struct configfile_t
+ {
+        /* ------ the fields in configfile_t are provided to the app via command_t's ; READ ONLY! --- */
+ 
+        FILE *stream;
+        char eof;                       /* end of file reached ? */
+        size_t size;                                    /* file size; cached on-demand for here-documents */
+ 
+        context_t *context;
+ 
+        configoption_t const **config_options;
+        int config_option_count;
+ 
+        /* ------ misc read-only fields ------------------------------------------------------------- */
+        char *filename;                         /* name of file this option was found in */
+        unsigned long line;             /* line number we're currently at */
+        unsigned long flags;                    /* runtime flags given to dotconf_open */
+ 
+        char *includepath;
+ 
+        /* ------ some callbacks for interactivity -------------------------------------------------- */
+        dotconf_errorhandler_t          errorhandler;
+        dotconf_contextchecker_t        contextchecker;
+ 
+        int (*cmp_func)(const char *, const char *, size_t);
+ };
+ 
+ struct configoption_t
+ {
+   const char *name;                                                            /* name of configuration option */
+   int type;                                                                            /* for possible values, see above */
+   dotconf_callback_t callback;                                 /* callback function */
+   info_t *info;                                                                        /* additional info for multi-option callbacks */
+   unsigned long context;                                               /* context sensitivity flags */
+ };
+ 
+ struct command_t
+ {
+        const char *name;                       /* name of the command */
+        configoption_t *option;         /* the option as given in the app; READ ONLY */
+ 
+        /* ------ argument data filled in for each line / command ----------------------------------- */
+        struct {
+                long value;                     /* ARG_INT, ARG_TOGGLE */
+                char *str;                      /* ARG_STR */
+                char **list;                    /* ARG_LIST */
+        } data;
+        int arg_count;                          /* number of arguments (in data.list) */
+ 
+        /* ------ misc context information ---------------------------------------------------------- */
+        configfile_t *configfile;
+        context_t *context;
+ };
+ 
+ /* ------ dotconf_create() - create the configfile_t needed for further dot.conf fun ------------ */
+ configfile_t *dotconf_create(char *, const configoption_t *, context_t *, unsigned long);
+ 
+ /* ------ dotconf_cleanup() - tidy up behind dotconf_create and the parser dust ----------------- */
+ void dotconf_cleanup(configfile_t *configfile);
+ 
+ /* ------ dotconf_command_loop() - iterate through each line of file and handle the commands ---- */
+ int dotconf_command_loop(configfile_t *configfile);
+ 
+ /* ------ dotconf_command_loop_until_error() - like continue_line but return on the first error - */
+ const char *dotconf_command_loop_until_error(configfile_t *configfile);
+ 
+ /* ------ dotconf_continue_line() - check if line continuation is to be handled ----------------- */
+ int dotconf_continue_line(char *buffer, size_t length);
+ 
+ /* ------ dotconf_get_next_line() - read in the next line of the configfile_t ------------------- */
+ int dotconf_get_next_line(char *buffer, size_t bufsize, configfile_t *configfile);
+ 
+ /* ------ dotconf_get_here_document() - read the here document until delimit is found ----------- */
+ char *dotconf_get_here_document(configfile_t *configfile, const char *delimit);
+ 
+ /* ------ dotconf_invoke_command() - call the callback for command_t ---------------------------- */
+ const char *dotconf_invoke_command(configfile_t *configfile, command_t *cmd);
+ 
+ /* ------ dotconf_find_command() - iterate through all registered options trying to match ------- */
+ configoption_t *dotconf_find_command(configfile_t *configfile, const char *command);
+ 
+ /* ------ dotconf_read_arg() - read one argument from the line handling quoting and escaping ---- */
+ /*
+        side effects: the char* returned by dotconf_read_arg is malloc() before, hence that pointer
+                   will have to be free()ed later. 
+ */
+ char *dotconf_read_arg(configfile_t *configfile, char **line);
+ 
+ /* ------ dotconf_handle_command() - parse, substitute, find, invoke the command found in buffer  */
+ const char *dotconf_handle_command(configfile_t *configfile, char *buffer);
+ 
+ /* ------ dotconf_register_option() - add a new option table to the list of commands ------------ */
+ void dotconf_register_options(configfile_t *configfile, const configoption_t *options);
+ 
+ /* ------ dotconf_warning() - handle the dispatch of error messages of various levels ----------- */
+ int dotconf_warning(configfile_t *configfile, int level, unsigned long errnum, const char *, ...);
+ 
+ /* ------ dotconf_callback() - register a special callback -------------------------------------- */
+ void dotconf_callback(configfile_t *configfile, callback_types type, dotconf_callback_t);
+ 
+ /* ------ dotconf_substitute_env() - handle the substitution on environment variables ----------- */
+ char *dotconf_substitute_env(configfile_t *, char *);
+ 
+ /* ------ internal utility function that verifies if a character is in the WILDCARDS list -- */
+ int dotconf_is_wild_card(char value);
+ 
+ /* ------ internal utility function that calls the appropriate routine for the wildcard passed in -- */
+ int dotconf_handle_wild_card(command_t* cmd, char wild_card, char* path, char* pre, char* ext);
+ 
+ /* ------ internal utility function that frees allocated memory from dotcont_find_wild_card -- */
+ void dotconf_wild_card_cleanup(char* path, char* pre);
+ 
+ /* ------ internal utility function to check for wild cards in file path -- */
+ /* ------ path and pre must be freed by the developer ( dotconf_wild_card_cleanup) -- */ 
+ int dotconf_find_wild_card(char* filename, char* wildcard, char** path, char** pre, char** ext);
+ 
+ /* ------ internal utility function that compares two stings from back to front -- */
+ int dotconf_strcmp_from_back(const char* s1, const char* s2);
+ 
+ /* ------ internal utility function that determins if a string matches the '?' criteria -- */
+ int dotconf_question_mark_match(char* dir_name, char* pre, char* ext);
+ 
+ /* ------ internal utility function that determins if a string matches the '*' criteria -- */
+ int dotconf_star_match(char* dir_name, char* pre, char* ext);
+ 
+ /* ------ internal utility function that determins matches for filenames with   -- */
+ /* ------ a '?' in name and calls the Internal Include function on that filename -- */
+ int dotconf_handle_question_mark(command_t* cmd, char* path, char* pre, char* ext);
+ 
+ /* ------ internal utility function that determins matches for filenames with   -- */
+ /* ------ a '*' in name and calls the Internal Include function on that filename -- */
+ int dotconf_handle_star(command_t* cmd, char* path, char* pre, char* ext);
+ 
+ 
+ 
+ #ifdef __cplusplus
+ } /* extern "C" */
+ #endif
+ 
+ #endif /* DOTCONF_H */


ossp-pkg/lmtp2nntp/lmtp2nntp_global.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,52 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  lmtp2nntp.h: LMTP to NNTP global header
+ */
+ 
+ #ifndef __LMTP2NNTP_H__
+ #define __LMTP2NNTP_H__
+ 
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ 
+ #define log0(ctx,level,msg) \
+     l2_channel_log((ctx)->l2, L2_LEVEL_##level, "%P" msg)
+ #define log1(ctx,level,msg,a1) \
+     l2_channel_log((ctx)->l2, L2_LEVEL_##level, "%P" msg, a1)
+ #define log2(ctx,level,msg,a1,a2) \
+     l2_channel_log((ctx)->l2, L2_LEVEL_##level, "%P" msg, a1, a2)
+ #define log3(ctx,level,msg,a1,a2,a3) \
+     l2_channel_log((ctx)->l2, L2_LEVEL_##level, "%P" msg, a1, a2, a3)
+ #define log4(ctx,level,msg,a1,a2,a3,a4) \
+     l2_channel_log((ctx)->l2, L2_LEVEL_##level, "%P" msg, a1, a2, a3, a4)
+ 
+ #define STMT(stuff) do { stuff } while (0)
+ #define CU(returncode) STMT( rc = returncode; goto CUS; )
+ #define VCU STMT( goto CUS; )
+ 
+ #endif /* __LMTP2NNTP_H__ */


ossp-pkg/lmtp2nntp/lmtp2nntp_lmtp.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,490 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  lmtp.c: Local Mail Transfer Protocol (LMTP) server library
+ */
+ 
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <ctype.h>
+ #include <errno.h>
+ 
+ #include "lmtp2nntp_lmtp.h"
+ 
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ 
+ #ifndef NUL
+ #define NUL '\0'
+ #endif
+ 
+ /* maximum LMTP protocol line length */
+ #define LMTP_LINE_MAXLEN 1024
+ 
+ /* maximum number of verbs/callbacks to be registered */
+ #define LMTP_MAXVERBS 32
+ 
+ typedef struct {
+     int   rl_cnt;
+     char *rl_bufptr;
+     char  rl_buf[LMTP_LINE_MAXLEN];
+ } lmtp_readline_t;
+ 
+ typedef struct {
+     char       *verb;
+     lmtp_cb_t   cb;
+     void       *ctx;
+ } lmtp_dispatch_t;
+ 
+ struct lmtp_st {
+     lmtp_io_t         io;       /* select, read, write functions */
+     lmtp_readline_t   rl;       /* a function to read in a single line */
+     lmtp_dispatch_t **dispatch; /* LMTP commands to be dispatched */
+ };
+ 
+ ssize_t lmtp_fd_read(void *_ctx, void *buf, size_t buflen)
+ {
+     lmtp_fd_t *ctx = (lmtp_fd_t *)_ctx;
+     return read(ctx->fd, buf, buflen);
+ }
+ 
+ ssize_t lmtp_fd_write(void *_ctx, const void *buf, size_t buflen)
+ {
+     lmtp_fd_t *ctx = (lmtp_fd_t *)_ctx;
+     return write(ctx->fd, buf, buflen);
+ }
+ 
+ static int verbindex(lmtp_t *lmtp, char *verb)
+ {
+     /* returns the index of the verb or -1 if verb not registered */
+     int i;
+ 
+     for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++)
+         if (strcasecmp(lmtp->dispatch[i]->verb, verb) == 0) 
+             return i;
+     return -1;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_default(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx)
+ {
+     lmtp_res_t res;
+     lmtp_rc_t rc = LMTP_OK;
+ 
+     res.statuscode = "500";
+     res.dsncode    = "5.5.1";
+     res.statusmsg  = "Command unrecognized.";
+     lmtp_response(lmtp, &res);
+     return rc;
+ }
+ 
+ lmtp_t *lmtp_create(lmtp_io_t *io)
+ {
+     /*  create a lmtp structure allocating memory for it and initializing it.
+      *  A lmtp_cb_default() callback is registered for the default "" verb.
+      *  The _rfd_ and _wfd_ args are passed to the read(), write() 
+      *  functions and must have meaning for them. If _io_ is NULL,
+      *  the system io functions are used. You can provide an _io_ structure
+      *  and specify alternate functions. Ommiting one or more functions inside
+      *  the _io_ structure by NULLing it causes use of the system default
+      *  function.
+      */
+     lmtp_t *lmtp;
+ 
+     if ((lmtp = (lmtp_t *)malloc(sizeof(lmtp_t))) == NULL) 
+         return NULL;
+ 
+     if (io == NULL) 
+         return NULL;
+ 
+     lmtp->io.ctx    = io->ctx;
+     lmtp->io.select = io->select;
+     lmtp->io.read   = io->read;
+     lmtp->io.write  = io->write;
+ 
+     lmtp->rl.rl_cnt = 0;
+     lmtp->rl.rl_bufptr = NULL;
+     lmtp->rl.rl_buf[0] = NUL;
+     
+     if ((lmtp->dispatch = (lmtp_dispatch_t **)malloc(sizeof(void *)*LMTP_MAXVERBS)) == NULL)
+         return NULL;
+     lmtp->dispatch[0] = NULL;
+ 
+     lmtp_register(lmtp, "", lmtp_cb_default, NULL, NULL, NULL);
+ 
+     return lmtp;
+ }
+ 
+ void lmtp_destroy(lmtp_t *lmtp)
+ {
+     int i;
+ 
+     if (lmtp == NULL)
+         return;
+ 
+     for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
+         free(lmtp->dispatch[i]->verb); /* lmtp_register() */
+         free(lmtp->dispatch[i]);       /* lmtp_register() */
+     }
+     free(lmtp->dispatch);              /* lmtp_create() */
+     free(lmtp);                        /* lmtp_create() */
+     return;
+ }
+ 
+ lmtp_rc_t lmtp_readline(lmtp_t *lmtp, char *buf, size_t buflen)
+ {
+     /* read a line (characters until NL) from input stream */
+     size_t n;
+     char c;
+     lmtp_readline_t *rl = &lmtp->rl;
+ 
+     if (lmtp == NULL)
+         return LMTP_ERR_ARG;
+     for (n = 0; n < buflen-1;) {
+ 
+         /* fetch one character (but read more) */
+         if (rl->rl_cnt <= 0) {
+             do {
+                 rl->rl_cnt = lmtp->io.read(lmtp->io.ctx, rl->rl_buf, LMTP_LINE_MAXLEN);
+             } while (rl->rl_cnt == -1 && errno == EINTR);
+             if (rl->rl_cnt == -1)
+                 return LMTP_ERR_SYSTEM;
+             if (rl->rl_cnt == 0)
+                 return LMTP_EOF;
+             rl->rl_bufptr = rl->rl_buf;
+         }
+ 
+         /* act on fetched character */
+         rl->rl_cnt--;
+         c = *rl->rl_bufptr++;
+         if (c == '\r')
+             continue;       /* skip copying CR */
+         if (c == '\n')
+             break;          /* end of line */
+         buf[n++] = c;       /* output char into given buffer */
+ 
+     }
+     buf[n] = NUL;          /* string termination */
+     if (n == (buflen-1)) 
+         return LMTP_ERR_OVERFLOW;
+     return LMTP_OK;
+ }
+ 
+ lmtp_rc_t lmtp_readmsg(lmtp_t *lmtp, char **cppBuf, size_t maxlen)
+ {
+     /*  read lines until end of message, unescape dots.
+      *  on success, returns a buffer which has to be free(3)d by the caller
+      *
+      *  NOTE: the underlying lmtp_readline() already reduces any
+      *        CR/LF combination to a string terminating zero. Callers of this
+      *        function must assume multiline messages have lines terminated
+      *        with NL only.
+      *
+      *  RFC0821 "Simple Mail Transfer Protocol" [excerpt]
+      *  4.5.2. TRANSPARENCY
+      *  When a line of mail text is received by the receiver-SMTP it checks
+      *  the line.  If the line is composed of a single period it is the end of
+      *  mail.  If the first character is a period and there are other
+      *  characters on the line, the first character is deleted.
+      */
+     lmtp_rc_t rc = LMTP_OK;
+     char *cpBuf;       /* buffer as a whole */
+     char *cpBufrealloc;/* buffer before realloc */
+     char *cpPtr;       /* write cursor */
+     char *cpLine;      /* start of the current line (see offsetline) */
+     size_t nBuf;       /* size of buffer, doubled through realloc until maximum reached */
+     size_t offset;     /* required when cpBuf changed through realloc */
+     size_t offsetline; /* memorizing start of line when reallocing in the middle of a line */
+ 
+     for (nBuf = 4096; nBuf > maxlen; nBuf = nBuf >> 1);
+     if ((cpBuf = (char *)malloc(nBuf)) == NULL)
+         return LMTP_ERR_MEM;
+     *cppBuf = cpBuf;                           /* tell caller about the buffer */
+     cpPtr = cpBuf;                             /* initialize write cursor */
+     cpLine = cpBuf;                            /* initialize start of line */
+     while (1) {
+         rc = lmtp_readline(lmtp, cpPtr, nBuf-(cpPtr-cpBuf));
+         if (rc == LMTP_ERR_OVERFLOW) {
+             if (nBuf == maxlen) 
+                 return LMTP_ERR_OVERFLOW;
+             offset = nBuf-1;                    /* write cursor offset is end of buffer */
+             offsetline = cpLine - cpBuf;        /* remember start of line offset */
+             nBuf *= 2;                          /* increase buffer */
+             if (nBuf > maxlen) 
+                 nBuf = maxlen;                  /* but don't exceed maximum */
+             if ((cpBufrealloc = (char *)realloc(cpBuf, nBuf)) == NULL) {
+                 free(cpBuf);
+                 return LMTP_ERR_MEM;
+             }
+             cpBuf = cpBufrealloc;
+             *cppBuf = cpBuf;                    /* tell caller about the new buffer */
+             cpPtr = cpBuf + offset;             /* recover write cursor */
+             cpLine = cpBuf + offsetline;        /* recover start of line */
+         }
+         else if (rc == LMTP_OK) {
+             if (strcmp(cpLine, ".") == 0) {     /* dot alone is end of message */
+                 *cpLine = NUL;                 /* hide dot from caller */
+                 break;
+             }
+             if (*cpLine == '.')                 /* escaped dot */
+                 memmove(cpLine, cpLine+1, strlen(cpLine+1)+1);
+             cpPtr += strlen(cpPtr);             /* write cursor to the end */
+             *cpPtr++ = '\n';                    /* artifical NL */
+             *cpPtr = NUL;                      /* artifical end of string */
+             cpLine = cpPtr;                     /* start of line */
+         }
+         else break;                             /* rc == LMTP_ERR* */
+     }
+     return rc;
+ }
+ 
+ lmtp_rc_t lmtp_request(lmtp_t *lmtp, lmtp_req_t *req)
+ {  
+     /*  reads a line and attaches the buffer to req->msg;
+      *  pulls the verb out and attaches the verb to req->verb;
+      * 
+      *  LMTP_OK           req->msg set, req->verb set  means normal operation
+      *  LMTP_OK           req->msg set, req->verb ""   means no verb seen
+      *  LMTP_EOF          req->msg set, req->verb NULL means eof
+      *  LMTP_ERR_OVERFLOW req->msg set, req->verb NULL means buf overflow
+      *  LMTP_ERR_SYSTEM   req->msg set, req->verb NULL means system error
+      *
+      *  RFC0821 "Simple Mail Transfer Protocol" [excerpts]
+      *  4.1.1. COMMAND SEMANTICS
+      *  The command codes themselves are alphabetic characters terminated by
+      *  <SP> if parameters follow and <CRLF> otherwise.
+      *  4.1.2. COMMAND SYNTAX
+      *  <SP> ::= the space character (ASCII code 32)
+      */
+     lmtp_rc_t rc;
+     char *verb;
+     int verblen;
+     int i;
+ 
+     req->verb = NULL;
+     if ((req->msg = (char *)malloc(LMTP_LINE_MAXLEN)) == (char *)NULL)
+         return LMTP_ERR_MEM;
+     if ((rc = lmtp_readline(lmtp, req->msg, LMTP_LINE_MAXLEN)) != LMTP_OK)
+         return rc;
+     for (i = 0; (i < LMTP_MAXVERBS) && (lmtp->dispatch[i] != NULL); i++) {
+         if ((verb = lmtp->dispatch[i]->verb) != NULL) {  /* skip NULL verb */
+             if ((verblen = strlen(verb)) == 0) 
+                 continue; /* skip  ""  verb */
+             if (   (strlen(req->msg) >= verblen)
+                 && (strncasecmp(req->msg, verb, verblen) == 0)
+                 && (   (req->msg[verblen] == NUL)
+                     || (req->msg[verblen] == ' ') )           ) {
+                 req->verb = verb;
+                 return LMTP_OK;
+             }
+         }
+     }
+     req->verb = "";
+     return LMTP_OK;
+ }
+ 
+ lmtp_rc_t lmtp_response(lmtp_t *lmtp, lmtp_res_t *res)
+ {
+     /*  write the status message. For multiline status messages it is
+      *  neccessary to repeat the status and dsn codes for every line with a
+      *  dash after the status for every line but the last one
+      */
+     lmtp_rc_t rc = LMTP_OK;
+     int rv;
+     int dash;
+     int len;
+     char *cpS;
+     char *cpE;
+     char formatbuf[LMTP_LINE_MAXLEN];
+ 
+     if (   strlen(res->statuscode) != 3
+         || !isdigit((int)res->statuscode[0])
+         || !isdigit((int)res->statuscode[1])
+         || !isdigit((int)res->statuscode[2]))
+         return LMTP_ERR_ARG;
+ 
+     if (res->dsncode != NULL) {
+         if (   (strlen(res->dsncode) != 5)
+             || !isdigit((int)res->dsncode[0])
+             || (res->dsncode[1] != '.')
+             || !isdigit((int)res->dsncode[2])
+             || (res->dsncode[3] != '.')
+             || !isdigit((int)res->dsncode[4])
+             || (res->dsncode[0] != res->statuscode[0]))
+             return LMTP_ERR_ARG;
+     }
+ 
+     cpS = res->statusmsg;
+     for (dash = 1; dash == 1; ) {
+         if ((cpE = strchr(cpS, '\n')) == NULL) {
+             cpE = cpS+strlen(cpS);
+             dash = 0;
+         }
+         if (res->dsncode != NULL)
+             len = sprintf(formatbuf, "%3.3s%c%5.5s ", res->statuscode, dash ? '-' : ' ', res->dsncode);
+         else
+             len = sprintf(formatbuf, "%3.3s%c", res->statuscode, dash ? '-' : ' ');
+         if ((len + cpE - cpS + 2) > sizeof(formatbuf)) { /* status + line + '\r\n' does not fit into formatbuf */
+             dash = 1;
+             if ((cpE = cpS + sizeof(formatbuf) - 2 - len) <= cpS) /* no space for line at all */
+                 return LMTP_ERR_ARG;
+         }
+         strncpy(formatbuf+len, cpS, cpE-cpS);
+         len += (cpE-cpS);
+         formatbuf[len++] = '\r';
+         formatbuf[len++] = '\n';
+         do {
+             rv = lmtp->io.write(lmtp->io.ctx, formatbuf, len);
+         } while (rv == -1 && errno == EINTR);
+         if (rv == -1)
+             return LMTP_ERR_SYSTEM;
+         cpS = cpE;
+         if (*cpS == '\n')
+             cpS++;
+     }
+     return rc;
+ }
+ 
+ char *lmtp_error(lmtp_rc_t rc)
+ {
+     /*  get an error message matching the given lmtp_rc_t code usually
+      *  returned by a previously called function
+      */
+     char *str;
+                                       str = "LMTP: errorcode has no description";
+     if      (rc == LMTP_OK          ) str = "LMTP: no error";
+     else if (rc == LMTP_EOF         ) str = "LMTP: eof";
+     else if (rc == LMTP_ERR_SYSTEM  ) str = "LMTP: see errno";
+     else if (rc == LMTP_ERR_MEM     ) str = "LMTP: dynamic memory allocation failed";
+     else if (rc == LMTP_ERR_OVERFLOW) str = "LMTP: static allocated memory exhausted";
+     else if (rc == LMTP_ERR_ARG     ) str = "LMTP: invalid arg was passed to function";
+     else if (rc == LMTP_ERR_UNKNOWN ) str = "LMTP: guru meditation";
+     return str;
+ }
+ 
+ lmtp_rc_t lmtp_register(lmtp_t *lmtp, char *verb, lmtp_cb_t cb, void *ctx, lmtp_cb_t *oldcb, void **oldctx)
+ {
+     /*  For _lmtp_ structure, register a _verb_ and associate a callback
+      *  function _cb_ to it. A context can be specified which will be passed
+      *  to the callback function for every call. Consider the context being
+      *  user data. The library itself does not care about the context except
+      *  passing it along.  If the verb was registered previously, the
+      *  registration is replaced and if _oldcb_ and/or _oldctx_ is given, the
+      *  previous registration is returned. Calling the previously registered
+      *  callbacks from within the newly registered callback effectively allows
+      *  hooking or chaining to a previous registered callback. The _ctx_,
+      *  _oldcb_ and _oldctx_ are optional and might be passed as NULL in case
+      *  you don't care. Setting _cb_ to NULL means to check only for a
+      *  previous registration;
+      */
+     lmtp_rc_t rc = LMTP_OK;
+     int overload = 0; /* overload (replacement) detected has to return old oldcb
+                          and/or oldctx, no overload requires growth of dispatch table */
+     int i;
+ 
+     if (cb == NULL) { /* checking for existing callback only */
+         i = verbindex(lmtp, verb);
+         if (oldcb  != NULL) 
+             *oldcb  = (i == -1) ? NULL : lmtp->dispatch[i]->cb;
+         if (oldctx != NULL) 
+             *oldctx = (i == -1) ? NULL : lmtp->dispatch[i]->ctx;
+         return LMTP_OK;
+     }
+ 
+     for (i = 0; lmtp->dispatch[i] != NULL; i++) {
+         if (strcasecmp(verb, lmtp->dispatch[i]->verb) == 0) {
+             overload = 1;
+             if (oldcb  != NULL) 
+                 *oldcb  = lmtp->dispatch[i]->cb;
+             if (oldctx != NULL) 
+                 *oldctx = lmtp->dispatch[i]->ctx;
+             break;
+         }
+     }
+     if (i > LMTP_MAXVERBS-2) 
+         return LMTP_ERR_OVERFLOW;
+ 
+     if (!overload) {
+         if ((lmtp->dispatch[i] = 
+             (lmtp_dispatch_t *)malloc(sizeof(lmtp_dispatch_t))) == NULL)
+             return LMTP_ERR_MEM;
+         lmtp->dispatch[i+1] = NULL;
+         if (oldcb  != NULL) 
+             *oldcb  = NULL;
+         if (oldctx != NULL) 
+             *oldctx = NULL;
+     }
+ 
+     lmtp->dispatch[i]->verb = strdup(verb);
+     lmtp->dispatch[i]->cb   = cb;
+     lmtp->dispatch[i]->ctx  = ctx;
+ 
+     return rc;
+ }
+ 
+ lmtp_rc_t lmtp_loop(lmtp_t *lmtp)
+ {
+     /*  Print a welcome message then execute a request/ dispatch loop until
+      *  request signals no more data. Each request is checked to contain a
+      *  registered verb and if a verb is found the correspondig callback is
+      *  executed. The lmtp_create() function usually cares to register a
+      *  default callback in order to handle unregistered verbs. The psoudoverb
+      *  for default is the empty string "" and the callback for this verb can
+      *  be overridden.
+      */
+     lmtp_rc_t rc = LMTP_OK;
+     lmtp_req_t req;
+     lmtp_res_t res;
+     char *verb;
+     int i;
+ 
+     req.verb = "";
+     req.msg = NULL;
+ 
+     res.statuscode = "220";
+     res.dsncode    = NULL;
+     res.statusmsg  = "LMTP Service ready.";
+     if ((rc = lmtp_response(lmtp, &res)) != LMTP_OK) 
+         return rc;
+ 
+     while ((rc = lmtp_request(lmtp, &req)) == LMTP_OK) {
+         verb = req.verb;
+         if ((i = verbindex(lmtp, verb)) != -1) {
+             rc = lmtp->dispatch[i]->cb(lmtp, &lmtp->io, &req, lmtp->dispatch[i]->ctx);
+             if (req.msg != NULL)
+                 free(req.msg);
+             req.verb = "";
+             req.msg = NULL;
+ 
+             if (rc != LMTP_OK) 
+                 break;
+         }
+     }
+     return rc;
+ }
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_lmtp.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:09 2024
***************
*** 0 ****
--- 1,85 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  lmtp.h: Local Mail Transfer Protocol (LMTP) server library (API)
+ */
+ 
+ #ifndef __LMTP_H__
+ #define __LMTP_H__
+ 
+ #include <sys/types.h>
+ #include <sys/uio.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ 
+ struct lmtp_st;
+ typedef struct lmtp_st lmtp_t;
+ 
+ typedef struct {
+     void    *ctx;
+     int     (*select)(void *, fd_set *, fd_set *, fd_set *, struct timeval *);
+     ssize_t (*read)(void *, void *, size_t);
+     ssize_t (*write)(void *, const void *, size_t);
+ } lmtp_io_t;
+ 
+ typedef struct {
+     char *verb;
+     char *msg;
+ } lmtp_req_t;
+ 
+ typedef struct {
+     char *statuscode;
+     char *dsncode;
+     char *statusmsg;
+ } lmtp_res_t;
+ 
+ typedef enum {
+     LMTP_OK,
+     LMTP_EOF,
+     LMTP_ERR_SYSTEM,
+     LMTP_ERR_MEM,
+     LMTP_ERR_OVERFLOW,
+     LMTP_ERR_ARG,
+     LMTP_ERR_UNKNOWN
+ } lmtp_rc_t;
+ 
+ typedef struct {
+     int fd;
+ } lmtp_fd_t;
+ 
+ typedef lmtp_rc_t (*lmtp_cb_t)(lmtp_t *, lmtp_io_t *, lmtp_req_t *, void *);
+ 
+ lmtp_t     *lmtp_create  (lmtp_io_t *);
+ void        lmtp_destroy (lmtp_t *);
+ lmtp_rc_t   lmtp_readline(lmtp_t *, char *, size_t);
+ lmtp_rc_t   lmtp_readmsg (lmtp_t *, char **, size_t);
+ lmtp_rc_t   lmtp_request (lmtp_t *, lmtp_req_t *);
+ lmtp_rc_t   lmtp_response(lmtp_t *, lmtp_res_t *);
+ char       *lmtp_error   (lmtp_rc_t);
+ lmtp_rc_t   lmtp_register(lmtp_t *, char *, lmtp_cb_t, void *, lmtp_cb_t *, void **);
+ lmtp_rc_t   lmtp_loop    (lmtp_t *);
+ ssize_t     lmtp_fd_read (void *, void *, size_t);
+ ssize_t     lmtp_fd_write(void *, const void *, size_t);
+ 
+ #endif /* __LMTP_H__ */
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_main.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,2375 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  lmtp2nntp.c: LMTP to NNTP main procedure
+ */
+ 
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <errno.h>
+ #include <string.h>
+ #include <fcntl.h>
+ #include <sys/utsname.h>
+ #include <sys/time.h>
+ #include <sys/types.h>
+ #include <sys/wait.h>
+ #include <sys/stat.h>
+ #include <signal.h>
+ #include <pwd.h>
+ 
+ /* third party (included) */
+ #include "lmtp2nntp_argz.h"
+ #include "lmtp2nntp_shpat.h"
+ #include "lmtp2nntp_daemon.h"
+ 
+ /* third party (linked in) */
+ #include "str.h"
+ #include "l2.h"
+ #include "var.h"
+ 
+ /* library version check (compile-time) */
+ #define  L2_VERSION_HEX_REQ 0x001200
+ #define  L2_VERSION_STR_REQ "0.1.0"
+ #define STR_VERSION_HEX_REQ 0x009206
+ #define STR_VERSION_STR_REQ "0.9.6"
+ #ifdef L2_VERSION_HEX
+ #if L2_VERSION_HEX < L2_VERSION_HEX_REQ
+ #error "require a newer version of OSSP L2"
+ #endif
+ #endif
+ #ifdef STR_VERSION_HEX
+ #if STR_VERSION_HEX < STR_VERSION_HEX_REQ
+ #error "require a newer version of OSSP Str"
+ #endif
+ #endif
+ 
+ /* own headers */
+ #include "lmtp2nntp_global.h"
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ #include "lmtp2nntp_lmtp.h"
+ #include "lmtp2nntp_nntp.h"
+ #include "lmtp2nntp_msg.h"
+ #include "sa.h"
+ #define _LMTP2NNTP_VERSION_C_AS_HEADER_
+ #include "lmtp2nntp_version.c"
+ #undef  _LMTP2NNTP_VERSION_C_AS_HEADER_
+ 
+ #ifndef FALSE
+ #define FALSE (1 != 1)
+ #endif
+ #ifndef TRUE
+ #define TRUE (!FALSE)
+ #endif
+ 
+ #ifndef NUL
+ #define NUL '\0'
+ #endif
+ 
+ #define ERR_EXECUTION 1
+ #define ERR_DELIVERY -2
+ 
+ #define STDSTRLEN 512
+ #define MAXNEWSSERVICES 16
+ #define MAXACLS 32
+ 
+ static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ static lmtp_rc_t lmtp_cb_mail(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ static lmtp_rc_t lmtp_cb_data(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ static lmtp_rc_t lmtp_cb_noop(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ static lmtp_rc_t lmtp_cb_rset(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ static lmtp_rc_t lmtp_cb_quit(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
+ 
+ static int helo_rfc0821domain(char *msg, char **domain);
+ static int helo_rfc1035domain(char *msg, char **domain);
+ 
+ struct session {
+     int     lhlo_seen;
+     char   *lhlo_domain;
+ };
+ 
+ static void catchsignal(int sig, ...);
+ static void initsession(struct session *session);
+ static void resetsession(struct session *session);
+ int groupmatch(char *, size_t, char *);
+ 
+ struct ns {
+     char           *h;    /* host */
+     char           *p;    /* port */
+     sa_addr_t      *saa;  /* socket address abstraction */
+     sa_t           *sa;   /* socket abstraction */
+     nntp_t         *nntp;
+     nntp_rc_t       rc;
+     l2_channel_t   *l2;
+ };
+ 
+ struct acl {
+     char      *acl;
+     int        not;
+     sa_addr_t *saa;
+     size_t     prefixlen;
+ };
+ 
+ typedef struct {
+     l2_context_t    ctx;
+     char           *progname;
+     char           *option_logfile;
+     int             option_groupmode;
+     int             option_operationmode;
+     char           *option_operationmodefakestatus;
+     char           *option_operationmodefakedsn;
+     int             option_maxmessagesize;
+     char           *azHeaderValuePairs;
+     size_t          asHeaderValuePairs;
+     int             option_timeout_lmtp_accept;
+     int             option_timeout_lmtp_read;
+     int             option_timeout_lmtp_write;
+     int             option_timeout_nntp_connect;
+     int             option_timeout_nntp_read;
+     int             option_timeout_nntp_write;
+     char           *option_mailfrom;
+     char           *option_restrictheader;
+     unsigned int    option_levelmask;
+     char           *option_pidfile;
+     int             option_killflag;
+     uid_t           option_uid;
+     int             option_daemon;
+     int             option_aclc;
+     struct acl      option_acl[MAXACLS];
+     int             option_veryverbose;
+     int             option_childsmax;
+     int             active_childs;
+     l2_env_t       *l2_env;
+     l2_channel_t   *l2;
+     sa_addr_t      *saaAltio;
+     sa_t           *saAltio;
+     char           *cpBindh;
+     char           *cpBindp;
+     sa_addr_t      *saaBind;
+     sa_t           *saBind;
+     sa_addr_t      *saaIO;
+     sa_t           *saIO;
+     int             fdIOi;
+     int             fdIOo;
+     int             nsc;
+     struct ns       ns[MAXNEWSSERVICES];
+     char           *azGroupargs;
+     size_t          asGroupargs;
+     struct          session session;
+     msg_t          *msg;
+     struct utsname  uname;
+ } lmtp2nntp_t;
+ 
+ static var_config_t ctx_lookup_cfg = {
+     '$',          /* varinit       */ 
+     '{',          /* startdelim    */ 
+     '}',          /* enddelim      */ 
+     '[',          /* startindex    */ 
+     ']',          /* endindex      */ 
+     '#',          /* current_index */ 
+     '\\',         /* escape        */ 
+     "a-zA-Z0-9_.-" /* namechars     */ 
+ };
+ 
+ static var_rc_t ctx_lookup(
+     void *_ctx, 
+     const char *var_ptr, size_t var_len, int var_idx,
+     const char **val_ptr, size_t *val_len, size_t *val_size)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     const char *name;
+     size_t len;
+     size_t n;
+     char *cp;
+     var_rc_t rc;
+ 
+     log2(ctx, DEBUG, "lookup variable \"%s\" (%d)", 
+          var_ptr /* FIXME: NUL-termination? */, var_len);
+     rc = VAR_ERR_UNDEFINED_VARIABLE;
+     if (strncasecmp(var_ptr, "lmtp2nntp.version", var_len) == 0) {
+     }
+     else if (strncasecmp(var_ptr, "os.name", var_len) == 0) {
+     }
+     else if (strncasecmp(var_ptr, "os.version", var_len) == 0) {
+     }
+     else if (var_len > 8 && strncasecmp(var_ptr, "msg.hdr.", 8) == 0) {
+         name = var_ptr + 8;
+         len = strlen(name);
+         if (ctx == NULL)
+             return VAR_ERR_UNDEFINED_VARIABLE;
+         if (ctx->msg == NULL)
+             return VAR_ERR_UNDEFINED_VARIABLE;
+         if (ctx->msg->azHeaders == NULL)
+             return VAR_ERR_UNDEFINED_VARIABLE;
+         cp = NULL;
+         while ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) != NULL) {
+             char *cpVar, *cpVal;
+             int nVar, nVal;
+             cpVar = cp;
+             nVar = strlen(cpVar);
+             if ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) == NULL)
+                 break;
+             cpVal = cp;
+             nVal = strlen(cpVal);
+             if (len == (nVar-1) && strncasecmp(cpVar, name, len) == 0 && cpVar[len] == ':') {
+                 *val_ptr = cpVal;
+                 *val_len = nVal;
+                 *val_size = 0;
+                 rc = VAR_OK;
+             }
+         }
+     }
+     if (rc == VAR_OK)
+         log4(ctx, DEBUG, "lookup variable \"%s\" (%d) ok: result is \"%s\" (%d)", 
+              var_ptr /* FIXME: NUL-termination? */, var_len, *val_ptr, *val_len);
+     else
+         log3(ctx, DEBUG, "lookup variable \"%s\" (%d) failed: %s", 
+              var_ptr /* FIXME: NUL-termination? */, var_len, var_strerror(rc));
+     return rc;
+ }
+ 
+ 
+ static void lmtp_gfs_ns(struct ns *);
+ static void lmtp_gfs_lhlo(lmtp2nntp_t *);
+ static void lmtp_gfs_rset(lmtp2nntp_t *);
+ static void lmtp_gfs_quit(lmtp2nntp_t *);
+ 
+ enum {
+     GROUPMODE_ARG,
+     GROUPMODE_ENVELOPE,
+     GROUPMODE_HEADER
+ };
+ 
+ enum {
+     OPERATIONMODE_FAKE,
+     OPERATIONMODE_POST,
+     OPERATIONMODE_FEED
+ };
+ 
+ /*
+  * print usage information
+  */
+ static void usage(char *command)
+ {
+     /*  use
+      *  perl <lmtp2nntp.c -e 'while (<>) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; s/.<(.*?)>/$1/g ; print "\"$_ \"\n" };}'
+      *  to pull the USAGE string out of this source
+      */
+     fprintf(stderr, 
+             "USAGE: %s "
+             "[-C childsmax] "
+             "[-D] "
+             "[-K] "
+             "[-P pidfile] "
+             "[-V] "
+             "[-a addr/mask[,addr/mask[,...]] "
+             "[-b addr[:port]|-|path[:perms]] "
+             "[-c addr[:port]] "
+             "[-d addr[:port][,addr[:port], ...]] "
+             "[-g groupmode] "
+             "[-h header:value] "
+             "[-l level[:logfile]] "
+             "[-m mailfrom] "
+             "[-n nodename] "
+             "[-o operationmode] "
+             "[-r restrictheader] "
+             "[-s size] "
+             "[-t name=sec[,name=sec[,...]] "
+             "[-u uid] "
+             "[-v] "
+             "newsgroup [newsgroup ...] "
+             "\n",
+             command);
+     return;
+ }
+ 
+ static ssize_t hook_lmtp_read(void *_ctx, void *buf, size_t nbytes)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     ssize_t rc;
+     size_t n;
+     sa_rc_t rv;
+ 
+     if (ctx->saIO != NULL) {
+         if ((rv = sa_read(ctx->saIO, buf, nbytes, &n)) != SA_OK)
+             rc = -1;
+         else
+             rc = (ssize_t)n;
+     }
+     else
+         rc = read(ctx->fdIOi, buf, nbytes);
+     if (rc == -1)
+         log0(ctx, TRACE, "LMTP read error: %m");
+     else
+         log3(ctx, TRACE, "LMTP %5d << \"%{text}D\"", rc, buf, rc);
+     log1(ctx, DEBUG, "hook_lmtp_read() return, rc=%d", rc);
+     return rc;
+ }
+ 
+ static ssize_t hook_lmtp_write(void *_ctx, const void *buf, size_t nbytes)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     ssize_t rc;
+     size_t n;
+     sa_rc_t rv;
+ 
+     log3(ctx, TRACE, "LMTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
+     if (ctx->saIO != NULL) {
+         if ((rv = sa_write(ctx->saIO, buf, nbytes, &n)) != SA_OK)
+             rc = -1;
+         else
+             rc = (ssize_t)n;
+     }
+     else
+         rc = write(ctx->fdIOo, buf, nbytes);
+     if (rc == -1)
+         log0(ctx, TRACE, "LMTP write error: %m");
+     return rc;
+ }
+ 
+ static ssize_t hook_nntp_read(void *_ctx, void *buf, size_t nbytes)
+ {
+     struct ns *ctx = (struct ns *)_ctx;
+     ssize_t rc;
+     size_t n;
+     sa_rc_t rv;
+ 
+     if ((rv = sa_read(ctx->sa, buf, nbytes, &n)) != SA_OK)
+         rc = -1;
+     else
+         rc = (ssize_t)n;
+     if (rc == -1)
+         log0(ctx, TRACE, "NNTP read error: %m");
+     else
+         log3(ctx, TRACE, "NNTP %5d << \"%{text}D\"", rc, buf, rc);
+     return rc;
+ }
+ 
+ static ssize_t hook_nntp_write(void *_ctx, const void *buf, size_t nbytes)
+ {
+     struct ns *ctx = (struct ns *)_ctx;
+     ssize_t rc;
+     size_t n;
+     sa_rc_t rv;
+ 
+     log3(ctx, TRACE, "NNTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
+     if ((rv = sa_write(ctx->sa, buf, nbytes, &n)) != SA_OK)
+         rc = -1;
+     else
+         rc = (ssize_t)n;
+     if (rc == -1)
+         log0(ctx, TRACE, "NNTP write error: %m");
+     return rc;
+ }
+ 
+ static l2_result_t 
+ formatter_prefix(l2_context_t *_ctx, const char id, const char *param,
+           char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx->vp;
+             
+     if ((ctx->msg != NULL) && (ctx->msg->cpFid != NULL)) {
+         sprintf(bufptr, "%s: ", ctx->msg->cpFid);
+         *buflen = strlen(bufptr);
+     }
+     else
+         *buflen = 0;
+     return L2_OK;
+ }
+ 
+ static l2_result_t 
+ formatter_errno(l2_context_t *_ctx, const char id, const char *param,
+           char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
+ {
+     sprintf(bufptr, "(%d) %s", errno, strerror(errno));
+     *buflen = strlen(bufptr);
+     return L2_OK;
+ }
+ 
+ static void catchsignal(int sig, ...)
+ {
+     va_list ap;
+     static lmtp2nntp_t *ctx = NULL;
+     pid_t pid;
+ 
+     if(sig == 0) {
+         va_start(ap, sig);
+         if ((ctx = va_arg(ap, lmtp2nntp_t *)) == NULL)
+             exit(ERR_EXECUTION);
+         log0(ctx, TRACE, "catching and logging signals now");
+         va_end(ap);
+         return;
+     }
+     if (ctx != NULL) {
+         switch (sig) {
+             case SIGCHLD:
+                 log1(ctx, NOTICE, "caught signal %d - wait for child", sig);
+                 pid = wait(NULL);
+                 ctx->active_childs--;
+                 log2(ctx, NOTICE, "caught signal %d - child [%ld] terminated", sig, (long)pid);
+                 return;
+             case SIGUSR1:
+                 log1(ctx, NOTICE, "caught signal %d - flush logging stream", sig);
+                 l2_channel_flush(ctx->l2);
+                 return;
+             case SIGHUP:
+             case SIGINT:
+             case SIGQUIT:
+                 log1(ctx, NOTICE, "caught signal %d - exit - no more logging", sig);
+                 break;
+             default:
+                 log1(ctx, PANIC, "CAUGHT SIGNAL %d - EXIT - NO MORE LOGGING", sig);
+         }
+         l2_channel_destroy(ctx->l2);
+         l2_env_destroy(ctx->l2_env);
+     }
+     exit(ERR_EXECUTION);
+ }
+ 
+ 
+ int main(int argc, char **argv)
+ {
+     int           rc;
+     lmtp_t       *lmtp = NULL;
+     lmtp_io_t     lmtp_io;
+     lmtp2nntp_t  *ctx = NULL;
+     int           bOk;
+     int           i;             /* general purpose scratch int, index ... */
+     char         *cp;            /* general purpose character pointer */
+     char         *azHosts;
+     size_t        asHosts;
+     char         *azTimeout;
+     size_t        asTimeout;
+     char         *azACL;
+     size_t        asACL;
+     char         *cpHost;
+     char         *cpPort;
+     pid_t         pid;
+     FILE         *fd;
+     char         *cpName;
+     char         *cpValue;
+     int           nValue;
+     char         *cpAddr;
+     char         *cpPrefixLen;
+     struct passwd *sPasswd;
+     char         *cpHeadername;
+     char         *cpHeadervalue;
+ 
+     /* drop effective uid/gid priviledges */
+     seteuid(getuid());
+     setegid(getgid());
+ 
+     /* library version check (run-time) */
+     if (l2_version.v_hex < L2_VERSION_HEX_REQ) {
+         fprintf(stderr, "require OSSP L2 >= %s, found %s\n", L2_VERSION_STR_REQ, L2_VERSION_STR);
+         CU(ERR_EXECUTION);
+     }
+     if (str_version.v_hex < STR_VERSION_HEX_REQ) {
+         fprintf(stderr, "require OSSP Str >= %s, found %s\n", STR_VERSION_STR_REQ, STR_VERSION_STR);
+         CU(ERR_EXECUTION);
+     }
+ 
+     /* create application context */
+     if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL)
+         CU(ERR_EXECUTION);
+     ctx->ctx.vp = ctx;
+     ctx->progname = strdup(argv[0]);
+     ctx->option_logfile = NULL;
+     ctx->option_groupmode = GROUPMODE_ARG;
+     ctx->option_operationmode = OPERATIONMODE_FAKE;
+     ctx->option_operationmodefakestatus = "553";   /* Requested action not taken: mailbox name not allowed */
+     ctx->option_operationmodefakedsn    = "5.7.1"; /* Delivery not authorized, message refused */
+     ctx->option_maxmessagesize = 8 * 1024 * 1024;
+     ctx->azHeaderValuePairs = NULL;
+     ctx->asHeaderValuePairs = 0;
+     ctx->option_timeout_lmtp_accept = 0;
+     ctx->option_timeout_lmtp_read = 10;
+     ctx->option_timeout_lmtp_write = 10;
+     ctx->option_timeout_nntp_connect = 360;
+     ctx->option_timeout_nntp_read = 60;
+     ctx->option_timeout_nntp_write = 60;
+     ctx->option_mailfrom = NULL;
+     ctx->option_restrictheader = NULL;
+     ctx->option_levelmask = L2_LEVEL_NONE;
+     ctx->option_pidfile = NULL;
+     ctx->option_killflag = FALSE;
+     ctx->option_uid = getuid();
+     ctx->option_daemon = FALSE;
+     ctx->option_veryverbose = FALSE;
+     ctx->option_childsmax = 10;
+     ctx->active_childs = 0;
+     ctx->l2_env = NULL;
+     ctx->l2 = NULL;
+     ctx->saaAltio = NULL;
+     ctx->saAltio = NULL;
+     ctx->cpBindh = NULL;
+     ctx->cpBindp = NULL;
+     ctx->saaBind = NULL;
+     ctx->saBind = NULL;
+     ctx->nsc = 0;
+     for (i=0; i < MAXNEWSSERVICES; i++) {
+         ctx->ns[i].h = NULL;
+         ctx->ns[i].p = NULL;
+         ctx->ns[i].saa = NULL;
+         ctx->ns[i].sa = NULL;
+         ctx->ns[i].nntp = NULL;
+         ctx->ns[i].rc = LMTP_ERR_UNKNOWN;
+         ctx->ns[i].l2 = NULL;
+     }
+     ctx->option_aclc = 0;
+     for (i = 0; i < MAXACLS; i++) {
+         ctx->option_acl[i].acl = NULL;
+         ctx->option_acl[i].not = FALSE;
+         ctx->option_acl[i].saa = NULL;
+         ctx->option_acl[i].prefixlen = 0;
+     }
+     ctx->azGroupargs = NULL;
+     ctx->asGroupargs = 0;
+     initsession(&ctx->session);
+     ctx->msg = NULL;
+     if (uname(&ctx->uname) == -1) {
+         fprintf(stderr, "%s:Error: uname failed \"%s\"\n", ctx->progname, strerror(errno));
+         CU(ERR_EXECUTION);
+     }
+ 
+     /*POD B<lmtp2nntp> */
+ 
+     /*  use
+      *  perl <lmtp2nntp.c -e 'while (<>) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; print "$_\n" };}'
+      *  to pull the POD SYNOPSIS header directly out of this source
+      */
+ 
+     /* read in the arguments */
+     while ((i = getopt(argc, argv, "C:DKP:Va:b:c:d:g:h:l:m:n:o:r:s:t:u:v")) != -1) {
+         switch (i) {
+             case 'C': /*POD [B<-C> I<childsmax>] */
+                 ctx->option_childsmax = atoi(optarg);
+                 if (ctx->option_childsmax <= 0) {
+                     fprintf(stderr, "%s:Error: Invalid number (%d) to option -C\n", ctx->progname, ctx->option_childsmax);
+                     CU(ERR_EXECUTION);
+                 }
+                 break;
+             case 'D': /*POD [B<-D>] */
+                 ctx->option_daemon = TRUE;
+                 break;
+             case 'K': /*POD [B<-K>] */
+                 ctx->option_killflag = TRUE;
+                 break;
+             case 'P': /*POD [B<-P> I<pidfile>] */
+                 ctx->option_pidfile = strdup(optarg);
+                 break;
+             case 'V': /*POD [B<-V>] */
+                 ctx->option_veryverbose = TRUE;
+                 break;
+             case 'a': /*POD [B<-a> I<addr>/I<mask>[,I<addr>/I<mask>[,...]] */
+                 if (argz_create_sep(optarg, ',', &azACL, &asACL) != 0)
+                     CU(ERR_EXECUTION);
+                 cp = NULL;
+                 while ((cp = argz_next(azACL, asACL, cp)) != NULL) {
+                     if (ctx->option_aclc >= MAXACLS) {
+                         fprintf(stderr, "%s:Error: Too many ACL (%d) using option -a\n", ctx->progname, ctx->option_aclc);
+                         CU(ERR_EXECUTION);
+                     }
+                     ctx->option_acl[ctx->option_aclc].acl = strdup(cp);
+                     if (cp[0] == '!') {
+                         ctx->option_acl[ctx->option_aclc].not = TRUE;
+                         cpAddr = strdup(cp+1);
+                     }
+                     else {
+                         cpAddr = strdup(cp);
+                     }
+                     if ((cpPrefixLen = strrchr(cpAddr, '/')) != NULL)
+                         *cpPrefixLen++ = NUL;
+                     else
+                         cpPrefixLen = "-1";
+                     ctx->option_acl[ctx->option_aclc].prefixlen = atoi(cpPrefixLen);
+                     if ((rc = sa_addr_create(&ctx->option_acl[ctx->option_aclc].saa)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Creating address failed for -a option (%d)\n", 
+                                 ctx->progname, rc);
+                     }
+                     if ((rc = sa_addr_u2a(ctx->option_acl[ctx->option_aclc].saa, "inet://%s:0", cpAddr)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:0\" (%d)\n", 
+                                 ctx->progname, cpAddr, rc);
+                         CU(ERR_EXECUTION);
+                     }
+                     ctx->option_aclc++;
+                     free(cpAddr);
+                 }
+                 free(azACL);
+                 break;
+             case 'b': /*POD [B<-b> I<addr>[I<:port>]|C<->|I<path>[:perms]] */
+                 if (strcmp(optarg, "-") != 0) {
+                     if ((rc = sa_create(&ctx->saAltio)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Creating TCP socket failed for \"%s\": %s\n", 
+                                 ctx->progname, optarg, strerror(errno));
+                         CU(ERR_EXECUTION);
+                     }
+                     if ((rc = sa_addr_create(&ctx->saaAltio)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Creating address failed for -b option (%d)\n", 
+                                 ctx->progname, rc);
+                     }
+                     if (optarg[0] == '/') {
+                         char *cpPath;
+                         char *cpPerm;
+                         int nPerm;
+                         int n;
+ 
+                         cpPath = strdup(optarg);
+                         cpPerm = NULL;
+                         nPerm  = -1;
+                         if ((cpPerm = strrchr(cpPath, ':')) != NULL) {
+                             *cpPerm++ = '\0';
+                             nPerm = 0;
+                             for (i = 0; i < 4 && cpPerm[i] != '\0'; i++) {
+                                 if (!isdigit((int)cpPerm[i])) {
+                                     nPerm = -1;
+                                     break;
+                                 }
+                                 n = cpPerm[i] - '0';
+                                 if (n > 7) {
+                                     nPerm = -1;
+                                     break;
+                                 }
+                                 nPerm = ((nPerm << 3) | n);
+                             }
+                             if (nPerm == -1 || cpPerm[i] != '\0') {
+                                 fprintf(stderr, "%s:Error: Invalid permissions \"%s\"\n", ctx->progname, cpPerm);
+                                 CU(ERR_EXECUTION);
+                             }
+                         }
+                         if ((rc = sa_addr_u2a(ctx->saaAltio, "unix:%s", cpPath)) != SA_OK) {
+                             fprintf(stderr, "%s:Error: Parsing alternate IO guessing UNIX domain socket failed for \"%s\" (%d)\n", 
+                                     ctx->progname, cpPath, rc);
+                             CU(ERR_EXECUTION);
+                         }
+                         if ((rc = sa_bind(ctx->saAltio, ctx->saaAltio)) != SA_OK) {
+                             fprintf(stderr, "%s:Error: Bind failed for \"%s\": %s\n", 
+                                     ctx->progname, cpPath, strerror(errno));
+                             CU(ERR_EXECUTION);
+                         }
+                         if (nPerm != -1) {
+                             if (chmod(cpPath, nPerm) == -1) {
+                                 fprintf(stderr, "%s:Error: chmod failed for \"%s\": %s\n", ctx->progname, cpPath, strerror(errno));
+                                 CU(ERR_EXECUTION);
+                             }
+                         }
+                         if (getuid() == 0 && getuid() != ctx->option_uid) {
+                             if (chown(cpPath, ctx->option_uid, -1) == -1) {
+                                 fprintf(stderr, "%s:Error: chown failed for \"%s\": %s\n", ctx->progname, cpPath, strerror(errno));
+                                 CU(ERR_EXECUTION);
+                             }
+                         }
+                         free(cpPath);
+                     }
+                     else {
+                         if ((rc = sa_addr_u2a(ctx->saaAltio, "inet://%s", optarg)) != SA_OK) {
+                             fprintf(stderr, "%s:Error: Parsing alternate IO guessing INET socket failed for \"%s\" (%d)\n", 
+                                     ctx->progname, optarg, rc);
+                             CU(ERR_EXECUTION);
+                         }
+                         if ((rc = sa_bind(ctx->saAltio, ctx->saaAltio)) != SA_OK) {
+                             fprintf(stderr, "%s:Error: Bind failed for \"%s\": %s\n", 
+                                     ctx->progname, optarg, strerror(errno));
+                             CU(ERR_EXECUTION);
+                         }
+                     }
+                     if ((rc = sa_listen(ctx->saAltio, -1)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Listen to failed for \"%s\": %s\n", 
+                                 ctx->progname, optarg, strerror(errno));
+                         CU(ERR_EXECUTION);
+                     }
+                 }
+                 break;
+             case 'c': /*POD [B<-c> I<addr>[I<:port>]] */
+                 ctx->cpBindh = strdup(optarg);
+                 if ((ctx->cpBindp = strrchr(ctx->cpBindh, ':')) != NULL) {
+                     *ctx->cpBindp++ = NUL;
+                     ctx->cpBindp = strdup(ctx->cpBindp);
+                 }
+                 else 
+                     ctx->cpBindp = strdup("0");
+                 if ((rc = sa_addr_create(&ctx->saaBind)) != SA_OK) {
+                     fprintf(stderr, "%s:Error: Creating address failed for -c option (%d)\n", 
+                             ctx->progname, rc);
+                 }
+                 if ((rc = sa_addr_u2a(ctx->saaBind, "inet://%s:%s", ctx->cpBindh, ctx->cpBindp)) != SA_OK) {
+                     fprintf(stderr, "%s:Error: Parsing bind address failed for \"%s:%s\" (%d)\n", 
+                             ctx->progname, ctx->cpBindh, ctx->cpBindp, rc);
+                     CU(ERR_EXECUTION);
+                 }
+                 break;
+             case 'd': /*POD [B<-d> I<addr>[I<:port>][,I<addr>[I<:port>], ...]] */
+                 if (argz_create_sep(optarg, ',', &azHosts, &asHosts) != 0)
+                     CU(ERR_EXECUTION);
+                 cp = NULL;
+                 while ((cp = argz_next(azHosts, asHosts, cp)) != NULL) {
+                     if (ctx->nsc >= MAXNEWSSERVICES) {
+                         fprintf(stderr, "%s:Error: Too many services (%d) using option -d\n", ctx->progname, ctx->nsc);
+                         CU(ERR_EXECUTION);
+                     }
+                     cpHost = strdup(cp);
+                     if ((cpPort = strrchr(cpHost, ':')) != NULL) {
+                         *cpPort++ = NUL;
+                         cpPort = strdup(cpPort);
+                     }
+                     else 
+                         cpPort = strdup("nntp");
+                     ctx->ns[ctx->nsc].h = cpHost;
+                     ctx->ns[ctx->nsc].p = cpPort;
+                     if ((rc = sa_addr_create(&ctx->ns[ctx->nsc].saa)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Creating address failed for -d option (%d)\n", 
+                             ctx->progname, rc);
+                     }
+                     if ((rc = sa_addr_u2a(ctx->ns[ctx->nsc].saa, "inet://%s:%s", 
+                                      ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:%s\" (%d)\n", 
+                                 ctx->progname, ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p, rc);
+                         CU(ERR_EXECUTION);
+                     }
+                     if ((rc = sa_create(&ctx->ns[ctx->nsc].sa)) != SA_OK) {
+                         fprintf(stderr, "%s:Error: Creating TCP socket failed for \"%s:%s\": %s\n", 
+                                 ctx->progname, ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p, strerror(errno));
+                         CU(ERR_EXECUTION);
+                     }
+                     ctx->ns[ctx->nsc].nntp = NULL;
+                     ctx->nsc++;
+                 }
+                 free(azHosts);
+                 break;
+             case 'g': /*POD [B<-g> I<groupmode>] */
+                 if      (strcasecmp(optarg, "arg") == 0)
+                     ctx->option_groupmode = GROUPMODE_ARG;
+                 else if (strcasecmp(optarg, "envelope") == 0)
+                     ctx->option_groupmode = GROUPMODE_ENVELOPE;
+                 else if (strcasecmp(optarg, "header") == 0)
+                     ctx->option_groupmode = GROUPMODE_HEADER;
+                 else {
+                     fprintf(stderr, "%s:Error: Invalid mode \"%s\" to option -g\n", ctx->progname, optarg);
+                     CU(ERR_EXECUTION);
+                 }
+                 break;
+             case 'h': /*POD [B<-h> I<header>:<value>] */
+                 cpHeadername = strdup(optarg);
+                 if ((cp = strchr(cpHeadername, ':')) == NULL) {
+                     free(cpHeadername);
+                     fprintf(stderr, "%s:Error: header \"%s\" for -h option not terminated with colon\n", 
+                             ctx->progname, cpHeadername);
+                     CU(ERR_EXECUTION);
+                 }
+                 cp++;
+                 if (*cp == NUL) {
+                     free(cpHeadername);
+                     fprintf(stderr, "%s:Error: header \"%s\" for -h option has no value\n", 
+                             ctx->progname, cpHeadername);
+                     CU(ERR_EXECUTION);
+                 }
+                 cpHeadervalue = strdup(cp);
+                 *cp = NUL;
+                 argz_add(&ctx->azHeaderValuePairs, &ctx->asHeaderValuePairs, cpHeadername);
+                 argz_add(&ctx->azHeaderValuePairs, &ctx->asHeaderValuePairs, cpHeadervalue);
+                 free(cpHeadervalue);
+                 free(cpHeadername);
+                 break;
+             case 'l': /*POD [B<-l> I<level>[:I<logfile>]] */
+                 if ((cp = strrchr(optarg, ':')) != NULL) {
+                     *cp++ = NUL;
+                     if (*cp == NUL) {
+                         fprintf(stderr, "%s:Error: empty logfile to option -l\n", ctx->progname);
+                         CU(ERR_EXECUTION);
+                     }
+                     else
+                         ctx->option_logfile = strdup(cp);
+                 }
+                 else
+                     ctx->option_logfile = strdup("logfile");
+ 
+                 if (l2_util_s2l(optarg, strlen(optarg), ',', &ctx->option_levelmask) != L2_OK) {
+                     fprintf(stderr, "%s:Error: invalid level \"%s\" to option -l\n", ctx->progname, optarg);
+                     CU(ERR_EXECUTION);
+                 }
+                 ctx->option_levelmask = L2_LEVEL_UPTO(ctx->option_levelmask);
+                 break;
+             case 'm': /*POD [B<-m> I<mailfrom>] */
+                 ctx->option_mailfrom = strdup(optarg);
+                 /* protect ourselfs from the substitution of backreferences.
+                  * Missing varargs would cause segfaults.  Rewrite capturing
+                  * brackets to clustering syntax. Use poor man's s///g
+                  * simulator as current str library doesn't support global
+                  * substitution */
+                 while (str_parse(ctx->option_mailfrom, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) {
+                     free(ctx->option_mailfrom);
+                     ctx->option_mailfrom = cp;
+                 }
+                 if (str_parse("<>", ctx->option_mailfrom) == -1) {
+                     fprintf(stderr, "%s:Error: illegal regex \"%s\" to option -m.\n", ctx->progname, ctx->option_mailfrom);
+                     CU(ERR_EXECUTION);
+                 }
+                 break;
+             case 'n': /*POD [B<-n> I<nodename>] */
+                 if (strlen(optarg) > sizeof(ctx->uname.nodename)-1) {
+                         fprintf(stderr, "%s:Error: nodename \"%s\" to long to option -n.\n", ctx->progname, optarg);
+                         CU(ERR_EXECUTION);
+                 }
+                 strcpy(ctx->uname.nodename, optarg);
+                 break;
+             case 'o': /*POD [B<-o> I<operationmode>] */
+                 if      (strcasecmp(optarg, "post") == 0)
+                     ctx->option_operationmode = OPERATIONMODE_POST;
+                 else if (strcasecmp(optarg, "feed") == 0)
+                     ctx->option_operationmode = OPERATIONMODE_FEED;
+                 else {
+                     if (strlen(optarg) != 9) {
+                         fprintf(stderr, "%s:Error: Invalid format or length \"%s\" to option -o\n", ctx->progname, optarg);
+                         CU(ERR_EXECUTION);
+                     }
+ 
+                     if (optarg[3] != '/') {
+                         fprintf(stderr, "%s:Error: Invalid format or missing slash \"%s\" to option -o\n", ctx->progname, optarg);
+                         CU(ERR_EXECUTION);
+                     }
+ 
+                     optarg[3] = NUL;
+                     ctx->option_operationmodefakestatus = &optarg[0];
+                     ctx->option_operationmodefakedsn    = &optarg[4];
+ 
+                     if (   strlen(ctx->option_operationmodefakestatus) != 3
+                         || !isdigit((int)ctx->option_operationmodefakestatus[0])
+                         || !isdigit((int)ctx->option_operationmodefakestatus[1])
+                         || !isdigit((int)ctx->option_operationmodefakestatus[2])) {
+                             fprintf(stderr, "%s:Error: Invalid status in format \"%s\" to option -o\n", ctx->progname, optarg);
+                             CU(ERR_EXECUTION);
+                         }
+ 
+                     if (   (strlen(ctx->option_operationmodefakedsn) != 5)
+                         || !isdigit((int)ctx->option_operationmodefakedsn[0])
+                         || (ctx->option_operationmodefakedsn[1] != '.')
+                         || !isdigit((int)ctx->option_operationmodefakedsn[2])
+                         || (ctx->option_operationmodefakedsn[3] != '.')
+                         || !isdigit((int)ctx->option_operationmodefakedsn[4])
+                         || (ctx->option_operationmodefakedsn[0] != ctx->option_operationmodefakestatus[0])) {
+                             fprintf(stderr, "%s:Error: Invalid dsn in format \"%s\" to option -o\n", ctx->progname, optarg);
+                             CU(ERR_EXECUTION);
+                         }
+                     }
+                 break;
+             case 'r': /*POD [B<-r> I<restrictheader>] */
+                 ctx->option_restrictheader = strdup(optarg);
+                 /* protect ourselfs from the substitution of backreferences.
+                  * Missing varargs would cause segfaults.  Rewrite capturing
+                  * brackets to clustering syntax. Use poor man's s///g
+                  * simulator as current str library doesn't support global
+                  * substitution */
+                 while (str_parse(ctx->option_restrictheader, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) {
+                     free(ctx->option_restrictheader);
+                     ctx->option_restrictheader = cp;
+                 }
+                 if (str_parse("<>", ctx->option_restrictheader) == -1) {
+                     fprintf(stderr, "%s:Error: illegal regex \"%s\" to option -r.\n", ctx->progname, ctx->option_restrictheader);
+                     CU(ERR_EXECUTION);
+                 }
+                 break;
+             case 's': /*POD [B<-s> I<size>] */
+                 ctx->option_maxmessagesize = atoi(optarg);
+                 if(ctx->option_maxmessagesize < 64) {
+                     fprintf(stderr, "%s:Error: maximum message size is unacceptable small.\n", ctx->progname);
+                     CU(ERR_EXECUTION);
+                 }
+                 break;
+             case 't': /*POD [B<-t> I<name>=I<sec>[,I<name>=I<sec>[,...]] */
+                 if (argz_create_sep(optarg, ',', &azTimeout, &asTimeout) != 0)
+                     CU(ERR_EXECUTION);
+                 cp = NULL;
+                 while ((cp = argz_next(azTimeout, asTimeout, cp)) != NULL) {
+                     cpName = strdup(cp);
+                     if ((cpValue = strrchr(cpName, '=')) == NULL) {
+                         fprintf(stderr, "%s:Error: comma-seperated argument %s to option -t have to be name=value.\n", ctx->progname, cp);
+                         CU(ERR_EXECUTION);
+                     }
+                     *cpValue++ = NUL;
+                     nValue = atoi(cpValue);
+                     if (nValue < 0) {
+                         fprintf(stderr, "%s:Error: timeout %s=%d to option -t must be a positive integer.\n", ctx->progname, cpName, nValue);
+                         CU(ERR_EXECUTION);
+                     }
+                     if (strcmp(cpName, "lmtp") == 0) {
+                         ctx->option_timeout_lmtp_accept = nValue;
+                         ctx->option_timeout_lmtp_read = nValue;
+                         ctx->option_timeout_lmtp_write = nValue;
+                     }
+                     else if (strcmp(cpName, "lmtp:accept") == 0)
+                         ctx->option_timeout_lmtp_accept = nValue;
+                     else if (strcmp(cpName, "lmtp:read") == 0)
+                         ctx->option_timeout_lmtp_read = nValue;
+                     else if (strcmp(cpName, "lmtp:write") == 0)
+                         ctx->option_timeout_lmtp_write = nValue;
+                     else if (strcmp(cpName, "nntp") == 0) {
+                         ctx->option_timeout_nntp_connect = nValue;
+                         ctx->option_timeout_nntp_read = nValue;
+                         ctx->option_timeout_nntp_write = nValue;
+                     }
+                     else if (strcmp(cpName, "nntp:connect") == 0)
+                         ctx->option_timeout_nntp_connect = nValue;
+                     else if (strcmp(cpName, "nntp:read") == 0)
+                         ctx->option_timeout_nntp_read = nValue;
+                     else if (strcmp(cpName, "nntp:write") == 0)
+                         ctx->option_timeout_nntp_write = nValue;
+                     else {
+                         fprintf(stderr, "%s:Error: unknown timeout %s to option -t.\n", ctx->progname, cpName);
+                         CU(ERR_EXECUTION);
+                     }
+                     free(cpName);
+                 }
+                 free(azTimeout);
+                 break;
+             case 'u': /*POD [B<-u> I<uid>] */
+                 if (isdigit((int)optarg[0])) {
+                     if ((sPasswd = getpwuid((uid_t)atoi(optarg))) == NULL) {
+                         fprintf(stderr, "%s:Error: uid \"%s\" not found for -u option.\n", ctx->progname, optarg);
+                         CU(ERR_EXECUTION);
+                     }
+                 }
+                 else {
+                     if ((sPasswd = getpwnam(optarg)) == NULL) {
+                         fprintf(stderr, "%s:Error: loginname \"%s\" not found for -u option.\n", ctx->progname, optarg);
+                         CU(ERR_EXECUTION);
+                     }
+                 }
+                 ctx->option_uid = sPasswd->pw_uid;
+                 break;
+             case 'v': /*POD [B<-v>] (version)*/
+                 fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu);
+                 CU(0);
+                 break;
+             case '?':
+             default:
+                 usage(ctx->progname);
+                 CU(ERR_EXECUTION);
+         }
+     }
+     /*POD I<newsgroup> [I<newsgroup> ...] */
+     for (i = optind; i < argc; i++) {
+         argz_add(&ctx->azGroupargs, &ctx->asGroupargs, argv[i]);
+     }
+ 
+     /* if no positive ACL exists (option -a) add a wildcard match-all for IPv4 and IPv6 */
+     bOk = FALSE;
+     for (i = 0; i < ctx->option_aclc; i++) {
+         if (!ctx->option_acl[i].not) {
+             bOk = TRUE;
+             break;
+         }
+     }
+     if (!bOk) {
+         if (ctx->option_aclc >= MAXACLS) {
+             fprintf(stderr, "%s:Error: Too many ACL (%d) using option -a (no space for additional fake IPv4 ACL)\n", ctx->progname, ctx->option_aclc);
+             CU(ERR_EXECUTION);
+         }
+         ctx->option_acl[ctx->option_aclc].acl = "0.0.0.0";
+         ctx->option_acl[ctx->option_aclc].not = FALSE;
+         ctx->option_acl[ctx->option_aclc].prefixlen = 0;
+         if ((rc = sa_addr_create(&ctx->option_acl[ctx->option_aclc].saa)) != SA_OK) {
+             fprintf(stderr, "%s:Error: Creating fake address failed for -a option (%d)\n", 
+                     ctx->progname, rc);
+         }
+         if ((rc = sa_addr_u2a(ctx->option_acl[ctx->option_aclc].saa, "inet://%s:0", ctx->option_acl[ctx->option_aclc].acl)) != SA_OK) {
+             fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:0\" (%s)\n", 
+                     ctx->progname, ctx->option_acl[ctx->option_aclc].acl,
+                     sa_error(rc));
+             CU(ERR_EXECUTION);
+         }
+         ctx->option_aclc++;
+     }
+     if (!bOk) {
+         if (ctx->option_aclc >= MAXACLS) {
+             fprintf(stderr, "%s:Error: Too many ACL (%d) using option -a (no space for additional fake IPv6 ACL)\n", ctx->progname, ctx->option_aclc);
+             CU(ERR_EXECUTION);
+         }
+         ctx->option_acl[ctx->option_aclc].acl = "[::]";
+         ctx->option_acl[ctx->option_aclc].not = FALSE;
+         ctx->option_acl[ctx->option_aclc].prefixlen = 0;
+         if ((rc = sa_addr_create(&ctx->option_acl[ctx->option_aclc].saa)) != SA_OK) {
+             fprintf(stderr, "%s:Error: Creating fake address failed for -a option (%d)\n", 
+                     ctx->progname, rc);
+         }
+         if ((rc = sa_addr_u2a(ctx->option_acl[ctx->option_aclc].saa, "inet://%s:0", ctx->option_acl[ctx->option_aclc].acl)) != SA_OK) {
+             fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:0\" (%s)\n", 
+                     ctx->progname, ctx->option_acl[ctx->option_aclc].acl,
+                     sa_error(rc));
+             CU(ERR_EXECUTION);
+         }
+         ctx->option_aclc++;
+     }
+ 
+     if (getuid() != ctx->option_uid) {
+         if (setuid(ctx->option_uid) == -1) {
+             fprintf(stderr, "%s:Error: Setting UID to %d failed: %s\n", 
+                     ctx->progname, ctx->option_uid, strerror(errno));
+             CU(ERR_EXECUTION);
+         }
+     }
+ 
+     /* create L2 environment */
+     if (l2_env_create(&ctx->l2_env) != L2_OK) {
+         fprintf(stderr, "%s:Error: failed to create L2 environment\n", ctx->progname);
+         CU(ERR_EXECUTION);
+     }
+ 
+     /* register custom L2 formatters */
+     if (l2_env_formatter(ctx->l2_env, 'P', formatter_prefix, &ctx->ctx) != L2_OK) {
+         fprintf(stderr, "%s:Error: logging failed to register prefix formatter\n", ctx->progname);
+         CU(ERR_EXECUTION);
+     }
+     if (l2_env_formatter(ctx->l2_env, 'D', l2_util_fmt_dump, NULL) != L2_OK) {
+         fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname);
+         CU(ERR_EXECUTION);
+     }
+     if (l2_env_formatter(ctx->l2_env, 'S', l2_util_fmt_string, NULL) != L2_OK) {
+         fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname);
+         CU(ERR_EXECUTION);
+     }
+     if (l2_env_formatter(ctx->l2_env, 'm', formatter_errno, NULL) != L2_OK) {
+         fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname);
+         CU(ERR_EXECUTION);
+     }
+ 
+     /* create channel stream */
+     if (ctx->option_levelmask != L2_LEVEL_NONE && ctx->option_logfile != NULL) {
+         if (ctx->option_veryverbose)
+             rc = l2_spec(&ctx->l2, ctx->l2_env, 
+                          "prefix(prefix=\"%%b %%d %%H:%%M:%%S <%%L> lmtp2nntp[%%P]: \",timezone=local)"
+                          "  -> buffer(size=65536)"
+                          "  -> file(path=%s,append=1,perm=%d)",
+                          ctx->option_logfile, 0644);
+         else
+             rc = l2_spec(&ctx->l2, ctx->l2_env, 
+                          "prefix(prefix=\"%%b %%d %%H:%%M:%%S <%%L> lmtp2nntp[%%P]: \",timezone=local)"
+                          "  -> file(path=%s,append=1,perm=%d)",
+                          ctx->option_logfile, 0644);
+         if (rc != L2_OK) {
+             fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname);
+             CU(ERR_EXECUTION);
+         }
+         if (l2_channel_levels(ctx->l2, ctx->option_levelmask, L2_LEVEL_NONE) != L2_OK) {
+             fprintf(stderr, "%s:Error: logging failed to set global logging level\n", ctx->progname);
+             CU(ERR_EXECUTION);
+         }
+         if (l2_channel_open(ctx->l2) != L2_OK) {
+             fprintf(stderr, "%s:Error: logging failed to open channel stream\n", ctx->progname);
+             CU(ERR_EXECUTION);
+         }
+     }
+ 
+     /* from this point on logging is up and running and fprintf(stderr, ...)
+      * should not be used in the remainder of the code
+      */
+ 
+     log1(ctx, NOTICE, "startup, version %s", lmtp2nntp_version.v_gnu);
+     if (ctx->option_veryverbose)
+         log0(ctx, NOTICE, "logging very verbose (unbuffered)");
+ 
+     if ((ctx->option_pidfile != NULL) && ctx->option_killflag) {
+         if ((fd = fopen(ctx->option_pidfile, "r")) == NULL)
+             log1(ctx, ERROR, "cannot open pidfile \"%s\" for reading %m", ctx->option_pidfile);
+         else {
+             if (fscanf(fd, "%d\n", &pid) != 1) {
+                 fclose(fd);
+                 log1(ctx, ERROR, "cannot extract pid from pidfile \"%s\"", ctx->option_pidfile);
+             }
+             else {
+                 fclose(fd);
+                 log1(ctx, TRACE, "going to kill pid[%d]", pid);
+                 if (kill(pid, SIGHUP) == -1)
+                     log1(ctx, ERROR, "killing pid[%d] failed %m", pid);
+                 if (unlink(ctx->option_pidfile) == -1)
+                     log1(ctx, ERROR, "unlinking pidfile \"%s\" failed %m", ctx->option_pidfile);
+             }
+         }
+         CU(0);
+     }
+ 
+     catchsignal(0, ctx);
+     signal(SIGCHLD, (void(*)())catchsignal);
+     signal(SIGHUP,  (void(*)())catchsignal);
+     signal(SIGINT,  (void(*)())catchsignal);
+     signal(SIGQUIT, (void(*)())catchsignal);
+     signal(SIGILL,  (void(*)())catchsignal);
+     signal(SIGBUS,  (void(*)())catchsignal);
+     signal(SIGSEGV, (void(*)())catchsignal);
+     signal(SIGSYS,  (void(*)())catchsignal);
+     signal(SIGTERM, (void(*)())catchsignal);
+     signal(SIGUSR1, (void(*)())catchsignal);
+     signal(SIGUSR2,            SIG_IGN    );
+ 
+     /* loop for LMTP protocol with support for alternate io through daemon */
+     if (ctx->saAltio == NULL) {
+         /* initialize LMTP context */
+         ctx->fdIOi = STDIN_FILENO;
+         ctx->fdIOo = STDOUT_FILENO;
+         lmtp_io.ctx    = ctx;
+         lmtp_io.read   = hook_lmtp_read;
+         lmtp_io.write  = hook_lmtp_write;
+         if ((lmtp = lmtp_create(&lmtp_io)) == NULL) {
+             log0(ctx, ERROR, "Unable to initialize LMTP library\n");
+             CU(ERR_EXECUTION);
+         }
+         /*  RFC0821, 4.5.1. MINIMUM IMPLEMENTATION
+          *  In order to make SMTP workable, the following minimum implementation
+          *  is required for all receivers: [...]
+          *  RFC0821, 4.1.2. COMMAND SYNTAX
+          *  
+          *  Verb Parameter
+          *  ----+-------------------------------
+          *  HELO <SP> <domain> <CRLF>
+          *  MAIL <SP> FROM:<reverse-path> <CRLF>
+          *  RCPT <SP> TO:<forward-path> <CRLF>
+          *  DATA <CRLF>
+          *  RSET <CRLF>
+          *  NOOP <CRLF>
+          *  QUIT <CRLF>
+          */
+         lmtp_register(lmtp, "LHLO", lmtp_cb_lhlo, ctx, NULL, NULL); 
+         lmtp_register(lmtp, "MAIL", lmtp_cb_mail, ctx, NULL, NULL);
+         lmtp_register(lmtp, "RCPT", lmtp_cb_rcpt, ctx, NULL, NULL);
+         lmtp_register(lmtp, "DATA", lmtp_cb_data, ctx, NULL, NULL);
+         lmtp_register(lmtp, "RSET", lmtp_cb_rset, ctx, NULL, NULL);
+         lmtp_register(lmtp, "NOOP", lmtp_cb_noop, ctx, NULL, NULL);
+         lmtp_register(lmtp, "QUIT", lmtp_cb_quit, ctx, NULL, NULL);
+         lmtp_loop(lmtp);
+         lmtp_gfs_quit(ctx);
+         lmtp_gfs_lhlo(ctx);
+         lmtp_destroy(lmtp);
+     } else {
+         pid = getpid();
+         if (ctx->option_daemon) {
+             daemonize();
+             log1(ctx, NOTICE, "daemonized, previous pid[%d]", pid);
+         }
+         if (ctx->option_pidfile != NULL) {
+             if ((fd = fopen(ctx->option_pidfile, "w+")) == NULL)
+                 log1(ctx, ERROR, "cannot open pidfile \"%s\" for writing %m", ctx->option_pidfile);
+             else {
+                 fprintf(fd, "%d\n", getpid());
+                 fclose(fd);
+             }
+         }
+ 
+         sa_timeout(ctx->saAltio, SA_TIMEOUT_ALL,    0, 0);
+         sa_timeout(ctx->saAltio, SA_TIMEOUT_ACCEPT, ctx->option_timeout_lmtp_accept, 0);
+         sa_timeout(ctx->saAltio, SA_TIMEOUT_READ,   ctx->option_timeout_lmtp_read,   0);
+         sa_timeout(ctx->saAltio, SA_TIMEOUT_WRITE,  ctx->option_timeout_lmtp_write,  0);
+         while (1) {
+             while (ctx->active_childs >= ctx->option_childsmax) {
+                 log1(ctx, ERROR, "maximum number of childs (%d) reached - waiting (1s)", ctx->option_childsmax);
+                 sleep(1);
+             }
+ 
+             if ((rc = sa_accept(ctx->saAltio, &ctx->saaIO, &ctx->saIO)) != SA_OK) {
+                 if (rc == SA_ERR_SYS)
+                     log3(ctx, ERROR, "accept failed: %s: (%d) %s", sa_error(rc), errno, strerror(errno));
+                 else
+                     log1(ctx, ERROR, "accept failed: %s", sa_error(rc));
+                 sleep(10);
+                 continue;
+             }
+ 
+             /* Access Control List */
+             bOk = FALSE;
+             /* check positive matches */
+             for (i = 0; i < ctx->option_aclc; i++) {
+                 char *cpA1;
+                 char *cpA2;
+                 if (ctx->option_acl[i].not)
+                     continue;
+                 sa_addr_a2u(ctx->option_acl[i].saa, &cpA1);
+                 sa_addr_a2u(ctx->saaIO, &cpA2);
+                 if (sa_addr_match(ctx->saaIO, ctx->option_acl[i].saa, ctx->option_acl[i].prefixlen) == SA_OK) {
+                     log4(ctx, TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: YES (stop comparison)", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
+                     bOk = TRUE;
+                     break;
+                 }
+                 else
+                     log4(ctx, TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: NO", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
+                 free(cpA1);
+                 free(cpA2);
+             }
+             /* check negative matches */
+             for (i = 0; i < ctx->option_aclc; i++) {
+                 char *cpA1;
+                 char *cpA2;
+                 if (!ctx->option_acl[i].not)
+                     continue;
+                 sa_addr_a2u(ctx->option_acl[i].saa, &cpA1);
+                 sa_addr_a2u(ctx->saaIO, &cpA2);
+                 if (sa_addr_match(ctx->saaIO, ctx->option_acl[i].saa, ctx->option_acl[i].prefixlen) == SA_OK) {
+                     log4(ctx, TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: YES (stop comparison)", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
+                     bOk = FALSE;
+                     break;
+                 }
+                 else {
+                     log4(ctx, TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: NO", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
+                 }
+             }
+             if (bOk) {
+                 char *cpA;
+                 sa_addr_a2u(ctx->saaIO, &cpA);
+                 log1(ctx, TRACE, "connection from %s accepted due to ACL", cpA); 
+                 free(cpA);
+             }
+             else {
+                 char *cpA;
+                 sa_addr_a2u(ctx->saaIO, &cpA);
+                 log1(ctx, ERROR, "connection from %s refused due to ACL", cpA); 
+                 free(cpA);
+                 sa_destroy(ctx->saIO);
+                 sa_addr_destroy(ctx->saaIO);
+                 continue;
+             }
+ 
+             /* logging buffer must be empty before fork otherwise content is
+              * duplicated and written twice on next flush */
+             l2_channel_flush(ctx->l2);
+             pid = fork();
+             if (pid == -1) {
+                 log0(ctx, ERROR, "daemon cannot spawn child %m");
+                 continue;
+             }
+             if (pid != 0) {
+                 log1(ctx, INFO, "daemon forked process, new child pid[%d]", pid);
+                 ctx->active_childs++;
+                 sa_destroy(ctx->saIO);
+                 sa_addr_destroy(ctx->saaIO);
+                 continue;
+             }
+             log1(ctx, NOTICE, "startup new child process, parent pid[%d]", getppid());
+ 
+             /* child must close listening socket */
+             sa_destroy(ctx->saAltio);
+             ctx->saAltio = NULL; /* prevent cleanup from free'ing this again */
+             
+             /* initialize LMTP context */
+             lmtp_io.ctx    = ctx;
+             lmtp_io.read   = hook_lmtp_read;
+             lmtp_io.write  = hook_lmtp_write;
+             if ((lmtp = lmtp_create(&lmtp_io)) == NULL) {
+                 log0(ctx, ERROR, "Unable to initialize LMTP library\n");
+                 CU(ERR_EXECUTION);
+             }
+             /*  RFC0821, 4.5.1. MINIMUM IMPLEMENTATION
+              *  In order to make SMTP workable, the following minimum implementation
+              *  is required for all receivers: [...]
+              *  RFC0821, 4.1.2. COMMAND SYNTAX
+              *  
+              *  Verb Parameter
+              *  ----+-------------------------------
+              *  HELO <SP> <domain> <CRLF>
+              *  MAIL <SP> FROM:<reverse-path> <CRLF>
+              *  RCPT <SP> TO:<forward-path> <CRLF>
+              *  DATA <CRLF>
+              *  RSET <CRLF>
+              *  NOOP <CRLF>
+              *  QUIT <CRLF>
+              */
+             lmtp_register(lmtp, "LHLO", lmtp_cb_lhlo, ctx, NULL, NULL); 
+             lmtp_register(lmtp, "MAIL", lmtp_cb_mail, ctx, NULL, NULL);
+             lmtp_register(lmtp, "RCPT", lmtp_cb_rcpt, ctx, NULL, NULL);
+             lmtp_register(lmtp, "DATA", lmtp_cb_data, ctx, NULL, NULL);
+             lmtp_register(lmtp, "RSET", lmtp_cb_rset, ctx, NULL, NULL);
+             lmtp_register(lmtp, "NOOP", lmtp_cb_noop, ctx, NULL, NULL);
+             lmtp_register(lmtp, "QUIT", lmtp_cb_quit, ctx, NULL, NULL);
+             lmtp_loop(lmtp);
+             lmtp_gfs_quit(ctx);
+             lmtp_gfs_lhlo(ctx);
+             lmtp_destroy(lmtp);
+             CU(0);
+         }
+     }
+     CU(0);
+ 
+     /* graceful shutdown */
+     CUS:
+     log0(ctx, NOTICE, "graceful shutdown shortly before exit - no more logging");
+     l2_channel_destroy(ctx->l2);
+     l2_env_destroy(ctx->l2_env);
+     if (ctx->saAltio)
+         sa_destroy(ctx->saAltio);
+     if (ctx->saaAltio)
+         sa_addr_destroy(ctx->saaAltio);
+     if (ctx->option_restrictheader != NULL)
+         free(ctx->option_restrictheader);
+     if (ctx->azHeaderValuePairs != NULL)
+         free(ctx->azHeaderValuePairs);
+     if (ctx->option_pidfile != NULL)
+         free(ctx->option_pidfile);
+     if (ctx->option_logfile != NULL)
+         free(ctx->option_logfile);
+     if (ctx->progname != NULL)
+         free(ctx->progname);
+     if (ctx->azGroupargs != NULL)
+         free(ctx->azGroupargs);
+     if (ctx != NULL)
+         free(ctx);
+     str_parse(NULL, NULL);
+ 
+     return rc;
+ }
+ 
+ static void resetsession(struct session *session)
+ {
+     if (session->lhlo_domain != NULL)
+         free(session->lhlo_domain);
+     initsession(session);
+     return;
+ }
+ 
+ static void initsession(struct session *session)
+ {
+     session->lhlo_seen = FALSE;
+     session->lhlo_domain = NULL;
+     return;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     /*  
+      *  RFC0821 [excerpt] 4.1. SMTP COMMANDS
+      *  4.1.1.  COMMAND SEMANTICS, HELO
+      *  This command and an OK reply to it confirm that both the sender-SMTP
+      *  and the receiver-SMTP are in the initial state, that is, there is no
+      *  transaction in progress and all state tables and buffers are cleared.
+      * 
+      *  The first command in a session must be the HELO command.  The HELO
+      *  command may be used later in a session as well.  If the HELO command
+      *  argument is not acceptable a 501 failure reply must be returned and
+      *  the receiver-SMTP must stay in the same state.
+      *
+      *  If the transaction beginning command argument is not acceptable a 501
+      *  failure reply must be returned and the receiver-SMTP must stay in the
+      *  same state.  If the commands in a transaction are out of order a 503
+      *  failure reply must be returned and the receiver-SMTP must stay in the
+      *  same state.
+      *
+      *  HELO <SP> <domain> <CRLF>
+      */
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     nntp_rc_t    rc;
+     lmtp_res_t   res;
+     char         str[STDSTRLEN];
+     int          bOk;
+     int          i;
+     nntp_io_t    nntp_io;
+ 
+     log1(ctx, INFO, "LMTP service executing LHLO command < %s", req->msg);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   503 Bad sequence of commands
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.0.0   Other undefined Status
+      */
+     log0(ctx, TRACE, "checking for duplicate LHLO");
+     if (ctx->session.lhlo_seen) {
+         res.statuscode = "503";
+         res.dsncode    = "5.0.0";
+         res.statusmsg  = "Duplicate LHLO.";
+         CU(LMTP_OK);
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   501 Syntax error in parameters or arguments
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.0.0   Other undefined Status
+      */
+     log0(ctx, TRACE, "checking domain to match either RFC0821 or RFC1035 syntax");
+     if (! (   helo_rfc0821domain(req->msg, &ctx->session.lhlo_domain) > 0
+            || helo_rfc1035domain(req->msg, &ctx->session.lhlo_domain) > 0)) {
+         res.statuscode = "501";
+         res.dsncode    = "5.0.0";
+         res.statusmsg  = "Please identify yourself. Domain must match RFC0821/RFC1035.";
+         CU(LMTP_OK);
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   451 Requested action aborted: local error in processing
+      *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+      *  RFC1893 3.5 Network and Routing Status          X.3.5   System incorrectly configured
+      */
+     if (ctx->option_operationmode != OPERATIONMODE_FAKE) {
+         log0(ctx, TRACE, "check if at least one NNTP service was successfully configured");
+         if (ctx->nsc == 0) {
+             res.statuscode = "451";
+             res.dsncode    = "4.3.5";
+             res.statusmsg  = "No valid NNTP services configured.";
+             CU(LMTP_OK);
+         }
+     }
+ 
+     log0(ctx, TRACE, "try to establish a session to any configured NNTP services");
+     if (ctx->option_operationmode == OPERATIONMODE_FAKE)
+         log0(ctx, NOTICE, "NNTP running in fake mode, network connections will be executed but result is ignored");
+     i = 0;
+     do {
+         log1(ctx, DEBUG, "trying ns[%d]", i);
+         bOk = TRUE;
+         log2(ctx, TRACE, "try %s:%s", ctx->ns[i].h, ctx->ns[i].p);
+ 
+         ctx->ns[i].l2 = ctx->l2;
+ 
+         if (bOk && (ctx->saaBind != NULL)) {
+             log2(ctx, DEBUG, "bind local socket to %s:%s", ctx->cpBindh, ctx->cpBindp);
+             if (sa_bind(ctx->ns[i].sa, ctx->saaBind) != SA_OK) {
+                 bOk = FALSE;
+                 log2(ctx, ERROR, "binding NNTP client to local address %s:%s failed, %m", ctx->cpBindh, ctx->cpBindp);
+             }
+         }
+ 
+         sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_ALL, 0, 0);
+         sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_CONNECT, ctx->option_timeout_nntp_connect, 0);
+         sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_READ,    ctx->option_timeout_nntp_read,    0);
+         sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_WRITE,   ctx->option_timeout_nntp_read,    0);
+ 
+         if (bOk) {
+             log0(ctx, DEBUG, "connect");
+             if (sa_connect(ctx->ns[i].sa, ctx->ns[i].saa) != SA_OK) {
+                 bOk = FALSE;
+                 log2(ctx, WARNING, "connect to %s:%s failed, %m",
+                      ctx->ns[i].h, ctx->ns[i].p);
+             }
+         }
+ 
+         if (bOk) {
+             log0(ctx, DEBUG, "nntp_create");
+             nntp_io.ctx    = &ctx->ns[i];
+             nntp_io.read   = hook_nntp_read;
+             nntp_io.write  = hook_nntp_write;
+             if ((ctx->ns[i].nntp = nntp_create(&nntp_io)) == NULL) {
+                 bOk = FALSE;
+                 log0(ctx, ERROR, "creation of NNTP context failed");
+             }
+         }
+ 
+         if (bOk) {
+             log0(ctx, DEBUG, "nntp_init");
+             if ((rc = nntp_init(ctx->ns[i].nntp)) != NNTP_OK) {
+                 bOk = FALSE;
+                 log2(ctx, ERROR, "initialization of NNTP context failed, (%d) %s", rc, nntp_error(rc));
+             }
+         }
+ 
+         if (bOk) {
+             log2(ctx, INFO, "NNTP session to %s:%s successfully established", ctx->ns[i].h, ctx->ns[i].p);
+             i++;
+         }
+         else {
+             log2(ctx, WARNING, "NNTP session establishment to %s:%s failed", ctx->ns[i].h, ctx->ns[i].p);
+             log1(ctx, DEBUG, "removing ns[%d] from list", i);
+             lmtp_gfs_ns(&ctx->ns[i]);
+             if (i < --ctx->nsc) {
+                 memcpy(&ctx->ns[i], &ctx->ns[i+1], (ctx->nsc - i ) * sizeof(struct ns));
+             }
+         }
+     } while (i < ctx->nsc);
+ 
+     if (ctx->option_operationmode == OPERATIONMODE_FAKE)
+         log1(ctx, NOTICE, "NNTP running in fake mode, network connections successfully established=%d but ignored", ctx->nsc);
+     else
+     {
+         /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   421 <domain> Service not available
+          *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+          *  RFC1893 3.5 Network and Routing Status          X.4.1   No answer from host
+          */
+         log0(ctx, DEBUG, "check if at least one NNTP session successfully established");
+         if (ctx->nsc == 0) {
+             log0(ctx, ERROR, "no NNTP session established");
+             res.statuscode = "421";
+             res.dsncode    = "4.4.1";
+             res.statusmsg  = "No NNTP session established.";
+             CU(LMTP_OK);
+         }
+     }
+         
+     ctx->session.lhlo_seen = TRUE;
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   250 Requested mail action okay, completed
+      */
+     str_format(str, sizeof(str),
+                "%s Hello %s, pleased to meet you.\n" /* RFC2821 4.1.1.1 */
+                "ENHANCEDSTATUSCODES\n"               /* RFC2034 */
+                "DSN\n"                               /* RFC1894 */
+                "PIPELINING\n"                        /* RFC1854 */
+                "8BITMIME",                           /* RFC1652 */
+                ctx->uname.nodename,
+                ctx->session.lhlo_domain);
+     res.statuscode = "250";
+     res.dsncode    = NULL; /* DSN not used for greeting */
+     res.statusmsg  = str;
+     CU(LMTP_OK);
+ 
+     CUS:
+     lmtp_response(lmtp, &res);
+     return rc;
+ }
+ 
+ static void lmtp_gfs_ns(struct ns *ns)
+ {
+     if (ns->nntp != NULL) {
+         nntp_destroy(ns->nntp);
+         ns->nntp = NULL;
+     }
+     if (ns->sa != NULL) {
+         sa_destroy(ns->sa);
+         ns->sa = NULL;
+     }
+     if (ns->saa != NULL) {
+         sa_addr_destroy(ns->saa);
+         ns->saa = NULL;
+     }
+     if (ns->p != NULL) {
+         free(ns->p);
+         ns->p = NULL;
+     }
+     if (ns->h != NULL) {
+         free(ns->h);
+         ns->h = NULL;
+     }
+ }
+ 
+ static void lmtp_gfs_lhlo(lmtp2nntp_t *ctx)
+ {
+     int i;
+ 
+     log0(ctx, TRACE, "LMTP service LHLO command - graceful shutdown");
+ 
+     for (i = 0; i < ctx->nsc; i++)
+             lmtp_gfs_ns(&ctx->ns[i]);
+ 
+     if (ctx->option_mailfrom != NULL)
+         free(ctx->option_mailfrom);
+     if (ctx->cpBindh != NULL)
+         free(ctx->cpBindh);
+     if (ctx->cpBindp != NULL)
+         free(ctx->cpBindp);
+     if (ctx->saBind != NULL)
+         sa_destroy(ctx->saBind);
+     if (ctx->saaBind != NULL)
+         sa_addr_destroy(ctx->saaBind);
+ }
+ 
+ static int helo_rfc0821domain(char *msg, char **domain)
+ {
+     int rc;
+ 
+     rc = str_parse(msg, 
+             "^.+ ("
+     /*
+      ##
+      ##  The mega Perl regular expression below is generated
+      ##  with the following Perl program. This is only possible
+      ##  because the given grammar is Chomsky-3 (right or left
+      ##  linear grammar, but noth both).
+      ##
+     
+      # BNF grammar for <domain> according to RFC0821:
+      # <snum>        ::= one, two, or three digits representing a decimal integer value in the range 0 through 255
+      # <a>           ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
+      # <d>           ::= any one of the ten digits 0 through 9
+      # <let-dig-hyp> ::= <a> | <d> | "-"
+      # <let-dig>     ::= <a> | <d>
+      # <ldh-str>     ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+      # <dotnum>      ::= <snum> "." <snum> "." <snum> "." <snum>
+      # <number>      ::= <d> | <d> <number>
+      # <name>        ::= <a> <ldh-str> <let-dig>
+      # <element>     ::= <name> | "#" <number> | "[" <dotnum> "]"
+      # <domain>      ::= <element> | <element> "." <domain>
+      #
+      # corresponding Perl regular expression ($domain)
+      $snum        = "(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])";
+      $d           = "[0-9]";
+      $a           = "[A-Za-z]";
+      $let_dig_hyp = "(?:$a|$d|-)";
+      $let_dig     = "(?:$a|$d)";
+      $ldh_str     = "${let_dig_hyp}+";
+      $dotnum      = "$snum\\.$snum\\.$snum\\.$snum";
+      $number      = "$d+";
+      $name        = "$a$ldh_str$let_dig";
+      $element     = "(?:$name|#$number|\\[$dotnum\\])";
+      $domain      = "(?:$element\.)*$element";
+      #
+      # translate into C string block suitable for passing to the Perl
+      # Compatible Regular Expressions (PCRE) based string library Str.
+      my $cregex = $domain;
+      $cregex .= "\n";
+      $cregex =~ s|\\|\\\\|sg;
+      $cregex =~ s|(.{17})|$1\n|sg;
+      $cregex =~ s|([^\n]+)\n|"$1"\n|sg;
+      $cregex =~ s|\n\n|\n|sg;
+      print "$cregex";
+      */
+ 
+     "(?:(?:[A-Za-z](?:[A-Za-z]|[0-9]|-)+(?:[A-Za-z]|[0-9])|#[0-9]+|\\[(?:[0"
+     "-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0"
+     "-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0"
+     "-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5"
+     "])\\]).)*(?:[A-Za-z](?:[A-Za-z]|[0-9]|-)+(?:[A-Za-z]|[0-9])|#[0-9]+|\\"
+     "[(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]"
+     "{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{"
+     "2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|"
+     "25[0-5])\\])"
+ 
+     ")$", domain);
+     return rc;
+ }
+ 
+ static int helo_rfc1035domain(char *msg, char **domain)
+ {
+     int rc;
+ 
+     rc = str_parse(msg, 
+             "^.+ ("
+     /*
+      ##
+      ##  The mega Perl regular expression below is generated
+      ##  with the following Perl program. This is only possible
+      ##  because the given grammar is Chomsky-3 (right or left
+      ##  linear grammar, but noth both).
+      ##
+ 
+      # BNF grammar for <domain> according to RFC1035:
+      # <letter>      ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
+      # <digit>       ::= any one of the ten digits 0 through 9
+      # <let-dig>     ::= <letter> | <digit>
+      # <let-dig-hyp> ::= <let-dig> | "-"
+      # <ldh-str>     ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+      # <label>       ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+      # <subdomain>   ::= <label> | <subdomain> "." <label>
+      # <domain>      ::= <subdomain> | " "
+      #
+      # corresponding Perl regular expression ($domain)
+      $letter      = "[A-Za-z]";
+      $digit       = "[0-9]";
+      $let_dig     = "(?:$letter|$digit)";
+      $let_dig_hyp = "(?:$let_dig|-)";
+      $ldh_str     = "${let_dig_hyp}+";
+      $label       = "(?:$letter(?:(?:$ldh_str)?$let_dig)?)";
+      $subdomain   = "(?:$label\.)*$label";
+      $domain      = "(?:$subdomain| )";
+      #
+      # translate into C string block suitable for passing to the Perl
+      # Compatible Regular Expressions (PCRE) based string library Str.
+      my $cregex = $domain;
+      $cregex .= "\n";
+      $cregex =~ s|\\|\\\\|sg;
+      $cregex =~ s|(.{17})|$1\n|sg;
+      $cregex =~ s|([^\n]+)\n|"$1"\n|sg;
+      $cregex =~ s|\n\n|\n|sg;
+      print "$cregex";
+      */
+ 
+     "(?:(?:(?:[A-Za-z](?:(?:(?:(?:[A-Za-z]|[0-9])|-)+)?(?:[A-Za-z]|[0-9]))?"
+     ").)*(?:[A-Za-z](?:(?:(?:(?:[A-Za-z]|[0-9])|-)+)?(?:[A-Za-z]|[0-9]))?)|"
+     " )"
+ 
+     ")$", domain);
+     return rc;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_mail(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     lmtp_rc_t    rc;
+     lmtp_res_t   res;
+ 
+     log1(ctx, INFO, "LMTP service executing MAIL command < %s", req->msg);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   553 Requested action not taken: mailbox name not allowed
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.1.8   Bad sender's system address
+      */
+     log0(ctx, TRACE, "checking for previous LHLO");
+     if (!ctx->session.lhlo_seen) {
+         res.statuscode = "553";
+         res.dsncode    = "5.1.8";
+         res.statusmsg  = "friendly people say LHLO to open a transmission channel.";
+         CU(LMTP_OK);
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   503 Bad sequence of commands
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.5.0   Other or undefined protocol status
+      */
+     log0(ctx, TRACE, "checking for previous MAIL");
+     if (ctx->msg != NULL) {
+         res.statuscode = "503";
+         res.dsncode    = "5.5.0";
+         res.statusmsg  = "Sender already specified.";
+         CU(LMTP_OK);
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   452 Requested action not taken: insufficient system storage
+      *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+      *  RFC1893 3.5 Network and Routing Status          X.3.1   Mail system full 
+      */
+     log0(ctx, TRACE, "msg_create");
+     if ((ctx->msg = msg_create()) == NULL) {
+         res.statuscode = "452";
+         res.dsncode    = "4.3.1";
+         res.statusmsg  = "Internal error - memory.";
+         CU(LMTP_ERR_MEM);
+     }
+     ctx->msg->l2 = ctx->l2;
+ 
+     /*  RFC1652 2. Framework for the 8bit MIME Transport Extension
+      *  (4)  one optional parameter using the keyword BODY is added to the MAIL
+      *  FROM command.  The value associated with this parameter is a keyword
+      *  indicating whether a 7bit message [...] or a MIME message [...] is
+      *  being sent. The syntax of the value is as follows, using the ABNF
+      *  notation [...]
+      *  
+      *  body-value ::= "7BIT" / "8BITMIME"
+      *  
+      *  "MAIL From:<foo@bar>"
+      *  "MAIL From:<foo@bar> BODY=8BITMIME"
+      *  "MAIL From:<foo@bar> BODY=7BIT"
+      *  
+      *  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   553 Requested action not taken: mailbox name not allowed
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.1.7   Bad sender's mailbox address syntax
+      */
+     log0(ctx, TRACE, "checking if sender address is a domain name");
+     if (str_parse(req->msg, "m/^MAIL From:\\s*<(?:.+@.+)>/i") <= 0) {
+         res.statuscode = "553";
+         res.dsncode    = "5.1.7";
+         res.statusmsg  = "Domain name required for sender address.";
+         CU(LMTP_OK);
+     }
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   501 Syntax error in parameters or arguments
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.5.4   Invalid command arguments
+      */
+     log0(ctx, TRACE, "checking BODY keyword");
+     if (str_parse(req->msg, "m/^MAIL From:\\s*<(.+@.+)>"
+                             "(?:\\s+BODY=(?:7BIT|8BITMIME)\\s*)?$/i", 
+                             &ctx->msg->mail_from) <= 0) {
+         res.statuscode = "501";
+         res.dsncode    = "5.5.4";
+         res.statusmsg  = "Unknown parameter for keyword BODY.";
+         CU(LMTP_OK);
+     }
+     
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   550 Requested action not taken: mailbox unavailable
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.7.1   Delivery not authorized, message refused
+      */
+     log0(ctx, TRACE, "checking if sender is allowed");
+     if (ctx->option_mailfrom != NULL) {
+         log2(ctx, TRACE, "\"%s\" matching against \"%s\"", ctx->msg->mail_from, ctx->option_mailfrom);
+         if (str_parse(ctx->msg->mail_from, ctx->option_mailfrom) <= 0) {
+             res.statuscode = "550";
+             res.dsncode    = "5.7.1";
+             res.statusmsg  = "Delivery not authorized, message refused.";
+             CU(LMTP_OK);
+         }
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   250 Requested mail action okay, completed
+      *  RFC1893 2. Status Codes                         2.X.X   Success
+      *  RFC1893 3.5 Network and Routing Status          X.1.0   Other address status
+      */
+     res.statuscode = "250";
+     res.dsncode    = "2.1.0";
+     res.statusmsg  = "Sender ok.";
+     lmtp_response(lmtp, &res);
+     return LMTP_OK;
+ 
+     CUS:
+     lmtp_response(lmtp, &res);
+     if (ctx->msg != NULL) {
+         msg_destroy(ctx->msg);
+         ctx->msg = NULL;
+     }
+     return rc;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     lmtp_res_t   res;
+     lmtp_rc_t    rc;
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     char        *cp;
+     char        *group;
+ 
+     log1(ctx, INFO, "LMTP service executing RCPT command < %s", req->msg);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   503 Bad sequence of commands
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.5.0   Other or undefined protocol status
+      */
+     log0(ctx, TRACE, "checking for previous MAIL");
+     if ((ctx->msg == NULL) || (ctx->msg->mail_from == NULL)) {
+         res.statuscode = "503";
+         res.dsncode    = "5.5.0";
+         res.statusmsg  = "specify sender with MAIL first.";
+         CU(LMTP_OK);
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   501 Syntax error in parameters or arguments
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.5.2   Syntax error
+      */
+     log0(ctx, TRACE, "checking parameter syntax");
+     if (str_parse(req->msg, "m/^RCPT To:\\s*(.+)$/i", &cp) <= 0) {
+         res.statuscode = "501";
+         res.dsncode    = "5.5.2";
+         res.statusmsg  = "Syntax error in parameters.";
+         CU(LMTP_OK);
+     }
+     
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   550 Requested action not taken: mailbox unavailable
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.1.1   Bad destination mailbox address
+      */
+     log0(ctx, TRACE, "checking for empty parameter");
+     if ((cp == NULL) || (strlen(cp) == 0)) {
+         res.statuscode = "550";
+         res.dsncode    = "5.1.1";
+         res.statusmsg  = "empty Recipient/ Group.";
+         CU(LMTP_OK);
+     }
+ 
+     /* in GROUPMODE = ARG|HEADER recipient must be acknowledged and stored to
+      * give proper pipelining responses.  in GROUPMODE = ENVELOPE recipient is
+      * transformed into a group and matched against groupfilter. Only valid
+      * groups are stored to give proper pipelining responses.
+      *
+      *  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   550 Requested action not taken: mailbox unavailable
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.1.1   Bad destination mailbox address
+      *                                                  X.7.2   Mailing list expansion prohibited
+      */
+     log1(ctx, DEBUG, "ctx->option_groupmode=%d", ctx->option_groupmode);
+     if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
+         log0(ctx, TRACE, "groupmode=envelope; transform recipient into group");
+         if (str_parse(cp, "m/^<(.+)?@[^@]+>$/i", &group) <= 0) {
+             res.statuscode = "550";
+             res.dsncode    = "5.1.1";
+             res.statusmsg  = "Recipient did not transform into group.";
+             CU(LMTP_OK);
+         }
+         log1(ctx, TRACE, "groupmode=envelope; match group %s", group);
+         if (!groupmatch(ctx->azGroupargs, ctx->asGroupargs, group)) {
+             res.statuscode = "550";
+             res.dsncode    = "5.7.2";
+             res.statusmsg  = "unmatched Group.";
+             CU(LMTP_OK);
+         }
+         log1(ctx, TRACE, "memorize group %s", group);
+         argz_add(&ctx->msg->azEnvgroups, &ctx->msg->asEnvgroups, group);
+     }
+     log1(ctx, TRACE, "memorize recipient %s", cp);
+     argz_add(&ctx->msg->azRcpt, &ctx->msg->asRcpt, cp);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   250 Requested mail action okay, completed
+      *  RFC1893 2. Status Codes                         2.X.X   Success
+      *  RFC1893 3.5 Network and Routing Status          X.1.5   Destination address valid
+      */
+     res.statuscode = "250";
+     res.dsncode    = "2.1.5";
+     res.statusmsg  = ctx->option_groupmode == GROUPMODE_ENVELOPE ? "Group accepted." : "Recipient accepted.";
+     CU(LMTP_OK);
+ 
+     CUS:
+     lmtp_response(lmtp, &res);
+     return rc;
+ }
+ 
+ int groupmatch(char *azPattern, size_t asPattern, char *cpGroup)
+ {
+     int bGroupmatch;
+     char *cpGroupmatch;
+ 
+     bGroupmatch = FALSE;
+     cpGroupmatch = NULL;
+     while ((cpGroupmatch = argz_next(azPattern, asPattern, cpGroupmatch)) != NULL) {
+         if (shpat_match(cpGroupmatch, cpGroup, 0) == 0)
+             bGroupmatch = TRUE;
+     }
+     return bGroupmatch;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_data(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     lmtp_rc_t    rc = LMTP_OK;
+     lmtp_res_t   res;
+     char        *azErr;
+     size_t       asErr;
+     char         errorstring[STDSTRLEN];
+     char        *rcpt;
+     int          i;
+     int          bSuccess;
+     char        *cp;
+     int          bOk;
+     char        *cpRestrictheader;
+     char        *cpRestrictvalue;
+ 
+     log1(ctx, INFO, "LMTP service executing DATA command < %s", req->msg);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   503 Bad sequence of commands
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.5.0   Other or undefined protocol status
+      */
+     log0(ctx, TRACE, "checking for previous RCPT");
+     if ((ctx->msg == NULL) || (argz_count(ctx->msg->azRcpt, ctx->msg->asRcpt) == 0)) {
+         res.statuscode = "503";
+         res.dsncode    = "5.5.0";
+         res.statusmsg  = "specify recipient with RCPT first.";
+         lmtp_response(lmtp, &res);
+         return LMTP_OK;
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   354 Start mail input; end with <CRLF>.<CRLF>
+      */
+     log0(ctx, TRACE, "tell remote to send message now");
+     res.statuscode = "354";
+     res.dsncode    = NULL; /* DSN not used for data */
+     res.statusmsg  = "Enter mail, end with \".\" on a line by itself";
+     lmtp_response(lmtp, &res);
+ 
+     log1(ctx, TRACE, "read message with maximum size to accept = %d", ctx->option_maxmessagesize);
+     rc = lmtp_readmsg(lmtp, &ctx->msg->cpMsg, ctx->option_maxmessagesize);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   552 Requested mail action aborted: exceeded storage allocation
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.2.3   Message length exceeds administrative limit.
+      */
+     log0(ctx, TRACE, "checking for excessive message size");
+     if (rc == LMTP_ERR_OVERFLOW) {
+         str_format(errorstring, sizeof(errorstring), "Message length exceeds administrative limit. %s", lmtp_error(rc));
+         res.statuscode = "552";
+         res.dsncode    = "5.2.3";
+         res.statusmsg  = errorstring;
+         rcpt = NULL;
+         while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+             lmtp_response(lmtp, &res);
+         }
+         return LMTP_OK;
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   451 Requested action aborted: local error in processing
+      *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+      *  RFC1893 3.5 Network and Routing Status          X.3.2   System not accepting network messages
+      */
+     log0(ctx, TRACE, "checking for system error");
+     if (rc == LMTP_ERR_SYSTEM) {
+         str_format(errorstring, sizeof(errorstring), "System error reading message: %s", strerror(errno));
+         res.statuscode = "451";
+         res.dsncode    = "4.3.2";
+         res.statusmsg  = errorstring;
+         rcpt = NULL;
+         while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+             lmtp_response(lmtp, &res);
+         }
+         return LMTP_OK;
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   451 Requested action aborted: local error in processing
+      *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+      *  RFC1893 3.5 Network and Routing Status          X.3.2   System not accepting network messages
+      */
+     log0(ctx, TRACE, "checking for other error");
+     if(rc != LMTP_OK) {
+         str_format(errorstring, sizeof(errorstring), "Unknown error reading message: %s", lmtp_error(rc));
+         res.statuscode = "451";
+         res.dsncode    = "4.3.2";
+         res.statusmsg  = errorstring;
+         rcpt = NULL;
+         while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+             lmtp_response(lmtp, &res);
+         }
+         return LMTP_OK;
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   554 Transaction failed
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.6.5   Conversion Failed
+      */
+     log0(ctx, TRACE, "split message");
+     if ((rc = msg_split(ctx->msg)) != MSG_OK) {
+         str_format(errorstring, sizeof(errorstring), "Error splitting message: %s", msg_error(rc));
+         res.statuscode = "554";
+         res.dsncode    = "5.6.5";
+         res.statusmsg  = errorstring;
+         rcpt = NULL;
+         while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+             lmtp_response(lmtp, &res);
+         }
+         return LMTP_OK;
+     }
+ 
+     if      (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
+         if ((cp = malloc(ctx->msg->asEnvgroups + 1)) == NULL) {
+             /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   452 Requested action not taken: insufficient system storage
+              *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+              *  RFC1893 3.5 Network and Routing Status          X.3.1   Mail system full 
+              */
+             if ((ctx->msg = msg_create()) == NULL) {
+                 res.statuscode = "452";
+                 res.dsncode    = "4.3.1";
+                 res.statusmsg  = "Internal error - memory.";
+                 lmtp_response(lmtp, &res);
+                 return LMTP_ERR_MEM;
+             }
+         }
+         ctx->msg->azNewsgroups = memcpy(cp, ctx->msg->azEnvgroups, ctx->msg->asEnvgroups);
+         ctx->msg->asNewsgroups =                                   ctx->msg->asEnvgroups;
+     }
+     else if (ctx->option_groupmode == GROUPMODE_ARG) {
+         if ((cp = malloc(ctx->asGroupargs + 1)) == NULL) {
+             /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   452 Requested action not taken: insufficient system storage
+              *  RFC1893 2. Status Codes                         4.X.X   Persistent Transient Failure
+              *  RFC1893 3.5 Network and Routing Status          X.3.1   Mail system full 
+              */
+             if ((ctx->msg = msg_create()) == NULL) {
+                 res.statuscode = "452";
+                 res.dsncode    = "4.3.1";
+                 res.statusmsg  = "Internal error - memory.";
+                 lmtp_response(lmtp, &res);
+                 return LMTP_ERR_MEM;
+             }
+         }
+         ctx->msg->azNewsgroups = memcpy(cp, ctx->azGroupargs, ctx->asGroupargs);
+         ctx->msg->asNewsgroups =                              ctx->asGroupargs;
+     }
+     else { /*                      == GROUPMODE_HEADER */
+         cp = ctx->msg->azNewsgroups;
+         while (cp != NULL) {
+             if (!groupmatch(ctx->azGroupargs, ctx->asGroupargs, cp)) {
+                 if (argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, cp) == NULL) {
+                     argz_delete(&ctx->msg->azNewsgroups, &ctx->msg->asNewsgroups, cp);
+                     break;
+                 }
+                 else
+                     argz_delete(&ctx->msg->azNewsgroups, &ctx->msg->asNewsgroups, cp);
+             } else {
+                 cp = argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, cp);
+             }
+         }
+         /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   550 Requested action not taken: mailbox unavailable
+          *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+          *  RFC1893 3.5 Network and Routing Status          X.7.2   Mailing list expansion prohibited
+          */
+         if (ctx->msg->asNewsgroups == 0) {
+             rcpt = NULL;
+             while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+                 res.statuscode = "550";
+                 res.dsncode    = "5.7.2";
+                 res.statusmsg  = "Header did not match any valid group.";
+                 lmtp_response(lmtp, &res);
+             }
+             return LMTP_OK;
+         }
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   550 Requested action not taken: mailbox unavailable
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.7.1   Delivery not authorized, message refused
+      */
+     log0(ctx, TRACE, "checking if restricted header causes reject");
+     if (ctx->option_restrictheader != NULL) {
+         bOk = FALSE;
+         cp = NULL;
+         while ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) != NULL) {
+             cpRestrictheader = cp;
+             if ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) == NULL)
+                 break;
+             cpRestrictvalue = cp;
+             str_format(errorstring, sizeof(errorstring), "%s %s", cpRestrictheader, cpRestrictvalue);
+             if (str_parse(errorstring, ctx->option_restrictheader) <= 0) {
+                 log2(ctx, TRACE, "\"%s\" matching against \"%s\" NO", errorstring, ctx->option_restrictheader);
+             }
+             else {
+                 log2(ctx, TRACE, "\"%s\" matching against \"%s\": YES", errorstring, ctx->option_restrictheader);
+                 bOk = TRUE;
+                 break;
+             }
+         }
+         if (bOk) {
+             log0(ctx, TRACE, "restricted header found");
+             rcpt = NULL;
+             while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+                 res.statuscode = "550";
+                 res.dsncode    = "5.7.1";
+                 res.statusmsg  = "Restricted header matched, message rejected.";
+                 lmtp_response(lmtp, &res);
+             }
+             return LMTP_OK;
+         }
+     }
+ 
+     /* Optionally add command line specified Header/Value Pairs
+      */
+     log0(ctx, TRACE, "adding header/value pairs");
+     if ((ctx->asHeaderValuePairs >= 1) && ((ctx->asHeaderValuePairs & 1) == 0)) {
+         cp = NULL;
+         while ((cp = argz_next(ctx->azHeaderValuePairs, ctx->asHeaderValuePairs, cp)) != NULL) {
+             var_rc_t var_rc;
+             char *res_ptr;
+             if ((var_rc = var_expand(cp, strlen(cp), &res_ptr, NULL,
+                                      ctx_lookup, ctx, &ctx_lookup_cfg, TRUE)) != VAR_OK) {
+                 log2(ctx, ERROR, "expansion of '%s' failed: %s", cp, var_strerror(var_rc));
+                 continue;
+             }
+             log1(ctx, DEBUG, "adding expanded header: \"%s\"", res_ptr);
+             argz_add(&ctx->msg->azHeaders, &ctx->msg->asHeaders, res_ptr);
+             free(res_ptr);
+         }
+     }
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   554 Transaction failed
+      *  RFC1893 2. Status Codes                         5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.6.5   Conversion Failed
+      */
+     log0(ctx, TRACE, "join message");
+     if ((rc = msg_join(ctx->msg)) != MSG_OK) {
+         str_format(errorstring, sizeof(errorstring), "Error joining message: %s", msg_error(rc));
+         res.statuscode = "554";
+         res.dsncode    = "5.6.5";
+         res.statusmsg  = errorstring;
+         rcpt = NULL;
+         while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+             lmtp_response(lmtp, &res);
+         }
+         return LMTP_OK;
+     }
+ 
+     log0(ctx, TRACE, "deliver message");
+     bSuccess = NNTP_ERR_DELIVERY; /* assume a hard error for the worst case */
+     for (i = 0; i < ctx->nsc; i++) {
+         switch (ctx->option_operationmode) {
+             case OPERATIONMODE_FAKE:
+                 ctx->ns[i].rc = NNTP_FAKE;
+                 break;
+             case OPERATIONMODE_POST:
+                 ctx->ns[i].rc = nntp_post(ctx->ns[i].nntp, ctx->msg);
+                 break;
+             case OPERATIONMODE_FEED:
+                 ctx->ns[i].rc = nntp_feed(ctx->ns[i].nntp, ctx->msg);
+                 break;
+         }
+         if (ctx->ns[i].rc == NNTP_OK)
+             bSuccess = NNTP_OK;
+         if (   bSuccess != NNTP_OK
+             && (
+                    (ctx->ns[i].rc == NNTP_ERR_SYSTEM)
+                 || (ctx->ns[i].rc == NNTP_DEFER)
+                   )
+               )
+             bSuccess = NNTP_DEFER;
+     }
+ 
+     if (ctx->option_operationmode == OPERATIONMODE_FAKE) {
+         str_format(errorstring, sizeof(errorstring),
+                    "NNTP running in fake mode, delivery of %s [%d bytes] %s but delivery status forced to",
+                    ctx->msg->cpMsgid,
+                    strlen(ctx->msg->cpMsg),
+                    ((bSuccess == NNTP_OK)    ? "succeeded" :
+                     (bSuccess == NNTP_DEFER) ? "deferred"  : "failed"));
+         switch (ctx->option_operationmodefakestatus[0]) {
+             case '5':
+                 bSuccess = NNTP_ERR_UNKNOWN;
+                 log2(ctx, NOTICE, "%s %s", errorstring, "failed");
+                 break;
+             case '4':
+                 bSuccess = NNTP_DEFER;
+                 log2(ctx, NOTICE, "%s %s", errorstring, "deferred");
+                 break;
+             default:
+                 bSuccess = NNTP_OK;
+                 log2(ctx, NOTICE, "%s %s", errorstring, "succeeded");
+                 break;
+         }
+     } else {
+         str_format(errorstring, sizeof(errorstring), "%sdelivery of %s [%d bytes]", 
+                    ((ctx->option_operationmode == OPERATIONMODE_POST) ? "post " :
+                    (ctx->option_operationmode == OPERATIONMODE_FEED) ? "feed " : ""),
+                    ctx->msg->cpMsgid,
+                    strlen(ctx->msg->cpMsg));
+         if (bSuccess == NNTP_OK)
+             log2(ctx, NOTICE,  "%s %s", errorstring, "succeeded");
+         else if(bSuccess == NNTP_DEFER)
+             log2(ctx, WARNING, "%s %s", errorstring, "deferred");
+         else
+             log2(ctx, ERROR,   "%s %s", errorstring, "failed");
+     }
+ 
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   250 Requested mail action okay, completed
+      *                                                  451 Requested action aborted: local error in processing
+      *                                                  554 Transaction failed
+      *  RFC1893 2. Status Codes                         2.X.X   Success
+      *                                                  4.X.X   Persistent Transient Failure
+      *                                                  5.X.X   Permanent Failure
+      *  RFC1893 3.5 Network and Routing Status          X.0.0   Other undefined Status
+      *                                                  X.4.2   Bad connection
+      */
+     rcpt = NULL;
+     while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
+         if (ctx->option_operationmode == OPERATIONMODE_FAKE) {
+                     res.statuscode = ctx->option_operationmodefakestatus;
+                     res.dsncode    = ctx->option_operationmodefakedsn;
+                     str_format(errorstring, sizeof(errorstring),
+                                "NNTP noop fake return for %s", rcpt);
+         } else {
+             switch (bSuccess) {
+                 case NNTP_OK:
+                     str_format(errorstring, sizeof(errorstring),
+                                "Message accepted for delivery to %s", rcpt);
+                     res.statuscode = "250";
+                     res.dsncode    = "2.0.0";
+                     break;
+                 case NNTP_DEFER:
+                     str_format(errorstring, sizeof(errorstring),
+                                "Requested action aborted for %s, local error in processing.", rcpt);
+                     res.statuscode = "451";
+                     res.dsncode    = "4.4.2";
+                     break;
+                 default:
+                     str_format(errorstring, sizeof(errorstring),
+                                "Error sending article for %s.", rcpt);
+                     res.statuscode = "554";
+                     res.dsncode    = "5.4.2";
+                     break;
+             }
+         }
+         azErr = NULL;
+         asErr = 0;
+         argz_add(&azErr, &asErr, errorstring);
+         for (i = 0; i < ctx->nsc; i++) {
+             if (ctx->ns[i].rc != NNTP_OK) {
+                 str_format(errorstring, sizeof(errorstring), 
+                 "%s:%s returned %s\n"
+                 "%s:%s lastresp \"%s\"", 
+                 ctx->ns[i].h, ctx->ns[i].p, nntp_error(ctx->ns[i].rc), 
+                 ctx->ns[i].h, ctx->ns[i].p, nntp_lastresp(ctx->ns[i].nntp));
+                 argz_add(&azErr, &asErr, errorstring);
+             }
+         }
+         if (azErr != NULL) {
+             argz_stringify(azErr, asErr, '\n');
+             res.statusmsg  = azErr;
+             lmtp_response(lmtp, &res);
+             free(azErr);
+             azErr = NULL;
+             asErr = 0;
+         }
+         else {
+             res.statusmsg  = errorstring;
+             lmtp_response(lmtp, &res);
+         }
+     }
+ 
+     msg_destroy(ctx->msg);
+     ctx->msg = NULL;
+ 
+     return LMTP_OK;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_noop(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     lmtp_res_t res;
+     lmtp_rc_t rc = LMTP_OK;
+ 
+     log1(ctx, INFO, "LMTP service executing NOOP command < %s", req->msg);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   250 Requested mail action okay, completed
+      *  RFC1893 2. Status Codes                         2.X.X   Success
+      *  RFC1893 3.5 Network and Routing Status          X.0.0   Other undefined Status
+      */
+     res.statuscode = "250";
+     res.dsncode    = "2.0.0";
+     res.statusmsg  = "OK. Nice talking to you.";
+     lmtp_response(lmtp, &res);
+     return rc;
+ }
+ 
+ static lmtp_rc_t lmtp_cb_rset(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     lmtp_res_t res;
+     lmtp_rc_t rc = LMTP_OK;
+ 
+     log1(ctx, INFO, "LMTP service executing RSET command < %s", req->msg);
+ 
+     lmtp_gfs_rset(ctx);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   250 Requested mail action okay, completed
+      *  RFC1893 2. Status Codes                         2.X.X   Success
+      *  RFC1893 3.5 Network and Routing Status          X.0.0   Other undefined Status
+      */
+     res.statuscode = "250";
+     res.dsncode    = "2.0.0";
+     res.statusmsg  = "Reset state.";
+     lmtp_response(lmtp, &res);
+     return rc;
+ }
+ 
+ static void lmtp_gfs_rset(lmtp2nntp_t *ctx)
+ {
+     log0(ctx, TRACE, "LMTP service RSET command - graceful shutdown");
+ 
+     if (ctx->msg != NULL) {
+         msg_destroy(ctx->msg);
+         ctx->msg = NULL;
+     }
+ }
+ 
+ static lmtp_rc_t lmtp_cb_quit(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
+ {
+     lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
+     lmtp_res_t res;
+     lmtp_rc_t rc = LMTP_EOF;
+ 
+     log1(ctx, INFO, "LMTP service executing QUIT command < %s", req->msg);
+ 
+     lmtp_gfs_quit(ctx);
+ 
+     /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS   221 <domain> Service closing transmission channel
+      *  RFC1893 2. Status Codes                         2.X.X   Success
+      *  RFC1893 3.5 Network and Routing Status          X.0.0   Other undefined Status
+      */
+     res.statuscode = "221";
+     res.dsncode    = "2.0.0";
+     res.statusmsg  = "LMTP Service closing transmission channel.";
+     lmtp_response(lmtp, &res);
+     return rc;
+ }
+ 
+ static void lmtp_gfs_quit(lmtp2nntp_t *ctx)
+ {
+     log0(ctx, TRACE, "LMTP service QUIT command - graceful shutdown");
+ 
+     lmtp_gfs_rset(ctx);
+     resetsession(&ctx->session);
+ }


ossp-pkg/lmtp2nntp/lmtp2nntp_msg.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,460 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  msg.c: mail message manipulation library
+ */
+ 
+ #include <stdlib.h>
+ #include <stdio.h>
+ 
+ #include "lmtp2nntp_msg.h"
+ #include "lmtp2nntp_argz.h"
+ 
+ #include "str.h"
+ 
+ /* third party */
+ #include "l2.h"
+ 
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ 
+ msg_t *msg_create(void)
+ {
+     msg_t *msg;
+ 
+     if ((msg = (msg_t *)malloc(sizeof(msg_t))) == NULL)
+         return NULL;
+ 
+     msg->azEnvgroups = NULL;
+     msg->asEnvgroups = 0;
+     msg->cpMsg = NULL;
+     msg->azHeaders = NULL;
+     msg->asHeaders = 0;
+     msg->cpFid = NULL;
+     msg->cpBody = NULL;
+     msg->cpMsgid = NULL;
+     msg->mail_from = NULL;
+     msg->azRcpt = NULL;
+     msg->asRcpt = 0;
+     msg->azNewsgroups = NULL;
+     msg->asNewsgroups = 0;
+     msg->l2 = NULL; /* this is a copy only */
+ 
+     return msg;
+ }
+ 
+ void msg_destroy(msg_t *msg)
+ {
+     if (msg == NULL)
+         return;
+ 
+     if (msg->azEnvgroups != NULL)
+         free(msg->azEnvgroups);
+     if (msg->cpMsg != NULL)
+         free(msg->cpMsg);
+     if (msg->azHeaders != NULL)
+         free(msg->azHeaders);
+     if (msg->cpFid != NULL)
+         free(msg->cpFid);
+     if (msg->cpBody != NULL)
+         free(msg->cpBody);
+     if (msg->cpMsgid != NULL)
+         free(msg->cpMsgid);
+     if (msg->mail_from != NULL)
+         free(msg->mail_from);
+     if (msg->azRcpt != NULL)
+         free(msg->azRcpt);
+     if (msg->azNewsgroups != NULL)
+         free(msg->azNewsgroups);
+     msg->l2 = NULL; /* this is a copy only, the "parent" needs to clean this up */
+ 
+     free(msg);
+     return;
+ }
+ 
+ msg_rc_t msg_split(msg_t *msg)
+ {
+     char        *cpName;
+     char        *cpValue;
+     char        *cpRem;     /* Remainder */
+     char        *cp;
+     char        *cpHeaders;
+ 
+     /* INPUTS
+      *
+      * msg->cpMsg
+      * must contain the wholly RFC0822 formatted message with native
+      * (unescaped) dots at the beginning of a line, the 'From ' envelope,
+      * headers, double newline, body, NUL, no trailing dot;
+      *
+      * OUTPUTS
+      *
+      * msg->cpMsg
+      * free()d and set to NULL
+      * 
+      * msg->azHeaders, msg->asHeaders contains the headers in argz format, one
+      * logical NUL-terminated line per header which might be wrapped into
+      * multiple '\n'-ended physical lines. The "From " envelope, "Received:",
+      * "Path:", "To:" and "Cc:" headers are removed silently. The
+      * "Newsgroups:" and "Message-ID" headers are removed and their values are
+      * stored in separate structures (see below).
+      *
+      * msg->cpBody
+      * contains the unmodified body of the message, NUL-terminated, no
+      * trailing dot.
+      *
+      * msg->cpMsgid
+      * contains the message id including surrounding angle brackets.
+      *
+      * msg->azNewsgroups, asNewsgroups
+      * is a argz-type array of strings containing the Newsgroups based on the
+      * header information.
+      */
+ 
+     log0(msg, DEBUG, "split message into header and body");
+     if (str_parse(msg->cpMsg, "m/((?:.*?)\\n)\\n(.*)$/s", &cpHeaders, &msg->cpBody) <= 0)
+         return MSG_ERR_SPLITHEADBODY;
+ 
+     free(msg->cpMsg);
+     msg->cpMsg = NULL;
+ 
+     log0(msg, DEBUG, "replace envelope From w/o colon by X-F: pseudotag");
+     /* This eliminates the special case of having one header, which is really
+      * an embedded envelope, not ending with a colon while all others do.
+      * After splitting headers into name and value pairs this envelope ist
+      * stripped off.
+      */
+     if (strncasecmp(cpHeaders, "From", 4) == 0)
+         memcpy(cpHeaders, "X-F:", 4);
+ 
+     log0(msg, DEBUG, "unwrap header lines");
+     /* poor man's s///g simulator as current str library doesn't support global substitution */
+     while (str_parse(cpHeaders, "s/(.*?)\\n[ \\t]+(.*)/$1 $2/s", &cpRem) > 0) {
+         free(cpHeaders);
+         cpHeaders = cpRem;
+     }
+ 
+     log0(msg, DEBUG, "split header lines into names and values");
+     while (str_parse(cpHeaders, "m/^[> \\t]*([\\x21-\\x7e]+?:)[ \\t]*([^\\n]*?)[ \\t]*\\n(.*)/s", &cpName, &cpValue, &cpRem) > 0) {
+         free(cpHeaders);
+         cpHeaders = cpRem;
+         argz_add(&msg->azHeaders, &msg->asHeaders, cpName);
+         argz_add(&msg->azHeaders, &msg->asHeaders, cpValue);
+         free(cpName);
+         free(cpValue);
+     }
+ 
+     log0(msg, DEBUG, "check for headers we care about and do whatever neccessary");
+     msg->cpMsgid = NULL;
+     msg->azNewsgroups = NULL;
+     msg->asNewsgroups = 0;
+     cp = msg->azHeaders;
+     while (cp != NULL) {
+         log1(msg, DEBUG, "processing header \"%s\"", cp);
+         if (strcasecmp("X-F:", cp) == 0) {
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if (strcasecmp("Path:", cp) == 0) {
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if (strcasecmp("Received:", cp) == 0) {
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             if ((msg->cpFid == NULL) &&
+                 (str_parse(cp, "m/\\sid\\s+<?([\\w\\d]{1,30})/i", &msg->cpFid) > 0))
+                     log1(msg, DEBUG, "found foreign-ID \"%s\" for logging", msg->cpFid);
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if (strcasecmp("To:", cp) == 0) {
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if (strcasecmp("Cc:", cp) == 0) {
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if (strcasecmp("Message-ID:", cp) == 0) {
+             if (msg->cpMsgid != NULL)
+                 return MSG_ERR_SPLITIDMULTI;
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             if ((cp == NULL) || (strlen(cp) == 0))                         /* get  value */
+                 return MSG_ERR_SPLITIDEMPTY;
+             if ((msg->cpMsgid = strdup(cp)) == NULL)
+                 return MSG_ERR_MEM;
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if (strcasecmp("Newsgroups:", cp) == 0) {
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  name  */
+             if (argz_add(&msg->azNewsgroups, &msg->asNewsgroups, cp) != 0) /* get  value */
+                 return MSG_ERR_MEM;
+             argz_delete(&msg->azHeaders, &msg->asHeaders, cp);             /* del  value */
+             continue;
+         }
+         if ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) == NULL)  /* next value */
+             break;
+         if ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) == NULL)  /* next name  */
+             break;
+     }
+ 
+     log0(msg, DEBUG, "checking Message-ID");
+     if (msg->cpMsgid == NULL)
+         return MSG_ERR_SPLITIDNONE;
+ 
+     log0(msg, DEBUG, "checking Newsgroups");
+     if (msg->azNewsgroups != NULL) {
+         argz_stringify(msg->azNewsgroups, msg->asNewsgroups, ',');
+         if (argz_create_sep(msg->azNewsgroups, ',', &msg->azNewsgroups, &msg->asNewsgroups) != 0)
+             return MSG_ERR_MEM;
+     }
+ 
+     log0(msg, DEBUG, "adding mandatory Path: header");
+     argz_add(&msg->azHeaders, &msg->asHeaders, "Path:");
+     argz_add(&msg->azHeaders, &msg->asHeaders, "lmtp2nntp!not-for-mail");
+ 
+     log0(msg, DEBUG, "split complete");
+     return MSG_OK;
+ }
+ 
+ msg_rc_t msg_join(msg_t *msg)
+ {
+     char        *cp;
+     char        *cpRem;
+     char       **aHeaders;
+     int          i;
+     int          o;
+     char        *cpCut;
+     char        *cpWrap;
+     char         c;
+     char         cOld;
+     int          n;
+     char        *cpHeaders;
+     char        *azNewheaders;
+     size_t       asNewheaders;
+ 
+     log0(msg, DEBUG, "verify Newsgroups");
+     if (msg->azNewsgroups == NULL)
+         return MSG_ERR_JOINGROUPNONE;
+     argz_stringify(msg->azNewsgroups, msg->asNewsgroups, ',');
+     if (strlen(msg->azNewsgroups) == 0)
+         return MSG_ERR_JOINGROUPEMPTY;
+     argz_add(&msg->azHeaders, &msg->asHeaders, "Newsgroups:");
+     argz_add(&msg->azHeaders, &msg->asHeaders, msg->azNewsgroups);
+ 
+     log0(msg, DEBUG, "verify Message-ID");
+     if (msg->cpMsgid == NULL)
+         return MSG_ERR_JOINIDNONE;
+     if (strlen(msg->cpMsgid) == 0)
+         return MSG_ERR_JOINIDEMPTY;
+     argz_add(&msg->azHeaders, &msg->asHeaders, "Message-ID:");
+     argz_add(&msg->azHeaders, &msg->asHeaders, msg->cpMsgid);
+ 
+     log0(msg, DEBUG, "merge name/value pairs into single string");
+     argz_add(&msg->azHeaders, &msg->asHeaders, ""); /* append empty string */
+     if ((aHeaders = (char **)malloc((argz_count(msg->azHeaders, msg->asHeaders) + 1) * sizeof(char *))) == NULL)
+         return MSG_ERR_MEM;
+     argz_extract(msg->azHeaders, msg->asHeaders, aHeaders);
+     /* replace the trailing NUL, which is *(cp-1) of the predecessor, with a
+      * space at every second string. Break action when terminating NULL string
+      * is detected */
+     i=0;
+     while(1) {
+         if ((cp = aHeaders[++i]) == NULL)
+             break;
+         *(cp-1) = ' ';
+         if ((cp = aHeaders[++i]) == NULL)
+             break;
+     }
+     free(aHeaders);
+ 
+     log0(msg, DEBUG, "fold headers");
+     /* A logical line is split into one or more physical '\n'-terminated
+      * lines. The physical line is never longer than WRAPAT characters. This
+      * includes the folded data and the header name + colon + space for the
+      * first line and WRAPUSING string prefix for all other lines. Leading and
+      * trailing blanks of folded lines are removed while blanks inside the
+      * line are preserved.  The header is never left alone in a physical line.
+      * Fragments exceeding WRAPAT characters without having a blank as a
+      * splitting point are forcibly cut at a non-blank character.
+      */
+     azNewheaders = NULL;
+     asNewheaders = 0;
+     cp = NULL;
+     while ((cp = argz_next(msg->azHeaders, msg->asHeaders, cp)) != NULL) {
+         if (strlen(cp) > WRAPAT) {
+             cpRem = cp;
+             cpWrap = NULL;
+             for (o = 0; (cpRem[o] != ':') && (cpRem[o] != NUL); o++); /* offset name so at least one char of value remains in first line */
+             o += 2; /* skip ": " */
+             while ((strlen(cpRem) + (cpWrap == NULL ? 0 : strlen(WRAPUSING))) > WRAPAT) {
+                 for (i = WRAPAT - 1 - (cpWrap == NULL ? 0 :
+                     strlen(WRAPUSING)); (i >= o) && !isspace((int)cpRem[i]); i--);
+                 if (i < o)
+                     i = WRAPAT - 1 - (cpWrap == NULL ? 0 : strlen(WRAPUSING) - 1); /* sorry, forced cut at non-blank */
+                 cpCut = cpRem;
+                 cpRem += i;
+                 for (; (isspace((int)*cpRem) && (*cpRem != NUL)); cpRem++); /* skip next lines leading blanks */
+                 for (; (i >= o) && isspace((int)cpCut[i-1]); i--); /* chop off this lines trailing blanks */
+                 if (i >= o) { /* only keep line fragment if some non-blanks inside */
+                     if (cpWrap == NULL) {
+                         if ((cpWrap = (char *)malloc(i+strlen(WRAPUSING)+1)) == NULL)
+                             return MSG_ERR_MEM;
+                         *cpWrap = NUL;
+                         o = 1;
+                     }
+                     else {
+                         if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+i+strlen(WRAPUSING)+1)) == NULL)
+                             return MSG_ERR_MEM;
+                         strcat(cpWrap, WRAPUSING);
+                     }
+                     strncat(cpWrap, cpCut, i);
+                 }
+             }
+             if (strlen(cpRem) > 0) {
+                 if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+strlen(cpRem)+strlen(WRAPUSING)+1)) == NULL)
+                         return MSG_ERR_MEM;
+                 strcat(cpWrap, WRAPUSING);
+                 strcat(cpWrap, cpRem);
+             }
+             argz_add(&azNewheaders, &asNewheaders, cpWrap);
+             log2(msg, DEBUG, "a folded header \"%{text}D\"", cpWrap, strlen(cpWrap));
+             free(cpWrap);
+         }
+         else {
+             argz_add(&azNewheaders, &asNewheaders, cp);
+             log2(msg, DEBUG, "verbatim header \"%{text}D\"", cp, strlen(cp));
+         }
+     }
+     free(msg->azHeaders);
+     msg->azHeaders = azNewheaders;
+     msg->asHeaders = asNewheaders;
+ 
+     log0(msg, DEBUG, "strigify headers");
+     argz_stringify(msg->azHeaders, msg->asHeaders, '\n');
+     cpHeaders = msg->azHeaders;
+ 
+     /********************************************************************
+      * header + CRLF + body + '.' + CRLF + NUL, replacing NL with CRLF *
+      ********************************************************************/
+ 
+     log0(msg, DEBUG, "assemble header and body");
+     n = 0;
+     /* count size of headers, reserve space for NL to CRLF conversion */
+     for (i = 0; ((c = cpHeaders[i]) != NUL); i++) {
+         if (c == '\n')
+             n++;
+         n++;
+     }
+     /* if headers don't end with NL, reserve space for CRLF */
+     if (i >= 0 && cpHeaders[i - 1] != '\n')
+         n+=2;
+     /* reserve space for CRLF between headers and body */
+     n+=2;
+     /* count size of body, reserve space for NL-DOT escape and NL to CRLF conversion */
+     cOld = '\n';
+     for (i = 0; ((c = msg->cpBody[i]) != NUL); i++) {
+         if (c == '\n')
+             n++;
+         if (c == '.' && cOld == '\n')
+             n++;
+         n++;
+         cOld = c;
+     }
+     /* if body doesn't end with NL, reserve space for CRLF */
+     if (i >= 0 && msg->cpBody[i - 1] != '\n')
+         n+=2;
+     /* reserve space for terminating '.'-CRLF-NUL at the end of the message */
+     n+=4;
+ 
+     if ((msg->cpMsg = (char *)malloc(n)) == NULL)
+         return MSG_ERR_MEM;
+ 
+     n = 0;
+     /* copy headers, do NL to CRLF conversion */
+     for (i = 0; ((c = cpHeaders[i]) != NUL); i++) {
+         if (c == '\n')
+             msg->cpMsg[n++] = '\r';
+         msg->cpMsg[n++] = c;
+     }
+     /* if headers don't end with NL, append CRLF */
+     if (i >= 0 && cpHeaders[i - 1] != '\n') {
+         msg->cpMsg[n++] = '\r';
+         msg->cpMsg[n++] = '\n';
+     }
+     /* add CRLF between headers and body */
+     msg->cpMsg[n++] = '\r';
+     msg->cpMsg[n++] = '\n';
+     /* copy body, do NL-DOT escape and NL to CRLF conversion */
+     cOld = '\n';
+     for (i = 0; ((c = msg->cpBody[i]) != NUL); i++) {
+         if (c == '\n')
+             msg->cpMsg[n++] = '\r';
+         if (c == '.' && cOld == '\n')
+             msg->cpMsg[n++] = '.';
+         msg->cpMsg[n++] = c;
+         cOld = c;
+     }
+     /* if body doesn't end with NL, append CRLF */
+     if (i >= 0 && msg->cpBody[i - 1] != '\n') {
+         msg->cpMsg[n++] = '\r';
+         msg->cpMsg[n++] = '\n';
+     }
+     /* add terminating '.'-CRLF-NUL at the end of the message */
+     msg->cpMsg[n++] =  '.';
+     msg->cpMsg[n++] = '\r';
+     msg->cpMsg[n++] = '\n';
+     msg->cpMsg[n]   = NUL;
+ 
+     log0(msg, DEBUG, "join complete");
+     return MSG_OK;
+ }
+ 
+ char *msg_error(msg_rc_t rc)
+ {
+     char *str;
+                                               str = "MSG: no description";
+     if      (rc == MSG_OK                   ) str = "MSG: no error";
+     else if (rc == MSG_ERR_MEM              ) str = "MSG: memory";
+     else if (rc == MSG_ERR_SPLITHEADBODY    ) str = "MSG: split into header and body failed";
+     else if (rc == MSG_ERR_SPLITLEN         ) str = "MSG: header is too short";
+     else if (rc == MSG_ERR_SPLITMISSINGFROM ) str = "MSG: header is missing 'From ' envelope";
+     else if (rc == MSG_ERR_SPLITIDNONE      ) str = "MSG: header is missing 'Message-ID'";
+     else if (rc == MSG_ERR_SPLITIDEMPTY     ) str = "MSG: header has empty 'Message-ID'";
+     else if (rc == MSG_ERR_SPLITIDMULTI     ) str = "MSG: header has multiple 'Message-ID's";
+     else if (rc == MSG_ERR_JOINGROUPNONE    ) str = "MSG: join with no 'Newsgroup'";
+     else if (rc == MSG_ERR_JOINGROUPEMPTY   ) str = "MSG: join with empty 'Newsgroup'";
+     else if (rc == MSG_ERR_JOINIDNONE       ) str = "MSG: join with no 'Message-ID'";
+     else if (rc == MSG_ERR_JOINIDEMPTY      ) str = "MSG: join with empty 'Message-ID'";
+     return str;
+ }
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_msg.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,79 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  msg.c: mail message manipulation library (API)
+ */
+ 
+ #ifndef __MSG_H__
+ #define __MSG_H__
+ 
+ #include <sys/types.h>
+ 
+ #include "l2.h"
+ 
+ #include "lmtp2nntp_global.h"
+ 
+ typedef struct {
+     char   *azEnvgroups;  /* Groups according to Envelope in GROUPMODE_ENVELOPE */
+     size_t  asEnvgroups;
+     char   *cpMsg;        /* the wholly message to be received by DATA command */
+     char   *azHeaders;    /* header part of message above */
+     size_t  asHeaders;
+     char   *cpFid;        /* foreign (aka sendmail queue) id from parsing headers */
+     char   *cpBody;       /* body part of message above */
+     char   *cpMsgid;
+     char   *mail_from;
+     char   *azRcpt;
+     size_t  asRcpt;
+     char   *azNewsgroups;
+     size_t  asNewsgroups;
+     l2_channel_t *l2;
+ } msg_t;
+ 
+ #define WRAPAT 120          /* join wraps header lines when exceeding this value */
+ #define WRAPUSING "\n    "  /* join inserts this value when wrapping lines       */
+ 
+ typedef enum {
+     MSG_OK,
+     MSG_ERR_MEM,
+     MSG_ERR_SPLITHEADBODY,
+     MSG_ERR_SPLITLEN,
+     MSG_ERR_SPLITMISSINGFROM,
+     MSG_ERR_SPLITIDNONE,
+     MSG_ERR_SPLITIDEMPTY,
+     MSG_ERR_SPLITIDMULTI,
+     MSG_ERR_JOINGROUPNONE,
+     MSG_ERR_JOINGROUPEMPTY,
+     MSG_ERR_JOINIDNONE,
+     MSG_ERR_JOINIDEMPTY,
+     MSG_ERR_ARG
+ } msg_rc_t;
+ 
+ msg_t    *msg_create(void);
+ msg_rc_t  msg_split(msg_t *);
+ msg_rc_t  msg_join(msg_t *);
+ void      msg_destroy(msg_t *);
+ char     *msg_error(msg_rc_t);
+ 
+ #endif /*  __MSG_H__ */
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_nntp.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,441 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  nntp.c: Network News Transfer Protocol (NNTP) client library
+ */
+ 
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <ctype.h>
+ #include <errno.h>
+ #include <sys/types.h>
+ #include <sys/time.h>
+ 
+ #include "lmtp2nntp_nntp.h"
+ 
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ 
+ #ifndef FALSE
+ #define FALSE (1 != 1)
+ #endif
+ #ifndef TRUE
+ #define TRUE (!FALSE)
+ #endif
+ 
+ #ifndef NUL
+ #define NUL '\0'
+ #endif
+ 
+ /* maximum NNTP protocol line length */
+ #define NNTP_LINE_MAXLEN 1024
+ 
+ typedef struct {
+     int   rl_cnt;
+     char *rl_bufptr;
+     char  rl_buf[NNTP_LINE_MAXLEN];
+ } nntp_readline_t;
+ 
+ struct nntp_st {
+     nntp_io_t       io;
+     nntp_readline_t rl; 
+     struct timeval  tv;
+     int             kludgeinn441dup;
+     char           *lastresp;
+ };
+ 
+ ssize_t nntp_fd_read(void *_ctx, void *buf, size_t buflen)
+ {
+     nntp_fd_t *ctx = (nntp_fd_t *)_ctx;
+     return read(ctx->fd, buf, buflen);
+ }
+ 
+ ssize_t nntp_fd_write(void *_ctx, const void *buf, size_t buflen)
+ {
+     nntp_fd_t *ctx = (nntp_fd_t *)_ctx;
+     return write(ctx->fd, buf, buflen);
+ }
+ 
+ nntp_t *nntp_create(nntp_io_t *io)
+ {
+     nntp_t *nntp;
+ 
+     if ((nntp = (nntp_t *)malloc(sizeof(nntp_t))) == NULL) 
+         return NULL;
+ 
+     if (io == NULL) 
+         return NULL;
+     nntp->io.ctx    = io->ctx;
+     nntp->io.read   = io->read;
+     nntp->io.write  = io->write;
+     nntp->rl.rl_cnt = 0;
+     nntp->rl.rl_bufptr = NULL;
+     nntp->rl.rl_buf[0] = NUL;
+     nntp->kludgeinn441dup = FALSE;
+     nntp->lastresp = NULL;
+ 
+     return nntp;
+ }
+ 
+ nntp_rc_t nntp_init(nntp_t *nntp)
+ {
+     nntp_rc_t rc;
+     char line[NNTP_LINE_MAXLEN];
+   
+     /* RFC0977 2.4.3. General Responses
+      * In general, 1xx codes may be ignored or displayed as desired;  code 200
+      * or 201 is sent upon initial connection to the NNTP server depending
+      * upon posting permission; code 400 will be sent when the NNTP server
+      * discontinues service (by operator request, for example); and 5xx codes
+      * indicate that the command could not be performed for some unusual
+      * reason.
+      *
+      * 1xx text
+      * 200 server ready - posting allowed
+      * 201 server ready - no posting allowed
+      * 400 service discontinued
+      * 500 command not recognized
+      * 501 command syntax error
+      * 502 access restriction or permission denied
+      * 503 program fault - command not performed
+      */
+ 
+     do {
+         if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) {
+             return rc;
+             }
+     } while (line[0] == '1');
+ 
+     /* prepare for the INN kludge using 441 returns for other things but
+      * "Duplicate".  Typical welcome string is "200 host InterNetNews NNRP
+      * server INN 2.3.2 ready (posting ok)."
+      */
+     if (strncmp(line, "200", 3) == 0 && strstr(line, " INN ") != NULL) {
+         nntp->kludgeinn441dup = TRUE;
+     }
+     else {
+         nntp->kludgeinn441dup = FALSE;
+     }
+ 
+     if (strncmp(line, "200", 3) == 0)
+         return NNTP_OK;
+ 
+     return NNTP_ERR_INIT;
+ }
+ 
+ void nntp_destroy(nntp_t *nntp)
+ {
+     if (nntp != NULL) {
+         if (nntp->lastresp != NULL)
+             free(nntp->lastresp);
+         free(nntp);
+     }
+     return;
+ }
+ 
+ char *nntp_lastresp(nntp_t *nntp)
+ {
+     if (nntp == NULL)
+         return "";
+     if(nntp->lastresp == NULL)
+         return "";
+     return nntp->lastresp;
+ }
+ 
+ nntp_rc_t nntp_readline(nntp_t *nntp, char *buf, size_t buflen)
+ {
+     /* read a line (characters until NL) from input stream */
+     size_t n;
+     char c;
+     nntp_readline_t *rl = &nntp->rl;
+ 
+     if (nntp == NULL)
+         return NNTP_ERR_ARG;
+     for (n = 0; n < buflen-1;) {
+ 
+         /* fetch one character (but read more) */
+         if (rl->rl_cnt <= 0) {
+             do {
+                 rl->rl_cnt = nntp->io.read(nntp->io.ctx, rl->rl_buf, NNTP_LINE_MAXLEN);
+             } while (rl->rl_cnt == -1 && errno == EINTR);
+             if (rl->rl_cnt == -1)
+                 return NNTP_ERR_SYSTEM;
+             if (rl->rl_cnt == 0)
+                 return NNTP_EOF;
+             rl->rl_bufptr = rl->rl_buf;
+         }
+ 
+         /* act on fetched character */
+         rl->rl_cnt--;
+         c = *rl->rl_bufptr++;
+         if (c == '\r')
+             continue;       /* skip copying CR */
+         if (c == '\n')
+             break;          /* end of line */
+         buf[n++] = c;       /* output char into given buffer */
+ 
+     }
+     buf[n] = NUL;          /* string termination */
+     if (n == (buflen-1)) 
+         return NNTP_ERR_OVERFLOW;
+     return NNTP_OK;
+ }
+ 
+ nntp_rc_t nntp_writeline(nntp_t *nntp, char *buf)
+ {
+     char tmp[NNTP_LINE_MAXLEN];
+ 
+     if (nntp == NULL)
+         return NNTP_ERR_ARG;
+     strncpy(tmp, buf, NNTP_LINE_MAXLEN-3);
+     strcat(tmp, "\r\n");
+     if (nntp->io.write(nntp->io.ctx, tmp, strlen(tmp)) < 0)
+         return NNTP_ERR_SYSTEM;
+     return NNTP_OK;
+ }
+ 
+ nntp_rc_t nntp_post(nntp_t *nntp, msg_t *msg)
+ {
+     nntp_rc_t rc = NNTP_OK;
+     char line[NNTP_LINE_MAXLEN];
+ 
+     /*  RFC2980
+      *   
+      *  2.3 MODE READER
+      *  MODE READER is used by the client to indicate to the server that it is
+      *  a news reading client.  Some implementations make use of this
+      *  information to reconfigure themselves for better performance in
+      *  responding to news reader commands.  This command can be contrasted
+      *  with the SLAVE command in RFC0977, which was not widely implemented.
+      *  MODE READER was first available in INN.
+      *  
+      *  2.3.1 Responses
+      *  200 Hello, you can post
+      *  201 Hello, you can't post
+      *  
+      *  Research:
+      *  
+      *  < 200 dev16 InterNetNews server INN 2.3.2 ready
+      *  > POST
+      *  < 500 "post" not implemented; try "help".
+      *  > MODE READER
+      *  < 200 dev16 InterNetNews NNRP server INN 2.3.2 ready (posting ok).
+      *  > POST
+      *  < 340 Ok, recommended ID <...>
+      *  
+      *  In other words, INN *requires* the use of "MODE READER". This has been
+      *  verified when INN is configured for expecting both news readers and
+      *  feeders from a given source address. When INN is configured to expect
+      *  readers only for a given source address the use of "MODE READER" is
+      *  optional.
+      */
+     *line = NUL;
+     strcat(line, "MODE READER");
+     if ((rc = nntp_writeline(nntp, line)) != NNTP_OK)
+         return rc;
+     if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK)
+         return rc;
+ 
+     /*  A 200 response means posting ok, 201 means posting not allowed. We
+      *  don't care about 5xx errors, they simply mean the server doesn't know
+      *  about the RFC2980 "MODE READER" command. But any other response is not
+      *  expected and we treat this as an error.
+      */
+     if (strncmp(line, "201", 3) == 0)
+         CU(NNTP_ERR_DELIVERY);
+     if (   strncmp(line, "200", 3) != 0
+         && strncmp(line, "5"  , 1) != 0
+           )
+         CU(NNTP_ERR_DELIVERY);
+ 
+     /*  check if this server already knows that artice
+      *  > STAT <message-id>
+      *  < 223 yes, article already known
+      *  < 430 no, i don't know the article, yet
+      */
+     *line = NUL;
+     strcat(line, "STAT ");
+     strcat(line, msg->cpMsgid);
+     if ((rc = nntp_writeline(nntp, line)) != NNTP_OK)
+         return rc;
+     if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK)
+         return rc;
+     if (strncmp(line, "223", 3) == 0)
+         return NNTP_OK;
+     if (strncmp(line, "430", 3) != 0)
+         CU(NNTP_ERR_DELIVERY);
+ 
+     /*  post the article
+      *  > POST
+      *  < 340 gimme that thing
+      *  > From: ...
+      *  > Subject: ...
+      *  > Newsgroups: ...
+      *  > Message-ID: <...>
+      *  > [additional headers]
+      *  > 
+      *  > [body with dots escaped]
+      *  > .
+      *  < 240 ok, thank you
+      *  < 441 duplicate (ok for us)
+      *  
+      *  Research:
+      *  
+      *  < 200 dev16 InterNetNews server INN 2.3.2 ready
+      *  > POST
+      *  [...]
+      *  240 Article posted <...>
+      *  441 435 Duplicate
+      *  441 437 Too old
+      *  441 Duplicate "Newsgroups" header
+      *  441 Required "Subject" header is missing
+      *  441 Obsolete "Received" header
+      *  441 Line # too long
+      *  
+      *  In other words, INN uses 441 for other status messages as well.
+      */
+     *line = NUL;
+     strcat(line, "POST");
+     if ((rc = nntp_writeline(nntp, line)) != NNTP_OK)
+         return rc;
+     if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK)
+         return rc;
+     if (strncmp(line, "340", 3) != 0)
+         CU(NNTP_ERR_DELIVERY);
+ 
+     do {
+         rc = nntp->io.write(nntp->io.ctx, msg->cpMsg, strlen(msg->cpMsg));
+     } while (rc == -1 && errno == EINTR);
+     if (rc == -1)
+         return NNTP_ERR_SYSTEM;
+ 
+     if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK)
+         return rc;
+ 
+     if (strncmp(line, "240", 3) == 0)
+         return NNTP_OK;
+ 
+     if (nntp->kludgeinn441dup) {
+         if (strncmp(line, "441 435", 7) == 0)
+             return NNTP_OK;
+     }
+     else {
+         if (strncmp(line, "441", 3) == 0)
+             return NNTP_OK;
+     }
+ 
+     CU(NNTP_ERR_DELIVERY);
+ 
+     CUS:
+     nntp->lastresp = strdup(line);
+     return rc;
+ }
+ 
+ nntp_rc_t nntp_feed(nntp_t *nntp, msg_t *msg)
+ {
+     nntp_rc_t rc = NNTP_OK;
+     char line[NNTP_LINE_MAXLEN];
+ 
+     /*  RFC0977 3.4. The IHAVE command, 3.4.2. Responses
+      *  < 235 article transferred ok
+      *  < 335 send article to be transferred.  End with <CR-LF>.<CR-LF>
+      *  < 435 article not wanted - do not send it
+      *  < 436 transfer failed - try again later
+      *  < 437 article rejected - do not try again
+      *  
+      *  Research:
+      *  < 200 dev16 InterNetNews server INN 2.3.2 ready
+      *  > IHAVE <messageid>
+      *  < 335 [no text; this number means positive response]
+      *  < 435 Duplicate
+      *  < 437 Missing "Path" header
+      *  < 437 Unwanted newsgroup "quux"
+      *  < 480 Transfer permission denied
+      */
+     *line = NUL;
+     strcat(line, "IHAVE ");
+     strcat(line, msg->cpMsgid);
+     if ((rc = nntp_writeline(nntp, line)) != NNTP_OK)
+         return rc;
+ 
+     if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK)
+         return rc;
+ 
+     if (strncmp(line, "435", 3) == 0)
+         return NNTP_OK;
+ 
+     if (strncmp(line, "436", 3) == 0)
+         return NNTP_DEFER;
+ 
+     if (   (strncmp(line, "437", 3) == 0)
+         || (strncmp(line, "480", 3) == 0))
+         CU(NNTP_ERR_DELIVERY);
+ 
+     if (strncmp(line, "335", 3) != 0)
+         CU(NNTP_ERR_DELIVERY);
+ 
+     do {
+         rc = nntp->io.write(nntp->io.ctx, msg->cpMsg, strlen(msg->cpMsg));
+     } while (rc == -1 && errno == EINTR);
+     if (rc == -1)
+         return NNTP_ERR_SYSTEM;
+ 
+     if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK)
+         return rc;
+ 
+     if (strncmp(line, "235", 3) == 0)
+         return NNTP_OK;
+ 
+     if (strncmp(line, "436", 3) == 0)
+         return NNTP_DEFER;
+ 
+     CU(NNTP_ERR_DELIVERY);
+ 
+     CUS:
+     nntp->lastresp = strdup(line);
+     return rc;
+ }
+ 
+ char *nntp_error(nntp_rc_t rc)
+ {
+     char *str;
+                                       str = "NNTP: errorcode has no description";
+     if      (rc == NNTP_OK          ) str = "NNTP: no error";
+     else if (rc == NNTP_EOF         ) str = "NNTP: end of file";
+     else if (rc == NNTP_DEFER       ) str = "NNTP: transmission deferred";
+     else if (rc == NNTP_FAKE        ) str = "NNTP: fake status not real";
+     else if (rc == NNTP_ERR_SYSTEM  ) str = "NNTP: see errno";
+     else if (rc == NNTP_ERR_ARG     ) str = "NNTP: invalid argument";
+     else if (rc == NNTP_ERR_OVERFLOW) str = "NNTP: buffer overflow";
+     else if (rc == NNTP_ERR_DELIVERY) str = "NNTP: cannot deliver message";
+     else if (rc == NNTP_ERR_INIT    ) str = "NNTP: initialization failed";
+     else if (rc == NNTP_ERR_UNKNOWN ) str = "NNTP: unknown error";
+     return str;
+ }
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_nntp.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,76 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  nntp.h: Network News Transfer Protocol (NNTP) client library (API)
+ */
+ 
+ #ifndef __NNTP_H__
+ #define __NNTP_H__
+ 
+ #include <sys/types.h>
+ #include <sys/uio.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ 
+ #include "lmtp2nntp_msg.h"
+ 
+ struct nntp_st;
+ typedef struct nntp_st nntp_t;
+ 
+ typedef struct {
+     void    *ctx;
+     ssize_t (*read)(void *, void *, size_t);
+     ssize_t (*write)(void *, const void *, size_t);
+ } nntp_io_t;
+ 
+ typedef enum {
+     NNTP_OK,
+     NNTP_EOF,
+     NNTP_DEFER,
+     NNTP_FAKE,
+     NNTP_ERR_SYSTEM,
+     NNTP_ERR_ARG,
+     NNTP_ERR_OVERFLOW,
+     NNTP_ERR_DELIVERY,
+     NNTP_ERR_INIT,
+     NNTP_ERR_UNKNOWN
+ } nntp_rc_t;
+ 
+ typedef struct {
+     int fd;
+ } nntp_fd_t;
+ 
+ nntp_t     *nntp_create   (nntp_io_t *);
+ nntp_rc_t   nntp_init     (nntp_t *);
+ void        nntp_destroy  (nntp_t *);
+ nntp_rc_t   nntp_readline (nntp_t *, char *, size_t);
+ nntp_rc_t   nntp_writeline(nntp_t *, char *);
+ nntp_rc_t   nntp_post     (nntp_t *, msg_t *msg);
+ nntp_rc_t   nntp_feed     (nntp_t *, msg_t *msg);
+ char       *nntp_lastresp (nntp_t *nntp);
+ char       *nntp_error    (nntp_rc_t);
+ ssize_t     nntp_fd_read  (void *, void *, size_t);
+ ssize_t     nntp_fd_write (void *, const void *, size_t);
+ 
+ #endif /* __NNTP_H__ */
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_shpat.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,286 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  shpat_match.c: Shell Pattern Matching library
+ */
+ 
+ /*
+  * Copyright (c) 1989, 1993, 1994
+  *      The Regents of the University of California.  All rights reserved.
+  *
+  * This code is derived from software contributed to Berkeley by
+  * Guido van Rossum.
+  *
+  * 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 acknowledgement:
+  *      This product includes software developed by the University of
+  *      California, Berkeley and its contributors.
+  * 4. Neither the name of the University nor the names of its contributors
+  *    may be used to endorse or promote products derived from this software
+  *    without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS 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.
+  */
+ 
+ /*
+  * Function shpat_match() as specified in POSIX 1003.2-1992, section B.6.
+  * Compares a filename or pathname to a pattern.
+  */
+ 
+ #include <ctype.h>
+ #include <string.h>
+ #include <stdio.h>
+ 
+ #ifdef HAVE_CONFIG_H
+ #include "config.h"
+ #endif
+ #if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ 
+ #include "lmtp2nntp_shpat.h"
+ 
+ #define EOS '\0'
+ 
+ #define RANGE_MATCH     1
+ #define RANGE_NOMATCH   0
+ #define RANGE_ERROR     (-1)
+ 
+ static int rangematch(const char *, char, int, char **);
+ 
+ int shpat_match(const char *pattern, const char *string, int flags)
+ {
+     const char *stringstart;
+     char *newp;
+     char c, test;
+ 
+     for (stringstart = string; ; ) {
+         switch (c = *pattern++) {
+             case EOS:
+                 if ((flags & SHPAT_M_LEADING_DIR) && *string == '/')
+                     return (0);
+                 return (*string == EOS ? 0 : SHPAT_M_NOMATCH);
+             case '?':
+                 if (*string == EOS)
+                     return (SHPAT_M_NOMATCH);
+                 if (*string == '/' && (flags & SHPAT_M_PATHNAME))
+                     return (SHPAT_M_NOMATCH);
+                 if (*string == '.' && (flags & SHPAT_M_PERIOD) &&
+                     (string == stringstart ||
+                      ((flags & SHPAT_M_PATHNAME) && *(string - 1) == '/')))
+                     return (SHPAT_M_NOMATCH);
+                 ++string;
+                 break;
+             case '*':
+                 c = *pattern;
+                 /* Collapse multiple stars. */
+                 while (c == '*')
+                     c = *++pattern;
+ 
+                 if (*string == '.' && (flags & SHPAT_M_PERIOD) &&
+                     (string == stringstart ||
+                      ((flags & SHPAT_M_PATHNAME) && *(string - 1) == '/')))
+                     return (SHPAT_M_NOMATCH);
+ 
+                 /* Optimize for pattern with * at end or before /. */
+                 if (c == EOS)
+                     if (flags & SHPAT_M_PATHNAME)
+                         return ((flags & SHPAT_M_LEADING_DIR) ||
+                                 strchr(string, '/') == NULL ?
+                                 0 : SHPAT_M_NOMATCH);
+                     else
+                         return (0);
+                 else if (c == '/' && flags & SHPAT_M_PATHNAME) {
+                     if ((string = strchr(string, '/')) == NULL)
+                         return (SHPAT_M_NOMATCH);
+                     break;
+                 }
+ 
+                 /* General case, use recursion. */
+                 while ((test = *string) != EOS) {
+                     if (!shpat_match(pattern, string, flags & ~SHPAT_M_PERIOD))
+                         return (0);
+                     if (test == '/' && flags & SHPAT_M_PATHNAME)
+                         break;
+                     ++string;
+                 }
+                 return (SHPAT_M_NOMATCH);
+             case '[':
+                 if (*string == EOS)
+                     return (SHPAT_M_NOMATCH);
+                 if (*string == '/' && (flags & SHPAT_M_PATHNAME))
+                     return (SHPAT_M_NOMATCH);
+                 if (*string == '.' && (flags & SHPAT_M_PERIOD) &&
+                     (string == stringstart ||
+                      ((flags & SHPAT_M_PATHNAME) && *(string - 1) == '/')))
+                     return (SHPAT_M_NOMATCH);
+ 
+                 switch (rangematch(pattern, *string, flags, &newp)) {
+                     case RANGE_ERROR:
+                         goto norm;
+                     case RANGE_MATCH:
+                         pattern = newp;
+                         break;
+                     case RANGE_NOMATCH:
+                         return (SHPAT_M_NOMATCH);
+                 }
+                 ++string;
+                 break;
+             case '\\':
+                 if (!(flags & SHPAT_M_NOESCAPE)) {
+                     if ((c = *pattern++) == EOS) {
+                         c = '\\';
+                         --pattern;
+                     }
+                 }
+                 /* FALLTHROUGH */
+             default:
+               norm:
+                 if (c == *string)
+                     ;
+                 else if ((flags & SHPAT_M_CASEFOLD) &&
+                          (tolower((unsigned char) c) ==
+                           tolower((unsigned char) *string)))
+                     ;
+                 else
+                     return (SHPAT_M_NOMATCH);
+                 string++;
+                 break;
+         }
+     }
+     /* NOTREACHED */
+ }
+ 
+ static int rangematch(
+      const char *pattern,
+      char test,
+      int flags,
+      char **newp)
+ {
+     int negate, ok;
+     char c, c2;
+ 
+     /*
+      * A bracket expression starting with an unquoted circumflex
+      * character produces unspecified results (IEEE 1003.2-1992,
+      * 3.13.2).  This implementation treats it like '!', for
+      * consistency with the regular expression syntax.
+      * J.T. Conklin (conklin@ngai.kaleida.com)
+      */
+     if ((negate = (*pattern == '!' || *pattern == '^')))
+         ++pattern;
+ 
+     if (flags & SHPAT_M_CASEFOLD)
+         test = tolower((unsigned char) test);
+ 
+     /*
+      * A right bracket shall lose its special meaning and represent
+      * itself in a bracket expression if it occurs first in the list.
+      * -- POSIX.2 2.8.3.2
+      */
+     ok = 0;
+     c = *pattern++;
+     do {
+         if (c == '\\' && !(flags & SHPAT_M_NOESCAPE))
+             c = *pattern++;
+         if (c == EOS)
+             return (RANGE_ERROR);
+ 
+         if (c == '/' && (flags & SHPAT_M_PATHNAME))
+             return (RANGE_NOMATCH);
+ 
+         if (flags & SHPAT_M_CASEFOLD)
+             c = tolower((unsigned char) c);
+ 
+         if (*pattern == '-'
+             && (c2 = *(pattern + 1)) != EOS && c2 != ']') {
+             pattern += 2;
+             if (c2 == '\\' && !(flags & SHPAT_M_NOESCAPE))
+                 c2 = *pattern++;
+             if (c2 == EOS)
+                 return (RANGE_ERROR);
+ 
+             if (flags & SHPAT_M_CASEFOLD)
+                 c2 = tolower((unsigned char) c2);
+ 
+             if (c <= test && test <= c2)
+                 ok = 1;
+         }
+         else if (c == test)
+             ok = 1;
+     } while ((c = *pattern++) != ']');
+ 
+     *newp = (char *) pattern;
+     return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
+ }
+ 
+ /* 
+  * This function returns non-zero if pattern 
+  * contains any glob chars. Borrowed from Apache.
+  */
+ int shpat_match_hasglobchar(const char *pattern)
+ {
+     int nesting;
+ 
+     nesting = 0;
+     while (*pattern) {
+         switch (*pattern) {
+             case '?':
+             case '*':
+                 return 1;
+             case '\\':
+                 if (*pattern++ == EOS)
+                     return 0;
+                 break;
+             case '[':
+                 /* '[' is only a glob if it has a matching ']' */
+                 nesting++;
+                 break;
+             case ']':
+                 if (nesting)
+                     return 1;
+                 break;
+         }
+         pattern++;
+     }
+     return 0;
+ }
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_shpat.h -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,84 ----
+ /*
+ **  Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ **  Copyright (c) 2001 Cable & Wireless Deutschland <http://www.cw.com/de/>
+ **
+ **  This file is part of OSSP lmtp2nntp, an LMTP speaking local
+ **  mailer which forwards mails as Usenet news articles via NNTP.
+ **  It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
+ **
+ **  This program is free software; you can redistribute it and/or
+ **  modify it under the terms of the GNU General Public  License
+ **  as published by the Free Software Foundation; either version
+ **  2.0 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 file; if not, write to the Free Software
+ **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ **  USA, or contact the OSSP project <ossp@ossp.org>.
+ **
+ **  shpat_match.c: Shell Pattern Matching library (API)
+ */
+ 
+ /*-
+  * Copyright (c) 1992, 1993
+  *      The Regents of the University of California.  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 acknowledgement:
+  *      This product includes software developed by the University of
+  *      California, Berkeley and its contributors.
+  * 4. Neither the name of the University nor the names of its contributors
+  *    may be used to endorse or promote products derived from this software
+  *    without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  * SUCH DAMAGE.
+  */
+ 
+ #ifndef __SHPAT_MATCH_H__
+ #define __SHPAT_MATCH_H__
+ 
+ #define SHPAT_M_NOMATCH     1       /* Match failed. */
+ 
+ #define SHPAT_M_NOESCAPE    0x01    /* Disable backslash escaping. */
+ #define SHPAT_M_PATHNAME    0x02    /* Slash must be matched by slash. */
+ #define SHPAT_M_PERIOD      0x04    /* Period must be matched by period. */
+ #define SHPAT_M_LEADING_DIR 0x08    /* Ignore /<tail> after Imatch. */
+ #define SHPAT_M_CASEFOLD    0x10    /* Case insensitive search. */
+ #define SHPAT_M_IGNORECASE  FNM_CASEFOLD
+ 
+ #if defined(__cplusplus)
+ extern "C" {
+ #endif
+ 
+ int shpat_match(const char *, const char *, int);
+ int shpat_match_hasglobchar(const char *);
+ 
+ #if defined(__cplusplus)
+ }
+ #endif
+ 
+ #endif /* __SHPAT_MATCH_H__ */
+ 


ossp-pkg/lmtp2nntp/lmtp2nntp_version.c -> 1.1

*** /dev/null    Sat Apr 20 14:52:11 2024
--- -    Sat Apr 20 14:54:10 2024
***************
*** 0 ****
--- 1,46 ----
+ /*
+ **  lmtp2nntp_version.c -- Version Information for lmtp2nntp (syntax: C/C++)
+ **  [automatically generated and maintained by GNU shtool]
+ */
+ 
+ #ifdef _LMTP2NNTP_VERSION_C_AS_HEADER_
+ 
+ #ifndef _LMTP2NNTP_VERSION_C_
+ #define _LMTP2NNTP_VERSION_C_
+ 
+ #define LMTP2NNTP_VERSION 0x102002
+ 
+ typedef struct {
+     const int   v_hex;
+     const char *v_short;
+     const char *v_long;
+     const char *v_tex;
+     const char *v_gnu;
+     const char *v_web;
+     const char *v_sccs;
+     const char *v_rcs;
+ } lmtp2nntp_version_t;
+ 
+ extern lmtp2nntp_version_t lmtp2nntp_version;
+ 
+ #endif /* _LMTP2NNTP_VERSION_C_ */
+ 
+ #else /* _LMTP2NNTP_VERSION_C_AS_HEADER_ */
+ 
+ #define _LMTP2NNTP_VERSION_C_AS_HEADER_
+ #include "lmtp2nntp_version.c"
+ #undef  _LMTP2NNTP_VERSION_C_AS_HEADER_
+ 
+ lmtp2nntp_version_t lmtp2nntp_version = {
+     0x102002,
+     "1.2a2",
+     "1.2a2 (31-Dec-2001)",
+     "This is lmtp2nntp, Version 1.2a2 (31-Dec-2001)",
+     "lmtp2nntp 1.2a2 (31-Dec-2001)",
+     "lmtp2nntp/1.2a2",
+     "@(#)lmtp2nntp 1.2a2 (31-Dec-2001)",
+     "$Id: lmtp2nntp 1.2a2 (31-Dec-2001) $"
+ };
+ 
+ #endif /* _LMTP2NNTP_VERSION_C_AS_HEADER_ */
+ 


ossp-pkg/lmtp2nntp/msg.c 1.22 -> 1.23



ossp-pkg/lmtp2nntp/msg.h 1.7 -> 1.8



ossp-pkg/lmtp2nntp/nntp.c 1.27 -> 1.28



ossp-pkg/lmtp2nntp/nntp.h 1.13 -> 1.14



ossp-pkg/lmtp2nntp/shpat_match.c 1.4 -> 1.5



ossp-pkg/lmtp2nntp/shpat_match.h 1.2 -> 1.3



ossp-pkg/lmtp2nntp/version.c 1.16 -> 1.17


CVSTrac 2.0.1