diff -pruN 2.8.1-4/debian/changelog 2.8.1-4ubuntu1/debian/changelog
--- 2.8.1-4/debian/changelog	2024-12-15 22:28:01.000000000 +0000
+++ 2.8.1-4ubuntu1/debian/changelog	2025-01-16 19:15:34.000000000 +0000
@@ -1,3 +1,14 @@
+nut (2.8.1-4ubuntu1) plucky; urgency=medium
+
+  * Merge with Debian unstable (LP: #2085258). Remaining changes:
+    - d/t/test-nut.py: scale sleeps by x10 to mitigate flaky results
+  * New changes:
+    - d/tests/testlib.py: replace crypt module with direct cffi calls
+      to native crypt_ra C function. Since Python 3.13 removed crypt module
+      from the standard library.
+
+ -- Zixing Liu <zixing.liu@canonical.com>  Thu, 16 Jan 2025 12:15:34 -0700
+
 nut (2.8.1-4) unstable; urgency=medium
 
   * Team upload (Debian namespace).
@@ -27,6 +38,21 @@ nut (2.8.1-3.2) unstable; urgency=medium
 
  -- Michael Biebl <biebl@debian.org>  Sat, 17 Aug 2024 20:08:31 +0200
 
+nut (2.8.1-3.1ubuntu2) noble; urgency=medium
+
+  * No-change rebuild for CVE-2024-3094
+
+ -- Steve Langasek <steve.langasek@ubuntu.com>  Sun, 31 Mar 2024 02:41:08 +0000
+
+nut (2.8.1-3.1ubuntu1) noble; urgency=medium
+
+  * Merge 2.8.1-3.1 to pick up 64-bit time_t transition
+    Remaining changes:
+    - d/t/test-nut.py: scale sleeps by x10 to mitigate flaky results
+      (LP: 2046263)
+
+ -- Christian Ehrhardt <christian.ehrhardt@canonical.com>  Mon, 04 Mar 2024 21:02:16 +0100
+
 nut (2.8.1-3.1) unstable; urgency=medium
 
   * Non-maintainer upload.
@@ -34,6 +60,13 @@ nut (2.8.1-3.1) unstable; urgency=medium
 
  -- Benjamin Drung <bdrung@debian.org>  Thu, 29 Feb 2024 01:26:20 +0000
 
+nut (2.8.1-3ubuntu1) noble; urgency=medium
+
+  * d/t/test-nut.py: scale sleeps by x10 to mitigate flaky results
+    (LP: #2046263)
+
+ -- Christian Ehrhardt <christian.ehrhardt@canonical.com>  Mon, 22 Jan 2024 08:11:47 +0100
+
 nut (2.8.1-3) unstable; urgency=medium
 
   * debian/control: Add missing Breaks/Replaces on nut-modbus (Closes:
diff -pruN 2.8.1-4/debian/control 2.8.1-4ubuntu1/debian/control
--- 2.8.1-4/debian/control	2024-12-15 22:28:01.000000000 +0000
+++ 2.8.1-4ubuntu1/debian/control	2025-01-16 19:15:34.000000000 +0000
@@ -1,7 +1,8 @@
 Source: nut
 Section: admin
 Priority: optional
-Maintainer: Laurent Bigonville <bigon@debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Laurent Bigonville <bigon@debian.org>
 Build-Depends: dpkg-dev (>= 1.22.5), asciidoc <!nodoc>,
                debhelper (>= 12),
                dh-python,
diff -pruN 2.8.1-4/debian/tests/control 2.8.1-4ubuntu1/debian/tests/control
--- 2.8.1-4/debian/tests/control	2024-12-15 22:28:01.000000000 +0000
+++ 2.8.1-4ubuntu1/debian/tests/control	2025-01-16 19:15:34.000000000 +0000
@@ -1,3 +1,3 @@
 Tests: nut
-Depends: lsb-release, netcat, nut-client, nut-server, psmisc, python3
+Depends: lsb-release, netcat, nut-client, nut-server, psmisc, python3, python3-cffi
 Restrictions: needs-root
diff -pruN 2.8.1-4/debian/tests/test-nut.py 2.8.1-4ubuntu1/debian/tests/test-nut.py
--- 2.8.1-4/debian/tests/test-nut.py	2024-12-15 22:28:01.000000000 +0000
+++ 2.8.1-4ubuntu1/debian/tests/test-nut.py	2025-01-16 19:15:34.000000000 +0000
@@ -202,7 +202,7 @@ UPSMON_OPTIONS=""
     def _tearDown(self):
         '''Clean up after each test_* function'''
         self._stop()
-        time.sleep(2)
+        time.sleep(20)
         os.unlink('/etc/nut/ups.conf')
         os.unlink('/etc/nut/upsd.conf')
         os.unlink('/etc/nut/upsd.users')
@@ -237,7 +237,7 @@ UPSMON_OPTIONS=""
         expected = 0
         result = 'Got exit code %d, expected %d\n' % (rc, expected)
         self.assertEqual(expected, rc, result + report)
-        time.sleep(10)
+        time.sleep(100)
 
     def _stop(self):
         '''Stop NUT'''
@@ -256,7 +256,7 @@ UPSMON_OPTIONS=""
     def _restart(self):
         '''Restart NUT'''
         self._stop()
-        time.sleep(2)
+        time.sleep(20)
         self._start()
 
     def _status(self):
@@ -344,7 +344,7 @@ class BasicTest(NutTestCommon, PrivateNu
         '''Test upsrw'''
         # Set ups.status to OB (On Battery)...
         self._nut_setvar('ups.model', 'Test')
-        time.sleep(2)
+        time.sleep(20)
         # and check the result on the client side
         rc, report = testlib.cmd(['/bin/upsc', 'dummy-dev1@localhost', 'ups.model'])
         self.assertTrue('Test' in report, 'UPS Model: ' + report + 'should be Test')
@@ -358,7 +358,7 @@ class BasicTest(NutTestCommon, PrivateNu
         '''Test upsmon notifications'''
         # Set ups.status to OB (On Battery)...
         self._nut_setvar('ups.status', 'OB')
-        time.sleep(1)
+        time.sleep(10)
         # and check the result on the client side
         rc, report = testlib.cmd(['/bin/upsc', 'dummy-dev1@localhost', 'ups.status'])
         self.assertTrue('OB' in report, 'UPS Status: ' + report + 'should be OB')
@@ -401,7 +401,7 @@ dd if=/dev/urandom count=64 | nc -q 1 12
         pidfile = os.path.join(self.rundir, 'upsd.pid')
         timeout = 50
         while timeout > 0 and os.path.exists(pidfile):
-            time.sleep(0.1)
+            time.sleep(1)
             timeout -= 1
         self.assertFalse(os.path.exists(pidfile), "Found %s" % pidfile)
         self.assertFalse(testlib.check_pidfile('upsd', pidfile), 'upsd is hung')
diff -pruN 2.8.1-4/debian/tests/testlib.py 2.8.1-4ubuntu1/debian/tests/testlib.py
--- 2.8.1-4/debian/tests/testlib.py	2024-12-15 22:28:01.000000000 +0000
+++ 2.8.1-4ubuntu1/debian/tests/testlib.py	2025-01-16 19:15:34.000000000 +0000
@@ -19,7 +19,7 @@
 
 '''Common classes and functions for package tests.'''
 
-import string, random, subprocess, pwd, grp,  signal, time, unittest, tempfile, shutil, os, os.path, re, glob
+import string, random, cffi, subprocess, pwd, grp,  signal, time, unittest, tempfile, shutil, os, os.path, re, glob
 import sys, socket, gzip
 from stat import *
 
@@ -54,6 +54,17 @@ def subprocess_setup():
     # non-Python subprocesses expect.
     signal.signal(signal.SIGPIPE, signal.SIG_DFL)
 
+def hash_password(phrase, salt):
+   ffi = cffi.FFI()
+   crypt_func = ffi.cdef("char* crypt_ra(const char *phrase, const char *setting, void **data, int *size);")
+   ffi.dlopen("crypt")
+   size = ffi.new("int*")
+   rbuf = ffi.new("void**")
+   result = crypt_func(phrase.encode("utf-8"), salt.encode("utf-8"), rbuf, size)
+   string_result = ffi.string(result)
+   ffi.release()
+   return string_result
+
 class TimedOutException(Exception):
     def __init__(self, value = "Timed Out"):
         self.value = value
@@ -907,6 +918,101 @@ class TestlibCase(unittest.TestCase):
                     return True
         return False
 
+class TestGroup:
+    '''Create a temporary test group and remove it again in the dtor.'''
+
+    def __init__(self, group=None, lower=False):
+        '''Create a new group'''
+
+        self.group = None
+        if group:
+            if group_exists(group):
+                raise ValueError('group name already exists')
+        else:
+            while(True):
+                group = random_string(7,lower=lower)
+                if not group_exists(group):
+                    break
+
+        assert subprocess.call(['groupadd',group]) == 0
+        self.group = group
+        g = grp.getgrnam(self.group)
+        self.gid = g[2]
+
+    def __del__(self):
+        '''Remove the created group.'''
+
+        if self.group:
+            rc, report = cmd(['groupdel', self.group])
+            assert rc == 0
+
+class TestUser:
+    '''Create a temporary test user and remove it again in the dtor.'''
+
+    def __init__(self, login=None, home=True, group=None, uidmin=None, lower=False, shell=None):
+        '''Create a new user account with a random password.
+
+        By default, the login name is random, too, but can be explicitly
+        specified with 'login'. By default, a home directory is created, this
+        can be suppressed with 'home=False'.'''
+
+        self.login = None
+
+        if os.geteuid() != 0:
+            raise ValueError("You must be root to run this test")
+
+        if login:
+            if login_exists(login):
+                raise ValueError('login name already exists')
+        else:
+            while(True):
+                login = 't' + random_string(7,lower=lower)
+                if not login_exists(login):
+                    break
+
+        self.salt = random_string(2)
+        self.password = random_string(8,lower=lower)
+        self.crypted = hash_password(self.password, self.salt)
+
+        creation = ['useradd', '-p', self.crypted]
+        if home:
+            creation += ['-m']
+        if group:
+            creation += ['-G',group]
+        if uidmin:
+            creation += ['-K','UID_MIN=%d'%uidmin]
+        if shell:
+            creation += ['-s',shell]
+        creation += [login]
+        assert subprocess.call(creation) == 0
+        # Set GECOS
+        assert subprocess.call(['usermod','-c','Buddy %s' % (login),login]) == 0
+
+        self.login = login
+        p = pwd.getpwnam(self.login)
+        self.uid   = p[2]
+        self.gid   = p[3]
+        self.gecos = p[4]
+        self.home  = p[5]
+        self.shell = p[6]
+
+    def __del__(self):
+        '''Remove the created user account.'''
+
+        if self.login:
+            # sanity check the login name so we don't accidentally wipe too much
+            if len(self.login)>3 and not '/' in self.login:
+                subprocess.call(['rm','-rf', '/home/'+self.login, '/var/mail/'+self.login])
+            rc, report = cmd(['userdel', '-f', self.login])
+            assert rc == 0
+
+    def add_to_group(self, group):
+        '''Add user to the specified group name'''
+        rc, report = cmd(['usermod', '-G', group, self.login])
+        if rc != 0:
+            print(report)
+        assert rc == 0
+
 # Timeout handler using alarm() from John P. Speno's Pythonic Avocado
 class TimeoutFunctionException(Exception):
     """Exception to raise on a timeout"""
