Compare commits

..

26 Commits

Author SHA1 Message Date
Wayne Davison
9a06b2edb0 Preparing for release of 3.3.0pre1 [buildall] 2023-04-29 09:01:43 -07:00
Wayne Davison
273dced284 Update the NEWS. 2023-04-29 09:01:09 -07:00
Wayne Davison
b6e2321973 Mention that --crtimes support is spotty. 2023-04-29 08:21:19 -07:00
Wayne Davison
fe95a9369a Fix issue with trailing --sparse --inplace blocks.
Fixes #450.
2023-04-29 07:56:27 -07:00
Wayne Davison
6ae7f4085a Add --force-link-text to md-convert. 2023-04-23 08:26:32 -07:00
Wayne Davison
0f599d3641 Fix overflow of sum2 buffer for sha1 rolling checksums.
Fixed #353.
2023-04-22 08:49:50 -07:00
Wayne Davison
c3d3b49d72 Make use of .UR & .UE for links. 2023-04-22 08:40:27 -07:00
Wayne Davison
c69dc7a5ab Tweak shell protection news to mention a few more characters. 2023-03-30 12:56:49 -07:00
dogvisor
2c82006b1f add rrsync option to enforce --ignore-existing (#461)
The `-no-overwrite` rrsync option disallows the updating of existing files for incoming rrsync copies.
2023-03-30 12:55:56 -07:00
Wayne Davison
0698ea9aeb Fix flist string comparison issue in tr_TR.utf-8 locale. 2023-02-05 19:46:45 -08:00
Wayne Davison
90df93e446 Don't call memcmp() on an empty lastdir. 2023-01-08 21:35:39 -08:00
Wayne Davison
5c93dedf45 Add backtick to SHELL_CHARS. 2023-01-04 21:52:48 -08:00
Wayne Davison
f1e3434b59 Trust the sender on a local transfer. 2022-12-01 20:24:17 -08:00
Wayne Davison
48252c3c2b A couple manpage links. 2022-11-23 07:59:12 -08:00
Wayne Davison
5b67ff2a86 Improve [global] module documentation. 2022-11-22 22:55:52 -08:00
Wayne Davison
8990ad96de Duplicate argv data before poptFreeContext(). 2022-11-22 22:21:15 -08:00
Wayne Davison
0f44e864d4 Another python conversion. 2022-11-20 09:38:12 -08:00
Wayne Davison
ab0d5021ed Convert a few more scripts to python3. 2022-11-16 00:10:09 -08:00
Wayne Davison
7402896523 Tweak an older NEWS item to be a bit clearer. 2022-11-09 16:04:02 -08:00
Wayne Davison
5374994089 Avoid quoting of tilde when it's a destination arg. 2022-11-05 09:22:10 -07:00
Wayne Davison
526366129a Upgrade verion of actions. 2022-11-02 23:54:41 -07:00
Wayne Davison
556a2c5bc2 Check for EVP_MD_CTX_copy in crypto lib instead of MD5_Init. 2022-10-25 21:55:53 -07:00
Wayne Davison
27feda0436 Call OpenSSL_add_all_algorithms() on older openssl versions. 2022-10-25 09:04:45 -07:00
Wayne Davison
bf96cd314c Init the checksum choices before the daemon auth. 2022-10-25 09:04:45 -07:00
Wayne Davison
1b2688807d Fix protocol <= 29 daemon auth if openssl is handling md4. 2022-10-24 08:38:00 -07:00
Wayne Davison
08ec80ac65 Cygwin needs stdout flushed. [buildall] 2022-10-22 12:04:32 -07:00
93 changed files with 1088 additions and 4682 deletions

View File

@@ -1,76 +0,0 @@
name: Test rsync on AlmaLinux 8
# Older-LTS coverage on the Fedora/RHEL family to help with backporting
# security fixes. AlmaLinux 8 is the RHEL 8 rebuild and is the oldest
# active LTS in this family (RHEL 8 full support runs to 2029).
# GitHub Actions has no native runner for this family, so the job runs
# inside an almalinux:8 container hosted on ubuntu-latest.
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/almalinux-8-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/almalinux-8-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
container:
image: almalinux:8
name: Test rsync on AlmaLinux 8
steps:
- name: install git
# actions/checkout needs git in the container before the checkout step.
run: dnf -y install git
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
# PowerTools is needed for libzstd-devel etc; xxhash and lz4 dev
# headers live in EPEL on RHEL 8. The default python3 on RHEL 8
# is 3.6, which is too old for runtests.py (uses capture_output=
# / text= introduced in 3.7), so install python39 and point
# /usr/bin/python3 at it.
run: |
dnf -y install epel-release
dnf config-manager --set-enabled powertools
dnf -y install gcc gcc-c++ make autoconf automake m4 \
python39 python39-pip diffutils \
openssl openssl-devel \
attr libattr-devel acl libacl-devel \
zstd libzstd-devel \
lz4 lz4-devel \
xxhash xxhash-devel
alternatives --set python3 /usr/bin/python3.9
pip3 install commonmark
- name: configure
run: ./configure --with-rrsync
- name: make
run: make
- name: info
run: ./rsync --version
- name: check
# In the container we already run as root, so no sudo. The
# crtimes-not-supported skip matches the other Linux jobs.
run: RSYNC_EXPECT_SKIPPED=crtimes make check
- name: ssl file list
run: ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: almalinux-8-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

125
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,125 @@
name: build
on:
push:
branches: [ master ]
paths-ignore: [ .cirrus.yml ]
pull_request:
branches: [ master ]
paths-ignore: [ .cirrus.yml ]
schedule:
- cron: '42 8 * * *'
jobs:
ubuntu-build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl wget
wget -O git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make
run: make
- name: install
run: sudo make install
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v3
with:
name: ubuntu-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync
macos-build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: prep
run: |
brew install automake openssl xxhash zstd lz4 wget
sudo pip3 install commonmark
wget -O git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: CPPFLAGS=-I/usr/local/opt/openssl/include/ LDFLAGS=-L/usr/local/opt/openssl/lib/ ./configure --with-rrsync
- name: make
run: make
- name: install
run: sudo make install
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,devices-fake,dir-sgid,protected-regular,xattrs-hlink,xattrs make check
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v3
with:
name: macos-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync
cygwin-build:
runs-on: windows-2022
if: (github.event_name == 'schedule' || contains(github.event.head_commit.message, '[buildall]'))
steps:
- uses: actions/checkout@v3
- name: cygwin
run: choco install -y --no-progress cygwin cyg-get
- name: prep
run: |
cyg-get make autoconf automake gcc-core attr libattr-devel python39 python39-pip libzstd-devel liblz4-devel libssl-devel libxxhash0 libxxhash-devel
curl.exe -o git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
echo "C:/tools/cygwin/bin" >>$Env:GITHUB_PATH
- name: commonmark
run: bash -c 'python3 -mpip install --user commonmark'
- name: configure
run: bash -c './configure --with-rrsync'
- name: make
run: bash -c 'make'
- name: install
run: bash -c 'make install'
- name: info
run: bash -c '/usr/local/bin/rsync --version'
- name: check
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,chown,devices,dir-sgid,protected-regular make check'
- name: ssl file list
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
- name: save artifact
uses: actions/upload-artifact@v3
with:
name: cygwin-bin
path: |
rsync.exe
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,55 +0,0 @@
name: Test rsync on Cygwin
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/cygwin-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/cygwin-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: windows-2022
name: Test rsync on Cygwin
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: cygwin
run: choco install -y --no-progress cygwin cyg-get
- name: prep
run: |
cyg-get make autoconf automake gcc-core attr libattr-devel python39 python39-pip libzstd-devel liblz4-devel libssl-devel libxxhash0 libxxhash-devel
echo "C:/tools/cygwin/bin" >>$Env:GITHUB_PATH
- name: commonmark
run: bash -c 'python3 -mpip install --user commonmark'
- name: configure
run: bash -c './configure --with-rrsync'
- name: make
run: bash -c 'make'
- name: install
run: bash -c 'make install'
- name: info
run: bash -c '/usr/local/bin/rsync --version'
- name: check
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,bare-do-open-symlink-race,chdir-symlink-race,chmod-symlink-race,chown,clean-fname-underflow,daemon-chroot-acl,devices,dir-sgid,protected-regular,sender-flist-symlink-leak,simd-checksum,symlink-dirlink-basis make check'
- name: ssl file list
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: cygwin-bin
path: |
rsync.exe
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,49 +0,0 @@
name: Test rsync on FreeBSD
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/freebsd-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/freebsd-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on FreeBSD
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test in FreeBSD VM
id: test
uses: vmactions/freebsd-vm@v1
with:
usesh: true
prepare: |
pkg install -y bash autotools m4 devel/xxhash zstd liblz4 python3 archivers/liblz4 git
run: |
freebsd-version
./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: freebsd-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,57 +0,0 @@
name: Test rsync on macOS
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/macos-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/macos-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: macos-latest
name: Test rsync on macOS
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
brew install automake openssl xxhash zstd lz4
pip3 install --user --break-system-packages commonmark
echo "$(brew --prefix)/bin" >>$GITHUB_PATH
- name: configure
run: |
BREW_PREFIX=$(brew --prefix)
OPENSSL_PREFIX=$(brew --prefix openssl)
CPPFLAGS="-I${BREW_PREFIX}/include -I${OPENSSL_PREFIX}/include" \
LDFLAGS="-L${BREW_PREFIX}/lib -L${OPENSSL_PREFIX}/lib" \
./configure --with-rrsync
- name: make
run: make
- name: install
run: sudo make install
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,clean-fname-underflow,daemon-chroot-acl,devices-fake,dir-sgid,protected-regular,simd-checksum,xattrs-hlink,xattrs make check
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: macos-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,51 +0,0 @@
name: Test rsync on NetBSD
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/netbsd-build.yml'
pull_request:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/netbsd-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on NetBSD
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test in NetBSD VM
id: test
uses: vmactions/netbsd-vm@v1
with:
usesh: true
prepare: |
PATH=/usr/sbin:$PATH pkg_add autoconf automake python312
ln -sf /usr/pkg/bin/python3.12 /usr/pkg/bin/python3
run: |
uname -a
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: netbsd-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,52 +0,0 @@
name: Test rsync on OpenBSD
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/openbsd-build.yml'
pull_request:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/openbsd-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on OpenBSD
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test in OpenBSD VM
id: test
uses: vmactions/openbsd-vm@v1
with:
usesh: true
prepare: |
pkg_add -I bash autoconf%2.71 automake%1.16
run: |
uname -a
export AUTOCONF_VERSION=2.71
export AUTOMAKE_VERSION=1.16
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: openbsd-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,49 +0,0 @@
name: Test rsync on Solaris
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/solaris-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/solaris-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on Solaris
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test in Solaris VM
id: test
uses: vmactions/solaris-vm@v1
with:
usesh: true
prepare: |
pkg install bash automake gnu-m4 pkg://solaris/runtime/python-35 autoconf gcc git
run: |
uname -a
./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: solaris-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,59 +0,0 @@
name: Test rsync on Ubuntu 22.04
# Older-LTS coverage to help with backporting security fixes. ubuntu-22.04
# is currently the oldest GitHub Actions runner image (20.04 was retired
# in April 2025).
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-22.04-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-22.04-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-22.04
name: Test rsync on Ubuntu 22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make
run: make
- name: install
run: sudo make install
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: ubuntu-22.04-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,55 +0,0 @@
name: Test rsync on Ubuntu
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-build.yml'
pull_request:
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on Ubuntu
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make
run: make
- name: install
run: sudo make install
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: ubuntu-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -57,13 +57,12 @@ 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_secure_relpath$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT)
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) wildtest$(EXEEXT)
CHECK_SYMLINKS = testsuite/chown-fake.test testsuite/devices-fake.test testsuite/xattrs-hlink.test
# Objects for CHECK_PROGS to clean
CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o t_chmod_secure.o t_secure_relpath.o trimslash.o wildtest.o
CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o trimslash.o wildtest.o
# note that the -I. is needed to handle config.h when using VPATH
.c.o:
@@ -179,14 +178,6 @@ T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/sn
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_chmod_secure$(EXEEXT): $(T_CHMOD_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CHMOD_SECURE_OBJ) $(LIBS)
T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_secure_relpath$(EXEEXT): $(T_SECURE_RELPATH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SECURE_RELPATH_OBJ) $(LIBS)
.PHONY: conf
conf: configure.sh config.h.in
@@ -343,14 +334,6 @@ wildtest.o: wildtest.c t_stub.o lib/wildmatch.c rsync.h config.h
wildtest$(EXEEXT): wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@ $(LIBS)
simdtest$(EXEEXT): simd-checksum-x86_64.cpp $(HEADERS)
@if test x"@ROLL_SIMD@" != x; then \
$(CXX) -I. $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -DTEST_SIMD_CHECKSUM1 \
-o $@ $(srcdir)/simd-checksum-x86_64.cpp @ROLL_ASM@ $(LIBS); \
else \
touch $@; \
fi
testsuite/chown-fake.test:
ln -s chown.test $(srcdir)/testsuite/chown-fake.test

70
NEWS.md
View File

