Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/perms
# Programs we must have to run the test cases
CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) t_chmod_secure$(EXEEXT) \
t_rename_secure$(EXEEXT) t_symlink_secure$(EXEEXT) t_secure_relpath$(EXEEXT) t_acl$(EXEEXT) t_hashtable_overflow$(EXEEXT) t_iwildmatch$(EXEEXT) t_clean_fname$(EXEEXT) t_safe_arg$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT)
t_rename_secure$(EXEEXT) t_symlink_secure$(EXEEXT) t_secure_relpath$(EXEEXT) t_acl$(EXEEXT) t_hashtable_overflow$(EXEEXT) t_iwildmatch$(EXEEXT) t_clean_fname$(EXEEXT) t_safe_arg$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT) @PAM_MOCK_SO@

CHECK_SYMLINKS = testsuite/chown-fake_test.py testsuite/devices-fake_test.py \
testsuite/xattrs-hlink_test.py testsuite/exclude-lsh_test.py
Expand Down Expand Up @@ -550,6 +550,9 @@ simdtest$(EXEEXT): simd-checksum-x86_64.cpp $(HEADERS)
touch $@; \
fi

pam_mock.so: $(srcdir)/testsuite/pam/pam_mock.c
$(CC) $(CFLAGS) $(CPPFLAGS) -I. -I$(srcdir) -shared -fPIC -o $@ $(srcdir)/testsuite/pam/pam_mock.c -lpam

testsuite/chown-fake_test.py:
ln -s chown_test.py $(srcdir)/testsuite/chown-fake_test.py

Expand Down
105 changes: 105 additions & 0 deletions authenticate.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,97 @@ static int get_random_bytes(char *buf, int len)
return got == len;
}

#ifdef SUPPORT_PAM
/* Cross-platform PAM header */
#if defined(HAVE_SECURITY_PAM_APPL_H)
# include <security/pam_appl.h> /* Linux, recent macOS */
#elif defined(HAVE_PAM_PAM_APPL_H)
# include <pam/pam_appl.h> /* UNIX-like */
#else
# error "PAM is enabled, but no pam_appl.h header was found."
#endif

/* Handle Solaris dropping the const qualifier in pam_message */
#if defined(__sun)
#define PAM_MSG_CONST
#else
#define PAM_MSG_CONST const
#endif

/*
* A cross-platform conversation function for PAM.
* Completely eliminates the need for the Linux-only pam_misc.h and misc_conv.
* Logs informational and error messages directly to the rsync daemon log.
* If PAM attempts to interactively prompt for a password, this instantly
* rejects it to prevent the background daemon from hanging.
*/
static int rsync_pam_conv(int num_msg, PAM_MSG_CONST struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
int i;
(void)appdata_ptr;
if (num_msg <= 0 || msg == NULL || resp == NULL)
return PAM_CONV_ERR;
*resp = NULL;

for (i = 0; i < num_msg; i++) {
if (msg[i] == NULL || msg[i]->msg == NULL)
return PAM_CONV_ERR;
switch (msg[i]->msg_style) {
case PAM_TEXT_INFO:
rprintf(FLOG, "PAM info: %s\n", msg[i]->msg);
break;
case PAM_ERROR_MSG:
rprintf(FLOG, "PAM error: %s\n", msg[i]->msg);
break;
case PAM_PROMPT_ECHO_ON:
case PAM_PROMPT_ECHO_OFF:
/*
* We don't support interactive prompts.
*/
return PAM_CONV_ERR;
default:
return PAM_CONV_ERR;
}
}
return PAM_SUCCESS;
}

static struct pam_conv conv = {
rsync_pam_conv,
NULL
};

const char *rsync_pam_validate_account(const char *username)
{
pam_handle_t *pamh = NULL;
int retval;
static char pam_err_buf[256];
const char *final_err = NULL;
/* 1. Initialize PAM */
retval = pam_start("rsync", username, &conv, &pamh);
if (retval != PAM_SUCCESS) {
snprintf(pam_err_buf, sizeof(pam_err_buf),
"PAM initialization failed for user %s", username);
return pam_err_buf;
}
/* 2. Validate account */
retval = pam_acct_mgmt(pamh, PAM_SILENT);
/* 3. Handle result */
if (retval == PAM_SUCCESS) {
rprintf(FLOG, "PAM: Account validation successful for user %s\n", username);
} else {
snprintf(pam_err_buf, sizeof(pam_err_buf),
"PAM account validation failed, %s",
pam_strerror(pamh, retval));
final_err = pam_err_buf;
}
/* 4. Cleanup */
pam_end(pamh, retval);
return final_err;
}
#endif

