mirror of
https://github.com/samba-team/samba.git
synced 2024-12-31 17:18:04 +03:00
Compare commits
65 Commits
919510d86b
...
826b75bf03
Author | SHA1 | Date | |
---|---|---|---|
|
826b75bf03 | ||
|
f5048e3d98 | ||
|
a18c6ff20b | ||
|
24710a5f4e | ||
|
549f67a9e6 | ||
|
221447d5ba | ||
|
882a761a50 | ||
|
986e398887 | ||
|
910a331f3e | ||
|
0998686355 | ||
|
8a75cb0e20 | ||
|
a8491e83b4 | ||
|
d100aaf4bf | ||
|
0bcc209d94 | ||
|
6907508cdb | ||
|
cb23d6a15e | ||
|
066576e7c8 | ||
|
6a60174c04 | ||
|
bb55fa100e | ||
|
8f8943547b | ||
|
a72146c4ef | ||
|
c33fb4bfd5 | ||
|
ff0cf5eda1 | ||
|
89db2e536c | ||
|
9bd37a450d | ||
|
8298cf4376 | ||
|
7140c506ee | ||
|
518854e29b | ||
|
4ac7e56bca | ||
|
43971bbada | ||
|
5a8b8a7799 | ||
|
4a4304ecf7 | ||
|
33bdebb7c8 | ||
|
7273cc7d89 | ||
|
57353b8650 | ||
|
2824ed2605 | ||
|
58fbba3897 | ||
|
9ffac29435 | ||
|
35b34e8e42 | ||
|
dc445490cc | ||
|
e3715ba548 | ||
|
948d0fcfe1 | ||
|
abcf7644a9 | ||
|
3f6254a670 | ||
|
5350b31a78 | ||
|
426c43963a | ||
|
3c55599e99 | ||
|
a5e330ee90 | ||
|
71b96f78df | ||
|
94e4d005b5 | ||
|
82d7208a85 | ||
|
e243b7c95f | ||
|
f6a22875d1 | ||
|
e4c28a2d57 | ||
|
1e2abfa313 | ||
|
aea926391f | ||
|
f1aa68041e | ||
|
92dca744e1 | ||
|
f7edbf70d0 | ||
|
f0cbe4d5a2 | ||
|
61a5dc11a7 | ||
|
f37e311af1 | ||
|
03c0c89809 | ||
|
8ac241017f | ||
|
94be411a94 |
@ -10,6 +10,7 @@ bre
|
||||
bu
|
||||
childres
|
||||
clen
|
||||
crate
|
||||
creat
|
||||
connectin
|
||||
daa
|
||||
|
@ -47,7 +47,7 @@ variables:
|
||||
# Set this to the contents of bootstrap/sha1sum.txt
|
||||
# which is generated by bootstrap/template.py --render
|
||||
#
|
||||
SAMBA_CI_CONTAINER_TAG: d1ce7e10953d16253a34b8e58077fd32c1dbd59c
|
||||
SAMBA_CI_CONTAINER_TAG: 936722ecb26bedf6ea0acd9228963ce45ed419d4
|
||||
#
|
||||
# We use the ubuntu2204 image as default as
|
||||
# it matches what we have on atb-devel-224
|
||||
|
@ -64,6 +64,7 @@ COMMON = [
|
||||
'tar',
|
||||
'tree',
|
||||
'wget',
|
||||
'cargo',
|
||||
]
|
||||
|
||||
|
||||
@ -83,6 +84,9 @@ PKGS = [
|
||||
('libcap-dev', 'libcap-devel'),
|
||||
('libacl1-dev', 'libacl-devel'),
|
||||
('libattr1-dev', 'libattr-devel'),
|
||||
('libutf8proc-dev', 'utf8proc-devel'),
|
||||
('libssl-dev', 'openssl-devel'),
|
||||
('libclang-dev', 'clang-devel'),
|
||||
|
||||
# libNAME1-dev, NAME2-devel
|
||||
('libpopt-dev', 'popt-devel'),
|
||||
@ -244,6 +248,11 @@ set -xueo pipefail
|
||||
yum update -y
|
||||
yum install -y dnf-plugins-core
|
||||
yum install -y epel-release
|
||||
yum install -y centos-release-ceph-pacific
|
||||
|
||||
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Ceph-*
|
||||
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Ceph-*
|
||||
sed -i 's/$contentdir/centos/g' /etc/yum.repos.d/CentOS-Ceph-*
|
||||
|
||||
yum -v repolist all
|
||||
yum config-manager --set-enabled powertools -y
|
||||
@ -252,6 +261,7 @@ yum update -y
|
||||
|
||||
yum install -y \
|
||||
--setopt=install_weak_deps=False \
|
||||
--setopt=centos-ceph-pacific.module_hotfixes=true \
|
||||
{pkgs}
|
||||
|
||||
yum clean all
|
||||
@ -443,6 +453,7 @@ DEB_DISTS = {
|
||||
'replace': {
|
||||
'language-pack-en': '', # included in locales
|
||||
'shfmt': '',
|
||||
'cargo': '', # included cargo is broken
|
||||
}
|
||||
},
|
||||
'debian11-32bit': {
|
||||
@ -451,6 +462,7 @@ DEB_DISTS = {
|
||||
'replace': {
|
||||
'language-pack-en': '', # included in locales
|
||||
'shfmt': '',
|
||||
'cargo': '', # included cargo is broken
|
||||
}
|
||||
},
|
||||
'debian12': {
|
||||
@ -459,6 +471,7 @@ DEB_DISTS = {
|
||||
'replace': {
|
||||
'language-pack-en': '', # included in locales
|
||||
'libtracker-sparql-2.0-dev': '', # only tracker 3.x is available
|
||||
'cargo': '', # included cargo is broken
|
||||
}
|
||||
},
|
||||
'debian12-32bit': {
|
||||
@ -467,6 +480,7 @@ DEB_DISTS = {
|
||||
'replace': {
|
||||
'language-pack-en': '', # included in locales
|
||||
'libtracker-sparql-2.0-dev': '', # only tracker 3.x is available
|
||||
'cargo': '', # included cargo is broken
|
||||
}
|
||||
},
|
||||
'ubuntu1804': {
|
||||
|
@ -27,8 +27,10 @@ dnf install -y \
|
||||
bind-utils \
|
||||
binutils \
|
||||
bison \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
clang-devel \
|
||||
crypto-policies-scripts \
|
||||
cups-devel \
|
||||
dbus-devel \
|
||||
@ -78,6 +80,7 @@ dnf install -y \
|
||||
mingw64-gcc \
|
||||
ncurses-devel \
|
||||
openldap-devel \
|
||||
openssl-devel \
|
||||
pam-devel \
|
||||
patch \
|
||||
perl \
|
||||
@ -115,6 +118,7 @@ dnf install -y \
|
||||
tar \
|
||||
tracker-devel \
|
||||
tree \
|
||||
utf8proc-devel \
|
||||
wget \
|
||||
which \
|
||||
xfsprogs-devel \
|
||||
|
@ -8,8 +8,10 @@ packages:
|
||||
- bind-utils
|
||||
- binutils
|
||||
- bison
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- clang-devel
|
||||
- crypto-policies-scripts
|
||||
- cups-devel
|
||||
- dbus-devel
|
||||
@ -59,6 +61,7 @@ packages:
|
||||
- mingw64-gcc
|
||||
- ncurses-devel
|
||||
- openldap-devel
|
||||
- openssl-devel
|
||||
- pam-devel
|
||||
- patch
|
||||
- perl
|
||||
@ -96,6 +99,7 @@ packages:
|
||||
- tar
|
||||
- tracker-devel
|
||||
- tree
|
||||
- utf8proc-devel
|
||||
- wget
|
||||
- which
|
||||
- xfsprogs-devel
|
||||
|
@ -50,6 +50,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -68,12 +69,14 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libtracker-sparql-2.0-dev \
|
||||
libunwind-dev \
|
||||
liburing-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -39,6 +39,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -57,12 +58,14 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libtracker-sparql-2.0-dev
|
||||
- libunwind-dev
|
||||
- liburing-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -50,6 +50,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -68,12 +69,14 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libtracker-sparql-2.0-dev \
|
||||
libunwind-dev \
|
||||
liburing-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -39,6 +39,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -57,12 +58,14 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libtracker-sparql-2.0-dev
|
||||
- libunwind-dev
|
||||
- liburing-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -50,6 +50,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -68,11 +69,13 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libunwind-dev \
|
||||
liburing-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -39,6 +39,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -57,11 +58,13 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libunwind-dev
|
||||
- liburing-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -50,6 +50,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -68,11 +69,13 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libunwind-dev \
|
||||
liburing-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -39,6 +39,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -57,11 +58,13 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libunwind-dev
|
||||
- liburing-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -20,8 +20,10 @@ dnf install -y \
|
||||
bind-utils \
|
||||
binutils \
|
||||
bison \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
clang-devel \
|
||||
codespell \
|
||||
crypto-policies-scripts \
|
||||
cups-devel \
|
||||
@ -75,6 +77,7 @@ dnf install -y \
|
||||
mold \
|
||||
ncurses-devel \
|
||||
openldap-devel \
|
||||
openssl-devel \
|
||||
pam-devel \
|
||||
patch \
|
||||
perl \
|
||||
@ -114,6 +117,7 @@ dnf install -y \
|
||||
tar \
|
||||
tracker-devel \
|
||||
tree \
|
||||
utf8proc-devel \
|
||||
wget \
|
||||
which \
|
||||
xfsprogs-devel \
|
||||
|
@ -9,8 +9,10 @@ packages:
|
||||
- bind-utils
|
||||
- binutils
|
||||
- bison
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- clang-devel
|
||||
- codespell
|
||||
- crypto-policies-scripts
|
||||
- cups-devel
|
||||
@ -64,6 +66,7 @@ packages:
|
||||
- mold
|
||||
- ncurses-devel
|
||||
- openldap-devel
|
||||
- openssl-devel
|
||||
- pam-devel
|
||||
- patch
|
||||
- perl
|
||||
@ -103,6 +106,7 @@ packages:
|
||||
- tar
|
||||
- tracker-devel
|
||||
- tree
|
||||
- utf8proc-devel
|
||||
- wget
|
||||
- which
|
||||
- xfsprogs-devel
|
||||
|
@ -20,8 +20,10 @@ zypper --non-interactive install \
|
||||
bind-utils \
|
||||
binutils \
|
||||
bison \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
clang-devel \
|
||||
codespell \
|
||||
crypto-policies-scripts \
|
||||
cups-devel \
|
||||
@ -72,6 +74,7 @@ zypper --non-interactive install \
|
||||
mingw64-gcc \
|
||||
ncurses-devel \
|
||||
openldap2-devel \
|
||||
openssl-devel \
|
||||
pam-devel \
|
||||
patch \
|
||||
perl \
|
||||
@ -106,6 +109,7 @@ zypper --non-interactive install \
|
||||
tar \
|
||||
tracker-devel \
|
||||
tree \
|
||||
utf8proc-devel \
|
||||
wget \
|
||||
which \
|
||||
xfsprogs-devel \
|
||||
|
@ -8,8 +8,10 @@ packages:
|
||||
- bind-utils
|
||||
- binutils
|
||||
- bison
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- clang-devel
|
||||
- codespell
|
||||
- crypto-policies-scripts
|
||||
- cups-devel
|
||||
@ -60,6 +62,7 @@ packages:
|
||||
- mingw64-gcc
|
||||
- ncurses-devel
|
||||
- openldap2-devel
|
||||
- openssl-devel
|
||||
- pam-devel
|
||||
- patch
|
||||
- perl
|
||||
@ -94,6 +97,7 @@ packages:
|
||||
- tar
|
||||
- tracker-devel
|
||||
- tree
|
||||
- utf8proc-devel
|
||||
- wget
|
||||
- which
|
||||
- xfsprogs-devel
|
||||
|
@ -10,6 +10,11 @@ set -xueo pipefail
|
||||
yum update -y
|
||||
yum install -y dnf-plugins-core
|
||||
yum install -y epel-release
|
||||
yum install -y centos-release-ceph-pacific
|
||||
|
||||
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Ceph-*
|
||||
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Ceph-*
|
||||
sed -i 's/$contentdir/centos/g' /etc/yum.repos.d/CentOS-Ceph-*
|
||||
|
||||
yum -v repolist all
|
||||
yum config-manager --set-enabled powertools -y
|
||||
@ -18,6 +23,7 @@ yum update -y
|
||||
|
||||
yum install -y \
|
||||
--setopt=install_weak_deps=False \
|
||||
--setopt=centos-ceph-pacific.module_hotfixes=true \
|
||||
"@Development Tools" \
|
||||
acl \
|
||||
attr \
|
||||
@ -26,8 +32,10 @@ yum install -y \
|
||||
bind-utils \
|
||||
binutils \
|
||||
bison \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
clang-devel \
|
||||
crypto-policies-scripts \
|
||||
cups-devel \
|
||||
curl \
|
||||
@ -77,6 +85,7 @@ yum install -y \
|
||||
mingw64-gcc \
|
||||
ncurses-devel \
|
||||
openldap-devel \
|
||||
openssl-devel \
|
||||
pam-devel \
|
||||
patch \
|
||||
perl \
|
||||
@ -115,6 +124,7 @@ yum install -y \
|
||||
tar \
|
||||
tracker-devel \
|
||||
tree \
|
||||
utf8proc-devel \
|
||||
wget \
|
||||
which \
|
||||
xfsprogs-devel \
|
||||
|
@ -8,8 +8,10 @@ packages:
|
||||
- bind-utils
|
||||
- binutils
|
||||
- bison
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- clang-devel
|
||||
- crypto-policies-scripts
|
||||
- cups-devel
|
||||
- curl
|
||||
@ -59,6 +61,7 @@ packages:
|
||||
- mingw64-gcc
|
||||
- ncurses-devel
|
||||
- openldap-devel
|
||||
- openssl-devel
|
||||
- pam-devel
|
||||
- patch
|
||||
- perl
|
||||
@ -97,6 +100,7 @@ packages:
|
||||
- tar
|
||||
- tracker-devel
|
||||
- tree
|
||||
- utf8proc-devel
|
||||
- wget
|
||||
- which
|
||||
- xfsprogs-devel
|
||||
|
@ -19,6 +19,7 @@ apt-get -y install \
|
||||
binutils \
|
||||
bison \
|
||||
build-essential \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
codespell \
|
||||
@ -51,6 +52,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -69,11 +71,13 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libtracker-sparql-2.0-dev \
|
||||
libunwind-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -8,6 +8,7 @@ packages:
|
||||
- binutils
|
||||
- bison
|
||||
- build-essential
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- codespell
|
||||
@ -40,6 +41,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -58,11 +60,13 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libtracker-sparql-2.0-dev
|
||||
- libunwind-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -19,6 +19,7 @@ apt-get -y install \
|
||||
binutils \
|
||||
bison \
|
||||
build-essential \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
codespell \
|
||||
@ -51,6 +52,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -69,11 +71,13 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libtracker-sparql-2.0-dev \
|
||||
libunwind-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -8,6 +8,7 @@ packages:
|
||||
- binutils
|
||||
- bison
|
||||
- build-essential
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- codespell
|
||||
@ -40,6 +41,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -58,11 +60,13 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libtracker-sparql-2.0-dev
|
||||
- libunwind-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -19,6 +19,7 @@ apt-get -y install \
|
||||
binutils \
|
||||
bison \
|
||||
build-essential \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
codespell \
|
||||
@ -51,6 +52,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -69,11 +71,13 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libtracker-sparql-2.0-dev \
|
||||
libunwind-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -8,6 +8,7 @@ packages:
|
||||
- binutils
|
||||
- bison
|
||||
- build-essential
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- codespell
|
||||
@ -40,6 +41,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -58,11 +60,13 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libtracker-sparql-2.0-dev
|
||||
- libunwind-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -19,6 +19,7 @@ apt-get -y install \
|
||||
binutils \
|
||||
bison \
|
||||
build-essential \
|
||||
cargo \
|
||||
ccache \
|
||||
chrpath \
|
||||
codespell \
|
||||
@ -51,6 +52,7 @@ apt-get -y install \
|
||||
libbsd-dev \
|
||||
libcap-dev \
|
||||
libcephfs-dev \
|
||||
libclang-dev \
|
||||
libcups2-dev \
|
||||
libdbus-1-dev \
|
||||
libglib2.0-dev \
|
||||
@ -69,11 +71,13 @@ apt-get -y install \
|
||||
libpcap-dev \
|
||||
libpopt-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtasn1-bin \
|
||||
libtasn1-dev \
|
||||
libunwind-dev \
|
||||
liburing-dev \
|
||||
libutf8proc-dev \
|
||||
lmdb-utils \
|
||||
locales \
|
||||
lsb-release \
|
||||
|
@ -8,6 +8,7 @@ packages:
|
||||
- binutils
|
||||
- bison
|
||||
- build-essential
|
||||
- cargo
|
||||
- ccache
|
||||
- chrpath
|
||||
- codespell
|
||||
@ -40,6 +41,7 @@ packages:
|
||||
- libbsd-dev
|
||||
- libcap-dev
|
||||
- libcephfs-dev
|
||||
- libclang-dev
|
||||
- libcups2-dev
|
||||
- libdbus-1-dev
|
||||
- libglib2.0-dev
|
||||
@ -58,11 +60,13 @@ packages:
|
||||
- libpcap-dev
|
||||
- libpopt-dev
|
||||
- libreadline-dev
|
||||
- libssl-dev
|
||||
- libsystemd-dev
|
||||
- libtasn1-bin
|
||||
- libtasn1-dev
|
||||
- libunwind-dev
|
||||
- liburing-dev
|
||||
- libutf8proc-dev
|
||||
- lmdb-utils
|
||||
- locales
|
||||
- lsb-release
|
||||
|
@ -1 +1 @@
|
||||
d1ce7e10953d16253a34b8e58077fd32c1dbd59c
|
||||
936722ecb26bedf6ea0acd9228963ce45ed419d4
|
||||
|
89
buildtools/wafsamba/samba_rust.py
Normal file
89
buildtools/wafsamba/samba_rust.py
Normal file
@ -0,0 +1,89 @@
|
||||
from waflib.Configure import conf
|
||||
from waflib import Build
|
||||
import os
|
||||
|
||||
@conf
|
||||
def SAMBA_CHECK_RUST(conf):
|
||||
conf.find_program('cargo', var='CARGO',
|
||||
mandatory=conf.env.enable_rust)
|
||||
|
||||
def vendor_sources(bld, enabled=True):
|
||||
# force-disable when we can't build rust modules, so
|
||||
# every single call doesn't need to pass this in.
|
||||
if not bld.env.enable_rust:
|
||||
enabled = False
|
||||
|
||||
# Save time, no need to build rust when fuzzing
|
||||
if bld.env.enable_fuzzing:
|
||||
enabled = False
|
||||
|
||||
# Determine the vendor directory
|
||||
vendor = bld.path.find_or_declare('./vendor')
|
||||
# WAF dependencies can only be explicit files, not directories, so we touch
|
||||
# a file to indicate vendoring has been completed.
|
||||
vendor_exists = '%s.exists' % vendor
|
||||
# Locate the source manifest file
|
||||
source_manifest = bld.path.find_or_declare('../../../rust/Cargo.toml')
|
||||
|
||||
rule = ['${CARGO}', 'vendor',
|
||||
'--manifest-path=${SRC[0].abspath(env)}',
|
||||
'%s' % vendor,
|
||||
'&& touch %s' % vendor_exists]
|
||||
bld.SAMBA_GENERATOR('vendor.exists',
|
||||
' '.join(rule),
|
||||
source=source_manifest,
|
||||
target=vendor_exists,
|
||||
group='final',
|
||||
enabled=enabled)
|
||||
Build.BuildContext.vendor_sources = vendor_sources
|
||||
|
||||
def find_sources(source_dir, dep_crate):
|
||||
sources = []
|
||||
for root, dirs, files in os.walk(os.path.join(source_dir, dep_crate)):
|
||||
for file in files:
|
||||
if os.path.splitext(file)[-1] in ['.rs', '.c', '.h']:
|
||||
sources.append(os.path.join(root, file))
|
||||
return sources
|
||||
|
||||
def SAMBA_RUST(bld, rust_subdir, target_name, dep_crates=[], enabled=True):
|
||||
# force-disable when we can't build rust modules, so
|
||||
# every single call doesn't need to pass this in.
|
||||
if not bld.env.enable_rust:
|
||||
enabled = False
|
||||
|
||||
# Save time, no need to build rust when fuzzing
|
||||
if bld.env.enable_fuzzing:
|
||||
enabled = False
|
||||
|
||||
release_flag = ''
|
||||
if bld.env.debug or bld.env.developer:
|
||||
target = os.path.join('debug', target_name)
|
||||
else:
|
||||
release_flag = '--release'
|
||||
target = os.path.join('release', target_name)
|
||||
target = bld.path.find_or_declare(target)
|
||||
# The Rust target directory is one directory above the located target
|
||||
target_dir = os.path.join(os.path.dirname('%s' % target), '../')
|
||||
# Try to determine the source directory
|
||||
source_dir = os.path.abspath(os.path.join(target_dir, '../../../rust'))
|
||||
if not os.path.exists(source_dir):
|
||||
raise Exception('Failed to determine rust source directory')
|
||||
# Now determine the sources of each local crate
|
||||
sources = find_sources(source_dir, rust_subdir)
|
||||
for dep_crate in dep_crates:
|
||||
sources.extend(find_sources(source_dir, dep_crate))
|
||||
sources = [os.path.relpath(p, source_dir) for p in sources]
|
||||
|
||||
rule = ['${CARGO}', 'build',
|
||||
'--manifest-path=${SRC[0].abspath(env)}',
|
||||
'--target-dir=%s' % target_dir,
|
||||
release_flag]
|
||||
bld.SAMBA_GENERATOR(target_name,
|
||||
' '.join(rule),
|
||||
source='%s/Cargo.toml vendor.exists %s' % \
|
||||
(rust_subdir, ' '.join(sources)),
|
||||
target=target,
|
||||
group='final',
|
||||
enabled=enabled)
|
||||
Build.BuildContext.SAMBA_RUST_LIBRARY = SAMBA_RUST
|
||||
Build.BuildContext.SAMBA_RUST_BINARY = SAMBA_RUST
|
@ -19,6 +19,7 @@ from samba_autoproto import *
|
||||
from samba_python import *
|
||||
from samba_perl import *
|
||||
from samba_deps import *
|
||||
from samba_rust import *
|
||||
from samba_bundled import *
|
||||
from samba_third_party import *
|
||||
import samba_cross
|
||||
|
@ -8,6 +8,13 @@ import wafsamba
|
||||
from samba_utils import symlink
|
||||
from optparse import SUPPRESS_HELP
|
||||
|
||||
def get_libc_version():
|
||||
import ctypes
|
||||
libc = ctypes.CDLL("libc.so.6")
|
||||
gnu_get_libc_version = libc.gnu_get_libc_version
|
||||
gnu_get_libc_version.restype = ctypes.c_char_p
|
||||
return gnu_get_libc_version().decode()
|
||||
|
||||
phs = os.environ.get("PYTHONHASHSEED", None)
|
||||
if phs != "1":
|
||||
raise Errors.WafError('''PYTHONHASHSEED=1 missing! Don't use waf directly, use ./configure and make!''')
|
||||
@ -288,6 +295,9 @@ Currently the only tested value is 'smbtorture,smbd/smbd' for Samba'''),
|
||||
opt.add_option('--disable-python',
|
||||
help='do not generate python modules',
|
||||
action='store_true', dest='disable_python', default=False)
|
||||
opt.add_option('--enable-rust',
|
||||
help='build rust modules',
|
||||
action='store_true', dest='enable_rust', default=False)
|
||||
|
||||
|
||||
@Utils.run_once
|
||||
@ -351,6 +361,13 @@ def configure(conf):
|
||||
conf.env.AUTOCONF_PROGRAM_PREFIX = Options.options.AUTOCONF_PROGRAM_PREFIX
|
||||
|
||||
conf.env.disable_python = Options.options.disable_python
|
||||
conf.env.enable_rust = Options.options.enable_rust
|
||||
if Options.options.enable_rust:
|
||||
glibc_vers = float('.'.join(get_libc_version().split('.')[:2]))
|
||||
if glibc_vers < 2.32:
|
||||
conf.fatal('--enable-rust cannot be specified with '
|
||||
'glibc version %s' % glibc_vers)
|
||||
conf.DEFINE('HAVE_RUST', '1')
|
||||
|
||||
if (conf.env.AUTOCONF_HOST and
|
||||
conf.env.AUTOCONF_BUILD and
|
||||
|
@ -19,4 +19,5 @@ echo "
|
||||
<!ENTITY pathconfig.SAMBA_DATADIR '\${prefix}/var/samba'>
|
||||
<!ENTITY pathconfig.CTDB_DATADIR '\${prefix}/share/ctdb'>
|
||||
<!ENTITY pathconfig.CONFIGFILE '\${prefix}/etc/smb.conf'>
|
||||
<!ENTITY pathconfig.HIMMELBLAUD_HSM_PIN_PATH '\${prefix}/var/lib/himmelblaud/hsm-pin'>
|
||||
"
|
||||
|
18
docs-xml/smbdotconf/himmelblaud/himmelblaudhelloenabled.xml
Normal file
18
docs-xml/smbdotconf/himmelblaud/himmelblaudhelloenabled.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<samba:parameter name="himmelblaud hello enabled"
|
||||
context="G"
|
||||
type="boolean"
|
||||
xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
|
||||
<description>
|
||||
|
||||
<para>This parameter controls Hello enrollment and authentication to
|
||||
Azure Entra ID. By default, it is disabled to prevent security risks,
|
||||
such as on hosts exposing the SSH port. Administrators should enable
|
||||
this setting only when Hello enrollment is appropriate for their
|
||||
environment.
|
||||
|
||||
</para>
|
||||
</description>
|
||||
|
||||
<value type="default">no</value>
|
||||
<value type="example">yes</value>
|
||||
</samba:parameter>
|
13
docs-xml/smbdotconf/himmelblaud/himmelblaudhsmpinpath.xml
Normal file
13
docs-xml/smbdotconf/himmelblaud/himmelblaudhsmpinpath.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<samba:parameter name="himmelblaud hsm pin path"
|
||||
context="G"
|
||||
type="string"
|
||||
xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
|
||||
<description>
|
||||
<para>Specifies the file path where the HSM PIN is stored. This PIN is used
|
||||
for unlocking TPM objects required for Azure Entra ID authentication. The HSM
|
||||
PIN is critical for ensuring secure communication and authentication within
|
||||
the Himmelblaud daemon.</para>
|
||||
</description>
|
||||
|
||||
<value type="default">&pathconfig.HIMMELBLAUD_HSM_PIN_PATH;</value>
|
||||
</samba:parameter>
|
17
docs-xml/smbdotconf/himmelblaud/himmelblaudsfafallback.xml
Normal file
17
docs-xml/smbdotconf/himmelblaud/himmelblaudsfafallback.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<samba:parameter name="himmelblaud sfa fallback"
|
||||
context="G"
|
||||
type="boolean"
|
||||
xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
|
||||
<description>
|
||||
|
||||
<para>This parameter is designed to control whether Himmelblaud should fallback to
|
||||
Single Factor Authentication (SFA) if Multi-Factor Authentication (MFA) isn't
|
||||
available. This normally is possible during a short window during which MFA
|
||||
enrollment is available to a new user.
|
||||
|
||||
</para>
|
||||
</description>
|
||||
|
||||
<value type="default">no</value>
|
||||
<value type="example">yes</value>
|
||||
</samba:parameter>
|
@ -107,3 +107,4 @@ DEFINE_DYN_CONFIG_PARAM(NTP_SIGND_SOCKET_DIR)
|
||||
DEFINE_DYN_CONFIG_PARAM(PYTHONDIR)
|
||||
DEFINE_DYN_CONFIG_PARAM(PYTHONARCHDIR)
|
||||
DEFINE_DYN_CONFIG_PARAM(SCRIPTSBINDIR)
|
||||
DEFINE_DYN_CONFIG_PARAM(HIMMELBLAUD_HSM_PIN_PATH)
|
||||
|
@ -58,3 +58,4 @@ DEFINE_DYN_CONFIG_PROTO(NTP_SIGND_SOCKET_DIR)
|
||||
DEFINE_DYN_CONFIG_PROTO(PYTHONDIR)
|
||||
DEFINE_DYN_CONFIG_PROTO(PYTHONARCHDIR)
|
||||
DEFINE_DYN_CONFIG_PROTO(SCRIPTSBINDIR)
|
||||
DEFINE_DYN_CONFIG_PROTO(HIMMELBLAUD_HSM_PIN_PATH)
|
||||
|
@ -285,6 +285,13 @@ dynconfig = {
|
||||
'HELPTEXT': 'Where to put the smbpasswd file',
|
||||
'DELAY': True,
|
||||
},
|
||||
'HIMMELBLAUD_HSM_PIN_PATH': {
|
||||
'STD-PATH': '${LOCALSTATEDIR}/lib/himmelblaud/hsm-pin',
|
||||
'FHS-PATH': '${LOCALSTATEDIR}/lib/himmelblaud/hsm-pin',
|
||||
'OPTION': '--with-himmelblaud-hsm-pin-path',
|
||||
'HELPTEXT': 'Where to store the hsm pin',
|
||||
'DELAY': True,
|
||||
},
|
||||
}
|
||||
|
||||
def options(opt):
|
||||
|
@ -3165,6 +3165,17 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx)
|
||||
"acl claims evaluation",
|
||||
"AD DC only");
|
||||
|
||||
/* Set the default Himmelblaud globals */
|
||||
lpcfg_do_global_parameter(lp_ctx,
|
||||
"himmelblaud hsm pin path",
|
||||
get_dyn_HIMMELBLAUD_HSM_PIN_PATH());
|
||||
lpcfg_do_global_parameter(lp_ctx,
|
||||
"himmelblaud hello enabled",
|
||||
"false");
|
||||
lpcfg_do_global_parameter(lp_ctx,
|
||||
"himmelblaud sfa fallback",
|
||||
"false");
|
||||
|
||||
for (i = 0; parm_table[i].label; i++) {
|
||||
if (!(lp_ctx->flags[i] & FLAG_CMDLINE)) {
|
||||
lp_ctx->flags[i] |= FLAG_DEFAULT;
|
||||
|
@ -22,9 +22,9 @@ bld.SAMBA_GENERATOR('hresult_generated',
|
||||
|
||||
bld.SAMBA_GENERATOR('ntstatus_generated',
|
||||
source='../../source4/scripting/bin/gen_ntstatus.py ntstatus_err_table.txt ../../source4/scripting/bin/gen_error_common.py',
|
||||
target='ntstatus_gen.h nterr_gen.c py_ntstatus.c',
|
||||
target='ntstatus_gen.h nterr_gen.c py_ntstatus.c ntstatus_gen.rs',
|
||||
group='build_source',
|
||||
rule='${PYTHON} ${SRC[0].abspath(env)} ${SRC[1].abspath(env)} ${TGT[0].abspath(env)} ${TGT[1].abspath(env)} ${TGT[2].abspath(env)}'
|
||||
rule='${PYTHON} ${SRC[0].abspath(env)} ${SRC[1].abspath(env)} ${TGT[0].abspath(env)} ${TGT[1].abspath(env)} ${TGT[2].abspath(env)} ${TGT[3].abspath(env)}'
|
||||
)
|
||||
|
||||
bld.SAMBA_GENERATOR('werror_generated',
|
||||
|
@ -309,6 +309,16 @@ static PyObject *py_is_ad_dc_built(PyObject *self,
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject *py_is_rust_built(PyObject *self,
|
||||
PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
#ifdef HAVE_RUST
|
||||
Py_RETURN_TRUE;
|
||||
#else
|
||||
Py_RETURN_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject *py_is_selftest_enabled(PyObject *self,
|
||||
PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
@ -580,6 +590,8 @@ static PyMethodDef py_misc_methods[] = {
|
||||
METH_NOARGS, "How many NDR internal tokens is too many for this build?" },
|
||||
{ "get_burnt_commandline", (PyCFunction)py_get_burnt_commandline,
|
||||
METH_VARARGS, "Return a redacted commandline to feed to setproctitle (None if no redaction required)" },
|
||||
{ "is_rust_built", (PyCFunction)py_is_rust_built, METH_NOARGS,
|
||||
"is Samba built with Rust?" },
|
||||
{0}
|
||||
};
|
||||
|
||||
|
@ -432,6 +432,7 @@ is_ntvfs_fileserver_built = _glue.is_ntvfs_fileserver_built
|
||||
is_heimdal_built = _glue.is_heimdal_built
|
||||
is_ad_dc_built = _glue.is_ad_dc_built
|
||||
is_selftest_enabled = _glue.is_selftest_enabled
|
||||
is_rust_built = _glue.is_rust_built
|
||||
|
||||
NTSTATUSError = _glue.NTSTATUSError
|
||||
HRESULTError = _glue.HRESULTError
|
||||
|
81
python/samba/tests/rust.py
Normal file
81
python/samba/tests/rust.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Unix SMB/CIFS implementation.
|
||||
#
|
||||
# Tests for Rust
|
||||
#
|
||||
# Copyright (C) David Mulder <dmulder@samba.org> 2024
|
||||
#
|
||||
# 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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""Cargo tests for Rust sources"""
|
||||
|
||||
from samba.tests import TestCase, BlackboxProcessError
|
||||
import os
|
||||
from subprocess import Popen, PIPE
|
||||
from samba import is_rust_built
|
||||
|
||||
|
||||
class RustCargoTests(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Locate the rust source directory
|
||||
self.rust_dir = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.realpath(
|
||||
os.path.dirname(__file__)
|
||||
),
|
||||
'../../../../rust'
|
||||
)
|
||||
)
|
||||
|
||||
# Locate the bin directory
|
||||
self.target_dir = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.realpath(
|
||||
os.path.dirname(__file__)
|
||||
),
|
||||
'../../..',
|
||||
'default/rust',
|
||||
)
|
||||
)
|
||||
|
||||
def check_cargo_test(self, crate_toml):
|
||||
# Execute the cargo test command
|
||||
cmd = 'cargo test --target-dir=%s --manifest-path=%s' % (self.target_dir, crate_toml)
|
||||
p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
|
||||
stdoutdata, stderrdata = p.communicate()
|
||||
retcode = p.returncode
|
||||
if retcode != 0:
|
||||
msg = "cargo test failed; return code %s" % retcode
|
||||
raise BlackboxProcessError(retcode,
|
||||
cmd,
|
||||
stdoutdata.decode('utf-8'),
|
||||
stderrdata.decode('utf-8'),
|
||||
msg)
|
||||
|
||||
def test_rust(self):
|
||||
if not is_rust_built():
|
||||
self.skipTest('Cannot test Samba Rust if not built')
|
||||
|
||||
crates = []
|
||||
for root, dirs, files in os.walk(self.rust_dir):
|
||||
for file in files:
|
||||
if os.path.basename(file) == 'Cargo.toml':
|
||||
if root != self.rust_dir:
|
||||
crates.append(os.path.join(root, file))
|
||||
|
||||
for crate_toml in crates:
|
||||
with self.subTest(crate_toml):
|
||||
self.check_cargo_test(crate_toml)
|
@ -108,6 +108,8 @@ EXCLUDE_DIRS = {
|
||||
'python/samba/tests/dcerpc',
|
||||
'python/samba/tests/krb5',
|
||||
'python/samba/tests/ndr',
|
||||
'bin/default/rust/vendor/unicode-width/scripts',
|
||||
'bin/default/rust/vendor/rustls-webpki/tests',
|
||||
}
|
||||
|
||||
|
||||
|
5
rust/.cargo/config.toml
Normal file
5
rust/.cargo/config.toml
Normal file
@ -0,0 +1,5 @@
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source.vendored-sources]
|
||||
directory = "../bin/default/rust/vendor"
|
14
rust/.gitignore
vendored
Normal file
14
rust/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# ---> Rust
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
vendor/
|
||||
tags
|
2899
rust/Cargo.lock
generated
Normal file
2899
rust/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
rust/Cargo.toml
Normal file
24
rust/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
homepage = "https://www.samba.org/"
|
||||
version = "4.21.0"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"chelps", "config", "dbg", "himmelblaud", "idmap",
|
||||
"nss", "ntstatus_gen", "pam",
|
||||
"param", "sock", "talloc", "tdb", "version",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
param = { path = "param" }
|
||||
dbg = { path = "dbg" }
|
||||
chelps = { path = "chelps" }
|
||||
sock = { path = "sock" }
|
||||
ntstatus_gen = { path = "ntstatus_gen" }
|
||||
tdb = { path = "tdb" }
|
||||
idmap = { path = "idmap" }
|
||||
libc = "0.2.155"
|
||||
config = { path = "config" }
|
19
rust/build.rs
Normal file
19
rust/build.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
if let Some(vers) = version::samba_version_string() {
|
||||
println!("cargo:rustc-env=CARGO_PKG_VERSION={}", vers);
|
||||
}
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION_MAJOR={}",
|
||||
version::SAMBA_VERSION_MAJOR
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION_MINOR={}",
|
||||
version::SAMBA_VERSION_MINOR
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION_PATCH={}",
|
||||
version::SAMBA_VERSION_RELEASE
|
||||
);
|
||||
}
|
8
rust/chelps/Cargo.toml
Normal file
8
rust/chelps/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "chelps"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
154
rust/chelps/src/lib.rs
Normal file
154
rust/chelps/src/lib.rs
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
C conversion helper functions and macros
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
|
||||
pub unsafe fn wrap_c_char(input: *const c_char) -> Option<String> {
|
||||
if input.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(input) };
|
||||
match c_str.to_str() {
|
||||
Ok(output) => Some(output.to_string()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_string(input: &str) -> *mut c_char {
|
||||
match CString::new(input.to_string()) {
|
||||
Ok(msg) => msg.into_raw(),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn string_free(input: *mut c_char) {
|
||||
if !input.is_null() {
|
||||
unsafe {
|
||||
let _ = CString::from_raw(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! function {
|
||||
() => {{
|
||||
fn f() {}
|
||||
fn type_name_of<T>(_: T) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
let name = type_name_of(f);
|
||||
|
||||
let base_name = match name.rfind("::") {
|
||||
Some(pos) => &name[..pos],
|
||||
None => name,
|
||||
};
|
||||
let parts: Vec<&str> = base_name
|
||||
.split("::")
|
||||
.filter(|&p| p != "{{closure}}")
|
||||
.collect();
|
||||
parts.join("::")
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
#[test]
|
||||
fn test_wrap_c_char_non_null() {
|
||||
let original = "Hello, world!";
|
||||
let c_string = CString::new(original).expect("CString::new failed");
|
||||
let c_ptr = c_string.as_ptr();
|
||||
|
||||
let result = unsafe { wrap_c_char(c_ptr) };
|
||||
assert_eq!(result, Some(original.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_c_char_null() {
|
||||
let result = unsafe { wrap_c_char(ptr::null()) };
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_c_char_invalid_utf8() {
|
||||
let invalid_utf8 = vec![0xff, 0xff, 0xff, 0xff];
|
||||
let c_string = CString::new(invalid_utf8).expect("CString::new failed");
|
||||
let c_ptr = c_string.as_ptr();
|
||||
|
||||
let result = unsafe { wrap_c_char(c_ptr) };
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_string() {
|
||||
let original = "Hello, world!";
|
||||
let c_ptr = wrap_string(original);
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(c_ptr) };
|
||||
let result = c_str.to_str().expect("CStr::to_str failed");
|
||||
|
||||
assert_eq!(result, original);
|
||||
|
||||
// Clean up the allocated memory
|
||||
unsafe { string_free(c_ptr) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_string_empty() {
|
||||
let original = "";
|
||||
let c_ptr = wrap_string(original);
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(c_ptr) };
|
||||
let result = c_str.to_str().expect("CStr::to_str failed");
|
||||
|
||||
assert_eq!(result, original);
|
||||
|
||||
// Clean up the allocated memory
|
||||
unsafe { string_free(c_ptr) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_string_null_pointer() {
|
||||
let c_ptr = wrap_string("\0");
|
||||
assert!(c_ptr.is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_free_null() {
|
||||
unsafe { string_free(ptr::null_mut()) };
|
||||
// No assertion needed, just ensuring no crash occurs
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_free_non_null() {
|
||||
let original = "Hello, world!";
|
||||
let c_ptr = wrap_string(original);
|
||||
|
||||
unsafe { string_free(c_ptr) };
|
||||
// No assertion needed, just ensuring the memory was freed without a crash
|
||||
}
|
||||
}
|
12
rust/config/Cargo.toml
Normal file
12
rust/config/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "config"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69.4"
|
8
rust/config/additions.h
Normal file
8
rust/config/additions.h
Normal file
@ -0,0 +1,8 @@
|
||||
#include "../../bin/default/include/config.h"
|
||||
#ifndef USING_SYSTEM_TDB
|
||||
#define USING_SYSTEM_TDB 0
|
||||
#endif
|
||||
|
||||
#ifndef USING_SYSTEM_TALLOC
|
||||
#define USING_SYSTEM_TALLOC 0
|
||||
#endif
|
20
rust/config/build.rs
Normal file
20
rust/config/build.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let header = "../../bin/default/include/config.h";
|
||||
println!("cargo:rerun-if-changed={}", header);
|
||||
let additions_header = "./additions.h";
|
||||
println!("cargo:rerun-if-changed={}", additions_header);
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(additions_header)
|
||||
.header(header)
|
||||
.generate()
|
||||
.expect("Failed generating config bindings!");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
}
|
52
rust/config/src/lib.rs
Normal file
52
rust/config/src/lib.rs
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Samba config imported into Rust
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_using_system_tdb() {
|
||||
// This test just ensures that USING_SYSTEM_TDB is available from the
|
||||
// config. None of the other options are really used at the moment.
|
||||
assert!(
|
||||
USING_SYSTEM_TDB == 0 || USING_SYSTEM_TDB == 1,
|
||||
"Unexpected value for USING_SYSTEM_TDB: {}",
|
||||
USING_SYSTEM_TDB
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_using_system_talloc() {
|
||||
assert!(
|
||||
USING_SYSTEM_TALLOC == 0 || USING_SYSTEM_TALLOC == 1,
|
||||
"Unexpected value for USING_SYSTEM_TALLOC: {}",
|
||||
USING_SYSTEM_TALLOC
|
||||
);
|
||||
}
|
||||
}
|
16
rust/dbg/Cargo.toml
Normal file
16
rust/dbg/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "dbg"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69.4"
|
||||
|
||||
[dependencies]
|
||||
chelps.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.12.0"
|
||||
paste = "1.0.15"
|
25
rust/dbg/build.rs
Normal file
25
rust/dbg/build.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("../../lib/util/debug.h")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
|
||||
let mut src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
src_dir.push("../../bin/default/lib/util");
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
src_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=samba-debug-private-samba");
|
||||
println!("cargo:rustc-link-lib=samba-util");
|
||||
println!("cargo:rustc-env=LD_LIBRARY_PATH=../../bin/shared:../../bin/shared/private/");
|
||||
}
|
217
rust/dbg/src/lib.rs
Normal file
217
rust/dbg/src/lib.rs
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Parameter loading functions
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pub mod ffi {
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
pub const MAX_DEBUG_LEVEL: u32 = ffi::MAX_DEBUG_LEVEL;
|
||||
pub const DBGLVL_ERR: u32 = ffi::DBGLVL_ERR;
|
||||
pub const DBGLVL_WARNING: u32 = ffi::DBGLVL_WARNING;
|
||||
pub const DBGLVL_NOTICE: u32 = ffi::DBGLVL_NOTICE;
|
||||
pub const DBGLVL_INFO: u32 = ffi::DBGLVL_INFO;
|
||||
pub const DBGLVL_DEBUG: u32 = ffi::DBGLVL_DEBUG;
|
||||
|
||||
pub const DEBUG_DEFAULT_STDERR: ffi::debug_logtype =
|
||||
ffi::debug_logtype_DEBUG_DEFAULT_STDERR;
|
||||
pub const DEBUG_DEFAULT_STDOUT: ffi::debug_logtype =
|
||||
ffi::debug_logtype_DEBUG_DEFAULT_STDOUT;
|
||||
pub const DEBUG_FILE: ffi::debug_logtype = ffi::debug_logtype_DEBUG_FILE;
|
||||
pub const DEBUG_STDOUT: ffi::debug_logtype = ffi::debug_logtype_DEBUG_STDOUT;
|
||||
pub const DEBUG_STDERR: ffi::debug_logtype = ffi::debug_logtype_DEBUG_STDERR;
|
||||
pub const DEBUG_CALLBACK: ffi::debug_logtype =
|
||||
ffi::debug_logtype_DEBUG_CALLBACK;
|
||||
|
||||
pub fn debug_set_logfile(name: &str) {
|
||||
let name_cstr = chelps::wrap_string(name);
|
||||
unsafe {
|
||||
ffi::debug_set_logfile(name_cstr);
|
||||
chelps::string_free(name_cstr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_logging(prog_name: &str, new_logtype: ffi::debug_logtype) {
|
||||
let prog_name_cstr = chelps::wrap_string(prog_name);
|
||||
unsafe {
|
||||
ffi::setup_logging(prog_name_cstr, new_logtype);
|
||||
chelps::string_free(prog_name_cstr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dbgflush() {
|
||||
unsafe {
|
||||
ffi::dbgflush();
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! debuglevel_set {
|
||||
($level:expr) => {{
|
||||
unsafe {
|
||||
$crate::ffi::debuglevel_set_class(
|
||||
$crate::ffi::DBGC_ALL as usize,
|
||||
$level as i32,
|
||||
)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! DBG_PREFIX {
|
||||
($level:expr $(, $arg:expr)* $(,)?) => {{
|
||||
if $level <= $crate::ffi::MAX_DEBUG_LEVEL {
|
||||
let location = format!("{}:{}", file!(), line!());
|
||||
let location_cstr = chelps::wrap_string(&location);
|
||||
let function = chelps::function!();
|
||||
let function_msg = format!("{}: ", function);
|
||||
let function_cstr = chelps::wrap_string(&function);
|
||||
let function_msg_cstr = chelps::wrap_string(&function_msg);
|
||||
// Always append a newline to the debug, otherwise it won't flush
|
||||
// to the log.
|
||||
let msg = format!("{}\n", format!($($arg),*));
|
||||
let msg_cstr = chelps::wrap_string(&msg);
|
||||
unsafe {
|
||||
let _ = $crate::ffi::debuglevel_get_class($crate::ffi::DBGC_CLASS as usize) >= ($level as i32)
|
||||
&& $crate::ffi::dbghdrclass($level as i32,
|
||||
$crate::ffi::DBGC_CLASS as i32,
|
||||
location_cstr,
|
||||
function_cstr)
|
||||
&& $crate::ffi::dbgtext(function_msg_cstr)
|
||||
&& $crate::ffi::dbgtext(msg_cstr);
|
||||
chelps::string_free(location_cstr);
|
||||
chelps::string_free(function_cstr);
|
||||
chelps::string_free(function_msg_cstr);
|
||||
chelps::string_free(msg_cstr);
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! DBG_ERR {
|
||||
($msg:expr $(, $arg:expr)* $(,)?) => {{
|
||||
$crate::DBG_PREFIX!($crate::ffi::DBGLVL_ERR, $msg, $($arg),*)
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! DBG_WARNING {
|
||||
($msg:expr $(, $arg:expr)* $(,)?) => {{
|
||||
$crate::DBG_PREFIX!($crate::ffi::DBGLVL_WARNING, $msg, $($arg),*)
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! DBG_NOTICE {
|
||||
($msg:expr $(, $arg:expr)* $(,)?) => {{
|
||||
$crate::DBG_PREFIX!($crate::ffi::DBGLVL_NOTICE, $msg, $($arg),*)
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! DBG_INFO {
|
||||
($msg:expr $(, $arg:expr)* $(,)?) => {{
|
||||
$crate::DBG_PREFIX!($crate::ffi::DBGLVL_INFO, $msg, $($arg),*)
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! DBG_DEBUG {
|
||||
($msg:expr $(, $arg:expr)* $(,)?) => {{
|
||||
$crate::DBG_PREFIX!($crate::ffi::DBGLVL_DEBUG, $msg, $($arg),*)
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use paste::paste;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn test_debug_constants() {
|
||||
assert_eq!(MAX_DEBUG_LEVEL, ffi::MAX_DEBUG_LEVEL);
|
||||
assert_eq!(DBGLVL_ERR, ffi::DBGLVL_ERR);
|
||||
assert_eq!(DBGLVL_WARNING, ffi::DBGLVL_WARNING);
|
||||
assert_eq!(DBGLVL_NOTICE, ffi::DBGLVL_NOTICE);
|
||||
assert_eq!(DBGLVL_INFO, ffi::DBGLVL_INFO);
|
||||
assert_eq!(DBGLVL_DEBUG, ffi::DBGLVL_DEBUG);
|
||||
|
||||
assert_eq!(
|
||||
DEBUG_DEFAULT_STDERR,
|
||||
ffi::debug_logtype_DEBUG_DEFAULT_STDERR
|
||||
);
|
||||
assert_eq!(
|
||||
DEBUG_DEFAULT_STDOUT,
|
||||
ffi::debug_logtype_DEBUG_DEFAULT_STDOUT
|
||||
);
|
||||
assert_eq!(DEBUG_FILE, ffi::debug_logtype_DEBUG_FILE);
|
||||
assert_eq!(DEBUG_STDOUT, ffi::debug_logtype_DEBUG_STDOUT);
|
||||
assert_eq!(DEBUG_STDERR, ffi::debug_logtype_DEBUG_STDERR);
|
||||
assert_eq!(DEBUG_CALLBACK, ffi::debug_logtype_DEBUG_CALLBACK);
|
||||
}
|
||||
|
||||
macro_rules! test_dbg_macro {
|
||||
($level:ident) => {
|
||||
paste! {
|
||||
#[test]
|
||||
fn [<test_dbg_ $level:lower _macro>]() {
|
||||
let logfile = NamedTempFile::new().expect("Failed to create temporary file");
|
||||
let logfile = logfile.path().to_str().unwrap();
|
||||
setup_logging("test_program", DEBUG_FILE);
|
||||
debug_set_logfile(logfile);
|
||||
|
||||
let logfile_output = concat!("This is a ", stringify!($level), " message");
|
||||
|
||||
debuglevel_set!([<DBGLVL_ $level:upper>]);
|
||||
|
||||
[<DBG_ $level:upper>]!("{}\n", logfile_output);
|
||||
dbgflush();
|
||||
|
||||
let mut file = File::open(logfile).expect("Failed to open logfile");
|
||||
let mut logfile_contents = String::new();
|
||||
file.read_to_string(&mut logfile_contents)
|
||||
.expect("Failed to read logfile");
|
||||
assert!(
|
||||
logfile_contents.contains(logfile_output),
|
||||
"Test data missing from logfile: {}",
|
||||
logfile_contents
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_dbg_macro!(DEBUG);
|
||||
// Multiple re-inits of the debug env cause it to fail, so we can't
|
||||
// reliably test all of these in one go.
|
||||
//test_dbg_macro!(INFO);
|
||||
//test_dbg_macro!(NOTICE);
|
||||
//test_dbg_macro!(WARNING);
|
||||
//test_dbg_macro!(ERR);
|
||||
}
|
32
rust/himmelblaud/Cargo.toml
Normal file
32
rust/himmelblaud/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "himmelblaud"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libhimmelblau = "0.2.9"
|
||||
ntstatus_gen.workspace = true
|
||||
param = { workspace = true }
|
||||
sock = { workspace = true }
|
||||
tdb = { workspace = true }
|
||||
dbg = { workspace = true }
|
||||
chelps.workspace = true
|
||||
clap = "4.5.9"
|
||||
kanidm-hsm-crypto = "0.2.0"
|
||||
serde_json = "1.0.120"
|
||||
tokio = { version = "1.38.1", features = [ "rt", "macros" ] }
|
||||
tokio-util = { version = "0.7.11", features = ["codec"] }
|
||||
bytes = "1.6.1"
|
||||
futures = "0.3.30"
|
||||
serde = "1.0.204"
|
||||
idmap = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
talloc = { version = "4.21.0", path = "../talloc" }
|
||||
|
||||
[build-dependencies]
|
||||
version = { path = "../version" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.12.0"
|
3
rust/himmelblaud/build.rs
Normal file
3
rust/himmelblaud/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-env=LD_LIBRARY_PATH=../../bin/shared:../../bin/shared/private/");
|
||||
}
|
658
rust/himmelblaud/src/cache.rs
Normal file
658
rust/himmelblaud/src/cache.rs
Normal file
@ -0,0 +1,658 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon cache
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use dbg::DBG_ERR;
|
||||
use himmelblau::error::MsalError;
|
||||
use himmelblau::graph::DirectoryObject;
|
||||
use himmelblau::UserToken;
|
||||
use kanidm_hsm_crypto::{
|
||||
AuthValue, BoxedDynTpm, LoadableIdentityKey, LoadableMachineKey,
|
||||
LoadableMsOapxbcRsaKey, Tpm,
|
||||
};
|
||||
use libc::uid_t;
|
||||
use ntstatus_gen::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{from_slice as json_from_slice, to_vec as json_to_vec};
|
||||
use std::collections::HashSet;
|
||||
use tdb::Tdb;
|
||||
|
||||
struct BasicCache {
|
||||
tdb: Tdb,
|
||||
}
|
||||
|
||||
impl BasicCache {
|
||||
fn new(cache_path: &str) -> Result<Self, Box<NTSTATUS>> {
|
||||
let tdb =
|
||||
Tdb::open(cache_path, None, None, None, None).map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_FILE_INVALID)
|
||||
})?;
|
||||
Ok(BasicCache { tdb })
|
||||
}
|
||||
|
||||
fn fetch_str(&self, key: &str) -> Option<String> {
|
||||
let key = key.to_string().to_lowercase();
|
||||
let exists = match self.tdb.exists(&key) {
|
||||
Ok(exists) => exists,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to fetch {}: {:?}", key, e);
|
||||
false
|
||||
}
|
||||
};
|
||||
if exists {
|
||||
match self.tdb.fetch(&key) {
|
||||
Ok(val) => Some(val),
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to fetch {}: {:?}", key, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch<'a, T>(&self, key: &str) -> Option<T>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let key = key.to_string().to_lowercase();
|
||||
match self.fetch_str(&key) {
|
||||
Some(val) => match json_from_slice::<T>(val.as_bytes()) {
|
||||
Ok(res) => Some(res),
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to decode {}: {:?}", key, e);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn store_bytes(
|
||||
&mut self,
|
||||
key: &str,
|
||||
val: &[u8],
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let key = key.to_string().to_lowercase();
|
||||
match self.tdb.transaction_start() {
|
||||
Ok(start) => {
|
||||
if !start {
|
||||
DBG_ERR!("Failed to start the database transaction.");
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to start the database transaction: {:?}", e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
|
||||
let res = match self.tdb.store(&key, val, None) {
|
||||
Ok(res) => Some(res),
|
||||
Err(e) => {
|
||||
DBG_ERR!("Unable to persist {}: {:?}", key, e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let res = match res {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
let _ = self.tdb.transaction_cancel();
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
if !res {
|
||||
DBG_ERR!("Unable to persist {}", key);
|
||||
let _ = self.tdb.transaction_cancel();
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
|
||||
let success = match self.tdb.transaction_commit() {
|
||||
Ok(success) => success,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to commit the database transaction: {:?}", e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
if !success {
|
||||
DBG_ERR!("Failed to commit the database transaction.");
|
||||
let _ = self.tdb.transaction_cancel();
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn store<T>(&mut self, key: &str, val: T) -> Result<(), Box<NTSTATUS>>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let key = key.to_string().to_lowercase();
|
||||
let val_bytes = match json_to_vec(&val) {
|
||||
Ok(val_bytes) => val_bytes,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Unable to serialize {}: {:?}", key, e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
self.store_bytes(&key, &val_bytes)
|
||||
}
|
||||
|
||||
fn keys(&self) -> Result<Vec<String>, Box<NTSTATUS>> {
|
||||
self.tdb.keys().map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct UserEntry {
|
||||
pub(crate) upn: String,
|
||||
pub(crate) uuid: String,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&UserToken> for UserEntry {
|
||||
type Error = MsalError;
|
||||
|
||||
fn try_from(token: &UserToken) -> Result<Self, Self::Error> {
|
||||
Ok(UserEntry {
|
||||
upn: token.spn()?,
|
||||
uuid: token.uuid()?.to_string(),
|
||||
name: token.id_token.name.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UserCache {
|
||||
cache: BasicCache,
|
||||
}
|
||||
|
||||
impl UserCache {
|
||||
pub(crate) fn new(cache_path: &str) -> Result<Self, Box<NTSTATUS>> {
|
||||
Ok(UserCache {
|
||||
cache: BasicCache::new(cache_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn fetch(&mut self, upn: &str) -> Option<UserEntry> {
|
||||
self.cache.fetch::<UserEntry>(upn)
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_all(
|
||||
&mut self,
|
||||
) -> Result<Vec<UserEntry>, Box<NTSTATUS>> {
|
||||
let keys = self.cache.keys()?;
|
||||
let mut res = Vec::new();
|
||||
for upn in keys {
|
||||
let entry = match self.cache.fetch::<UserEntry>(&upn) {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
DBG_ERR!("Unable to fetch user {}", upn);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
res.push(entry);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn store(
|
||||
&mut self,
|
||||
entry: UserEntry,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let key = entry.upn.clone();
|
||||
self.cache.store::<UserEntry>(&key, entry)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UidCache {
|
||||
cache: BasicCache,
|
||||
}
|
||||
|
||||
impl UidCache {
|
||||
pub(crate) fn new(cache_path: &str) -> Result<Self, Box<NTSTATUS>> {
|
||||
Ok(UidCache {
|
||||
cache: BasicCache::new(cache_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn store(
|
||||
&mut self,
|
||||
uid: uid_t,
|
||||
upn: &str,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let key = format!("{}", uid);
|
||||
let upn = upn.to_string().to_lowercase();
|
||||
self.cache.store_bytes(&key, upn.as_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn fetch(&mut self, uid: uid_t) -> Option<String> {
|
||||
let key = format!("{}", uid);
|
||||
self.cache.fetch_str(&key)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct GroupEntry {
|
||||
pub(crate) uuid: String,
|
||||
members: HashSet<String>,
|
||||
}
|
||||
|
||||
impl From<DirectoryObject> for GroupEntry {
|
||||
fn from(obj: DirectoryObject) -> Self {
|
||||
GroupEntry {
|
||||
uuid: obj.id.clone(),
|
||||
members: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupEntry {
|
||||
pub(crate) fn add_member(&mut self, member: &str) {
|
||||
// Only ever use lowercase names, otherwise the group
|
||||
// memberships will have duplicates.
|
||||
self.members.insert(member.to_lowercase());
|
||||
}
|
||||
|
||||
pub(crate) fn remove_member(&mut self, member: &str) {
|
||||
// Only ever use lowercase names, otherwise the group
|
||||
// memberships will have duplicates.
|
||||
self.members.remove(&member.to_lowercase());
|
||||
}
|
||||
|
||||
pub(crate) fn into_with_member(obj: DirectoryObject, member: &str) -> Self {
|
||||
let mut g: GroupEntry = obj.into();
|
||||
g.add_member(member);
|
||||
g
|
||||
}
|
||||
|
||||
pub(crate) fn members(&self) -> Vec<String> {
|
||||
self.members.clone().into_iter().collect::<Vec<String>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupEntry {
|
||||
pub fn new(uuid: &str) -> Self {
|
||||
GroupEntry {
|
||||
uuid: uuid.to_string(),
|
||||
members: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GroupCache {
|
||||
cache: BasicCache,
|
||||
}
|
||||
|
||||
impl GroupCache {
|
||||
pub(crate) fn new(cache_path: &str) -> Result<Self, Box<NTSTATUS>> {
|
||||
Ok(GroupCache {
|
||||
cache: BasicCache::new(cache_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn fetch(&mut self, uuid: &str) -> Option<GroupEntry> {
|
||||
self.cache.fetch::<GroupEntry>(uuid)
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_all(
|
||||
&mut self,
|
||||
) -> Result<Vec<GroupEntry>, Box<NTSTATUS>> {
|
||||
let keys = self.cache.keys()?;
|
||||
let mut res = Vec::new();
|
||||
for uuid in keys {
|
||||
let entry = match self.cache.fetch::<GroupEntry>(&uuid) {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
DBG_ERR!("Unable to fetch group {}", uuid);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
res.push(entry);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn merge_groups(
|
||||
&mut self,
|
||||
member: &str,
|
||||
mut entries: Vec<GroupEntry>,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
// We need to ensure the member is removed from non-intersecting
|
||||
// groups, otherwise we don't honor group membership removals.
|
||||
let group_uuids: HashSet<String> = entries
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|g| g.uuid.clone())
|
||||
.collect();
|
||||
let existing_group_uuids = {
|
||||
let cache = &self.cache;
|
||||
match cache.keys() {
|
||||
Ok(keys) => keys,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Unable to fetch groups: {:?}", e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
}
|
||||
};
|
||||
let existing_group_uuids: HashSet<String> =
|
||||
existing_group_uuids.into_iter().collect();
|
||||
let difference: HashSet<String> = existing_group_uuids
|
||||
.difference(&group_uuids)
|
||||
.cloned()
|
||||
.collect();
|
||||
for group_uuid in &difference {
|
||||
if let Some(mut group) =
|
||||
self.cache.fetch::<GroupEntry>(&group_uuid).clone()
|
||||
{
|
||||
group.remove_member(member);
|
||||
if let Err(e) =
|
||||
self.cache.store::<GroupEntry>(&group.uuid.clone(), group)
|
||||
{
|
||||
DBG_ERR!("Unable to store membership change: {:?}", e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the member is added to the listed groups
|
||||
for group in &mut entries {
|
||||
group.add_member(member);
|
||||
}
|
||||
|
||||
// Now add the new entries, merging with existing memberships
|
||||
for group in entries {
|
||||
match self.cache.fetch::<GroupEntry>(&group.uuid) {
|
||||
Some(mut existing_group) => {
|
||||
// Merge with an existing entry
|
||||
existing_group.add_member(member);
|
||||
if let Err(e) = self.cache.store::<GroupEntry>(
|
||||
&existing_group.uuid.clone(),
|
||||
existing_group,
|
||||
) {
|
||||
DBG_ERR!("Unable to store membership change: {:?}", e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Err(e) = self
|
||||
.cache
|
||||
.store::<GroupEntry>(&group.uuid.clone(), group)
|
||||
{
|
||||
DBG_ERR!("Unable to store membership change: {:?}", e);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PrivateCache {
|
||||
cache: BasicCache,
|
||||
}
|
||||
|
||||
impl PrivateCache {
|
||||
pub(crate) fn new(cache_path: &str) -> Result<Self, Box<NTSTATUS>> {
|
||||
Ok(PrivateCache {
|
||||
cache: BasicCache::new(cache_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_machine_key_fetch_or_create(
|
||||
&mut self,
|
||||
hsm: &mut BoxedDynTpm,
|
||||
auth_value: &AuthValue,
|
||||
) -> Result<LoadableMachineKey, Box<NTSTATUS>> {
|
||||
match self
|
||||
.cache
|
||||
.fetch::<LoadableMachineKey>("loadable_machine_key")
|
||||
{
|
||||
Some(loadable_machine_key) => Ok(loadable_machine_key),
|
||||
None => {
|
||||
// No machine key found - create one, and store it.
|
||||
let loadable_machine_key =
|
||||
match hsm.machine_key_create(&auth_value) {
|
||||
Ok(loadable_machine_key) => loadable_machine_key,
|
||||
Err(e) => {
|
||||
DBG_ERR!(
|
||||
"Unable to create hsm loadable \
|
||||
machine key: {:?}",
|
||||
e
|
||||
);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
};
|
||||
|
||||
self.cache.store::<LoadableMachineKey>(
|
||||
"loadable_machine_key",
|
||||
loadable_machine_key.clone(),
|
||||
)?;
|
||||
|
||||
Ok(loadable_machine_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_transport_key_fetch(
|
||||
&mut self,
|
||||
realm: &str,
|
||||
) -> Option<LoadableMsOapxbcRsaKey> {
|
||||
let transport_key_tag = format!("{}/transport", realm);
|
||||
self.cache
|
||||
.fetch::<LoadableMsOapxbcRsaKey>(&transport_key_tag)
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_cert_key_fetch(
|
||||
&mut self,
|
||||
realm: &str,
|
||||
) -> Option<LoadableIdentityKey> {
|
||||
let cert_key_tag = format!("{}/certificate", realm);
|
||||
self.cache.fetch::<LoadableIdentityKey>(&cert_key_tag)
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_hello_key_fetch(
|
||||
&mut self,
|
||||
account_id: &str,
|
||||
) -> Option<LoadableIdentityKey> {
|
||||
let hello_key_tag = format!("{}/hello", account_id);
|
||||
self.cache.fetch::<LoadableIdentityKey>(&hello_key_tag)
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_cert_key_store(
|
||||
&mut self,
|
||||
realm: &str,
|
||||
cert_key: LoadableIdentityKey,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let cert_key_tag = format!("{}/certificate", realm);
|
||||
self.cache
|
||||
.store::<LoadableIdentityKey>(&cert_key_tag, cert_key)
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_hello_key_store(
|
||||
&mut self,
|
||||
account_id: &str,
|
||||
hello_key: LoadableIdentityKey,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let hello_key_tag = format!("{}/hello", account_id);
|
||||
self.cache
|
||||
.store::<LoadableIdentityKey>(&hello_key_tag, hello_key)
|
||||
}
|
||||
|
||||
pub(crate) fn loadable_transport_key_store(
|
||||
&mut self,
|
||||
realm: &str,
|
||||
transport_key: LoadableMsOapxbcRsaKey,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let transport_key_tag = format!("{}/transport", realm);
|
||||
self.cache
|
||||
.store::<LoadableMsOapxbcRsaKey>(&transport_key_tag, transport_key)
|
||||
}
|
||||
|
||||
pub(crate) fn device_id(&mut self, realm: &str) -> Option<String> {
|
||||
let device_id_tag = format!("{}/device_id", realm);
|
||||
self.cache.fetch_str(&device_id_tag)
|
||||
}
|
||||
|
||||
pub(crate) fn device_id_store(
|
||||
&mut self,
|
||||
realm: &str,
|
||||
device_id: &str,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
let device_id_tag = format!("{}/device_id", realm);
|
||||
self.cache.store_bytes(&device_id_tag, device_id.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use kanidm_hsm_crypto::soft::SoftTpm;
|
||||
use std::str::FromStr;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_basic_cache_new() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let cache = BasicCache::new(cache_path.to_str().unwrap());
|
||||
assert!(cache.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_cache_store_fetch_str() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let mut cache = BasicCache::new(cache_path.to_str().unwrap()).unwrap();
|
||||
|
||||
let key = "test_key";
|
||||
let value = "test_value";
|
||||
cache.store_bytes(key, value.as_bytes()).unwrap();
|
||||
let fetched_value = cache.fetch_str(key).unwrap();
|
||||
assert_eq!(fetched_value, value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_cache_store_fetch() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let mut cache = BasicCache::new(cache_path.to_str().unwrap()).unwrap();
|
||||
|
||||
let key = "test_key";
|
||||
let value = UserEntry {
|
||||
upn: "user@test.com".to_string(),
|
||||
uuid: "f63a43c7-b783-4da9-acb4-89f8ebfc49e9".to_string(),
|
||||
name: "Test User".to_string(),
|
||||
};
|
||||
|
||||
cache.store(key, &value).unwrap();
|
||||
let fetched_value: Option<UserEntry> = cache.fetch(key);
|
||||
assert!(fetched_value.is_some());
|
||||
let fetched_value = fetched_value.unwrap();
|
||||
assert_eq!(fetched_value.upn, value.upn);
|
||||
assert_eq!(fetched_value.uuid, value.uuid);
|
||||
assert_eq!(fetched_value.name, value.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user_cache_store_fetch() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let mut cache = UserCache::new(cache_path.to_str().unwrap()).unwrap();
|
||||
|
||||
let entry = UserEntry {
|
||||
upn: "user@test.com".to_string(),
|
||||
uuid: "f63a43c7-b783-4da9-acb4-89f8ebfc49e9".to_string(),
|
||||
name: "Test User".to_string(),
|
||||
};
|
||||
|
||||
cache.store(entry.clone()).unwrap();
|
||||
let fetched_entry = cache.fetch(&entry.upn);
|
||||
assert!(fetched_entry.is_some());
|
||||
let fetched_entry = fetched_entry.unwrap();
|
||||
assert_eq!(fetched_entry.upn, entry.upn);
|
||||
assert_eq!(fetched_entry.uuid, entry.uuid);
|
||||
assert_eq!(fetched_entry.name, entry.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uid_cache_store_fetch() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let mut cache = UidCache::new(cache_path.to_str().unwrap()).unwrap();
|
||||
|
||||
let uid: uid_t = 1000;
|
||||
let upn = "user@test.com";
|
||||
|
||||
cache.store(uid, upn).unwrap();
|
||||
let fetched_upn = cache.fetch(uid);
|
||||
assert!(fetched_upn.is_some());
|
||||
assert_eq!(fetched_upn.unwrap(), upn);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_group_cache_store_fetch() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let mut cache = GroupCache::new(cache_path.to_str().unwrap()).unwrap();
|
||||
|
||||
let mut group = GroupEntry {
|
||||
uuid: "5f8be63a-a379-4324-9f42-9ea40bed9d7f".to_string(),
|
||||
members: HashSet::new(),
|
||||
};
|
||||
group.add_member("user@test.com");
|
||||
|
||||
cache.cache.store(&group.uuid, &group).unwrap();
|
||||
let fetched_group = cache.fetch(&group.uuid);
|
||||
assert!(fetched_group.is_some());
|
||||
let fetched_group = fetched_group.unwrap();
|
||||
assert_eq!(fetched_group.uuid, group.uuid);
|
||||
assert!(fetched_group.members.contains("user@test.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_cache_loadable_machine_key_fetch_or_create() {
|
||||
let dir = tempdir().unwrap();
|
||||
let cache_path = dir.path().join("test.tdb");
|
||||
let mut cache =
|
||||
PrivateCache::new(cache_path.to_str().unwrap()).unwrap();
|
||||
|
||||
let mut hsm = BoxedDynTpm::new(SoftTpm::new());
|
||||
let auth_str = AuthValue::generate().expect("Failed to create hex pin");
|
||||
let auth_value = AuthValue::from_str(&auth_str)
|
||||
.expect("Unable to create auth value");
|
||||
|
||||
let result =
|
||||
cache.loadable_machine_key_fetch_or_create(&mut hsm, &auth_value);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let fetched_key = cache
|
||||
.cache
|
||||
.fetch::<LoadableMachineKey>("loadable_machine_key");
|
||||
assert!(fetched_key.is_some());
|
||||
}
|
||||
}
|
1
rust/himmelblaud/src/constants.rs
Normal file
1
rust/himmelblaud/src/constants.rs
Normal file
@ -0,0 +1 @@
|
||||
pub const DEFAULT_ODC_PROVIDER: &str = "odc.officeapps.live.com";
|
295
rust/himmelblaud/src/himmelblaud.rs
Normal file
295
rust/himmelblaud/src/himmelblaud.rs
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::cache::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
#[cfg(not(test))]
|
||||
use crate::himmelblaud::himmelblaud_pam_auth::AuthSession;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use dbg::{DBG_DEBUG, DBG_ERR, DBG_WARNING};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use himmelblau::graph::Graph;
|
||||
use himmelblau::BrokerClientApplication;
|
||||
use idmap::Idmap;
|
||||
use kanidm_hsm_crypto::{BoxedDynTpm, MachineKey};
|
||||
use param::LoadParm;
|
||||
use sock::{PamAuthResponse, Request, Response};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::io::{Error as IoError, ErrorKind};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UnixStream;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub(crate) struct Resolver {
|
||||
realm: String,
|
||||
tenant_id: String,
|
||||
lp: LoadParm,
|
||||
idmap: Idmap,
|
||||
graph: Graph,
|
||||
pcache: PrivateCache,
|
||||
user_cache: UserCache,
|
||||
uid_cache: UidCache,
|
||||
group_cache: GroupCache,
|
||||
hsm: Mutex<BoxedDynTpm>,
|
||||
machine_key: MachineKey,
|
||||
client: Arc<Mutex<BrokerClientApplication>>,
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
impl Resolver {
|
||||
pub(crate) fn new(
|
||||
realm: &str,
|
||||
tenant_id: &str,
|
||||
lp: LoadParm,
|
||||
idmap: Idmap,
|
||||
graph: Graph,
|
||||
pcache: PrivateCache,
|
||||
user_cache: UserCache,
|
||||
uid_cache: UidCache,
|
||||
group_cache: GroupCache,
|
||||
hsm: BoxedDynTpm,
|
||||
machine_key: MachineKey,
|
||||
client: BrokerClientApplication,
|
||||
) -> Self {
|
||||
Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
graph,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
hsm: Mutex::new(hsm),
|
||||
machine_key,
|
||||
client: Arc::new(Mutex::new(client)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The test environment is unable to communicate with Entra ID, therefore
|
||||
// we alter the resolver to only test the cache interactions.
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) struct Resolver {
|
||||
realm: String,
|
||||
tenant_id: String,
|
||||
lp: LoadParm,
|
||||
idmap: Idmap,
|
||||
pcache: PrivateCache,
|
||||
user_cache: UserCache,
|
||||
uid_cache: UidCache,
|
||||
group_cache: GroupCache,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Resolver {
|
||||
pub(crate) fn new(
|
||||
realm: &str,
|
||||
tenant_id: &str,
|
||||
lp: LoadParm,
|
||||
idmap: Idmap,
|
||||
pcache: PrivateCache,
|
||||
user_cache: UserCache,
|
||||
uid_cache: UidCache,
|
||||
group_cache: GroupCache,
|
||||
) -> Self {
|
||||
Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientCodec;
|
||||
|
||||
impl Decoder for ClientCodec {
|
||||
type Error = io::Error;
|
||||
type Item = Request;
|
||||
|
||||
fn decode(
|
||||
&mut self,
|
||||
src: &mut BytesMut,
|
||||
) -> Result<Option<Self::Item>, Self::Error> {
|
||||
match serde_json::from_slice::<Request>(src) {
|
||||
Ok(msg) => {
|
||||
src.clear();
|
||||
Ok(Some(msg))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<Response> for ClientCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(
|
||||
&mut self,
|
||||
msg: Response,
|
||||
dst: &mut BytesMut,
|
||||
) -> Result<(), Self::Error> {
|
||||
DBG_DEBUG!("Attempting to send response -> {:?} ...", msg);
|
||||
let data = serde_json::to_vec(&msg).map_err(|e| {
|
||||
DBG_ERR!("socket encoding error -> {:?}", e);
|
||||
io::Error::new(ErrorKind::Other, "JSON encode error")
|
||||
})?;
|
||||
dst.put(data.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCodec {
|
||||
fn new() -> Self {
|
||||
ClientCodec
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_client(
|
||||
stream: UnixStream,
|
||||
resolver: Arc<Mutex<Resolver>>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
DBG_DEBUG!("Accepted connection");
|
||||
|
||||
let Ok(_ucred) = stream.peer_cred() else {
|
||||
return Err(Box::new(IoError::new(
|
||||
ErrorKind::Other,
|
||||
"Unable to verify peer credentials.",
|
||||
)));
|
||||
};
|
||||
|
||||
let mut reqs = Framed::new(stream, ClientCodec::new());
|
||||
#[cfg(not(test))]
|
||||
let mut pam_auth_session_state = None;
|
||||
|
||||
while let Some(Ok(req)) = reqs.next().await {
|
||||
let mut resolver = resolver.lock().await;
|
||||
let resp = match req {
|
||||
#[cfg(not(test))]
|
||||
Request::PamAuthenticateInit(account_id) => {
|
||||
DBG_DEBUG!("pam authenticate init");
|
||||
|
||||
match &pam_auth_session_state {
|
||||
Some(_) => {
|
||||
DBG_WARNING!(
|
||||
"Attempt to init \
|
||||
auth session while current \
|
||||
session is active"
|
||||
);
|
||||
pam_auth_session_state = None;
|
||||
Response::Error
|
||||
}
|
||||
None => {
|
||||
let (auth_session, resp) =
|
||||
resolver.pam_auth_init(&account_id)?;
|
||||
pam_auth_session_state = Some(auth_session);
|
||||
resp
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
Request::PamAuthenticateStep(pam_next_req) => {
|
||||
DBG_DEBUG!("pam authenticate step");
|
||||
match &mut pam_auth_session_state {
|
||||
Some(AuthSession::InProgress {
|
||||
account_id,
|
||||
cred_handler,
|
||||
}) => {
|
||||
let resp = resolver
|
||||
.pam_auth_step(
|
||||
account_id,
|
||||
cred_handler,
|
||||
pam_next_req,
|
||||
)
|
||||
.await?;
|
||||
match resp {
|
||||
Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
) => {
|
||||
pam_auth_session_state =
|
||||
Some(AuthSession::Success);
|
||||
}
|
||||
Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Denied,
|
||||
) => {
|
||||
pam_auth_session_state =
|
||||
Some(AuthSession::Denied);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
resp
|
||||
}
|
||||
_ => {
|
||||
DBG_WARNING!(
|
||||
"Attempt to \
|
||||
continue auth session \
|
||||
while current session is \
|
||||
inactive"
|
||||
);
|
||||
Response::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
Request::NssAccounts => resolver.getpwent().await?,
|
||||
Request::NssAccountByName(account_id) => {
|
||||
resolver.getpwnam(&account_id).await?
|
||||
}
|
||||
Request::NssAccountByUid(uid) => resolver.getpwuid(uid).await?,
|
||||
Request::NssGroups => resolver.getgrent().await?,
|
||||
Request::NssGroupByName(grp_id) => {
|
||||
resolver.getgrnam(&grp_id).await?
|
||||
}
|
||||
Request::NssGroupByGid(gid) => resolver.getgrgid(gid).await?,
|
||||
#[cfg(not(test))]
|
||||
Request::PamAccountAllowed(account_id) => {
|
||||
resolver.pam_acct_mgmt(&account_id).await?
|
||||
}
|
||||
Request::PamAccountBeginSession(_account_id) => Response::Success,
|
||||
#[cfg(test)]
|
||||
_ => Response::Error,
|
||||
};
|
||||
reqs.send(resp).await?;
|
||||
reqs.flush().await?;
|
||||
DBG_DEBUG!("flushed response!");
|
||||
}
|
||||
|
||||
DBG_DEBUG!("Disconnecting client ...");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod himmelblaud_getgrent;
|
||||
mod himmelblaud_getgrgid;
|
||||
mod himmelblaud_getgrnam;
|
||||
mod himmelblaud_getpwent;
|
||||
mod himmelblaud_getpwnam;
|
||||
mod himmelblaud_getpwuid;
|
||||
#[cfg(not(test))]
|
||||
mod himmelblaud_pam_acct_mgmt;
|
||||
#[cfg(not(test))]
|
||||
mod himmelblaud_pam_auth;
|
154
rust/himmelblaud/src/himmelblaud/himmelblaud_getgrent.rs
Normal file
154
rust/himmelblaud/src/himmelblaud/himmelblaud_getgrent.rs
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for nss getgrent
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::himmelblaud::Resolver;
|
||||
use dbg::DBG_ERR;
|
||||
use ntstatus_gen::*;
|
||||
use sock::{Group, Response};
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) async fn getgrent(&mut self) -> Result<Response, Box<NTSTATUS>> {
|
||||
let group_entries = self.group_cache.fetch_all()?;
|
||||
let mut res = Vec::new();
|
||||
for entry in group_entries {
|
||||
let name = entry.uuid.clone();
|
||||
let gid = self
|
||||
.idmap
|
||||
.gen_to_unix(&self.tenant_id, &entry.uuid)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NO_SUCH_GROUP)
|
||||
})?;
|
||||
let group = Group {
|
||||
name,
|
||||
passwd: "x".to_string(),
|
||||
gid,
|
||||
members: entry.members(),
|
||||
};
|
||||
res.push(group);
|
||||
}
|
||||
Ok(Response::NssGroups(res))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cache::GroupEntry;
|
||||
use crate::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
use idmap::Idmap;
|
||||
use param::LoadParm;
|
||||
use std::collections::HashSet;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_getgrent() {
|
||||
// Create a temporary directory for the cache
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Initialize the caches
|
||||
let private_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let pcache = PrivateCache::new(&private_cache_path).unwrap();
|
||||
let user_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_users.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let user_cache = UserCache::new(&user_cache_path).unwrap();
|
||||
let uid_cache_path = dir
|
||||
.path()
|
||||
.join("uid_cache.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let uid_cache = UidCache::new(&uid_cache_path).unwrap();
|
||||
let group_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_groups.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut group_cache = GroupCache::new(&group_cache_path).unwrap();
|
||||
|
||||
// Insert dummy GroupEntries into the cache
|
||||
let group_uuid1 = "c490c3ea-fd98-4d45-b6aa-2a3520f804fa";
|
||||
let group_uuid2 = "f7a51b58-84de-42a3-b5b1-967b17c04f89";
|
||||
let dummy_group1 = GroupEntry::new(group_uuid1);
|
||||
let dummy_group2 = GroupEntry::new(group_uuid2);
|
||||
group_cache
|
||||
.merge_groups("user1@test.com", vec![dummy_group1.clone()])
|
||||
.unwrap();
|
||||
group_cache
|
||||
.merge_groups("user2@test.com", vec![dummy_group2.clone()])
|
||||
.unwrap();
|
||||
|
||||
// Initialize the Idmap with dummy configuration
|
||||
let realm = "test.com";
|
||||
let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
|
||||
let mut idmap = Idmap::new().unwrap();
|
||||
idmap
|
||||
.add_gen_domain(realm, tenant_id, (1000, 2000))
|
||||
.unwrap();
|
||||
|
||||
// Initialize dummy configuration
|
||||
let lp = LoadParm::new(None).expect("Failed loading default config");
|
||||
|
||||
// Initialize the Resolver
|
||||
let mut resolver = Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
};
|
||||
|
||||
// Test the getgrent function
|
||||
let result = resolver.getgrent().await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssGroups(mut groups) => {
|
||||
groups.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
assert_eq!(groups.len(), 2);
|
||||
|
||||
let group1 = &groups[0];
|
||||
assert_eq!(group1.name, dummy_group1.uuid);
|
||||
assert_eq!(group1.gid, 1388);
|
||||
assert_eq!(group1.members, vec!["user1@test.com".to_string()]);
|
||||
|
||||
let group2 = &groups[1];
|
||||
assert_eq!(group2.name, dummy_group2.uuid);
|
||||
assert_eq!(group2.gid, 1593);
|
||||
assert_eq!(group2.members, vec!["user2@test.com".to_string()]);
|
||||
}
|
||||
other => {
|
||||
panic!("Expected NssGroups with a list of groups: {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
rust/himmelblaud/src/himmelblaud/himmelblaud_getgrgid.rs
Normal file
147
rust/himmelblaud/src/himmelblaud/himmelblaud_getgrgid.rs
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for nss getgrgid
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::himmelblaud::Resolver;
|
||||
use libc::gid_t;
|
||||
use ntstatus_gen::NTSTATUS;
|
||||
use sock::{Group, Response};
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) async fn getgrgid(
|
||||
&mut self,
|
||||
gid: gid_t,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
if let Some(uuid) = self.uid_cache.fetch(gid) {
|
||||
if let Some(entry) = self.group_cache.fetch(&uuid) {
|
||||
return Ok(Response::NssGroup(Some(Group {
|
||||
name: entry.uuid.clone(),
|
||||
passwd: "x".to_string(),
|
||||
gid,
|
||||
members: entry.members(),
|
||||
})));
|
||||
}
|
||||
}
|
||||
Ok(Response::NssGroup(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cache::GroupEntry;
|
||||
use crate::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
use idmap::Idmap;
|
||||
use param::LoadParm;
|
||||
use std::collections::HashSet;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_getgrgid() {
|
||||
// Create a temporary directory for the cache
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Initialize the caches
|
||||
let private_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let pcache = PrivateCache::new(&private_cache_path).unwrap();
|
||||
let user_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_users.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let user_cache = UserCache::new(&user_cache_path).unwrap();
|
||||
let uid_cache_path = dir
|
||||
.path()
|
||||
.join("uid_cache.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut uid_cache = UidCache::new(&uid_cache_path).unwrap();
|
||||
let group_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_groups.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut group_cache = GroupCache::new(&group_cache_path).unwrap();
|
||||
|
||||
// Initialize the Idmap with dummy configuration
|
||||
let realm = "test.com";
|
||||
let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
|
||||
let mut idmap = Idmap::new().unwrap();
|
||||
idmap
|
||||
.add_gen_domain(realm, tenant_id, (1000, 2000))
|
||||
.unwrap();
|
||||
|
||||
// Insert a dummy GroupEntry into the cache
|
||||
let group_uuid = "c490c3ea-fd98-4d45-b6aa-2a3520f804fa".to_string();
|
||||
let dummy_gid = idmap
|
||||
.gen_to_unix(tenant_id, &group_uuid)
|
||||
.expect("Failed to map group gid");
|
||||
// Store the calculated gid -> uuid map in the cache
|
||||
uid_cache
|
||||
.store(dummy_gid, &group_uuid)
|
||||
.expect("Failed to store group gid");
|
||||
let dummy_group = GroupEntry::new(&group_uuid);
|
||||
group_cache
|
||||
.merge_groups("user1@test.com", vec![dummy_group.clone()])
|
||||
.unwrap();
|
||||
|
||||
// Initialize dummy configuration
|
||||
let lp = LoadParm::new(None).expect("Failed loading default config");
|
||||
|
||||
// Initialize the Resolver
|
||||
let mut resolver = Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
};
|
||||
|
||||
// Test the getgrgid function with a gid that exists
|
||||
let result = resolver.getgrgid(dummy_gid).await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssGroup(Some(group)) => {
|
||||
assert_eq!(group.name, dummy_group.uuid);
|
||||
assert_eq!(group.gid, dummy_gid);
|
||||
assert_eq!(group.members, vec!["user1@test.com".to_string()]);
|
||||
}
|
||||
other => panic!("Expected NssGroup with Some(group): {:?}", other),
|
||||
}
|
||||
|
||||
// Test the getgrgid function with a gid that does not exist
|
||||
let nonexistent_gid: gid_t = 1600;
|
||||
let result = resolver.getgrgid(nonexistent_gid).await.unwrap();
|
||||
match result {
|
||||
Response::NssGroup(None) => {} // This is the expected result
|
||||
_ => panic!("Expected NssGroup with None"),
|
||||
}
|
||||
}
|
||||
}
|
159
rust/himmelblaud/src/himmelblaud/himmelblaud_getgrnam.rs
Normal file
159
rust/himmelblaud/src/himmelblaud/himmelblaud_getgrnam.rs
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for nss getgrnam
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::himmelblaud::Resolver;
|
||||
use dbg::DBG_ERR;
|
||||
use ntstatus_gen::*;
|
||||
use sock::{Group, Response};
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) async fn getgrnam(
|
||||
&mut self,
|
||||
grp_id: &str,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
let entry = match self.group_cache.fetch(grp_id) {
|
||||
Some(entry) => entry,
|
||||
None => return Ok(Response::NssGroup(None)),
|
||||
};
|
||||
let gid = self
|
||||
.idmap
|
||||
.gen_to_unix(&self.tenant_id, &entry.uuid)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_INVALID_TOKEN)
|
||||
})?;
|
||||
// Store the calculated gid -> uuid map in the cache
|
||||
self.uid_cache.store(gid, &entry.uuid)?;
|
||||
let group = Group {
|
||||
name: entry.uuid.clone(),
|
||||
passwd: "x".to_string(),
|
||||
gid,
|
||||
members: entry.members(),
|
||||
};
|
||||
return Ok(Response::NssGroup(Some(group)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cache::GroupEntry;
|
||||
use crate::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
use idmap::Idmap;
|
||||
use param::LoadParm;
|
||||
use std::collections::HashSet;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_getgrnam() {
|
||||
// Create a temporary directory for the cache
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Initialize the caches
|
||||
let private_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let pcache = PrivateCache::new(&private_cache_path).unwrap();
|
||||
let user_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_users.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let user_cache = UserCache::new(&user_cache_path).unwrap();
|
||||
let uid_cache_path = dir
|
||||
.path()
|
||||
.join("uid_cache.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let uid_cache = UidCache::new(&uid_cache_path).unwrap();
|
||||
let group_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_groups.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut group_cache = GroupCache::new(&group_cache_path).unwrap();
|
||||
|
||||
// Insert a dummy GroupEntry into the cache
|
||||
let group_uuid = "c490c3ea-fd98-4d45-b6aa-2a3520f804fa";
|
||||
let dummy_group = GroupEntry::new(group_uuid);
|
||||
group_cache
|
||||
.merge_groups("user1@test.com", vec![dummy_group.clone()])
|
||||
.unwrap();
|
||||
group_cache
|
||||
.merge_groups("user2@test.com", vec![dummy_group.clone()])
|
||||
.unwrap();
|
||||
|
||||
// Initialize the Idmap with dummy configuration
|
||||
let realm = "test.com";
|
||||
let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
|
||||
let mut idmap = Idmap::new().unwrap();
|
||||
idmap
|
||||
.add_gen_domain(realm, tenant_id, (1000, 2000))
|
||||
.unwrap();
|
||||
|
||||
// Initialize dummy configuration
|
||||
let lp = LoadParm::new(None).expect("Failed loading default config");
|
||||
|
||||
// Initialize the Resolver
|
||||
let mut resolver = Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
};
|
||||
|
||||
// Test the getgrnam function with a group that exists
|
||||
let result = resolver.getgrnam(group_uuid).await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssGroup(Some(mut group)) => {
|
||||
group.members.sort();
|
||||
assert_eq!(group.name, dummy_group.uuid);
|
||||
assert_eq!(group.gid, 1388);
|
||||
assert_eq!(
|
||||
group.members,
|
||||
vec![
|
||||
"user1@test.com".to_string(),
|
||||
"user2@test.com".to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
other => panic!("Expected NssGroup with Some(group): {:?}", other),
|
||||
}
|
||||
|
||||
// Test the getgrnam function with a group that does not exist
|
||||
let nonexistent_group_uuid = "2ea8f1d4-1b94-4003-865b-cb247a8a1f5d";
|
||||
let result = resolver.getgrnam(nonexistent_group_uuid).await.unwrap();
|
||||
match result {
|
||||
Response::NssGroup(None) => {} // This is the expected result
|
||||
_ => panic!("Expected NssGroup with None"),
|
||||
}
|
||||
}
|
||||
}
|
210
rust/himmelblaud/src/himmelblaud/himmelblaud_getpwent.rs
Normal file
210
rust/himmelblaud/src/himmelblaud/himmelblaud_getpwent.rs
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for nss getpwent
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::himmelblaud::Resolver;
|
||||
use crate::utils::split_username;
|
||||
use dbg::DBG_ERR;
|
||||
use ntstatus_gen::*;
|
||||
use sock::{Passwd, Response};
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) async fn getpwent(&mut self) -> Result<Response, Box<NTSTATUS>> {
|
||||
let user_entries = self.user_cache.fetch_all()?;
|
||||
let template_homedir = self
|
||||
.lp
|
||||
.template_homedir()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
DBG_ERR!("Failed to discover template homedir. Is it set?");
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?;
|
||||
let shell = self
|
||||
.lp
|
||||
.template_shell()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
DBG_ERR!("Failed to discover template shell. Is it set?");
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?;
|
||||
let mut res = Vec::new();
|
||||
for entry in user_entries {
|
||||
let uid = self
|
||||
.idmap
|
||||
.gen_to_unix(&self.tenant_id, &entry.upn)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_INVALID_TOKEN)
|
||||
})?;
|
||||
let upn = entry.upn.clone();
|
||||
let (cn, domain) = match split_username(&upn) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
DBG_ERR!(
|
||||
"Failed to parse user upn '{}': {:?}",
|
||||
&entry.upn,
|
||||
e
|
||||
);
|
||||
return Err(Box::new(
|
||||
NT_STATUS_INVALID_USER_PRINCIPAL_NAME,
|
||||
));
|
||||
}
|
||||
};
|
||||
let homedir = template_homedir
|
||||
.clone()
|
||||
.replace("%D", &domain)
|
||||
.replace("%U", &cn);
|
||||
let passwd = Passwd {
|
||||
name: entry.upn.clone(),
|
||||
passwd: "x".to_string(),
|
||||
uid,
|
||||
gid: uid,
|
||||
gecos: entry.name,
|
||||
dir: homedir,
|
||||
shell: shell.clone(),
|
||||
};
|
||||
res.push(passwd);
|
||||
}
|
||||
Ok(Response::NssAccounts(res))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cache::UserEntry;
|
||||
use crate::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
use idmap::Idmap;
|
||||
use param::LoadParm;
|
||||
use std::collections::HashSet;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_getpwent() {
|
||||
// Create a temporary directory for the cache
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Initialize the caches
|
||||
let private_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let pcache = PrivateCache::new(&private_cache_path).unwrap();
|
||||
let user_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_users.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut user_cache = UserCache::new(&user_cache_path).unwrap();
|
||||
let uid_cache_path = dir
|
||||
.path()
|
||||
.join("uid_cache.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let uid_cache = UidCache::new(&uid_cache_path).unwrap();
|
||||
let group_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_groups.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let group_cache = GroupCache::new(&group_cache_path).unwrap();
|
||||
|
||||
// Insert dummy UserEntrys into the cache
|
||||
let dummy_user = UserEntry {
|
||||
upn: "user1@test.com".to_string(),
|
||||
uuid: "731e9af3-668d-4033-afd1-9f09b9120cc7".to_string(),
|
||||
name: "User One".to_string(),
|
||||
};
|
||||
user_cache
|
||||
.store(dummy_user.clone())
|
||||
.expect("Failed storing user in cache");
|
||||
|
||||
let dummy_user2 = UserEntry {
|
||||
upn: "user2@test.com".to_string(),
|
||||
uuid: "7be6c0c5-5763-4633-aecf-f8c460b338fd".to_string(),
|
||||
name: "User Two".to_string(),
|
||||
};
|
||||
user_cache
|
||||
.store(dummy_user2.clone())
|
||||
.expect("Failed storing user in cache");
|
||||
|
||||
// Initialize the Idmap with dummy configuration
|
||||
let realm = "test.com";
|
||||
let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
|
||||
let mut idmap = Idmap::new().unwrap();
|
||||
idmap
|
||||
.add_gen_domain(realm, tenant_id, (1000, 2000))
|
||||
.unwrap();
|
||||
|
||||
// Initialize dummy configuration for LoadParm
|
||||
let lp = LoadParm::new(None).expect("Failed loading default config");
|
||||
|
||||
// Initialize the Resolver
|
||||
let mut resolver = Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
};
|
||||
|
||||
// Test the getpwent function
|
||||
let result = resolver.getpwent().await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssAccounts(accounts) => {
|
||||
assert_eq!(accounts.len(), 2);
|
||||
|
||||
let account1 = &accounts[0];
|
||||
assert_eq!(account1.name, dummy_user.upn);
|
||||
assert_eq!(account1.uid, 1316);
|
||||
assert_eq!(account1.gid, 1316);
|
||||
assert_eq!(account1.gecos, dummy_user.name);
|
||||
assert_eq!(account1.dir, "/home/test.com/user1");
|
||||
assert_eq!(account1.shell, "/bin/false");
|
||||
|
||||
let account2 = &accounts[1];
|
||||
assert_eq!(account2.name, dummy_user2.upn);
|
||||
assert_eq!(account2.uid, 1671);
|
||||
assert_eq!(account2.gid, 1671);
|
||||
assert_eq!(account2.gecos, dummy_user2.name);
|
||||
assert_eq!(account2.dir, "/home/test.com/user2");
|
||||
assert_eq!(account2.shell, "/bin/false");
|
||||
}
|
||||
other => panic!(
|
||||
"Expected NssAccounts with a list of accounts: {:?}",
|
||||
other
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
232
rust/himmelblaud/src/himmelblaud/himmelblaud_getpwnam.rs
Normal file
232
rust/himmelblaud/src/himmelblaud/himmelblaud_getpwnam.rs
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for nss getpwnam
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::cache::GroupEntry;
|
||||
use crate::himmelblaud::Resolver;
|
||||
use crate::utils::split_username;
|
||||
use dbg::{DBG_ERR, DBG_WARNING};
|
||||
use ntstatus_gen::*;
|
||||
use sock::{Passwd, Response};
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) fn create_passwd_from_upn(
|
||||
&mut self,
|
||||
upn: &str,
|
||||
gecos: &str,
|
||||
) -> Result<Passwd, Box<NTSTATUS>> {
|
||||
let template_homedir = self
|
||||
.lp
|
||||
.template_homedir()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
DBG_ERR!("Failed to discover template homedir. Is it set?");
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?;
|
||||
let shell = self
|
||||
.lp
|
||||
.template_shell()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
DBG_ERR!("Failed to discover template shell. Is it set?");
|
||||
Box::new(NT_STATUS_NOT_A_DIRECTORY)
|
||||
})?;
|
||||
let uid =
|
||||
self.idmap.gen_to_unix(&self.tenant_id, &upn).map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_INVALID_TOKEN)
|
||||
})?;
|
||||
// Store the calculated uid -> upn map in the cache
|
||||
self.uid_cache.store(uid, &upn)?;
|
||||
// Store the primary group (which is a fake group matching the user upn)
|
||||
let mut group = GroupEntry::new(upn);
|
||||
group.add_member(upn);
|
||||
self.group_cache.merge_groups(upn, vec![group])?;
|
||||
let (cn, domain) = match split_username(&upn) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to parse user upn '{}': {:?}", upn, e);
|
||||
return Err(Box::new(NT_STATUS_INVALID_USER_PRINCIPAL_NAME));
|
||||
}
|
||||
};
|
||||
let homedir = template_homedir
|
||||
.clone()
|
||||
.replace("%D", &domain)
|
||||
.replace("%U", &cn);
|
||||
let passwd = Passwd {
|
||||
name: upn.to_string(),
|
||||
passwd: "x".to_string(),
|
||||
uid,
|
||||
gid: uid,
|
||||
gecos: gecos.to_string(),
|
||||
dir: homedir,
|
||||
shell: shell.clone(),
|
||||
};
|
||||
return Ok(passwd);
|
||||
}
|
||||
|
||||
pub(crate) async fn getpwnam(
|
||||
&mut self,
|
||||
account_id: &str,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
// We first try to fetch the user from the cache, so that we
|
||||
// get the gecos. Otherwise we can just create a passwd entry
|
||||
// based on whether the upn exists in Entra ID.
|
||||
let entry = match self.user_cache.fetch(account_id) {
|
||||
Some(entry) => entry,
|
||||
#[cfg(not(test))]
|
||||
None => {
|
||||
// Check if the user exists in Entra ID
|
||||
let exists = match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.check_user_exists(&account_id)
|
||||
.await
|
||||
{
|
||||
Ok(exists) => exists,
|
||||
Err(e) => {
|
||||
DBG_WARNING!("{:?}", e);
|
||||
return Ok(Response::NssAccount(None));
|
||||
}
|
||||
};
|
||||
if exists {
|
||||
return Ok(Response::NssAccount(Some(
|
||||
self.create_passwd_from_upn(account_id, "")?,
|
||||
)));
|
||||
}
|
||||
return Ok(Response::NssAccount(None));
|
||||
}
|
||||
#[cfg(test)]
|
||||
None => return Ok(Response::NssAccount(None)),
|
||||
};
|
||||
return Ok(Response::NssAccount(Some(
|
||||
self.create_passwd_from_upn(&entry.upn, &entry.name)?,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cache::UserEntry;
|
||||
use crate::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
use idmap::Idmap;
|
||||
use param::LoadParm;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_getpwnam() {
|
||||
// Create a temporary directory for the cache
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Initialize the caches
|
||||
let private_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let pcache = PrivateCache::new(&private_cache_path).unwrap();
|
||||
let user_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_users.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut user_cache = UserCache::new(&user_cache_path).unwrap();
|
||||
let uid_cache_path = dir
|
||||
.path()
|
||||
.join("uid_cache.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let uid_cache = UidCache::new(&uid_cache_path).unwrap();
|
||||
let group_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_groups.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let group_cache = GroupCache::new(&group_cache_path).unwrap();
|
||||
|
||||
// Insert a dummy UserEntry into the cache
|
||||
let dummy_user = UserEntry {
|
||||
upn: "user1@test.com".to_string(),
|
||||
uuid: "731e9af3-668d-4033-afd1-9f09b9120cc7".to_string(),
|
||||
name: "User One".to_string(),
|
||||
};
|
||||
let _ = user_cache.store(dummy_user.clone());
|
||||
|
||||
// Initialize the Idmap with dummy configuration
|
||||
let realm = "test.com";
|
||||
let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
|
||||
let mut idmap = Idmap::new().unwrap();
|
||||
idmap
|
||||
.add_gen_domain(realm, tenant_id, (1000, 2000))
|
||||
.unwrap();
|
||||
|
||||
// Initialize dummy configuration for LoadParm
|
||||
let lp = LoadParm::new(None).expect("Failed loading default config");
|
||||
|
||||
// Initialize the Resolver
|
||||
let mut resolver = Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
};
|
||||
|
||||
// Test the getpwnam function with a user that exists in the cache
|
||||
let result = resolver.getpwnam(&dummy_user.upn).await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssAccount(Some(account)) => {
|
||||
assert_eq!(account.name, dummy_user.upn);
|
||||
assert_eq!(account.uid, 1316);
|
||||
assert_eq!(account.gid, 1316);
|
||||
assert_eq!(account.gecos, dummy_user.name);
|
||||
assert_eq!(account.dir, "/home/test.com/user1");
|
||||
assert_eq!(account.shell, "/bin/false");
|
||||
}
|
||||
other => {
|
||||
panic!("Expected NssAccount with Some(account): {:?}", other)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the getpwnam function with a user that does not exist in the cache
|
||||
let nonexistent_user_upn = "nonexistent@test.com";
|
||||
let result = resolver.getpwnam(nonexistent_user_upn).await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssAccount(None) => {} // This is the expected result
|
||||
other => panic!("Expected NssAccount with None: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
156
rust/himmelblaud/src/himmelblaud/himmelblaud_getpwuid.rs
Normal file
156
rust/himmelblaud/src/himmelblaud/himmelblaud_getpwuid.rs
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for nss getpwuid
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::himmelblaud::Resolver;
|
||||
use libc::uid_t;
|
||||
use ntstatus_gen::NTSTATUS;
|
||||
use sock::Response;
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) async fn getpwuid(
|
||||
&mut self,
|
||||
uid: uid_t,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
if let Some(upn) = self.uid_cache.fetch(uid) {
|
||||
if let Some(entry) = self.user_cache.fetch(&upn) {
|
||||
Ok(Response::NssAccount(Some(
|
||||
self.create_passwd_from_upn(&entry.upn, &entry.name)?,
|
||||
)))
|
||||
} else {
|
||||
Ok(Response::NssAccount(Some(
|
||||
self.create_passwd_from_upn(&upn, "")?,
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(Response::NssAccount(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cache::UserEntry;
|
||||
use crate::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
use idmap::Idmap;
|
||||
use param::LoadParm;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_getpwuid() {
|
||||
// Create a temporary directory for the cache
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Initialize the caches
|
||||
let private_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let pcache = PrivateCache::new(&private_cache_path).unwrap();
|
||||
let user_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_users.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut user_cache = UserCache::new(&user_cache_path).unwrap();
|
||||
let uid_cache_path = dir
|
||||
.path()
|
||||
.join("uid_cache.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let mut uid_cache = UidCache::new(&uid_cache_path).unwrap();
|
||||
let group_cache_path = dir
|
||||
.path()
|
||||
.join("himmelblau_groups.tdb")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let group_cache = GroupCache::new(&group_cache_path).unwrap();
|
||||
|
||||
// Insert a dummy UserEntry into the cache
|
||||
let dummy_user = UserEntry {
|
||||
upn: "user1@test.com".to_string(),
|
||||
uuid: "731e9af3-668d-4033-afd1-9f09b9120cc7".to_string(),
|
||||
name: "User One".to_string(),
|
||||
};
|
||||
let _ = user_cache.store(dummy_user.clone());
|
||||
|
||||
// Initialize the Idmap with dummy configuration
|
||||
let realm = "test.com";
|
||||
let tenant_id = "89a61bb7-d1b9-4356-a1e0-75d88e06f14e";
|
||||
let mut idmap = Idmap::new().unwrap();
|
||||
idmap
|
||||
.add_gen_domain(realm, tenant_id, (1000, 2000))
|
||||
.unwrap();
|
||||
|
||||
let uid = idmap
|
||||
.gen_to_unix(tenant_id, &dummy_user.upn)
|
||||
.expect("Failed to generate uid for user");
|
||||
// Store the calculated uid -> upn map in the cache
|
||||
uid_cache
|
||||
.store(uid, &dummy_user.upn)
|
||||
.expect("Failed storing generated uid in the cache");
|
||||
|
||||
// Initialize dummy configuration for LoadParm
|
||||
let lp = LoadParm::new(None).expect("Failed loading default config");
|
||||
|
||||
// Initialize the Resolver
|
||||
let mut resolver = Resolver {
|
||||
realm: realm.to_string(),
|
||||
tenant_id: tenant_id.to_string(),
|
||||
lp,
|
||||
idmap,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
};
|
||||
|
||||
// Test the getpwuid function with a uid that exists in the cache
|
||||
let result = resolver.getpwuid(uid).await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssAccount(Some(account)) => {
|
||||
assert_eq!(account.name, dummy_user.upn);
|
||||
assert_eq!(account.uid, uid);
|
||||
assert_eq!(account.gid, uid);
|
||||
assert_eq!(account.gecos, dummy_user.name);
|
||||
assert_eq!(account.dir, "/home/test.com/user1");
|
||||
assert_eq!(account.shell, "/bin/false");
|
||||
}
|
||||
other => {
|
||||
panic!("Expected NssAccount with Some(account): {:?}", other)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the getpwuid function with a uid that does not exist in the cache
|
||||
let nonexistent_uid = 9999;
|
||||
let result = resolver.getpwuid(nonexistent_uid).await.unwrap();
|
||||
|
||||
match result {
|
||||
Response::NssAccount(None) => {} // This is the expected result
|
||||
other => panic!("Expected NssAccount with None: {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for pam_acct_mgmt
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::himmelblaud::Resolver;
|
||||
use dbg::DBG_WARNING;
|
||||
use ntstatus_gen::*;
|
||||
use sock::Response;
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) async fn pam_acct_mgmt(
|
||||
&self,
|
||||
account_id: &str,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
// Check if the user exists in Entra ID
|
||||
// TODO: If we're offline, check the cache instead
|
||||
match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.check_user_exists(&account_id)
|
||||
.await
|
||||
{
|
||||
Ok(exists) => Ok(Response::PamStatus(Some(exists))),
|
||||
Err(e) => {
|
||||
DBG_WARNING!("{:?}", e);
|
||||
Ok(Response::PamStatus(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
794
rust/himmelblaud/src/himmelblaud/himmelblaud_pam_auth.rs
Normal file
794
rust/himmelblaud/src/himmelblaud/himmelblaud_pam_auth.rs
Normal file
@ -0,0 +1,794 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon implementation for PAM_AUTH
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::cache::{GroupEntry, UserEntry};
|
||||
use crate::himmelblaud::Resolver;
|
||||
use dbg::{DBG_DEBUG, DBG_ERR, DBG_INFO, DBG_WARNING};
|
||||
use himmelblau::error::{
|
||||
MsalError, AUTH_PENDING, DEVICE_AUTH_FAIL, REQUIRES_MFA,
|
||||
};
|
||||
use himmelblau::{
|
||||
DeviceAuthorizationResponse, EnrollAttrs, MFAAuthContinue,
|
||||
UserToken as UnixUserToken,
|
||||
};
|
||||
use ntstatus_gen::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sock::{PamAuthRequest, PamAuthResponse, Response};
|
||||
use std::env;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct GroupToken {
|
||||
pub name: String,
|
||||
pub spn: String,
|
||||
pub object_id: String,
|
||||
pub gidnumber: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UserToken {
|
||||
pub name: String,
|
||||
pub spn: String,
|
||||
pub object_id: String,
|
||||
pub gidnumber: u32,
|
||||
pub displayname: String,
|
||||
pub shell: Option<String>,
|
||||
pub groups: Vec<GroupToken>,
|
||||
}
|
||||
|
||||
pub(crate) enum AuthCredHandler {
|
||||
MFA { flow: MFAAuthContinue },
|
||||
DeviceAuthorizationGrant { flow: DeviceAuthorizationResponse },
|
||||
SetupPin { token: UnixUserToken },
|
||||
None,
|
||||
}
|
||||
|
||||
pub(crate) enum AuthSession {
|
||||
InProgress {
|
||||
account_id: String,
|
||||
cred_handler: AuthCredHandler,
|
||||
},
|
||||
Success,
|
||||
Denied,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub(crate) fn pam_auth_init(
|
||||
&mut self,
|
||||
account_id: &str,
|
||||
) -> Result<(AuthSession, Response), Box<NTSTATUS>> {
|
||||
let auth_session = AuthSession::InProgress {
|
||||
account_id: account_id.to_string(),
|
||||
cred_handler: AuthCredHandler::None,
|
||||
};
|
||||
let hello_key = self.pcache.loadable_hello_key_fetch(account_id);
|
||||
// Skip Hello authentication if it is disabled by config
|
||||
let hello_enabled =
|
||||
self.lp.himmelblaud_hello_enabled().map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if !self.is_domain_joined() || hello_key.is_none() || !hello_enabled {
|
||||
// Send a password request to the client
|
||||
Ok((
|
||||
auth_session,
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Password),
|
||||
))
|
||||
} else {
|
||||
// Send a pin request to the client
|
||||
Ok((
|
||||
auth_session,
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Pin),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn pam_auth_step(
|
||||
&mut self,
|
||||
account_id: &str,
|
||||
cred_handler: &mut AuthCredHandler,
|
||||
pam_next_req: PamAuthRequest,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
macro_rules! enroll_and_obtain_enrolled_token {
|
||||
($token:ident) => {{
|
||||
if !self.is_domain_joined() {
|
||||
DBG_DEBUG!("Device is not enrolled. Enrolling now.");
|
||||
self.join_domain(&$token)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("Failed to join domain: {:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
}
|
||||
let mut tpm = self.hsm.lock().await;
|
||||
let mtoken2 = self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_refresh_token(
|
||||
&$token.refresh_token,
|
||||
vec!["User.Read"],
|
||||
Some("https://graph.microsoft.com".to_string()),
|
||||
&mut tpm,
|
||||
&self.machine_key,
|
||||
)
|
||||
.await;
|
||||
match mtoken2 {
|
||||
Ok(token) => token,
|
||||
Err(e) => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
match e {
|
||||
MsalError::AcquireTokenFailed(err_resp) => {
|
||||
if err_resp.error_codes.contains(&DEVICE_AUTH_FAIL) {
|
||||
/* A device authentication failure may happen
|
||||
* if Azure hasn't finished replicating the new
|
||||
* device object. Wait 5 seconds and try again. */
|
||||
DBG_INFO!("Azure hasn't finished replicating the device...");
|
||||
DBG_INFO!("Retrying in 5 seconds");
|
||||
sleep(Duration::from_secs(5));
|
||||
self.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_refresh_token(
|
||||
&$token.refresh_token,
|
||||
vec!["User.Read"],
|
||||
Some("https://graph.microsoft.com".to_string()),
|
||||
&mut tpm,
|
||||
&self.machine_key,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_FOUND)
|
||||
})?
|
||||
} else {
|
||||
return Err(Box::new(NT_STATUS_NOT_FOUND));
|
||||
}
|
||||
}
|
||||
_ => return Err(Box::new(NT_STATUS_NOT_FOUND)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
macro_rules! auth_and_validate_hello_key {
|
||||
($hello_key:ident, $cred:ident) => {{
|
||||
let token = {
|
||||
let mut tpm = self.hsm.lock().await;
|
||||
self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_hello_for_business_key(
|
||||
account_id,
|
||||
&$hello_key,
|
||||
vec!["User.Read"],
|
||||
Some("https://graph.microsoft.com".to_string()),
|
||||
&mut tpm,
|
||||
&self.machine_key,
|
||||
&$cred,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DBG_ERR!(
|
||||
"Failed to authenticate with hello key: {:?}",
|
||||
e
|
||||
);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?
|
||||
};
|
||||
|
||||
self.token_validate(account_id, &token).await
|
||||
}};
|
||||
}
|
||||
match (&mut *cred_handler, pam_next_req) {
|
||||
(
|
||||
AuthCredHandler::SetupPin { token },
|
||||
PamAuthRequest::SetupPin { pin },
|
||||
) => {
|
||||
let hello_key = {
|
||||
let mut tpm = self.hsm.lock().await;
|
||||
match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.provision_hello_for_business_key(
|
||||
&token,
|
||||
&mut tpm,
|
||||
&self.machine_key,
|
||||
&pin,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hello_key) => hello_key,
|
||||
Err(e) => {
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::SetupPin {
|
||||
msg: format!(
|
||||
"Failed to provision hello key: {:?}\n{}",
|
||||
e,
|
||||
"Create a PIN to use in place of passwords."
|
||||
),
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
self.pcache
|
||||
.loadable_hello_key_store(account_id, hello_key.clone())
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("Failed to provision hello key: {:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
|
||||
auth_and_validate_hello_key!(hello_key, pin)
|
||||
}
|
||||
(_, PamAuthRequest::Pin { pin }) => {
|
||||
let hello_key = self
|
||||
.pcache
|
||||
.loadable_hello_key_fetch(account_id)
|
||||
.ok_or_else(|| {
|
||||
DBG_ERR!("Authentication failed. Hello key missing.");
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
|
||||
auth_and_validate_hello_key!(hello_key, pin)
|
||||
}
|
||||
(_, PamAuthRequest::Password { cred }) => {
|
||||
// Always attempt to force MFA when enrolling the device, otherwise
|
||||
// the device object will not have the MFA claim. If we are already
|
||||
// enrolled but creating a new Hello Pin, we follow the same process,
|
||||
// since only an enrollment token can be exchanged for a PRT (which
|
||||
// will be needed to enroll the Hello Pin).
|
||||
let mresp = self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.initiate_acquire_token_by_mfa_flow_for_device_enrollment(
|
||||
account_id, &cred,
|
||||
)
|
||||
.await;
|
||||
// We need to wait to handle the response until after we've released
|
||||
// the lock on the client, otherwise we will deadlock.
|
||||
let resp = match mresp {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
// If SFA is disabled, we need to skip the SFA fallback.
|
||||
let sfa_enabled = self
|
||||
.lp
|
||||
.himmelblaud_sfa_fallback()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
macro_rules! init_dag {
|
||||
($msg:expr) => {{
|
||||
DBG_WARNING!(
|
||||
"SFA auth failed, falling back to DAG: {}",
|
||||
$msg
|
||||
);
|
||||
// We've exhausted alternatives, and must perform a DAG
|
||||
let resp = self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.initiate_device_flow_for_device_enrollment()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
let msg = match &resp.message {
|
||||
Some(msg) => msg.to_string(),
|
||||
None => format!("Using a browser on another \
|
||||
device, visit:\n{}\nAnd enter the code:\n{}",
|
||||
resp.verification_uri, resp.user_code),
|
||||
};
|
||||
let polling_interval = match resp.interval {
|
||||
Some(polling_interval) => polling_interval,
|
||||
None => 5,
|
||||
};
|
||||
*cred_handler = AuthCredHandler::DeviceAuthorizationGrant {
|
||||
flow: resp,
|
||||
};
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::MFAPoll {
|
||||
msg,
|
||||
polling_interval,
|
||||
}
|
||||
));
|
||||
}}
|
||||
}
|
||||
let token = if sfa_enabled {
|
||||
DBG_WARNING!(
|
||||
"MFA auth failed, falling back to SFA: {:?}",
|
||||
e
|
||||
);
|
||||
// Again, we need to wait to handle the response until after
|
||||
// we've released the write lock on the client, otherwise we
|
||||
// will deadlock.
|
||||
match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_username_password_for_device_enrollment(
|
||||
account_id, &cred,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(token) => token,
|
||||
Err(e) => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
match e {
|
||||
MsalError::AcquireTokenFailed(
|
||||
err_resp,
|
||||
) => {
|
||||
if err_resp
|
||||
.error_codes
|
||||
.contains(&REQUIRES_MFA)
|
||||
{
|
||||
init_dag!(
|
||||
err_resp.error_description
|
||||
);
|
||||
}
|
||||
return Err(Box::new(
|
||||
NT_STATUS_LOGON_FAILURE,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(Box::new(
|
||||
NT_STATUS_LOGON_FAILURE,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
init_dag!(
|
||||
"SFA fallback is disabled by configuration"
|
||||
)
|
||||
};
|
||||
let token2 = enroll_and_obtain_enrolled_token!(token);
|
||||
return self.token_validate(account_id, &token2).await;
|
||||
}
|
||||
};
|
||||
match resp.mfa_method.as_str() {
|
||||
"PhoneAppOTP" | "OneWaySMS" | "ConsolidatedTelephony" => {
|
||||
let msg = resp.msg.clone();
|
||||
*cred_handler = AuthCredHandler::MFA { flow: resp };
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::MFACode { msg },
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
let msg = resp.msg.clone();
|
||||
let polling_interval =
|
||||
resp.polling_interval.ok_or_else(|| {
|
||||
DBG_ERR!("Invalid response from the server");
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
*cred_handler = AuthCredHandler::MFA { flow: resp };
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::MFAPoll {
|
||||
msg,
|
||||
// pam expects a polling_interval in
|
||||
// seconds, not milliseconds.
|
||||
polling_interval: polling_interval / 1000,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
AuthCredHandler::DeviceAuthorizationGrant { flow },
|
||||
PamAuthRequest::MFAPoll { .. },
|
||||
) => {
|
||||
let token = match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_device_flow(flow.clone())
|
||||
.await
|
||||
{
|
||||
Err(MsalError::AcquireTokenFailed(ref resp)) => {
|
||||
if resp.error_codes.contains(&AUTH_PENDING) {
|
||||
DBG_DEBUG!(
|
||||
"Polling for acquire_token_by_device_flow"
|
||||
);
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::MFAPollWait,
|
||||
));
|
||||
} else {
|
||||
DBG_ERR!("{}", resp.error_description);
|
||||
return Err(Box::new(NT_STATUS_LOGON_FAILURE));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
return Err(Box::new(NT_STATUS_LOGON_FAILURE));
|
||||
}
|
||||
Ok(token) => token,
|
||||
};
|
||||
let token2 = enroll_and_obtain_enrolled_token!(token);
|
||||
match self.token_validate(account_id, &token2).await {
|
||||
Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
)) => {
|
||||
let mfa = token2.amr_mfa().map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_FOUND)
|
||||
})?;
|
||||
// If the DAG didn't obtain an MFA amr, and SFA fallback
|
||||
// is disabled, we need to reject the authentication
|
||||
// attempt here.
|
||||
let sfa_enabled = self
|
||||
.lp
|
||||
.himmelblaud_sfa_fallback()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if !mfa && !sfa_enabled {
|
||||
DBG_INFO!(
|
||||
"A DAG produced an SFA token, yet SFA \
|
||||
fallback is disabled by configuration"
|
||||
);
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Denied,
|
||||
));
|
||||
}
|
||||
// STOP! If the DAG doesn't hold an MFA amr, then we
|
||||
// need to bail out here and refuse Hello enrollment
|
||||
// (we can't enroll in Hello with an SFA token).
|
||||
// Also skip Hello enrollment if it is disabled by config
|
||||
let hello_enabled = self
|
||||
.lp
|
||||
.himmelblaud_hello_enabled()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if !mfa || !hello_enabled {
|
||||
if !mfa {
|
||||
DBG_INFO!(
|
||||
"Skipping Hello enrollment because \
|
||||
the token doesn't contain an MFA amr"
|
||||
);
|
||||
} else if !hello_enabled {
|
||||
DBG_INFO!(
|
||||
"Skipping Hello enrollment \
|
||||
because it is disabled"
|
||||
);
|
||||
}
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
));
|
||||
}
|
||||
|
||||
// Setup Windows Hello
|
||||
*cred_handler = AuthCredHandler::SetupPin { token };
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::SetupPin {
|
||||
msg: format!(
|
||||
"Set up a PIN\n {}{}",
|
||||
"A Hello PIN is a fast, secure way to sign",
|
||||
"in to your device, apps, and services."
|
||||
),
|
||||
},
|
||||
));
|
||||
}
|
||||
Ok(auth_result) => Ok(auth_result),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
(
|
||||
AuthCredHandler::MFA { ref mut flow },
|
||||
PamAuthRequest::MFACode { cred },
|
||||
) => {
|
||||
let token = self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_mfa_flow(
|
||||
account_id,
|
||||
Some(&cred),
|
||||
None,
|
||||
flow,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_NOT_FOUND)
|
||||
})?;
|
||||
let token2 = enroll_and_obtain_enrolled_token!(token);
|
||||
match self.token_validate(account_id, &token2).await {
|
||||
Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
)) => {
|
||||
// Skip Hello enrollment if it is disabled by config
|
||||
let hello_enabled = self
|
||||
.lp
|
||||
.himmelblaud_hello_enabled()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if !hello_enabled {
|
||||
DBG_INFO!("Skipping Hello enrollment because it is disabled");
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
));
|
||||
}
|
||||
|
||||
// Setup Windows Hello
|
||||
*cred_handler = AuthCredHandler::SetupPin { token };
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::SetupPin {
|
||||
msg: format!(
|
||||
"Set up a PIN\n {}{}",
|
||||
"A Hello PIN is a fast, secure way to sign",
|
||||
"in to your device, apps, and services."
|
||||
),
|
||||
},
|
||||
));
|
||||
}
|
||||
Ok(auth_result) => Ok(auth_result),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
(
|
||||
AuthCredHandler::MFA { flow },
|
||||
PamAuthRequest::MFAPoll { poll_attempt },
|
||||
) => {
|
||||
let max_poll_attempts =
|
||||
flow.max_poll_attempts.ok_or_else(|| {
|
||||
DBG_ERR!("Invalid response from the server");
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if poll_attempt > max_poll_attempts {
|
||||
DBG_ERR!("MFA polling timed out");
|
||||
return Err(Box::new(NT_STATUS_LOGON_FAILURE));
|
||||
}
|
||||
let token = match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.acquire_token_by_mfa_flow(
|
||||
account_id,
|
||||
None,
|
||||
Some(poll_attempt),
|
||||
flow,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(token) => token,
|
||||
Err(e) => match e {
|
||||
MsalError::MFAPollContinue => {
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::MFAPollWait,
|
||||
));
|
||||
}
|
||||
e => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
return Err(Box::new(NT_STATUS_NOT_FOUND));
|
||||
}
|
||||
},
|
||||
};
|
||||
let token2 = enroll_and_obtain_enrolled_token!(token);
|
||||
match self.token_validate(account_id, &token2).await {
|
||||
Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
)) => {
|
||||
// Skip Hello enrollment if it is disabled by config
|
||||
let hello_enabled = self
|
||||
.lp
|
||||
.himmelblaud_hello_enabled()
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if !hello_enabled {
|
||||
DBG_INFO!("Skipping Hello enrollment because it is disabled");
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Success,
|
||||
));
|
||||
}
|
||||
|
||||
// Setup Windows Hello
|
||||
*cred_handler = AuthCredHandler::SetupPin { token };
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::SetupPin {
|
||||
msg: format!(
|
||||
"Set up a PIN\n {}{}",
|
||||
"A Hello PIN is a fast, secure way to sign",
|
||||
"in to your device, apps, and services."
|
||||
),
|
||||
},
|
||||
));
|
||||
}
|
||||
Ok(auth_result) => Ok(auth_result),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
DBG_ERR!(
|
||||
"Unexpected AuthCredHandler and PamAuthRequest pairing"
|
||||
);
|
||||
Err(Box::new(NT_STATUS_NOT_IMPLEMENTED))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn token_validate(
|
||||
&mut self,
|
||||
account_id: &str,
|
||||
token: &UnixUserToken,
|
||||
) -> Result<Response, Box<NTSTATUS>> {
|
||||
match &token.access_token {
|
||||
Some(access_token) => {
|
||||
/* MFA can respond with different user than requested.
|
||||
* Azure resource names are case insensitive.
|
||||
*/
|
||||
let spn = token.spn().map_err(|e| {
|
||||
DBG_ERR!("Failed fetching user spn: {:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
if account_id.to_string().to_lowercase()
|
||||
!= spn.to_string().to_lowercase()
|
||||
{
|
||||
DBG_ERR!(
|
||||
"Authenticated user {} does not match requested user {}",
|
||||
spn, account_id
|
||||
);
|
||||
return Ok(Response::PamAuthStepResponse(
|
||||
PamAuthResponse::Denied,
|
||||
));
|
||||
}
|
||||
DBG_INFO!(
|
||||
"Authentication successful for user '{}'",
|
||||
account_id
|
||||
);
|
||||
|
||||
// Store the user in the cache
|
||||
let user_entry: UserEntry = token.try_into().map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
self.user_cache.store(user_entry).map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
|
||||
// Get the users groups, and store those groups in the cache
|
||||
let groups: Vec<GroupEntry> = self
|
||||
.graph
|
||||
.request_user_groups(access_token)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?
|
||||
.into_iter()
|
||||
.map(|g| GroupEntry::into_with_member(g, account_id))
|
||||
.collect();
|
||||
self.group_cache.merge_groups(account_id, groups).map_err(
|
||||
|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Response::PamAuthStepResponse(PamAuthResponse::Success))
|
||||
}
|
||||
None => {
|
||||
DBG_INFO!("Authentication failed for user '{}'", account_id);
|
||||
Err(Box::new(NT_STATUS_NOT_FOUND))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn join_domain(
|
||||
&mut self,
|
||||
token: &UnixUserToken,
|
||||
) -> Result<(), Box<NTSTATUS>> {
|
||||
/* If not already joined, join the domain now. */
|
||||
let attrs = EnrollAttrs::new(
|
||||
self.realm.clone(),
|
||||
None,
|
||||
Some(env::consts::OS.to_string()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
let mut tpm = self.hsm.lock().await;
|
||||
match self
|
||||
.client
|
||||
.lock()
|
||||
.await
|
||||
.enroll_device(
|
||||
&token.refresh_token,
|
||||
attrs,
|
||||
&mut tpm,
|
||||
&self.machine_key,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((
|
||||
new_loadable_transport_key,
|
||||
new_loadable_cert_key,
|
||||
device_id,
|
||||
)) => {
|
||||
DBG_INFO!("Joined domain {} ({})", self.realm, device_id);
|
||||
// Store the new_loadable_cert_key in the keystore
|
||||
self.pcache
|
||||
.loadable_cert_key_store(&self.realm, new_loadable_cert_key)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
// Store the new_loadable_transport_key
|
||||
self.pcache
|
||||
.loadable_transport_key_store(
|
||||
&self.realm,
|
||||
new_loadable_transport_key,
|
||||
)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
// Store the device_id
|
||||
self.pcache
|
||||
.device_id_store(&self.realm, &device_id)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Box::new(NT_STATUS_LOGON_FAILURE)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
Err(Box::new(NT_STATUS_LOGON_FAILURE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_domain_joined(&mut self) -> bool {
|
||||
/* If we have access to tpm keys, and the domain device_id is
|
||||
* configured, we'll assume we are domain joined. */
|
||||
let device_id = self.pcache.device_id(&self.realm);
|
||||
if device_id.is_none() {
|
||||
return false;
|
||||
}
|
||||
let transport_key =
|
||||
self.pcache.loadable_transport_key_fetch(&self.realm);
|
||||
if transport_key.is_none() {
|
||||
return false;
|
||||
}
|
||||
let cert_key = self.pcache.loadable_cert_key_fetch(&self.realm);
|
||||
if cert_key.is_none() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
476
rust/himmelblaud/src/main.rs
Normal file
476
rust/himmelblaud/src/main.rs
Normal file
@ -0,0 +1,476 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Ignore unused/dead code when running cargo test
|
||||
#![cfg_attr(test, allow(unused_imports))]
|
||||
#![cfg_attr(test, allow(dead_code))]
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use dbg::*;
|
||||
use himmelblau::graph::Graph;
|
||||
use himmelblau::BrokerClientApplication;
|
||||
use idmap::Idmap;
|
||||
use kanidm_hsm_crypto::soft::SoftTpm;
|
||||
use kanidm_hsm_crypto::{BoxedDynTpm, Tpm};
|
||||
use libc::umask;
|
||||
use param::LoadParm;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
mod constants;
|
||||
use constants::DEFAULT_ODC_PROVIDER;
|
||||
mod cache;
|
||||
mod himmelblaud;
|
||||
use cache::{GroupCache, PrivateCache, UidCache, UserCache};
|
||||
mod utils;
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> ExitCode {
|
||||
let clap_args = Command::new("himmelblaud")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.about("Samba Himmelblau Authentication Daemon")
|
||||
.arg(
|
||||
Arg::new("debuglevel")
|
||||
.help("Set debug level")
|
||||
.short('d')
|
||||
.long("debuglevel")
|
||||
.value_parser(
|
||||
clap::value_parser!(u16).range(0..(MAX_DEBUG_LEVEL as i64)),
|
||||
)
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("debug-stdout")
|
||||
.help("Send debug output to standard output")
|
||||
.long("debug-stdout")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("configfile")
|
||||
.help("Use alternative configuration file")
|
||||
.short('s')
|
||||
.long("configfile")
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let stop_now = Arc::new(AtomicBool::new(false));
|
||||
let terminate_now = Arc::clone(&stop_now);
|
||||
let quit_now = Arc::clone(&stop_now);
|
||||
let interrupt_now = Arc::clone(&stop_now);
|
||||
|
||||
let frame = talloc::talloc_stackframe!();
|
||||
|
||||
async {
|
||||
// Set the command line debug level
|
||||
if let Some(debuglevel) = clap_args.get_one::<u16>("debuglevel") {
|
||||
debuglevel_set!(*debuglevel);
|
||||
}
|
||||
|
||||
// Initialize the LoadParm from the command line specified config file
|
||||
let lp = match LoadParm::new(
|
||||
clap_args
|
||||
.get_one::<String>("configfile")
|
||||
.map(|x| x.as_str()),
|
||||
) {
|
||||
Ok(lp) => lp,
|
||||
Err(e) => {
|
||||
eprintln!("Failed loading smb.conf: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
// Check that the realm is configured. This the bare minimum for
|
||||
// himmelblaud, since a device join can happen at authentication time,
|
||||
// but we need to know the permitted enrollment domain.
|
||||
let realm = match lp.realm() {
|
||||
Ok(Some(realm)) => realm,
|
||||
_ => {
|
||||
eprintln!(
|
||||
"The realm MUST be set in the \
|
||||
smb.conf to start himmelblaud"
|
||||
);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
// Setup logging, either to the configured logfile, or to stdout, depending
|
||||
// on what is specified on the command line.
|
||||
match clap_args.get_flag("debug-stdout") {
|
||||
true => setup_logging(env!("CARGO_PKG_NAME"), DEBUG_STDOUT),
|
||||
false => {
|
||||
setup_logging(env!("CARGO_PKG_NAME"), DEBUG_FILE);
|
||||
match lp.logfile() {
|
||||
Ok(Some(logfile)) => debug_set_logfile(&logfile),
|
||||
_ => {
|
||||
eprintln!("Failed to determine logfile name");
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the unix socket path
|
||||
let sock_dir_str = match lp.winbindd_socket_directory() {
|
||||
Ok(Some(sock_dir)) => sock_dir,
|
||||
_ => {
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
let sock_dir = Path::new(&sock_dir_str);
|
||||
let mut sock_path_buf = PathBuf::from(sock_dir);
|
||||
sock_path_buf.push("hb_pipe");
|
||||
let sock_path = match sock_path_buf.to_str() {
|
||||
Some(sock_path) => sock_path,
|
||||
None => {
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if sock_path_buf.exists() {
|
||||
DBG_DEBUG!("Cleaning up socket from previous invocations");
|
||||
if let Err(_) = std::fs::remove_file(sock_path) {
|
||||
DBG_ERR!("Failed removing socket");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the Himmelblau cache
|
||||
let private_cache_path = match lp.private_path("himmelblau.tdb") {
|
||||
Ok(Some(private_cache_path)) => private_cache_path,
|
||||
_ => {
|
||||
DBG_ERR!("Failed to determine private cache path");
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
let mut private_dir = Path::new(&private_cache_path).to_path_buf();
|
||||
private_dir.pop();
|
||||
if !private_dir.exists() {
|
||||
DBG_ERR!(
|
||||
"The private directory '{}' does not exist",
|
||||
private_dir.display()
|
||||
);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
let mut pcache = match PrivateCache::new(&private_cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) => {
|
||||
DBG_ERR!(
|
||||
"Failed to open the himmelblau private cache: {:?}",
|
||||
e
|
||||
);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let cache_dir = match lp.cache_directory() {
|
||||
Ok(Some(cache_dir)) => cache_dir,
|
||||
_ => {
|
||||
DBG_ERR!("Failed to determine cache directory");
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if !Path::new(&cache_dir).exists() {
|
||||
DBG_ERR!("The cache directory '{}' does not exist", cache_dir);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
let user_cache_path = Path::new(&cache_dir)
|
||||
.join("himmelblau_users.tdb")
|
||||
.display()
|
||||
.to_string();
|
||||
let user_cache = match UserCache::new(&user_cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to open the himmelblau user cache: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let uid_cache_path = Path::new(&cache_dir)
|
||||
.join("himmelblau_uid_map.tdb")
|
||||
.display()
|
||||
.to_string();
|
||||
let uid_cache = match UidCache::new(&uid_cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to open the himmelblau uid cache: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let group_cache_path = Path::new(&cache_dir)
|
||||
.join("himmelblau_groups.tdb")
|
||||
.display()
|
||||
.to_string();
|
||||
let group_cache = match GroupCache::new(&group_cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed to open the himmelblau group cache: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
// Check for and create the hsm pin if required.
|
||||
let hsm_pin_path = match lp.himmelblaud_hsm_pin_path() {
|
||||
Ok(Some(hsm_pin_path)) => hsm_pin_path,
|
||||
_ => {
|
||||
DBG_ERR!("Failed loading hsm pin path.");
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
let mut hsm_pin_dir = Path::new(&hsm_pin_path).to_path_buf();
|
||||
hsm_pin_dir.pop();
|
||||
if !hsm_pin_dir.exists() {
|
||||
DBG_ERR!(
|
||||
"The hsm pin directory '{}' does not exist",
|
||||
hsm_pin_dir.display()
|
||||
);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
let auth_value =
|
||||
match utils::hsm_pin_fetch_or_create(&hsm_pin_path).await {
|
||||
Ok(auth_value) => auth_value,
|
||||
Err(e) => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
// Setup the HSM and its machine key
|
||||
let mut hsm: BoxedDynTpm = BoxedDynTpm::new(SoftTpm::new());
|
||||
|
||||
let loadable_machine_key = match pcache
|
||||
.loadable_machine_key_fetch_or_create(&mut hsm, &auth_value)
|
||||
{
|
||||
Ok(lmk) => lmk,
|
||||
Err(e) => {
|
||||
DBG_ERR!("{:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let res = hsm.machine_key_load(&auth_value, &loadable_machine_key);
|
||||
let machine_key = match res {
|
||||
Ok(machine_key) => machine_key,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Unable to load machine root key: {:?}", e);
|
||||
DBG_INFO!("This can occur if you have changed your HSM pin.");
|
||||
DBG_INFO!(
|
||||
"To proceed, run `tdbtool erase {}`",
|
||||
private_cache_path
|
||||
);
|
||||
DBG_INFO!("The host will forget domain enrollments.");
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
// Get the transport key for a joined domain
|
||||
let loadable_transport_key =
|
||||
pcache.loadable_transport_key_fetch(&realm);
|
||||
|
||||
// Get the certificate key for a joined domain
|
||||
let loadable_cert_key = pcache.loadable_cert_key_fetch(&realm);
|
||||
|
||||
// Contact the odc provider to get the authority host and tenant id
|
||||
let graph = match Graph::new(DEFAULT_ODC_PROVIDER, &realm).await {
|
||||
Ok(graph) => graph,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed initializing the graph: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
let authority_host = graph.authority_host();
|
||||
let tenant_id = graph.tenant_id();
|
||||
let authority = format!("https://{}/{}", authority_host, tenant_id);
|
||||
|
||||
let client = match BrokerClientApplication::new(
|
||||
Some(&authority),
|
||||
loadable_transport_key,
|
||||
loadable_cert_key,
|
||||
) {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed initializing the broker: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let mut idmap = match Idmap::new() {
|
||||
Ok(idmap) => idmap,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed initializing the idmapper: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
// Configure the idmap range
|
||||
let (low, high) = match lp.idmap_range(&realm) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed fetching idmap range: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if let Err(e) = idmap.add_gen_domain(&realm, &tenant_id, (low, high)) {
|
||||
DBG_ERR!("Failed adding the domain idmap range: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
let resolver = Arc::new(Mutex::new(himmelblaud::Resolver::new(
|
||||
&realm,
|
||||
&tenant_id,
|
||||
lp,
|
||||
idmap,
|
||||
graph,
|
||||
pcache,
|
||||
user_cache,
|
||||
uid_cache,
|
||||
group_cache,
|
||||
hsm,
|
||||
machine_key,
|
||||
client,
|
||||
)));
|
||||
|
||||
// Set the umask while we open the path for most clients.
|
||||
let before = unsafe { umask(0) };
|
||||
// Listen for incoming requests from PAM and NSS
|
||||
let listener = match UnixListener::bind(sock_path) {
|
||||
Ok(listener) => listener,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed setting up the socket listener: {:?}", e);
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
// Undo umask changes.
|
||||
let _ = unsafe { umask(before) };
|
||||
|
||||
let server = tokio::spawn(async move {
|
||||
while !stop_now.load(Ordering::Relaxed) {
|
||||
let resolver_ref = resolver.clone();
|
||||
match listener.accept().await {
|
||||
Ok((socket, _addr)) => {
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = himmelblaud::handle_client(
|
||||
socket,
|
||||
resolver_ref.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
DBG_ERR!(
|
||||
"handle_client error occurred: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("Error while handling connection: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let terminate_task = tokio::spawn(async move {
|
||||
match signal(SignalKind::terminate()) {
|
||||
Ok(mut stream) => {
|
||||
stream.recv().await;
|
||||
terminate_now.store(true, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed registering terminate signal: {}", e);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let quit_task = tokio::spawn(async move {
|
||||
match signal(SignalKind::quit()) {
|
||||
Ok(mut stream) => {
|
||||
stream.recv().await;
|
||||
quit_now.store(true, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed registering quit signal: {}", e);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
let interrupt_task = tokio::spawn(async move {
|
||||
match signal(SignalKind::interrupt()) {
|
||||
Ok(mut stream) => {
|
||||
stream.recv().await;
|
||||
interrupt_now.store(true, Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("Failed registering interrupt signal: {}", e);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
DBG_INFO!("Server started ...");
|
||||
|
||||
tokio::select! {
|
||||
_ = server => {
|
||||
DBG_DEBUG!("Main listener task is terminating");
|
||||
},
|
||||
_ = terminate_task => {
|
||||
DBG_DEBUG!("Received signal to terminate");
|
||||
},
|
||||
_ = quit_task => {
|
||||
DBG_DEBUG!("Received signal to quit");
|
||||
},
|
||||
_ = interrupt_task => {
|
||||
DBG_DEBUG!("Received signal to interrupt");
|
||||
}
|
||||
}
|
||||
|
||||
talloc::TALLOC_FREE!(frame);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
.await
|
||||
}
|
149
rust/himmelblaud/src/utils.rs
Normal file
149
rust/himmelblaud/src/utils.rs
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Himmelblau daemon common utilities
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use dbg::{DBG_ERR, DBG_INFO};
|
||||
use kanidm_hsm_crypto::AuthValue;
|
||||
use ntstatus_gen::*;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
pub fn split_username(
|
||||
username: &str,
|
||||
) -> Result<(String, String), Box<NTSTATUS>> {
|
||||
let tup: Vec<&str> = username.split('@').collect();
|
||||
if tup.len() == 2 {
|
||||
return Ok((tup[0].to_string(), tup[1].to_string()));
|
||||
}
|
||||
Err(Box::new(NT_STATUS_INVALID_USER_PRINCIPAL_NAME))
|
||||
}
|
||||
|
||||
pub(crate) async fn hsm_pin_fetch_or_create(
|
||||
hsm_pin_path: &str,
|
||||
) -> Result<AuthValue, Box<NTSTATUS>> {
|
||||
let auth_value = if !PathBuf::from_str(hsm_pin_path)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("Failed to create hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})?
|
||||
.exists()
|
||||
{
|
||||
let auth_value = AuthValue::generate().map_err(|e| {
|
||||
DBG_ERR!("Failed to create hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})?;
|
||||
std::fs::write(hsm_pin_path, auth_value.clone()).map_err(|e| {
|
||||
DBG_ERR!("Failed to write hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})?;
|
||||
|
||||
DBG_INFO!("Generated new HSM pin");
|
||||
auth_value
|
||||
} else {
|
||||
let mut file = File::open(hsm_pin_path).await.map_err(|e| {
|
||||
DBG_ERR!("Failed to read hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})?;
|
||||
let mut auth_value = vec![];
|
||||
file.read_to_end(&mut auth_value).await.map_err(|e| {
|
||||
DBG_ERR!("Failed to read hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})?;
|
||||
std::str::from_utf8(&auth_value)
|
||||
.map_err(|e| {
|
||||
DBG_ERR!("Failed to read hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})?
|
||||
.to_string()
|
||||
};
|
||||
AuthValue::try_from(auth_value.as_bytes()).map_err(|e| {
|
||||
DBG_ERR!("Invalid hsm pin: {:?}", e);
|
||||
Box::new(NT_STATUS_UNSUCCESSFUL)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use tempfile::tempdir;
|
||||
use tokio::fs;
|
||||
|
||||
#[test]
|
||||
fn test_split_username_success() {
|
||||
let username = "user@domain.com";
|
||||
let result = split_username(username);
|
||||
assert!(result.is_ok());
|
||||
let (user, domain) = result.unwrap();
|
||||
assert_eq!(user, "user");
|
||||
assert_eq!(domain, "domain.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_username_failure() {
|
||||
let username = "invalid_username";
|
||||
let result = split_username(username);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(*result.unwrap_err(), NT_STATUS_INVALID_USER_PRINCIPAL_NAME);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_hsm_pin_fetch_or_create_generate() {
|
||||
let dir = tempdir().unwrap();
|
||||
let path = dir.path().join("hsm_pin");
|
||||
|
||||
let result = hsm_pin_fetch_or_create(path.to_str().unwrap()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify that the file is created and contains a valid auth value
|
||||
let saved_pin = fs::read(path).await.expect("Auth value missing");
|
||||
AuthValue::try_from(saved_pin.as_slice())
|
||||
.expect("Failed parsing auth value");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_hsm_pin_fetch_or_create_invalid_path() {
|
||||
let result = hsm_pin_fetch_or_create("invalid_path\0").await;
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(e) => assert_eq!(*e, NT_STATUS_UNSUCCESSFUL),
|
||||
Ok(_) => panic!("Expected error but got success"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_hsm_pin_fetch_or_create_invalid_auth_value() {
|
||||
let dir = tempdir().unwrap();
|
||||
let path = dir.path().join("hsm_pin");
|
||||
|
||||
// Write invalid content to the file
|
||||
let mut file = std::fs::File::create(&path).unwrap();
|
||||
file.write_all(b"invalid_auth_value").unwrap();
|
||||
|
||||
// Test reading the invalid file
|
||||
let result = hsm_pin_fetch_or_create(path.to_str().unwrap()).await;
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(e) => assert_eq!(*e, NT_STATUS_UNSUCCESSFUL),
|
||||
Ok(_) => panic!("Expected error but got success"),
|
||||
}
|
||||
}
|
||||
}
|
15
rust/idmap/Cargo.toml
Normal file
15
rust/idmap/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "idmap"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
chelps.workspace = true
|
||||
dbg.workspace = true
|
||||
libc.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.97"
|
||||
bindgen = "0.69.4"
|
33
rust/idmap/build.rs
Normal file
33
rust/idmap/build.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("src/sss_idmap.c")
|
||||
.file("src/sss_idmap_conv.c")
|
||||
.file("src/murmurhash3.c")
|
||||
.include(Path::new("../../bin/default/include"))
|
||||
.warnings(false)
|
||||
.compile("sss_idmap");
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.blocklist_function("qgcvt")
|
||||
.blocklist_function("qgcvt_r")
|
||||
.blocklist_function("qfcvt")
|
||||
.blocklist_function("qfcvt_r")
|
||||
.blocklist_function("qecvt")
|
||||
.blocklist_function("qecvt_r")
|
||||
.blocklist_function("strtold")
|
||||
.header("src/sss_idmap.h")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
println!("cargo:rustc-link-lib=utf8proc");
|
||||
println!("cargo:rustc-env=LD_LIBRARY_PATH=../../bin/shared/private/");
|
||||
}
|
227
rust/idmap/src/lib.rs
Normal file
227
rust/idmap/src/lib.rs
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
Himmelblaud
|
||||
|
||||
ID-mapping library
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use dbg::DBG_ERR;
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod ffi {
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct IdmapError(u32);
|
||||
|
||||
pub const IDMAP_SUCCESS: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_SUCCESS);
|
||||
pub const IDMAP_NOT_IMPLEMENTED: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_NOT_IMPLEMENTED);
|
||||
pub const IDMAP_ERROR: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_ERROR);
|
||||
pub const IDMAP_OUT_OF_MEMORY: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_OUT_OF_MEMORY);
|
||||
pub const IDMAP_NO_DOMAIN: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_NO_DOMAIN);
|
||||
pub const IDMAP_CONTEXT_INVALID: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_CONTEXT_INVALID);
|
||||
pub const IDMAP_SID_INVALID: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_SID_INVALID);
|
||||
pub const IDMAP_SID_UNKNOWN: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_SID_UNKNOWN);
|
||||
pub const IDMAP_NO_RANGE: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_NO_RANGE);
|
||||
pub const IDMAP_BUILTIN_SID: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_BUILTIN_SID);
|
||||
pub const IDMAP_OUT_OF_SLICES: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_OUT_OF_SLICES);
|
||||
pub const IDMAP_COLLISION: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_COLLISION);
|
||||
pub const IDMAP_EXTERNAL: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_EXTERNAL);
|
||||
pub const IDMAP_NAME_UNKNOWN: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_NAME_UNKNOWN);
|
||||
pub const IDMAP_NO_REVERSE: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_NO_REVERSE);
|
||||
pub const IDMAP_ERR_LAST: IdmapError =
|
||||
IdmapError(ffi::idmap_error_code_IDMAP_ERR_LAST);
|
||||
|
||||
impl fmt::Display for IdmapError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "IdmapError({:#x})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for IdmapError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "IdmapError({:#x})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for IdmapError {}
|
||||
|
||||
pub struct Idmap {
|
||||
ctx: Arc<Mutex<*mut ffi::sss_idmap_ctx>>,
|
||||
}
|
||||
|
||||
impl Idmap {
|
||||
pub fn new() -> Result<Idmap, IdmapError> {
|
||||
let mut ctx = ptr::null_mut();
|
||||
unsafe {
|
||||
match IdmapError(ffi::sss_idmap_init(
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
&mut ctx,
|
||||
)) {
|
||||
IDMAP_SUCCESS => Ok(Idmap {
|
||||
ctx: Arc::new(Mutex::new(ctx)),
|
||||
}),
|
||||
e => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_gen_domain(
|
||||
&mut self,
|
||||
domain_name: &str,
|
||||
tenant_id: &str,
|
||||
range: (u32, u32),
|
||||
) -> Result<(), IdmapError> {
|
||||
let ctx = self.ctx.lock().map_err(|e| {
|
||||
DBG_ERR!("Failed obtaining write lock on sss_idmap_ctx: {}", e);
|
||||
IDMAP_ERROR
|
||||
})?;
|
||||
let domain_name_cstr =
|
||||
CString::new(domain_name).map_err(|_| IDMAP_OUT_OF_MEMORY)?;
|
||||
let tenant_id_cstr =
|
||||
CString::new(tenant_id).map_err(|_| IDMAP_OUT_OF_MEMORY)?;
|
||||
let mut idmap_range = ffi::sss_idmap_range {
|
||||
min: range.0,
|
||||
max: range.1,
|
||||
};
|
||||
unsafe {
|
||||
match IdmapError(ffi::sss_idmap_add_gen_domain_ex(
|
||||
*ctx,
|
||||
domain_name_cstr.as_ptr(),
|
||||
tenant_id_cstr.as_ptr(),
|
||||
&mut idmap_range,
|
||||
ptr::null_mut(),
|
||||
None,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
false,
|
||||
)) {
|
||||
IDMAP_SUCCESS => Ok(()),
|
||||
e => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_to_unix(
|
||||
&self,
|
||||
tenant_id: &str,
|
||||
input: &str,
|
||||
) -> Result<u32, IdmapError> {
|
||||
let ctx = self.ctx.lock().map_err(|e| {
|
||||
DBG_ERR!("Failed obtaining write lock on sss_idmap_ctx: {}", e);
|
||||
IDMAP_ERROR
|
||||
})?;
|
||||
let tenant_id_cstr =
|
||||
CString::new(tenant_id).map_err(|_| IDMAP_OUT_OF_MEMORY)?;
|
||||
let input_cstr = CString::new(input.to_lowercase())
|
||||
.map_err(|_| IDMAP_OUT_OF_MEMORY)?;
|
||||
unsafe {
|
||||
let mut id: u32 = 0;
|
||||
match IdmapError(ffi::sss_idmap_gen_to_unix(
|
||||
*ctx,
|
||||
tenant_id_cstr.as_ptr(),
|
||||
input_cstr.as_ptr(),
|
||||
&mut id,
|
||||
)) {
|
||||
IDMAP_SUCCESS => Ok(id),
|
||||
e => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Idmap {
|
||||
fn drop(&mut self) {
|
||||
match self.ctx.lock() {
|
||||
Ok(ctx) => unsafe {
|
||||
let _ = ffi::sss_idmap_free(*ctx);
|
||||
},
|
||||
Err(e) => {
|
||||
DBG_ERR!(
|
||||
"Failed obtaining write lock on sss_idmap_ctx during drop: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Idmap {}
|
||||
unsafe impl Sync for Idmap {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Idmap;
|
||||
use std::collections::HashMap;
|
||||
pub const DEFAULT_IDMAP_RANGE: (u32, u32) = (200000, 2000200000);
|
||||
|
||||
#[test]
|
||||
fn sssd_idmapping() {
|
||||
let domain = "contoso.onmicrosoft.com";
|
||||
let tenant_id = "d7af6c1b-0497-40fe-9d17-07e6b0f8332e";
|
||||
let mut idmap = Idmap::new().expect("Idmap initialization failed");
|
||||
|
||||
idmap
|
||||
.add_gen_domain(domain, tenant_id, DEFAULT_IDMAP_RANGE)
|
||||
.expect("Failed initializing test domain idmapping");
|
||||
|
||||
// Verify we always get the same mapping for various users
|
||||
let mut usermap: HashMap<String, u32> = HashMap::new();
|
||||
usermap.insert("tux@contoso.onmicrosoft.com".to_string(), 1912749799);
|
||||
usermap.insert("admin@contoso.onmicrosoft.com".to_string(), 297515919);
|
||||
usermap.insert("dave@contoso.onmicrosoft.com".to_string(), 132631922);
|
||||
usermap.insert("joe@contoso.onmicrosoft.com".to_string(), 361591965);
|
||||
usermap.insert("georg@contoso.onmicrosoft.com".to_string(), 866887005);
|
||||
|
||||
for (username, expected_uid) in &usermap {
|
||||
let uid = idmap.gen_to_unix(tenant_id, username).expect(&format!(
|
||||
"Failed converting username {} to uid",
|
||||
username
|
||||
));
|
||||
assert_eq!(
|
||||
uid, *expected_uid,
|
||||
"Uid for {} did not match",
|
||||
username
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
114
rust/idmap/src/murmurhash3.c
Normal file
114
rust/idmap/src/murmurhash3.c
Normal file
@ -0,0 +1,114 @@
|
||||
/* This file is based on the public domain MurmurHash3 from Austin Appleby:
|
||||
* http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
|
||||
*
|
||||
* We use only the 32 bit variant because the 2 produce different result while
|
||||
* we need to produce the same result regardless of the architecture as
|
||||
* clients can be both 64 or 32 bit at the same time.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "murmurhash3.h"
|
||||
#include "util.h"
|
||||
|
||||
static uint32_t rotl(uint32_t x, int8_t r)
|
||||
{
|
||||
return (x << r) | (x >> (32 - r));
|
||||
}
|
||||
|
||||
/* slower than original but is endian neutral and handles platforms that
|
||||
* do only aligned reads */
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t getblock(const uint8_t *p, int i)
|
||||
{
|
||||
uint32_t r;
|
||||
size_t size = sizeof(uint32_t);
|
||||
|
||||
memcpy(&r, &p[i * size], size);
|
||||
|
||||
return le32toh(r);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finalization mix - force all bits of a hash block to avalanche
|
||||
*/
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t fmix(uint32_t h)
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6b;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35;
|
||||
h ^= h >> 16;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
uint32_t murmurhash3(const char *key, int len, uint32_t seed)
|
||||
{
|
||||
const uint8_t *blocks;
|
||||
const uint8_t *tail;
|
||||
int nblocks;
|
||||
uint32_t h1;
|
||||
uint32_t k1;
|
||||
uint32_t c1;
|
||||
uint32_t c2;
|
||||
int i;
|
||||
|
||||
blocks = (const uint8_t *)key;
|
||||
nblocks = len / 4;
|
||||
h1 = seed;
|
||||
c1 = 0xcc9e2d51;
|
||||
c2 = 0x1b873593;
|
||||
|
||||
/* body */
|
||||
|
||||
for (i = 0; i < nblocks; i++) {
|
||||
|
||||
k1 = getblock(blocks, i);
|
||||
|
||||
k1 *= c1;
|
||||
k1 = rotl(k1, 15);
|
||||
k1 *= c2;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = rotl(h1, 13);
|
||||
h1 = h1 * 5 + 0xe6546b64;
|
||||
}
|
||||
|
||||
/* tail */
|
||||
|
||||
tail = (const uint8_t *)key + nblocks * 4;
|
||||
|
||||
k1 = 0;
|
||||
|
||||
switch (len & 3) {
|
||||
case 3:
|
||||
k1 ^= tail[2] << 16;
|
||||
SSS_ATTRIBUTE_FALLTHROUGH;
|
||||
case 2:
|
||||
k1 ^= tail[1] << 8;
|
||||
SSS_ATTRIBUTE_FALLTHROUGH;
|
||||
case 1:
|
||||
k1 ^= tail[0];
|
||||
k1 *= c1;
|
||||
k1 = rotl(k1, 15);
|
||||
k1 *= c2;
|
||||
h1 ^= k1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* finalization */
|
||||
|
||||
h1 ^= len;
|
||||
h1 = fmix(h1);
|
||||
|
||||
return h1;
|
||||
}
|
21
rust/idmap/src/murmurhash3.h
Normal file
21
rust/idmap/src/murmurhash3.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* This file is based on the public domain MurmurHash3 from Austin Appleby:
|
||||
* http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
|
||||
*
|
||||
* We use only the 32 bit variant because the 2 produce different result while
|
||||
* we need to produce the same result regardless of the architecture as
|
||||
* clients can be both 64 or 32 bit at the same time.
|
||||
*/
|
||||
|
||||
#ifndef _SHARED_MURMURHASH3_H_
|
||||
#define _SHARED_MURMURHASH3_H_
|
||||
|
||||
/* CAUTION:
|
||||
* This file is also used in sss_client (pam, nss). Therefore it have to be
|
||||
* minimalist and cannot include DEBUG macros or header file util.h.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint32_t murmurhash3(const char *key, int len, uint32_t seed);
|
||||
|
||||
#endif /* _SHARED_MURMURHASH3_H_ */
|
1909
rust/idmap/src/sss_idmap.c
Normal file
1909
rust/idmap/src/sss_idmap.c
Normal file
File diff suppressed because it is too large
Load Diff
1100
rust/idmap/src/sss_idmap.h
Normal file
1100
rust/idmap/src/sss_idmap.h
Normal file
File diff suppressed because it is too large
Load Diff
570
rust/idmap/src/sss_idmap_conv.c
Normal file
570
rust/idmap/src/sss_idmap_conv.c
Normal file
@ -0,0 +1,570 @@
|
||||
/*
|
||||
SSSD
|
||||
|
||||
ID-mapping library - conversion utilities
|
||||
|
||||
Authors:
|
||||
Sumit Bose <sbose@redhat.com>
|
||||
|
||||
Copyright (C) 2012 Red Hat
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "sss_idmap.h"
|
||||
#include "sss_idmap_private.h"
|
||||
//#include "util/util.h"
|
||||
//#include "util/sss_endian.h"
|
||||
#include "util.h"
|
||||
|
||||
#define SID_ID_AUTHS 6
|
||||
#define SID_SUB_AUTHS 15
|
||||
struct sss_dom_sid {
|
||||
uint8_t sid_rev_num;
|
||||
int8_t num_auths; /* [range(0,15)] */
|
||||
uint8_t id_auth[SID_ID_AUTHS]; /* highest order byte has index 0 */
|
||||
uint32_t sub_auths[SID_SUB_AUTHS]; /* host byte-order */
|
||||
};
|
||||
|
||||
enum idmap_error_code sss_idmap_bin_sid_to_dom_sid(struct sss_idmap_ctx *ctx,
|
||||
const uint8_t *bin_sid,
|
||||
size_t length,
|
||||
struct sss_dom_sid **_dom_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid;
|
||||
size_t i = 0;
|
||||
size_t p = 0;
|
||||
uint32_t val;
|
||||
|
||||
CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID);
|
||||
|
||||
if (length > sizeof(struct sss_dom_sid)) return IDMAP_SID_INVALID;
|
||||
|
||||
dom_sid = ctx->alloc_func(sizeof(struct sss_dom_sid), ctx->alloc_pvt);
|
||||
if (dom_sid == NULL) {
|
||||
return IDMAP_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(dom_sid, 0, sizeof(struct sss_dom_sid));
|
||||
|
||||
/* Safely copy in the SID revision number */
|
||||
dom_sid->sid_rev_num = (uint8_t) *(bin_sid + p);
|
||||
p++;
|
||||
|
||||
/* Safely copy in the number of sub auth values */
|
||||
dom_sid->num_auths = (uint8_t) *(bin_sid + p);
|
||||
p++;
|
||||
|
||||
/* Make sure we aren't being told to read more bin_sid
|
||||
* than can fit in the structure
|
||||
*/
|
||||
if (dom_sid->num_auths > SID_SUB_AUTHS) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Safely copy in the id_auth values */
|
||||
for (i = 0; i < SID_ID_AUTHS; i++) {
|
||||
dom_sid->id_auth[i] = (uint8_t) *(bin_sid + p);
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Safely copy in the sub_auths values */
|
||||
for (i = 0; i < dom_sid->num_auths; i++) {
|
||||
/* SID sub auth values in Active Directory are stored little-endian,
|
||||
* we store them in host order */
|
||||
SAFEALIGN_COPY_UINT32(&val, bin_sid + p, &p);
|
||||
dom_sid->sub_auths[i] = le32toh(val);
|
||||
}
|
||||
|
||||
*_dom_sid = dom_sid;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_dom_sid_to_bin_sid(struct sss_idmap_ctx *ctx,
|
||||
struct sss_dom_sid *dom_sid,
|
||||
uint8_t **_bin_sid,
|
||||
size_t *_length)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
uint8_t *bin_sid;
|
||||
size_t length;
|
||||
size_t i = 0;
|
||||
size_t p = 0;
|
||||
uint32_t val;
|
||||
|
||||
CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID);
|
||||
|
||||
if (dom_sid->num_auths > SID_SUB_AUTHS) {
|
||||
return IDMAP_SID_INVALID;
|
||||
}
|
||||
|
||||
length = 2 + SID_ID_AUTHS + dom_sid->num_auths * 4;
|
||||
|
||||
bin_sid = ctx->alloc_func(length, ctx->alloc_pvt);
|
||||
if (bin_sid == NULL) {
|
||||
return IDMAP_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bin_sid[p] = dom_sid->sid_rev_num;
|
||||
p++;
|
||||
|
||||
bin_sid[p] = dom_sid->num_auths;
|
||||
p++;
|
||||
|
||||
for (i = 0; i < SID_ID_AUTHS; i++) {
|
||||
bin_sid[p] = dom_sid->id_auth[i];
|
||||
p++;
|
||||
}
|
||||
|
||||
for (i = 0; i < dom_sid->num_auths; i++) {
|
||||
if (p + sizeof(uint32_t) > length) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
val = htole32(dom_sid->sub_auths[i]);
|
||||
SAFEALIGN_COPY_UINT32(bin_sid + p, &val, &p);
|
||||
}
|
||||
|
||||
*_bin_sid = bin_sid;
|
||||
*_length = length;
|
||||
|
||||
err = IDMAP_SUCCESS;
|
||||
done:
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(bin_sid, ctx->alloc_pvt);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_dom_sid_to_sid(struct sss_idmap_ctx *ctx,
|
||||
struct sss_dom_sid *dom_sid,
|
||||
char **_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
char *sid_buf;
|
||||
size_t sid_buf_len;
|
||||
char *p;
|
||||
int nc;
|
||||
int8_t i;
|
||||
uint32_t id_auth_val = 0;
|
||||
|
||||
if (dom_sid->num_auths > SID_SUB_AUTHS) {
|
||||
return IDMAP_SID_INVALID;
|
||||
}
|
||||
|
||||
sid_buf_len = 25 + dom_sid->num_auths * 11;
|
||||
sid_buf = ctx->alloc_func(sid_buf_len, ctx->alloc_pvt);
|
||||
if (sid_buf == NULL) {
|
||||
return IDMAP_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(sid_buf, 0, sid_buf_len);
|
||||
|
||||
/* Only 32bits are used for the string representation */
|
||||
id_auth_val = (dom_sid->id_auth[2] << 24) +
|
||||
(dom_sid->id_auth[3] << 16) +
|
||||
(dom_sid->id_auth[4] << 8) +
|
||||
(dom_sid->id_auth[5]);
|
||||
|
||||
nc = snprintf(sid_buf, sid_buf_len, "S-%u-%lu", dom_sid->sid_rev_num,
|
||||
(unsigned long) id_auth_val);
|
||||
if (nc < 0 || nc >= sid_buf_len) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
||||
/* Loop through the sub-auths, if any, prepending a hyphen
|
||||
* for each one.
|
||||
*/
|
||||
p = sid_buf;
|
||||
for (i = 0; i < dom_sid->num_auths ; i++) {
|
||||
p += nc;
|
||||
sid_buf_len -= nc;
|
||||
|
||||
nc = snprintf(p, sid_buf_len, "-%lu",
|
||||
(unsigned long) dom_sid->sub_auths[i]);
|
||||
if (nc < 0 || nc >= sid_buf_len) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
*_sid = sid_buf;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(sid_buf, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_sid_to_dom_sid(struct sss_idmap_ctx *ctx,
|
||||
const char *sid,
|
||||
struct sss_dom_sid **_dom_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
unsigned long ul;
|
||||
char *r;
|
||||
char *end;
|
||||
struct sss_dom_sid *dom_sid;
|
||||
|
||||
CHECK_IDMAP_CTX(ctx, IDMAP_CONTEXT_INVALID);
|
||||
|
||||
if (sid == NULL || (sid[0] != 'S' && sid[0] != 's') || sid[1] != '-') {
|
||||
return IDMAP_SID_INVALID;
|
||||
}
|
||||
|
||||
dom_sid = ctx->alloc_func(sizeof(struct sss_dom_sid), ctx->alloc_pvt);
|
||||
if (dom_sid == NULL) {
|
||||
return IDMAP_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(dom_sid, 0, sizeof(struct sss_dom_sid));
|
||||
|
||||
|
||||
if (!isdigit(sid[2])) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
errno = 0;
|
||||
ul = strtoul(sid + 2, &r, 10);
|
||||
if (errno != 0 || r == NULL || *r != '-' || ul > UINT8_MAX) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
dom_sid->sid_rev_num = (uint8_t) ul;
|
||||
r++;
|
||||
|
||||
if (!isdigit(*r)) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
errno = 0;
|
||||
ul = strtoul(r, &r, 10);
|
||||
if (errno != 0 || r == NULL || ul > UINT32_MAX) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* id_auth in the string should always be <2^32 in decimal */
|
||||
/* store values in the same order as the binary representation */
|
||||
dom_sid->id_auth[0] = 0;
|
||||
dom_sid->id_auth[1] = 0;
|
||||
dom_sid->id_auth[2] = (ul & 0xff000000) >> 24;
|
||||
dom_sid->id_auth[3] = (ul & 0x00ff0000) >> 16;
|
||||
dom_sid->id_auth[4] = (ul & 0x0000ff00) >> 8;
|
||||
dom_sid->id_auth[5] = (ul & 0x000000ff);
|
||||
|
||||
if (*r == '\0') {
|
||||
/* no sub auths given */
|
||||
err = IDMAP_SUCCESS;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (*r != '-') {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
do {
|
||||
if (dom_sid->num_auths >= SID_SUB_AUTHS) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
r++;
|
||||
if (!isdigit(*r)) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
ul = strtoul(r, &end, 10);
|
||||
if (errno != 0 || ul > UINT32_MAX || end == NULL ||
|
||||
(*end != '\0' && *end != '-')) {
|
||||
err = IDMAP_SID_INVALID;
|
||||
goto done;
|
||||
}
|
||||
|
||||
dom_sid->sub_auths[dom_sid->num_auths++] = ul;
|
||||
|
||||
r = end;
|
||||
} while (*r != '\0');
|
||||
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
} else {
|
||||
*_dom_sid = dom_sid;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_sid_to_bin_sid(struct sss_idmap_ctx *ctx,
|
||||
const char *sid,
|
||||
uint8_t **_bin_sid,
|
||||
size_t *_length)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid = NULL;
|
||||
size_t length;
|
||||
uint8_t *bin_sid = NULL;
|
||||
|
||||
err = sss_idmap_sid_to_dom_sid(ctx, sid, &dom_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = sss_idmap_dom_sid_to_bin_sid(ctx, dom_sid, &bin_sid, &length);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*_length = length;
|
||||
*_bin_sid = bin_sid;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(bin_sid, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_bin_sid_to_sid(struct sss_idmap_ctx *ctx,
|
||||
const uint8_t *bin_sid,
|
||||
size_t length,
|
||||
char **_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid = NULL;
|
||||
char *sid = NULL;
|
||||
|
||||
err = sss_idmap_bin_sid_to_dom_sid(ctx, bin_sid, length, &dom_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = sss_idmap_dom_sid_to_sid(ctx, dom_sid, &sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*_sid = sid;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(sid, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_sid_to_smb_sid(struct sss_idmap_ctx *ctx,
|
||||
const char *sid,
|
||||
struct dom_sid **_smb_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid = NULL;
|
||||
struct dom_sid *smb_sid = NULL;
|
||||
|
||||
err = sss_idmap_sid_to_dom_sid(ctx, sid, &dom_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = sss_idmap_dom_sid_to_smb_sid(ctx, dom_sid, &smb_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*_smb_sid = smb_sid;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(smb_sid, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_smb_sid_to_sid(struct sss_idmap_ctx *ctx,
|
||||
struct dom_sid *smb_sid,
|
||||
char **_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid = NULL;
|
||||
char *sid = NULL;
|
||||
|
||||
err = sss_idmap_smb_sid_to_dom_sid(ctx, smb_sid, &dom_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = sss_idmap_dom_sid_to_sid(ctx, dom_sid, &sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*_sid = sid;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(sid, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_dom_sid_to_smb_sid(struct sss_idmap_ctx *ctx,
|
||||
struct sss_dom_sid *dom_sid,
|
||||
struct dom_sid **_smb_sid)
|
||||
{
|
||||
struct dom_sid *smb_sid;
|
||||
size_t c;
|
||||
|
||||
smb_sid = ctx->alloc_func(sizeof(struct dom_sid), ctx->alloc_pvt);
|
||||
if (smb_sid == NULL) {
|
||||
return IDMAP_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(smb_sid, 0, sizeof(struct dom_sid));
|
||||
|
||||
smb_sid->sid_rev_num = dom_sid->sid_rev_num;
|
||||
smb_sid->num_auths = dom_sid->num_auths;
|
||||
for (c = 0; c < SID_ID_AUTHS; c++) {
|
||||
smb_sid->id_auth[c] = dom_sid->id_auth[c];
|
||||
}
|
||||
for (c = 0; c < SID_SUB_AUTHS; c++) {
|
||||
smb_sid->sub_auths[c] = dom_sid->sub_auths[c];
|
||||
}
|
||||
|
||||
*_smb_sid = smb_sid;
|
||||
|
||||
return IDMAP_SUCCESS;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_smb_sid_to_dom_sid(struct sss_idmap_ctx *ctx,
|
||||
struct dom_sid *smb_sid,
|
||||
struct sss_dom_sid **_dom_sid)
|
||||
{
|
||||
struct sss_dom_sid *dom_sid;
|
||||
size_t c;
|
||||
|
||||
dom_sid = ctx->alloc_func(sizeof(struct sss_dom_sid), ctx->alloc_pvt);
|
||||
if (dom_sid == NULL) {
|
||||
return IDMAP_OUT_OF_MEMORY;
|
||||
}
|
||||
memset(dom_sid, 0, sizeof(struct sss_dom_sid));
|
||||
|
||||
dom_sid->sid_rev_num = smb_sid->sid_rev_num;
|
||||
dom_sid->num_auths = smb_sid->num_auths;
|
||||
for (c = 0; c < SID_ID_AUTHS; c++) {
|
||||
dom_sid->id_auth[c] = smb_sid->id_auth[c];
|
||||
}
|
||||
for (c = 0; c < SID_SUB_AUTHS; c++) {
|
||||
dom_sid->sub_auths[c] = smb_sid->sub_auths[c];
|
||||
}
|
||||
|
||||
*_dom_sid = dom_sid;
|
||||
|
||||
return IDMAP_SUCCESS;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_bin_sid_to_smb_sid(struct sss_idmap_ctx *ctx,
|
||||
const uint8_t *bin_sid,
|
||||
size_t length,
|
||||
struct dom_sid **_smb_sid)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid = NULL;
|
||||
struct dom_sid *smb_sid = NULL;
|
||||
|
||||
err = sss_idmap_bin_sid_to_dom_sid(ctx, bin_sid, length, &dom_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = sss_idmap_dom_sid_to_smb_sid(ctx, dom_sid, &smb_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*_smb_sid = smb_sid;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(smb_sid, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
enum idmap_error_code sss_idmap_smb_sid_to_bin_sid(struct sss_idmap_ctx *ctx,
|
||||
struct dom_sid *smb_sid,
|
||||
uint8_t **_bin_sid,
|
||||
size_t *_length)
|
||||
{
|
||||
enum idmap_error_code err;
|
||||
struct sss_dom_sid *dom_sid = NULL;
|
||||
uint8_t *bin_sid = NULL;
|
||||
size_t length;
|
||||
|
||||
err = sss_idmap_smb_sid_to_dom_sid(ctx, smb_sid, &dom_sid);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = sss_idmap_dom_sid_to_bin_sid(ctx, dom_sid, &bin_sid, &length);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*_bin_sid = bin_sid;
|
||||
*_length = length;
|
||||
err = IDMAP_SUCCESS;
|
||||
|
||||
done:
|
||||
ctx->free_func(dom_sid, ctx->alloc_pvt);
|
||||
if (err != IDMAP_SUCCESS) {
|
||||
ctx->free_func(bin_sid, ctx->alloc_pvt);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
84
rust/idmap/src/sss_idmap_private.h
Normal file
84
rust/idmap/src/sss_idmap_private.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
SSSD
|
||||
|
||||
ID-mapping library - private headers
|
||||
|
||||
Authors:
|
||||
Sumit Bose <sbose@redhat.com>
|
||||
|
||||
Copyright (C) 2012 Red Hat
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SSS_IDMAP_PRIVATE_H_
|
||||
#define SSS_IDMAP_PRIVATE_H_
|
||||
|
||||
#define SSS_IDMAP_DEFAULT_LOWER 200000
|
||||
#define SSS_IDMAP_DEFAULT_UPPER 2000200000
|
||||
#define SSS_IDMAP_DEFAULT_RANGESIZE 200000
|
||||
#define SSS_IDMAP_DEFAULT_AUTORID false
|
||||
#define SSS_IDMAP_DEFAULT_EXTRA_SLICE_INIT 10
|
||||
|
||||
#define CHECK_IDMAP_CTX(ctx, ret) do { \
|
||||
if (ctx == NULL || ctx->alloc_func == NULL || ctx->free_func == NULL) { \
|
||||
return ret; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
struct sss_idmap_opts {
|
||||
/* true if autorid compatibility mode is used */
|
||||
bool autorid_mode;
|
||||
|
||||
/* smallest available id (for all domains) */
|
||||
id_t idmap_lower;
|
||||
|
||||
/* highest available id (for all domains) */
|
||||
id_t idmap_upper;
|
||||
|
||||
/* number of available UIDs (for single domain) */
|
||||
id_t rangesize;
|
||||
|
||||
/* maximal number of secondary slices */
|
||||
int extra_slice_init;
|
||||
};
|
||||
|
||||
struct sss_idmap_ctx {
|
||||
idmap_alloc_func *alloc_func;
|
||||
void *alloc_pvt;
|
||||
idmap_free_func *free_func;
|
||||
struct sss_idmap_opts idmap_opts;
|
||||
struct idmap_domain_info *idmap_domain_info;
|
||||
};
|
||||
|
||||
/* This is a copy of the definition in the samba gen_ndr/security.h header
|
||||
* file. We use it here to be able to offer conversions form struct dom_sid to
|
||||
* string or binary representation since those are not made available by
|
||||
* public samba libraries.
|
||||
*
|
||||
* If the definition ever changes on the samba side we have to adopt the
|
||||
* change. But chances are very low that this will ever happen since e.g. this
|
||||
* struct is also defined in public documentation from Microsoft. See e.g.
|
||||
* section 2.4.2.3 of "[MS-DTYP]: Windows Data Types"
|
||||
* http://msdn.microsoft.com/en-us/library/cc230364(v=prot.10)
|
||||
*/
|
||||
|
||||
struct dom_sid {
|
||||
uint8_t sid_rev_num;
|
||||
int8_t num_auths;
|
||||
uint8_t id_auth[6];
|
||||
uint32_t sub_auths[15];
|
||||
};
|
||||
|
||||
#endif /* SSS_IDMAP_PRIVATE_H_ */
|
46
rust/idmap/src/util.h
Normal file
46
rust/idmap/src/util.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Himmelblaud
|
||||
|
||||
ID-mapping library utils
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _UTILS_H
|
||||
#define _UTILS_H
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static inline void
|
||||
safealign_memcpy(void *dest, const void *src, size_t n, size_t *counter)
|
||||
{
|
||||
memcpy(dest, src, n);
|
||||
if (counter) {
|
||||
*counter += n;
|
||||
}
|
||||
}
|
||||
|
||||
#define SAFEALIGN_COPY_UINT32(dest, src, pctr) \
|
||||
safealign_memcpy(dest, src, sizeof(uint32_t), pctr)
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 7
|
||||
#define SSS_ATTRIBUTE_FALLTHROUGH __attribute__ ((fallthrough))
|
||||
#else
|
||||
#define SSS_ATTRIBUTE_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
#endif /* _UTILS_H */
|
21
rust/nss/Cargo.toml
Normal file
21
rust/nss/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "nss_himmelblau"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "nss_himmelblau"
|
||||
crate-type = [ "cdylib" ]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
libnss = "0.8.0"
|
||||
ntstatus_gen.workspace = true
|
||||
param = { workspace = true }
|
||||
sock = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
version = { path = "../version" }
|
18
rust/nss/build.rs
Normal file
18
rust/nss/build.rs
Normal file
@ -0,0 +1,18 @@
|
||||
fn main() {
|
||||
if let Some(vers) = version::samba_version_string() {
|
||||
println!("cargo:rustc-env=CARGO_PKG_VERSION={}", vers);
|
||||
}
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION_MAJOR={}",
|
||||
version::SAMBA_VERSION_MAJOR
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION_MINOR={}",
|
||||
version::SAMBA_VERSION_MINOR
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION_PATCH={}",
|
||||
version::SAMBA_VERSION_RELEASE
|
||||
);
|
||||
println!("cargo:rustc-env=LD_LIBRARY_PATH=../../bin/shared:../../bin/shared/private/");
|
||||
}
|
185
rust/nss/src/lib.rs
Normal file
185
rust/nss/src/lib.rs
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
NSS module for the Himmelblau daemon
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[macro_use]
|
||||
extern crate libnss;
|
||||
|
||||
use libnss::group::{Group as NssGroup, GroupHooks};
|
||||
use libnss::interop::Response as NssResponse;
|
||||
use libnss::passwd::{Passwd as NssPasswd, PasswdHooks};
|
||||
use param::LoadParm;
|
||||
use sock::{stream_and_timeout, Group, Passwd, Request, Response};
|
||||
|
||||
struct HimmelblauPasswd;
|
||||
libnss_passwd_hooks!(himmelblau, HimmelblauPasswd);
|
||||
|
||||
impl PasswdHooks for HimmelblauPasswd {
|
||||
fn get_all_entries() -> NssResponse<Vec<NssPasswd>> {
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok((stream, timeout)) => (stream, timeout),
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
|
||||
let req = Request::NssAccounts;
|
||||
stream
|
||||
.send(&req, timeout)
|
||||
.map(|r| match r {
|
||||
Response::NssAccounts(l) => {
|
||||
l.into_iter().map(|passwd| passwd.into()).collect()
|
||||
}
|
||||
_ => vec![],
|
||||
})
|
||||
.map(NssResponse::Success)
|
||||
.unwrap_or_else(|_| NssResponse::Success(vec![]))
|
||||
}
|
||||
|
||||
fn get_entry_by_uid(uid: libc::uid_t) -> NssResponse<NssPasswd> {
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok((stream, timeout)) => (stream, timeout),
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
|
||||
let req = Request::NssAccountByUid(uid);
|
||||
stream
|
||||
.send(&req, timeout)
|
||||
.map(|r| match r {
|
||||
Response::NssAccount(passwd) => passwd
|
||||
.map(nsspasswd_from_passwd)
|
||||
.map(NssResponse::Success)
|
||||
.unwrap_or_else(|| NssResponse::NotFound),
|
||||
_ => NssResponse::NotFound,
|
||||
})
|
||||
.unwrap_or_else(|_| NssResponse::NotFound)
|
||||
}
|
||||
|
||||
fn get_entry_by_name(name: String) -> NssResponse<NssPasswd> {
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok((stream, timeout)) => (stream, timeout),
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
|
||||
let req = Request::NssAccountByName(name);
|
||||
stream
|
||||
.send(&req, timeout)
|
||||
.map(|r| match r {
|
||||
Response::NssAccount(passwd) => passwd
|
||||
.map(nsspasswd_from_passwd)
|
||||
.map(NssResponse::Success)
|
||||
.unwrap_or_else(|| NssResponse::NotFound),
|
||||
_ => NssResponse::NotFound,
|
||||
})
|
||||
.unwrap_or_else(|_| NssResponse::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
struct HimmelblauGroup;
|
||||
libnss_group_hooks!(himmelblau, HimmelblauGroup);
|
||||
|
||||
impl GroupHooks for HimmelblauGroup {
|
||||
fn get_all_entries() -> NssResponse<Vec<NssGroup>> {
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok((stream, timeout)) => (stream, timeout),
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
|
||||
let req = Request::NssGroups;
|
||||
stream
|
||||
.send(&req, timeout)
|
||||
.map(|r| match r {
|
||||
Response::NssGroups(l) => {
|
||||
l.into_iter().map(|group| group.into()).collect()
|
||||
}
|
||||
_ => vec![],
|
||||
})
|
||||
.map(NssResponse::Success)
|
||||
.unwrap_or_else(|_| NssResponse::Success(vec![]))
|
||||
}
|
||||
|
||||
fn get_entry_by_gid(gid: libc::gid_t) -> NssResponse<NssGroup> {
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok((stream, timeout)) => (stream, timeout),
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
|
||||
let req = Request::NssGroupByGid(gid);
|
||||
stream
|
||||
.send(&req, timeout)
|
||||
.map(|r| match r {
|
||||
Response::NssGroup(group) => group
|
||||
.map(nssgroup_from_group)
|
||||
.map(NssResponse::Success)
|
||||
.unwrap_or_else(|| NssResponse::NotFound),
|
||||
_ => NssResponse::NotFound,
|
||||
})
|
||||
.unwrap_or_else(|_| NssResponse::NotFound)
|
||||
}
|
||||
|
||||
fn get_entry_by_name(name: String) -> NssResponse<NssGroup> {
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok((stream, timeout)) => (stream, timeout),
|
||||
Err(_) => return NssResponse::Unavail,
|
||||
};
|
||||
|
||||
let req = Request::NssGroupByName(name);
|
||||
stream
|
||||
.send(&req, timeout)
|
||||
.map(|r| match r {
|
||||
Response::NssGroup(group) => group
|
||||
.map(nssgroup_from_group)
|
||||
.map(NssResponse::Success)
|
||||
.unwrap_or_else(|| NssResponse::NotFound),
|
||||
_ => NssResponse::NotFound,
|
||||
})
|
||||
.unwrap_or_else(|_| NssResponse::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
fn nsspasswd_from_passwd(passwd: Passwd) -> NssPasswd {
|
||||
passwd.into()
|
||||
}
|
||||
|
||||
fn nssgroup_from_group(group: Group) -> NssGroup {
|
||||
group.into()
|
||||
}
|
6
rust/ntstatus_gen/Cargo.toml
Normal file
6
rust/ntstatus_gen/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "ntstatus_gen"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
24
rust/ntstatus_gen/src/lib.rs
Normal file
24
rust/ntstatus_gen/src/lib.rs
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Generated NTSTATUS Errors
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[path = "../../../bin/default/libcli/util/ntstatus_gen.rs"]
|
||||
mod ntstatus_gen;
|
||||
pub use ntstatus_gen::*;
|
23
rust/pam/Cargo.toml
Normal file
23
rust/pam/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "pam_himmelblau"
|
||||
links = "pam"
|
||||
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "pam_himmelblau"
|
||||
crate-type = [ "cdylib" ]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = { workspace = true }
|
||||
dbg = { workspace = true }
|
||||
sock.workspace = true
|
||||
chelps.workspace = true
|
||||
param.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3.30"
|
10
rust/pam/build.rs
Normal file
10
rust/pam/build.rs
Normal file
@ -0,0 +1,10 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
// ignore errors here since older versions of pam do not ship the pkg-config `pam.pc` file.
|
||||
// Not setting anything here will fall back on just blindly linking with `-lpam`,
|
||||
// which will work on environments with libpam.so, but no pkg-config file.
|
||||
let _ = pkg_config::Config::new()
|
||||
.atleast_version("1.3.0")
|
||||
.probe("pam");
|
||||
println!("cargo:rustc-env=LD_LIBRARY_PATH=../../bin/shared:../../bin/shared/private/");
|
||||
}
|
43
rust/pam/src/lib.rs
Normal file
43
rust/pam/src/lib.rs
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#![deny(warnings)]
|
||||
#![warn(unused_extern_crates)]
|
||||
#![deny(clippy::todo)]
|
||||
#![deny(clippy::unimplemented)]
|
||||
// In this file, we do want to panic on these faults.
|
||||
// #![deny(clippy::unwrap_used)]
|
||||
// #![deny(clippy::expect_used)]
|
||||
#![deny(clippy::panic)]
|
||||
#![deny(clippy::unreachable)]
|
||||
#![deny(clippy::await_holding_lock)]
|
||||
#![deny(clippy::needless_pass_by_value)]
|
||||
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
mod pam;
|
||||
|
||||
// pub use needs to be here so it'll compile and export all the things
|
||||
#[cfg(target_family = "unix")]
|
||||
pub use crate::pam::*;
|
118
rust/pam/src/pam/constants.rs
Normal file
118
rust/pam/src/pam/constants.rs
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
use libc::{c_int, c_uint};
|
||||
|
||||
pub type PamFlag = c_uint;
|
||||
pub type PamItemType = c_int;
|
||||
pub type PamMessageStyle = c_int;
|
||||
pub type AlwaysZero = c_int;
|
||||
|
||||
// The Linux-PAM flags
|
||||
// see /usr/include/security/_pam_types.h
|
||||
pub const _PAM_SILENT: PamFlag = 0x8000;
|
||||
pub const _PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001;
|
||||
pub const _PAM_ESTABLISH_CRED: PamFlag = 0x0002;
|
||||
pub const _PAM_DELETE_CRED: PamFlag = 0x0004;
|
||||
pub const _PAM_REINITIALIZE_CRED: PamFlag = 0x0008;
|
||||
pub const _PAM_REFRESH_CRED: PamFlag = 0x0010;
|
||||
pub const _PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020;
|
||||
|
||||
// The Linux-PAM item types
|
||||
// see /usr/include/security/_pam_types.h
|
||||
/// The service name
|
||||
pub const PAM_SERVICE: PamItemType = 1;
|
||||
/// The user name
|
||||
pub const PAM_USER: PamItemType = 2;
|
||||
/// The tty name
|
||||
pub const PAM_TTY: PamItemType = 3;
|
||||
/// The remote host name
|
||||
pub const PAM_RHOST: PamItemType = 4;
|
||||
/// The pam_conv structure
|
||||
pub const PAM_CONV: PamItemType = 5;
|
||||
/// The authentication token (password)
|
||||
pub const PAM_AUTHTOK: PamItemType = 6;
|
||||
/// The old authentication token
|
||||
pub const PAM_OLDAUTHTOK: PamItemType = 7;
|
||||
/// The remote user name
|
||||
pub const PAM_RUSER: PamItemType = 8;
|
||||
/// the prompt for getting a username
|
||||
pub const PAM_USER_PROMPT: PamItemType = 9;
|
||||
/* Linux-PAM :extensionsPamItemType = */
|
||||
/// app supplied function to override failure delays
|
||||
pub const _PAM_FAIL_DELAY: PamItemType = 10;
|
||||
/// X :display name
|
||||
pub const _PAM_XDISPLAY: PamItemType = 11;
|
||||
/// X :server authentication data
|
||||
pub const _PAM_XAUTHDATA: PamItemType = 12;
|
||||
/// The type for pam_get_authtok
|
||||
pub const _PAM_AUTHTOK_TYPE: PamItemType = 13;
|
||||
|
||||
// Message styles
|
||||
pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1;
|
||||
pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2;
|
||||
pub const PAM_ERROR_MSG: PamMessageStyle = 3;
|
||||
pub const PAM_TEXT_INFO: PamMessageStyle = 4;
|
||||
/// yes/no/maybe conditionals
|
||||
pub const _PAM_RADIO_TYPE: PamMessageStyle = 5;
|
||||
pub const _PAM_BINARY_PROMPT: PamMessageStyle = 7;
|
||||
|
||||
// The Linux-PAM return values
|
||||
// see /usr/include/security/_pam_types.h
|
||||
#[allow(non_camel_case_types, dead_code)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub enum PamResultCode {
|
||||
PAM_SUCCESS = 0,
|
||||
PAM_OPEN_ERR = 1,
|
||||
PAM_SYMBOL_ERR = 2,
|
||||
PAM_SERVICE_ERR = 3,
|
||||
PAM_SYSTEM_ERR = 4,
|
||||
PAM_BUF_ERR = 5,
|
||||
PAM_PERM_DENIED = 6,
|
||||
PAM_AUTH_ERR = 7,
|
||||
PAM_CRED_INSUFFICIENT = 8,
|
||||
PAM_AUTHINFO_UNAVAIL = 9,
|
||||
PAM_USER_UNKNOWN = 10,
|
||||
PAM_MAXTRIES = 11,
|
||||
PAM_NEW_AUTHTOK_REQD = 12,
|
||||
PAM_ACCT_EXPIRED = 13,
|
||||
PAM_SESSION_ERR = 14,
|
||||
PAM_CRED_UNAVAIL = 15,
|
||||
PAM_CRED_EXPIRED = 16,
|
||||
PAM_CRED_ERR = 17,
|
||||
PAM_NO_MODULE_DATA = 18,
|
||||
PAM_CONV_ERR = 19,
|
||||
PAM_AUTHTOK_ERR = 20,
|
||||
PAM_AUTHTOK_RECOVERY_ERR = 21,
|
||||
PAM_AUTHTOK_LOCK_BUSY = 22,
|
||||
PAM_AUTHTOK_DISABLE_AGING = 23,
|
||||
PAM_TRY_AGAIN = 24,
|
||||
PAM_IGNORE = 25,
|
||||
PAM_ABORT = 26,
|
||||
PAM_AUTHTOK_EXPIRED = 27,
|
||||
PAM_MODULE_UNKNOWN = 28,
|
||||
PAM_BAD_ITEM = 29,
|
||||
PAM_CONV_AGAIN = 30,
|
||||
PAM_INCOMPLETE = 31,
|
||||
}
|
112
rust/pam/src/pam/conv.rs
Normal file
112
rust/pam/src/pam/conv.rs
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ptr;
|
||||
|
||||
use libc::{c_char, c_int};
|
||||
|
||||
use crate::pam::constants::{PamResultCode, *};
|
||||
use crate::pam::module::{PamItem, PamResult};
|
||||
|
||||
#[allow(missing_copy_implementations)]
|
||||
pub enum AppDataPtr {}
|
||||
|
||||
#[repr(C)]
|
||||
struct PamMessage {
|
||||
msg_style: PamMessageStyle,
|
||||
msg: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct PamResponse {
|
||||
resp: *const c_char,
|
||||
resp_retcode: AlwaysZero,
|
||||
}
|
||||
|
||||
/// `PamConv` acts as a channel for communicating with user.
|
||||
///
|
||||
/// Communication is mediated by the pam client (the application that invoked
|
||||
/// pam). Messages sent will be relayed to the user by the client, and response
|
||||
/// will be relayed back.
|
||||
#[repr(C)]
|
||||
pub struct PamConv {
|
||||
conv: extern "C" fn(
|
||||
num_msg: c_int,
|
||||
pam_message: &&PamMessage,
|
||||
pam_response: &mut *const PamResponse,
|
||||
appdata_ptr: *const AppDataPtr,
|
||||
) -> PamResultCode,
|
||||
appdata_ptr: *const AppDataPtr,
|
||||
}
|
||||
|
||||
impl PamConv {
|
||||
/// Sends a message to the pam client.
|
||||
///
|
||||
/// This will typically result in the user seeing a message or a prompt.
|
||||
/// There are several message styles available:
|
||||
///
|
||||
/// - PAM_PROMPT_ECHO_OFF
|
||||
/// - PAM_PROMPT_ECHO_ON
|
||||
/// - PAM_ERROR_MSG
|
||||
/// - PAM_TEXT_INFO
|
||||
/// - PAM_RADIO_TYPE
|
||||
/// - PAM_BINARY_PROMPT
|
||||
///
|
||||
/// Note that the user experience will depend on how the client implements
|
||||
/// these message styles - and not all applications implement all message
|
||||
/// styles.
|
||||
pub fn send(
|
||||
&self,
|
||||
style: PamMessageStyle,
|
||||
msg: &str,
|
||||
) -> PamResult<Option<String>> {
|
||||
let mut resp_ptr: *const PamResponse = ptr::null();
|
||||
let msg_cstr = CString::new(msg).unwrap();
|
||||
let msg = PamMessage {
|
||||
msg_style: style,
|
||||
msg: msg_cstr.as_ptr(),
|
||||
};
|
||||
|
||||
let ret = (self.conv)(1, &&msg, &mut resp_ptr, self.appdata_ptr);
|
||||
|
||||
if PamResultCode::PAM_SUCCESS == ret {
|
||||
// PamResponse.resp is null for styles that don't return user input like PAM_TEXT_INFO
|
||||
let response = unsafe { (*resp_ptr).resp };
|
||||
if response.is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let bytes = unsafe { CStr::from_ptr(response).to_bytes() };
|
||||
Ok(String::from_utf8(bytes.to_vec()).ok())
|
||||
}
|
||||
} else {
|
||||
Err(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PamItem for PamConv {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_CONV
|
||||
}
|
||||
}
|
101
rust/pam/src/pam/items.rs
Normal file
101
rust/pam/src/pam/items.rs
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
use crate::pam::constants::{
|
||||
PamItemType, PAM_AUTHTOK, PAM_OLDAUTHTOK, PAM_RHOST, PAM_RUSER,
|
||||
PAM_SERVICE, PAM_TTY, PAM_USER, PAM_USER_PROMPT,
|
||||
};
|
||||
pub use crate::pam::conv::PamConv;
|
||||
use crate::pam::module::PamItem;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamService {}
|
||||
|
||||
impl PamItem for PamService {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_SERVICE
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamUser {}
|
||||
|
||||
impl PamItem for PamUser {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_USER
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamUserPrompt {}
|
||||
|
||||
impl PamItem for PamUserPrompt {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_USER_PROMPT
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamTty {}
|
||||
|
||||
impl PamItem for PamTty {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_TTY
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamRUser {}
|
||||
|
||||
impl PamItem for PamRUser {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_RUSER
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamRHost {}
|
||||
|
||||
impl PamItem for PamRHost {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_RHOST
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamAuthTok {}
|
||||
|
||||
impl PamItem for PamAuthTok {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_AUTHTOK
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PamOldAuthTok {}
|
||||
|
||||
impl PamItem for PamOldAuthTok {
|
||||
fn item_type() -> PamItemType {
|
||||
PAM_OLDAUTHTOK
|
||||
}
|
||||
}
|
140
rust/pam/src/pam/macros.rs
Normal file
140
rust/pam/src/pam/macros.rs
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
/// Macro to generate the `extern "C"` entrypoint bindings needed by PAM
|
||||
///
|
||||
/// You can call `pam_hooks!(SomeType);` for any type that implements `PamHooks`
|
||||
///
|
||||
/// ## Examples:
|
||||
///
|
||||
/// Here is full example of a PAM module that would authenticate and authorize everybody:
|
||||
///
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// extern crate pam;
|
||||
///
|
||||
/// use pam::constants::{PamFlag, PamResultCode};
|
||||
/// use pam::module::{PamHandle, PamHooks};
|
||||
/// use std::ffi::CStr;
|
||||
///
|
||||
/// # fn main() {}
|
||||
/// struct MyPamModule;
|
||||
/// pam_hooks!(MyPamModule);
|
||||
///
|
||||
/// impl PamHooks for MyPamModule {
|
||||
/// fn sm_authenticate(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
|
||||
/// println!("Everybody is authenticated!");
|
||||
/// PamResultCode::PAM_SUCCESS
|
||||
/// }
|
||||
///
|
||||
/// fn acct_mgmt(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
|
||||
/// println!("Everybody is authorized!");
|
||||
/// PamResultCode::PAM_SUCCESS
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pam_hooks {
|
||||
($ident:ident) => {
|
||||
pub use self::pam_hooks_scope::*;
|
||||
mod pam_hooks_scope {
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use $crate::pam::constants::{PamFlag, PamResultCode};
|
||||
use $crate::pam::module::{PamHandle, PamHooks};
|
||||
|
||||
fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
|
||||
(0..argc)
|
||||
.map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pam_sm_acct_mgmt(
|
||||
pamh: &PamHandle,
|
||||
flags: PamFlag,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) -> PamResultCode {
|
||||
let args = extract_argv(argc, argv);
|
||||
super::$ident::acct_mgmt(pamh, args, flags)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pam_sm_authenticate(
|
||||
pamh: &PamHandle,
|
||||
flags: PamFlag,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) -> PamResultCode {
|
||||
let args = extract_argv(argc, argv);
|
||||
super::$ident::sm_authenticate(pamh, args, flags)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pam_sm_chauthtok(
|
||||
pamh: &PamHandle,
|
||||
flags: PamFlag,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) -> PamResultCode {
|
||||
let args = extract_argv(argc, argv);
|
||||
super::$ident::sm_chauthtok(pamh, args, flags)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pam_sm_close_session(
|
||||
pamh: &PamHandle,
|
||||
flags: PamFlag,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) -> PamResultCode {
|
||||
let args = extract_argv(argc, argv);
|
||||
super::$ident::sm_close_session(pamh, args, flags)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pam_sm_open_session(
|
||||
pamh: &PamHandle,
|
||||
flags: PamFlag,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) -> PamResultCode {
|
||||
let args = extract_argv(argc, argv);
|
||||
super::$ident::sm_open_session(pamh, args, flags)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pam_sm_setcred(
|
||||
pamh: &PamHandle,
|
||||
flags: PamFlag,
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
) -> PamResultCode {
|
||||
let args = extract_argv(argc, argv);
|
||||
super::$ident::sm_setcred(pamh, args, flags)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
615
rust/pam/src/pam/mod.rs
Executable file
615
rust/pam/src/pam/mod.rs
Executable file
@ -0,0 +1,615 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
Copyright (c) 2020 William Brown <william@blackhats.net.au>
|
||||
Copyright (c) 2024 David Mulder <dmulder@samba.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
//! Interface to the pluggable authentication module framework (PAM).
|
||||
//!
|
||||
//! The goal of this library is to provide a type-safe API that can be used to
|
||||
//! interact with PAM. The library is incomplete - currently it supports
|
||||
//! a subset of functions for use in a pam authentication module. A pam module
|
||||
//! is a shared library that is invoked to authenticate a user, or to perform
|
||||
//! other functions.
|
||||
//!
|
||||
//! For general information on writing pam modules, see
|
||||
//! [The Linux-PAM Module Writers' Guide][module-guide]
|
||||
//!
|
||||
//! [module-guide]: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_MWG.html
|
||||
//!
|
||||
//! A typical authentication module will define an external function called
|
||||
//! `pam_sm_authenticate()`, which will use functions in this library to
|
||||
//! interrogate the program that requested authentication for more information,
|
||||
//! and to render a result. For a working example that uses this library, see
|
||||
//! [toznyauth-pam][].
|
||||
//!
|
||||
//! [toznyauth-pam]: https://github.com/tozny/toznyauth-pam
|
||||
//!
|
||||
//! Note that constants that are normally read from pam header files are
|
||||
//! hard-coded in the `constants` module. The values there are taken from
|
||||
//! a Linux system. That means that it might take some work to get this library
|
||||
//! to work on other platforms.
|
||||
|
||||
pub mod constants;
|
||||
pub mod conv;
|
||||
pub mod items;
|
||||
#[doc(hidden)]
|
||||
pub mod macros;
|
||||
pub mod module;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::CStr;
|
||||
|
||||
use dbg::*;
|
||||
use param::LoadParm;
|
||||
use sock::{
|
||||
stream_and_timeout, PamAuthRequest, PamAuthResponse, Request, Response,
|
||||
};
|
||||
|
||||
use crate::pam::constants::*;
|
||||
use crate::pam::conv::PamConv;
|
||||
use crate::pam::module::{PamHandle, PamHooks};
|
||||
use crate::pam_hooks;
|
||||
use constants::PamResultCode;
|
||||
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn install_subscriber(lp: &LoadParm, debug: bool) {
|
||||
debuglevel_set!(if debug { DBGLVL_DEBUG } else { DBGLVL_ERR });
|
||||
match lp.logfile() {
|
||||
Ok(Some(logfile)) => {
|
||||
debug_set_logfile(&logfile);
|
||||
setup_logging(env!("CARGO_PKG_NAME"), DEBUG_FILE)
|
||||
}
|
||||
_ => setup_logging(env!("CARGO_PKG_NAME"), DEBUG_STDOUT),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Options {
|
||||
debug: bool,
|
||||
use_first_pass: bool,
|
||||
ignore_unknown_user: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<&Vec<&CStr>> for Options {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(args: &Vec<&CStr>) -> Result<Self, Self::Error> {
|
||||
let opts: Result<BTreeSet<&str>, _> =
|
||||
args.iter().map(|cs| cs.to_str()).collect();
|
||||
let gopts = match opts {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
println!("Error in module args -> {:?}", e);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Options {
|
||||
debug: gopts.contains("debug"),
|
||||
use_first_pass: gopts.contains("use_first_pass"),
|
||||
ignore_unknown_user: gopts.contains("ignore_unknown_user"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PamHimmelblau;
|
||||
|
||||
pam_hooks!(PamHimmelblau);
|
||||
|
||||
macro_rules! match_sm_auth_client_response {
|
||||
($expr:expr, $opts:ident, $conv:ident, $req:ident, $authtok:ident, $($pat:pat => $result:expr),*) => {
|
||||
match $expr {
|
||||
Ok(r) => match r {
|
||||
$($pat => $result),*
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Success) => {
|
||||
return PamResultCode::PAM_SUCCESS;
|
||||
}
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Denied) => {
|
||||
return PamResultCode::PAM_AUTH_ERR;
|
||||
}
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Unknown) => {
|
||||
if $opts.ignore_unknown_user {
|
||||
return PamResultCode::PAM_IGNORE;
|
||||
} else {
|
||||
return PamResultCode::PAM_USER_UNKNOWN;
|
||||
}
|
||||
}
|
||||
Response::PamAuthStepResponse(PamAuthResponse::SetupPin {
|
||||
msg,
|
||||
}) => {
|
||||
match $conv.send(PAM_TEXT_INFO, &msg) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
if $opts.debug {
|
||||
println!("Message prompt failed");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
let mut pin;
|
||||
let mut confirm;
|
||||
loop {
|
||||
pin = match $conv.send(PAM_PROMPT_ECHO_OFF, "New PIN: ") {
|
||||
Ok(password) => match password {
|
||||
Some(cred) => cred,
|
||||
None => {
|
||||
DBG_DEBUG!("no pin");
|
||||
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
DBG_DEBUG!("unable to get pin");
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
confirm = match $conv.send(PAM_PROMPT_ECHO_OFF, "Confirm PIN: ") {
|
||||
Ok(password) => match password {
|
||||
Some(cred) => cred,
|
||||
None => {
|
||||
DBG_DEBUG!("no confirmation pin");
|
||||
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
DBG_DEBUG!("unable to get confirmation pin");
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
if pin == confirm {
|
||||
break;
|
||||
} else {
|
||||
match $conv.send(PAM_TEXT_INFO, "Inputs did not match. Try again.") {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
if $opts.debug {
|
||||
println!("Message prompt failed");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now setup the request for the next loop.
|
||||
$req = Request::PamAuthenticateStep(PamAuthRequest::SetupPin {
|
||||
pin,
|
||||
});
|
||||
continue;
|
||||
},
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Pin) => {
|
||||
let mut consume_authtok = None;
|
||||
// Swap the authtok out with a None, so it can only be consumed once.
|
||||
// If it's already been swapped, we are just swapping two null pointers
|
||||
// here effectively.
|
||||
std::mem::swap(&mut $authtok, &mut consume_authtok);
|
||||
let cred = if let Some(cred) = consume_authtok {
|
||||
cred
|
||||
} else {
|
||||
match $conv.send(PAM_PROMPT_ECHO_OFF, "PIN: ") {
|
||||
Ok(password) => match password {
|
||||
Some(cred) => cred,
|
||||
None => {
|
||||
DBG_DEBUG!("no pin");
|
||||
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
DBG_DEBUG!("unable to get pin");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Now setup the request for the next loop.
|
||||
$req = Request::PamAuthenticateStep(PamAuthRequest::Pin { pin: cred });
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// unexpected response.
|
||||
DBG_ERR!("PAM_IGNORE, unexpected resolver response: {:?}", r);
|
||||
return PamResultCode::PAM_IGNORE;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
DBG_ERR!("PAM_IGNORE: {:?}", e);
|
||||
return PamResultCode::PAM_IGNORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PamHooks for PamHimmelblau {
|
||||
fn acct_mgmt(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
_flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
let opts = match Options::try_from(&args) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
install_subscriber(&lp, opts.debug);
|
||||
|
||||
let tty = pamh.get_tty();
|
||||
let rhost = pamh.get_rhost();
|
||||
|
||||
DBG_DEBUG!("{:?} {:?} {:?} {:?} acct_mgmt", args, opts, tty, rhost);
|
||||
|
||||
let account_id = match pamh.get_user(None) {
|
||||
Ok(aid) => aid,
|
||||
Err(e) => {
|
||||
DBG_ERR!("get_user: {:?}", e);
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
let req = Request::PamAccountAllowed(account_id);
|
||||
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Error stream_and_timeout: {:?}", e);
|
||||
return PamResultCode::PAM_SERVICE_ERR;
|
||||
}
|
||||
};
|
||||
|
||||
match stream.send(&req, timeout) {
|
||||
Ok(r) => match r {
|
||||
Response::PamStatus(Some(true)) => {
|
||||
DBG_DEBUG!("PamResultCode::PAM_SUCCESS");
|
||||
PamResultCode::PAM_SUCCESS
|
||||
}
|
||||
Response::PamStatus(Some(false)) => {
|
||||
DBG_DEBUG!("PamResultCode::PAM_AUTH_ERR");
|
||||
PamResultCode::PAM_AUTH_ERR
|
||||
}
|
||||
Response::PamStatus(None) => {
|
||||
if opts.ignore_unknown_user {
|
||||
DBG_DEBUG!("PamResultCode::PAM_IGNORE");
|
||||
PamResultCode::PAM_IGNORE
|
||||
} else {
|
||||
DBG_DEBUG!("PamResultCode::PAM_USER_UNKNOWN");
|
||||
PamResultCode::PAM_USER_UNKNOWN
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// unexpected response.
|
||||
DBG_ERR!(
|
||||
"PAM_IGNORE, unexpected resolver response: {:?}",
|
||||
r
|
||||
);
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
DBG_ERR!("PamResultCode::PAM_IGNORE: {:?}", e);
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sm_authenticate(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
_flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
let opts = match Options::try_from(&args) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
install_subscriber(&lp, opts.debug);
|
||||
|
||||
let tty = pamh.get_tty();
|
||||
let rhost = pamh.get_rhost();
|
||||
|
||||
DBG_DEBUG!(
|
||||
"{:?} {:?} {:?} {:?} sm_authenticate",
|
||||
args,
|
||||
opts,
|
||||
tty,
|
||||
rhost
|
||||
);
|
||||
|
||||
let account_id = match pamh.get_user(None) {
|
||||
Ok(aid) => aid,
|
||||
Err(e) => {
|
||||
DBG_ERR!("get_user: {:?}", e);
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Error stream_and_timeout: {:?}", e);
|
||||
return PamResultCode::PAM_SERVICE_ERR;
|
||||
}
|
||||
};
|
||||
|
||||
// Later we may need to move this to a function and call it as a oneshot for auth methods
|
||||
// that don't require any authtoks at all. For example, imagine a user authed and they
|
||||
// needed to follow a URL to continue. In that case, they would fail here because they
|
||||
// didn't enter an authtok that they didn't need!
|
||||
let mut authtok = match pamh.get_authtok() {
|
||||
Ok(Some(v)) => Some(v),
|
||||
Ok(None) => {
|
||||
if opts.use_first_pass {
|
||||
DBG_DEBUG!("Don't have an authtok, returning PAM_AUTH_ERR");
|
||||
return PamResultCode::PAM_AUTH_ERR;
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
DBG_ERR!("get_authtok: {:?}", e);
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
let conv = match pamh.get_item::<PamConv>() {
|
||||
Ok(conv) => conv,
|
||||
Err(e) => {
|
||||
DBG_ERR!("pam_conv: {:?}", e);
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
let mut req = Request::PamAuthenticateInit(account_id);
|
||||
|
||||
loop {
|
||||
match_sm_auth_client_response!(stream.send(&req, timeout), opts, conv, req, authtok,
|
||||
Response::PamAuthStepResponse(PamAuthResponse::Password) => {
|
||||
let mut consume_authtok = None;
|
||||
// Swap the authtok out with a None, so it can only be consumed once.
|
||||
// If it's already been swapped, we are just swapping two null pointers
|
||||
// here effectively.
|
||||
std::mem::swap(&mut authtok, &mut consume_authtok);
|
||||
let cred = if let Some(cred) = consume_authtok {
|
||||
cred
|
||||
} else {
|
||||
match conv.send(PAM_PROMPT_ECHO_OFF, "Password: ") {
|
||||
Ok(password) => match password {
|
||||
Some(cred) => cred,
|
||||
None => {
|
||||
DBG_DEBUG!("no password");
|
||||
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
DBG_DEBUG!("unable to get password");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Now setup the request for the next loop.
|
||||
req = Request::PamAuthenticateStep(PamAuthRequest::Password { cred });
|
||||
continue;
|
||||
},
|
||||
Response::PamAuthStepResponse(PamAuthResponse::MFACode {
|
||||
msg,
|
||||
}) => {
|
||||
match conv.send(PAM_TEXT_INFO, &msg) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
if opts.debug {
|
||||
println!("Message prompt failed");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
let cred = match conv.send(PAM_PROMPT_ECHO_OFF, "Code: ") {
|
||||
Ok(password) => match password {
|
||||
Some(cred) => cred,
|
||||
None => {
|
||||
DBG_DEBUG!("no mfa code");
|
||||
return PamResultCode::PAM_CRED_INSUFFICIENT;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
DBG_DEBUG!("unable to get mfa code");
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
// Now setup the request for the next loop.
|
||||
req = Request::PamAuthenticateStep(PamAuthRequest::MFACode {
|
||||
cred,
|
||||
});
|
||||
continue;
|
||||
},
|
||||
Response::PamAuthStepResponse(PamAuthResponse::MFAPoll {
|
||||
msg,
|
||||
polling_interval,
|
||||
}) => {
|
||||
match conv.send(PAM_TEXT_INFO, &msg) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
if opts.debug {
|
||||
println!("Message prompt failed");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Necessary because of OpenSSH bug
|
||||
// https://bugzilla.mindrot.org/show_bug.cgi?id=2876 -
|
||||
// PAM_TEXT_INFO and PAM_ERROR_MSG conversation not
|
||||
// honoured during PAM authentication
|
||||
let _ = conv.send(PAM_PROMPT_ECHO_OFF, "Press enter to continue");
|
||||
|
||||
let mut poll_attempt = 0;
|
||||
req = Request::PamAuthenticateStep(
|
||||
PamAuthRequest::MFAPoll { poll_attempt }
|
||||
);
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(polling_interval.into()));
|
||||
|
||||
match_sm_auth_client_response!(
|
||||
stream.send(&req, timeout), opts, conv, req, authtok,
|
||||
Response::PamAuthStepResponse(
|
||||
PamAuthResponse::MFAPollWait,
|
||||
) => {
|
||||
// Continue polling if the daemon says to wait
|
||||
poll_attempt += 1;
|
||||
req = Request::PamAuthenticateStep(
|
||||
PamAuthRequest::MFAPoll { poll_attempt }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
} // while true, continue calling PamAuthenticateStep until we get a decision.
|
||||
}
|
||||
|
||||
fn sm_chauthtok(
|
||||
_pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
_flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
let opts = match Options::try_from(&args) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
install_subscriber(&lp, opts.debug);
|
||||
|
||||
DBG_DEBUG!("{:?} {:?} sm_chauthtok", args, opts);
|
||||
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
|
||||
fn sm_close_session(
|
||||
_pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
_flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
let opts = match Options::try_from(&args) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
install_subscriber(&lp, opts.debug);
|
||||
|
||||
DBG_DEBUG!("{:?} {:?} sm_close_session", args, opts);
|
||||
|
||||
PamResultCode::PAM_SUCCESS
|
||||
}
|
||||
|
||||
fn sm_open_session(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
_flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
let opts = match Options::try_from(&args) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
install_subscriber(&lp, opts.debug);
|
||||
|
||||
DBG_DEBUG!("{:?} {:?} sm_open_session", args, opts);
|
||||
|
||||
let account_id = match pamh.get_user(None) {
|
||||
Ok(aid) => aid,
|
||||
Err(e) => {
|
||||
DBG_ERR!("get_user: {:?}", e);
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
let req = Request::PamAccountBeginSession(account_id);
|
||||
|
||||
let (mut stream, timeout) = match stream_and_timeout(&lp) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
DBG_ERR!("Error stream_and_timeout: {:?}", e);
|
||||
return PamResultCode::PAM_SERVICE_ERR;
|
||||
}
|
||||
};
|
||||
|
||||
match stream.send(&req, timeout) {
|
||||
Ok(Response::Success) => PamResultCode::PAM_SUCCESS,
|
||||
other => {
|
||||
DBG_DEBUG!("PAM_IGNORE: {:?}", other);
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sm_setcred(
|
||||
_pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
_flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
let opts = match Options::try_from(&args) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
let lp = match LoadParm::new(None) {
|
||||
Ok(lp) => lp,
|
||||
Err(_) => return PamResultCode::PAM_SERVICE_ERR,
|
||||
};
|
||||
|
||||
install_subscriber(&lp, opts.debug);
|
||||
|
||||
DBG_DEBUG!("{:?} {:?} sm_setcred", args, opts);
|
||||
|
||||
PamResultCode::PAM_SUCCESS
|
||||
}
|
||||
}
|
361
rust/pam/src/pam/module.rs
Executable file
361
rust/pam/src/pam/module.rs
Executable file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 TOZNY
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
//! Functions for use in pam modules.
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::{mem, ptr};
|
||||
|
||||
use libc::c_char;
|
||||
|
||||
use crate::pam::constants::{
|
||||
PamFlag, PamItemType, PamResultCode, PAM_AUTHTOK, PAM_RHOST, PAM_TTY,
|
||||
};
|
||||
|
||||
/// Opaque type, used as a pointer when making pam API calls.
|
||||
///
|
||||
/// A module is invoked via an external function such as `pam_sm_authenticate`.
|
||||
/// Such a call provides a pam handle pointer. The same pointer should be given
|
||||
/// as an argument when making API calls.
|
||||
#[allow(missing_copy_implementations)]
|
||||
pub enum PamHandle {}
|
||||
|
||||
#[allow(missing_copy_implementations)]
|
||||
enum PamItemT {}
|
||||
|
||||
#[allow(missing_copy_implementations)]
|
||||
pub enum PamDataT {}
|
||||
|
||||
#[link(name = "pam")]
|
||||
extern "C" {
|
||||
fn pam_get_data(
|
||||
pamh: *const PamHandle,
|
||||
module_data_name: *const c_char,
|
||||
data: &mut *const PamDataT,
|
||||
) -> PamResultCode;
|
||||
|
||||
fn pam_set_data(
|
||||
pamh: *const PamHandle,
|
||||
module_data_name: *const c_char,
|
||||
data: *mut PamDataT,
|
||||
cleanup: unsafe extern "C" fn(
|
||||
pamh: *const PamHandle,
|
||||
data: *mut PamDataT,
|
||||
error_status: PamResultCode,
|
||||
),
|
||||
) -> PamResultCode;
|
||||
|
||||
fn pam_get_item(
|
||||
pamh: *const PamHandle,
|
||||
item_type: PamItemType,
|
||||
item: &mut *const PamItemT,
|
||||
) -> PamResultCode;
|
||||
|
||||
fn pam_set_item(
|
||||
pamh: *mut PamHandle,
|
||||
item_type: PamItemType,
|
||||
item: &PamItemT,
|
||||
) -> PamResultCode;
|
||||
|
||||
fn pam_get_user(
|
||||
pamh: *const PamHandle,
|
||||
user: &*mut c_char,
|
||||
prompt: *const c_char,
|
||||
) -> PamResultCode;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// We're doing what we can for this one, but it's FFI.
|
||||
pub unsafe extern "C" fn cleanup<T>(
|
||||
_: *const PamHandle,
|
||||
c_data: *mut PamDataT,
|
||||
_: PamResultCode,
|
||||
) {
|
||||
let c_data = Box::from_raw(c_data);
|
||||
let data: Box<T> = mem::transmute(c_data);
|
||||
mem::drop(data);
|
||||
}
|
||||
|
||||
pub type PamResult<T> = Result<T, PamResultCode>;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Type-level mapping for safely retrieving values with `get_item`.
|
||||
///
|
||||
/// See `pam_get_item` in
|
||||
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
|
||||
pub trait PamItem {
|
||||
/// Maps a Rust type to a pam constant.
|
||||
///
|
||||
/// For example, the type PamConv maps to the constant PAM_CONV. The pam
|
||||
/// API contract specifies that when the API function `pam_get_item` is
|
||||
/// called with the constant PAM_CONV, it will return a value of type
|
||||
/// `PamConv`.
|
||||
fn item_type() -> PamItemType;
|
||||
}
|
||||
|
||||
impl PamHandle {
|
||||
/// # Safety
|
||||
///
|
||||
/// Gets some value, identified by `key`, that has been set by the module
|
||||
/// previously.
|
||||
///
|
||||
/// See `pam_get_data` in
|
||||
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
|
||||
pub unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
|
||||
let c_key = CString::new(key).unwrap();
|
||||
let mut ptr: *const PamDataT = ptr::null();
|
||||
let res = pam_get_data(self, c_key.as_ptr(), &mut ptr);
|
||||
if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
|
||||
let typed_ptr: *const T = ptr as *const T;
|
||||
let data: &T = &*typed_ptr;
|
||||
Ok(data)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a value that can be retrieved later with `get_data`. The value lives
|
||||
/// as long as the current pam cycle.
|
||||
///
|
||||
/// See `pam_set_data` in
|
||||
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
|
||||
pub fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> {
|
||||
let c_key = CString::new(key).unwrap();
|
||||
let res = unsafe {
|
||||
let c_data: Box<PamDataT> = mem::transmute(data);
|
||||
let c_data = Box::into_raw(c_data);
|
||||
pam_set_data(self, c_key.as_ptr(), c_data, cleanup::<T>)
|
||||
};
|
||||
if PamResultCode::PAM_SUCCESS == res {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves a value that has been set, possibly by the pam client. This is
|
||||
/// particularly useful for getting a `PamConv` reference.
|
||||
///
|
||||
/// See `pam_get_item` in
|
||||
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
|
||||
pub fn get_item<'a, T: PamItem>(&self) -> PamResult<&'a T> {
|
||||
let mut ptr: *const PamItemT = ptr::null();
|
||||
let (res, item) = unsafe {
|
||||
let r = pam_get_item(self, T::item_type(), &mut ptr);
|
||||
let typed_ptr: *const T = ptr as *const T;
|
||||
let t: &T = &*typed_ptr;
|
||||
(r, t)
|
||||
};
|
||||
if PamResultCode::PAM_SUCCESS == res {
|
||||
Ok(item)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a value in the pam context. The value can be retrieved using
|
||||
/// `get_item`.
|
||||
///
|
||||
/// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`.
|
||||
///
|
||||
/// See `pam_set_item` in
|
||||
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
|
||||
pub fn set_item_str<T: PamItem>(&mut self, item: &str) -> PamResult<()> {
|
||||
let c_item = CString::new(item).unwrap();
|
||||
|
||||
let res = unsafe {
|
||||
pam_set_item(
|
||||
self,
|
||||
T::item_type(),
|
||||
// unwrapping is okay here, as c_item will not be a NULL
|
||||
// pointer
|
||||
(c_item.as_ptr() as *const PamItemT).as_ref().unwrap(),
|
||||
)
|
||||
};
|
||||
if PamResultCode::PAM_SUCCESS == res {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the name of the user who is authenticating or logging in.
|
||||
///
|
||||
/// This is really a specialization of `get_item`.
|
||||
///
|
||||
/// See `pam_get_user` in
|
||||
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
|
||||
pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
|
||||
let ptr: *mut c_char = ptr::null_mut();
|
||||
let res = match prompt {
|
||||
Some(p) => {
|
||||
let c_prompt = CString::new(p).unwrap();
|
||||
unsafe { pam_get_user(self, &ptr, c_prompt.as_ptr()) }
|
||||
}
|
||||
None => unsafe { pam_get_user(self, &ptr, ptr::null()) },
|
||||
};
|
||||
|
||||
if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
|
||||
let const_ptr = ptr as *const c_char;
|
||||
let bytes = unsafe { CStr::from_ptr(const_ptr).to_bytes() };
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|_| PamResultCode::PAM_CONV_ERR)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_authtok(&self) -> PamResult<Option<String>> {
|
||||
let mut ptr: *const PamItemT = ptr::null();
|
||||
let (res, item) = unsafe {
|
||||
let r = pam_get_item(self, PAM_AUTHTOK, &mut ptr);
|
||||
let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
|
||||
let typed_ptr: *const c_char = ptr as *const c_char;
|
||||
Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(r, t)
|
||||
};
|
||||
if PamResultCode::PAM_SUCCESS == res {
|
||||
Ok(item)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tty(&self) -> PamResult<Option<String>> {
|
||||
let mut ptr: *const PamItemT = ptr::null();
|
||||
let (res, item) = unsafe {
|
||||
let r = pam_get_item(self, PAM_TTY, &mut ptr);
|
||||
let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
|
||||
let typed_ptr: *const c_char = ptr as *const c_char;
|
||||
Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(r, t)
|
||||
};
|
||||
if PamResultCode::PAM_SUCCESS == res {
|
||||
Ok(item)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rhost(&self) -> PamResult<Option<String>> {
|
||||
let mut ptr: *const PamItemT = ptr::null();
|
||||
let (res, item) = unsafe {
|
||||
let r = pam_get_item(self, PAM_RHOST, &mut ptr);
|
||||
let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
|
||||
let typed_ptr: *const c_char = ptr as *const c_char;
|
||||
Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(r, t)
|
||||
};
|
||||
if PamResultCode::PAM_SUCCESS == res {
|
||||
Ok(item)
|
||||
} else {
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides functions that are invoked by the entrypoints generated by the
|
||||
/// [`pam_hooks!` macro](../macro.pam_hooks.html).
|
||||
///
|
||||
/// All of hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`.
|
||||
/// Override any functions that you want to handle with your module. See `man pam(3)`.
|
||||
#[allow(unused_variables)]
|
||||
pub trait PamHooks {
|
||||
/// This function performs the task of establishing whether the user is permitted to gain access at
|
||||
/// this time. It should be understood that the user has previously been validated by an
|
||||
/// authentication module. This function checks for other things. Such things might be: the time of
|
||||
/// day or the date, the terminal line, remote hostname, etc. This function may also determine
|
||||
/// things like the expiration on passwords, and respond that the user change it before continuing.
|
||||
fn acct_mgmt(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
|
||||
/// This function performs the task of authenticating the user.
|
||||
fn sm_authenticate(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
|
||||
/// This function is used to (re-)set the authentication token of the user.
|
||||
///
|
||||
/// The PAM library calls this function twice in succession. The first time with
|
||||
/// PAM_PRELIM_CHECK and then, if the module does not return PAM_TRY_AGAIN, subsequently with
|
||||
/// PAM_UPDATE_AUTHTOK. It is only on the second call that the authorization token is
|
||||
/// (possibly) changed.
|
||||
fn sm_chauthtok(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
|
||||
/// This function is called to terminate a session.
|
||||
fn sm_close_session(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
|
||||
/// This function is called to commence a session.
|
||||
fn sm_open_session(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
|
||||
/// This function performs the task of altering the credentials of the user with respect to the
|
||||
/// corresponding authorization scheme. Generally, an authentication module may have access to more
|
||||
/// information about a user than their authentication token. This function is used to make such
|
||||
/// information available to the application. It should only be called after the user has been
|
||||
/// authenticated but before a session has been established.
|
||||
fn sm_setcred(
|
||||
pamh: &PamHandle,
|
||||
args: Vec<&CStr>,
|
||||
flags: PamFlag,
|
||||
) -> PamResultCode {
|
||||
PamResultCode::PAM_IGNORE
|
||||
}
|
||||
}
|
15
rust/param/Cargo.toml
Normal file
15
rust/param/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "param"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
chelps = { workspace = true }
|
||||
dbg.workspace = true
|
||||
ntstatus_gen.workspace = true
|
||||
paste = "1.0.15"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69.4"
|
53
rust/param/build.rs
Normal file
53
rust/param/build.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.blocklist_function("qgcvt")
|
||||
.blocklist_function("qgcvt_r")
|
||||
.blocklist_function("qfcvt")
|
||||
.blocklist_function("qfcvt_r")
|
||||
.blocklist_function("qecvt")
|
||||
.blocklist_function("qecvt_r")
|
||||
.blocklist_function("strtold")
|
||||
.clang_arg("-Dbool=int")
|
||||
.clang_arg("-Doffset_t=loff_t")
|
||||
.clang_arg("-I../../bin/default")
|
||||
.clang_arg("-I../../lib/talloc")
|
||||
.generate_comments(false)
|
||||
.clang_arg("-includestdint.h")
|
||||
.header("../../lib/param/param.h")
|
||||
.header("../../lib/param/loadparm.h")
|
||||
.header("../../source3/param/loadparm.h")
|
||||
.header("../../bin/default/lib/param/param_functions.h")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
println!("cargo:rerun-if-changed=../../lib/param/param.h");
|
||||
println!("cargo:rerun-if-changed=../../lib/param/loadparm.h");
|
||||
println!("cargo:rerun-if-changed=../../source3/param/loadparm.h");
|
||||
println!(
|
||||
"cargo:rerun-if-changed=../../bin/default/lib/param/param_functions.h"
|
||||
);
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
|
||||
let mut src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
src_dir.push("../../bin/default/source3");
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
src_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=smbconf");
|
||||
|
||||
let mut src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
src_dir.push("../../bin/default/lib/param");
|
||||
println!("cargo:rustc-link-lib=samba-hostconfig-private-samba");
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
src_dir.to_str().unwrap()
|
||||
);
|
||||
}
|
201
rust/param/src/lib.rs
Normal file
201
rust/param/src/lib.rs
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
Parameter loading functions
|
||||
|
||||
Copyright (C) David Mulder 2024
|
||||
|
||||
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 3 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 program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use chelps::{string_free, wrap_c_char, wrap_string};
|
||||
use dbg::{DBG_ERR, DBG_INFO, DBG_WARNING};
|
||||
use ntstatus_gen::NT_STATUS_UNSUCCESSFUL;
|
||||
use std::error::Error;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod ffi {
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
pub struct LoadParm {
|
||||
lp: Arc<Mutex<*mut ffi::loadparm_context>>,
|
||||
}
|
||||
|
||||
macro_rules! lpcfg_str {
|
||||
($var:ident) => {
|
||||
paste::item! {
|
||||
pub fn $var (&self) -> Result<Option<String>, Box<dyn Error + '_>> {
|
||||
let lp = self.lp.lock()?;
|
||||
let val = unsafe { ffi::[< lpcfg_ $var >] (*lp) } ;
|
||||
unsafe { Ok(wrap_c_char(val)) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! lpcfg_i32 {
|
||||
($var:ident) => {
|
||||
paste::item! {
|
||||
pub fn $var (&self) -> Result<i32, Box<dyn Error + '_>> {
|
||||
let lp = self.lp.lock()?;
|
||||
unsafe { Ok(ffi::[< lpcfg_ $var >] (*lp)) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! lpcfg_bool {
|
||||
($var:ident) => {
|
||||
paste::item! {
|
||||
pub fn $var (&self) -> Result<bool, Box<dyn Error + '_>> {
|
||||
let lp = self.lp.lock()?;
|
||||
unsafe { Ok(ffi::[< lpcfg_ $var >] (*lp) != 0) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl LoadParm {
|
||||
pub fn new(configfile: Option<&str>) -> Result<Self, Box<dyn Error + '_>> {
|
||||
let lp = unsafe {
|
||||
match configfile {
|
||||
Some(configfile) => {
|
||||
let configfile_cstr = wrap_string(configfile);
|
||||
let lp = ffi::loadparm_init_global(0);
|
||||
if ffi::lpcfg_load(lp, configfile_cstr) != 1 {
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
string_free(configfile_cstr);
|
||||
lp
|
||||
}
|
||||
None => ffi::loadparm_init_global(1),
|
||||
}
|
||||
};
|
||||
Ok(LoadParm {
|
||||
lp: Arc::new(Mutex::new(lp)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn private_path(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<Option<String>, Box<dyn Error + '_>> {
|
||||
let lp = self.lp.lock()?;
|
||||
let path = unsafe {
|
||||
let name_cstr = wrap_string(name);
|
||||
let path =
|
||||
ffi::lpcfg_private_path(*lp as *mut c_void, *lp, name_cstr);
|
||||
string_free(name_cstr);
|
||||
path
|
||||
};
|
||||
unsafe { Ok(wrap_c_char(path)) }
|
||||
}
|
||||
|
||||
pub fn logfile(&self) -> Result<Option<String>, Box<dyn Error + '_>> {
|
||||
let lp = self.lp.lock()?;
|
||||
let logfile = unsafe {
|
||||
let lp_sub = ffi::lpcfg_noop_substitution();
|
||||
ffi::lpcfg_logfile(*lp, lp_sub, *lp as *mut c_void)
|
||||
};
|
||||
unsafe { Ok(wrap_c_char(logfile)) }
|
||||
}
|
||||
|
||||
pub fn idmap_range(
|
||||
&self,
|
||||
domain_name: &str,
|
||||
) -> Result<(u32, u32), Box<dyn Error + '_>> {
|
||||
if let Ok(Some(backend)) = self.idmap_backend(domain_name) {
|
||||
if backend != "upn" {
|
||||
DBG_ERR!("Backend '{}' is not supported for Entra ID", backend);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
} else if domain_name != "*" {
|
||||
DBG_WARNING!(
|
||||
"No idmap backend configured for domain '{}'",
|
||||
domain_name
|
||||
);
|
||||
DBG_INFO!("Falling back to default idmap configuration");
|
||||
return self.idmap_range("*");
|
||||
} else {
|
||||
DBG_WARNING!("No idmap backend configured");
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
let lp = self.lp.lock()?;
|
||||
let parm_name = format!("idmap config {}", domain_name);
|
||||
let parm_opt = "range";
|
||||
let range = unsafe {
|
||||
let parm_name_cstr = wrap_string(&parm_name);
|
||||
let parm_opt_cstr = wrap_string(parm_opt);
|
||||
let parm_opt_value = ffi::lpcfg_parm_string(
|
||||
*lp,
|
||||
ptr::null_mut(),
|
||||
parm_name_cstr,
|
||||
parm_opt_cstr,
|
||||
);
|
||||
string_free(parm_name_cstr);
|
||||
string_free(parm_opt_cstr);
|
||||
wrap_c_char(parm_opt_value)
|
||||
}
|
||||
.ok_or(Box::new(NT_STATUS_UNSUCCESSFUL))?;
|
||||
let parts: Vec<&str> = range.split('-').collect();
|
||||
if parts.len() != 2 {
|
||||
DBG_ERR!("Failed to parse the idmap range: {}", range);
|
||||
return Err(Box::new(NT_STATUS_UNSUCCESSFUL));
|
||||
}
|
||||
Ok((parts[0].parse::<u32>()?, parts[1].parse::<u32>()?))
|
||||
}
|
||||
|
||||
pub fn idmap_backend(
|
||||
&self,
|
||||
domain_name: &str,
|
||||
) -> Result<Option<String>, Box<dyn Error + '_>> {
|
||||
let lp = self.lp.lock()?;
|
||||
let parm_name = format!("idmap config {}", domain_name);
|
||||
let parm_opt = "backend";
|
||||
unsafe {
|
||||
let parm_name_cstr = wrap_string(&parm_name);
|
||||
let parm_opt_cstr = wrap_string(parm_opt);
|
||||
let parm_opt_value = ffi::lpcfg_parm_string(
|
||||
*lp,
|
||||
ptr::null_mut(),
|
||||
parm_name_cstr,
|
||||
parm_opt_cstr,
|
||||
);
|
||||
string_free(parm_name_cstr);
|
||||
string_free(parm_opt_cstr);
|
||||
Ok(wrap_c_char(parm_opt_value))
|
||||
}
|
||||
}
|
||||
|
||||
lpcfg_str!(realm);
|
||||
lpcfg_str!(winbindd_socket_directory);
|
||||
lpcfg_i32!(winbind_request_timeout);
|
||||
lpcfg_bool!(himmelblaud_sfa_fallback);
|
||||
lpcfg_bool!(himmelblaud_hello_enabled);
|
||||
lpcfg_str!(cache_directory);
|
||||
lpcfg_str!(template_homedir);
|
||||
lpcfg_str!(template_shell);
|
||||
lpcfg_str!(himmelblaud_hsm_pin_path);
|
||||
}
|
||||
|
||||
unsafe impl Send for LoadParm {}
|
||||
unsafe impl Sync for LoadParm {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user