@@ -1,3 +1,53 @@
# NEWS for rsync 3.3.0 (UNRELEASED)
## Changes in this version:
### BUG FIXES:
- Fixed a bug with `--sparse --inplace` where a trailing gap in the source
file would not clear out the trailing data in the destination file.
- Fixed an buffer overflow in the checksum2 code if SHA1 is being used for
the checksum2 algorithm.
- Fixed a string-comparison issue in the internal file-list code that affected
tr_TR.utf-8.
- Add a backtick to the list of characters that the filename quoting needs to
escape using backslashes.
- Make sure that a local transfer marks the sender side as trusted.
- Change the argv handling to work with a newer popt library -- one that likes
to free more data than it used to.
- Rsync now calls `OpenSSL_add_all_algorithms()` when compiled against an older
openssl library.
- Fixed a problem in the daemon auth for older protocols (29 and before) if the
openssl library is being used to compute md4 checksums.
- Fixed `rsync -VV` on Cygwin -- it needed a flush of stdout.
### ENHANCEMENTS:
- Enhanced rrsync with the `-no-overwrite` option that allows you to ensure
that existing files on your restricted but writable directory can't be
modified.
- Enhanced the manpages to mark links with .UR & .UE. If your nroff doesn't
support these idioms, touch the file `.md2man-force` in the source directory
so that `md-convert` gets called with the `--force-link-text` option, and
that should ensure that your manpages are still readable even with the
ignored markup.
- Some manpage improvements on the handling of [global] modules.
- Changed the mapfrom & mapto perl scripts (in the support dir) into a single
python script named idmap. Converted a couple more perl scripts into python.
------------------------------------------------------------------------------
# NEWS for rsync 3.2.7 (20 Oct 2022)
## Changes in this version:
@@ -217,9 +267,10 @@
- A new form of arg protection was added that works similarly to the older
`--protect-args` ([`-s`](rsync.1#opt)) option but in a way that avoids
breaking things like rrsync (the restricted rsync script): rsync now uses
backslash escaping for sending "shell-active" characters to the remote
shell. This includes spaces, so fetching a remote file via a simple quoted
filename value now works by default without any extra quoting:
backslash escaping for sending "shell-active" characters to the remote shell
(such as `$(){}<>#&` and others). This includes spaces, so fetching a remote
file via a quoted filename value now works by default without any extra
quoting:
```shell
rsync -aiv host:'a simple file.pdf' .
@@ -227,10 +278,14 @@
Wildcards are not escaped in filename args, but they are escaped in options
like the [`--suffix`](rsync.1#opt) and [`--usermap`](rsync.1#opt) values.
If your rsync script depends on the old arg-splitting behavior, either run
it with the [`--old-args`](rsync.1#opt) option or `export RSYNC_OLD_ARGS=1`
in the script's environment. See also the [ADVANCED USAGE](rsync.1#)
section of rsync's manpage for how to use a more modern arg style.
If a script depends on the old arg behavior (perhaps because it quotes or
protects the args already, or perhaps because it expects arg splitting),
there are two easy ways to get things going with a modern rsync: either
`export RSYNC_OLD_ARGS=1` in the script's environment (perhaps in the script
itself) or add the option [`--old-args`](rsync.1#opt) to the rsync commands
that are run. See also the [ADVANCED USAGE](rsync.1#) section of rsync's
manpage for how to use a more modern arg style.
- A long-standing bug was preventing rsync from figuring out the current
locale's decimal point character, which made rsync always output numbers
@@ -4692,6 +4747,7 @@
| RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL |
|--------------|--------|------------------|-------------|
| ?? May 2023 | 3.3.0 | | 31 |
| 20 Oct 2022 | 3.2.7 | | 31 |
| 09 Sep 2022 | 3.2.6 | | 31 |
| 14 Aug 2022 | 3.2.5 | | 31 |

View File

@@ -99,7 +99,7 @@ static void make_mask(char *mask, int plen, int addrlen)
return;
}
static int match_address(const char *addr, char *tok)
static int match_address(const char *addr, const char *tok)
{
char *p;
struct addrinfo hints, *resa, *rest;

4
acls.c
View File

@@ -697,7 +697,7 @@ static uint32 recv_acl_access(int f, uchar *name_follows_ptr)
static uchar recv_ida_entries(int f, ida_entries *ent)
{
uchar computed_mask_bits = 0;
int i, count = read_varint_bounded(f, 0, MAX_WIRE_ACL_COUNT, "ACL count");
int i, count = read_varint(f);
ent->idas = count ? new_array(id_access, count) : NULL;
ent->count = count;
@@ -713,7 +713,7 @@ static uchar recv_ida_entries(int f, ida_entries *ent)
else
id = recv_group_name(f, id, NULL);
} else if (access & NAME_IS_USER) {
if (inc_recurse && !numeric_ids)
if (inc_recurse && am_root && !numeric_ids)
id = match_uid(id);
} else {
if (inc_recurse && (!am_root || !numeric_ids))

View File

@@ -39,7 +39,7 @@ static int validate_backup_dir(void)
{
STRUCT_STAT st;
if (do_lstat_at(backup_dir_buf, &st) < 0) {
if (do_lstat(backup_dir_buf, &st) < 0) {
if (errno == ENOENT)
return 0;
rsyserr(FERROR, errno, "backup lstat %s failed", backup_dir_buf);
@@ -98,7 +98,7 @@ static BOOL copy_valid_path(const char *fname)
for ( ; b; name = b + 1, b = strchr(name, '/')) {
*b = '\0';
while (do_mkdir_at(backup_dir_buf, ACCESSPERMS) < 0) {
while (do_mkdir(backup_dir_buf, ACCESSPERMS) < 0) {
if (errno == EEXIST) {
val = validate_backup_dir();
if (val > 0)
@@ -197,7 +197,7 @@ static inline int link_or_rename(const char *from, const char *to,
if (IS_SPECIAL(stp->st_mode) || IS_DEVICE(stp->st_mode))
return 0; /* Use copy code. */
#endif
if (do_link_at(from, to) == 0) {
if (do_link(from, to) == 0) {
if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: HLINK %s successful.\n", from);
return 2;
@@ -207,7 +207,7 @@ static inline int link_or_rename(const char *from, const char *to,
return 0;
}
#endif
if (do_rename_at(from, to) == 0) {
if (do_rename(from, to) == 0) {
if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) {
/* If someone has hard-linked the file into the backup
* dir, rename() might return success but do nothing! */
@@ -246,7 +246,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
goto success;
if (errno == EEXIST || errno == EISDIR) {
STRUCT_STAT bakst;
if (do_lstat_at(buf, &bakst) == 0) {
if (do_lstat(buf, &bakst) == 0) {
int flags = get_del_for_flag(bakst.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE;
if (delete_item(buf, bakst.st_mode, flags) != 0)
return 0;
@@ -277,7 +277,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
/* Check to see if this is a device file, or link */
if ((am_root && preserve_devices && IS_DEVICE(file->mode))
|| (preserve_specials && IS_SPECIAL(file->mode))) {
if (do_mknod_at(buf, file->mode, sx.st.st_rdev) < 0)
if (do_mknod(buf, file->mode, sx.st.st_rdev) < 0)
rsyserr(FERROR, errno, "mknod %s failed", full_fname(buf));
else if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: DEVICE %s successful.\n", fname);
@@ -294,7 +294,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
}
ret = 2;
} else {
if (do_symlink_at(sl, buf) < 0)
if (do_symlink(sl, buf) < 0)
rsyserr(FERROR, errno, "link %s -> \"%s\"", full_fname(buf), sl);
else if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: SYMLINK %s successful.\n", fname);

View File

@@ -3,7 +3,7 @@
*
* Copyright (C) 1996 Andrew Tridgell
* Copyright (C) 1996 Paul Mackerras
* Copyright (C) 2004-2022 Wayne Davison
* Copyright (C) 2004-2023 Wayne Davison
*
* 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
@@ -154,7 +154,7 @@ static const EVP_MD *csum_evp_md(struct name_num_item *nni)
emd = NULL;
else
#endif
emd = EVP_get_digestbyname(nni->name);
emd = EVP_get_digestbyname(nni->name);
if (emd && !(nni->flags & NNI_EVP_OK)) { /* Make sure it works before we advertise it */
if (!ctx_evp && !(ctx_evp = EVP_MD_CTX_create()))
out_of_memory("csum_evp_md");
@@ -176,7 +176,7 @@ void parse_checksum_choice(int final_call)
if (valid_checksums.negotiated_nni)
xfer_sum_nni = file_sum_nni = valid_checksums.negotiated_nni;
else {
const char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
if (cp) {
xfer_sum_nni = parse_csum_name(checksum_choice, cp - checksum_choice);
file_sum_nni = parse_csum_name(cp+1, -1);
@@ -300,6 +300,7 @@ uint32 get_checksum1(char *buf1, int32 len)
}
#endif
/* The "sum" buffer must be at least MAX_DIGEST_LEN bytes! */
void get_checksum2(char *buf, int32 len, char *sum)
{
#ifdef USE_OPENSSL
@@ -365,8 +366,9 @@ void get_checksum2(char *buf, int32 len, char *sum)
mdfour_begin(&m);
if (len > len1 || !buf1) {
free(buf1);
if (len > len1) {
if (buf1)
free(buf1);
buf1 = new_array(char, len+4);
len1 = len;
}
@@ -404,7 +406,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
int32 remainder;
int fd;
fd = do_open_checklinks(fname);
fd = do_open(fname, O_RDONLY, 0);
if (fd == -1) {
memset(sum, 0, file_sum_len);
return;
@@ -786,6 +788,10 @@ void init_checksum_choices()
if (initialized_choices)
return;
#if defined USE_OPENSSL && OPENSSL_VERSION_NUMBER < 0x10100000L
OpenSSL_add_all_algorithms();
#endif
#if defined SUPPORT_XXH3 || defined USE_OPENSSL
for (nni = valid_checksums.list; nni->name; nni++)
verify_digest(nni, True);

View File

@@ -198,7 +198,7 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
switch_step++;
if (cleanup_fname)
do_unlink_at(cleanup_fname);
do_unlink(cleanup_fname);
if (exit_code)
kill_all(SIGUSR1);
if (cleanup_pid && cleanup_pid == getpid()) {

View File

@@ -167,7 +167,7 @@ int read_proxy_protocol_header(int fd)
char sig[PROXY_V2_SIG_SIZE];
char ver_cmd;
char fam;
unsigned char len[2];
char len[2];
union {
struct {
char src_addr[4];

View File

@@ -30,7 +30,6 @@ extern int list_only;
extern int am_sender;
extern int am_server;
extern int am_daemon;
extern int am_chrooted;
extern int am_root;
extern int msgs2stderr;
extern int rsync_port;
@@ -39,7 +38,6 @@ extern int ignore_errors;
extern int preserve_xattrs;
extern int kluge_around_eof;
extern int munge_symlinks;
extern int use_secure_symlinks;
extern int open_noatime;
extern int sanitize_paths;
extern int numeric_ids;
@@ -978,14 +976,11 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
if (use_chroot) {
/* Cache timezone data before chroot makes /etc/localtime inaccessible */
tzset();
if (chroot(module_chdir)) {
rsyserr(FLOG, errno, "chroot(\"%s\") failed", module_chdir);
io_printf(f_out, "@ERROR: chroot failed\n");
return -1;
}
am_chrooted = 1;
module_chdir = module_dir;
}
@@ -1008,15 +1003,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
}
/* Enable secure symlink handling for any non-chrooted daemon module.
* This prevents TOCTOU race attacks where an attacker could switch a
* directory to a symlink between path validation and file open.
* Match the gate used by the do_*_at() wrappers in syscall.c
* (am_daemon && !am_chrooted) -- the protection has nothing to do
* with symlink munging, so a module configured with
* "munge symlinks = false" must still get the secure-open path. */
use_secure_symlinks = am_daemon && !am_chrooted;
if (gid_list.count) {
gid_t *gid_array = gid_list.items;
if (setgid(gid_array[0])) {
@@ -1312,49 +1298,13 @@ int start_daemon(int f_in, int f_out)
if (lp_proxy_protocol() && !read_proxy_protocol_header(f_in))
return -1;
/* Do reverse DNS lookup before chroot/setuid. The result is cached,
* so the later client_name() call will use this cached value. This
* ensures hostname-based ACLs work even when DNS is unavailable
* after chroot.
*
* "reverse lookup" can be set globally OR per-module, so we also
* scan each module: a deployment with "reverse lookup = no" in the
* global section but "reverse lookup = yes" in a specific module
* still triggers a post-chroot lookup at access-check time
* (rsync_module() in this file), which would also fail in the
* chroot and turn hostname-based deny rules into silent bypasses. */
{
int need_reverse = lp_reverse_lookup(-1);
int j, num_modules = lp_num_modules();
for (j = 0; !need_reverse && j < num_modules; j++) {
if (lp_reverse_lookup(j))
need_reverse = 1;
}
if (need_reverse)
(void)client_name(client_addr(f_in));
}
p = lp_daemon_chroot();
if (*p) {
log_init(0); /* Make use we've initialized syslog before chrooting. */
tzset();
if (chroot(p) < 0) {
rsyserr(FLOG, errno, "daemon chroot(\"%s\") failed", p);
return -1;
}
/* Deliberately do NOT set am_chrooted here. am_chrooted
* gates the per-module symlink-race defenses
* (secure_relative_open() and the do_*_at() wrappers in
* syscall.c) and means "the kernel is enforcing path
* confinement at the module boundary". The daemon chroot
* confines path resolution to the daemon-chroot directory,
* not to any individual module path -- modules sharing the
* daemon chroot are still distinguishable filesystem
* subtrees and a sender-controlled symlink in module A
* could redirect a syscall to module B (or to other files
* inside the daemon chroot) without the per-module
* defenses. Leave am_chrooted=0 here so secure_relative_open()
* still fires for "use chroot = no" modules. */
if (chdir("/") < 0) {
rsyserr(FLOG, errno, "daemon chdir(\"/\") failed");
return -1;

View File

@@ -131,7 +131,7 @@ static const char *client_info;
* of that protocol for it to be advertised as available. */
static void check_sub_protocol(void)
{
const char *dot;
char *dot;
int their_protocol, their_sub;
int our_sub = get_subprotocol_version();
@@ -414,7 +414,7 @@ static const char *getenv_nstr(int ntype)
env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 30 ? "md5" : "md4";
if (am_server && env_str) {
const char *cp = strchr(env_str, '&');
char *cp = strchr(env_str, '&');
if (cp)
env_str = cp + 1;
}
@@ -834,6 +834,8 @@ void output_daemon_greeting(int f_out, int am_client)
char tmpbuf[MAX_NSTR_STRLEN];
int our_sub = get_subprotocol_version();
init_checksum_choices();
get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0');
io_printf(f_out, "@RSYNCD: %d.%d %s\n", protocol_version, our_sub, tmpbuf);
@@ -873,8 +875,10 @@ void negotiate_daemon_auth(int f_out, int am_client)
}
}
am_server = save_am_server;
if (md4_is_old && valid_auth_checksums.negotiated_nni->num == CSUM_MD4)
if (md4_is_old && valid_auth_checksums.negotiated_nni->num == CSUM_MD4) {
valid_auth_checksums.negotiated_nni->num = CSUM_MD4_OLD;
valid_auth_checksums.negotiated_nni->flags = 0;
}
}
int get_subprotocol_version()

View File

@@ -432,10 +432,10 @@ AH_TEMPLATE([USE_OPENSSL],
if test x"$enable_openssl" != x"no"; then
if test x"$ac_cv_header_openssl_md4_h" = x"yes" && test x"$ac_cv_header_openssl_md5_h" = x"yes"; then
AC_MSG_RESULT(yes)
AC_SEARCH_LIBS(MD5_Init, crypto,
AC_SEARCH_LIBS(EVP_MD_CTX_copy, crypto,
[AC_DEFINE(USE_OPENSSL)
enable_openssl=yes],
[err_msg="$err_msg$nl- Failed to find MD5_Init function in openssl crypto lib.";
[err_msg="$err_msg$nl- Failed to find EVP_MD_CTX_copy function in openssl crypto lib.";
no_lib="$no_lib openssl"])
else
AC_MSG_RESULT(no)
@@ -1390,7 +1390,7 @@ else
AC_DEFINE(HAVE_LINUX_XATTRS, 1, [True if you have Linux xattrs (or equivalent)])
AC_DEFINE(SUPPORT_XATTRS, 1)
AC_DEFINE(NO_SYMLINK_USER_XATTRS, 1, [True if symlinks do not support user xattrs])
AC_SEARCH_LIBS(getxattr,attr)
AC_CHECK_LIB(attr,getxattr)
;;
darwin*)
AC_MSG_RESULT(Using OS X xattrs)

View File

@@ -98,7 +98,7 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
strlcpy(p, fp->basename, remainder);
if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
do_chmod_at(fname, fp->mode | S_IWUSR);
do_chmod(fname, fp->mode | S_IWUSR);
/* Save stack by recursing to ourself directly. */
if (S_ISDIR(fp->mode)) {
if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
@@ -139,7 +139,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
}
if (flags & DEL_NO_UID_WRITE)
do_chmod_at(fbuf, mode | S_IWUSR);
do_chmod(fbuf, mode | S_IWUSR);
if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
/* This only happens on the first call to delete_item() since
@@ -160,7 +160,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
if (S_ISDIR(mode)) {
what = "rmdir";
ok = do_rmdir_at(fbuf) == 0;
ok = do_rmdir(fbuf) == 0;
} else {
if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) {
what = "make_backup";

View File

@@ -720,8 +720,7 @@ static BOOL setup_merge_file(int mergelist_num, filter_rule *ex,
parent_dirscan = True;
while (*y) {
char save[MAXPATHLEN];
/* copylen is strlen(y) which is < MAXPATHLEN. +1 for \0 */
size_t copylen = strlcpy(save, y, MAXPATHLEN) + 1;
strlcpy(save, y, MAXPATHLEN);
*y = '\0';
dirbuf_len = y - dirbuf;
strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));
@@ -735,7 +734,7 @@ static BOOL setup_merge_file(int mergelist_num, filter_rule *ex,
lp->head = NULL;
}
lp->tail = NULL;
strlcpy(y, save, copylen);
strlcpy(y, save, MAXPATHLEN);
while ((*x++ = *y++) != '/') {}
}
parent_dirscan = False;
@@ -904,7 +903,7 @@ static int rule_matches(const char *fname, filter_rule *ex, int name_flags)
{
int slash_handling, str_cnt = 0, anchored_match = 0;
int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1;
const char *p, *pattern = ex->pattern;
char *p, *pattern = ex->pattern;
const char *strings[16]; /* more than enough */
const char *name = fname + (*fname == '/');

View File

@@ -3,7 +3,7 @@
*
* Copyright (C) 1998 Andrew Tridgell
* Copyright (C) 2002 Martin Pool
* Copyright (C) 2004-2020 Wayne Davison
* Copyright (C) 2004-2023 Wayne Davison
*
* 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
@@ -40,30 +40,34 @@ OFF_T preallocated_len = 0;
static OFF_T sparse_seek = 0;
static OFF_T sparse_past_write = 0;
int sparse_end(int f, OFF_T size)
int sparse_end(int f, OFF_T size, int updating_basis_or_equiv)
{
int ret;
int ret = 0;
sparse_past_write = 0;
if (!sparse_seek)
return 0;
#ifdef HAVE_FTRUNCATE
ret = do_ftruncate(f, size);
#else
if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1)
ret = -1;
else {
do {
ret = write(f, "", 1);
} while (ret < 0 && errno == EINTR);
ret = ret <= 0 ? -1 : 0;
}
if (updating_basis_or_equiv) {
if (sparse_seek && do_punch_hole(f, sparse_past_write, sparse_seek) < 0)
ret = -1;
#ifdef HAVE_FTRUNCATE /* A compilation formality -- in-place requires ftruncate() */
else /* Just in case the original file was longer */
ret = do_ftruncate(f, size);
#endif
} else if (sparse_seek) {
#ifdef HAVE_FTRUNCATE
ret = do_ftruncate(f, size);
#else
if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1)
ret = -1;
else {
do {
ret = write(f, "", 1);
} while (ret < 0 && errno == EINTR);
sparse_seek = 0;
ret = ret <= 0 ? -1 : 0;
}
#endif
}
sparse_past_write = sparse_seek = 0;
return ret;
}

36
flist.c
View File

@@ -4,7 +4,7 @@
* Copyright (C) 1996 Andrew Tridgell
* Copyright (C) 1996 Paul Mackerras
* Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
* Copyright (C) 2002-2022 Wayne Davison
* Copyright (C) 2002-2023 Wayne Davison
*
* 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
@@ -840,9 +840,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
}
if (xflags & XMIT_MOD_NSEC)
#ifndef CAN_SET_NSEC
(void)read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
(void)read_varint(f);
#else
modtime_nsec = read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
modtime_nsec = read_varint(f);
else
modtime_nsec = 0;
#endif
@@ -861,19 +861,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
#endif
}
#endif
if (!(xflags & XMIT_SAME_MODE)) {
if (!(xflags & XMIT_SAME_MODE))
mode = from_wire_mode(read_int(f));
/* Reject modes whose type bits are not one of the standard
* file types; otherwise garbage mode values propagate through
* the file-type checks below unpredictably. */
if (!S_ISREG(mode) && !S_ISDIR(mode) && !S_ISLNK(mode)
&& !S_ISCHR(mode) && !S_ISBLK(mode)
&& !S_ISFIFO(mode) && !S_ISSOCK(mode)) {
rprintf(FERROR, "invalid file mode 0%o for %s [%s]\n",
(unsigned)mode, lastname, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
}
if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
atime = read_varlong(f, 4);
#if SIZEOF_TIME_T < SIZEOF_INT64
@@ -1401,7 +1390,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) {
if (st.st_size == 0) {
int fd = do_open_checklinks(fname);
int fd = do_open(fname, O_RDONLY, 0);
if (fd >= 0) {
st.st_size = get_device_size(fd, fname);
close(fd);
@@ -2378,7 +2367,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
}
dirlen = dir ? strlen(dir) : 0;
if (dirlen != lastdir_len || memcmp(lastdir, dir, dirlen) != 0) {
if (dirlen != lastdir_len || (dirlen && memcmp(lastdir, dir, dirlen) != 0)) {
if (!change_pathname(NULL, dir, -dirlen))
goto bad_path;
lastdir = pathname;
@@ -2595,19 +2584,6 @@ struct file_list *recv_file_list(int f, int dir_ndx)
init_hard_links();
#endif
if (inc_recurse && dir_ndx >= 0) {
if (dir_ndx >= dir_flist->used) {
rprintf(FERROR_XFER, "rsync: refusing invalid dir_ndx %u >= %u\n", dir_ndx, dir_flist->used);
exit_cleanup(RERR_PROTOCOL);
}
struct file_struct *file = dir_flist->files[dir_ndx];
if (file->flags & FLAG_GOT_DIR_FLIST) {
rprintf(FERROR_XFER, "rsync: refusing malicious duplicate flist for dir %d\n", dir_ndx);
exit_cleanup(RERR_PROTOCOL);
}
file->flags |= FLAG_GOT_DIR_FLIST;
}
flist = flist_new(0, "recv_file_list");
flist_expand(flist, FLIST_START_LARGE);

View File

@@ -4,7 +4,7 @@
* Copyright (C) 1996-2000 Andrew Tridgell
* Copyright (C) 1996 Paul Mackerras
* Copyright (C) 2002 Martin Pool <mbp@samba.org>
* Copyright (C) 2003-2022 Wayne Davison
* Copyright (C) 2003-2023 Wayne Davison
*
* 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
@@ -229,13 +229,11 @@ static int read_delay_line(char *buf, int *flags_p)
*flags_p = 0;
if (sscanf(bp, "%x ", &mode) != 1) {
goto invalid_data;
invalid_data:
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
return -1;
}
past_space = strchr(bp, ' ');
if (!past_space) {
goto invalid_data;
}
past_space++;
past_space = strchr(bp, ' ') + 1;
len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
read_pos = j + 1;
@@ -249,10 +247,6 @@ static int read_delay_line(char *buf, int *flags_p)
memcpy(buf, past_space, len);
return mode;
invalid_data:
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
return -1;
}
static void do_delayed_deletions(char *delbuf)
@@ -789,7 +783,7 @@ static int generate_and_send_sums(int fd, OFF_T len, int f_out, int f_copy)
for (i = 0; i < sum.count; i++) {
int32 n1 = (int32)MIN(len, (OFF_T)sum.blength);
char *map = map_ptr(mapbuf, offset, n1);
char sum2[SUM_LENGTH];
char sum2[MAX_DIGEST_LEN];
uint32 sum1;
len -= n1;
@@ -990,7 +984,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
if (find_exact_for_existing) {
if (alt_dest_type == LINK_DEST && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino)
return -1;
if (do_unlink_at(fname) < 0 && errno != ENOENT)
if (do_unlink(fname) < 0 && errno != ENOENT)
goto got_nothing_for_ya;
}
#ifdef SUPPORT_HARD_LINKS
@@ -1118,7 +1112,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
&& !IS_SPECIAL(file->mode) && !IS_DEVICE(file->mode)
#endif
&& !S_ISDIR(file->mode)) {
if (do_link_at(cmpbuf, fname) < 0) {
if (do_link(cmpbuf, fname) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to hard-link %s with %s",
cmpbuf, fname);
@@ -1321,7 +1315,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
}
if (relative_paths && !implied_dirs && file->mode != 0
&& do_stat_at(dn, &sx.st) < 0) {
&& do_stat(dn, &sx.st) < 0) {
if (dry_run)
goto parent_is_dry_missing;
if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
@@ -1433,7 +1427,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
&& (stype == FT_DIR
|| delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0))
goto cleanup; /* Any errors get reported later. */
if (do_mkdir_at(fname, (file->mode|added_perms) & 0700) == 0)
if (do_mkdir(fname, (file->mode|added_perms) & 0700) == 0)
file->flags |= FLAG_DIR_CREATED;
goto cleanup;
}
@@ -1475,10 +1469,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
itemize(fnamecmp, file, ndx, statret, &sx,
statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL);
}
if (real_ret != 0 && do_mkdir_at(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
if (!relative_paths || errno != ENOENT
|| make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
|| (do_mkdir_at(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
|| (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
rsyserr(FERROR_XFER, errno,
"recv_generator: mkdir %s failed",
full_fname(fname));
@@ -1505,7 +1499,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
#ifdef HAVE_CHMOD
if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
mode_t mode = file->mode | S_IRWXU;
if (do_chmod_at(fname, mode) < 0) {
if (do_chmod(fname, mode) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to modify permissions on %s",
full_fname(fname));
@@ -1804,7 +1798,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
if (write_devices && IS_DEVICE(sx.st.st_mode) && sx.st.st_size == 0) {
/* This early open into fd skips the regular open below. */
if ((fd = do_open_nofollow(fnamecmp, O_RDONLY)) >= 0)
if ((fd = do_open(fnamecmp, O_RDONLY, 0)) >= 0)
real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp);
}
@@ -1814,7 +1808,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
;
else if (quick_check_ok(FT_REG, fnamecmp, file, &sx.st)) {
if (partialptr) {
do_unlink_at(partialptr);
do_unlink(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT | maybe_ATTRS_ACCURATE_TIME);
@@ -1873,7 +1867,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
/* open the file */
if (fd < 0 && (fd = do_open_checklinks(fnamecmp)) < 0) {
if (fd < 0 && (fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) {
rsyserr(FERROR, errno, "failed to open %s, continuing",
full_fname(fnamecmp));
pretend_missing:
@@ -1902,7 +1896,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
back_file = NULL;
goto cleanup;
}
if ((f_copy = do_open_at(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
rsyserr(FERROR_XFER, errno, "open %s", full_fname(backupptr));
unmake_file(back_file);
back_file = NULL;
@@ -2022,7 +2016,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
if (slnk) {
#ifdef SUPPORT_LINKS
if (do_symlink_at(slnk, create_name) < 0) {
if (do_symlink(slnk, create_name) < 0) {
rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
full_fname(create_name), slnk);
return 0;
@@ -2038,7 +2032,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
return 0;
#endif
} else {
if (do_mknod_at(create_name, file->mode, rdev) < 0) {
if (do_mknod(create_name, file->mode, rdev) < 0) {
rsyserr(FERROR_XFER, errno, "mknod %s failed",
full_fname(create_name));
return 0;
@@ -2046,14 +2040,10 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
}
if (!skip_atomic) {
if (do_rename_at(tmpname, fname) < 0) {
char *full_tmpname = strdup(full_fname(tmpname));
if (full_tmpname == NULL)
out_of_memory("atomic_create");
if (do_rename(tmpname, fname) < 0) {
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed",
full_tmpname, full_fname(fname));
free(full_tmpname);
do_unlink_at(tmpname);
full_fname(tmpname), full_fname(fname));
do_unlink(tmpname);
return 0;
}
}
@@ -2117,7 +2107,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
continue;
fname = f_name(file, NULL);
if (fix_dir_perms)
do_chmod_at(fname, file->mode);
do_chmod(fname, file->mode);
if (need_retouch_dir_times) {
STRUCT_STAT st;
if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
@@ -2152,8 +2142,6 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
if (send_failed)
ndx = get_hlink_num();
flist = flist_for_ndx(ndx, "check_for_finished_files.1");
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = flist->files[ndx - flist->ndx_start];
assert(file->flags & FLAG_HLINKED);
if (send_failed)
@@ -2182,8 +2170,6 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
flist = cur_flist;
cur_flist = flist_for_ndx(ndx, "check_for_finished_files.2");
if (ndx < cur_flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = cur_flist->files[ndx - cur_flist->ndx_start];
if (solo_file)

View File

@@ -117,7 +117,7 @@ static void match_gnums(int32 *ndx_list, int ndx_count)
struct ht_int32_node *node = NULL;
int32 gnum, gnum_next;
qsort(ndx_list, ndx_count, sizeof ndx_list[0], (int (*)(const void*, const void*))hlink_compare_gnum);
qsort(ndx_list, ndx_count, sizeof ndx_list[0], (int (*)()) hlink_compare_gnum);
for (from = 0; from < ndx_count; from++) {
file = hlink_flist->sorted[ndx_list[from]];
@@ -454,7 +454,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
int hard_link_one(struct file_struct *file, const char *fname,
const char *oldname, int terse)
{
if (do_link_at(oldname, fname) < 0) {
if (do_link(oldname, fname) < 0) {
enum logcode code;
if (terse) {
if (!INFO_GTE(NAME, 1))

64
io.c
View File

@@ -55,7 +55,6 @@ extern int read_batch;
extern int compat_flags;
extern int protect_args;
extern int checksum_seed;
extern int xfer_sum_len;
extern int daemon_connection;
extern int protocol_version;
extern int remove_source_files;
@@ -1090,9 +1089,6 @@ static void got_flist_entry_status(enum festatus status, int ndx)
{
struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status");
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
if (remove_source_files) {
active_filecnt--;
active_bytecnt -= F_LENGTH(flist->files[ndx - flist->ndx_start]);
@@ -1161,8 +1157,8 @@ void set_io_timeout(int secs)
static void check_for_d_option_error(const char *msg)
{
static const char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
const char *colon;
static char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
char *colon;
int saw_d = 0;
if (*msg != 'r'
@@ -1868,45 +1864,6 @@ int64 read_varlong(int f, uchar min_bytes)
return u.x;
}
/* Read an int32 and verify lo <= v <= hi. On out-of-range, abort with a
* protocol error naming "what". The bound is co-located with the read so it
* cannot be forgotten by a downstream user. */
int32 read_int_bounded(int f, int32 lo, int32 hi, const char *what)
{
int32 v = read_int(f);
if (v < lo || v > hi) {
rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
what, (long)v, (long)lo, (long)hi, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
return v;
}
/* As read_int_bounded but for varint-encoded values. */
int32 read_varint_bounded(int f, int32 lo, int32 hi, const char *what)
{
int32 v = read_varint(f);
if (v < lo || v > hi) {
rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
what, (long)v, (long)lo, (long)hi, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
return v;
}
/* Read a varint that will be used as a size_t. Rejects negative values
* (which would wrap to ~SIZE_MAX) and values exceeding the supplied max. */
size_t read_varint_size(int f, size_t max, const char *what)
{
int32 v = read_varint(f);
if (v < 0 || (size_t)v > max) {
rprintf(FERROR, "wire size %s out of range: %ld > %lu [%s]\n",
what, (long)v, (unsigned long)max, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
return (size_t)v;
}
int64 read_longint(int f)
{
#if SIZEOF_INT64 >= 8
@@ -2013,21 +1970,6 @@ void read_sum_head(int f, struct sum_struct *sum)
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
/* Guard against integer overflow in downstream allocations sized by
* count*element_size. my_alloc uses divide-not-multiply so it is
* already wraparound-safe, but checking here gives a clearer error
* and also covers the (size_t)count * xfer_sum_len arithmetic that
* is performed *before* reaching my_alloc. */
if (xfer_sum_len > 0 && (size_t)sum->count > SIZE_MAX / (size_t)xfer_sum_len) {
rprintf(FERROR, "Invalid checksum count %ld (too large) [%s]\n",
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
if ((size_t)sum->count > SIZE_MAX / sizeof(struct sum_buf)) {
rprintf(FERROR, "Invalid checksum count %ld (sum_buf overflow) [%s]\n",
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
sum->blength = read_int(f);
if (sum->blength < 0 || sum->blength > max_blength) {
rprintf(FERROR, "Invalid block length %ld [%s]\n",
@@ -2035,7 +1977,7 @@ void read_sum_head(int f, struct sum_struct *sum)
exit_cleanup(RERR_PROTOCOL);
}
sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f);
if (sum->s2length < 0 || sum->s2length > xfer_sum_len) {
if (sum->s2length < 0 || sum->s2length > MAX_DIGEST_LEN) {
rprintf(FERROR, "Invalid checksum length %d [%s]\n",
sum->s2length, who_am_i());
exit_cleanup(RERR_PROTOCOL);

View File

@@ -1 +1 @@
#define LATEST_YEAR "2022"
#define LATEST_YEAR "2023"

View File

@@ -9,7 +9,7 @@ struct alloc_pool
size_t size; /* extent size */
size_t quantum; /* allocation quantum */
struct pool_extent *extents; /* top extent is "live" */
void (*bomb)(const char*, const char*, int); /* called if malloc fails */
void (*bomb)(); /* called if malloc fails */
int flags;
/* statistical data */

View File

@@ -178,7 +178,7 @@ static char *expand_vars(const char *str)
for (t = buf, f = str; bufsize && *f; ) {
if (*f == '%' && isUpper(f+1)) {
const char *percent = strchr(f+1, '%');
char *percent = strchr(f+1, '%');
if (percent && percent - f < bufsize) {
char *val;
strlcpy(t, f+1, percent - f);

12
log.c
View File

@@ -456,17 +456,11 @@ void rsyserr(enum logcode code, int errcode, const char *format, ...)
char buf[BIGPATHBUFLEN];
size_t len;
/* snprintf returns the would-have-been length on truncation, so
* each cumulative call must be guarded; if not, sizeof buf - len
* can underflow when promoted to size_t and the next call writes
* past the buffer. */
len = snprintf(buf, sizeof buf, RSYNC_NAME ": [%s] ", who_am_i());
if (len < sizeof buf) {
va_start(ap, format);
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
va_end(ap);
}
va_start(ap, format);
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
va_end(ap);
if (len < sizeof buf) {
len += snprintf(buf + len, sizeof buf - len,

42
main.c
View File

@@ -89,6 +89,8 @@ extern int backup_dir_len;
extern int basis_dir_cnt;
extern int default_af_hint;
extern int stdout_format_has_i;
extern int trust_sender_filter;
extern int trust_sender_args;
extern struct stats stats;
extern char *stdout_format;
extern char *logfile_format;
@@ -237,11 +239,11 @@ void write_del_stats(int f)
void read_del_stats(int f)
{
stats.deleted_files = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_files");
stats.deleted_files += stats.deleted_dirs = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_dirs");
stats.deleted_files += stats.deleted_symlinks = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_symlinks");
stats.deleted_files += stats.deleted_devices = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_devices");
stats.deleted_files += stats.deleted_specials = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_specials");
stats.deleted_files = read_varint(f);
stats.deleted_files += stats.deleted_dirs = read_varint(f);
stats.deleted_files += stats.deleted_symlinks = read_varint(f);
stats.deleted_files += stats.deleted_devices = read_varint(f);
stats.deleted_files += stats.deleted_specials = read_varint(f);
}
static void become_copy_as_user()
@@ -392,18 +394,9 @@ static void output_itemized_counts(const char *prefix, int *counts)
counts[0] -= counts[1] + counts[2] + counts[3] + counts[4];
for (j = 0; j < 5; j++) {
if (counts[j]) {
/* snprintf can return more than its size arg
* on truncation; keep len <= sizeof buf - 2 so
* the closing ')' and trailing NUL always
* have room and the next iteration's
* sizeof buf - len - 2 cannot underflow. */
if (len >= (int)sizeof buf - 2)
break;
len += snprintf(buf+len, sizeof buf - len - 2,
"%s%s: %s",
pre, labels[j], comma_num(counts[j]));
if (len > (int)sizeof buf - 2)
len = (int)sizeof buf - 2;
pre = ", ";
}
}
@@ -1390,15 +1383,6 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
return MAX(exit_code, exit_code2);
}
static void dup_argv(char *argv[])
{
int i;
for (i = 0; argv[i]; i++)
argv[i] = strdup(argv[i]);
}
/* Start a client for either type of remote connection. Work out
* whether the arguments request a remote shell or rsyncd connection,
* and call the appropriate connection function, then run_client.
@@ -1414,10 +1398,6 @@ static int start_client(int argc, char *argv[])
int ret;
pid_t pid;
/* Don't clobber argv[] so that ps(1) can still show the right
* command line. */
dup_argv(argv);
if (!read_batch) { /* for read_batch, NO source is specified */
char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port);
if (path) { /* source is remote */
@@ -1493,8 +1473,10 @@ static int start_client(int argc, char *argv[])
}
/* A local transfer doesn't unbackslash anything, so leave the args alone. */
if (local_server)
if (local_server) {
old_style_args = 2;
trust_sender_args = trust_sender_filter = 1;
}
if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */
*remote_argv = ".";
@@ -1577,10 +1559,6 @@ static int start_client(int argc, char *argv[])
shell_user = shell_machine;
shell_machine = p+1;
}
if (*shell_machine == '-') {
rprintf(FERROR, "Invalid remote host: hostnames may not start with '-'.\n");
exit_cleanup(RERR_SYNTAX);
}
}
if (DEBUG_GTE(CMD, 2)) {

15
match.c
View File

@@ -3,7 +3,7 @@
*
* Copyright (C) 1996 Andrew Tridgell
* Copyright (C) 1996 Paul Mackerras
* Copyright (C) 2003-2022 Wayne Davison
* Copyright (C) 2003-2023 Wayne Davison
*
* 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
@@ -142,14 +142,11 @@ static void hash_search(int f,struct sum_struct *s,
{
OFF_T offset, aligned_offset, end;
int32 k, want_i, aligned_i, backup;
char sum2[SUM_LENGTH];
char sum2[MAX_DIGEST_LEN];
uint32 s1, s2, sum;
int more;
schar *map;
// prevent possible memory leaks
memset(sum2, 0, sizeof sum2);
/* want_i is used to encourage adjacent matches, allowing the RLL
* coding of the output to work more efficiently. */
want_i = 0;
@@ -235,7 +232,7 @@ static void hash_search(int f,struct sum_struct *s,
done_csum2 = 1;
}
if (memcmp(sum2, sum2_at(s, i), s->s2length) != 0) {
if (memcmp(sum2,s->sums[i].sum2,s->s2length) != 0) {
false_alarms++;
continue;
}
@@ -255,7 +252,7 @@ static void hash_search(int f,struct sum_struct *s,
if (i != aligned_i) {
if (sum != s->sums[aligned_i].sum1
|| l != s->sums[aligned_i].len
|| memcmp(sum2, sum2_at(s, aligned_i), s->s2length) != 0)
|| memcmp(sum2, s->sums[aligned_i].sum2, s->s2length) != 0)
goto check_want_i;
i = aligned_i;
}
@@ -274,7 +271,7 @@ static void hash_search(int f,struct sum_struct *s,
if (sum != s->sums[i].sum1)
goto check_want_i;
get_checksum2((char *)map, l, sum2);
if (memcmp(sum2, sum2_at(s, i), s->s2length) != 0)
if (memcmp(sum2, s->sums[i].sum2, s->s2length) != 0)
goto check_want_i;
/* OK, we have a re-alignment match. Bump the offset
* forward to the new match point. */
@@ -293,7 +290,7 @@ static void hash_search(int f,struct sum_struct *s,
&& (!updating_basis_file || s->sums[want_i].offset >= offset
|| s->sums[want_i].flags & SUMFLG_SAME_OFFSET)
&& sum == s->sums[want_i].sum1
&& memcmp(sum2, sum2_at(s, want_i), s->s2length) == 0) {
&& memcmp(sum2, s->sums[want_i].sum2, s->s2length) == 0) {
/* we've found an adjacent match - the RLL coder
* will be happy */
i = want_i;

View File

@@ -8,6 +8,7 @@ fi
inname="$1"
srcdir=`dirname "$0"`
flagfile="$srcdir/.md2man-works"
force_flagfile="$srcdir/.md2man-force"
if [ ! -f "$flagfile" ]; then
# We test our smallest manpage just to see if the python setup works.
@@ -32,4 +33,10 @@ if [ ! -f "$flagfile" ]; then
fi
fi
"$srcdir/md-convert" "$srcdir/$inname"
if [ -f "$force_flagfile" ]; then
opt='--force-link-text'
else
opt=''
fi
"$srcdir/md-convert" $opt "$srcdir/$inname"

View File

@@ -276,7 +276,10 @@ class TransformHtml(HTMLParser):
bad_hashtags = set(),
latest_targets = [ ],
opt_prefix = 'opt',
a_href = None,
a_href_external = False,
a_txt_start = None,
after_a_tag = False,
target_suf = '',
)
@@ -315,6 +318,13 @@ class TransformHtml(HTMLParser):
for bad in st.referenced_hashtags - st.created_hashtags:
warn('Unknown hashtag link in', self.fn + ':', '#' + bad)
def handle_UE(self):
st = self.state
if st.txt.startswith(('.', ',', '!', '?', ';', ':')):
st.man_out[-1] = ".UE " + st.txt[0] + "\n"
st.txt = st.txt[1:]
st.after_a_tag = False
def handle_starttag(self, tag, attrs_list):
st = self.state
if args.debug:
@@ -387,13 +397,20 @@ class TransformHtml(HTMLParser):
for var, val in attrs_list:
if var == 'href':
if val.startswith(('https://', 'http://', 'mailto:', 'ftp:')):
pass # nothing to check
if st.after_a_tag:
self.handle_UE()
st.man_out.append(manify(st.txt.strip()) + "\n")
st.man_out.append(".UR " + val + "\n")
st.txt = ''
st.a_href = val
st.a_href_external = True
elif '#' in val:
pg, tgt = val.split('#', 1)
if pg and pg not in VALID_PAGES or '#' in tgt:
st.bad_hashtags.add(val)
elif tgt in ('', 'opt', 'dopt'):
st.a_href = val
st.a_href_external = False
elif pg == '':
st.referenced_hashtags.add(tgt)
if tgt in st.latest_targets:
@@ -409,6 +426,8 @@ class TransformHtml(HTMLParser):
st = self.state
if args.debug:
self.output_debug('END', (tag,))
if st.after_a_tag:
self.handle_UE()
if tag in CONSUMES_TXT or st.dt_from == tag:
txt = st.txt.strip()
st.txt = ''
@@ -473,7 +492,15 @@ class TransformHtml(HTMLParser):
elif tag == 'hr':
return
elif tag == 'a':
if st.a_href:
if st.a_href_external:
st.txt = st.txt.strip()
if args.force_link_text or st.a_href != st.txt:
st.man_out.append(manify(st.txt) + "\n")
st.man_out.append(".UE\n") # This might get replaced with a punctuation version in handle_UE()
st.after_a_tag = True
st.a_href_external = False
st.txt = ''
elif st.a_href:
atxt = st.txt[st.a_txt_start:]
find = 'href="' + st.a_href + '"'
for j in range(len(st.html_out)-1, 0, -1):
@@ -612,6 +639,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Convert markdown into html and (optionally) nroff. Each input filename must have a .md suffix, which is changed to .html for the output filename. If the input filename ends with .num.md (e.g. foo.1.md) then a nroff file is also output with the input filename's .md suffix removed (e.g. foo.1).", add_help=False)
parser.add_argument('--test', action='store_true', help="Just test the parsing without outputting any files.")
parser.add_argument('--dest', metavar='DIR', help="Create files in DIR instead of the current directory.")
parser.add_argument('--force-link-text', action='store_true', help="Don't remove the link text if it matches the link href. Useful when nroff doesn't understand .UR and .UE.")
parser.add_argument('--debug', '-D', action='count', default=0, help='Output copious info on the html parsing. Repeat for even more.')
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
parser.add_argument("mdfiles", metavar='FILE.md', nargs='+', help="One or more .md files to convert.")

183
options.c
View File

@@ -3,7 +3,7 @@
*
* Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
* Copyright (C) 2000, 2001, 2002 Martin Pool <mbp@samba.org>
* Copyright (C) 2002-2022 Wayne Davison
* Copyright (C) 2002-2023 Wayne Davison
*
* 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
@@ -113,20 +113,11 @@ int mkpath_dest_arg = 0;
int allow_inc_recurse = 1;
int xfer_dirs = -1;
int am_daemon = 0;
/* Set after a successful per-module chroot ("use chroot = yes") in
* clientserver.c. NOT set for the daemon-level "daemon chroot = /X"
* chroot: that confines path resolution to /X, but module paths
* /X/modA, /X/modB, etc. are not chroot boundaries, so the per-module
* symlink-race defenses (secure_relative_open() / do_*_at() in
* syscall.c, gated by `am_daemon && !am_chrooted`) must still fire
* even when the daemon is inside a daemon chroot. */
int am_chrooted = 0;
int connect_timeout = 0;
int keep_partial = 0;
int safe_symlinks = 0;
int copy_unsafe_links = 0;
int munge_symlinks = 0;
int use_secure_symlinks = 0;
int size_only = 0;
int daemon_bwlimit = 0;
int bwlimit = 0;
@@ -209,6 +200,7 @@ int remote_option_cnt = 0;
const char **remote_options = NULL;
const char *checksum_choice = NULL;
const char *compress_choice = NULL;
static const char *empty_argv[1];
int quiet = 0;
int output_motd = 1;
@@ -1164,7 +1156,7 @@ static time_t parse_time(const char *arg)
{
const char *cp;
time_t val, now = time(NULL);
struct tm t, tmp, *today = localtime_r(&now, &tmp);
struct tm t, *today = localtime(&now);
int in_date, old_mday, n;
memset(&t, 0, sizeof t);
@@ -1356,7 +1348,7 @@ char *alt_dest_opt(int type)
**/
int parse_arguments(int *argc_p, const char ***argv_p)
{
static poptContext pc;
poptContext pc;
const char *arg, **argv = *argv_p;
int argc = *argc_p;
int opt, want_dest_type;
@@ -1376,15 +1368,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
/* TODO: Call poptReadDefaultConfig; handle errors. */
/* The context leaks in case of an error, but if there's a
* problem we always exit anyhow. */
if (pc)
poptFreeContext(pc);
pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
if (pc == NULL) {
strlcpy(err_buf, "poptGetContext returned NULL\n", sizeof err_buf);
return 0;
}
if (!am_server) {
poptReadDefaultConfig(pc, 0);
popt_unalias(pc, "--daemon");
@@ -1426,7 +1410,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
strlcpy(err_buf,
"Attempt to hack rsync thwarted!\n",
sizeof err_buf);
return 0;
goto cleanup;
}
#ifdef ICONV_OPTION
iconv_opt = NULL;
@@ -1472,7 +1456,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
snprintf(err_buf, sizeof err_buf,
"the --temp-dir path is WAY too long.\n");
return 0;
goto cleanup;
}
if (!daemon_opt) {
@@ -1482,8 +1466,16 @@ int parse_arguments(int *argc_p, const char ***argv_p)
exit_cleanup(RERR_SYNTAX);
}
*argv_p = argv = poptGetArgs(pc);
*argc_p = argc = count_args(argv);
argv = poptGetArgs(pc);
argc = count_args(argv);
if (!argc) {
*argv_p = empty_argv;
*argc_p = 0;
} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
out_of_memory("parse_arguments");
argv = *argv_p;
poptFreeContext(pc);
am_starting_up = 0;
daemon_opt = 0;
am_daemon = 1;
@@ -1538,7 +1530,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
case 'a':
if (refused_archive_part) {
create_refuse_error(refused_archive_part);
return 0;
goto cleanup;
}
if (!recurse) /* preserve recurse == 2 */
recurse = 1;
@@ -1608,7 +1600,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
case 'P':
if (refused_partial || refused_progress) {
create_refuse_error(refused_partial ? refused_partial : refused_progress);
return 0;
goto cleanup;
}
do_progress = 1;
keep_partial = 1;
@@ -1643,7 +1635,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (*arg != '-') {
snprintf(err_buf, sizeof err_buf,
"Remote option must start with a dash: %s\n", arg);
return 0;
goto cleanup;
}
if (remote_option_cnt+2 >= remote_option_alloc) {
remote_option_alloc += 16;
@@ -1685,27 +1677,27 @@ int parse_arguments(int *argc_p, const char ***argv_p)
ssize_t size;
arg = poptGetOptArg(pc);
if ((size = parse_size_arg(arg, 'b', "block-size", 0, max_blength, False)) < 0)
return 0;
goto cleanup;
block_size = (int32)size;
break;
}
case OPT_MAX_SIZE:
if ((max_size = parse_size_arg(max_size_arg, 'b', "max-size", 0, -1, False)) < 0)
return 0;
goto cleanup;
max_size_arg = strdup(do_big_num(max_size, 0, NULL));
break;
case OPT_MIN_SIZE:
if ((min_size = parse_size_arg(min_size_arg, 'b', "min-size", 0, -1, False)) < 0)
return 0;
goto cleanup;
min_size_arg = strdup(do_big_num(min_size, 0, NULL));
break;
case OPT_BWLIMIT: {
ssize_t size = parse_size_arg(bwlimit_arg, 'K', "bwlimit", 512, -1, True);
if (size < 0)
return 0;
goto cleanup;
bwlimit_arg = strdup(do_big_num(size, 0, NULL));
bwlimit = (size + 512) / 1024;
break;
@@ -1734,7 +1726,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf, sizeof err_buf,
"ERROR: the %s option conflicts with the %s option\n",
alt_dest_opt(want_dest_type), alt_dest_opt(0));
return 0;
goto cleanup;
}
alt_dest_type = want_dest_type;
@@ -1742,7 +1734,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf, sizeof err_buf,
"ERROR: at most %d %s args may be specified\n",
MAX_BASIS_DIRS, alt_dest_opt(0));
return 0;
goto cleanup;
}
/* We defer sanitizing this arg until we know what
* our destination directory is going to be. */
@@ -1755,7 +1747,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf, sizeof err_buf,
"Invalid argument passed to --chmod (%s)\n",
arg);
return 0;
goto cleanup;
}
break;
@@ -1774,11 +1766,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (usermap_via_chown) {
snprintf(err_buf, sizeof err_buf,
"--usermap conflicts with prior --chown.\n");
return 0;
goto cleanup;
}
snprintf(err_buf, sizeof err_buf,
"You can only specify --usermap once.\n");
return 0;
goto cleanup;
}
usermap = (char *)poptGetOptArg(pc);
usermap_via_chown = False;
@@ -1790,11 +1782,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (groupmap_via_chown) {
snprintf(err_buf, sizeof err_buf,
"--groupmap conflicts with prior --chown.\n");
return 0;
goto cleanup;
}
snprintf(err_buf, sizeof err_buf,
"You can only specify --groupmap once.\n");
return 0;
goto cleanup;
}
groupmap = (char *)poptGetOptArg(pc);
groupmap_via_chown = False;
@@ -1813,11 +1805,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (!usermap_via_chown) {
snprintf(err_buf, sizeof err_buf,
"--chown conflicts with prior --usermap.\n");
return 0;
goto cleanup;
}
snprintf(err_buf, sizeof err_buf,
"You can only specify a user-affecting --chown once.\n");
return 0;
goto cleanup;
}
if (asprintf(&usermap, "*:%.*s", len, chown) < 0)
out_of_memory("parse_arguments");
@@ -1829,11 +1821,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (!groupmap_via_chown) {
snprintf(err_buf, sizeof err_buf,
"--chown conflicts with prior --groupmap.\n");
return 0;
goto cleanup;
}
snprintf(err_buf, sizeof err_buf,
"You can only specify a group-affecting --chown once.\n");
return 0;
goto cleanup;
}
if (asprintf(&groupmap, "*:%s", arg) < 0)
out_of_memory("parse_arguments");
@@ -1861,7 +1853,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf,sizeof(err_buf),
"ACLs are not supported on this %s\n",
am_server ? "server" : "client");
return 0;
goto cleanup;
#endif
case 'X':
@@ -1872,7 +1864,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf,sizeof(err_buf),
"extended attributes are not supported on this %s\n",
am_server ? "server" : "client");
return 0;
goto cleanup;
#endif
case OPT_STOP_AFTER: {
@@ -1881,7 +1873,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
stop_at_utime = time(NULL);
if ((val = atol(arg) * 60) <= 0 || LONG_MAX - val < stop_at_utime || (long)(time_t)val != val) {
snprintf(err_buf, sizeof err_buf, "invalid --stop-after value: %s\n", arg);
return 0;
goto cleanup;
}
stop_at_utime += val;
break;
@@ -1892,11 +1884,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
arg = poptGetOptArg(pc);
if ((stop_at_utime = parse_time(arg)) == (time_t)-1) {
snprintf(err_buf, sizeof err_buf, "invalid --stop-at format: %s\n", arg);
return 0;
goto cleanup;
}
if (stop_at_utime <= time(NULL)) {
snprintf(err_buf, sizeof err_buf, "--stop-at time is not in the future: %s\n", arg);
return 0;
goto cleanup;
}
break;
#endif
@@ -1914,7 +1906,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
else {
snprintf(err_buf, sizeof err_buf,
"--stderr mode \"%s\" is not one of errors, all, or client\n", arg);
return 0;
goto cleanup;
}
saw_stderr_opt = 1;
break;
@@ -1925,13 +1917,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
* turned this option off. */
if (opt >= OPT_REFUSED_BASE) {
create_refuse_error(opt);
return 0;
goto cleanup;
}
snprintf(err_buf, sizeof err_buf, "%s%s: %s\n",
am_server ? "on remote machine: " : "",
poptBadOption(pc, POPT_BADOPTION_NOALIAS),
poptStrerror(opt));
return 0;
goto cleanup;
}
}
@@ -1951,7 +1943,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (max_alloc_arg) {
ssize_t size = parse_size_arg(max_alloc_arg, 'B', "max-alloc", 1024*1024, -1, True);
if (size < 0)
return 0;
goto cleanup;
max_alloc = size;
}
@@ -1965,7 +1957,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (protect_args > 0) {
snprintf(err_buf, sizeof err_buf,
"--secluded-args conflicts with --old-args.\n");
return 0;
goto cleanup;
}
protect_args = 0;
}
@@ -2010,7 +2002,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
do_compression = CPRES_AUTO;
if (do_compression && refused_compress) {
create_refuse_error(refused_compress);
return 0;
goto cleanup;
}
}
@@ -2035,7 +2027,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
default:
snprintf(err_buf, sizeof err_buf,
"Invalid --outbuf setting -- specify N, L, or B.\n");
return 0;
goto cleanup;
}
setvbuf(stdout, (char *)NULL, mode, 0);
}
@@ -2063,7 +2055,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
}
if (refused_no_iconv && !iconv_opt) {
create_refuse_error(refused_no_iconv);
return 0;
goto cleanup;
}
#endif
@@ -2074,18 +2066,30 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (orig_protect_args == 2 && am_server)
protect_args = orig_protect_args;
if (protect_args == 1 && am_server)
if (protect_args == 1 && am_server) {
poptFreeContext(pc);
return 1;
}
*argv_p = argv = poptGetArgs(pc);
*argc_p = argc = count_args(argv);
/* Because popt 1.19 has started to free the returned args data, we now
* make a copy of the array and then do an immediate cleanup. */
argv = poptGetArgs(pc);
argc = count_args(argv);
if (!argc) {
*argv_p = empty_argv;
*argc_p = 0;
} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
out_of_memory("parse_arguments");
argv = *argv_p;
poptFreeContext(pc);
pc = NULL;
#ifndef SUPPORT_LINKS
if (preserve_links && !am_sender) {
snprintf(err_buf, sizeof err_buf,
"symlinks are not supported on this %s\n",
am_server ? "server" : "client");
return 0;
goto cleanup;
}
#endif
@@ -2094,7 +2098,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf, sizeof err_buf,
"hard links are not supported on this %s\n",
am_server ? "server" : "client");
return 0;
goto cleanup;
}
#endif
@@ -2102,20 +2106,20 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (am_root < 0 && preserve_xattrs > 1) {
snprintf(err_buf, sizeof err_buf,
"--fake-super conflicts with -XX\n");
return 0;
goto cleanup;
}
#else
if (am_root < 0) {
snprintf(err_buf, sizeof err_buf,
"--fake-super requires an rsync with extended attributes enabled\n");
return 0;
goto cleanup;
}
#endif
if (write_batch && read_batch) {
snprintf(err_buf, sizeof err_buf,
"--write-batch and --read-batch can not be used together\n");
return 0;
goto cleanup;
}
if (write_batch > 0 || read_batch) {
if (am_server) {
@@ -2134,25 +2138,25 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (read_batch && files_from) {
snprintf(err_buf, sizeof err_buf,
"--read-batch cannot be used with --files-from\n");
return 0;
goto cleanup;
}
if (read_batch && remove_source_files) {
snprintf(err_buf, sizeof err_buf,
"--read-batch cannot be used with --remove-%s-files\n",
remove_source_files == 1 ? "source" : "sent");
return 0;
goto cleanup;
}
if (batch_name && strlen(batch_name) > MAX_BATCH_NAME_LEN) {
snprintf(err_buf, sizeof err_buf,
"the batch-file name must be %d characters or less.\n",
MAX_BATCH_NAME_LEN);
return 0;
goto cleanup;
}
if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
snprintf(err_buf, sizeof err_buf,
"the --temp-dir path is WAY too long.\n");
return 0;
goto cleanup;
}
if (max_delete < 0 && max_delete != INT_MIN) {
@@ -2186,7 +2190,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (delete_before + !!delete_during + delete_after > 1) {
snprintf(err_buf, sizeof err_buf,
"You may not combine multiple --delete-WHEN options.\n");
return 0;
goto cleanup;
}
if (delete_before || delete_during || delete_after)
delete_mode = 1;
@@ -2197,7 +2201,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
delete_during = 1;
else {
create_refuse_error(refused_delete_before);
return 0;
goto cleanup;
}
} else if (refused_delete_during)
delete_before = 1;
@@ -2206,14 +2210,14 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (!xfer_dirs && delete_mode) {
snprintf(err_buf, sizeof err_buf,
"--delete does not work without --recursive (-r) or --dirs (-d).\n");
return 0;
goto cleanup;
}
if (missing_args == 3) /* simplify if both options were specified */
missing_args = 2;
if (refused_delete && (delete_mode || missing_args == 2)) {
create_refuse_error(refused_delete);
return 0;
goto cleanup;
}
if (remove_source_files) {
@@ -2222,7 +2226,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
* options. */
if (refused_delete && am_sender) {
create_refuse_error(refused_delete);
return 0;
goto cleanup;
}
need_messages_from_generator = 1;
}
@@ -2276,7 +2280,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf, sizeof err_buf,
"--suffix cannot contain slashes: %s\n",
backup_suffix);
return 0;
goto cleanup;
}
if (backup_dir) {
size_t len;
@@ -2289,7 +2293,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (len > sizeof backup_dir_buf - 128) {
snprintf(err_buf, sizeof err_buf,
"the --backup-dir path is WAY too long.\n");
return 0;
goto cleanup;
}
backup_dir_len = (int)len;
if (!backup_dir_len) {
@@ -2308,7 +2312,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
"--suffix cannot be empty %s\n", backup_dir_len < 0
? "when --backup-dir is the same as the dest dir"
: "without a --backup-dir");
return 0;
goto cleanup;
} else if (make_backups && delete_mode && !delete_excluded && !am_server) {
snprintf(backup_dir_buf, sizeof backup_dir_buf,
"P *%s", backup_suffix);
@@ -2336,7 +2340,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (do_progress && !am_server) {
if (!log_before_transfer && INFO_EQ(NAME, 0))
parse_output_words(info_words, info_levels, "name", DEFAULT_PRIORITY);
parse_output_words(info_words, info_levels, "flist2,progress", DEFAULT_PRIORITY);
parse_output_words(info_words, info_levels, "FLIST2,PROGRESS", DEFAULT_PRIORITY);
}
if (dry_run)
@@ -2377,11 +2381,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (whole_file > 0) {
snprintf(err_buf, sizeof err_buf,
"--append cannot be used with --whole-file\n");
return 0;
goto cleanup;
}
if (refused_inplace) {
create_refuse_error(refused_inplace);
return 0;
goto cleanup;
}
inplace = 1;
}
@@ -2389,7 +2393,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (write_devices) {
if (refused_inplace) {
create_refuse_error(refused_inplace);
return 0;
goto cleanup;
}
inplace = 1;
}
@@ -2404,13 +2408,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
"--%s cannot be used with --%s\n",
append_mode ? "append" : "inplace",
delay_updates ? "delay-updates" : "partial-dir");
return 0;
goto cleanup;
}
/* --inplace implies --partial for refusal purposes, but we
* clear the keep_partial flag for internal logic purposes. */
if (refused_partial) {
create_refuse_error(refused_partial);
return 0;
goto cleanup;
}
keep_partial = 0;
#else
@@ -2418,7 +2422,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
"--%s is not supported on this %s\n",
append_mode ? "append" : "inplace",
am_server ? "server" : "client");
return 0;
goto cleanup;
#endif
} else {
if (keep_partial && !partial_dir && !am_server) {
@@ -2432,7 +2436,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
partial_dir = NULL;
if (!partial_dir && refused_partial) {
create_refuse_error(refused_partial);
return 0;
goto cleanup;
}
keep_partial = 1;
}
@@ -2453,14 +2457,14 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (am_server) {
snprintf(err_buf, sizeof err_buf,
"The --files-from sent to the server cannot specify a host.\n");
return 0;
goto cleanup;
}
files_from = p;
filesfrom_host = h;
if (strcmp(files_from, "-") == 0) {
snprintf(err_buf, sizeof err_buf,
"Invalid --files-from remote filename\n");
return 0;
goto cleanup;
}
} else {
if (sanitize_paths)
@@ -2479,7 +2483,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
snprintf(err_buf, sizeof err_buf,
"failed to open files-from file %s: %s\n",
files_from, strerror(errno));
return 0;
goto cleanup;
}
}
}
@@ -2496,6 +2500,9 @@ int parse_arguments(int *argc_p, const char ***argv_p)
options_rejected:
snprintf(err_buf, sizeof err_buf,
"Your options have been rejected by the server.\n");
cleanup:
if (pc)
poptFreeContext(pc);
return 0;
}
@@ -2511,7 +2518,7 @@ static char SPLIT_ARG_WHEN_OLD[1];
**/
char *safe_arg(const char *opt, const char *arg)
{
#define SHELL_CHARS "!#$&;|<>(){}\"' \t\\"
#define SHELL_CHARS "!#$&;|<>(){}\"'` \t\\"
#define WILD_CHARS "*?[]" /* We don't allow remote brace expansion */
BOOL is_filename_arg = !opt;
char *escapes = is_filename_arg ? SHELL_CHARS : WILD_CHARS SHELL_CHARS;
@@ -2523,7 +2530,7 @@ char *safe_arg(const char *opt, const char *arg)
char *ret;
if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) {
const char *f;
if (!trust_sender_args && *arg == '~'
if (*arg == '~' && is_filename_arg && !am_sender && !trust_sender_args
&& ((relative_paths && !strstr(arg, "/./"))
|| !strchr(arg, '/'))) {
extras++;

View File

@@ -1,9 +1,9 @@
Summary: A fast, versatile, remote (and local) file-copying tool
Name: rsync
Version: 3.2.7
%define fullversion %{version}
Release: 1
%define srcdir src
Version: 3.3.0
%define fullversion %{version}pre1
Release: 0.1.pre1
%define srcdir src-previews
Group: Applications/Internet
License: GPL
Source0: https://rsync.samba.org/ftp/rsync/%{srcdir}/rsync-%{fullversion}.tar.gz
@@ -79,8 +79,8 @@ rm -rf $RPM_BUILD_ROOT
%dir /etc/rsync-ssl/certs
%changelog
* Thu Oct 20 2022 Wayne Davison <wayne@opencoder.net>
Released 3.2.7.
* Sat Apr 29 2023 Wayne Davison <wayne@opencoder.net>
Released 3.3.0pre1.
* Fri Mar 21 2008 Wayne Davison <wayne@opencoder.net>
Added installation of /etc/xinetd.d/rsync file and some commented-out

View File

@@ -36,15 +36,9 @@ int poptDupArgv(int argc, const char **argv,
dst += (argc + 1) * sizeof(*argv);
/*@-branchstate@*/
{
char * const end_buf = (char *)argv2 + nb;
for (i = 0; i < argc; i++) {
argv2[i] = dst;
/* nb is the TOTAL buffer size, not the remaining bytes; use the
* remaining bytes from dst to end_buf so glibc 2.39+ fortified
* strlcpy doesn't trip __bos() and abort. */
dst += strlcpy(dst, argv[i], end_buf - dst) + 1;
}
for (i = 0; i < argc; i++) {
argv2[i] = dst;
dst += strlcpy(dst, argv[i], nb) + 1;
}
/*@=branchstate@*/
argv2[argc] = NULL;

View File

@@ -3,7 +3,7 @@
*
* Copyright (C) 1996-2000 Andrew Tridgell
* Copyright (C) 1996 Paul Mackerras
* Copyright (C) 2003-2022 Wayne Davison
* Copyright (C) 2003-2023 Wayne Davison
*
* 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
@@ -66,11 +66,9 @@ extern char sender_file_sum[MAX_DIGEST_LEN];
extern struct file_list *cur_flist, *first_flist, *dir_flist;
extern filter_rule_list daemon_filter_list;
extern OFF_T preallocated_len;
extern int fuzzy_basis;
extern struct name_num_item *xfer_sum_nni;
extern int xfer_sum_len;
extern int use_secure_symlinks;
static struct bitbag *delayed_bits = NULL;
static int phase = 0, redoing = 0;
@@ -215,12 +213,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
* access to ensure that there is no race condition. They will be
* correctly updated after the right owner and group info is set.
* (Thanks to snabb@epipe.fi for pointing this out.) */
/* When use_secure_symlinks is on (non-chroot daemon with munge_symlinks),
* use secure_mkstemp to prevent symlink race attacks on parent directories. */
if (use_secure_symlinks)
fd = secure_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
else
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
#if 0
/* In most cases parent directories will already exist because their
@@ -318,12 +311,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
}
}
while (1) {
data = NULL;
i = recv_token(f_in, &data);
if (i == 0)
break;
while ((i = recv_token(f_in, &data)) != 0) {
if (INFO_GTE(PROGRESS, 1))
show_progress(offset, total_size);
@@ -331,10 +319,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH | MSK_ACTIVE_RECEIVER);
if (i > 0) {
if (!data) {
rprintf(FERROR, "Invalid literal token with no data [%s]\n", who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
if (DEBUG_GTE(DELTASUM, 3)) {
rprintf(FINFO,"data recv %d at %s\n",
i, big_num(offset));
@@ -352,11 +336,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
}
i = -(i+1);
if (i < 0 || i >= sum.count) {
rprintf(FERROR, "Invalid block index %d (count=%ld) [%s]\n",
i, (long)sum.count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
offset2 = i * (OFF_T)sum.blength;
len = sum.blength;
if (i == (int)sum.count-1 && sum.remainder != 0)
@@ -393,7 +372,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
if (fd != -1 && offset > 0) {
if (sparse_files > 0) {
if (sparse_end(fd, offset) != 0)
if (sparse_end(fd, offset, updating_basis_or_equiv) != 0)
goto report_write_error;
} else if (flush_write_file(fd) < 0) {
report_write_error:
@@ -456,7 +435,7 @@ static void handle_delayed_updates(char *local_name)
}
/* We don't use robust_rename() here because the
* partial-dir must be on the same drive. */
if (do_rename_at(partialptr, fname) < 0) {
if (do_rename(partialptr, fname) < 0) {
rsyserr(FERROR_XFER, errno,
"rename failed for %s (from %s)",
full_fname(fname), partialptr);
@@ -472,10 +451,7 @@ static void handle_delayed_updates(char *local_name)
static void no_batched_update(int ndx, BOOL is_redo)
{
struct file_list *flist = flist_for_ndx(ndx, "no_batched_update");
struct file_struct *file;
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = flist->files[ndx - flist->ndx_start];
struct file_struct *file = flist->files[ndx - flist->ndx_start];
rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n",
is_redo ? " resend of" : "", f_name(file, NULL));
@@ -575,8 +551,6 @@ int recv_files(int f_in, int f_out, char *local_name)
progress_init();
while (1) {
const char *basedir = NULL;
cleanup_disable();
/* This call also sets cur_flist. */
@@ -612,8 +586,6 @@ int recv_files(int f_in, int f_out, char *local_name)
if (ndx - cur_flist->ndx_start >= 0)
file = cur_flist->files[ndx - cur_flist->ndx_start];
else if (cur_flist->parent_ndx < 0)
exit_cleanup(RERR_PROTOCOL);
else
file = dir_flist->files[cur_flist->parent_ndx];
fname = local_name ? local_name : f_name(file, fbuf);
@@ -744,34 +716,28 @@ int recv_files(int f_in, int f_out, char *local_name)
fnamecmp = get_backup_name(fname);
break;
case FNAMECMP_FUZZY:
if (fuzzy_basis == 0) {
rprintf(FERROR_XFER, "rsync: refusing malicious fuzzy operation for %s\n", xname);
exit_cleanup(RERR_PROTOCOL);
}
if (file->dirname) {
basedir = file->dirname;
}
fnamecmp = xname;
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, file->dirname, xname);
fnamecmp = fnamecmpbuf;
} else
fnamecmp = xname;
break;
default:
if (fnamecmp_type > FNAMECMP_FUZZY && fnamecmp_type-FNAMECMP_FUZZY <= basis_dir_cnt) {
fnamecmp_type -= FNAMECMP_FUZZY + 1;
if (file->dirname) {
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], file->dirname);
basedir = fnamecmpbuf;
} else {
basedir = basis_dir[fnamecmp_type];
}
fnamecmp = xname;
stringjoin(fnamecmpbuf, sizeof fnamecmpbuf,
basis_dir[fnamecmp_type], "/", file->dirname, "/", xname, NULL);
} else
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], xname);
} else if (fnamecmp_type >= basis_dir_cnt) {
rprintf(FERROR,
"invalid basis_dir index: %d.\n",
fnamecmp_type);
exit_cleanup(RERR_PROTOCOL);
} else {
basedir = basis_dir[fnamecmp_type];
fnamecmp = fname;
}
} else
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], fname);
fnamecmp = fnamecmpbuf;
break;
}
if (!fnamecmp || (daemon_filter_list.head
@@ -794,31 +760,25 @@ int recv_files(int f_in, int f_out, char *local_name)
}
/* open the file */
fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
fd1 = do_open(fnamecmp, O_RDONLY, 0);
if (fd1 == -1 && protocol_version < 29) {
if (fnamecmp != fname) {
fnamecmp = fname;
fnamecmp_type = FNAMECMP_FNAME;
fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
fd1 = do_open(fnamecmp, O_RDONLY, 0);
}
if (fd1 == -1 && basis_dir[0]) {
/* pre-29 allowed only one alternate basis */
basedir = basis_dir[0];
fnamecmp = fname;
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
basis_dir[0], fname);
fnamecmp = fnamecmpbuf;
fnamecmp_type = FNAMECMP_BASIS_DIR_LOW;
fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
fd1 = do_open(fnamecmp, O_RDONLY, 0);
}
}
if (basedir) {
// for the following code we need the full
// path name as a single string
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basedir, fnamecmp);
fnamecmp = fnamecmpbuf;
}
one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR;
updating_basis_or_equiv = one_inplace
|| (inplace && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP));
@@ -879,21 +839,11 @@ int recv_files(int f_in, int f_out, char *local_name)
/* We now check to see if we are writing the file "inplace" */
if (inplace || one_inplace) {
fnametmp = one_inplace ? partialptr : fname;
/* When use_secure_symlinks is on (non-chroot daemon),
* use secure open to prevent symlink race attacks where an
* attacker could switch a directory to a symlink between
* path validation and file open. */
if (use_secure_symlinks)
fd2 = secure_relative_open(NULL, fnametmp, O_WRONLY|O_CREAT, 0600);
else
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
#ifdef linux
if (fd2 == -1 && errno == EACCES) {
/* Maybe the error was due to protected_regular setting? */
if (use_secure_symlinks)
fd2 = secure_relative_open(NULL, fname, O_WRONLY, 0600);
else
fd2 = do_open(fname, O_WRONLY, 0600);
fd2 = do_open(fname, O_WRONLY, 0600);
}
#endif
if (fd2 == -1) {
@@ -945,7 +895,7 @@ int recv_files(int f_in, int f_out, char *local_name)
recv_ok = -1;
else if (fnamecmp == partialptr) {
if (!one_inplace)
do_unlink_at(partialptr);
do_unlink(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
} else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
@@ -954,7 +904,7 @@ int recv_files(int f_in, int f_out, char *local_name)
"Unable to create partial-dir for %s -- discarding %s.\n",
local_name ? local_name : f_name(file, NULL),
recv_ok ? "completed file" : "partial file");
do_unlink_at(fnametmp);
do_unlink(fnametmp);
recv_ok = -1;
} else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL,
file, recv_ok, !partial_dir))
@@ -965,7 +915,7 @@ int recv_files(int f_in, int f_out, char *local_name)
} else
partialptr = NULL;
} else if (!one_inplace)
do_unlink_at(fnametmp);
do_unlink(fnametmp);
cleanup_disable();