/* Generate a challenge buffer and return it base64-encoded. */
static void gen_challenge(const char *addr, char *challenge)
{
Expand Down Expand Up @@ -289,6 +380,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
const char *addr, const char *leader)
{
char *users = lp_auth_users(module);
int use_pam = lp_use_pam(module);
char challenge[MAX_DIGEST_LEN*2];
char line[BIGPATHBUFLEN];
const char **auth_uid_groups = NULL;
Expand Down Expand Up @@ -414,7 +506,20 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
err = "denied by rule";
else {
const char *group = group_match >= 0 ? auth_uid_groups[group_match] : NULL;
/* 1. Verify standard rsync credentials first */
err = check_secret(module, line, group, challenge, pass);
/* 2. Validate PAM requirements and account status */
if (!err && use_pam) {
#ifndef SUPPORT_PAM
err = "PAM enabled but rsync compiled without PAM support";
#else
/* If PAM fails, this points to our detailed static buffer.
If it succeeds, it returns NULL and err remains NULL. */
const char *pam_err = rsync_pam_validate_account(line);
if (pam_err)
err = pam_err;
#endif
}
}

force_memzero(challenge, sizeof challenge);
Expand Down
28 changes: 27 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ AC_CHECK_HEADERS(poll.h sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h
sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \
popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \
zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \
sys/resource.h bsd/string.h idn2.h)
sys/resource.h bsd/string.h idn2.h security/pam_modules.h security/pam_appl.h pam/pam_appl.h)
AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include <netinet/in.h>]])
AC_HEADER_MAJOR_FIXED

Expand Down Expand Up @@ -655,6 +655,32 @@ else
AC_MSG_RESULT(no)
fi

PAM_MOCK_SO=""
AC_MSG_CHECKING([whether to enable PAM support])
AC_ARG_ENABLE([pam],
AS_HELP_STRING([--enable-pam], [enable PAM support (default is NO)]))
AH_TEMPLATE([SUPPORT_PAM],
[Define to 1 if you want PAM support. By default this is undefined.])

if test x"$enable_pam" = x"yes"; then
if test x"$ac_cv_header_security_pam_appl_h" = x"yes" || test x"$ac_cv_header_pam_pam_appl_h" = x"yes"; then
AC_MSG_RESULT(yes)
AC_SEARCH_LIBS(pam_start, pam,
[AC_DEFINE(SUPPORT_PAM)
PAM_MOCK_SO="pam_mock.so"],
[err_msg="$err_msg$nl- Failed to find pam_start function in pam lib.";
no_lib="$no_lib pam"])
else
AC_MSG_RESULT(no)
err_msg="$err_msg$nl- Failed to find pam_appl.h for PAM support."
no_lib="$no_lib pam"
fi
else
AC_MSG_RESULT(no)
fi

AC_SUBST(PAM_MOCK_SO)

if test x"$no_lib" != x; then
echo ""
echo "Configure found the following issues:"
Expand Down
1 change: 1 addition & 0 deletions daemon-parm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ BOOL reverse_lookup True
BOOL strict_modes True
BOOL transfer_logging False
BOOL write_only False
BOOL use_pam False

BOOL3 munge_symlinks Unset
BOOL3 numeric_ids Unset
Expand Down
14 changes: 14 additions & 0 deletions rsyncd.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,20 @@ in the values of parameters. See that section for details.
the exact check. If the file is not found or is rejected, no logins for an
"[auth users](#)" module will be possible.

0. `use pam`

This parameter determines whether the rsync daemon will utilize Pluggable
Authentication Modules (PAM) for account validation. If "use pam" is true,
rsync will invoke the PAM account management subsystem (`pam_acct_mgmt`)
after a user successfully authenticates. This allows administrators to
enforce system-level access policies (such as locked, expired, or disabled
accounts) without duplicating those controls inside rsync.

Note that this is strictly for account management, not primary password
authentication. Password verification is always handled by rsync's internal
challenge-response mechanism using the "[secrets file](#)" parameter. The
default is false.

0. `auth digest`

This parameter sets the *minimum* message digest that the daemon will accept
Expand Down
Loading
Loading