Source: unixodbc
Version: 2.3.12-1
Severity: normal
X-Debbugs-Cc: athos.ribe...@canonical.com

In PHP, when using the following command through php-odbc:

odbc_connect(\$dsn, \$user, \$password, SQL_CUR_USE_ODBC);

I get the following error:

PHP Warning:  odbc_connect(): SQL error: [unixODBC][Driver Manager]Can't open 
cursor lib '/etc/libodbccr.so' : file not found, SQL state 01000 in SQLConnect 
in /root/reproducer.php on line 5

The missing libodbccr.so issue happens when this code runs:
https://sources.debian.org/src/unixodbc/2.3.12-1/DriverManager/SQLConnect.c/?hl=2451#L2451

Here we see that, instead of linking libodbccr2, libodbc2 opens it in runtime
with dlopen. Hence, at a first glance, libodbc2 should at least suggest
libodbccr2.

However, as shown in the error message, the file being searched for is the
unversioned shared object, provided by unixodbc-dev, and not the versioned one
provided by libodbccr2.  This happens because DEFINE_CURSOR_LIB_VER is not
defined, as shown in the following snippet:
https://sources.debian.org/src/unixodbc/2.3.12-1/DriverManager/SQLConnect.c/?hl=2451#L606.
This makes libodbc2 try loading the unversioned shared library libodbccr.so
instead of the versioned one (libodbccr.so.2).

This issue affects all supported ubuntu series.

At a first glance, we may identify 2 fixes being needed here:

1) the unixodbc package should be built with DEFINE_CURSOR_LIB_VER set so the
   runtime dlopen call looks for the versioned shared object so we do not need 
to
   depend on a -dev package in production environments; and

2) libodbc2 should depend, recommend, or at least suggest libodbccr2

The problem is the latter fix (2) would create a circular dependency since
libodbccr2 already depends on libodbc2. Interestingly, none of the binaries are
linked against the other.

In this case, an alternative solution to (2) would be

2.alternative) making php-odbc (and all other packages using the odbc cursor) 
depend on
               both libodbc2 and libodbccr2 (or at least suggest/recommend the 
libodbccr2).
               This would only make sense after fix (1) is applied.

I also wonder if libodbccr2 should really depend on libodbc2 since they are not
linked (maybe the dependency should go the other way around, which would be yet
another alternative for fix (2)). This wound need further investigation, i.e.,
we need an answer to "why does libodbccr2 depend on libodbc2?".

A reproducer for trixie using incus is attached.
#!/bin/bash

CONTAINER_NAME=php-odbc

PHP_REPRODUCER=$(mktemp)

cat > $PHP_REPRODUCER <<EOF
<?php
\$dsn = 
"Driver={SQLite3};Server=127.0.0.1;Database=odbc;uid=someuser;pwd=somepassword";
\$user = "root";
\$password = "";
\$success = odbc_connect(\$dsn, \$user, \$password, SQL_CUR_USE_ODBC);
if(\$success) {
        echo "OK\n";
} else {
        echo "Connection failure\n";
}
EOF

echo "DEBUG: Preparing container"
incus delete -f $CONTAINER_NAME > /dev/null 2>&1
incus launch images:debian/trixie $CONTAINER_NAME > /dev/null 2>&1

echo "DEBUG: Installing dependencies"
incus exec $CONTAINER_NAME -- apt update > /dev/null 2>&1
incus exec $CONTAINER_NAME -- apt install -y php php-odbc php-sqlite3 odbcinst 
libsqliteodbc > /dev/null 2>&1
echo "DEBUG: Pushing reproducer"
incus file push $PHP_REPRODUCER $CONTAINER_NAME/root/reproducer.php
rm -f $PHP_REPRODUCER
echo "DEBUG: Configuring environment"
incus exec $CONTAINER_NAME -- cp /etc/odbcinst.ini /etc/odbc.ini
echo "DEBUG: Reproducing failure"
incus exec $CONTAINER_NAME -- php reproducer.php
echo "DEBUG: Installing libodbccr2"
incus exec $CONTAINER_NAME -- apt install -y libodbccr2 > /dev/null 2>&1
echo "DEBUG: Reproducing failure with libodbccr2 installed (libodbccr.so.2 is 
present)"
incus exec $CONTAINER_NAME -- php reproducer.php
echo "DEBUG: Installing unixodbc-dev"
incus exec $CONTAINER_NAME -- apt install -y unixodbc-dev > /dev/null 2>&1
echo "DEBUG: Demonstrating success with same reproducer, now with unixodbc-dev 
installed (libodbccr.so is present)"
incus exec $CONTAINER_NAME -- php reproducer.php

Reply via email to