View File

@@ -1636,7 +1636,9 @@ expand it.
0. `--crtimes`, `-N,`
This tells rsync to set the create times (newness) of the destination
files to the same value as the source files.
files to the same value as the source files. Your OS & filesystem must
support the setting of arbitrary creation (birth) times for this option
to be supported.
0. `--omit-dir-times`, `-O`

13
rsync.c
View File

@@ -437,10 +437,7 @@ int read_ndx_and_attrs(int f_in, int f_out, int *iflag_ptr, uchar *type_ptr, cha
*/
void free_sums(struct sum_struct *s)
{
if (s->sums) {
free(s->sums);
free(s->sum2_array);
}
if (s->sums) free(s->sums);
free(s);
}
@@ -547,7 +544,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
if (am_root >= 0) {
uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
if (do_lchown_at(fname, uid, gid) != 0) {
if (do_lchown(fname, uid, gid) != 0) {
/* We shouldn't have attempted to change uid
* or gid unless have the privilege. */
rsyserr(FERROR_XFER, errno, "%s %s failed",
@@ -657,7 +654,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
#ifdef HAVE_CHMOD
if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
int ret = am_root < 0 ? 0 : do_chmod_at(fname, new_mode);
int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
if (ret < 0) {
rsyserr(FERROR_XFER, errno,
"failed to set permissions on %s",
@@ -758,7 +755,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
full_fname(fnametmp), fname);
if (!partialptr || (ret == -2 && temp_copy_name)
|| robust_rename(fnametmp, partialptr, NULL, file->mode) < 0)
do_unlink_at(fnametmp);
do_unlink(fnametmp);
return 0;
}
if (ret == 0) {
@@ -774,7 +771,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
if (temp_copy_name) {
if (do_rename_at(fnametmp, fname) < 0) {
if (do_rename(fnametmp, fname) < 0) {
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"",
full_fname(fnametmp), fname);
return 0;

28
rsync.h
View File

@@ -84,7 +84,6 @@
#define FLAG_DUPLICATE (1<<4) /* sender */
#define FLAG_MISSING_DIR (1<<4) /* generator */
#define FLAG_HLINKED (1<<5) /* receiver/generator (checked on all types) */
#define FLAG_GOT_DIR_FLIST (1<<5)/* sender/receiver/generator - dir_flist only */
#define FLAG_HLINK_FIRST (1<<6) /* receiver/generator (w/FLAG_HLINKED) */
#define FLAG_IMPLIED_DIR (1<<6) /* sender/receiver/generator (dirs only) */
#define FLAG_HLINK_LAST (1<<7) /* receiver/generator */
@@ -163,29 +162,6 @@
/* For compatibility with older rsyncs */
#define OLD_MAX_BLOCK_SIZE ((int32)1 << 29)
/* Policy ceilings on attacker-controlled wire values. Picked well above any
* legitimate filesystem / protocol traffic but well below sizes that could
* cause integer overflow or DoS-grade allocations. See input_checking.txt.
*
* Note on MAX_WIRE_XATTR_DATALEN: xattr datum size is bounded only by the
* wire-format maximum (signed int32 varint, ~2GB). macOS resource forks
* are transferred as the com.apple.ResourceFork xattr and can legitimately
* be many GB; --max-alloc (default 1GB, configurable) is the real
* allocation cap. read_varint_size() still rejects negative values so a
* hostile peer cannot wrap to ~SIZE_MAX. */
#define MAX_WIRE_XATTR_COUNT 65536
#define MAX_WIRE_XATTR_NAMELEN 4096
#define MAX_WIRE_XATTR_DATALEN ((int32)0x7fffffff)
#define MAX_WIRE_ACL_COUNT 65536
#define MAX_WIRE_NSEC 999999999
/* MAX_WIRE_DEL_STAT is the per-category cap for read_del_stats() in main.c,
* which accumulates 5 wire-supplied counts into the int32 stats.deleted_files
* accumulator. Capped at 2^28 so 5 * 2^28 = 1.34 GB stays under INT32_MAX
* (2.15 GB) with margin -- a higher cap (e.g. 2^30) would let a hostile peer
* supplying 3+ max-sized counts overflow the accumulator, which is signed-int
* UB. 2^28 is still well above any plausible real transfer's deletion count. */
#define MAX_WIRE_DEL_STAT ((int32)1 << 28)
#define ROUND_UP_1024(siz) ((siz) & (1024-1) ? ((siz) | (1024-1)) + 1 : (siz))
#define IOERR_GENERAL (1<<0) /* For backward compatibility, this must == 1 */
@@ -982,12 +958,12 @@ struct sum_buf {
uint32 sum1; /**< simple checksum */
int32 chain; /**< next hash-table collision */
short flags; /**< flag bits */
char sum2[SUM_LENGTH]; /**< checksum */
};
struct sum_struct {
OFF_T flength; /**< total file length */
struct sum_buf *sums; /**< points to info for each chunk */
char *sum2_array; /**< checksums of length xfer_sum_len */
int32 count; /**< how many chunks */
int32 blength; /**< block_length */
int32 remainder; /**< flength % block_length */
@@ -1006,8 +982,6 @@ struct map_struct {
int status; /* first errno from read errors */
};
#define sum2_at(s, i) ((s)->sum2_array + ((size_t)(i) * xfer_sum_len))
#define NAME_IS_FILE (0) /* filter name as a file */
#define NAME_IS_DIR (1<<0) /* filter name as a dir */
#define NAME_IS_XATTR (1<<2) /* filter name as an xattr */

View File

@@ -74,25 +74,7 @@ reread the `rsyncd.conf` file. The file is re-read on each client connection.
## GLOBAL PARAMETERS
The first parameters in the file (before a [module] header) are the global
parameters. Rsync also allows for the use of a "[global]" module name to
indicate the start of one or more global-parameter sections (the name must be
lower case).
You may also include any module parameters in the global part of the config
file in which case the supplied value will override the default for that
parameter.
You may use references to environment variables in the values of parameters.
String parameters will have %VAR% references expanded as late as possible (when
the string is first used in the program), allowing for the use of variables
that rsync sets at connection time, such as RSYNC_USER_NAME. Non-string
parameters (such as true/false settings) are expanded when read from the config
file. If a variable does not exist in the environment, or if a sequence of
characters is not a valid reference (such as an un-paired percent sign), the
raw characters are passed through unchanged. This helps with backward
compatibility and safety (e.g. expanding a non-existent %VAR% to an empty
string in a path could result in a very unsafe path). The safest way to insert
a literal % into a value is to use %%.
parameters:
[comment]: # (An OL starting at 0 is converted into a DL by the parser.)
@@ -138,6 +120,22 @@ a literal % into a value is to use %%.
You can override the default backlog value when the daemon listens for
connections. It defaults to 5.
You may also include any [MODULE PARAMETERS](#) in the global part of the
config file, in which case the supplied value will override the default for
that parameter.
You may use references to environment variables in the values of parameters.
String parameters will have %VAR% references expanded as late as possible (when
the string is first used in the program), allowing for the use of variables
that rsync sets at connection time, such as RSYNC_USER_NAME. Non-string
parameters (such as true/false settings) are expanded when read from the config
file. If a variable does not exist in the environment, or if a sequence of
characters is not a valid reference (such as an un-paired percent sign), the
raw characters are passed through unchanged. This helps with backward
compatibility and safety (e.g. expanding a non-existent %VAR% to an empty
string in a path could result in a very unsafe path). The safest way to insert
a literal % into a value is to use %%.
## MODULE PARAMETERS
After the global parameters you should define a number of modules, each module
@@ -146,11 +144,17 @@ a module name in square brackets [module] followed by the parameters for that
module. The module name cannot contain a slash or a closing square bracket.
If the name contains whitespace, each internal sequence of whitespace will be
changed into a single space, while leading or trailing whitespace will be
discarded. Also, the name cannot be "global" as that exact name indicates that
global parameters follow (see above).
discarded.
As with GLOBAL PARAMETERS, you may use references to environment variables in
the values of parameters. See the GLOBAL PARAMETERS section for more details.
There is also a special module name of "[global]" that does not define a module
but instead switches back to the global settings context where default
parameters can be specified. Because each defined module gets its full set of
parameters as a combination of the default values that are set at that position
in the config file plus its own parameter list, the use of a "[global]" section
can help to maintain shared config values for multiple modules.
As with [GLOBAL PARAMETERS](#), you may use references to environment variables
in the values of parameters. See that section for details.
0. `comment`

View File

@@ -31,7 +31,6 @@ extern int log_before_transfer;
extern int stdout_format_has_i;
extern int logfile_format_has_i;
extern int want_xattr_optim;
extern int xfer_sum_len;
extern int csum_length;
extern int append_mode;
extern int copy_links;
@@ -48,8 +47,6 @@ extern int make_backups;
extern int inplace;
extern int inplace_partial;
extern int batch_fd;
extern int use_secure_symlinks;
extern char *module_dir;
extern int write_batch;
extern int file_old_total;
extern BOOL want_progress_now;
@@ -97,11 +94,10 @@ static struct sum_struct *receive_sums(int f)
return(s);
s->sums = new_array(struct sum_buf, s->count);
s->sum2_array = new_array(char, (size_t)s->count * xfer_sum_len);
for (i = 0; i < s->count; i++) {
s->sums[i].sum1 = read_int(f);
read_buf(f, sum2_at(s, i), s->s2length);
read_buf(f, s->sums[i].sum2, s->s2length);
s->sums[i].offset = offset;
s->sums[i].flags = 0;
@@ -140,8 +136,6 @@ void successful_send(int ndx)
return;
flist = flist_for_ndx(ndx, "successful_send");
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = flist->files[ndx - flist->ndx_start];
if (!change_pathname(file, NULL, 0))
return;
@@ -266,8 +260,6 @@ void send_files(int f_in, int f_out)
if (ndx - cur_flist->ndx_start >= 0)
file = cur_flist->files[ndx - cur_flist->ndx_start];
else if (cur_flist->parent_ndx < 0)
exit_cleanup(RERR_PROTOCOL);
else
file = dir_flist->files[cur_flist->parent_ndx];
if (F_PATHNAME(file)) {
@@ -356,25 +348,7 @@ void send_files(int f_in, int f_out)
exit_cleanup(RERR_PROTOCOL);
}
if (use_secure_symlinks) {
/* Open from module root to prevent TOCTOU race where
* change_pathname's chdir follows a directory symlink.
* Reconstruct the full path relative to module_dir
* from F_PATHNAME (path) and f_name (fname). */
char secure_path[MAXPATHLEN];
int slen = snprintf(secure_path, sizeof secure_path, "%s%s%s", path, slash, fname);
if (slen >= (int)sizeof secure_path) {
io_error |= IOERR_GENERAL;
rprintf(FERROR_XFER, "path too long: %s%s%s\n", path, slash, fname);
free_sums(s);
if (protocol_version >= 30)
send_msg_int(MSG_NO_SEND, ndx);
continue;
}
fd = secure_relative_open(module_dir, secure_path, O_RDONLY, 0);
} else {
fd = do_open_checklinks(fname);
}
fd = do_open(fname, O_RDONLY, 0);
if (fd == -1) {
if (errno == ENOENT) {
enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;

View File

@@ -347,7 +347,8 @@ __attribute__ ((target("avx2"))) MVSTATIC int32 get_checksum1_avx2_64(schar* buf
__m128i tmp = _mm_load_si128((__m128i*) mul_t1_buf);
__m256i mul_t1 = _mm256_cvtepu8_epi16(tmp);
__m256i mul_const = _mm256_broadcastd_epi32(_mm_cvtsi32_si128(4 | (3 << 8) | (2 << 16) | (1 << 24)));
__m256i mul_one = _mm256_set1_epi8(1);
__m256i mul_one;
mul_one = _mm256_abs_epi8(_mm256_cmpeq_epi16(mul_one,mul_one)); // set all vector elements to 1
for (; i < (len-64); i+=64) {
// Load ... 4*[int8*16]
@@ -547,118 +548,6 @@ int main() {
#pragma clang optimize on
#endif /* BENCHMARK_SIMD_CHECKSUM1 */
#ifdef TEST_SIMD_CHECKSUM1
static uint32 checksum_via_default(char *buf, int32 len)
{
uint32 s1 = 0, s2 = 0;
get_checksum1_default_1((schar*)buf, len, 0, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
static uint32 checksum_via_sse2(char *buf, int32 len)
{
int32 i;
uint32 s1 = 0, s2 = 0;
i = get_checksum1_sse2_32((schar*)buf, len, 0, &s1, &s2);
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
static uint32 checksum_via_ssse3(char *buf, int32 len)
{
int32 i;
uint32 s1 = 0, s2 = 0;
i = get_checksum1_ssse3_32((schar*)buf, len, 0, &s1, &s2);
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
static uint32 checksum_via_avx2(char *buf, int32 len)
{
int32 i;
uint32 s1 = 0, s2 = 0;
#ifdef USE_ROLL_ASM
i = get_checksum1_avx2_asm((schar*)buf, len, 0, &s1, &s2);
#else
i = get_checksum1_avx2_64((schar*)buf, len, 0, &s1, &s2);
#endif
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
int main()
{
static const int sizes[] = {1, 4, 31, 32, 33, 63, 64, 65, 128, 129, 256, 700, 1024, 4096, 65536};
int num_sizes = sizeof(sizes) / sizeof(sizes[0]);
int max_size = sizes[num_sizes - 1];
int failures = 0;
/* Allocate with extra bytes for unaligned test */
unsigned char *raw = (unsigned char *)malloc(max_size + 64 + 1);
if (!raw) {
fprintf(stderr, "malloc failed\n");
return 1;
}
/* Fill with deterministic data */
for (int i = 0; i < max_size + 64 + 1; i++)
raw[i] = (i + (i % 3) + (i % 11)) % 256;
/* Test with aligned buffer (64-byte aligned) */
unsigned char *aligned = raw + (64 - ((uintptr_t)raw % 64));
/* Test with unaligned buffer (+1 byte offset) */
unsigned char *unaligned = aligned + 1;
struct { const char *name; unsigned char *buf; } buffers[] = {
{"aligned", aligned},
{"unaligned", unaligned},
};
for (int b = 0; b < 2; b++) {
char *buf = (char *)buffers[b].buf;
const char *bname = buffers[b].name;
for (int s = 0; s < num_sizes; s++) {
int32 len = sizes[s];
uint32 ref = checksum_via_default(buf, len);
uint32 cs_sse2 = checksum_via_sse2(buf, len);
uint32 cs_ssse3 = checksum_via_ssse3(buf, len);
uint32 cs_avx2 = checksum_via_avx2(buf, len);
uint32 cs_auto = get_checksum1(buf, len);
if (cs_sse2 != ref) {
printf("FAIL %-9s size=%5d: SSE2=%08x ref=%08x\n", bname, len, cs_sse2, ref);
failures++;
}
if (cs_ssse3 != ref) {
printf("FAIL %-9s size=%5d: SSSE3=%08x ref=%08x\n", bname, len, cs_ssse3, ref);
failures++;
}
if (cs_avx2 != ref) {
printf("FAIL %-9s size=%5d: AVX2=%08x ref=%08x\n", bname, len, cs_avx2, ref);
failures++;
}
if (cs_auto != ref) {
printf("FAIL %-9s size=%5d: auto=%08x ref=%08x\n", bname, len, cs_auto, ref);
failures++;
}
}
}
free(raw);
if (failures) {
printf("%d checksum mismatches!\n", failures);
return 1;
}
printf("All SIMD checksum tests passed.\n");
return 0;
}
#endif /* TEST_SIMD_CHECKSUM1 */
#endif /* } USE_ROLL_SIMD */
#endif /* } __cplusplus */
#endif /* } __x86_64__ */

View File

@@ -47,23 +47,21 @@ static struct sigaction sigact;
static int sock_exec(const char *prog);
#define PROXY_BUF_SIZE 1024
/* Establish a proxy connection on an open socket to a web proxy by using the
* CONNECT method. If proxy_user and proxy_pass are not NULL, they are used to
* authenticate to the proxy using the "Basic" proxy-authorization protocol. */
static int establish_proxy_connection(int fd, char *host, int port, char *proxy_user, char *proxy_pass)
{
char *cp, buffer[PROXY_BUF_SIZE + 1];
char *authhdr, authbuf[PROXY_BUF_SIZE + 1];
char *cp, buffer[1024];
char *authhdr, authbuf[1024];
int len;
if (proxy_user && proxy_pass) {
stringjoin(buffer, PROXY_BUF_SIZE,
stringjoin(buffer, sizeof buffer,
proxy_user, ":", proxy_pass, NULL);
len = strlen(buffer);
if ((len*8 + 5) / 6 >= PROXY_BUF_SIZE - 3) {
if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) {
rprintf(FERROR,
"authentication information is too long\n");
return -1;
@@ -76,14 +74,14 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
authhdr = "";
}
len = snprintf(buffer, PROXY_BUF_SIZE, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
assert(len > 0 && len < PROXY_BUF_SIZE);
len = snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
assert(len > 0 && len < (int)sizeof buffer);
if (write(fd, buffer, len) != len) {
rsyserr(FERROR, errno, "failed to write to proxy");
return -1;
}
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE - 1]; cp++) {
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
if (read(fd, cp, 1) != 1) {
rsyserr(FERROR, errno, "failed to read from proxy");
return -1;
@@ -92,13 +90,11 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
break;
}
if (cp == &buffer[PROXY_BUF_SIZE - 1]) {
rprintf(FERROR, "proxy response line too long\n");
return -1;
}
*cp = '\0';
if (cp > buffer && cp[-1] == '\r')
cp[-1] = '\0';
if (*cp != '\n')
cp++;
*cp-- = '\0';
if (*cp == '\r')
*cp = '\0';
if (strncmp(buffer, "HTTP/", 5) != 0) {
rprintf(FERROR, "bad response from proxy -- %s\n",
buffer);
@@ -114,7 +110,7 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
}
/* throw away the rest of the HTTP header */
while (1) {
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE]; cp++) {
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
if (read(fd, cp, 1) != 1) {
rsyserr(FERROR, errno,
"failed to read from proxy");

View File

@@ -1,42 +1,63 @@
#!/usr/bin/env perl
#
#!/usr/bin/env python3
# This script finds all CVS/Entries files in the current directory and below
# and creates a local .cvsinclude file with non-inherited rules including each
# checked-in file. Then, use this option whenever using --cvs-exclude (-C):
#
# -f ': .cvsinclude'
# -f ': .cvsinclude'
#
# That ensures that all checked-in files/dirs are included in the transfer.
# (You could alternately put ": .cvsinclude" into an .rsync-filter file and
# use the -F option, which is easier to type.)
#
# The downside is that you need to remember to re-run cvs2includes whenever
# you add a new file to the project.
use strict;
# CVS gets an added or removed file. Maybe just run it before every copy.
open(FIND, 'find . -name CVS -type d |') or die $!;
while (<FIND>) {
chomp;
s#^\./##;
import os, argparse
my $entries = "$_/Entries";
s/CVS$/.cvsinclude/;
my $filter = $_;
INC_NAME = '.cvsinclude'
open(ENTRIES, $entries) or die "Unable to open $entries: $!\n";
my @includes;
while (<ENTRIES>) {
push(@includes, $1) if m#/(.+?)/#;
}
close ENTRIES;
if (@includes) {
open(FILTER, ">$filter") or die "Unable to write $filter: $!\n";
print FILTER map "+ /$_\n", @includes;
close FILTER;
print "Updated $filter\n";
} elsif (-f $filter) {
unlink($filter);
print "Removed $filter\n";
}
}
close FIND;
def main():
if args.dir:
os.chdir(args.dir)
cvs_includes = set()
for root, dirs, files in os.walk('.'):
if INC_NAME in files:
cvs_includes.add((root + '/' + INC_NAME)[2:])
if root.endswith('/CVS') and 'Entries' in files:
entries = root[2:] + '/Entries'
includes = [ ]
with open(entries) as fh:
for line in fh:
if line.startswith(('/', 'D/')):
includes.append(line.split('/', 2)[1])
if includes:
inc = root[2:-3] + INC_NAME
cvs_includes.discard(inc)
try:
with open(inc) as fh:
old_txt = fh.read()
except OSError:
old_txt = ''
txt = ''.join(f"+ /{x}\n" for x in includes)
if txt == old_txt:
print("Unchanged", inc)
else:
print("Updating", inc)
with open(inc, 'w') as fh:
fh.write(txt)
dirs.sort()
for inc in sorted(cvs_includes):
print("Removing", inc)
os.unlink(inc)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=f"Transform CVS/Entries into {INC_NAME} files.", add_help=False)
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
parser.add_argument("dir", nargs='?', help="The top CVS dir. Defaults to the current directory.")
args = parser.parse_args()
main()
# vim: sw=4 et

View File

@@ -1,27 +1,37 @@
#!/usr/bin/env perl
# This script takes an input of filenames and outputs a set of
# include/exclude directives that can be used by rsync to copy
# just the indicated files using an --exclude-from=FILE option.
use strict;
#!/usr/bin/env python3
# This script takes an input of filenames and outputs a set of include/exclude
# directives that can be used by rsync to copy just the indicated files using
# an --exclude-from=FILE or -f'. FILE' option. To be able to delete files on
# the receiving side, either use --delete-excluded or change the exclude (-)
# rules to hide filter rules (H) that only affect the sending side.
my %hash;
import os, fileinput, argparse
while (<>) {
chomp;
s#^/+##;
my $path = '/';
while (m#([^/]+/)/*#g) {
$path .= $1;
print "+ $path\n" unless $hash{$path}++;
}
if (m#([^/]+)$#) {
print "+ $path$1\n";
} else {
delete $hash{$path};
}
}
def main():
paths = set()
for line in fileinput.input(args.files):
dirs = line.strip().lstrip('/').split('/')
if not dirs:
continue
for j in range(1, len(dirs)):
if dirs[j] == '':
continue
path = '/' + '/'.join(dirs[:j]) + '/'
if path not in paths:
print('+', path)
paths.add(path)
print('+', '/' + '/'.join(dirs))
foreach (sort keys %hash) {
print "- $_*\n";
}
print "- /*\n";
for path in sorted(paths):
print('-', path + '*')
print('-', '/*')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Transform a list of files into a set of include/exclude rules.", add_help=False)
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
parser.add_argument("files", metavar="FILE", default='-', nargs='*', help="The file(s) that hold the pathnames to translate. Defaults to stdin.")
args = parser.parse_args()
main()
# vim: sw=4 et

45
support/idmap Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# This helper script makes it easy to use a passwd or group file to map values
# in a LOCAL transfer. For instance, if you mount a backup that does not have
# the same passwd setup as the local machine, you can do a copy to/from the
# backup area as follows and get the differing ID values mapped just like a
# remote transfer to/from the backed-up machine would do:
#
# rsync -av --usermap=`idmap --to /mnt/backup/etc/passwd` \
# --groupmap=`idmap --to /mnt/backup/etc/group` \
# /some/src/ /mnt/backup/some/dest/
#
# rsync -av --usermap=`idmap --from /mnt/backup/etc/passwd` \
# --groupmap=`idmap --from /mnt/backup/etc/group` \
# /mnt/backup/some/src/ /some/dest/
import re, fileinput, argparse
NAME_ID_RE = re.compile(r'^(\w+):[^:]+:(\d+)')
def main():
maps = [ ]
for line in fileinput.input(args.files):
m = NAME_ID_RE.match(line)
if not m:
continue
if args.to:
pair = (m[1], m[2])
else:
pair = (m[2], m[1])
maps.append(':'.join(pair))
print(','.join(maps))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Output usermap or groupmap args for rsync.", add_help=False)
action = parser.add_argument_group()
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument("--from", action="store_true", help="Output the map for use on the sending side.")
action.add_argument("--to", action="store_true", help="Output the map for use on the receiving side.")
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
parser.add_argument("files", metavar="FILE", default='-', nargs='*', help="The file(s) that hold the name & id pairs. Defaults to stdin.")
args = parser.parse_args()
main()
# vim: sw=4 et

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/env python3
import sys, argparse, subprocess, json

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env perl
# This helper script makes it easy to use a passwd or group file to map
# values in a LOCAL transfer. For instance, if you mount a backup that
# does not have the same passwd setup as the local machine, you can do
# a copy FROM the backup area as follows and get the differing ID values
# mapped just like a remote transfer FROM the backed-up machine would do:
#
# rsync -av --usermap=`mapfrom /mnt/backup/etc/passwd` \
# --groupmap=`mapfrom /mnt/backup/etc/group` \
# /mnt/backup/some/src/ /some/dest/
while (<>) {
push @_, "$2:$1" if /^(\w+):[^:]+:(\d+)/;
}
print join(',', @_), "\n";

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env perl
# This helper script makes it easy to use a passwd or group file to map
# values in a LOCAL transfer. For instance, if you mount a backup that
# does not have the same passwd setup as the local machine, you can do
# a copy TO the backup area as follows and get the differing ID values
# mapped just like a remote transfer TO the backed-up machine would do:
#
# rsync -av --usermap=`mapto /mnt/backup/etc/passwd` \
# --groupmap=`mapto /mnt/backup/etc/group` \
# /some/src/ /mnt/backup/some/dest/
while (<>) {
push @_, "$1:$2" if /^(\w+):[^:]+:(\d+)/;
}
print join(',', @_), "\n";

View File

@@ -258,6 +258,9 @@ def main():
if args.munge:
rsync_opts.append('--munge-links')
if args.no_overwrite:
rsync_opts.append('--ignore-existing')
if not rsync_args:
rsync_args = [ '.' ]
@@ -364,6 +367,7 @@ if __name__ == '__main__':
arg_parser.add_argument('-munge', action='store_true', help="Enable rsync's --munge-links on the server side.")
arg_parser.add_argument('-no-del', action='store_true', help="Disable rsync's --delete* and --remove* options.")
arg_parser.add_argument('-no-lock', action='store_true', help="Avoid the single-run (per-user) lock check.")
arg_parser.add_argument('-no-overwrite', action='store_true', help="Prevent overwriting existing files by enforcing --ignore-existing")
arg_parser.add_argument('-help', '-h', action='help', help="Output this help message and exit.")
arg_parser.add_argument('dir', metavar='DIR', help="The restricted directory to use.")
args = arg_parser.parse_args()

View File

@@ -5,7 +5,7 @@ rrsync - a script to setup restricted rsync users via ssh logins
## SYNOPSIS
```
rrsync [-ro|-rw] [-munge] [-no-del] [-no-lock] DIR
rrsync [-ro|-rw] [-munge] [-no-del] [-no-lock] [-no-overwrite] DIR
```
The single non-option argument specifies the restricted _DIR_ to use. It can be
@@ -85,6 +85,11 @@ The remainder of this manpage is dedicated to using the rrsync script.
Avoid the single-run (per-user) lock check. Useful with [`-munge`](#opt).
0. `-no-overwrite`
Enforce `--ignore-existing` on the server. Prevents overwriting existing
files when the server is the receiver.
0. `-help`, `-h`
Output this help message and exit.

1352
syscall.c
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +0,0 @@
/*
* Test harness for do_chmod_at(). Confirms the symlink-TOCTOU
* primitive used by CVE-2026-29518 (and its incomplete-fix follow-up
* for chmod) is closed by do_chmod_at(): a parent directory component
* being a symlink that escapes the receiver's confinement must be
* rejected, while a parent symlink that resolves *within* the tree
* must still work (so legitimate dir-symlinks are not regressed).
*
* Not linked into rsync itself.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "rsync.h"
#include <sys/stat.h>
int dry_run = 0;
int am_root = 0;
int am_sender = 0;
int read_only = 0;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
extern int am_daemon, am_chrooted;
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
static int errs = 0;
static void check(const char *label, int actual_rc, int expect_ok,
const char *path, mode_t expected_mode)
{
struct stat st;
int got_ok = (actual_rc == 0);
if (got_ok != expect_ok) {
fprintf(stderr, "FAIL [%s]: rc=%d errno=%d (%s), expected %s\n",
label, actual_rc, errno, strerror(errno),
expect_ok ? "success" : "rejection");
errs++;
return;
}
if (path && stat(path, &st) < 0) {
fprintf(stderr, "FAIL [%s]: stat(%s) failed: %s\n",
label, path, strerror(errno));
errs++;
return;
}
if (path && (st.st_mode & 07777) != expected_mode) {
fprintf(stderr,
"FAIL [%s]: %s mode is 0%o, expected 0%o\n",
label, path, st.st_mode & 07777, expected_mode);
errs++;
return;
}
fprintf(stderr, "OK [%s]\n", label);
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage: %s <module-dir>\n", argv[0]);
return 2;
}
if (chdir(argv[1]) < 0) {
perror("chdir");
return 2;
}
/* Simulate the daemon-without-chroot deployment that do_chmod_at()
* defends. With am_daemon=0 or am_chrooted=1 the wrapper falls
* through to plain do_chmod() and the symlink-race test would be
* meaningless. */
am_daemon = 1;
am_chrooted = 0;
/* Test layout (all inside the directory we just chdir'd to):
*
* ./realdir/sentinel -- regular target file
* ./inside_link -> realdir -- legitimate dir-symlink within the tree
* ./escape_link -> ../trap -- attacker swap, target outside tree
* ../trap/sentinel -- the file the attacker wants to alter
*
* The shell wrapper that calls this helper has set both sentinel
* files to mode 0600 so we have a clean baseline to compare.
*/
/* Scenario A: legitimate parent dir-symlink, chmod must succeed. */
int rc = do_chmod_at("inside_link/sentinel", 0640);
check("A: legit dir-symlink within tree",
rc, 1, "realdir/sentinel", 0640);
/* Scenario B: parent symlink escapes the tree -- chmod must be
* rejected and the outside file's mode must be unchanged. */
rc = do_chmod_at("escape_link/sentinel", 0666);
check("B: parent symlink escapes tree (the attack)",
rc, 0, "../trap/sentinel", 0600);
/* Scenario C: plain relative path with no symlink components,
* regression check that the safe wrapper doesn't break the
* normal case. */
rc = do_chmod_at("realdir/sentinel", 0644);
check("C: plain relative path (regression check)",
rc, 1, "realdir/sentinel", 0644);
/* Scenario D: top-level file, no parent directory component.
* Falls back to do_chmod(); should succeed. */
rc = do_chmod_at("topfile", 0640);
check("D: top-level file, no parent component",
rc, 1, "topfile", 0640);
if (errs)
fprintf(stderr, "%d failure(s)\n", errs);
return errs ? 1 : 0;
}

View File

@@ -1,151 +0,0 @@
/*
* Test harness for secure_relative_open()'s front-door input
* validation. Codex audit Finding 5 noted that the existing check
*
* if (strncmp(relpath, "../", 3) == 0 || strstr(relpath, "/../"))
*
* catches "../foo" and "foo/../bar" but misses bare ".." (an actual
* one-level escape on platforms that fall back to the per-component
* walk), as well as "a/..", "foo/..", and any other form that
* decomposes to a ".." component when split on "/". The kernel-
* enforced RESOLVE_BENEATH (Linux 5.6+) and O_RESOLVE_BENEATH
* (FreeBSD 13+, macOS 15+) reject these in-kernel; the per-
* component fallback used on NetBSD, OpenBSD, Solaris, Cygwin and
* pre-5.6 Linux does not, so the validation must happen at the
* front door.
*
* This helper invokes secure_relative_open() with each suspect
* input and checks both the failure (rc < 0) and the errno
* (EINVAL means "rejected at the front door"). Pre-fix, the kernel
* may reject with a different errno (EXDEV from RESOLVE_BENEATH);
* post-fix, the front-door check catches every variant up front
* with a consistent EINVAL across platforms.
*
* Not linked into rsync itself.
*/
#include "rsync.h"
#include <sys/stat.h>
int dry_run = 0;
int am_root = 0;
int am_sender = 0;
int read_only = 0;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
extern int am_daemon, am_chrooted;
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
static int errs = 0;
static void check_relpath(const char *relpath)
{
int fd;
int saved_errno;
errno = 0;
fd = secure_relative_open(NULL, relpath, O_RDONLY | O_DIRECTORY, 0);
saved_errno = errno;
if (fd >= 0) {
fprintf(stderr,
"FAIL [relpath=%-12s]: returned valid fd %d (escape) -- expected -1 EINVAL\n",
relpath, fd);
close(fd);
errs++;
return;
}
if (saved_errno != EINVAL) {
fprintf(stderr,
"FAIL [relpath=%-12s]: rejected but errno=%d (%s), expected EINVAL\n",
relpath, saved_errno, strerror(saved_errno));
errs++;
return;
}
fprintf(stderr, "OK [relpath=%-12s]: rejected with EINVAL\n", relpath);
}
static void check_basedir(const char *basedir)
{
int fd;
int saved_errno;
errno = 0;
fd = secure_relative_open(basedir, "ok", O_RDONLY | O_DIRECTORY, 0);
saved_errno = errno;
if (fd >= 0) {
fprintf(stderr,
"FAIL [basedir=%-12s]: returned valid fd %d -- expected -1 EINVAL\n",
basedir, fd);
close(fd);
errs++;
return;
}
if (saved_errno != EINVAL) {
fprintf(stderr,
"FAIL [basedir=%-12s]: rejected but errno=%d (%s), expected EINVAL\n",
basedir, saved_errno, strerror(saved_errno));
errs++;
return;
}
fprintf(stderr, "OK [basedir=%-12s]: rejected with EINVAL\n", basedir);
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage: %s <test-dir>\n", argv[0]);
return 2;
}
if (chdir(argv[1]) < 0) {
perror("chdir");
return 2;
}
/* secure_relative_open's daemon-only confinement protections only
* fire when am_daemon && !am_chrooted (the threat model is the
* daemon-no-chroot deployment), but the front-door input
* validation runs unconditionally. We set am_daemon anyway so the
* helper exercises the same code shape the receiver does. */
am_daemon = 1;
am_chrooted = 0;
mkdir("subdir", 0755);
/* Each of these relpaths must be rejected with EINVAL at the
* secure_relative_open() front door. ".." is the actual one-level
* escape; the others ("subdir/..", "subdir/../subdir") resolve
* back to the start dir on systems that allow them, but we still
* reject them as defence-in-depth: a path containing a ".." token
* is suspicious and the caller should normalise before passing
* it in. The "../foo" / "foo/../bar" / "/foo" / "/" cases are
* regression checks for the existing checks. */
check_relpath("..");
check_relpath("../foo");
check_relpath("subdir/..");
check_relpath("subdir/../subdir");
check_relpath("foo/../bar");
check_relpath("/foo");
check_relpath("/");
/* Same checks against basedir (which the codex Finding 2 fix
* routes through the same RESOLVE_BENEATH-equivalent). Absolute
* basedirs are operator-trusted and intentionally not validated
* here. */
check_basedir("..");
check_basedir("../subdir");
check_basedir("subdir/..");
check_basedir("foo/../bar");
if (errs)
fprintf(stderr, "\n%d failure(s)\n", errs);
return errs ? 1 : 0;
}

View File

@@ -23,8 +23,6 @@
int do_fsync = 0;
int inplace = 0;
int am_daemon = 0;
int am_chrooted = 0;
int modify_window = 0;
int preallocate_files = 0;
int protect_args = 0;

View File

@@ -28,9 +28,6 @@ int am_root = 0;
int am_sender = 1;
int read_only = 0;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
int

View File

@@ -1,113 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the basedir-confinement gap in
# secure_relative_open(). The function opens basedir with a plain
# openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY), without
# RESOLVE_BENEATH or a per-component O_NOFOLLOW walk, so a parent
# symlink ON basedir is followed unrestrictedly. RESOLVE_BENEATH is
# then applied only to relpath, anchored at the wrong directory.
#
# The receiver's basis-file lookup at receiver.c passes
# basis_dir[fnamecmp_type] (from --copy-dest / --link-dest /
# --compare-dest -- all sender-controllable in daemon mode) as
# basedir. A daemon-module attacker with write access can plant a
# symlink at module/cd -> /outside, then run --link-dest=cd to
# make the daemon's basis-file lookup resolve into /outside,
# leaking the contents of daemon-readable files via the rsync
# delta-rolling read-disclosure primitive.
#
# We detect the escape by leveraging --link-dest: when basis
# matches source exactly (content + mtime + mode), --link-dest
# hard-links the destination to the basis file. With the bug, the
# destination ends up as a hard link to the outside-the-module
# file (same inode). With the fix, no basis is found and the
# destination is a fresh copy (different inode).
#
# The vulnerable code path is the same on every platform
# (including the per-component fallback on systems without
# RESOLVE_BENEATH), so this test is not platform-gated.
. "$suitedir/rsync.fns"
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src"
# Portable inode-number helper (GNU coreutils stat -c, BSD stat -f).
file_inode() {
stat -c %i "$1" 2>/dev/null || stat -f %i "$1"
}
# Outside-the-module file an attacker would like the daemon to
# treat as a basis.
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
chmod 0644 "$outside/target.txt"
# The symlink trap planted in the module by the local attacker.
ln -s "$outside" "$mod/cd"
# Source file matches outside/target.txt exactly (content + mtime
# + mode) so --link-dest will hard-link the destination to the
# basis file iff the daemon's basedir lookup reaches outside/.
echo "OUTSIDE_SECRET_DATA" > "$src/target.txt"
touch -r "$outside/target.txt" "$src/target.txt"
chmod 0644 "$src/target.txt"
# When running as root the daemon would drop to "nobody" by
# default, which can't write into the test scratch dir. Force the
# daemon to keep our uid/gid in that case so the basis-link
# transfer can actually create the destination file. (Non-root
# can't specify uid/gid in rsyncd.conf -- comment them out then.)
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
# Recursive --link-dest push directly into the module root. We
# avoid pushing into a destination subdir because the receiver
# would chdir into it before resolving --link-dest, making the
# relative basedir "cd" resolve in the wrong CWD and masking the
# bug. The realistic attack pushes into the module root (or the
# attacker uses a basedir path that resolves correctly from
# whichever subdir the receiver chdirs into).
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rtp --link-dest=cd "$src/" rsync://localhost/upload/ \
>/dev/null 2>&1 || true
if [ ! -f "$mod/target.txt" ]; then
test_fail "destination file was not created -- daemon transfer failed before the test could observe the basedir behaviour"
fi
outside_inode=$(file_inode "$outside/target.txt")
dst_inode=$(file_inode "$mod/target.txt")
if [ "$outside_inode" = "$dst_inode" ]; then
test_fail "basedir-escape: --link-dest hard-linked module/target.txt to outside/target.txt (inode $outside_inode); daemon's basis-file lookup followed the parent symlink on the basedir"
fi
exit 0

View File

@@ -1,206 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex audit Findings 3b and 3c:
#
# 3b: generator.c:1905 -- the in-place backup creation opens
# backupptr via bare do_open(O_WRONLY|O_CREAT|O_TRUNC|O_EXCL).
# With --backup-dir set to an attacker-planted parent symlink,
# the backup file is written outside the module under the
# daemon's authority.
#
# 3c-symlink: syscall.c:207 -- do_symlink_at falls through to bare
# do_symlink for am_root < 0 (fake-super), which then opens
# the destination path with bare open() (final-component
# fake-super file). A parent symlink on the destination path
# redirects the file creation outside the module.
#
# 3c-mknod: syscall.c:506 -- do_mknod_at falls through to bare
# do_mknod for am_root < 0, same path-based open(). For
# FIFOs/sockets/devices the bare path is also used.
#
# Each scenario plants a "secret" file outside the module at a
# location the symlink trap points to. The check is that the
# outside file's content and mode are unchanged after the attack
# attempt.
. "$suitedir/rsync.fns"
# All three scenarios depend on receiver-side daemon code paths
# that are only secured on platforms with a working
# secure_relative_open. The chdir/chmod tests already skip the
# same set; mirror that.
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure_relative_open relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
# Portable inode-and-mode helpers.
file_mode() {
stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1"
}
setup() {
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src"
echo "OUTSIDE_PROTECTED_DATA" > "$outside/target.txt"
chmod 0644 "$outside/target.txt"
outside_pristine="$scratchdir/outside-pristine.txt"
cp -p "$outside/target.txt" "$outside_pristine"
ln -s "$outside" "$mod/cd"
}
verify_outside_unchanged() {
label="$1"
mode=$(file_mode "$outside/target.txt")
case "$mode" in
644|0644) ;;
*) test_fail "$label: outside/target.txt mode changed from 644 to $mode" ;;
esac
if ! cmp -s "$outside/target.txt" "$outside_pristine"; then
test_fail "$label: outside/target.txt content changed -- daemon followed the cd symlink"
fi
}
verify_outside_unchanged_or_absent() {
label="$1"
target="$2" # specific file under outside/ to check absence of
if [ -e "$outside/$target" ]; then
test_fail "$label: outside/$target was created -- daemon followed the cd symlink"
fi
}
# When running as root the daemon would drop to "nobody" by default
# and fail to write into the test scratch dir. Force it to keep our
# uid/gid in that case so the receiver actually runs the code paths
# we want to test.
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
############################################################
# Scenario 3b: --inplace --backup --backup-dir=cd
#
# Pre-create module/target.txt so the receiver enters the in-place
# update path; a backup of the existing content must be made
# before the update. With --backup-dir=cd, backupptr resolves to
# "cd/target.txt"; with the bug, robust_unlink and the bare
# do_open at generator.c:1905 both follow the cd symlink, the
# unlink deletes outside/target.txt and the create writes the
# pre-existing module/target.txt content there.
############################################################
setup
echo "EXISTING_MODULE_DATA" > "$mod/target.txt"
chmod 0666 "$mod/target.txt"
echo "NEW_DATA_FROM_SENDER" > "$src/target.txt"
chmod 0644 "$src/target.txt"
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC --inplace --backup --backup-dir=cd "$src/target.txt" \
rsync://localhost/upload/target.txt >/dev/null 2>&1 || true
verify_outside_unchanged "3b inplace+backup-dir=cd"
############################################################
# Scenario 3c-symlink: fake-super symlink push to a path with a
# symlinked parent
#
# With "fake super = yes" set on the module, the receiver
# represents symlinks as fake-super files (regular files with the
# link target written to them). The path-based open() in
# do_symlink's fake-super branch follows parent symlinks. We push
# a single symlink to the destination path "cd/sym" so the
# receiver's create-file call lands at "cd/sym" relative to the
# module root, where cd is the symlink trap.
############################################################
setup
mkdir -p "$src/cd"
ln -s /etc/passwd "$src/cd/sym"
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload_fake]
path = $mod
use chroot = no
read only = no
fake super = yes
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rl "$src/" rsync://localhost/upload_fake/ >/dev/null 2>&1 || true
verify_outside_unchanged_or_absent "3c-symlink fake-super symlink push" "sym"
############################################################
# Scenario 3c-mknod: fake-super FIFO push to a path with a
# symlinked parent
#
# Similar to 3c-symlink but for special files. mkfifo works
# without root; we push a FIFO and verify the receiver doesn't
# create a fake-super file at outside/fifo.
############################################################
setup
mkdir -p "$src/cd"
mkfifo "$src/cd/fifo" 2>/dev/null
if [ ! -p "$src/cd/fifo" ]; then
test_skipped "mkfifo unavailable; cannot exercise 3c-mknod"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload_fake]
path = $mod
use chroot = no
read only = no
fake super = yes
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rD "$src/" rsync://localhost/upload_fake/ >/dev/null 2>&1 || true
verify_outside_unchanged_or_absent "3c-mknod fake-super FIFO push" "fifo"
exit 0

View File

@@ -1,135 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the symlink-TOCTOU class of bug at the receiver's
# chdir(). After the CVE-2026-29518 fix to secure_relative_open(), an
# attack remained where the receiver's chdir() into a destination
# subdirectory followed an attacker-planted symlink, escaping the
# module. Every subsequent path-relative syscall (open, chmod, lchown,
# utimes, etc.) inherited the escape -- secure_relative_open's
# RESOLVE_BENEATH anchor itself was outside the module by then, so it
# stopped protecting against anything.
#
# This test runs an actual rsync daemon (via RSYNC_CONNECT_PROG to
# avoid the network) configured with "use chroot = no", plants a
# symlink at module/subdir -> ../outside, and runs four flavours of
# rsync transfer that previously all reached files in ../outside:
#
# 1. single-file dest = subdir/target.txt (the original poc_chmod)
# 2. -r src/subdir/ to upload/subdir/ (the chdir-escape case)
# 3. -r src/subdir/ to upload/subdir/ (no --size-only: forces basis read+write)
# 4. -r src/ to upload/ (was already protected by the
# original CVE-2026-29518 fix;
# regression-checked here)
#
# All four must leave the outside-the-module sentinel file's mode AND
# content unchanged.
. "$suitedir/rsync.fns"
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure chdir relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src" "$src/subdir"
# Portable octal-mode helper -- macOS and FreeBSD's stat use -f, GNU
# coreutils stat uses -c.
file_mode() {
stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1"
}
# The "secret" file outside the module the attacker is trying to alter.
# Save a pristine copy alongside it so we can compare with cmp(1) rather
# than depending on sha1sum/shasum/sha1, which differ across platforms.
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
chmod 0600 "$outside/target.txt"
outside_pristine="$scratchdir/outside-pristine.txt"
cp -p "$outside/target.txt" "$outside_pristine"
# Symlink trap planted in the module by the local attacker.
ln -s "$outside" "$mod/subdir"
# Source files the sender will push: same size as the outside target,
# different content, mode 0666 (the perms the attacker tries to push).
SIZE=$(stat -c %s "$outside/target.txt" 2>/dev/null \
|| stat -f %z "$outside/target.txt")
head -c "$SIZE" /dev/urandom > "$src/target.txt"
head -c "$SIZE" /dev/urandom > "$src/subdir/target.txt"
chmod 0666 "$src/target.txt" "$src/subdir/target.txt"
cat > "$conf" <<EOF
use chroot = no
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
reset_outside() {
chmod 0600 "$outside/target.txt"
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
}
verify_unchanged() {
label="$1"
mode=$(file_mode "$outside/target.txt")
case "$mode" in
600|0600) ;;
*) test_fail "$label: outside file mode changed from 600 to $mode (chmod escape)" ;;
esac
if ! cmp -s "$outside/target.txt" "$outside_pristine"; then
test_fail "$label: outside file content changed (write escape)"
fi
}
run_attack() {
label="$1"; shift
reset_outside
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC "$@" >/dev/null 2>&1 || true
verify_unchanged "$label"
}
# 1. The original poc_chmod scenario: single file, dest path with
# the symlinked subdir as a path component. With --size-only the
# receiver normally skips the basis open and goes straight to chmod
# -- only the chdir-escape blocks the chmod from reaching outside.
run_attack "single-file --size-only" \
-tp --size-only \
"$src/target.txt" rsync://localhost/upload/subdir/target.txt
# 2. -r push into the symlinked subdir: receiver chdir's into "subdir",
# follows the symlink, ends up in outside.
run_attack "-r --size-only into subdir/" \
-rtp --size-only \
"$src/subdir/" rsync://localhost/upload/subdir/
# 3. Same but no --size-only -- forces the basis-file open and a real
# rename, so this exercises the read-disclosure and write-escape
# paths together.
run_attack "-r without --size-only into subdir/" \
-rtp \
"$src/subdir/" rsync://localhost/upload/subdir/
# 4. -r src/ to upload/ -- this case was already covered by the
# original CVE-2026-29518 fix because the receiver stays at module
# root and operates on slashed paths. Regression check.
run_attack "-r --size-only into upload/ root" \
-rtp --size-only \
"$src/" rsync://localhost/upload/
exit 0

View File

@@ -1,68 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the symlink-TOCTOU class of bug applied to
# chmod() on the receiver side. The CVE-2026-29518 fix used
# secure_relative_open() for the basis-file open, but every other
# path-based syscall the receiver runs on sender-controllable paths
# is vulnerable to the same primitive: a local attacker swaps a
# symlink into one of the parent directory components between the
# receiver's check and its act, and the syscall escapes the module.
#
# This test exercises the new do_chmod_at() wrapper via the
# t_chmod_secure helper. The helper sets up two scenarios:
# - a parent dir-symlink that resolves WITHIN the module tree
# (legitimate -K-style use, must continue to work)
# - a parent dir-symlink that escapes the module tree (the
# attack, must be rejected)
# plus two regression scenarios (plain relative path, top-level
# file) that just confirm the safe wrapper doesn't break the
# normal case.
#
# The kernel-enforced "stay below dirfd" path resolution is
# only available on Linux 5.6+, FreeBSD 13+, and macOS 15+.
# Skip on platforms that fall back to per-component O_NOFOLLOW
# (Solaris, OpenBSD, NetBSD, Cygwin); the per-component fallback
# would also reject the attack but the legitimate dir-symlink
# scenario would fail there.
. "$suitedir/rsync.fns"
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "do_chmod_at relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
trap_outside="$scratchdir/trap"
rm -rf "$mod" "$trap_outside"
mkdir -p "$mod/realdir" "$trap_outside"
# Set up the four file-system objects the helper expects:
echo bystander > "$mod/realdir/sentinel"
chmod 0600 "$mod/realdir/sentinel"
echo target > "$trap_outside/sentinel"
chmod 0600 "$trap_outside/sentinel"
ln -s realdir "$mod/inside_link"
ln -s ../trap "$mod/escape_link"
echo top > "$mod/topfile"
chmod 0600 "$mod/topfile"
"$TOOLDIR/t_chmod_secure" "$mod" || \
test_fail "t_chmod_secure reported failures (see stderr above)"
# Sanity-check from the shell side too: the outside file's mode must
# still be 0600 -- the helper checked this, but a second look from
# the shell guards against a helper-internal stat() bug.
mode=$(stat -c '%a' "$trap_outside/sentinel" 2>/dev/null \
|| stat -f '%Lp' "$trap_outside/sentinel" 2>/dev/null)
if [ "$mode" != "600" ]; then
test_fail "outside sentinel mode changed from 600 to $mode -- chmod escaped the module"
fi
exit 0

View File

@@ -1,80 +0,0 @@
#!/bin/sh
# clean-fname-underflow.test
# Ensure clean_fname() does not read-before-buffer when collapsing "..".
# This exercises the --server path where a crafted merge filename hits clean_fname().
#
# Usage:
# ./configure && make
# make check TESTS='clean-fname-underflow.test'
set -eu
# clean_fname() is platform-agnostic; the test only needs to run on one
# host. Skip on non-Linux to avoid quirks in older /bin/sh implementations
# (Solaris exited silently here, producing an empty log under set -eu).
# runtests.sh expects $scratchdir/whyskipped to exist when a test exits 77.
case "$(uname -s)" in
Linux) ;;
*)
if [ -n "${scratchdir:-}" ]; then
echo "Linux-only test (uname -s = $(uname -s))" > "$scratchdir/whyskipped"
fi
exit 77 ;;
esac
# Try to find the just-built rsync binary if RSYNC_BIN isn't set.
if [ -z "${RSYNC_BIN:-}" ]; then
if [ -x "./rsync" ]; then
RSYNC_BIN=$(pwd)/rsync
elif [ -x "../rsync" ]; then
RSYNC_BIN=$(cd .. && pwd)/rsync
else
RSYNC_BIN=rsync
fi
fi
workdir="${TMPDIR:-/tmp}/rsync-clean-fname.$$"
mkdir -p "$workdir"
trap 'rm -rf "$workdir"' EXIT INT TERM
cd "$workdir"
# Minimal rsyncd.conf using chroot so the crafted path reaches the server parser.
cat > rsyncd.conf <<'EOF'
pid file = rsyncd.pid
use chroot = true
[mod]
path = ./mod
read only = false
EOF
mkdir -p mod
# Start daemon on a random high port.
PORT=$(awk 'BEGIN{srand(); printf "%d", 20000+int(rand()*20000)}')
"$RSYNC_BIN" --daemon --no-detach --config=rsyncd.conf --port="$PORT" >/dev/null 2>&1 &
DAEMON_PID=$!
# Give the daemon a moment to come up.
# Use integer second; subsecond sleep is not portable (e.g. Solaris /usr/bin/sleep).
sleep 1
# Invoke the server-side path. We don't need a real transfer; we just want to
# ensure clean_fname() doesn't crash when given "a/../test" via --filter=merge.
EXIT_OK=0
if "$RSYNC_BIN" --server --sender -vlr --filter='merge a/../test' . mod/ >/dev/null 2>&1; then
EXIT_OK=1
else
status=$?
# Non-zero exit is expected for bogus input; ensure it wasn't a signal/crash.
if [ $status -lt 128 ]; then
EXIT_OK=1
fi
fi
kill "$DAEMON_PID" >/dev/null 2>&1 || true
if [ "$EXIT_OK" -ne 1 ]; then
echo "clean-fname-underflow.test: rsync exited due to a signal or unexpected status"
exit 1
fi
echo "OK: clean_fname() handled 'a/../test' without crashing"
exit 0

View File

@@ -1,98 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex audit Finding 3a: copy_file()'s source
# open in copy_altdest_file() is via do_open_nofollow(), which only
# refuses a final-component symlink. Parent components are still
# resolved with normal symlink-following. A daemon module attacker
# who plants a parent symlink at module/cd -> /outside, then runs
# --copy-dest=cd against a source file matching the size+mtime of
# /outside/target.txt, drives the receiver to:
#
# 1. Find a match-level >= 2 basis at "cd/target.txt"
# 2. Call copy_altdest_file -> copy_file(src="cd/target.txt", ...)
# 3. do_open_nofollow follows the "cd" parent symlink and reads
# the contents of /outside/target.txt under the daemon's
# authority
# 4. Copy that content into the module destination
#
# Result: outside/target.txt content lands at module/target.txt,
# accessible to the attacker on a subsequent pull.
#
# We detect by content: src/target.txt and outside/target.txt have
# identical metadata (size + mtime + mode) but different content.
# After the transfer, module/target.txt should match src (no
# basedir escape) -- if it matches outside, the bug copied across
# the symlink boundary.
. "$suitedir/rsync.fns"
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src"
# Outside-the-module file the daemon should not read on the
# attacker's behalf.
echo "OUTSIDE_LEAKED_DATA!" > "$outside/target.txt"
chmod 0644 "$outside/target.txt"
# The symlink trap.
ln -s "$outside" "$mod/cd"
# Source: same size, same mtime, same mode as outside -- so the
# generator's link_stat + quick_check_ok finds a match-level >= 2
# basis and calls copy_altdest_file.
echo "ATTACKER_KNOWN_DATA!" > "$src/target.txt"
touch -r "$outside/target.txt" "$src/target.txt"
chmod 0644 "$src/target.txt"
# When running as root the daemon would drop to "nobody" by
# default and fail to mkstemp in the scratch dir; force it to
# keep our uid/gid in that case.
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
# --copy-dest push to module root.
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rtp --copy-dest=cd "$src/" rsync://localhost/upload/ \
>/dev/null 2>&1 || true
if [ ! -f "$mod/target.txt" ]; then
test_fail "destination file was not created -- daemon transfer failed before the test could observe the basedir behaviour"
fi
if cmp -s "$mod/target.txt" "$outside/target.txt"; then
test_fail "basedir-escape via copy_file source: module/target.txt now contains the contents of outside/target.txt -- daemon read /outside via the cd symlink and copied it into the module"
fi
if ! cmp -s "$mod/target.txt" "$src/target.txt"; then
test_fail "destination doesn't match source content (and isn't outside content either): unexpected state"
fi
exit 0

View File

@@ -1,111 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for GHSA-rjfm-3w2m-jf4f: a hostname-based "hosts deny"
# rule must still match when the daemon performs a 'daemon chroot' and
# the chroot does not contain the NSS files glibc needs for reverse DNS.
#
# Pre-fix, reverse DNS happened *after* the daemon chroot. With an empty
# chroot the NSS lookup failed, client_name() returned "UNKNOWN", and a
# deny rule referring to the connecting hostname silently failed to
# match.
#
# Two scenarios are exercised so we can distinguish the case the fix
# definitely covers from the per-module path that may still be
# vulnerable:
# A. global "reverse lookup = yes" (covered by b6abdb4c)
# B. only module "reverse lookup = yes" (gap to verify)
. "$suitedir/rsync.fns"
case `uname -s` in
Linux*) ;;
*) test_skipped "test is Linux-specific (uses chroot+unshare)" ;;
esac
# We need CAP_SYS_CHROOT. Re-exec under a user namespace if not root.
if ! chroot / /bin/true 2>/dev/null; then
if [ -z "$RSYNC_UNSHARED" ] && unshare --user --map-root-user true 2>/dev/null; then
echo "Re-running under unshare --user --map-root-user..."
RSYNC_UNSHARED=1 exec unshare --user --map-root-user "$SHELL_PATH" $RUNSHFLAGS "$0"
fi
test_skipped "need CAP_SYS_CHROOT (root or unshare --user --map-root-user)"
fi
# We need 127.0.0.1 to reverse-resolve to a real hostname while NSS is
# still working (i.e. before the daemon's chroot). The daemon will
# look that name up itself as part of its hostname-based ACL check;
# we then deny that name and assert the connection is rejected.
client_hostname=`getent hosts 127.0.0.1 2>/dev/null | awk 'NR==1 {print $2}'`
if [ -z "$client_hostname" ] || [ "$client_hostname" = "127.0.0.1" ]; then
test_skipped "no reverse DNS for 127.0.0.1"
fi
chrootdir="$scratchdir/chroot"
rm -rf "$chrootdir"
mkdir -p "$chrootdir/modroot"
echo "from chroot" > "$chrootdir/modroot/file1"
conf="$scratchdir/test-rsyncd.conf"
logfile="$scratchdir/rsyncd.log"
write_conf() {
cat >"$conf" <<EOF
use chroot = no
log file = $logfile
daemon chroot = $chrootdir
reverse lookup = $1
hosts deny = $client_hostname
max verbosity = 4
[chrootmod]
path = /modroot
read only = yes
reverse lookup = $2
EOF
}
# Run a transfer and return 0 if the daemon refused with @ERROR access
# denied (the expected outcome when the deny rule matches).
run_check() {
label="$1"
rm -f "$logfile"
rm -rf "$todir"
mkdir -p "$todir"
out="$scratchdir/run.out"
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -av localhost::chrootmod/ "$todir/" >"$out" 2>&1
rc=$?
echo "----- $label (rsync exit $rc):"
cat "$out"
echo "----- daemon log:"
[ -f "$logfile" ] && cat "$logfile"
echo "-----"
grep -q '@ERROR.*access denied' "$out"
}
# Scenario A: global reverse lookup. Covered by b6abdb4c.
write_conf yes yes
if ! run_check "Scenario A (global reverse lookup = yes)"; then
test_fail "Scenario A: hostname deny rule was bypassed"
fi
# Scenario B: only the per-module reverse-lookup setting is enabled.
# The b6abdb4c fix only pre-warms client_name()'s cache when the
# global setting is on, so the post-chroot lookup in this path may
# still produce "UNKNOWN" and bypass the deny rule.
write_conf no yes
if ! run_check "Scenario B (per-module reverse lookup only)"; then
test_fail "Scenario B: hostname deny rule was bypassed (per-module reverse lookup with daemon chroot still has the bypass)"
fi
exit 0

View File

@@ -1,51 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Test that a daemon module configured with "refuse options = compress"
# rejects clients that ask for compression and still serves the same
# transfer when the client does not.
. "$suitedir/rsync.fns"
build_rsyncd_conf
# Append a module that refuses --compress (-z).
cat >>"$conf" <<EOF
[no-compress]
path = $fromdir
read only = yes
refuse options = compress
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
export RSYNC_CONNECT_PROG
hands_setup
# Build a reference tree mirroring the daemon's global exclude rule.
$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"
# A compressed transfer must be refused.
errlog="$scratchdir/refuse.err"
if $RSYNC -avz localhost::no-compress/ "$todir/" >/dev/null 2>"$errlog"; then
cat "$errlog" >&2
test_fail "compressed transfer was not refused"
fi
grep -- '--compress' "$errlog" >/dev/null || {
cat "$errlog" >&2
test_fail "expected refuse error mentioning --compress"
}
# The same transfer without -z must succeed.
rm -rf "$todir"
mkdir "$todir"
checkit "$RSYNC -av localhost::no-compress/ '$todir/'" "$chkdir" "$todir"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -1,128 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the off-by-one stack OOB write in
# establish_proxy_connection() in socket.c when a malicious or
# man-in-the-middle HTTP proxy returns a first response line of
# 1023+ bytes without a '\n' terminator.
#
# Pre-fix: the read loop walked buffer[0..sizeof-2] one byte at a
# time, then post-loop logic did "if (*cp != '\n') cp++; *cp-- =
# '\0';". If no newline arrived before the loop filled the buffer,
# cp was left at &buffer[sizeof-1] (never written by the loop),
# *cp held stale stack bytes, and cp++ pushed cp one past the array.
# The null-termination then wrote one byte out of bounds on the
# stack. AddressSanitizer reports stack-buffer-overflow at the
# null-termination site.
#
# Post-fix: the bound-exhaustion case is detected by position and
# rejected with an "proxy response line too long" message, so no
# OOB write occurs and rsync exits with a non-signal status.
. "$suitedir/rsync.fns"
command -v python3 >/dev/null 2>&1 || test_skipped "python3 not available"
workdir="$scratchdir/workdir"
mkdir -p "$workdir"
cd "$workdir"
port_file="$workdir/port"
proxy_log="$workdir/proxy.log"
# A minimal TCP listener: binds to an ephemeral port on 127.0.0.1,
# writes the chosen port to $port_file *before* accept() so the test
# can synchronise without a sleep, accepts one connection, reads
# until end-of-headers or 64 KiB, sends exactly 1023 bytes of 'X'
# with no '\n', then closes.
python3 - "$port_file" >"$proxy_log" 2>&1 <<'PYEOF' &
import socket, sys, os
port_file = sys.argv[1]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
tmp = port_file + ".tmp"
with open(tmp, "w") as fp:
fp.write("%d\n" % port)
os.rename(tmp, port_file) # atomic visibility to the shell side
s.listen(1)
conn, _ = s.accept()
conn.settimeout(5)
try:
data = b""
while b"\r\n\r\n" not in data and len(data) < 65536:
chunk = conn.recv(8192)
if not chunk:
break
data += chunk
except socket.timeout:
pass
conn.sendall(b"X" * 1023) # exactly the buffer-1 trigger size
try:
conn.shutdown(socket.SHUT_RDWR)
except OSError:
pass
conn.close()
s.close()
PYEOF
proxy_pid=$!
# Wait up to ~10s for the listener to publish its port.
i=0
while [ ! -s "$port_file" ] && [ $i -lt 10 ]; do
sleep 1
i=$((i + 1))
done
if [ ! -s "$port_file" ]; then
kill "$proxy_pid" 2>/dev/null
cat "$proxy_log" >&2 2>/dev/null
test_fail "proxy listener never published a port"
fi
port=`cat "$port_file"`
case "$port" in
*[!0-9]*|"") kill "$proxy_pid" 2>/dev/null; test_fail "bogus port from listener: '$port'" ;;
esac
# Run rsync through the malicious proxy. Any rsync:// URL works:
# the proxy intercepts the CONNECT and never forwards anywhere.
rsync_err="$workdir/rsync.err"
# rsync MUST exit non-zero here (the proxy is misbehaving).
# Use `|| status=$?` so we capture the real exit code under `sh -e`;
# `if ! cmd; then status=$?` would only ever see 0 because the `!`
# is the last command before `$?`.
status=0
RSYNC_PROXY="127.0.0.1:$port" \
$RSYNC rsync://example.invalid:873/whatever/ "$workdir/out/" \
>/dev/null 2>"$rsync_err" || status=$?
# Reap the listener.
wait "$proxy_pid" 2>/dev/null || true
# 1. rsync must not have crashed (SIGSEGV/SIGABRT report >= 128).
if [ "$status" -ge 128 ]; then
cat "$rsync_err" >&2
test_fail "rsync killed by signal (status=$status) -- possible stack OOB regression"
fi
# 2. rsync must have actually exited non-zero (i.e. saw the bad proxy).
if [ "$status" -eq 0 ]; then
cat "$rsync_err" >&2
test_fail "rsync returned success despite malformed proxy response"
fi
# 3. The new error message must appear.
if ! grep -q "proxy response line too long" "$rsync_err"; then
cat "$rsync_err" >&2
test_fail "expected 'proxy response line too long' in rsync stderr"
fi
echo "OK: over-long proxy response line rejected cleanly without crashing"
exit 0

View File

@@ -1,55 +0,0 @@
#!/bin/sh
. "$suitedir/rsync.fns"
test_symlink() {
is_a_link "$1" || test_fail "File $1 is not a symlink"
}
test_regular() {
if [ ! -f "$1" ]; then
test_fail "File $1 is not regular file or not exists"
fi
}
test_notexist() {
if [ -e "$1" ]; then
test_fail "File $1 exists"
fi
if [ -h "$1" ]; then
test_fail "File $1 exists as a symlink"
fi
}
cd "$tmpdir"
mkdir from
mkdir "from/safe"
mkdir "from/unsafe"
mkdir "from/safe/files"
mkdir "from/safe/links"
touch "from/safe/files/file1"
touch "from/safe/files/file2"
touch "from/unsafe/unsafefile"
ln -s ../files/file1 "from/safe/links/"
ln -s ../files/file2 "from/safe/links/"
ln -s ../../unsafe/unsafefile "from/safe/links/"
ln -s a/a/a/../../../unsafe2 "from/safe/links/"
#echo "LISTING FROM"
#ls -lR from
echo "rsync with relative path and just -a"
$RSYNC -avv --safe-links from/safe/ to
#echo "LISTING TO"
#ls -lR to
test_symlink to/links/file1
test_symlink to/links/file2
test_notexist to/links/unsafefile
test_notexist to/links/unsafe2

View File

@@ -1,34 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex audit Finding 5: secure_relative_open()'s
# front-door input check rejects "../foo" and "foo/../bar" but
# misses bare "..", "subdir/..", and other variants whose "/"-split
# components contain a literal "..". The kernel-enforced
# RESOLVE_BENEATH (Linux 5.6+) and O_RESOLVE_BENEATH
# (FreeBSD 13+, macOS 15+) reject these in-kernel; the per-component
# walk fallback used on NetBSD, OpenBSD, Solaris, Cygwin and pre-5.6
# Linux does not -- so the validation must happen at the front door.
#
# This test invokes the t_secure_relpath helper, which calls
# secure_relative_open() with each suspect input and verifies the
# return value is -1 with errno == EINVAL. EINVAL is the marker
# that the front-door rejected the input, not the kernel; pre-fix
# the kernel returns -1 with EXDEV (or, on the per-component
# fallback, may return a valid fd at all -- "escape").
. "$suitedir/rsync.fns"
testdir="$scratchdir/relpath-test"
rm -rf "$testdir"
mkdir -p "$testdir"
if ! "$TOOLDIR/t_secure_relpath" "$testdir"; then
test_fail "t_secure_relpath rejected one or more inputs incorrectly (see stderr above for the specific case)"
fi
exit 0

View File

@@ -1,90 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex re-check finding: the sender-side file-
# list generator can still follow an attacker-planted symlink out of
# the module via change_pathname() -> change_dir(...,CD_SKIP_CHDIR)
# followed by change_dir(...,CD_NORMAL). The CD_SKIP_CHDIR sets
# skipped_chdir=1, and the next CD_NORMAL call's secure-branch in
# util1.c is gated on `!skipped_chdir`, so the secure path is
# bypassed and a raw chdir(curr_dir) follows attacker-controlled
# symlinks during flist generation.
#
# Reach: rsync daemon module with `use chroot = no`. A local
# attacker plants module/cd -> /outside. A client (innocent or
# malicious) pulls rsync://<daemon>/<module>/cd/. The daemon, as
# sender, enumerates files in /outside and ships their metadata
# (names, sizes, modes, mtimes) to the client. The actual content
# transfer fails later at the secure_relative_open step with EXDEV,
# but by then the metadata has already leaked.
#
# We detect by running a dry-run pull of the symlinked subdir and
# checking whether the client's --list-only output mentions any
# file from /outside. With the bug, /outside/secret.txt appears in
# the list with its size; with the fix, the daemon's chdir into
# the symlinked subdir is rejected and no /outside file is listed.
. "$suitedir/rsync.fns"
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure change_dir relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
outside="$scratchdir/outside"
listfile="$scratchdir/listed.txt"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside"
mkdir -p "$mod" "$outside"
# Outside-the-module file the daemon should NOT enumerate to clients.
# A distinctive name + non-trivial size makes the leak easy to spot.
echo "OUTSIDE_PROTECTED_FILE_USED_AS_LEAK_DETECTOR" > "$outside/leak_marker.txt"
chmod 0644 "$outside/leak_marker.txt"
# The symlink trap planted by the local attacker.
ln -s "$outside" "$mod/cd"
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
# Pull recursively into the symlinked subdir with dry-run + verbose,
# capturing the daemon's flist (file list) on stdout. If the daemon
# enumerates /outside, leak_marker.txt will appear in the listing.
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -nrv rsync://localhost/upload/cd/ "$scratchdir/dst/" \
> "$listfile" 2>&1 || true
if grep -q "leak_marker\.txt" "$listfile"; then
echo "----- leaked listing follows" >&2
sed 's/^/ /' "$listfile" >&2
echo "----- leaked listing ends" >&2
test_fail "sender flist leak: outside/leak_marker.txt was enumerated to the client (daemon's chdir followed the cd symlink during flist generation)"
fi
exit 0

View File

@@ -1,11 +0,0 @@
#!/bin/sh
# Test SIMD checksum implementations against the C reference
. "$suitedir/rsync.fns"
if ! test -x "$TOOLDIR/simdtest"; then
test_skipped "simdtest not built (SIMD not available)"
fi
"$TOOLDIR/simdtest"

View File

@@ -1,259 +0,0 @@
#!/bin/sh
# Test that updating a file through a directory symlink works when using
# -K (--copy-dirlinks). This is a regression test for:
# https://github.com/RsyncProject/rsync/issues/715
#
# The CVE fix in commit c35e283 introduced secure_relative_open() which
# uses O_NOFOLLOW on all path components, breaking legitimate directory
# symlinks on the receiver side. The fix splits the path into basedir
# (dirname, symlinks followed) and basename (O_NOFOLLOW) so that
# directory symlinks are traversed while the final file component is
# still protected.
#
# The regression only manifests when delta matching is triggered (i.e.,
# the sender finds matching blocks in the old file). Small files with
# completely different content are transferred in full and don't trigger
# the bug. We use a large file with a small modification to ensure
# delta transfer is used.
#
# In addition to the original regression, this test covers edge cases
# in the fix itself:
# - --backup with directory symlinks (finish_transfer pointer identity)
# - --partial-dir with protocol < 29 (fnamecmp != partialptr guard)
# - --inplace with directory symlinks (updating_basis_or_equiv check)
# - Files without a dirname (top-level files, no split needed)
. "$suitedir/rsync.fns"
# secure_relative_open() uses kernel-enforced "stay below dirfd" via
# openat2(RESOLVE_BENEATH) on Linux 5.6+ and openat(O_RESOLVE_BENEATH)
# on FreeBSD 13+. Other platforms fall back to a per-component
# O_NOFOLLOW walk that rejects every symlink including legitimate
# directory symlinks -- the very case this test exercises. Skip on
# those rather than report a known failure.
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure_relative_open lacks RESOLVE_BENEATH equivalent on $(uname -s); issue #715 still affects this platform"
;;
esac
RSYNC_RSH="$scratchdir/src/support/lsh.sh"
export RSYNC_RSH
# $HOME is set to $scratchdir by rsync.fns
# localhost: destination will cd to $HOME (i.e., $scratchdir)
# Helper: create a large file suitable for delta transfers.
# ~32KB is large enough for rsync's block matching to find matches.
make_testfile() {
dd if=/dev/urandom of="$1" bs=1024 count=32 2>/dev/null \
|| test_fail "failed to create test file $1"
}
# Set up source tree
srcbase="$tmpdir/src"
######################################################################
# Test 1: Basic directory symlink update (the original issue #715)
######################################################################
mkdir -p "$HOME/real-dir"
ln -s real-dir "$HOME/dir"
mkdir -p "$srcbase/dir"
make_testfile "$srcbase/dir/file"
# First transfer (initial): should create the file through the symlink
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 1: initial transfer failed"
if [ ! -f "$HOME/real-dir/file" ]; then
test_fail "test 1: initial transfer did not create file through symlink"
fi
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 1: initial transfer content mismatch"
# Small modification to trigger delta transfer
echo "appended update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
# Second transfer (update): was failing with "failed verification"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 1: update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 1: update transfer content mismatch"
######################################################################
# Test 2: Compression (-z) as in the original reproducer
######################################################################
echo "another line" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptzv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 2: compressed update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 2: compressed update content mismatch"
######################################################################
# Test 3: Nested directory symlinks (nested/sub/data.txt where
# "nested" is a symlink to "nested_real")
######################################################################
mkdir -p "$HOME/nested_real/sub"
ln -s nested_real "$HOME/nested"
mkdir -p "$srcbase/nested/sub"
make_testfile "$srcbase/nested/sub/data.txt"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" nested/sub/data.txt localhost:) \
|| test_fail "test 3: initial nested transfer failed"
echo "appended nested" >> "$srcbase/nested/sub/data.txt"
sleep 1
touch "$srcbase/nested/sub/data.txt"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" nested/sub/data.txt localhost:) \
|| test_fail "test 3: update through nested directory symlink failed"
diff "$srcbase/nested/sub/data.txt" "$HOME/nested_real/sub/data.txt" >/dev/null \
|| test_fail "test 3: nested update content mismatch"
######################################################################
# Test 4: --backup with directory symlinks
#
# Exercises the finish_transfer() "fnamecmp == fname" pointer
# comparison that determines whether to update fnamecmp to the
# backup name. If broken, --backup would reference a renamed file
# for xattr handling.
######################################################################
# Reset destination
rm -f "$HOME/real-dir/file" "$HOME/real-dir/file~"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 4: initial transfer for backup test failed"
echo "backup update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --backup --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 4: update with --backup through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 4: backup update content mismatch"
if [ ! -f "$HOME/real-dir/file~" ]; then
test_fail "test 4: backup file was not created"
fi
######################################################################
# Test 5: --inplace with directory symlinks
#
# Exercises the updating_basis_or_equiv check which uses
# "fnamecmp == fname". With --inplace, rsync writes directly to
# the destination file instead of a temp file.
######################################################################
rm -f "$HOME/real-dir/file" "$HOME/real-dir/file~"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --inplace --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 5: initial inplace transfer failed"
echo "inplace update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --inplace --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 5: inplace update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 5: inplace update content mismatch"
######################################################################
# Test 6: Top-level file (no dirname, no split needed)
#
# Ensures the dirname/basename split is not attempted for files
# at the top level (file->dirname is NULL).
######################################################################
make_testfile "$srcbase/topfile"
mkdir -p "$HOME"
(cd "$srcbase" && $RSYNC -Rlptv --rsync-path="$RSYNC" topfile localhost:) \
|| test_fail "test 6: initial top-level transfer failed"
echo "toplevel update" >> "$srcbase/topfile"
sleep 1
touch "$srcbase/topfile"
(cd "$srcbase" && $RSYNC -Rlptv --rsync-path="$RSYNC" topfile localhost:) \
|| test_fail "test 6: top-level update failed"
diff "$srcbase/topfile" "$HOME/topfile" >/dev/null \
|| test_fail "test 6: top-level update content mismatch"
######################################################################
# Test 7: --partial-dir with protocol < 29
#
# For protocol < 29, fnamecmp_type stays FNAMECMP_FNAME even when
# fnamecmp is set to partialptr. The dirname/basename split must
# NOT trigger in this case (guarded by "fnamecmp == fname").
######################################################################
rm -f "$HOME/real-dir/file"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 --partial-dir=.rsync-partial \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 7: initial proto28 partial-dir transfer failed"
echo "partial-dir update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 --partial-dir=.rsync-partial \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 7: proto28 partial-dir update through dirlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 7: proto28 partial-dir update content mismatch"
######################################################################
# Test 8: Protocol < 29 basic directory symlink update
#
# Exercises the protocol < 29 code path and its fallback logic
# (clearing basedir on retry).
######################################################################
rm -f "$HOME/real-dir/file"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 8: initial proto28 transfer failed"
echo "proto28 update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 8: proto28 update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 8: proto28 update content mismatch"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -40,7 +40,7 @@ test_unsafe ..//../dest from/dir unsafe
test_unsafe .. from/file safe
test_unsafe ../.. from/file unsafe
test_unsafe ..//.. from//file unsafe
test_unsafe dir/.. from unsafe
test_unsafe dir/.. from safe
test_unsafe dir/../.. from unsafe
test_unsafe dir/..//.. from unsafe

View File

@@ -38,10 +38,7 @@ EOF
xls() {
for fn in "${@}"; do
runat "$fn" "$SHELL_PATH" <<EOF
for x in *; do
case "\$x" in SUNWattr_*) continue;; esac
echo "\$x=\`cat \$x\`"
done
for x in *; do echo "\$x=\`cat \$x\`"; done
EOF
done
}

5
tls.c
View File

@@ -49,9 +49,6 @@ int list_only = 0;
int link_times = 0;
int link_owner = 0;
int nsec_times = 0;
int safe_symlinks = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
#ifdef SUPPORT_XATTRS
@@ -127,7 +124,7 @@ static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
{
if (t) {
int len;
struct tm tmp, *mt = gmtime_r(&t, &tmp);
struct tm *mt = gmtime(&t);
len = snprintf(dest, destsize,
" %04d-%02d-%02d %02d:%02d:%02d",

102
token.c
View File

@@ -291,14 +291,6 @@ static int32 simple_recv_token(int f, char **data)
int32 i = read_int(f);
if (i <= 0)
return i;
/* simple_send_token caps each literal chunk at CHUNK_SIZE;
* reject anything larger so a hostile peer cannot drive the
* read_buf below past our static CHUNK_SIZE buffer. */
if (i > CHUNK_SIZE) {
rprintf(FERROR, "invalid uncompressed token length %ld [%s]\n",
(long)i, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
residue = i;
}
@@ -501,52 +493,9 @@ static char *cbuf;
static char *dbuf;
/* for decoding runs of tokens */
#define MAX_TOKEN_INDEX ((int32)0x7ffffffe)
static int32 rx_token;
static int32 rx_run;
static NORETURN void invalid_compressed_token(void)
{
rprintf(FERROR, "invalid token number in compressed stream\n");
exit_cleanup(RERR_PROTOCOL);
}
static int32 recv_compressed_token_num(int f, int32 flag)
{
if (flag & TOKEN_REL) {
int32 incr = flag & 0x3f;
if (rx_token > MAX_TOKEN_INDEX - incr)
invalid_compressed_token();
rx_token += incr;
flag >>= 6;
} else {
rx_token = read_int(f);
if (rx_token < 0 || rx_token > MAX_TOKEN_INDEX)
invalid_compressed_token();
}
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
if (rx_run <= 0 || rx_token > MAX_TOKEN_INDEX - rx_run)
invalid_compressed_token();
recv_state = r_running;
}
return -1 - rx_token;
}
static int32 recv_compressed_token_run(void)
{
if (rx_run <= 0 || rx_token >= MAX_TOKEN_INDEX)
invalid_compressed_token();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
/* Receive a deflated token and inflate it */
static int32 recv_deflated_token(int f, char **data)
{
@@ -637,7 +586,17 @@ static int32 recv_deflated_token(int f, char **data)
}
/* here we have a token of some kind */
return recv_compressed_token_num(f, flag);
if (flag & TOKEN_REL) {
rx_token += flag & 0x3f;
flag >>= 6;
} else
rx_token = read_int(f);
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
recv_state = r_running;
}
return -1 - rx_token;
case r_inflating:
rx_strm.next_out = (Bytef *)dbuf;
@@ -657,7 +616,10 @@ static int32 recv_deflated_token(int f, char **data)
break;
case r_running:
return recv_compressed_token_run();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
}
}
@@ -866,7 +828,17 @@ static int32 recv_zstd_token(int f, char **data)
return 0;
}
/* here we have a token of some kind */
return recv_compressed_token_num(f, flag);
if (flag & TOKEN_REL) {
rx_token += flag & 0x3f;
flag >>= 6;
} else
rx_token = read_int(f);
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
recv_state = r_running;
}
return -1 - rx_token;
case r_inflated: /* zstd doesn't get into this state */
break;
@@ -897,7 +869,10 @@ static int32 recv_zstd_token(int f, char **data)
break;
case r_running:
return recv_compressed_token_run();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
}
}
@@ -1017,7 +992,17 @@ static int32 recv_compressed_token(int f, char **data)
}
/* here we have a token of some kind */
return recv_compressed_token_num(f, flag);
if (flag & TOKEN_REL) {
rx_token += flag & 0x3f;
flag >>= 6;
} else
rx_token = read_int(f);
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
recv_state = r_running;
}
return -1 - rx_token;
case r_inflating:
avail_out = LZ4_decompress_safe(next_in, dbuf, avail_in, size);
@@ -1033,7 +1018,10 @@ static int32 recv_compressed_token(int f, char **data)
break;
case r_running:
return recv_compressed_token_run();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
}
}

View File

@@ -26,8 +26,6 @@ int am_root = 0;
int am_sender = 1;
int read_only = 1;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
int
main(int argc, char **argv)

146
util1.c
View File

@@ -141,7 +141,7 @@ int set_times(const char *fname, STRUCT_STAT *stp)
#ifdef HAVE_UTIMENSAT
#include "case_N.h"
if (do_utimensat_at(fname, stp) == 0)
if (do_utimensat(fname, stp) == 0)
break;
if (errno != ENOSYS)
return -1;
@@ -336,13 +336,7 @@ static int unlink_and_reopen(const char *dest, mode_t mode)
mode |= S_IWUSR;
#endif
mode &= INITACCESSPERMS;
/* Use do_open_at so the create/truncate goes through a secure
* parent dirfd in the daemon-no-chroot deployment. Otherwise
* an attacker could swap a parent component with a symlink in
* the window between robust_unlink (which uses do_unlink_at,
* already secure) and the create here, and redirect the new
* file outside the module. */
if ((ofd = do_open_at(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
errno = save_errno;
@@ -366,23 +360,12 @@ static int unlink_and_reopen(const char *dest, mode_t mode)
* --copy-dest options. */
int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
{
extern int am_daemon, am_chrooted;
int ifd, ofd;
char buf[1024 * 8];
int len; /* Number of bytes read into `buf'. */
OFF_T prealloc_len = 0, offset = 0;
/* On a daemon without chroot, route the source open through
* secure_relative_open so a parent-symlink on the source path
* (e.g. --copy-dest=cd where cd is a symlink to an outside
* directory) cannot redirect the read to a file the daemon can
* see but the attacker should not. Plain do_open_nofollow only
* refuses a final-component symlink; parents are still followed. */
if (am_daemon && !am_chrooted && source && *source && source[0] != '/')
ifd = secure_relative_open(NULL, source, O_RDONLY | O_NOFOLLOW, 0);
else
ifd = do_open_nofollow(source, O_RDONLY);
if (ifd < 0) {
if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
errno = save_errno;
@@ -496,13 +479,13 @@ int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
int robust_unlink(const char *fname)
{
#ifndef ETXTBSY
return do_unlink_at(fname);
return do_unlink(fname);
#else
static int counter = 1;
int rc, pos, start;
char path[MAXPATHLEN];
rc = do_unlink_at(fname);
rc = do_unlink(fname);
if (rc == 0 || errno != ETXTBSY)
return rc;
@@ -532,7 +515,7 @@ int robust_unlink(const char *fname)
}
/* maybe we should return rename()'s exit status? Nah. */
if (do_rename_at(fname, path) != 0) {
if (do_rename(fname, path) != 0) {
errno = ETXTBSY;
return -1;
}
@@ -555,7 +538,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
return 0;
while (tries--) {
if (do_rename_at(from, to) == 0)
if (do_rename(from, to) == 0)
return 0;
switch (errno) {
@@ -576,7 +559,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
}
if (copy_file(from, to, -1, mode) != 0)
return -2;
do_unlink_at(from);
do_unlink(from);
return 1;
default:
return -1;
@@ -959,7 +942,7 @@ int count_dir_elements(const char *p)
* resulting name would be empty, returns ".". */
int clean_fname(char *name, int flags)
{
char *limit = name, *t = name, *f = name;
char *limit = name - 1, *t = name, *f = name;
int anchored;
if (!name)
@@ -1004,13 +987,9 @@ int clean_fname(char *name, int flags)
f += 2;
continue;
}
/* backing up for ".." — avoid reading before 'name' */
while (s > limit && s[-1] != '/')
s--;
/* If found prior '/', or we reached the start, adjust t. */
if (s != t - 1 && (s <= name || *s == '/')) {
t = (s == name) ? name : s + 1;
while (s > limit && *--s != '/') {}
if (s != t - 1 && (s < name || *s == '/')) {
t = s + 1;
f += 2;
continue;
}
@@ -1133,7 +1112,6 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, i
* Also cleans the path using the clean_fname() function. */
int change_dir(const char *dir, int set_path_only)
{
extern int am_daemon, am_chrooted;
static int initialised, skipped_chdir;
unsigned int len;
@@ -1172,57 +1150,10 @@ int change_dir(const char *dir, int set_path_only)
curr_dir[curr_dir_len++] = '/';
memcpy(curr_dir + curr_dir_len, dir, len + 1);
if (!set_path_only) {
int chdir_failed;
/* In the daemon-without-chroot deployment we must not
* follow a symlink in any component of the chdir
* target -- otherwise CWD escapes the module and
* every subsequent path-relative syscall (open,
* chmod, lchown, ...) inherits the escape, which
* defeats secure_relative_open's RESOLVE_BENEATH
* anchor and re-opens the CVE-2026-29518 class of
* symlink TOCTOU attacks. Use the secure resolver
* to get a confined dirfd, then fchdir() to it.
*
* If skipped_chdir is set, a previous CD_SKIP_CHDIR
* call buffered an absolute prefix in curr_dir
* (e.g. change_pathname's CD_SKIP_CHDIR to orig_dir)
* without syncing the kernel's CWD. Resolve `dir`
* relative to that prefix as basedir so the secure
* branch still anchors at the operator-trusted
* directory rather than wherever the kernel CWD
* happens to be. */
if (am_daemon && !am_chrooted) {
const char *basedir = NULL;
char prefix[MAXPATHLEN];
int dfd;
if (skipped_chdir) {
if (save_dir_len >= sizeof prefix) {
errno = ENAMETOOLONG;
chdir_failed = 1;
goto chdir_cleanup;
}
memcpy(prefix, curr_dir, save_dir_len);
prefix[save_dir_len] = '\0';
basedir = prefix;
}
dfd = secure_relative_open(basedir, dir,
O_RDONLY | O_DIRECTORY, 0);
if (dfd < 0) {
chdir_failed = 1;
} else {
chdir_failed = fchdir(dfd) != 0;
close(dfd);
}
} else {
chdir_failed = chdir(curr_dir) != 0;
}
chdir_cleanup:
if (chdir_failed) {
curr_dir_len = save_dir_len;
curr_dir[curr_dir_len] = '\0';
return 0;
}
if (!set_path_only && chdir(curr_dir)) {
curr_dir_len = save_dir_len;
curr_dir[curr_dir_len] = '\0';
return 0;
}
skipped_chdir = set_path_only;
}
@@ -1350,20 +1281,20 @@ int handle_partial_dir(const char *fname, int create)
dir = partial_fname;
if (create) {
STRUCT_STAT st;
int statret = do_lstat_at(dir, &st);
int statret = do_lstat(dir, &st);
if (statret == 0 && !S_ISDIR(st.st_mode)) {
if (do_unlink_at(dir) < 0) {
if (do_unlink(dir) < 0) {
*fn = '/';
return 0;
}
statret = -1;
}
if (statret < 0 && do_mkdir_at(dir, 0700) < 0) {
if (statret < 0 && do_mkdir(dir, 0700) < 0) {
*fn = '/';
return 0;
}
} else
do_rmdir_at(dir);
do_rmdir(dir);
*fn = '/';
return 1;
@@ -1387,14 +1318,7 @@ int handle_partial_dir(const char *fname, int create)
*
* "src" is the top source directory currently applicable at the level
* of the referenced symlink. This is usually the symlink's full path
* (including its name), as referenced from the root of the transfer.
*
* NOTE: this also rejects dest names with a .. component in other
* than the first component of the name ie. it rejects names such as
* a/b/../x/y. This needs to be done as the leading subpaths 'a' or
* 'b' could later be replaced with symlinks such as a link to '.'
* resulting in the link being transferred now becoming unsafe
*/
* (including its name), as referenced from the root of the transfer. */
int unsafe_symlink(const char *dest, const char *src)
{
const char *name, *slash;
@@ -1404,23 +1328,6 @@ int unsafe_symlink(const char *dest, const char *src)
if (!dest || !*dest || *dest == '/')
return 1;
// reject destinations with /../ in the name other than at the start of the name
const char *dest2 = dest;
while (strncmp(dest2, "../", 3) == 0) {
dest2 += 3;
while (*dest2 == '/') {
// allow for ..//..///../foo
dest2++;
}
}
if (strstr(dest2, "/../"))
return 1;
// reject if the destination ends in /..
const size_t dlen = strlen(dest);
if (dlen > 3 && strcmp(&dest[dlen-3], "/..") == 0)
return 1;
/* find out what our safety margin is */
for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) {
/* ".." segment starts the count over. "." segment is ignored. */
@@ -1458,13 +1365,8 @@ char *timestring(time_t t)
static int ndx = 0;
static char buffers[4][20]; /* We support 4 simultaneous timestring results. */
char *TimeBuf = buffers[ndx = (ndx + 1) % 4];
struct tm tmp, *tm = localtime_r(&t, &tmp);
int len;
if (!tm) {
strlcpy(TimeBuf, "(time out of range)", sizeof buffers[0]);
return TimeBuf;
}
len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
struct tm *tm = localtime(&t);
int len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
(int)tm->tm_year + 1900, (int)tm->tm_mon + 1, (int)tm->tm_mday,
(int)tm->tm_hour, (int)tm->tm_min, (int)tm->tm_sec);
assert(len > 0); /* Silence gcc warning */
@@ -1788,8 +1690,6 @@ void *expand_item_list(item_list *lp, size_t item_size, const char *desc, int in
new_ptr == lp->items ? " not" : "");
}
memset((char *)new_ptr + lp->malloced * item_size, 0,
(expand_size - lp->malloced) * item_size);
lp->items = new_ptr;
lp->malloced = expand_size;
}

View File

@@ -79,7 +79,9 @@ void *my_alloc(void *ptr, size_t num, size_t size, const char *file, int line)
who_am_i(), do_big_num(max_alloc, 0, NULL), src_file(file), line);
exit_cleanup(RERR_MALLOC);
}
if (!ptr || ptr == do_calloc)
if (!ptr)
ptr = malloc(num * size);
else if (ptr == do_calloc)
ptr = calloc(num, size);
else
ptr = realloc(ptr, num * size);

View File

@@ -1,2 +1,2 @@
#define RSYNC_VERSION "3.2.7"
#define RSYNC_VERSION "3.3.0pre1"
#define MAINTAINER_TZ_OFFSET -7.0

View File

@@ -32,9 +32,7 @@ int fnmatch_errors = 0;
int wildmatch_errors = 0;
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L
typedef char bool;
#endif
int output_iterations = 0;
int explode_mod = 0;

View File

@@ -697,13 +697,6 @@ int recv_xattr_request(struct file_struct *file, int f_in)
rxa = lst->items;
num = 0;
while ((rel_pos = read_varint(f_in)) != 0) {
/* Detect signed overflow before the accumulating add. A hostile
* peer could otherwise wrap 'num' to land on an arbitrary value. */
if ((rel_pos > 0 && num > INT_MAX - rel_pos)
|| (rel_pos < 0 && num < INT_MIN - rel_pos)) {
rprintf(FERROR, "xattr rel_pos accumulation overflow [%s]\n", who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
num += rel_pos;
if (am_sender) {
/* The sender-related num values are only in order on the sender.
@@ -749,7 +742,7 @@ int recv_xattr_request(struct file_struct *file, int f_in)
}
old_datum = rxa->datum;
rxa->datum_len = read_varint_size(f_in, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
rxa->datum_len = read_varint(f_in);
if (SIZE_MAX - rxa->name_len < rxa->datum_len)
overflow_exit("recv_xattr_request");
@@ -790,8 +783,7 @@ void receive_xattr(int f, struct file_struct *file)
return;
}
count = read_varint_bounded(f, 0, MAX_WIRE_XATTR_COUNT, "xattr count");
if (count != 0) {
if ((count = read_varint(f)) != 0) {
(void)EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count);
temp_xattr.count = 0;
}
@@ -799,8 +791,8 @@ void receive_xattr(int f, struct file_struct *file)
for (num = 1; num <= count; num++) {
char *ptr, *name;
rsync_xa *rxa;
size_t name_len = read_varint_size(f, MAX_WIRE_XATTR_NAMELEN, "xattr name_len");
size_t datum_len = read_varint_size(f, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
size_t name_len = read_varint(f);
size_t datum_len = read_varint(f);
size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)
@@ -868,8 +860,8 @@ void receive_xattr(int f, struct file_struct *file)
rxa->num = num;
}
if (need_sort && temp_xattr.count > 1)
qsort(temp_xattr.items, temp_xattr.count, sizeof (rsync_xa), rsync_xal_compare_names);
if (need_sort && count > 1)
qsort(temp_xattr.items, count, sizeof (rsync_xa), rsync_xal_compare_names);
ndx = rsync_xal_store(&temp_xattr); /* adds item to rsync_xal_l */
@@ -1094,7 +1086,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
&& !S_ISLNK(sxp->st.st_mode)
#endif
&& access(fname, W_OK) < 0
&& do_chmod_at(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
&& do_chmod(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
added_write_perm = 1;
ndx = F_XATTR(file);
@@ -1102,7 +1094,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
lst = &glst->xa_items;
int return_value = rsync_xal_set(fname, lst, fnamecmp, sxp);
if (added_write_perm) /* remove the temporary write permission */
do_chmod_at(fname, sxp->st.st_mode);
do_chmod(fname, sxp->st.st_mode);
return return_value;
}
@@ -1219,7 +1211,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
| (S_ISDIR(fst.st_mode) ? 0700 : 0600);
if (fst.st_mode != mode)
do_chmod_at(fname, mode);
do_chmod(fname, mode);
if (!IS_DEVICE(fst.st_mode))
fst.st_rdev = 0; /* just in case */
@@ -1257,12 +1249,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
/* Use the *_at variants so that on a daemon-no-chroot deployment
* the metadata read goes through a secure parent dirfd instead
* of bare path resolution. The *_at wrappers fall through to
* plain do_stat outside the daemon-no-chroot context, so this
* change is transparent for non-daemon use. */
int ret = do_stat_at(fname, fst);
int ret = do_stat(fname, fst);
if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
xst->st_mode = 0;
return ret;
@@ -1270,7 +1257,7 @@ int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
int ret = do_lstat_at(fname, fst);
int ret = do_lstat(fname, fst);
if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
xst->st_mode = 0;
return ret;

View File

@@ -62,7 +62,10 @@ local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
#endif
/* ========================================================================= */
uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len)
uLong ZEXPORT adler32(adler, buf, len)
uLong adler;
const Bytef *buf;
uInt len;
{
unsigned long sum2;
unsigned n;
@@ -130,7 +133,10 @@ uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len)
}
/* ========================================================================= */
local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2)
local uLong adler32_combine_(adler1, adler2, len2)
uLong adler1;
uLong adler2;
z_off64_t len2;
{
unsigned long sum1;
unsigned long sum2;
@@ -156,12 +162,18 @@ local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2)
}
/* ========================================================================= */
uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2)
uLong ZEXPORT adler32_combine(adler1, adler2, len2)
uLong adler1;
uLong adler2;
z_off_t len2;
{
return adler32_combine_(adler1, adler2, len2);
}
uLong ZEXPORT adler32_combine64(uLong adler1, uLong adler2, z_off64_t len2)
uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
uLong adler1;
uLong adler2;
z_off64_t len2;
{
return adler32_combine_(adler1, adler2, len2);
}

View File

@@ -19,7 +19,12 @@
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
Z_STREAM_ERROR if the level parameter is invalid.
*/
int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
uLong sourceLen;
int level;
{
z_stream stream;
int err;
@@ -54,7 +59,11 @@ int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong
/* ===========================================================================
*/
int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
int ZEXPORT compress (dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
uLong sourceLen;
{
return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION);
}
@@ -63,7 +72,8 @@ int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong s
If the default memLevel or windowBits for deflateInit() is changed, then
this function needs to be updated.
*/
uLong ZEXPORT compressBound (uLong sourceLen)
uLong ZEXPORT compressBound (sourceLen)
uLong sourceLen;
{
return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
(sourceLen >> 25) + 13;

View File

@@ -87,7 +87,7 @@ local void make_crc_table OF((void));
allow for word-at-a-time CRC calculation for both big-endian and little-
endian machines, where a word is four bytes.
*/
local void make_crc_table(void)
local void make_crc_table()
{
z_crc_t c;
int n, k;
@@ -164,7 +164,9 @@ local void make_crc_table(void)
}
#ifdef MAKECRCH
local void write_table(FILE *out, const z_crc_t FAR *table)
local void write_table(out, table)
FILE *out;
const z_crc_t FAR *table;
{
int n;
@@ -185,7 +187,7 @@ local void write_table(FILE *out, const z_crc_t FAR *table)
/* =========================================================================
* This function can be used by asm versions of crc32()
*/
const z_crc_t FAR * ZEXPORT get_crc_table(void)
const z_crc_t FAR * ZEXPORT get_crc_table()
{
#ifdef DYNAMIC_CRC_TABLE
if (crc_table_empty)
@@ -199,7 +201,10 @@ const z_crc_t FAR * ZEXPORT get_crc_table(void)
#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
/* ========================================================================= */
unsigned long ZEXPORT crc32(unsigned long crc, const unsigned char FAR *buf, uInt len)
unsigned long ZEXPORT crc32(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
uInt len;
{
if (buf == Z_NULL) return 0UL;
@@ -239,7 +244,10 @@ unsigned long ZEXPORT crc32(unsigned long crc, const unsigned char FAR *buf, uIn
#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
/* ========================================================================= */
local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf, unsigned len)
local unsigned long crc32_little(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
unsigned len;
{
register z_crc_t c;
register const z_crc_t FAR *buf4;
@@ -276,7 +284,10 @@ local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf
#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
/* ========================================================================= */
local unsigned long crc32_big(unsigned long crc, const unsigned char FAR *buf, unsigned len)
local unsigned long crc32_big(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
unsigned len;
{
register z_crc_t c;
register const z_crc_t FAR *buf4;
@@ -311,7 +322,9 @@ local unsigned long crc32_big(unsigned long crc, const unsigned char FAR *buf, u
#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */
/* ========================================================================= */
local unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec)
local unsigned long gf2_matrix_times(mat, vec)
unsigned long *mat;
unsigned long vec;
{
unsigned long sum;
@@ -326,7 +339,9 @@ local unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec)
}
/* ========================================================================= */
local void gf2_matrix_square(unsigned long *square, unsigned long *mat)
local void gf2_matrix_square(square, mat)
unsigned long *square;
unsigned long *mat;
{
int n;
@@ -335,7 +350,10 @@ local void gf2_matrix_square(unsigned long *square, unsigned long *mat)
}
/* ========================================================================= */
local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2)
local uLong crc32_combine_(crc1, crc2, len2)
uLong crc1;
uLong crc2;
z_off64_t len2;
{
int n;
unsigned long row;
@@ -388,12 +406,18 @@ local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2)
}
/* ========================================================================= */
uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2)
uLong ZEXPORT crc32_combine(crc1, crc2, len2)
uLong crc1;
uLong crc2;
z_off_t len2;
{
return crc32_combine_(crc1, crc2, len2);
}
uLong ZEXPORT crc32_combine64(uLong crc1, uLong crc2, z_off64_t len2)
uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
uLong crc1;
uLong crc2;
z_off64_t len2;
{
return crc32_combine_(crc1, crc2, len2);
}

View File

@@ -200,8 +200,11 @@ struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
/* ========================================================================= */
int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version,
int stream_size)
int ZEXPORT deflateInit_(strm, level, version, stream_size)
z_streamp strm;
int level;
const char *version;
int stream_size;
{
return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
Z_DEFAULT_STRATEGY, version, stream_size);
@@ -209,8 +212,16 @@ int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version,
}
/* ========================================================================= */
int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, int windowBits,
int memLevel, int strategy, const char *version, int stream_size)
int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
version, stream_size)
z_streamp strm;
int level;
int method;
int windowBits;
int memLevel;
int strategy;
const char *version;
int stream_size;
{
deflate_state *s;
int wrap = 1;
@@ -348,8 +359,10 @@ int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, int windowBits,
}
/* ========================================================================= */
int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary,
uInt dictLength)
int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
z_streamp strm;
const Bytef *dictionary;
uInt dictLength;
{
deflate_state *s;
uInt str, n;
@@ -415,7 +428,8 @@ int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary,
}
/* ========================================================================= */
int ZEXPORT deflateResetKeep (z_streamp strm)
int ZEXPORT deflateResetKeep (strm)
z_streamp strm;
{
deflate_state *s;
@@ -449,7 +463,8 @@ int ZEXPORT deflateResetKeep (z_streamp strm)
}
/* ========================================================================= */
int ZEXPORT deflateReset (z_streamp strm)
int ZEXPORT deflateReset (strm)
z_streamp strm;
{
int ret;
@@ -460,7 +475,9 @@ int ZEXPORT deflateReset (z_streamp strm)
}
/* ========================================================================= */
int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head)
int ZEXPORT deflateSetHeader (strm, head)
z_streamp strm;
gz_headerp head;
{
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
if (strm->state->wrap != 2) return Z_STREAM_ERROR;
@@ -469,7 +486,10 @@ int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head)
}
/* ========================================================================= */
int ZEXPORT deflatePending (z_streamp strm, unsigned *pending, int *bits)
int ZEXPORT deflatePending (strm, pending, bits)
unsigned *pending;
int *bits;
z_streamp strm;
{
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
if (pending != Z_NULL)
@@ -480,7 +500,10 @@ int ZEXPORT deflatePending (z_streamp strm, unsigned *pending, int *bits)
}
/* ========================================================================= */
int ZEXPORT deflatePrime (z_streamp strm, int bits, int value)
int ZEXPORT deflatePrime (strm, bits, value)
z_streamp strm;
int bits;
int value;
{
deflate_state *s;
int put;
@@ -503,7 +526,10 @@ int ZEXPORT deflatePrime (z_streamp strm, int bits, int value)
}
/* ========================================================================= */
int ZEXPORT deflateParams(z_streamp strm, int level, int strategy)
int ZEXPORT deflateParams(strm, level, strategy)
z_streamp strm;
int level;
int strategy;
{
deflate_state *s;
compress_func func;
@@ -541,8 +567,12 @@ int ZEXPORT deflateParams(z_streamp strm, int level, int strategy)
}
/* ========================================================================= */
int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy,
int nice_length, int max_chain)
int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
z_streamp strm;
int good_length;
int max_lazy;
int nice_length;
int max_chain;
{
deflate_state *s;
@@ -572,7 +602,9 @@ int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy,
* upper bound of about 14% expansion does not seem onerous for output buffer
* allocation.
*/
uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen)
uLong ZEXPORT deflateBound(strm, sourceLen)
z_streamp strm;
uLong sourceLen;
{
deflate_state *s;
uLong complen, wraplen;
@@ -632,7 +664,9 @@ uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen)
* IN assertion: the stream state is correct and there is enough room in
* pending_buf.
*/
local void putShortMSB (deflate_state *s, uInt b)
local void putShortMSB (s, b)
deflate_state *s;
uInt b;
{
put_byte(s, (Byte)(b >> 8));
put_byte(s, (Byte)(b & 0xff));
@@ -644,7 +678,8 @@ local void putShortMSB (deflate_state *s, uInt b)
* to avoid allocating a large strm->next_out buffer and copying into it.
* (See also read_buf()).
*/
local void flush_pending(z_streamp strm)
local void flush_pending(strm)
z_streamp strm;
{
unsigned len;
deflate_state *s = strm->state;
@@ -666,7 +701,9 @@ local void flush_pending(z_streamp strm)
}
/* ========================================================================= */
int ZEXPORT deflate (z_streamp strm, int flush)
int ZEXPORT deflate (strm, flush)
z_streamp strm;
int flush;
{
int old_flush; /* value of flush param for previous deflate call */
deflate_state *s;
@@ -978,7 +1015,8 @@ int ZEXPORT deflate (z_streamp strm, int flush)
}
/* ========================================================================= */
int ZEXPORT deflateEnd (z_streamp strm)
int ZEXPORT deflateEnd (strm)
z_streamp strm;
{
int status;
@@ -1012,7 +1050,9 @@ int ZEXPORT deflateEnd (z_streamp strm)
* To simplify the source, this is not supported for 16-bit MSDOS (which
* doesn't have enough memory anyway to duplicate compression states).
*/
int ZEXPORT deflateCopy (z_streamp dest, z_streamp source)
int ZEXPORT deflateCopy (dest, source)
z_streamp dest;
z_streamp source;
{
#ifdef MAXSEG_64K
return Z_STREAM_ERROR;
@@ -1069,7 +1109,10 @@ int ZEXPORT deflateCopy (z_streamp dest, z_streamp source)
* allocating a large strm->next_in buffer and copying from it.
* (See also flush_pending()).
*/
local int read_buf(z_streamp strm, Bytef *buf, unsigned size)
local int read_buf(strm, buf, size)
z_streamp strm;
Bytef *buf;
unsigned size;
{
unsigned len = strm->avail_in;
@@ -1096,7 +1139,8 @@ local int read_buf(z_streamp strm, Bytef *buf, unsigned size)
/* ===========================================================================
* Initialize the "longest match" routines for a new zlib stream
*/
local void lm_init (deflate_state *s)
local void lm_init (s)
deflate_state *s;
{
s->window_size = (ulg)2L*s->w_size;
@@ -1137,7 +1181,9 @@ local void lm_init (deflate_state *s)
/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
* match.S. The code will be functionally equivalent.
*/
local uInt longest_match(deflate_state *s, IPos cur_match)
local uInt longest_match(s, cur_match)
deflate_state *s;
IPos cur_match; /* current match */
{
unsigned chain_length = s->max_chain_length;/* max hash chain length */
register Bytef *scan = s->window + s->strstart; /* current string */
@@ -1284,7 +1330,9 @@ local uInt longest_match(deflate_state *s, IPos cur_match)
/* ---------------------------------------------------------------------------
* Optimized version for FASTEST only
*/
local uInt longest_match(deflate_state *s, IPos cur_match)
local uInt longest_match(s, cur_match)
deflate_state *s;
IPos cur_match; /* current match */
{
register Bytef *scan = s->window + s->strstart; /* current string */
register Bytef *match; /* matched string */
@@ -1341,7 +1389,10 @@ local uInt longest_match(deflate_state *s, IPos cur_match)
/* ===========================================================================
* Check that the match at match_start is indeed a match.
*/
local void check_match(deflate_state *s, IPos start, IPos match, int length)
local void check_match(s, start, match, length)
deflate_state *s;
IPos start, match;
int length;
{
/* check that the match is indeed a match */
if (zmemcmp(s->window + match,
@@ -1372,7 +1423,8 @@ local void check_match(deflate_state *s, IPos start, IPos match, int length)
* performed for at least two bytes (required for the zip translate_eol
* option -- not supported here).
*/
local void fill_window(deflate_state *s)
local void fill_window(s)
deflate_state *s;
{
register unsigned n, m;
register Posf *p;
@@ -1545,7 +1597,9 @@ local void fill_window(deflate_state *s)
* NOTE: this function should be optimized to avoid extra copying from
* window to pending_buf.
*/
local block_state deflate_stored(deflate_state *s, int flush)
local block_state deflate_stored(s, flush)
deflate_state *s;
int flush;
{
/* Stored blocks are limited to 0xffff bytes, pending_buf is limited
* to pending_buf_size, and each stored block has a 5 byte header:
@@ -1616,7 +1670,9 @@ local block_state deflate_stored(deflate_state *s, int flush)
* new strings in the dictionary only for unmatched strings or for short
* matches. It is used only for the fast compression options.
*/
local block_state deflate_fast(deflate_state *s, int flush)
local block_state deflate_fast(s, flush)
deflate_state *s;
int flush;
{
IPos hash_head; /* head of the hash chain */
int bflush; /* set if current block must be flushed */
@@ -1726,7 +1782,9 @@ local block_state deflate_fast(deflate_state *s, int flush)
* evaluation for matches: a match is finally adopted only if there is
* no better match at the next window position.
*/
local block_state deflate_slow(deflate_state *s, int flush)
local block_state deflate_slow(s, flush)
deflate_state *s;
int flush;
{
IPos hash_head; /* head of hash chain */
int bflush; /* set if current block must be flushed */
@@ -1865,7 +1923,9 @@ local block_state deflate_slow(deflate_state *s, int flush)
* one. Do not maintain a hash table. (It will be regenerated if this run of
* deflate switches away from Z_RLE.)
*/
local block_state deflate_rle(deflate_state *s, int flush)
local block_state deflate_rle(s, flush)
deflate_state *s;
int flush;
{
int bflush; /* set if current block must be flushed */
uInt prev; /* byte at distance one to match */
@@ -1936,7 +1996,9 @@ local block_state deflate_rle(deflate_state *s, int flush)
* For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
* (It will be regenerated if this run of deflate switches away from Huffman.)
*/
local block_state deflate_huff(deflate_state *s, int flush)
local block_state deflate_huff(s, flush)
deflate_state *s;
int flush;
{
int bflush; /* set if current block must be flushed */

View File

@@ -45,7 +45,9 @@
requires strm->avail_out >= 258 for each loop to avoid checking for
output space.
*/
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start)
void ZLIB_INTERNAL inflate_fast(strm, start)
z_streamp strm;
unsigned start; /* inflate()'s starting value for strm->avail_out */
{
struct inflate_state FAR *state;
z_const unsigned char FAR *in; /* local strm->next_in */

View File

@@ -101,7 +101,8 @@ local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf,
unsigned len));
int ZEXPORT inflateResetKeep(z_streamp strm)
int ZEXPORT inflateResetKeep(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
@@ -125,7 +126,8 @@ int ZEXPORT inflateResetKeep(z_streamp strm)
return Z_OK;
}
int ZEXPORT inflateReset(z_streamp strm)
int ZEXPORT inflateReset(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
@@ -137,7 +139,9 @@ int ZEXPORT inflateReset(z_streamp strm)
return inflateResetKeep(strm);
}
int ZEXPORT inflateReset2(z_streamp strm, int windowBits)
int ZEXPORT inflateReset2(strm, windowBits)
z_streamp strm;
int windowBits;
{
int wrap;
struct inflate_state FAR *state;
@@ -173,7 +177,11 @@ int ZEXPORT inflateReset2(z_streamp strm, int windowBits)
return inflateReset(strm);
}
int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, int stream_size)
int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
z_streamp strm;
int windowBits;
const char *version;
int stream_size;
{
int ret;
struct inflate_state FAR *state;
@@ -211,12 +219,18 @@ int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, i
return ret;
}
int ZEXPORT inflateInit_(z_streamp strm, const char *version, int stream_size)
int ZEXPORT inflateInit_(strm, version, stream_size)
z_streamp strm;
const char *version;
int stream_size;
{
return inflateInit2_(strm, DEF_WBITS, version, stream_size);
}
int ZEXPORT inflatePrime(z_streamp strm, int bits, int value)
int ZEXPORT inflatePrime(strm, bits, value)
z_streamp strm;
int bits;
int value;
{
struct inflate_state FAR *state;
@@ -244,7 +258,8 @@ int ZEXPORT inflatePrime(z_streamp strm, int bits, int value)
used for threaded applications, since the rewriting of the tables and virgin
may not be thread-safe.
*/
local void fixedtables(struct inflate_state FAR *state)
local void fixedtables(state)
struct inflate_state FAR *state;
{
#ifdef BUILDFIXED
static int virgin = 1;
@@ -361,7 +376,10 @@ void makefixed()
output will fall in the output data, making match copies simpler and faster.
The advantage may be dependent on the size of the processor's data caches.
*/
local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy)
local int updatewindow(strm, end, copy)
z_streamp strm;
const Bytef *end;
unsigned copy;
{
struct inflate_state FAR *state;
unsigned dist;
@@ -584,7 +602,9 @@ local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy)
will return Z_BUF_ERROR if it has not reached the end of the stream.
*/
int ZEXPORT inflate(z_streamp strm, int flush)
int ZEXPORT inflate(strm, flush)
z_streamp strm;
int flush;
{
struct inflate_state FAR *state;
z_const unsigned char FAR *next; /* next input */
@@ -1254,7 +1274,8 @@ int ZEXPORT inflate(z_streamp strm, int flush)
return ret;
}
int ZEXPORT inflateEnd(z_streamp strm)
int ZEXPORT inflateEnd(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
@@ -1267,7 +1288,10 @@ int ZEXPORT inflateEnd(z_streamp strm)
return Z_OK;
}
int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, uInt *dictLength)
int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength)
z_streamp strm;
Bytef *dictionary;
uInt *dictLength;
{
struct inflate_state FAR *state;
@@ -1287,7 +1311,10 @@ int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, uInt *dictLe
return Z_OK;
}
int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, uInt dictLength)
int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
z_streamp strm;
const Bytef *dictionary;
uInt dictLength;
{
struct inflate_state FAR *state;
unsigned long dictid;
@@ -1319,7 +1346,9 @@ int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, uInt d
return Z_OK;
}
int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head)
int ZEXPORT inflateGetHeader(strm, head)
z_streamp strm;
gz_headerp head;
{
struct inflate_state FAR *state;
@@ -1345,7 +1374,10 @@ int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head)
called again with more data and the *have state. *have is initialized to
zero for the first call.
*/
local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, unsigned len)
local unsigned syncsearch(have, buf, len)
unsigned FAR *have;
const unsigned char FAR *buf;
unsigned len;
{
unsigned got;
unsigned next;
@@ -1365,7 +1397,8 @@ local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, unsi
return next;
}
int ZEXPORT inflateSync(z_streamp strm)
int ZEXPORT inflateSync(strm)
z_streamp strm;
{
unsigned len; /* number of bytes to look at or looked at */
unsigned long in, out; /* temporary to save total_in and total_out */
@@ -1415,7 +1448,8 @@ int ZEXPORT inflateSync(z_streamp strm)
block. When decompressing, PPP checks that at the end of input packet,
inflate is waiting for these length bytes.
*/
int ZEXPORT inflateSyncPoint(z_streamp strm)
int ZEXPORT inflateSyncPoint(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
@@ -1424,7 +1458,9 @@ int ZEXPORT inflateSyncPoint(z_streamp strm)
return state->mode == STORED && state->bits == 0;
}
int ZEXPORT inflateCopy(z_streamp dest, z_streamp source)
int ZEXPORT inflateCopy(dest, source)
z_streamp dest;
z_streamp source;
{
struct inflate_state FAR *state;
struct inflate_state FAR *copy;
@@ -1469,7 +1505,9 @@ int ZEXPORT inflateCopy(z_streamp dest, z_streamp source)
return Z_OK;
}
int ZEXPORT inflateUndermine(z_streamp strm, int subvert)
int ZEXPORT inflateUndermine(strm, subvert)
z_streamp strm;
int subvert;
{
struct inflate_state FAR *state;
@@ -1484,7 +1522,8 @@ int ZEXPORT inflateUndermine(z_streamp strm, int subvert)
#endif
}
long ZEXPORT inflateMark(z_streamp strm)
long ZEXPORT inflateMark(strm)
z_streamp strm;
{
struct inflate_state FAR *state;

View File

@@ -29,9 +29,13 @@ const char inflate_copyright[] =
table index bits. It will differ if the request is greater than the
longest code or if it is less than the shortest code.
*/
int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens,
unsigned codes, code FAR * FAR *table, unsigned FAR *bits,
unsigned short FAR *work)
int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
codetype type;
unsigned short FAR *lens;
unsigned codes;
code FAR * FAR *table;
unsigned FAR *bits;
unsigned short FAR *work;
{
unsigned len; /* a code's length in bits */
unsigned sym; /* index of code symbols */

View File

@@ -185,7 +185,10 @@ local void gen_trees_header OF((void));
#ifdef DEBUG
local void send_bits OF((deflate_state *s, int value, int length));
local void send_bits(deflate_state *s, int value, int length)
local void send_bits(s, value, length)
deflate_state *s;
int value; /* value to send */
int length; /* number of bits */
{
Tracevv((stderr," l %2d v %4x ", length, value));
Assert(length > 0 && length <= 15, "invalid length");
@@ -228,7 +231,7 @@ local void send_bits(deflate_state *s, int value, int length)
/* ===========================================================================
* Initialize the various 'constant' tables.
*/
local void tr_static_init(void)
local void tr_static_init()
{
#if defined(GEN_TREES_H) || !defined(STDC)
static int static_init_done = 0;
@@ -322,7 +325,7 @@ local void tr_static_init(void)
((i) == (last)? "\n};\n\n" : \
((i) % (width) == (width)-1 ? ",\n" : ", "))
void gen_trees_header(void)
void gen_trees_header()
{
FILE *header = fopen("trees.h", "w");
int i;
@@ -375,7 +378,8 @@ void gen_trees_header(void)
/* ===========================================================================
* Initialize the tree data structures for a new zlib stream.
*/
void ZLIB_INTERNAL _tr_init(deflate_state *s)
void ZLIB_INTERNAL _tr_init(s)
deflate_state *s;
{
tr_static_init();
@@ -402,7 +406,8 @@ void ZLIB_INTERNAL _tr_init(deflate_state *s)
/* ===========================================================================
* Initialize a new block.
*/
local void init_block(deflate_state *s)
local void init_block(s)
deflate_state *s;
{
int n; /* iterates over tree elements */
@@ -445,7 +450,10 @@ local void init_block(deflate_state *s)
* when the heap property is re-established (each father smaller than its
* two sons).
*/
local void pqdownheap(deflate_state *s, ct_data *tree, int k)
local void pqdownheap(s, tree, k)
deflate_state *s;
ct_data *tree; /* the tree to restore */
int k; /* node to move down */
{
int v = s->heap[k];
int j = k << 1; /* left son of k */
@@ -477,7 +485,9 @@ local void pqdownheap(deflate_state *s, ct_data *tree, int k)
* The length opt_len is updated; static_len is also updated if stree is
* not null.
*/
local void gen_bitlen(deflate_state *s, tree_desc *desc)
local void gen_bitlen(s, desc)
deflate_state *s;
tree_desc *desc; /* the tree descriptor */
{
ct_data *tree = desc->dyn_tree;
int max_code = desc->max_code;
@@ -562,7 +572,10 @@ local void gen_bitlen(deflate_state *s, tree_desc *desc)
* OUT assertion: the field code is set for all tree elements of non
* zero code length.
*/
local void gen_codes(ct_data *tree, int max_code, ushf *bl_count)
local void gen_codes (tree, max_code, bl_count)
ct_data *tree; /* the tree to decorate */
int max_code; /* largest code with non zero frequency */
ushf *bl_count; /* number of codes at each bit length */
{
ush next_code[MAX_BITS+1]; /* next code value for each bit length */
ush code = 0; /* running code value */
@@ -601,7 +614,9 @@ local void gen_codes(ct_data *tree, int max_code, ushf *bl_count)
* and corresponding code. The length opt_len is updated; static_len is
* also updated if stree is not null. The field max_code is set.
*/
local void build_tree(deflate_state *s, tree_desc *desc)
local void build_tree(s, desc)
deflate_state *s;
tree_desc *desc; /* the tree descriptor */
{
ct_data *tree = desc->dyn_tree;
const ct_data *stree = desc->stat_desc->static_tree;
@@ -687,7 +702,10 @@ local void build_tree(deflate_state *s, tree_desc *desc)
* Scan a literal or distance tree to determine the frequencies of the codes
* in the bit length tree.
*/
local void scan_tree(deflate_state *s, ct_data *tree, int max_code)
local void scan_tree (s, tree, max_code)
deflate_state *s;
ct_data *tree; /* the tree to be scanned */
int max_code; /* and its largest code of non zero frequency */
{
int n; /* iterates over all tree elements */
int prevlen = -1; /* last emitted length */
@@ -729,7 +747,10 @@ local void scan_tree(deflate_state *s, ct_data *tree, int max_code)
* Send a literal or distance tree in compressed form, using the codes in
* bl_tree.
*/
local void send_tree(deflate_state *s, ct_data *tree, int max_code)
local void send_tree (s, tree, max_code)
deflate_state *s;
ct_data *tree; /* the tree to be scanned */
int max_code; /* and its largest code of non zero frequency */
{
int n; /* iterates over all tree elements */
int prevlen = -1; /* last emitted length */
@@ -777,7 +798,8 @@ local void send_tree(deflate_state *s, ct_data *tree, int max_code)
* Construct the Huffman tree for the bit lengths and return the index in
* bl_order of the last bit length code to send.
*/
local int build_bl_tree(deflate_state *s)
local int build_bl_tree(s)
deflate_state *s;
{
int max_blindex; /* index of last bit length code of non zero freq */
@@ -811,7 +833,9 @@ local int build_bl_tree(deflate_state *s)
* lengths of the bit length codes, the literal tree and the distance tree.
* IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
*/
local void send_all_trees(deflate_state *s, int lcodes, int dcodes, int blcodes)
local void send_all_trees(s, lcodes, dcodes, blcodes)
deflate_state *s;
int lcodes, dcodes, blcodes; /* number of codes for each tree */
{
int rank; /* index in bl_order */
@@ -838,7 +862,11 @@ local void send_all_trees(deflate_state *s, int lcodes, int dcodes, int blcodes)
/* ===========================================================================
* Send a stored block
*/
void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, ulg stored_len, int last)
void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
deflate_state *s;
charf *buf; /* input block */
ulg stored_len; /* length of input block */
int last; /* one if this is the last block for a file */
{
send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
#ifdef DEBUG
@@ -851,7 +879,8 @@ void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, ulg stored_len
/* ===========================================================================
* Flush the bits in the bit buffer to pending output (leaves at most 7 bits)
*/
void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s)
void ZLIB_INTERNAL _tr_flush_bits(s)
deflate_state *s;
{
bi_flush(s);
}
@@ -860,7 +889,8 @@ void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s)
* Send one empty static block to give enough lookahead for inflate.
* This takes 10 bits, of which 7 may remain in the bit buffer.
*/
void ZLIB_INTERNAL _tr_align(deflate_state *s)
void ZLIB_INTERNAL _tr_align(s)
deflate_state *s;
{
send_bits(s, STATIC_TREES<<1, 3);
send_code(s, END_BLOCK, static_ltree);
@@ -874,7 +904,11 @@ void ZLIB_INTERNAL _tr_align(deflate_state *s)
* Determine the best encoding for the current block: dynamic trees, static
* trees or store, and output the encoded block to the zip file.
*/
void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, ulg stored_len, int last)
void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
deflate_state *s;
charf *buf; /* input block, or NULL if too old */
ulg stored_len; /* length of input block */
int last; /* one if this is the last block for a file */
{
ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
int max_blindex = 0; /* index of last bit length code of non zero freq */
@@ -973,7 +1007,10 @@ void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, ulg stored_len,
* Save the match info and tally the frequency counts. Return true if
* the current block must be flushed.
*/
int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc)
int ZLIB_INTERNAL _tr_tally (s, dist, lc)
deflate_state *s;
unsigned dist; /* distance of matched string */
unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
{
s->sym_buf[s->sym_next++] = dist;
s->sym_buf[s->sym_next++] = dist >> 8;
@@ -998,7 +1035,10 @@ int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc)
/* ===========================================================================
* Send the block data compressed using the given Huffman trees
*/
local void compress_block(deflate_state *s, const ct_data *ltree, const ct_data *dtree)
local void compress_block(s, ltree, dtree)
deflate_state *s;
const ct_data *ltree; /* literal tree */
const ct_data *dtree; /* distance tree */
{
unsigned dist; /* distance of matched string */
int lc; /* match length or unmatched char (if dist == 0) */
@@ -1055,7 +1095,8 @@ local void compress_block(deflate_state *s, const ct_data *ltree, const ct_data
* (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
* IN assertion: the fields Freq of dyn_ltree are set.
*/
local int detect_data_type(deflate_state *s)
local int detect_data_type(s)
deflate_state *s;
{
/* black_mask is the bit mask of black-listed bytes
* set bits 0..6, 14..25, and 28..31
@@ -1088,7 +1129,9 @@ local int detect_data_type(deflate_state *s)
* method would use a table)
* IN assertion: 1 <= len <= 15
*/
local unsigned bi_reverse(unsigned code, int len)
local unsigned bi_reverse(code, len)
unsigned code; /* the value to invert */
int len; /* its bit length */
{
register unsigned res = 0;
do {
@@ -1101,7 +1144,8 @@ local unsigned bi_reverse(unsigned code, int len)
/* ===========================================================================
* Flush the bit buffer, keeping at most 7 bits in it.
*/
local void bi_flush(deflate_state *s)
local void bi_flush(s)
deflate_state *s;
{
if (s->bi_valid == 16) {
put_short(s, s->bi_buf);
@@ -1117,7 +1161,8 @@ local void bi_flush(deflate_state *s)
/* ===========================================================================
* Flush the bit buffer and align the output on a byte boundary
*/
local void bi_windup(deflate_state *s)
local void bi_windup(s)
deflate_state *s;
{
if (s->bi_valid > 8) {
put_short(s, s->bi_buf);
@@ -1135,7 +1180,11 @@ local void bi_windup(deflate_state *s)
* Copy a stored block, storing first the length and its
* one's complement if requested.
*/
local void copy_block(deflate_state *s, charf *buf, unsigned len, int header)
local void copy_block(s, buf, len, header)
deflate_state *s;
charf *buf; /* the input data */
unsigned len; /* its length */
int header; /* true if block header must be written */
{
bi_windup(s); /* align on byte boundary */

View File

@@ -27,12 +27,12 @@ z_const char * const z_errmsg[10] = {
""};
const char * ZEXPORT zlibVersion(void)
const char * ZEXPORT zlibVersion()
{
return ZLIB_VERSION;
}
uLong ZEXPORT zlibCompileFlags(void)
uLong ZEXPORT zlibCompileFlags()
{
uLong flags;
@@ -122,7 +122,8 @@ uLong ZEXPORT zlibCompileFlags(void)
# endif
int ZLIB_INTERNAL z_verbose = verbose;
void ZLIB_INTERNAL z_error (char *m)
void ZLIB_INTERNAL z_error (m)
char *m;
{
fprintf(stderr, "%s\n", m);
exit(1);
@@ -132,7 +133,8 @@ void ZLIB_INTERNAL z_error (char *m)
/* exported to allow conversion of error code to string for compress() and
* uncompress()
*/
const char * ZEXPORT zError(int err)
const char * ZEXPORT zError(err)
int err;
{
return ERR_MSG(err);
}
@@ -147,7 +149,10 @@ const char * ZEXPORT zError(int err)
#ifndef HAVE_MEMCPY
void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len)
void ZLIB_INTERNAL zmemcpy(dest, source, len)
Bytef* dest;
const Bytef* source;
uInt len;
{
if (len == 0) return;
do {
@@ -155,7 +160,10 @@ void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len)
} while (--len != 0);
}
int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len)
int ZLIB_INTERNAL zmemcmp(s1, s2, len)
const Bytef* s1;
const Bytef* s2;
uInt len;
{
uInt j;
@@ -165,7 +173,9 @@ int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len)
return 0;
}
void ZLIB_INTERNAL zmemzero(Bytef* dest, uInt len)
void ZLIB_INTERNAL zmemzero(dest, len)
Bytef* dest;
uInt len;
{
if (len == 0) return;
do {
@@ -291,14 +301,19 @@ extern voidp calloc OF((uInt items, uInt size));
extern void free OF((voidpf ptr));
#endif
voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
voidpf opaque;
unsigned items;
unsigned size;
{
if (opaque) items += size - size; /* make compiler happy */
return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
(voidpf)calloc(items, size);
}
void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
void ZLIB_INTERNAL zcfree (opaque, ptr)
voidpf opaque;
voidpf ptr;
{
free(ptr);
if (opaque) return; /* make compiler happy */