#1059746 ucf: Symlinks followed before looking up dpkg-divert

Package:
ucf
Source:
ucf
Submitter:
Hristo Venev
Date:
2025-02-26 18:15:01 UTC
Severity:
normal
Tags:
#1059746#5
Date:
2023-12-31 11:54:06 UTC
From:
To:
Dear Maintainer,

It appears that ucf follows symbolic links before checking if those
symlinks correspond to diverted packages:

    setq dest_file "$(readlink -q -m $temp_dest_file)" "The Destination file";
    ...
    dest_file=$(dpkg-divert --truename "$dest_file")

This means that if a configuration file is diverted and a symlink is
installed in its place, ucf will try to act on the target of the symlink
instead of on the original config file.

Instead, a more reasonable behaviour would be the following:

1. First, resolve the real path of the directory that contains the
configuration file, but not the trailing symlink
2. Then, check if the resulting path is diverted
3. Finally, dereference the trailing symlink

#1059746#10
Date:
2025-01-10 16:45:05 UTC
From:
To:
Hristo,

Thanks for this. I have been giving it some thought, but it is a thorny issue
and I have not found a good solution (yet?).

The root of the issue is alluded to in Debian Policy[1]: sharing and diverting
configuration files is at best discouraged and likely to be broken.

On that basis I am minded to close this as wontfix. Does that seem reasonable?

Mark

[1]  https://www.debian.org/doc/debian-policy/ch-files.html#sharing-configuration-files

#1059746#15
Date:
2025-01-13 19:05:10 UTC
From:
To:
Perhaps it is.

What I'm looking for is a way to manage configuration files. For me
practically all configuration files (there are few exceptions, most
notably `/etc/passwd`) fall into one of two categories:

- The configuration file is provided by the package in one way or
another, and its contents are always exactly what is provided by the
package without any modification.

- The configuration file is provided by the user. Any package
management operations must not modify the configuration file.

Ideally it would be possible keep a list of the configuration files
that fall into the second category and apply it automatically. This
would restore the original contents of all files removed from the list,
and ensure that newly-added files will no longer be modified by package
updates.

Using `dpkg-divert --rename` + `dpkg --force-confnew,confmiss,confask`
seems to work pretty well for dpkg-managed conffiles. The list of
diversions is maintained to be exactly the list of user-managed
configuration files.

For ucf-managed configuration files, however, things are a bit more
difficult. In my case all of them happen to fall into the second
category, so currently my `/etc/ucf.conf` is `exit 0`. This works for
now, but it is not ideal, and will become worse if more packages start
using ucf.

#1059746#20
Date:
2025-01-14 11:50:32 UTC
From:
To:
Hristo,

I have just implemented support for DPKG_FORCE in ucf[1] and that should be
uploaded shortly. Maybe that will serve your needs?

Mark

[1]  https://bugs.debian.org/925375

#1059746#25
Date:
2025-01-14 21:09:56 UTC
From:
To:
Yes, as long as ucf respects `dpkg-divert` the same way dpkg does.
#1059746#30
Date:
2025-02-19 11:03:58 UTC
From:
To:
Hi,

I got into the same issue while using config-package-dev
(https://debathena.mit.edu/config-packages/).
I have a configuration package that divert `sshd_config`.

But when upgrading openssh-server, ucf prompt the user as it detects
changes in the symlinked config file.

This is the same issue, but I'm using apt without force-new or anything.


I've followed the patch on #477773 from debathena page.
It seems that the added dpkg-divert check was already after the readlink
at that time, so I'm wondering if the implementation ever worked.



I tried to just implement what Hristo proposed and didn't find it too
much hacky:
Put the dpkg-divert check in a function "follow_divert"
Call that function in "handle_file_args" function:

```
follow_divert() {
	dest_file="$1"
	opt_package="$2"

	# Follow dpkg-divert as though we are installed as part of $opt_package
	divert_line=$(dpkg-divert --listpackage "$dest_file")
	if [ -n "$divert_line" ]; then
		# name of the package or 'LOCAL' for a local diversion
		divert_package="$divert_line"

		if [ "$divert_package" != "$opt_package" ]; then
			dest_file=$(dpkg-divert --truename "$dest_file")
		fi
	fi

	echo "$dest_file"
}
[...]

handle_file_args() {
[...]
	temp_new_file="$1"
	temp_dest_file=$(follow_divert "$2" "$opt_package")
[...]
}
```

What do you think ?

#1059746#35
Date:
2025-02-23 16:39:39 UTC
From:
To:
Alexis,

Thanks

I see this is a usage that would be good to support.

I am testing your patch. Thanks.

Could you provide a testsuite case as well?

Mark

#1059746#42
Date:
2025-02-26 18:12:26 UTC
From:
To:
Hi,

On 23/02/2025 17:39, Mark Hindley wrote:
[...]

I found that the general case is more complex, compared to what
openssh-server does with ucf.

openssh-server includes a md5sum history containing all hashes of
previous sshd_config files.
This makes my simple patch work.

But without that md5sum history, it doesn't because the real destination
file after dpkg-divert is not the same as before.


This is the test I have locally so far:

Before dpkg-divert:
- ucf uses the file given in argument, /tmp/input.conf in the test
- ucf hash the file and put something like
   "514654654968198179817 /tmp/input.conf" in its /var/lib/ucf/hashfile.

config-package-dev's script is executed (dpkg-divert + symlinks):
- dpkg-divert renames /tmp/input.conf into
   /tmp/input.conf.diverter-package-orig
- A symlink is created at /tmp/input.conf pointing to
   /tmp/input.conf.diverter-package (which is provided by the config
   package using the config-package-dev script in its postinst)

So we have at this point:
- /tmp/input.conf -> input.conf.diverter-package
- /tmp/input.conf.diverter-package-orig
- /tmp/input.conf.diverter-package

input.conf.diverter-package-orig contains the original input.conf data.
input.conf.diverter-package contains the custom modified input.conf.

Then ucf is called again to simulate a new input.conf version:
- ucf check the destination file /tmp/input.conf for diversion
   (implemented by the patch).
- ucf uses the diverted path "/tmp/input.conf.diverter-package-orig"
   instead.
- ucf check if the file was modified by checking the md5sum and search in
   "/var/lib/ucf/hashfile" the path
   "/tmp/input.conf.diverter-package-orig".
- ucf doesn't find it, then it check historical md5sums (which is what
   provides openssh-server package, but not my test).
- ucf doesn't have an historical md5sums directory, so it asks the user.


When I add --sum-file with a file containing the md5sum of the original
input.conf and input2.conf, ucf won't ask the user.



To handle the case without "--sum-file", I'm thinking about splitting
the notion of "destination file" and "real destination file", the former
would be /tmp/input.conf and the later
/tmp/input.conf.diverter-package-orig.

But I'm not sure this is worth the risk of regressions.


In my case, I only wanted it to work with openssh-server, maybe that
special case make it still worth to implement, but I wonder if there are
that much packages using historical md5sums.