Package: getmail6
Version: 6.18.13-1
Severity: minor

Since upgrading from getmail6 6.18.11-2 to 6.18.13-1, I've been getting
this warning whenever getmail delivers a message:
  handler called, but no children

The message comes from the ForkingBase class, whose signal handler is
getting called for a SIGCHLD other than from the expected child.  Then
the expected SIGCHLD comes and is handled, waking up _wait_for_child().

I've attached a test case showing the problem.  Run as follows:
  cd getmailtest
  socat TCP6-LISTEN:1110,fork EXEC:./pop3 &
  getmail -g cfg/

I guess ForkingBase only exists to handle the "user" option, which I'm
not using.  Otherwise, forking would be unnecessary or could be handled
by Python's subprocess module--and as of Python 3.9, that module can do
the setreuid() and setregid() calls too.  It also supports a timeout,
which is the only justification I can see for messing around with the
process's SIGCHLD handler (if not for that, a simple waitpid() would
work).

Given that no timeout seems to be documented for filter and destination
classes, and it could cause problems for people not expecting it, I'm
inclined to call that a bug.  I've attached a patch that gets rid of the
handler and its timeout.
  ( To the extent possible under law, the author(s) have dedicated all
    copyright and related and neighboring rights to this work to the
    public domain worldwide.
    See https://creativecommons.org/publicdomain/zero/1.0/legalcode )

I've done only trivial testing on the patched version.  As hinted above,
a cleaner fix would be to delete all this code and use the subprocess
module (or os.seteuid() and such for cases not requiring a sub-process).

- Michael


-- System Information:
Debian Release: trixie/sid
  APT prefers unstable-debug
  APT policy: (500, 'unstable-debug'), (500, 'unstable'), (1, 'experimental')
Architecture: amd64 (x86_64)

Kernel: Linux 6.6.15-amd64 (SMP w/32 CPU threads; PREEMPT)
Locale: LANG=en_CA.UTF-8, LC_CTYPE=en_CA.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_CA:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages getmail6 depends on:
ii  python3  3.11.6-1

getmail6 recommends no packages.

getmail6 suggests no packages.

-- no debconf information

Attachment: getmailtest.tar.gz
Description: Binary data

diff --git a/getmail b/getmail
index 58ad52b0353e..821cfb22f579 100755
--- a/getmail
+++ b/getmail
@@ -643,6 +643,8 @@ No other entry is `Unseen`, i.e. `-s,` means 
`imap_search,imap_on_delete=Unseen,
             s += '%s="%s"' % (attr, pprint.pformat(getattr(options, attr)))
         log.debug('parsed options:  %s\n' % s)
 
+        signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+
         imap_override = {}
         flags,search = imap_search_flags(options.override_imap)
         if flags:
diff --git a/getmailcore/baseclasses.py b/getmailcore/baseclasses.py
index cc2ed948c59d..d081926de25d 100755
--- a/getmailcore/baseclasses.py
+++ b/getmailcore/baseclasses.py
@@ -403,60 +403,28 @@ class ForkingBase(object):
         log - an object of type getmailcore.logging.Logger()
 
     '''
-    def _child_handler(self, sig, stackframe):
-        def notify():
-            self.__child_exited.acquire()
-            self.__child_exited.notify_all()
-            self.__child_exited.release()
-        self.log.trace('handler called for signal %s' % sig)
+
+    def _wait_for_child(self, child_pid):
         try:
-            pid, r = os.waitpid(self.child.childpid,0)
+            pid, status = os.waitpid(child_pid, 0)
         except OSError as o:
-            # No children on SIGCHLD.  Can't happen?
-            self.log.warning('handler called, but no children (%s)' % o)
-            notify()
+            self.log.warning('waitpid() failed (%s)' % o)
             return
-        if self.__orig_handler:
-            signal.signal(signal.SIGCHLD, self.__orig_handler)
-        self.__child_pid = pid
-        self.__child_status = r
-        self.log.trace('handler reaped child %s with status %s' % (pid, r))
-        notify()
-
-    def _prepare_child(self):
-        self.log.trace('')
-        self.__child_exited = Condition()
-        self.__child_pid = 0
-        self.__child_status = None
-        self.__orig_handler = None
-        self.__orig_handler = signal.signal(signal.SIGCHLD, 
self._child_handler)
-
-    def _wait_for_child(self, childpid):
-        self.__child_exited.acquire()
-        if self.__child_exited.wait(socket.getdefaulttimeout() or 60) == 
False: # py2, <py3.2: always None
-            raise getmailOperationError('waiting child pid %d timed out'
-                                        % childpid)
-        self.__child_exited.release()
-        if self.__child_pid != childpid:
-            #self.log.error('got child pid %d, not %d' % (pid, childpid))
-            raise getmailOperationError(
-                'got child pid %d, not %d'
-                % (self.__child_pid, childpid)
-            )
-        if os.WIFSTOPPED(self.__child_status):
+
+        if os.WIFSTOPPED(status):
             raise getmailOperationError(
                 'child pid %d stopped by signal %d'
-                % (self.__child_pid, os.WSTOPSIG(self.__child_status))
+                % (child_pid, os.WSTOPSIG(status))
             )
-        if os.WIFSIGNALED(self.__child_status):
+        if os.WIFSIGNALED(status):
             raise getmailOperationError(
                 'child pid %d killed by signal %d'
-                % (self.__child_pid, os.WTERMSIG(self.__child_status))
+                % (child_pid, os.WTERMSIG(status))
             )
-        if not os.WIFEXITED(self.__child_status):
+        if not os.WIFEXITED(status):
             raise getmailOperationError('child pid %d failed to exit'
-                                        % self.__child_pid)
-        exitcode = os.WEXITSTATUS(self.__child_status)
+                                        % child_pid)
+        exitcode = os.WEXITSTATUS(status)
         return exitcode
 
     def _pipemail(self, msg, delivered_to, received, unixfrom, stdout, stderr):
@@ -485,7 +453,6 @@ class ForkingBase(object):
         child.stderr = TemporaryFile23()
         child.childpid = os.fork()
         if child.childpid != 0: # here (in the parent)
-            self._prepare_child()
             self.log.debug('spawned child %d\n' % child.childpid)
             child.exitcode = self._wait_for_child(child.childpid)
             child.stderr.seek(0)

Attachment: signature.asc
Description: PGP signature

Reply via email to