Compare commits

..

1 Commits

Author SHA1 Message Date
Nick Couchman
ef289eaa4a TELNET: Add ability to override detected echo setting. 2017-09-09 14:45:11 -04:00
448 changed files with 13328 additions and 35090 deletions

View File

@ -1,5 +1,3 @@
# Docker build spec
Dockerfile
# Git repository metadata # Git repository metadata
.git .git
@ -56,5 +54,5 @@ tests/test_*
!tests/test_*.[ch] !tests/test_*.[ch]
# Generated docs # Generated docs
doc/*/doxygen-output doc/doxygen-output

24
.gitignore vendored
View File

@ -10,10 +10,6 @@
*.gcov *.gcov
*.gcno *.gcno
# Test suite output
*.log
*.trs
# Backup files # Backup files
*~ *~
@ -27,24 +23,32 @@
.deps/ .deps/
.dirstamp .dirstamp
.libs/ .libs/
Doxyfile
Makefile Makefile
Makefile.in Makefile.in
aclocal.m4 aclocal.m4
autom4te.cache/ autom4te.cache/
build-aux/
libtool
m4/* m4/*
!README !README
compile
config.guess
config.h config.h
config.h.in config.h.in
config.log config.log
config.status config.status
config.sub
configure configure
depcomp
install-sh
libtool
ltmain.sh
missing
stamp-h1 stamp-h1
test-driver
# Test binaries
tests/test_*
!tests/test_*.[ch]
# Generated docs # Generated docs
doc/*/doxygen-output doc/doxygen-output
# IDE metadata
nbproject/

View File

@ -24,10 +24,10 @@ the review process.
The Guacamole source is maintained in git repositories hosted on GitHub: The Guacamole source is maintained in git repositories hosted on GitHub:
https://github.com/apache/guacamole-client https://github.com/apache/incubator-guacamole-client
https://github.com/apache/guacamole-manual https://github.com/apache/incubator-guacamole-manual
https://github.com/apache/guacamole-server https://github.com/apache/incubator-guacamole-server
https://github.com/apache/guacamole-website https://github.com/apache/incubator-guacamole-website
To make your changes, fork the applicable repositories and make commits To make your changes, fork the applicable repositories and make commits
to a topic branch in your fork. Commits should be made in logical units to a topic branch in your fork. Commits should be made in logical units

18
ChangeLog Normal file
View File

@ -0,0 +1,18 @@
2013-08-26 Michael Jumper <mike.jumper@guac-dev.org>
* Experimental sound support for VNC (ticket #369)
* Improved handling of frame flush (ticket #380)
* SSL transport for guacd (ticket #371)
* Fix segfault in RDP disconnect (ticket #385)
* Add security options to RDP (ticket #190)
2013-07-02 Michael Jumper <mike.jumper@guac-dev.org>
* Optional threadsafety in guac_socket
* Printing support in for RDP (ticket #110)
* Fix ENABLE_OGG bug (ticket #355)
2013-06-06 Michael Jumper <mike.jumper@guac-dev.org>
* Created new repository layout

7
DISCLAIMER Normal file
View File

@ -0,0 +1,7 @@
Apache Guacamole is an effort undergoing incubation at The Apache Software
Foundation (ASF). Incubation is required of all newly accepted projects until a
further review indicates that the infrastructure, communications, and decision
making process have stabilized in a manner consistent with other successful ASF
projects. While incubation status is not necessarily a reflection of the
completeness or stability of the code, it does indicate that the project has
yet to be fully endorsed by the ASF.

View File

@ -21,181 +21,68 @@
# Dockerfile for guacamole-server # Dockerfile for guacamole-server
# #
# The Alpine Linux image that should be used as the basis for the guacd image # Start from CentOS base image
ARG ALPINE_BASE_IMAGE=latest FROM centos:centos7
FROM alpine:${ALPINE_BASE_IMAGE} AS builder
# Install build dependencies # Environment variables
RUN apk add --no-cache \ ENV \
BUILD_DIR=/tmp/guacd-docker-BUILD \
LC_ALL=en_US.UTF-8 \
RUNTIME_DEPENDENCIES=" \
cairo \
dejavu-sans-mono-fonts \
freerdp \
freerdp-plugins \
ghostscript \
libjpeg-turbo \
libssh2 \
liberation-mono-fonts \
libtelnet \
libvorbis \
libvncserver \
libwebp \
pango \
pulseaudio-libs \
terminus-fonts \
uuid" \
BUILD_DEPENDENCIES=" \
autoconf \ autoconf \
automake \ automake \
build-base \ cairo-devel \
cairo-dev \ freerdp-devel \
cmake \ gcc \
git \ libjpeg-turbo-devel \
grep \ libssh2-devel \
libjpeg-turbo-dev \
libpng-dev \
libtool \ libtool \
libwebp-dev \ libtelnet-devel \
libvorbis-devel \
libvncserver-devel \
libwebp-devel \
make \ make \
openssl-dev \ pango-devel \
pango-dev \ pulseaudio-libs-devel \
pulseaudio-dev \ uuid-devel"
util-linux-dev
# Bring environment up-to-date and install guacamole-server dependencies
RUN yum -y update && \
yum -y install epel-release && \
yum -y install $RUNTIME_DEPENDENCIES && \
yum clean all
# Add configuration scripts
COPY src/guacd-docker/bin /opt/guacd/bin/
# Copy source to container for sake of build # Copy source to container for sake of build
ARG BUILD_DIR=/tmp/guacamole-server COPY . "$BUILD_DIR"
COPY . ${BUILD_DIR}
# # Build guacamole-server from local source
# Base directory for installed build artifacts. RUN yum -y install $BUILD_DEPENDENCIES && \
# /opt/guacd/bin/build-guacd.sh "$BUILD_DIR" && \
# NOTE: Due to limitations of the Docker image build process, this value is rm -Rf "$BUILD_DIR" && \
# duplicated in an ARG in the second stage of the build. yum -y autoremove $BUILD_DEPENDENCIES && \
# yum clean all
ARG PREFIX_DIR=/opt/guacamole
#
# Automatically select the latest versions of each core protocol support
# library (these can be overridden at build time if a specific version is
# needed)
#
ARG WITH_FREERDP='2(\.\d+)+'
ARG WITH_LIBSSH2='libssh2-\d+(\.\d+)+'
ARG WITH_LIBTELNET='\d+(\.\d+)+'
ARG WITH_LIBVNCCLIENT='LibVNCServer-\d+(\.\d+)+'
ARG WITH_LIBWEBSOCKETS='v\d+(\.\d+)+'
#
# Default build options for each core protocol support library, as well as
# guacamole-server itself (these can be overridden at build time if different
# options are needed)
#
ARG FREERDP_OPTS="\
-DBUILTIN_CHANNELS=OFF \
-DCHANNEL_URBDRC=OFF \
-DWITH_ALSA=OFF \
-DWITH_CAIRO=ON \
-DWITH_CHANNELS=ON \
-DWITH_CLIENT=ON \
-DWITH_CUPS=OFF \
-DWITH_DIRECTFB=OFF \
-DWITH_FFMPEG=OFF \
-DWITH_GSM=OFF \
-DWITH_GSSAPI=OFF \
-DWITH_IPP=OFF \
-DWITH_JPEG=ON \
-DWITH_LIBSYSTEMD=OFF \
-DWITH_MANPAGES=OFF \
-DWITH_OPENH264=OFF \
-DWITH_OPENSSL=ON \
-DWITH_OSS=OFF \
-DWITH_PCSC=OFF \
-DWITH_PULSE=OFF \
-DWITH_SERVER=OFF \
-DWITH_SERVER_INTERFACE=OFF \
-DWITH_SHADOW_MAC=OFF \
-DWITH_SHADOW_X11=OFF \
-DWITH_SSE2=ON \
-DWITH_WAYLAND=OFF \
-DWITH_X11=OFF \
-DWITH_X264=OFF \
-DWITH_XCURSOR=ON \
-DWITH_XEXT=ON \
-DWITH_XI=OFF \
-DWITH_XINERAMA=OFF \
-DWITH_XKBFILE=ON \
-DWITH_XRENDER=OFF \
-DWITH_XTEST=OFF \
-DWITH_XV=OFF \
-DWITH_ZLIB=ON"
ARG GUACAMOLE_SERVER_OPTS="\
--disable-guaclog"
ARG LIBSSH2_OPTS="\
-DBUILD_EXAMPLES=OFF \
-DBUILD_SHARED_LIBS=ON"
ARG LIBTELNET_OPTS="\
--disable-static \
--disable-util"
ARG LIBVNCCLIENT_OPTS=""
ARG LIBWEBSOCKETS_OPTS="\
-DDISABLE_WERROR=ON \
-DLWS_WITHOUT_SERVER=ON \
-DLWS_WITHOUT_TESTAPPS=ON \
-DLWS_WITHOUT_TEST_CLIENT=ON \
-DLWS_WITHOUT_TEST_PING=ON \
-DLWS_WITHOUT_TEST_SERVER=ON \
-DLWS_WITHOUT_TEST_SERVER_EXTPOLL=ON \
-DLWS_WITH_STATIC=OFF"
# Build guacamole-server and its core protocol library dependencies
RUN ${BUILD_DIR}/src/guacd-docker/bin/build-all.sh
# Record the packages of all runtime library dependencies
RUN ${BUILD_DIR}/src/guacd-docker/bin/list-dependencies.sh \
${PREFIX_DIR}/sbin/guacd \
${PREFIX_DIR}/lib/libguac-client-*.so \
${PREFIX_DIR}/lib/freerdp2/*guac*.so \
> ${PREFIX_DIR}/DEPENDENCIES
# Use same Alpine version as the base for the runtime image
FROM alpine:${ALPINE_BASE_IMAGE}
#
# Base directory for installed build artifacts. See also the
# CMD directive at the end of this build stage.
#
# NOTE: Due to limitations of the Docker image build process, this value is
# duplicated in an ARG in the first stage of the build.
#
ARG PREFIX_DIR=/opt/guacamole
# Runtime environment
ENV LC_ALL=C.UTF-8
ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib
ENV GUACD_LOG_LEVEL=info
# Copy build artifacts into this stage
COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR}
# Bring runtime environment up to date and install runtime dependencies
RUN apk add --no-cache \
ca-certificates \
ghostscript \
netcat-openbsd \
shadow \
terminus-font \
ttf-dejavu \
ttf-liberation \
util-linux-login && \
xargs apk add --no-cache < ${PREFIX_DIR}/DEPENDENCIES
# Checks the operating status every 5 minutes with a timeout of 5 seconds
HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
# Create a new user guacd
ARG UID=1000
ARG GID=1000
RUN groupadd --gid $GID guacd
RUN useradd --system --create-home --shell /sbin/nologin --uid $UID --gid $GID guacd
# Run with user guacd
USER guacd
# Expose the default listener port
EXPOSE 4822
# Start guacd, listening on port 0.0.0.0:4822 # Start guacd, listening on port 0.0.0.0:4822
# EXPOSE 4822
# Note the path here MUST correspond to the value specified in the CMD [ "/usr/local/sbin/guacd", "-b", "0.0.0.0", "-f" ]
# PREFIX_DIR build argument.
#
CMD /opt/guacamole/sbin/guacd -b 0.0.0.0 -L $GUACD_LOG_LEVEL -f

View File

@ -16,34 +16,28 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
# #
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
# Subprojects # Subprojects
DIST_SUBDIRS = \ DIST_SUBDIRS = \
src/libguac \ src/libguac \
src/common \ src/common \
src/common-ssh \ src/common-ssh \
src/terminal \ src/terminal \
src/guacd \ src/guacd \
src/guacenc \ src/guacenc \
src/guaclog \ src/pulse \
src/pulse \ src/protocols/rdp \
src/protocols/kubernetes \ src/protocols/ssh \
src/protocols/rdp \ src/protocols/telnet \
src/protocols/ssh \ src/protocols/vnc \
src/protocols/telnet \ tests
src/protocols/vnc
SUBDIRS = \ SUBDIRS = \
src/libguac \ src/libguac \
src/common src/common \
tests
if ENABLE_COMMON_SSH if ENABLE_COMMON_SSH
SUBDIRS += src/common-ssh SUBDIRS += src/common-ssh
@ -57,10 +51,6 @@ if ENABLE_PULSE
SUBDIRS += src/pulse SUBDIRS += src/pulse
endif endif
if ENABLE_KUBERNETES
SUBDIRS += src/protocols/kubernetes
endif
if ENABLE_RDP if ENABLE_RDP
SUBDIRS += src/protocols/rdp SUBDIRS += src/protocols/rdp
endif endif
@ -85,19 +75,14 @@ if ENABLE_GUACENC
SUBDIRS += src/guacenc SUBDIRS += src/guacenc
endif endif
if ENABLE_GUACLOG EXTRA_DIST = \
SUBDIRS += src/guaclog .dockerignore \
endif CONTRIBUTING \
DISCLAIMER \
EXTRA_DIST = \ Dockerfile \
.dockerignore \ LICENSE \
CONTRIBUTING \ NOTICE \
Dockerfile \ bin/guacctl \
LICENSE \ doc/Doxyfile \
NOTICE \ src/guacd-docker
bin/guacctl \
doc/libguac/Doxyfile.in \
doc/libguac-terminal/Doxyfile.in \
src/guacd-docker \
util/generate-test-runner.pl

2
NOTICE
View File

@ -1,5 +1,5 @@
Apache Guacamole Apache Guacamole
Copyright 2020 The Apache Software Foundation Copyright 2017 The Apache Software Foundation
This product includes software developed at This product includes software developed at
The Apache Software Foundation (http://www.apache.org/). The Apache Software Foundation (http://www.apache.org/).

6
README
View File

@ -8,11 +8,11 @@ technical users intending to compile parts of Apache Guacamole themselves.
Source archives are available from the downloads section of the project website: Source archives are available from the downloads section of the project website:
http://guacamole.apache.org/ http://guacamole.incubator.apache.org/
A full manual is available as well: A full manual is available as well:
http://guacamole.apache.org/doc/gug/ http://guacamole.incubator.apache.org/doc/gug/
------------------------------------------------------------ ------------------------------------------------------------
@ -25,7 +25,7 @@ libraries.
guacd is the Guacamole proxy daemon used by the Guacamole web application and guacd is the Guacamole proxy daemon used by the Guacamole web application and
framework. As JavaScript cannot handle binary protocols (like VNC and remote framework. As JavaScript cannot handle binary protocols (like VNC and remote
desktop) efficiently, a new text-based protocol was developed which would desktop) efficiently, a new test-based protocol was developed which would
contain a common superset of the operations needed for efficient remote desktop contain a common superset of the operations needed for efficient remote desktop
access, but would be easy for JavaScript programs to process. guacd is the access, but would be easy for JavaScript programs to process. guacd is the
proxy which translates between arbitrary protocols and the Guacamole protocol. proxy which translates between arbitrary protocols and the Guacamole protocol.

View File

@ -1,89 +0,0 @@
Unit testing and guacamole-server
=================================
Unit tests within guacamole-server are implemented using the following:
* automake, which allows arbitrary tests to be declared within `Makefile.am`
and uses `make check` to run those tests.
* CUnit (libcunit), a unit testing framework.
* `util/generate-test-runner.pl`, a Perl script which generates a test runner
written in C which leverages CUnit, running the unit tests declared in each
of the given `.c` files. The generated test runner produces output in [TAP
format](https://testanything.org/) which is consumed by the TAP test driver
provided by automake.
Writing unit tests
------------------
All unit tests should be within reasonably-isolated C source files, with each
logical test having its own function of the form:
void test_SUITENAME__TESTNAME() {
...
}
where `TESTNAME` is the arbitrary name of the test and `SUITENAME` is the
arbitrary name of the test suite that this test belongs to.
**This naming convention is required by `generate-test-runner.pl`.** Absolutely
all tests MUST follow the above convention if they are to be picked up and
organized by the test runner generation script. Functions which are not tests
MUST NOT follow the above convention so that they are _not_ picked up mistakenly
by the test runner generator as if they were tests.
The `Makefile.am` for a subproject which contains such tests is typically
modified to contain a sections like the following:
#
# Unit tests for myproj
#
check_PROGRAMS = test_myproj
TESTS = $(check_PROGRAMS)
test_myproj_SOURCES = \
...all source files...
test_myproj_CFLAGS = \
-Werror -Wall -pedantic \
...other flags...
test_myproj_LDADD = \
...libraries...
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_myproj_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_myproj_SOURCES) > $@
nodist_test_libguac_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
The above declares ...
* ... that a binary, `test_myproj` should be built from the given sources.
Note that `test_myproj_SOURCES` contains only the source which was actually
written by hand while `nodist_test_myproj_SOURCES` contains only the source
which was generated by `generate-test-runner.pl`.
* ... that this `test_myproj` binary should be run to test this project when
`make check` is run, and that automake's TAP driver should be used to
consume its output.
* ... that the `_generated_runner.c` source file is generated dynamically
(through running `generate-test-runner.pl` on all non-generated test source)
and should not be distributed as part of the source archive.
With tests following the above naming convention in place, and with the
necessary changes made to the applicable `Makefile.am`, all tests will be
run automatically when `make check` is run.

View File

@ -90,18 +90,6 @@ send_close_pipe_stream() {
printf "\033]482203;\007" printf "\033]482203;\007"
} }
##
## Sends the Guacamole-specific console code for resizing the scrollback
## buffer.
##
## @param ROWS
## The number of rows that the scrollback buffer should contain.
##
send_resize_scrollback() {
ROWS="$1"
printf "\033]482204;%s\007" "$ROWS"
}
## ##
## Prints the given error text to STDERR. ## Prints the given error text to STDERR.
## ##
@ -117,7 +105,7 @@ error() {
## ##
usage() { usage() {
cat >&2 <<END cat >&2 <<END
guacctl 1.5.0, Apache Guacamole terminal session control utility. guacctl 0.9.11-incubating, Guacamole terminal session control utility.
Usage: guacctl [OPTION] [FILE or NAME]... Usage: guacctl [OPTION] [FILE or NAME]...
-d, --download download each of the files listed. -d, --download download each of the files listed.
@ -127,8 +115,6 @@ Usage: guacctl [OPTION] [FILE or NAME]...
name. name.
-c, --close-pipe close any existing pipe stream and redirect output -c, --close-pipe close any existing pipe stream and redirect output
back to the terminal emulator. back to the terminal emulator.
-S, --scrollback request that the scrollback buffer be limited to the
given number of rows.
END END
} }
@ -257,39 +243,6 @@ close_pipe_stream() {
} }
##
## Resizes the scrollback buffer to the given number of rows.
##
## @param ...
## The number of rows that should be contained within the scrollback
## buffer, as provided to guacctl.
##
resize_scrollback() {
#
# Validate arguments
#
if [ $# -lt 1 ]; then
error "No row count specified."
return;
fi
if [ $# -gt 1 ]; then
error "Only one row count may be given."
return;
fi
#
# Send code for resizing scrollback
#
ROWS="$1"
send_resize_scrollback "$ROWS"
}
# #
# Get script name # Get script name
# #
@ -347,15 +300,6 @@ case "$1" in
close_pipe_stream "$@" close_pipe_stream "$@"
;; ;;
#
# Resize scrollback
#
"--scrollback"|"-S")
shift
resize_scrollback "$@"
;;
# #
# Show usage info if options are invalid # Show usage info if options are invalid
# #

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
# #
PROJECT_NAME = libguac PROJECT_NAME = libguac
PROJECT_NUMBER = @PACKAGE_VERSION@ PROJECT_NUMBER = 0.9.13-incubating
# #
# Warn about undocumented parameters and return values, but do not fill output # Warn about undocumented parameters and return values, but do not fill output
@ -52,9 +52,9 @@ SHOW_INCLUDE_FILES = NO
CASE_SENSE_NAMES = YES CASE_SENSE_NAMES = YES
EXCLUDE_SYMBOLS = __* guac_palette* EXCLUDE_SYMBOLS = __* guac_palette*
FILE_PATTERNS = *.h FILE_PATTERNS = *.h
INPUT = ../../src/libguac/guacamole INPUT = ../src/libguac/guacamole
JAVADOC_AUTOBRIEF = YES JAVADOC_AUTOBRIEF = YES
STRIP_FROM_PATH = ../../src/libguac STRIP_FROM_PATH = ../src/libguac
TAB_SIZE = 4 TAB_SIZE = 4
TYPEDEF_HIDES_STRUCT = YES TYPEDEF_HIDES_STRUCT = YES

View File

@ -1,58 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# Project name / version
#
PROJECT_NAME = libguac-terminal
PROJECT_NUMBER = @PACKAGE_VERSION@
#
# Warn about undocumented parameters and return values, but do not fill output
# with verbose progress info.
#
QUIET = YES
WARN_NO_PARAMDOC = YES
#
# Output format
#
ALPHABETICAL_INDEX = YES
GENERATE_HTML = YES
GENERATE_LATEX = NO
OPTIMIZE_OUTPUT_FOR_C = YES
OUTPUT_DIRECTORY = doxygen-output
RECURSIVE = YES
SHOW_INCLUDE_FILES = NO
#
# Input format
#
CASE_SENSE_NAMES = YES
FILE_PATTERNS = *.h
STRIP_FROM_PATH = ../../src/terminal
INPUT = ../../src/terminal/terminal/terminal.h
JAVADOC_AUTOBRIEF = YES
TAB_SIZE = 4
TYPEDEF_HIDES_STRUCT = YES

View File

@ -1,5 +0,0 @@
# Auto-generated test runner and binary
_generated_runner.c
test_common_ssh

View File

@ -16,21 +16,16 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
# #
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
noinst_LTLIBRARIES = libguac_common_ssh.la noinst_LTLIBRARIES = libguac_common_ssh.la
SUBDIRS = . tests
libguac_common_ssh_la_SOURCES = \ libguac_common_ssh_la_SOURCES = \
buffer.c \ buffer.c \
dsa-compat.c \
rsa-compat.c \
sftp.c \ sftp.c \
ssh.c \ ssh.c \
key.c \ key.c \
@ -38,6 +33,8 @@ libguac_common_ssh_la_SOURCES = \
noinst_HEADERS = \ noinst_HEADERS = \
common-ssh/buffer.h \ common-ssh/buffer.h \
common-ssh/dsa-compat.h \
common-ssh/rsa-compat.h \
common-ssh/key.h \ common-ssh/key.h \
common-ssh/sftp.h \ common-ssh/sftp.h \
common-ssh/ssh.h \ common-ssh/ssh.h \

View File

@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_COMMON_SSH_DSA_COMPAT_H
#define GUAC_COMMON_SSH_DSA_COMPAT_H
#include "config.h"
#include <openssl/bn.h>
#include <openssl/dsa.h>
#ifndef HAVE_DSA_GET0_PQG
/**
* DSA_get0_pqg() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_pqg.html
*/
void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p,
const BIGNUM** q, const BIGNUM** g);
#endif
#ifndef HAVE_DSA_GET0_KEY
/**
* DSA_get0_key() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_key.html
*/
void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key,
const BIGNUM** priv_key);
#endif
#ifndef HAVE_DSA_SIG_GET0
/**
* DSA_SIG_get0() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_SIG_get0.html
*/
void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s);
#endif
#endif

View File

@ -22,30 +22,75 @@
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <openssl/ossl_typ.h>
#include <libssh2.h>
/** /**
* OpenSSH v1 private keys are PEM-wrapped base64-encoded blobs. The encoded data begins with: * The expected header of RSA private keys.
* "openssh-key-v1\0"
*/ */
#define OPENSSH_V1_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEA" #define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----"
/** /**
* The base64-encoded prefix indicating an OpenSSH v1 private key is NOT protected by a * The expected header of DSA private keys.
* passphrase. Specifically, it is the following data fields and values:
* pascal string: cipher name ("none")
* pascal string: kdf name ("none")
* pascal string: kdf params (NULL)
* 32-bit int: number of keys (1)
*/ */
#define OPENSSH_V1_UNENCRYPTED_KEY "AAAABG5vbmUAAAAEbm9uZQAAAAAAAAAB" #define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----"
/**
* The size of single number within a DSA signature, in bytes.
*/
#define DSA_SIG_NUMBER_SIZE 20
/**
* The size of a DSA signature, in bytes.
*/
#define DSA_SIG_SIZE DSA_SIG_NUMBER_SIZE*2
/**
* The type of an SSH key.
*/
typedef enum guac_common_ssh_key_type {
/**
* RSA key.
*/
SSH_KEY_RSA,
/**
* DSA key.
*/
SSH_KEY_DSA
} guac_common_ssh_key_type;
/** /**
* Abstraction of a key used for SSH authentication. * Abstraction of a key used for SSH authentication.
*/ */
typedef struct guac_common_ssh_key { typedef struct guac_common_ssh_key {
/**
* The type of this key.
*/
guac_common_ssh_key_type type;
/**
* Underlying RSA private key, if any.
*/
RSA* rsa;
/**
* Underlying DSA private key, if any.
*/
DSA* dsa;
/**
* The associated public key, encoded as necessary for SSH.
*/
char* public_key;
/**
* The length of the public key, in bytes.
*/
int public_key_length;
/** /**
* The private key, encoded as necessary for SSH. * The private key, encoded as necessary for SSH.
*/ */
@ -56,11 +101,6 @@ typedef struct guac_common_ssh_key {
*/ */
int private_key_length; int private_key_length;
/**
* The private key's passphrase, if any.
*/
char *passphrase;
} guac_common_ssh_key; } guac_common_ssh_key;
/** /**
@ -102,51 +142,29 @@ const char* guac_common_ssh_key_error();
void guac_common_ssh_key_free(guac_common_ssh_key* key); void guac_common_ssh_key_free(guac_common_ssh_key* key);
/** /**
* Verifies the host key for the given hostname/port combination against * Signs the given data using the given key, returning the length of the
* one or more known_hosts entries. The known_host entries can either be a * signature in bytes, or a value less than zero on error.
* single host_key, provided by the client, or a set of known_hosts entries
* provided in the /etc/guacamole/ssh_known_hosts file. Failure to correctly
* load the known_hosts entries will result in a connection abort and a returned
* error code. A return code of zero indiciates that either no known_hosts entries
* were provided, or that the verification succeeded (match). Negative values
* indicate internal libssh2 error codes; positive values indicate a failure
* during verification of the host key against the known hosts.
* *
* @param session * @param key
* A pointer to the LIBSSH2_SESSION structure of the SSH connection already * The key to use when signing the given data.
* in progress.
* *
* @param client * @param data
* The current guac_client instance for which the known_hosts checking is * The arbitrary data to sign.
* being performed.
* *
* @param host_key * @param length
* The known host entry provided by the client. If this is non-null and not * The length of the arbitrary data being signed, in bytes.
* empty, it will be the only host key loaded and used for verification. If
* this is null or empty an attempt will be made to read the
* /etc/guacamole/ssh_known_hosts file and load entries from it.
* *
* @param hostname * @param sig
* The hostname or IP of the server that is being verified. * The buffer into which the signature should be written. The buffer must
* * be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size
* @param port * is dependent only on key size, and is equal to the length of the
* The port number of the server being verified. * modulus, in bytes.
*
* @param remote_hostkey
* The host key of the remote system being verified.
*
* @param remote_hostkey_len
* The length of the remote host key being verified
* *
* @return * @return
* The status of the known_hosts check. This will be zero if no entries * The number of bytes in the resulting signature.
* are provided or if the match succeeds, negative to indicate internal
* libssh2 errors, or positive to indicate failures during host key
* checking.
*/ */
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client, int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
const char* host_key, const char* hostname, int port, const char* remote_hostkey, int length, unsigned char* sig);
const size_t remote_hostkey_len);
#endif #endif

View File

@ -17,25 +17,24 @@
* under the License. * under the License.
*/ */
#ifndef GUAC_KUBERNETES_CLIPBOARD_H #ifndef GUAC_COMMON_SSH_RSA_COMPAT_H
#define GUAC_KUBERNETES_CLIPBOARD_H #define GUAC_COMMON_SSH_RSA_COMPAT_H
#include <guacamole/user.h> #include "config.h"
#include <openssl/bn.h>
#include <openssl/rsa.h>
#ifndef HAVE_RSA_GET0_KEY
/** /**
* Handler for inbound clipboard streams. * RSA_get0_key() implementation for versions of OpenSSL which lack this
* function (pre 1.1).
*
* See: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html
*/ */
guac_user_clipboard_handler guac_kubernetes_clipboard_handler; void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n,
const BIGNUM** e, const BIGNUM**d);
/** #endif
* Handler for data received along clipboard streams.
*/
guac_user_blob_handler guac_kubernetes_clipboard_blob_handler;
/**
* Handler for end-of-stream related to clipboard.
*/
guac_user_end_handler guac_kubernetes_clipboard_end_handler;
#endif #endif

View File

@ -69,16 +69,6 @@ typedef struct guac_common_ssh_sftp_filesystem {
* instruction. * instruction.
*/ */
char upload_path[GUAC_COMMON_SSH_SFTP_MAX_PATH]; char upload_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/**
* If downloads from SFTP to the local browser should be disabled.
*/
int disable_download;
/**
* If uploads from the local browser to SFTP should be disabled.
*/
int disable_upload;
} guac_common_ssh_sftp_filesystem; } guac_common_ssh_sftp_filesystem;
@ -132,20 +122,13 @@ typedef struct guac_common_ssh_sftp_ls_state {
* The name to send as the name of the filesystem whenever it is exposed * The name to send as the name of the filesystem whenever it is exposed
* to a user, or NULL to automatically generate a name from the provided * to a user, or NULL to automatically generate a name from the provided
* root_path. * root_path.
*
* @param disable_download
* Whether downloads from the SFTP share to the local browser should be
* disabled.
*
* @param disable_upload
* Whether uploads from the local browser to SFTP should be disabled.
* *
* @return * @return
* A new SFTP filesystem object, not yet exposed to users. * A new SFTP filesystem object, not yet exposed to users.
*/ */
guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem( guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
guac_common_ssh_session* session, const char* root_path, guac_common_ssh_session* session, const char* root_path,
const char* name, int disable_download, int disable_upload); const char* name);
/** /**
* Destroys the given filesystem object, disconnecting from SFTP and freeing * Destroys the given filesystem object, disconnecting from SFTP and freeing
@ -272,30 +255,5 @@ int guac_common_ssh_sftp_handle_file_stream(
void guac_common_ssh_sftp_set_upload_path( void guac_common_ssh_sftp_set_upload_path(
guac_common_ssh_sftp_filesystem* filesystem, const char* path); guac_common_ssh_sftp_filesystem* filesystem, const char* path);
/**
* Given an arbitrary absolute path, which may contain "..", ".", and
* backslashes, creates an equivalent absolute path which does NOT contain
* relative path components (".." or "."), backslashes, or empty path
* components. With the exception of paths referring to the root directory, the
* resulting path is guaranteed to not contain trailing slashes.
*
* Normalization will fail if the given path is not absolute, is too long, or
* contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components.
*
* @param fullpath
* The buffer to populate with the normalized path. The normalized path
* will not contain relative path components like ".." or ".", nor will it
* contain backslashes. This buffer MUST be at least
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
*
* @param path
* The absolute path to normalize.
*
* @return
* Non-zero if normalization succeeded, zero otherwise.
*/
int guac_common_ssh_sftp_normalize_path(char* fullpath,
const char* path);
#endif #endif

View File

@ -25,23 +25,6 @@
#include <guacamole/client.h> #include <guacamole/client.h>
#include <libssh2.h> #include <libssh2.h>
/**
* Handler for retrieving additional credentials.
*
* @param client
* The Guacamole Client associated with this need for additional
* credentials.
*
* @param cred_name
* The name of the credential being requested, which will be shared
* with the client in order to generate a meaningful prompt.
*
* @return
* A newly-allocated string containing the credentials provided by
* the user, which must be freed by a call to free().
*/
typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name);
/** /**
* An SSH session, backed by libssh2 and associated with a particular * An SSH session, backed by libssh2 and associated with a particular
* Guacamole client. * Guacamole client.
@ -67,11 +50,6 @@ typedef struct guac_common_ssh_session {
* The file descriptor of the socket being used for the SSH connection. * The file descriptor of the socket being used for the SSH connection.
*/ */
int fd; int fd;
/**
* Callback function to retrieve credentials.
*/
guac_ssh_credential_handler* credential_handler;
} guac_common_ssh_session; } guac_common_ssh_session;
@ -114,32 +92,13 @@ void guac_common_ssh_uninit();
* *
* @param user * @param user
* The user to authenticate as, once connected. * The user to authenticate as, once connected.
*
* @param keepalive
* How frequently the connection should send keepalive packets, in
* seconds. Zero disables keepalive packets, and 2 is the minimum
* configurable value.
*
* @param host_key
* The known public host key of the server, as provided by the client. If
* provided the identity of the server will be checked against this key,
* and a mis-match between this and the server identity will cause the
* connection to fail. If not provided, no checks will be done and the
* connection will proceed.
*
* @param credential_handler
* The handler function for retrieving additional credentials from the user
* as required by the SSH server, or NULL if the user will not be asked
* for additional credentials.
* *
* @return * @return
* A new SSH session if the connection and authentication succeed, or NULL * A new SSH session if the connection and authentication succeed, or NULL
* if the connection or authentication were not successful. * if the connection or authentication were not successful.
*/ */
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user, const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive);
int keepalive, const char* host_key,
guac_ssh_credential_handler* credential_handler);
/** /**
* Disconnects and destroys the given SSH session, freeing all associated * Disconnects and destroys the given SSH session, freeing all associated

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include <openssl/bn.h>
#include <openssl/dsa.h>
#include <stdlib.h>
#ifndef HAVE_DSA_GET0_PQG
void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p,
const BIGNUM** q, const BIGNUM** g) {
/* Retrieve all requested internal values */
if (p != NULL) *p = dsa_key->p;
if (q != NULL) *q = dsa_key->q;
if (g != NULL) *g = dsa_key->g;
}
#endif
#ifndef HAVE_DSA_GET0_KEY
void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key,
const BIGNUM** priv_key) {
/* Retrieve all requested internal values */
if (pub_key != NULL) *pub_key = dsa_key->pub_key;
if (priv_key != NULL) *priv_key = dsa_key->priv_key;
}
#endif
#ifndef HAVE_DSA_SIG_GET0
void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s) {
/* Retrieve all requested internal values */
if (r != NULL) *r = dsa_sig->r;
if (s != NULL) *s = dsa_sig->s;
}
#endif

View File

@ -20,9 +20,9 @@
#include "config.h" #include "config.h"
#include "common-ssh/buffer.h" #include "common-ssh/buffer.h"
#include "common-ssh/dsa-compat.h"
#include "common-ssh/key.h" #include "common-ssh/key.h"
#include "common-ssh/rsa-compat.h"
#include <guacamole/string.h>
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/bn.h> #include <openssl/bn.h>
@ -33,118 +33,118 @@
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
/**
* Check for a PKCS#1/PKCS#8 ENCRYPTED marker.
*
* @param data
* The buffer to scan.
* @param length
* The length of the buffer.
*
* @return
* True if the buffer contains the marker, false otherwise.
*/
static bool is_pkcs_encrypted_key(char* data, int length) {
return guac_strnstr(data, "ENCRYPTED", length) != NULL;
}
/**
* Check for a PEM header & initial base64-encoded data indicating this is an
* OpenSSH v1 key.
*
* @param data
* The buffer to scan.
* @param length
* The length of the buffer.
*
* @return
* True if the buffer contains a private key, false otherwise.
*/
static bool is_ssh_private_key(char* data, int length) {
if (length < sizeof(OPENSSH_V1_KEY_HEADER) - 1) {
return false;
}
return !strncmp(data, OPENSSH_V1_KEY_HEADER, sizeof(OPENSSH_V1_KEY_HEADER) - 1);
}
/**
* Assuming an offset into a key past the header, check for the base64-encoded
* data indicating this key is not protected by a passphrase.
*
* @param data
* The buffer to scan.
* @param length
* The length of the buffer.
*
* @return
* True if the buffer contains an unencrypted key, false otherwise.
*/
static bool is_ssh_key_unencrypted(char* data, int length) {
if (length < sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1) {
return false;
}
return !strncmp(data, OPENSSH_V1_UNENCRYPTED_KEY, sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1);
}
/**
* A passphrase is needed if the key is an encrypted PKCS#1/PKCS#8 key OR if
* the key is both an OpenSSH v1 key AND there isn't a marker indicating the
* key is unprotected.
*
* @param data
* The buffer to scan.
* @param length
* The length of the buffer.
*
* @return
* True if the buffer contains a key needing a passphrase, false otherwise.
*/
static bool is_passphrase_needed(char* data, int length) {
/* Is this an encrypted PKCS#1/PKCS#8 key? */
if (is_pkcs_encrypted_key(data, length)) {
return true;
}
/* Is this an OpenSSH v1 key? */
if (is_ssh_private_key(data, length)) {
/* This is safe due to the check in is_ssh_private_key. */
data += sizeof(OPENSSH_V1_KEY_HEADER) - 1;
length -= sizeof(OPENSSH_V1_KEY_HEADER) - 1;
/* If this is NOT unprotected, we need a passphrase. */
if (!is_ssh_key_unencrypted(data, length)) {
return true;
}
}
return false;
}
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
char* passphrase) { char* passphrase) {
/* Because libssh2 will do the actual key parsing (to let it deal with guac_common_ssh_key* key;
* different key algorithms) we need to perform a heuristic here to check BIO* key_bio;
* if a passphrase is needed. This could allow junk keys through that
* would never be able to auth. libssh2 should display errors to help
* admins track down malformed keys and delete or replace them.
*/
if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0')) char* public_key;
char* pos;
/* Create BIO for reading key from memory */
key_bio = BIO_new_mem_buf(data, length);
/* If RSA key, load RSA */
if (length > sizeof(SSH_RSA_KEY_HEADER)-1
&& memcmp(SSH_RSA_KEY_HEADER, data,
sizeof(SSH_RSA_KEY_HEADER)-1) == 0) {
RSA* rsa_key;
const BIGNUM* key_e;
const BIGNUM* key_n;
/* Read key */
rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase);
if (rsa_key == NULL)
return NULL;
/* Allocate key */
key = malloc(sizeof(guac_common_ssh_key));
key->rsa = rsa_key;
/* Set type */
key->type = SSH_KEY_RSA;
/* Allocate space for public key */
public_key = malloc(4096);
pos = public_key;
/* Retrieve public key */
RSA_get0_key(rsa_key, &key_n, &key_e, NULL);
/* Send public key formatted for SSH */
guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1);
guac_common_ssh_buffer_write_bignum(&pos, key_e);
guac_common_ssh_buffer_write_bignum(&pos, key_n);
/* Save public key to structure */
key->public_key = public_key;
key->public_key_length = pos - public_key;
}
/* If DSA key, load DSA */
else if (length > sizeof(SSH_DSA_KEY_HEADER)-1
&& memcmp(SSH_DSA_KEY_HEADER, data,
sizeof(SSH_DSA_KEY_HEADER)-1) == 0) {
DSA* dsa_key;
const BIGNUM* key_p;
const BIGNUM* key_q;
const BIGNUM* key_g;
const BIGNUM* pub_key;
/* Read key */
dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase);
if (dsa_key == NULL)
return NULL;
/* Allocate key */
key = malloc(sizeof(guac_common_ssh_key));
key->dsa = dsa_key;
/* Set type */
key->type = SSH_KEY_DSA;
/* Allocate space for public key */
public_key = malloc(4096);
pos = public_key;
/* Retrieve public key */
DSA_get0_pqg(dsa_key, &key_p, &key_q, &key_g);
DSA_get0_key(dsa_key, &pub_key, NULL);
/* Send public key formatted for SSH */
guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1);
guac_common_ssh_buffer_write_bignum(&pos, key_p);
guac_common_ssh_buffer_write_bignum(&pos, key_q);
guac_common_ssh_buffer_write_bignum(&pos, key_g);
guac_common_ssh_buffer_write_bignum(&pos, pub_key);
/* Save public key to structure */
key->public_key = public_key;
key->public_key_length = pos - public_key;
}
/* Otherwise, unsupported type */
else {
BIO_free(key_bio);
return NULL; return NULL;
}
guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_key));
/* Copy private key to structure */ /* Copy private key to structure */
key->private_key_length = length; key->private_key_length = length;
key->private_key = malloc(length); key->private_key = malloc(length);
memcpy(key->private_key, data, length); memcpy(key->private_key, data, length);
key->passphrase = strdup(passphrase);
BIO_free(key_bio);
return key; return key;
} }
@ -158,91 +158,90 @@ const char* guac_common_ssh_key_error() {
void guac_common_ssh_key_free(guac_common_ssh_key* key) { void guac_common_ssh_key_free(guac_common_ssh_key* key) {
/* Free key-specific data */
if (key->type == SSH_KEY_RSA)
RSA_free(key->rsa);
else if (key->type == SSH_KEY_DSA)
DSA_free(key->dsa);
free(key->private_key); free(key->private_key);
free(key->passphrase); free(key->public_key);
free(key); free(key);
} }
int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client, int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data,
const char* host_key, const char* hostname, int port, const char* remote_hostkey, int length, unsigned char* sig) {
const size_t remote_hostkey_len) {
LIBSSH2_KNOWNHOSTS* ssh_known_hosts = libssh2_knownhost_init(session); const EVP_MD* md;
int known_hosts = 0;
/* Add host key provided from settings */ unsigned char digest[EVP_MAX_MD_SIZE];
if (host_key && strcmp(host_key, "") != 0) { unsigned int dlen, len;
known_hosts = libssh2_knownhost_readline(ssh_known_hosts, host_key, strlen(host_key), /* Get SHA1 digest */
LIBSSH2_KNOWNHOST_FILE_OPENSSH); if ((md = EVP_get_digestbynid(NID_sha1)) == NULL)
return -1;
/* readline function returns 0 on success, so we increment to indicate a valid entry */ /* Allocate digest context */
if (known_hosts == 0) EVP_MD_CTX* md_ctx = EVP_MD_CTX_create();
known_hosts++; if (md_ctx == NULL)
return -1;
} /* Digest data */
EVP_DigestInit(md_ctx, md);
EVP_DigestUpdate(md_ctx, data, length);
EVP_DigestFinal(md_ctx, digest, &dlen);
/* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */ /* Digest context no longer needed */
else { EVP_MD_CTX_destroy(md_ctx);
const char *guac_known_hosts = "/etc/guacamole/ssh_known_hosts"; /* Sign with key */
if (access(guac_known_hosts, F_OK) != -1) switch (key->type) {
known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, guac_known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
} case SSH_KEY_RSA:
if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1)
/* If there's an error provided, abort connection and return that. */ return len;
if (known_hosts < 0) {
char* errmsg;
int errval = libssh2_session_last_error(session, &errmsg, NULL, 0);
guac_client_log(client, GUAC_LOG_ERROR,
"Error %d trying to load SSH host keys: %s", errval, errmsg);
libssh2_knownhost_free(ssh_known_hosts);
return known_hosts;
}
/* No host keys were loaded, so we bail out checking and continue the connection. */
else if (known_hosts == 0) {
guac_client_log(client, GUAC_LOG_WARNING,
"No known host keys provided, host identity will not be verified.");
libssh2_knownhost_free(ssh_known_hosts);
return known_hosts;
}
/* Check remote host key against known hosts */
int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port,
remote_hostkey, remote_hostkey_len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW,
NULL);
/* Deal with the return of the host key check */
switch (kh_check) {
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
guac_client_log(client, GUAC_LOG_DEBUG,
"Host key match found for %s", hostname);
break; break;
case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
guac_client_log(client, GUAC_LOG_ERROR, case SSH_KEY_DSA: {
"Host key not found for %s.", hostname);
break; DSA_SIG* dsa_sig = DSA_do_sign(digest, dlen, key->dsa);
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH: if (dsa_sig != NULL) {
guac_client_log(client, GUAC_LOG_ERROR,
"Host key does not match known hosts entry for %s", hostname); const BIGNUM* sig_r;
break; const BIGNUM* sig_s;
case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
default: /* Retrieve DSA signature values */
guac_client_log(client, GUAC_LOG_ERROR, DSA_SIG_get0(dsa_sig, &sig_r, &sig_s);
"Host %s could not be checked against known hosts.",
hostname); /* Compute size of each half of signature */
int rlen = BN_num_bytes(sig_r);
int slen = BN_num_bytes(sig_s);
/* Ensure each number is within the required size */
if (rlen > DSA_SIG_NUMBER_SIZE || slen > DSA_SIG_NUMBER_SIZE)
return -1;
/* Init to all zeroes */
memset(sig, 0, DSA_SIG_SIZE);
/* Add R at the end of the first block of the signature */
BN_bn2bin(sig_r, sig + DSA_SIG_SIZE
- DSA_SIG_NUMBER_SIZE - rlen);
/* Add S at the end of the second block of the signature */
BN_bn2bin(sig_s, sig + DSA_SIG_SIZE - slen);
/* Done */
DSA_SIG_free(dsa_sig);
return DSA_SIG_SIZE;
}
}
} }
/* Return the check value */ return -1;
libssh2_knownhost_free(ssh_known_hosts);
return kh_check;
} }

View File

@ -17,23 +17,22 @@
* under the License. * under the License.
*/ */
#include "common/rect.h" #include "config.h"
#include <CUnit/CUnit.h> #include <openssl/bn.h>
#include <openssl/rsa.h>
/** #include <stdlib.h>
* Test which verifies rectangle initialization via guac_common_rect_init().
*/
void test_rect__init() {
guac_common_rect max; #ifndef HAVE_RSA_GET0_KEY
void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n,
const BIGNUM** e, const BIGNUM**d) {
guac_common_rect_init(&max, 0, 0, 100, 100); /* Retrieve all requested internal values */
if (n != NULL) *n = rsa_key->n;
CU_ASSERT_EQUAL(0, max.x); if (e != NULL) *e = rsa_key->e;
CU_ASSERT_EQUAL(0, max.y); if (d != NULL) *d = rsa_key->d;
CU_ASSERT_EQUAL(100, max.width);
CU_ASSERT_EQUAL(100, max.height);
} }
#endif

View File

@ -24,7 +24,6 @@
#include <guacamole/object.h> #include <guacamole/object.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
#include <guacamole/string.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <libssh2.h> #include <libssh2.h>
@ -33,70 +32,107 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
int guac_common_ssh_sftp_normalize_path(char* fullpath, /**
* Given an arbitrary absolute path, which may contain "..", ".", and
* backslashes, creates an equivalent absolute path which does NOT contain
* relative path components (".." or "."), backslashes, or empty path
* components. With the exception of paths referring to the root directory, the
* resulting path is guaranteed to not contain trailing slashes.
*
* Normalization will fail if the given path is not absolute, is too long, or
* contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components.
*
* @param fullpath
* The buffer to populate with the normalized path. The normalized path
* will not contain relative path components like ".." or ".", nor will it
* contain backslashes. This buffer MUST be at least
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
*
* @param path
* The absolute path to normalize.
*
* @return
* Non-zero if normalization succeeded, zero otherwise.
*/
static int guac_common_ssh_sftp_normalize_path(char* fullpath,
const char* path) { const char* path) {
int i;
int path_depth = 0; int path_depth = 0;
char path_component_data[GUAC_COMMON_SSH_SFTP_MAX_PATH];
const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH]; const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH];
const char** current_path_component = &(path_components[0]);
const char* current_path_component_data = &(path_component_data[0]);
/* If original path is not absolute, normalization fails */ /* If original path is not absolute, normalization fails */
if (path[0] != '\\' && path[0] != '/') if (path[0] != '\\' && path[0] != '/')
return 0; return 1;
/* Create scratch copy of path excluding leading slash (we will be /* Skip past leading slash */
* replacing path separators with null terminators and referencing those path++;
* substrings directly as path components) */
char path_scratch[GUAC_COMMON_SSH_SFTP_MAX_PATH - 1];
int length = guac_strlcpy(path_scratch, path + 1,
sizeof(path_scratch));
/* Fail if provided path is too long */ /* Copy path into component data for parsing */
if (length >= sizeof(path_scratch)) strncpy(path_component_data, path, sizeof(path_component_data) - 1);
return 0;
/* Locate all path components within path */ /* Find path components within path */
const char* current_path_component = &(path_scratch[0]); for (i = 0; i < sizeof(path_component_data) - 1; i++) {
for (int i = 0; i <= length; i++) {
/* If current character is a path separator, parse as component */ /* If current character is a path separator, parse as component */
char c = path_scratch[i]; char c = path_component_data[i];
if (c == '/' || c == '\\' || c == '\0') { if (c == '/' || c == '\\' || c == '\0') {
/* Terminate current component */ /* Terminate current component */
path_scratch[i] = '\0'; path_component_data[i] = '\0';
/* If component refers to parent, just move up in depth */ /* If component refers to parent, just move up in depth */
if (strcmp(current_path_component, "..") == 0) { if (strcmp(current_path_component_data, "..") == 0) {
if (path_depth > 0) if (path_depth > 0)
path_depth--; path_depth--;
} }
/* Otherwise, if component not current directory, add to list */ /* Otherwise, if component not current directory, add to list */
else if (strcmp(current_path_component, ".") != 0 else if (strcmp(current_path_component_data, ".") != 0
&& strcmp(current_path_component, "") != 0) { && strcmp(current_path_component_data, "") != 0)
path_components[path_depth++] = current_path_component_data;
/* Fail normalization if path is too deep */ /* If end of string, stop */
if (path_depth >= GUAC_COMMON_SSH_SFTP_MAX_DEPTH) if (c == '\0')
return 0; break;
path_components[path_depth++] = current_path_component;
}
/* Update start of next component */ /* Update start of next component */
current_path_component = &(path_scratch[i+1]); current_path_component_data = &(path_component_data[i+1]);
} /* end if separator */ } /* end if separator */
} /* end for each character */ } /* end for each character */
/* Add leading slash for resulting absolute path */ /* If no components, the path is simply root */
fullpath[0] = '/'; if (path_depth == 0) {
strcpy(fullpath, "/");
return 1;
}
/* Append normalized components to path, separated by slashes */ /* Ensure last component is null-terminated */
guac_strljoin(fullpath + 1, path_components, path_depth, path_component_data[i] = 0;
"/", GUAC_COMMON_SSH_SFTP_MAX_PATH - 1);
/* Convert components back into path */
for (; path_depth > 0; path_depth--) {
const char* filename = *(current_path_component++);
/* Add separator */
*(fullpath++) = '/';
/* Copy string */
while (*filename != 0)
*(fullpath++) = *(filename++);
}
/* Terminate absolute path */
*(fullpath++) = 0;
return 1; return 1;
} }
@ -193,7 +229,7 @@ static guac_protocol_status guac_sftp_get_status(
static int guac_ssh_append_filename(char* fullpath, const char* path, static int guac_ssh_append_filename(char* fullpath, const char* path,
const char* filename) { const char* filename) {
int length; int i;
/* Disallow "." as a filename */ /* Disallow "." as a filename */
if (strcmp(filename, ".") == 0) if (strcmp(filename, ".") == 0)
@ -203,30 +239,50 @@ static int guac_ssh_append_filename(char* fullpath, const char* path,
if (strcmp(filename, "..") == 0) if (strcmp(filename, "..") == 0)
return 0; return 0;
/* Filenames may not contain slashes */ /* Copy path, append trailing slash */
if (strchr(filename, '/') != NULL) for (i=0; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
return 0;
/* Copy base path */ /*
length = guac_strlcpy(fullpath, path, GUAC_COMMON_SSH_SFTP_MAX_PATH); * Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
/* char c = path[i];
* Append trailing slash only if: if (c == '\0') {
* 1) Trailing slash is not already present if (i > 0 && path[i-1] != '/')
* 2) Path is non-empty fullpath[i++] = '/';
*/ break;
if (length > 0 && fullpath[length - 1] != '/') }
length += guac_strlcpy(fullpath + length, "/",
GUAC_COMMON_SSH_SFTP_MAX_PATH - length); /* Copy character if not end of string */
fullpath[i] = c;
}
/* Append filename */ /* Append filename */
length += guac_strlcpy(fullpath + length, filename, for (; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
char c = *(filename++);
if (c == '\0')
break;
/* Filenames may not contain slashes */
if (c == '\\' || c == '/')
return 0;
/* Append each character within filename */
fullpath[i] = c;
}
/* Verify path length is within maximum */ /* Verify path length is within maximum */
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0; return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */ /* Append was successful */
return 1; return 1;
@ -254,30 +310,46 @@ static int guac_ssh_append_filename(char* fullpath, const char* path,
static int guac_ssh_append_path(char* fullpath, const char* path_a, static int guac_ssh_append_path(char* fullpath, const char* path_a,
const char* path_b) { const char* path_b) {
int length; int i;
/* Copy first half of path */ /* Copy path, appending a trailing slash */
length = guac_strlcpy(fullpath, path_a, GUAC_COMMON_SSH_SFTP_MAX_PATH); for (i = 0; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0;
/* Ensure path ends with trailing slash */ char c = path_a[i];
if (length == 0 || fullpath[length - 1] != '/') if (c == '\0') {
length += guac_strlcpy(fullpath + length, "/", if (i > 0 && path_a[i-1] != '/')
GUAC_COMMON_SSH_SFTP_MAX_PATH - length); fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Skip past leading slashes in second path */ /* Skip past leading slashes in second path */
while (*path_b == '/') while (*path_b == '/')
path_b++; path_b++;
/* Append final half of path */ /* Append path */
length += guac_strlcpy(fullpath + length, path_b, for (; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
char c = *(path_b++);
if (c == '\0')
break;
/* Append each character within path */
fullpath[i] = c;
}
/* Verify path length is within maximum */ /* Verify path length is within maximum */
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH) if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0; return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */ /* Append was successful */
return 1; return 1;
@ -376,18 +448,6 @@ int guac_common_ssh_sftp_handle_file_stream(
char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH]; char fullpath[GUAC_COMMON_SSH_SFTP_MAX_PATH];
LIBSSH2_SFTP_HANDLE* file; LIBSSH2_SFTP_HANDLE* file;
/* Ignore upload if uploads have been disabled */
if (filesystem->disable_upload) {
guac_user_log(user, GUAC_LOG_WARNING, "A upload attempt has "
"been blocked due to uploads being disabled, however it "
"should have been blocked at a higher level. This is likely "
"a bug.");
guac_protocol_send_ack(user->socket, stream, "SFTP: Upload disabled",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
/* Concatenate filename with path */ /* Concatenate filename with path */
if (!guac_ssh_append_filename(fullpath, filesystem->upload_path, if (!guac_ssh_append_filename(fullpath, filesystem->upload_path,
filename)) { filename)) {
@ -441,7 +501,7 @@ int guac_common_ssh_sftp_handle_file_stream(
/** /**
* Handler for ack messages which continue an outbound SFTP data transfer * Handler for ack messages which continue an outbound SFTP data transfer
* (download), signaling the current status and requesting additional data. * (download), signalling the current status and requesting additional data.
* The data associated with the given stream is expected to be a pointer to an * The data associated with the given stream is expected to be a pointer to an
* open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read. * open LIBSSH2_SFTP_HANDLE for the file from which the data is to be read.
* *
@ -528,15 +588,6 @@ guac_stream* guac_common_ssh_sftp_download_file(
guac_stream* stream; guac_stream* stream;
LIBSSH2_SFTP_HANDLE* file; LIBSSH2_SFTP_HANDLE* file;
/* Ignore download if downloads have been disabled */
if (filesystem->disable_download) {
guac_user_log(user, GUAC_LOG_WARNING, "A download attempt has "
"been blocked due to downloads being disabled, however it "
"should have been blocked at a higher level. This is likely "
"a bug.");
return NULL;
}
/* Attempt to open file for reading */ /* Attempt to open file for reading */
file = libssh2_sftp_open(filesystem->sftp_session, filename, file = libssh2_sftp_open(filesystem->sftp_session, filename,
LIBSSH2_FXF_READ, 0); LIBSSH2_FXF_READ, 0);
@ -607,6 +658,7 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
guac_stream* stream, char* message, guac_protocol_status status) { guac_stream* stream, char* message, guac_protocol_status status) {
int bytes_read; int bytes_read;
int blob_written = 0;
char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH]; char filename[GUAC_COMMON_SSH_SFTP_MAX_PATH];
LIBSSH2_SFTP_ATTRIBUTES attributes; LIBSSH2_SFTP_ATTRIBUTES attributes;
@ -628,7 +680,8 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
/* While directory entries remain */ /* While directory entries remain */
while ((bytes_read = libssh2_sftp_readdir(list_state->directory, while ((bytes_read = libssh2_sftp_readdir(list_state->directory,
filename, sizeof(filename), &attributes)) > 0) { filename, sizeof(filename), &attributes)) > 0
&& !blob_written) {
char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH]; char absolute_path[GUAC_COMMON_SSH_SFTP_MAX_PATH];
@ -658,10 +711,9 @@ static int guac_common_ssh_sftp_ls_ack_handler(guac_user* user,
else else
mimetype = "application/octet-stream"; mimetype = "application/octet-stream";
/* Write entry, waiting for next ack if a blob is written */ /* Write entry */
if (guac_common_json_write_property(user, stream, blob_written |= guac_common_json_write_property(user, stream,
&list_state->json_state, absolute_path, mimetype)) &list_state->json_state, absolute_path, mimetype);
break;
} }
@ -778,17 +830,8 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
list_state->directory = dir; list_state->directory = dir;
list_state->filesystem = filesystem; list_state->filesystem = filesystem;
strncpy(list_state->directory_name, name,
int length = guac_strlcpy(list_state->directory_name, name, sizeof(list_state->directory_name) - 1);
sizeof(list_state->directory_name));
/* Bail out if directory name is too long to store */
if (length >= sizeof(list_state->directory_name)) {
guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory "
"\"%s\": Path too long", fullpath);
free(list_state);
return 0;
}
/* Allocate stream for body */ /* Allocate stream for body */
guac_stream* stream = guac_user_alloc_stream(user); guac_stream* stream = guac_user_alloc_stream(user);
@ -807,14 +850,6 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
/* Otherwise, send file contents */ /* Otherwise, send file contents */
else { else {
/* If downloads are disabled, log and return. */
if (filesystem->disable_download) {
guac_user_log(user, GUAC_LOG_INFO,
"Unable to download file \"%s\", "
"file downloads have been disabled.", fullpath);
return 0;
}
/* Open as normal file */ /* Open as normal file */
LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath, LIBSSH2_SFTP_HANDLE* file = libssh2_sftp_open(sftp, fullpath,
LIBSSH2_FXF_READ, 0); LIBSSH2_FXF_READ, 0);
@ -871,18 +906,6 @@ static int guac_common_ssh_sftp_put_handler(guac_user* user,
guac_common_ssh_sftp_filesystem* filesystem = guac_common_ssh_sftp_filesystem* filesystem =
(guac_common_ssh_sftp_filesystem*) object->data; (guac_common_ssh_sftp_filesystem*) object->data;
/* Ignore upload if uploads have been disabled */
if (filesystem->disable_upload) {
guac_user_log(user, GUAC_LOG_WARNING, "A upload attempt has "
"been blocked due to uploads being disabled, however it "
"should have been blocked at a higher level. This is likely "
"a bug.");
guac_protocol_send_ack(user->socket, stream, "SFTP: Upload disabled",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
LIBSSH2_SFTP* sftp = filesystem->sftp_session; LIBSSH2_SFTP* sftp = filesystem->sftp_session;
/* Translate stream name into filesystem path */ /* Translate stream name into filesystem path */
@ -943,11 +966,7 @@ guac_object* guac_common_ssh_alloc_sftp_filesystem_object(
/* Init filesystem */ /* Init filesystem */
guac_object* fs_object = guac_user_alloc_object(user); guac_object* fs_object = guac_user_alloc_object(user);
fs_object->get_handler = guac_common_ssh_sftp_get_handler; fs_object->get_handler = guac_common_ssh_sftp_get_handler;
fs_object->put_handler = guac_common_ssh_sftp_put_handler;
/* Only handle uploads if not disabled. */
if (!filesystem->disable_upload)
fs_object->put_handler = guac_common_ssh_sftp_put_handler;
fs_object->data = filesystem; fs_object->data = filesystem;
/* Send filesystem to user */ /* Send filesystem to user */
@ -960,7 +979,7 @@ guac_object* guac_common_ssh_alloc_sftp_filesystem_object(
guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem( guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
guac_common_ssh_session* session, const char* root_path, guac_common_ssh_session* session, const char* root_path,
const char* name, int disable_download, int disable_upload) { const char* name) {
/* Request SFTP */ /* Request SFTP */
LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session); LIBSSH2_SFTP* sftp_session = libssh2_sftp_init(session->session);
@ -974,10 +993,6 @@ guac_common_ssh_sftp_filesystem* guac_common_ssh_create_sftp_filesystem(
/* Associate SSH session with SFTP data and user */ /* Associate SSH session with SFTP data and user */
filesystem->ssh_session = session; filesystem->ssh_session = session;
filesystem->sftp_session = sftp_session; filesystem->sftp_session = sftp_session;
/* Copy over disable flags */
filesystem->disable_download = disable_download;
filesystem->disable_upload = disable_upload;
/* Normalize and store the provided root path */ /* Normalize and store the provided root path */
if (!guac_common_ssh_sftp_normalize_path(filesystem->root_path, if (!guac_common_ssh_sftp_normalize_path(filesystem->root_path,

View File

@ -22,7 +22,6 @@
#include "common-ssh/user.h" #include "common-ssh/user.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/fips.h>
#include <libssh2.h> #include <libssh2.h>
#ifdef LIBSSH2_USES_GCRYPT #ifdef LIBSSH2_USES_GCRYPT
@ -36,7 +35,6 @@
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pthread.h> #include <pthread.h>
#include <pwd.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -47,20 +45,6 @@
GCRY_THREAD_OPTION_PTHREAD_IMPL; GCRY_THREAD_OPTION_PTHREAD_IMPL;
#endif #endif
/**
* A list of all key exchange algorithms that are both FIPS-compliant, and
* OpenSSL-supported. Note that "ext-info-c" is also included. While not a key
* exchange algorithm per se, it must be in the list to ensure that the server
* will send a SSH_MSG_EXT_INFO response, which is required to perform RSA key
* upgrades.
*/
#define FIPS_COMPLIANT_KEX_ALGORITHMS "diffie-hellman-group-exchange-sha256,ext-info-c"
/**
* A list of ciphers that are both FIPS-compliant, and OpenSSL-supported.
*/
#define FIPS_COMPLIANT_CIPHERS "aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,aes192-cbc,aes256-cbc"
#ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS #ifdef OPENSSL_REQUIRES_THREADING_CALLBACKS
/** /**
* Array of mutexes, used by OpenSSL. * Array of mutexes, used by OpenSSL.
@ -155,21 +139,11 @@ static void guac_common_ssh_openssl_free_locks(int count) {
int guac_common_ssh_init(guac_client* client) { int guac_common_ssh_init(guac_client* client) {
#ifdef LIBSSH2_USES_GCRYPT #ifdef LIBSSH2_USES_GCRYPT
/* Init threadsafety in libgcrypt */
if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
if (!gcry_check_version(GCRYPT_VERSION)) {
/* Init threadsafety in libgcrypt */ guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); return 1;
/* Initialize GCrypt */
if (!gcry_check_version(GCRYPT_VERSION)) {
guac_client_log(client, GUAC_LOG_ERROR, "libgcrypt version mismatch.");
return 1;
}
/* Mark initialization as completed. */
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
} }
#endif #endif
@ -180,11 +154,9 @@ int guac_common_ssh_init(guac_client* client) {
CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback); CRYPTO_set_locking_callback(guac_common_ssh_openssl_locking_callback);
#endif #endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L /* Init OpenSSL */
/* Init OpenSSL - only required for OpenSSL Versions < 1.1.0 */
SSL_library_init(); SSL_library_init();
ERR_load_crypto_strings(); ERR_load_crypto_strings();
#endif
/* Init libssh2 */ /* Init libssh2 */
libssh2_init(0); libssh2_init(0);
@ -200,6 +172,55 @@ void guac_common_ssh_uninit() {
#endif #endif
} }
/**
* Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked.
* This callback must sign the given data, returning the signature as newly-
* allocated buffer space.
*
* @param session
* The SSH session for which the signature is being generated.
*
* @param sig
* A pointer to the buffer space containing the signature. This callback
* MUST allocate and assign this space.
*
* @param sig_len
* The length of the signature within the allocated buffer space, in bytes.
* This value must be set to the size of the signature after the signing
* operation completes.
*
* @param data
* The arbitrary data that must be signed.
*
* @param data_len
* The length of the arbitrary data to be signed, in bytes.
*
* @param abstract
* The value of the abstract parameter provided with the corresponding call
* to libssh2_userauth_publickey().
*
* @return
* Zero on success, non-zero if the signing operation failed.
*/
static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session,
unsigned char** sig, size_t* sig_len,
const unsigned char* data, size_t data_len, void **abstract) {
guac_common_ssh_key* key = (guac_common_ssh_key*) abstract;
int length;
/* Allocate space for signature */
*sig = malloc(4096);
/* Sign with key */
length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig);
if (length < 0)
return 1;
*sig_len = length;
return 0;
}
/** /**
* Callback for the keyboard-interactive authentication method. Currently * Callback for the keyboard-interactive authentication method. Currently
* supports just one prompt for the password. This callback is invoked as * supports just one prompt for the password. This callback is invoked as
@ -282,27 +303,20 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
LIBSSH2_SESSION* session = common_session->session; LIBSSH2_SESSION* session = common_session->session;
/* Get user credentials */ /* Get user credentials */
char* username = user->username;
char* password = user->password;
guac_common_ssh_key* key = user->private_key; guac_common_ssh_key* key = user->private_key;
/* Validate username provided */ /* Validate username provided */
if (user->username == NULL) { if (username == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"SSH authentication requires a username."); "SSH authentication requires a username.");
return 1; return 1;
} }
/* Get list of supported authentication methods */ /* Get list of supported authentication methods */
size_t username_len = strlen(user->username); char* user_authlist = libssh2_userauth_list(session, username,
char* user_authlist = libssh2_userauth_list(session, user->username, strlen(username));
username_len);
/* If auth list is NULL, then authentication has succeeded with NONE */
if (user_authlist == NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"SSH NONE authentication succeeded.");
return 0;
}
guac_client_log(client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
"Supported authentication methods: %s", user_authlist); "Supported authentication methods: %s", user_authlist);
@ -318,9 +332,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
} }
/* Attempt public key auth */ /* Attempt public key auth */
if (libssh2_userauth_publickey_frommemory(session, user->username, if (libssh2_userauth_publickey(session, username,
username_len, NULL, 0, key->private_key, (unsigned char*) key->public_key, key->public_key_length,
key->private_key_length, key->passphrase)) { guac_common_ssh_sign_callback, (void**) key)) {
/* Abort on failure */ /* Abort on failure */
char* error_message; char* error_message;
@ -337,18 +351,14 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
} }
/* Attempt authentication with username + password. */
if (user->password == NULL && common_session->credential_handler)
user->password = common_session->credential_handler(client, "Password: ");
/* Authenticate with password, if provided */ /* Authenticate with password, if provided */
if (user->password != NULL) { else if (password != NULL) {
/* Check if password auth is supported on the server */ /* Check if password auth is supported on the server */
if (strstr(user_authlist, "password") != NULL) { if (strstr(user_authlist, "password") != NULL) {
/* Attempt password authentication */ /* Attempt password authentication */
if (libssh2_userauth_password(session, user->username, user->password)) { if (libssh2_userauth_password(session, username, password)) {
/* Abort on failure */ /* Abort on failure */
char* error_message; char* error_message;
@ -369,7 +379,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
if (strstr(user_authlist, "keyboard-interactive") != NULL) { if (strstr(user_authlist, "keyboard-interactive") != NULL) {
/* Attempt keyboard-interactive auth using provided password */ /* Attempt keyboard-interactive auth using provided password */
if (libssh2_userauth_keyboard_interactive(session, user->username, if (libssh2_userauth_keyboard_interactive(session, username,
&guac_common_ssh_kbd_callback)) { &guac_common_ssh_kbd_callback)) {
/* Abort on failure */ /* Abort on failure */
@ -404,9 +414,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
} }
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client, guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user, const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive) {
int keepalive, const char* host_key,
guac_ssh_credential_handler* credential_handler) {
int retval; int retval;
@ -423,11 +431,20 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
.ai_protocol = IPPROTO_TCP .ai_protocol = IPPROTO_TCP
}; };
/* Get socket */
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to create socket: %s", strerror(errno));
return NULL;
}
/* Get addresses connection */ /* Get addresses connection */
if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) { if ((retval = getaddrinfo(hostname, port, &hints, &addresses))) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Error parsing given address or port: %s", "Error parsing given address or port: %s",
gai_strerror(retval)); gai_strerror(retval));
close(fd);
return NULL; return NULL;
} }
@ -444,15 +461,6 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
guac_client_log(client, GUAC_LOG_DEBUG, guac_client_log(client, GUAC_LOG_DEBUG,
"Unable to resolve host: %s", gai_strerror(retval)); "Unable to resolve host: %s", gai_strerror(retval));
/* Get socket */
fd = socket(current_address->ai_family, SOCK_STREAM, 0);
if (fd < 0) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to create socket: %s", strerror(errno));
freeaddrinfo(addresses);
return NULL;
}
/* Connect */ /* Connect */
if (connect(fd, current_address->ai_addr, if (connect(fd, current_address->ai_addr,
current_address->ai_addrlen) == 0) { current_address->ai_addrlen) == 0) {
@ -467,11 +475,11 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
} }
/* Otherwise log information regarding bind failure */ /* Otherwise log information regarding bind failure */
guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to " else
"host %s, port %s: %s", guac_client_log(client, GUAC_LOG_DEBUG, "Unable to connect to "
connected_address, connected_port, strerror(errno)); "host %s, port %s: %s",
connected_address, connected_port, strerror(errno));
close(fd);
current_address = current_address->ai_next; current_address = current_address->ai_next;
} }
@ -483,6 +491,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
if (current_address == NULL) { if (current_address == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND,
"Unable to connect to any addresses."); "Unable to connect to any addresses.");
close(fd);
return NULL; return NULL;
} }
@ -501,17 +510,6 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
return NULL; return NULL;
} }
/*
* If FIPS mode is enabled, prefer only FIPS-compatible algorithms and
* ciphers that are also supported by libssh2. For more info, see:
* https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp2906.pdf
*/
if (guac_fips_enabled()) {
libssh2_session_method_pref(session, LIBSSH2_METHOD_KEX, FIPS_COMPLIANT_KEX_ALGORITHMS);
libssh2_session_method_pref(session, LIBSSH2_METHOD_CRYPT_CS, FIPS_COMPLIANT_CIPHERS);
libssh2_session_method_pref(session, LIBSSH2_METHOD_CRYPT_SC, FIPS_COMPLIANT_CIPHERS);
}
/* Perform handshake */ /* Perform handshake */
if (libssh2_session_handshake(session, fd)) { if (libssh2_session_handshake(session, fd)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR, guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_ERROR,
@ -521,48 +519,11 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
return NULL; return NULL;
} }
/* Get host key of remote system we're connecting to */
size_t remote_hostkey_len;
const char *remote_hostkey = libssh2_session_hostkey(session, &remote_hostkey_len, NULL);
/* Failure to retrieve a host key means we should abort */
if (!remote_hostkey) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Failed to get host key for %s", hostname);
free(common_session);
close(fd);
return NULL;
}
/* SSH known host key checking. */
int known_host_check = guac_common_ssh_verify_host_key(session, client, host_key,
hostname, atoi(port), remote_hostkey,
remote_hostkey_len);
/* Abort on any error codes */
if (known_host_check != 0) {
char* err_msg;
libssh2_session_last_error(session, &err_msg, NULL, 0);
if (known_host_check < 0)
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Error occurred attempting to check host key: %s", err_msg);
if (known_host_check > 0)
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Host key did not match any provided known host keys. %s", err_msg);
free(common_session);
close(fd);
return NULL;
}
/* Store basic session data */ /* Store basic session data */
common_session->client = client; common_session->client = client;
common_session->user = user; common_session->user = user;
common_session->session = session; common_session->session = session;
common_session->fd = fd; common_session->fd = fd;
common_session->credential_handler = credential_handler;
/* Attempt authentication */ /* Attempt authentication */
if (guac_common_ssh_authenticate(common_session)) { if (guac_common_ssh_authenticate(common_session)) {
@ -600,3 +561,4 @@ void guac_common_ssh_destroy_session(guac_common_ssh_session* session) {
free(session); free(session);
} }

View File

@ -1,67 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
#
# Unit tests for common SSH support
#
check_PROGRAMS = test_common_ssh
TESTS = $(check_PROGRAMS)
test_common_ssh_SOURCES = \
sftp/normalize_path.c
test_common_ssh_CFLAGS = \
-Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@
test_common_ssh_LDADD = \
@CUNIT_LIBS@ \
@COMMON_SSH_LTLIB@ \
@COMMON_LTLIB@
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_common_ssh_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_common_ssh_SOURCES) > $@
nodist_test_common_ssh_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh

View File

@ -1,263 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common-ssh/sftp.h"
#include <CUnit/CUnit.h>
#include <stdlib.h>
/**
* Test which verifies absolute Windows-style paths are correctly normalized to
* absolute paths with UNIX separators and no relative components.
*/
void test_sftp__normalize_absolute_windows() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\baz\\"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\baz\\a\\..\\b"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\..\\..\\..\\..\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
}
/**
* Test which verifies absolute UNIX-style paths are correctly normalized to
* absolute paths with UNIX separators and no relative components.
*/
void test_sftp__normalize_absolute_unix() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../baz/"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../baz/a/../b"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/./bar/baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../../../../../baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
}
/**
* Test which verifies absolute paths consisting of mixed Windows and UNIX path
* separators are correctly normalized to absolute paths with UNIX separators
* and no relative components.
*/
void test_sftp__normalize_absolute_mixed() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo\\bar/..\\baz/"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../../baz\\a\\..\\b"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar/baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../..\\..\\..\\../..\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
}
/**
* Test which verifies relative Windows-style paths are always rejected.
*/
void test_sftp__normalize_relative_windows() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar\\baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo\\bar\\baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo\\bar\\baz"), 0);
}
/**
* Test which verifies relative UNIX-style paths are always rejected.
*/
void test_sftp__normalize_relative_unix() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo/bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo/bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo/bar/baz"), 0);
}
/**
* Test which verifies relative paths consisting of mixed Windows and UNIX path
* separators are always rejected.
*/
void test_sftp__normalize_relative_mixed() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo/bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo\\bar\\baz"), 0);
}
/**
* Generates a dynamically-allocated path having the given number of bytes, not
* counting the null-terminator. The path will contain only UNIX-style path
* separators. The returned path must eventually be freed with a call to
* free().
*
* @param length
* The number of bytes to include in the generated path, not counting the
* null-terminator. If -1, the length of the path will be automatically
* determined from the provided max_depth.
*
* @param max_depth
* The maximum number of path components to include within the generated
* path.
*
* @return
* A dynamically-allocated path containing the given number of bytes, not
* counting the null-terminator. This path must eventually be freed with a
* call to free().
*/
static char* generate_path(int length, int max_depth) {
/* If no length given, calculate space required from max_depth */
if (length == -1)
length = max_depth * 2;
int i;
char* input = malloc(length + 1);
/* Fill path with /x/x/x/x/x/x/x/x/x/x/.../xxxxxxxxx... */
for (i = 0; i < length; i++) {
if (max_depth > 0 && i % 2 == 0) {
input[i] = '/';
max_depth--;
}
else
input[i] = 'x';
}
/* Add null terminator */
input[length] = '\0';
return input;
}
/**
* Test which verifies that paths exceeding the maximum path length are
* rejected.
*/
void test_sftp__normalize_long() {
char* input;
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/* Exceeds maximum length by a factor of 2 */
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH * 2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exceeds maximum length by one byte */
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exactly maximum length */
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
}
/**
* Test which verifies that paths exceeding the maximum path depth are
* rejected.
*/
void test_sftp__normalize_deep() {
char* input;
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/* Exceeds maximum depth by a factor of 2 */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH * 2);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exceeds maximum depth by one component */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH + 1);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exactly maximum depth (should still be rejected as SFTP depth limits are
* set such that a path with the maximum depth will exceed the maximum
* length) */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Less than maximum depth */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH - 1);
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
}

View File

@ -1,5 +0,0 @@
# Auto-generated test runner and binary
_generated_runner.c
test_common

View File

@ -16,25 +16,17 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
# #
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
noinst_LTLIBRARIES = libguac_common.la noinst_LTLIBRARIES = libguac_common.la
SUBDIRS = . tests
noinst_HEADERS = \ noinst_HEADERS = \
common/io.h \ common/io.h \
common/blank_cursor.h \ common/blank_cursor.h \
common/clipboard.h \ common/clipboard.h \
common/cursor.h \ common/cursor.h \
common/defaults.h \
common/display.h \ common/display.h \
common/dot_cursor.h \ common/dot_cursor.h \
common/ibar_cursor.h \ common/ibar_cursor.h \
@ -42,6 +34,7 @@ noinst_HEADERS = \
common/json.h \ common/json.h \
common/list.h \ common/list.h \
common/pointer_cursor.h \ common/pointer_cursor.h \
common/recording.h \
common/rect.h \ common/rect.h \
common/string.h \ common/string.h \
common/surface.h common/surface.h
@ -58,6 +51,7 @@ libguac_common_la_SOURCES = \
json.c \ json.c \
list.c \ list.c \
pointer_cursor.c \ pointer_cursor.c \
recording.c \
rect.c \ rect.c \
string.c \ string.c \
surface.c surface.c

View File

@ -23,37 +23,26 @@
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/stream.h> #include <guacamole/stream.h>
#include <guacamole/string.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <pthread.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
guac_common_clipboard* guac_common_clipboard_alloc() { guac_common_clipboard* guac_common_clipboard_alloc(int size) {
guac_common_clipboard* clipboard = malloc(sizeof(guac_common_clipboard)); guac_common_clipboard* clipboard = malloc(sizeof(guac_common_clipboard));
/* Init clipboard */ /* Init clipboard */
clipboard->mimetype[0] = '\0'; clipboard->mimetype[0] = '\0';
clipboard->buffer = malloc(GUAC_COMMON_CLIPBOARD_MAX_LENGTH); clipboard->buffer = malloc(size);
clipboard->available = GUAC_COMMON_CLIPBOARD_MAX_LENGTH;
clipboard->length = 0; clipboard->length = 0;
clipboard->available = size;
pthread_mutex_init(&(clipboard->lock), NULL);
return clipboard; return clipboard;
} }
void guac_common_clipboard_free(guac_common_clipboard* clipboard) { void guac_common_clipboard_free(guac_common_clipboard* clipboard) {
/* Destroy lock */
pthread_mutex_destroy(&(clipboard->lock));
/* Free buffer */
free(clipboard->buffer); free(clipboard->buffer);
/* Free base structure */
free(clipboard); free(clipboard);
} }
@ -119,36 +108,18 @@ static void* __send_user_clipboard(guac_user* user, void* data) {
} }
void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* client) { void guac_common_clipboard_send(guac_common_clipboard* clipboard, guac_client* client) {
pthread_mutex_lock(&(clipboard->lock));
guac_client_log(client, GUAC_LOG_DEBUG, "Broadcasting clipboard to all connected users."); guac_client_log(client, GUAC_LOG_DEBUG, "Broadcasting clipboard to all connected users.");
guac_client_foreach_user(client, __send_user_clipboard, clipboard); guac_client_foreach_user(client, __send_user_clipboard, clipboard);
guac_client_log(client, GUAC_LOG_DEBUG, "Broadcast of clipboard complete."); guac_client_log(client, GUAC_LOG_DEBUG, "Broadcast of clipboard complete.");
pthread_mutex_unlock(&(clipboard->lock));
} }
void guac_common_clipboard_reset(guac_common_clipboard* clipboard, void guac_common_clipboard_reset(guac_common_clipboard* clipboard, const char* mimetype) {
const char* mimetype) {
pthread_mutex_lock(&(clipboard->lock));
/* Clear clipboard contents */
clipboard->length = 0; clipboard->length = 0;
strncpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype)-1);
/* Assign given mimetype */
guac_strlcpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype));
pthread_mutex_unlock(&(clipboard->lock));
} }
void guac_common_clipboard_append(guac_common_clipboard* clipboard, const char* data, int length) { void guac_common_clipboard_append(guac_common_clipboard* clipboard, const char* data, int length) {
pthread_mutex_lock(&(clipboard->lock));
/* Truncate data to available length */ /* Truncate data to available length */
int remaining = clipboard->available - clipboard->length; int remaining = clipboard->available - clipboard->length;
if (remaining < length) if (remaining < length)
@ -160,7 +131,5 @@ void guac_common_clipboard_append(guac_common_clipboard* clipboard, const char*
/* Update length */ /* Update length */
clipboard->length += length; clipboard->length += length;
pthread_mutex_unlock(&(clipboard->lock));
} }

View File

@ -23,7 +23,6 @@
#include "config.h" #include "config.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <pthread.h>
/** /**
* The maximum number of bytes to send in an individual blob when * The maximum number of bytes to send in an individual blob when
@ -31,23 +30,11 @@
*/ */
#define GUAC_COMMON_CLIPBOARD_BLOCK_SIZE 4096 #define GUAC_COMMON_CLIPBOARD_BLOCK_SIZE 4096
/**
* The maximum number of bytes to allow within the clipboard.
*/
#define GUAC_COMMON_CLIPBOARD_MAX_LENGTH 262144
/** /**
* Generic clipboard structure. * Generic clipboard structure.
*/ */
typedef struct guac_common_clipboard { typedef struct guac_common_clipboard {
/**
* Lock which restricts simultaneous access to the clipboard, guaranteeing
* ordered modifications to the clipboard and that changes to the clipboard
* are not allowed while the clipboard is being broadcast to all users.
*/
pthread_mutex_t lock;
/** /**
* The mimetype of the contained clipboard data. * The mimetype of the contained clipboard data.
*/ */
@ -71,9 +58,12 @@ typedef struct guac_common_clipboard {
} guac_common_clipboard; } guac_common_clipboard;
/** /**
* Creates a new clipboard. * Creates a new clipboard having the given initial size.
*
* @param size The maximum number of bytes to allow within the clipboard.
* @return A newly-allocated clipboard.
*/ */
guac_common_clipboard* guac_common_clipboard_alloc(); guac_common_clipboard* guac_common_clipboard_alloc(int size);
/** /**
* Frees the given clipboard. * Frees the given clipboard.

View File

@ -102,27 +102,6 @@ typedef struct guac_common_cursor {
*/ */
int y; int y;
/**
* An integer value representing the current state of each button, where
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
* button is currently pressed. The lowest-order bit is the left mouse
* button, followed by the middle button, right button, and finally the up
* and down buttons of the scroll wheel.
*
* @see GUAC_CLIENT_MOUSE_LEFT
* @see GUAC_CLIENT_MOUSE_MIDDLE
* @see GUAC_CLIENT_MOUSE_RIGHT
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*/
int button_mask;
/**
* The server timestamp representing the point in time when the mousr
* location was last updated.
*/
guac_timestamp timestamp;
} guac_common_cursor; } guac_common_cursor;
/** /**
@ -163,12 +142,12 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
guac_socket* socket); guac_socket* socket);
/** /**
* Updates the current position and button state of the mouse cursor, marking * Moves the mouse cursor, marking the given user as the most recent user of
* the given user as the most recent user of the mouse. The remote mouse cursor * the mouse. The remote mouse cursor will be hidden for this user and shown
* will be hidden for this user and shown for all others. * for all others.
* *
* @param cursor * @param cursor
* The cursor being updated. * The cursor being moved.
* *
* @param user * @param user
* The user that moved the cursor. * The user that moved the cursor.
@ -178,22 +157,9 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
* *
* @param y * @param y
* The new Y coordinate of the cursor. * The new Y coordinate of the cursor.
*
* @param button_mask
* An integer value representing the current state of each button, where
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
* button is currently pressed. The lowest-order bit is the left mouse
* button, followed by the middle button, right button, and finally the up
* and down buttons of the scroll wheel.
*
* @see GUAC_CLIENT_MOUSE_LEFT
* @see GUAC_CLIENT_MOUSE_MIDDLE
* @see GUAC_CLIENT_MOUSE_RIGHT
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*/ */
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user,
int x, int y, int button_mask); int x, int y);
/** /**
* Sets the cursor image to the given raw image data. This raw image data must * Sets the cursor image to the given raw image data. This raw image data must

View File

@ -1,31 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_COMMON_DEFAULTS_H
#define GUAC_COMMON_DEFAULTS_H
/**
* The default number of seconds to wait after sending the Wake-on-LAN packet
* for the destination host to start responding.
*/
#define GUAC_WOL_DEFAULT_BOOT_WAIT_TIME 0
#endif /* GUAC_COMMON_DEFAULTS_H */

View File

@ -99,13 +99,6 @@ typedef struct guac_common_display {
*/ */
guac_common_display_layer* buffers; guac_common_display_layer* buffers;
/**
* Non-zero if all graphical updates for this display should use lossless
* compression, 0 otherwise. By default, newly-created displays will use
* lossy compression when heuristics determine it is appropriate.
*/
int lossless;
/** /**
* Mutex which is locked internally when access to the display must be * Mutex which is locked internally when access to the display must be
* synchronized. All public functions of guac_common_display should be * synchronized. All public functions of guac_common_display should be
@ -235,27 +228,5 @@ void guac_common_display_free_layer(guac_common_display* display,
void guac_common_display_free_buffer(guac_common_display* display, void guac_common_display_free_buffer(guac_common_display* display,
guac_common_display_layer* display_buffer); guac_common_display_layer* display_buffer);
/**
* Sets the overall lossless compression policy of the given display to the
* given value, affecting all current and future layers/buffers maintained by
* the display. By default, newly-created displays will use lossy compression
* for graphical updates when heuristics determine that doing so is
* appropriate. Specifying a non-zero value here will force all graphical
* updates to always use lossless compression, whereas specifying zero will
* restore the default policy.
*
* Note that this can also be adjusted on a per-layer / per-buffer basis with
* guac_common_surface_set_lossless().
*
* @param display
* The display to modify.
*
* @param lossless
* Non-zero if all graphical updates for this display should use lossless
* compression, 0 otherwise.
*/
void guac_common_display_set_lossless(guac_common_display* display,
int lossless);
#endif #endif

View File

@ -76,30 +76,6 @@ guac_iconv_read GUAC_READ_CP1252;
*/ */
guac_iconv_read GUAC_READ_ISO8859_1; guac_iconv_read GUAC_READ_ISO8859_1;
/**
* Read function for UTF-8 which normalizes newline character sequences like
* "\r\n" to Unix-style newlines ('\n').
*/
guac_iconv_read GUAC_READ_UTF8_NORMALIZED;
/**
* Read function for UTF-16 which normalizes newline character sequences like
* "\r\n" to Unix-style newlines ('\n').
*/
guac_iconv_read GUAC_READ_UTF16_NORMALIZED;
/**
* Read function for CP-1252 which normalizes newline character sequences like
* "\r\n" to Unix-style newlines ('\n').
*/
guac_iconv_read GUAC_READ_CP1252_NORMALIZED;
/**
* Read function for ISO 8859-1 which normalizes newline character sequences
* like "\r\n" to Unix-style newlines ('\n').
*/
guac_iconv_read GUAC_READ_ISO8859_1_NORMALIZED;
/** /**
* Write function for UTF8. * Write function for UTF8.
*/ */
@ -120,29 +96,5 @@ guac_iconv_write GUAC_WRITE_CP1252;
*/ */
guac_iconv_write GUAC_WRITE_ISO8859_1; guac_iconv_write GUAC_WRITE_ISO8859_1;
/**
* Write function for UTF-8 which writes newline characters ('\n') as
* Windows-style newlines ("\r\n").
*/
guac_iconv_write GUAC_WRITE_UTF8_CRLF;
/**
* Write function for UTF-16 which writes newline characters ('\n') as
* Windows-style newlines ("\r\n").
*/
guac_iconv_write GUAC_WRITE_UTF16_CRLF;
/**
* Write function for CP-1252 which writes newline characters ('\n') as
* Windows-style newlines ("\r\n").
*/
guac_iconv_write GUAC_WRITE_CP1252_CRLF;
/**
* Write function for ISO 8859-1 which writes newline characters ('\n') as
* Windows-style newlines ("\r\n").
*/
guac_iconv_write GUAC_WRITE_ISO8859_1_CRLF;
#endif #endif

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_COMMON_RECORDING_H
#define GUAC_COMMON_RECORDING_H
#include <guacamole/client.h>
/**
* The maximum numeric value allowed for the .1, .2, .3, etc. suffix appended
* to the end of the session recording filename if a recording having the
* requested name already exists.
*/
#define GUAC_COMMON_RECORDING_MAX_SUFFIX 255
/**
* The maximum length of the string containing a sequential numeric suffix
* between 1 and GUAC_COMMON_RECORDING_MAX_SUFFIX inclusive, in bytes,
* including NULL terminator.
*/
#define GUAC_COMMON_RECORDING_MAX_SUFFIX_LENGTH 4
/**
* The maximum overall length of the full path to the session recording file,
* including any additional suffix and NULL terminator, in bytes.
*/
#define GUAC_COMMON_RECORDING_MAX_NAME_LENGTH 2048
/**
* Replaces the socket of the given client such that all further Guacamole
* protocol output will be copied into a file within the given path and having
* the given name. If the create_path flag is non-zero, the given path will be
* created if it does not yet exist. If creation of the recording file or path
* fails, error messages will automatically be logged, and no recording will be
* written. The recording will automatically be closed once the client is
* freed.
*
* @param client
* The client whose output should be copied to a recording file.
*
* @param path
* The full absolute path to a directory in which the recording file should
* be created.
*
* @param name
* The base name to use for the recording file created within the specified
* path.
*
* @param create_path
* Zero if the specified path MUST exist for the recording file to be
* written, or non-zero if the path should be created if it does not yet
* exist.
*
* @return
* Zero if the recording file has been successfully created and a recording
* will be written, non-zero otherwise.
*/
int guac_common_recording_create(guac_client* client, const char* path,
const char* name, int create_path);
#endif

View File

@ -120,19 +120,6 @@ typedef struct guac_common_surface {
*/ */
guac_socket* socket; guac_socket* socket;
/**
* The number of simultaneous touches that this surface can accept, where 0
* indicates that the surface does not support touch events at all.
*/
int touches;
/**
* Non-zero if all graphical updates for this surface should use lossless
* compression, 0 otherwise. By default, newly-created surfaces will use
* lossy compression when heuristics determine it is appropriate.
*/
int lossless;
/** /**
* The X coordinate of the upper-left corner of this layer, in pixels, * The X coordinate of the upper-left corner of this layer, in pixels,
* relative to its parent layer. This is only applicable to visible * relative to its parent layer. This is only applicable to visible
@ -499,41 +486,5 @@ void guac_common_surface_flush(guac_common_surface* surface);
void guac_common_surface_dup(guac_common_surface* surface, guac_user* user, void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
guac_socket* socket); guac_socket* socket);
/**
* Declares that the given surface should receive touch events. By default,
* surfaces are assumed to not expect touch events. This value is advisory, and
* the client is not required to honor the declared level of touch support.
* Implementations are expected to safely handle or ignore any received touch
* events, regardless of the level of touch support declared. regardless of
* the level of touch support declared.
*
* @param surface
* The surface to modify.
*
* @param touches
* The number of simultaneous touches that this surface can accept, where 0
* indicates that the surface does not support touch events at all.
*/
void guac_common_surface_set_multitouch(guac_common_surface* surface,
int touches);
/**
* Sets the lossless compression policy of the given surface to the given
* value. By default, newly-created surfaces will use lossy compression for
* graphical updates when heuristics determine that doing so is appropriate.
* Specifying a non-zero value here will force all graphical updates to always
* use lossless compression, whereas specifying zero will restore the default
* policy.
*
* @param surface
* The surface to modify.
*
* @param lossless
* Non-zero if all graphical updates for this surface should use lossless
* compression, 0 otherwise.
*/
void guac_common_surface_set_lossless(guac_common_surface* surface,
int lossless);
#endif #endif

View File

@ -28,23 +28,12 @@
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/socket.h> #include <guacamole/socket.h>
#include <guacamole/timestamp.h>
#include <guacamole/user.h> #include <guacamole/user.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
/**
* Allocates a cursor as well as an image buffer where the cursor is rendered.
*
* @param client
* The client owning the cursor.
*
* @return
* The newly-allocated cursor or NULL if cursor cannot be allocated.
*/
guac_common_cursor* guac_common_cursor_alloc(guac_client* client) { guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
guac_common_cursor* cursor = malloc(sizeof(guac_common_cursor)); guac_common_cursor* cursor = malloc(sizeof(guac_common_cursor));
@ -68,7 +57,6 @@ guac_common_cursor* guac_common_cursor_alloc(guac_client* client) {
/* No user has moved the mouse yet */ /* No user has moved the mouse yet */
cursor->user = NULL; cursor->user = NULL;
cursor->timestamp = guac_timestamp_current();
/* Start cursor in upper-left */ /* Start cursor in upper-left */
cursor->x = 0; cursor->x = 0;
@ -103,8 +91,7 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
guac_socket* socket) { guac_socket* socket) {
/* Synchronize location */ /* Synchronize location */
guac_protocol_send_mouse(socket, cursor->x, cursor->y, cursor->button_mask, guac_protocol_send_mouse(socket, cursor->x, cursor->y);
cursor->timestamp);
/* Synchronize cursor image */ /* Synchronize cursor image */
if (cursor->surface != NULL) { if (cursor->surface != NULL) {
@ -125,25 +112,23 @@ void guac_common_cursor_dup(guac_common_cursor* cursor, guac_user* user,
/** /**
* Callback for guac_client_foreach_user() which sends the current cursor * Callback for guac_client_foreach_user() which sends the current cursor
* position and button state to any given user except the user that moved the * position to any given user except the user that moved the cursor last.
* cursor last.
* *
* @param data * @param data
* A pointer to the guac_common_cursor whose state should be broadcast to * A pointer to the guac_common_cursor whose position should be broadcast
* all users except the user that moved the cursor last. * to all users except the user that moved the cursor last.
* *
* @return * @return
* Always NULL. * Always NULL.
*/ */
static void* guac_common_cursor_broadcast_state(guac_user* user, static void* guac_common_cursor_broadcast_position(guac_user* user,
void* data) { void* data) {
guac_common_cursor* cursor = (guac_common_cursor*) data; guac_common_cursor* cursor = (guac_common_cursor*) data;
/* Send cursor state only if the user is not moving the cursor */ /* Send cursor position only if the user is not moving the cursor */
if (user != cursor->user) { if (user != cursor->user) {
guac_protocol_send_mouse(user->socket, cursor->x, cursor->y, guac_protocol_send_mouse(user->socket, cursor->x, cursor->y);
cursor->button_mask, cursor->timestamp);
guac_socket_flush(user->socket); guac_socket_flush(user->socket);
} }
@ -151,23 +136,19 @@ static void* guac_common_cursor_broadcast_state(guac_user* user,
} }
void guac_common_cursor_update(guac_common_cursor* cursor, guac_user* user, void guac_common_cursor_move(guac_common_cursor* cursor, guac_user* user,
int x, int y, int button_mask) { int x, int y) {
/* Update current user of cursor */ /* Update current user of cursor */
cursor->user = user; cursor->user = user;
/* Update cursor state */ /* Update cursor position */
cursor->x = x; cursor->x = x;
cursor->y = y; cursor->y = y;
cursor->button_mask = button_mask;
/* Store time at which cursor was updated */ /* Notify all other users of change in cursor position */
cursor->timestamp = guac_timestamp_current();
/* Notify all other users of change in cursor state */
guac_client_foreach_user(cursor->client, guac_client_foreach_user(cursor->client,
guac_common_cursor_broadcast_state, cursor); guac_common_cursor_broadcast_position, cursor);
} }

View File

@ -99,22 +99,6 @@ static void guac_common_display_free_layers(guac_common_display_layer* layers,
} }
/**
* Allocates a display and a cursor which are used to represent the remote
* display and cursor.
*
* @param client
* The client owning the cursor.
*
* @param width
* The desired width of the display.
*
* @param height
* The desired height of the display.
*
* @return
* The newly-allocated display or NULL if display cannot be allocated.
*/
guac_common_display* guac_common_display_alloc(guac_client* client, guac_common_display* guac_common_display_alloc(guac_client* client,
int width, int height) { int width, int height) {
@ -123,18 +107,14 @@ guac_common_display* guac_common_display_alloc(guac_client* client,
if (display == NULL) if (display == NULL)
return NULL; return NULL;
/* Allocate shared cursor */
display->cursor = guac_common_cursor_alloc(client);
if (display->cursor == NULL) {
free(display);
return NULL;
}
pthread_mutex_init(&display->_lock, NULL); pthread_mutex_init(&display->_lock, NULL);
/* Associate display with given client */ /* Associate display with given client */
display->client = client; display->client = client;
/* Allocate shared cursor */
display->cursor = guac_common_cursor_alloc(client);
display->default_surface = guac_common_surface_alloc(client, display->default_surface = guac_common_surface_alloc(client,
client->socket, GUAC_DEFAULT_LAYER, width, height); client->socket, GUAC_DEFAULT_LAYER, width, height);
@ -166,8 +146,6 @@ void guac_common_display_free(guac_common_display* display) {
void guac_common_display_dup(guac_common_display* display, guac_user* user, void guac_common_display_dup(guac_common_display* display, guac_user* user,
guac_socket* socket) { guac_socket* socket) {
guac_client* client = user->client;
pthread_mutex_lock(&display->_lock); pthread_mutex_lock(&display->_lock);
/* Sunchronize shared cursor */ /* Sunchronize shared cursor */
@ -180,33 +158,6 @@ void guac_common_display_dup(guac_common_display* display, guac_user* user,
guac_common_display_dup_layers(display->layers, user, socket); guac_common_display_dup_layers(display->layers, user, socket);
guac_common_display_dup_layers(display->buffers, user, socket); guac_common_display_dup_layers(display->buffers, user, socket);
/* Sends a sync instruction to mark the boundary of the first frame */
guac_protocol_send_sync(socket, client->last_sent_timestamp, 1);
pthread_mutex_unlock(&display->_lock);
}
void guac_common_display_set_lossless(guac_common_display* display,
int lossless) {
pthread_mutex_lock(&display->_lock);
/* Update lossless setting to be applied to all newly-allocated
* layers/buffers */
display->lossless = lossless;
/* Update losslessness of all allocated layers/buffers */
guac_common_display_layer* current = display->layers;
while (current != NULL) {
guac_common_surface_set_lossless(current->surface, lossless);
current = current->next;
}
/* Update losslessness of default display layer (not included within layers
* list) */
guac_common_surface_set_lossless(display->default_surface, lossless);
pthread_mutex_unlock(&display->_lock); pthread_mutex_unlock(&display->_lock);
} }
@ -316,9 +267,6 @@ guac_common_display_layer* guac_common_display_alloc_layer(
guac_common_surface* surface = guac_common_surface_alloc(display->client, guac_common_surface* surface = guac_common_surface_alloc(display->client,
display->client->socket, layer, width, height); display->client->socket, layer, width, height);
/* Apply current display losslessness */
guac_common_surface_set_lossless(surface, display->lossless);
/* Add layer and surface to list */ /* Add layer and surface to list */
guac_common_display_layer* display_layer = guac_common_display_layer* display_layer =
guac_common_display_add_layer(&display->layers, layer, surface); guac_common_display_add_layer(&display->layers, layer, surface);
@ -340,9 +288,6 @@ guac_common_display_layer* guac_common_display_alloc_buffer(
guac_common_surface* surface = guac_common_surface_alloc(display->client, guac_common_surface* surface = guac_common_surface_alloc(display->client,
display->client->socket, buffer, width, height); display->client->socket, buffer, width, height);
/* Apply current display losslessness */
guac_common_surface_set_lossless(surface, display->lossless);
/* Add buffer and surface to list */ /* Add buffer and surface to list */
guac_common_display_layer* display_layer = guac_common_display_layer* display_layer =
guac_common_display_add_layer(&display->buffers, buffer, surface); guac_common_display_add_layer(&display->buffers, buffer, surface);
@ -389,3 +334,4 @@ void guac_common_display_free_buffer(guac_common_display* display,
pthread_mutex_unlock(&display->_lock); pthread_mutex_unlock(&display->_lock);
} }

View File

@ -138,70 +138,6 @@ int GUAC_READ_ISO8859_1(const char** input, int remaining) {
} }
/**
* Invokes the given reader function, automatically normalizing newline
* sequences as Unix-style newline characters ('\n'). All other charaters are
* read verbatim.
*
* @param reader
* The reader to use to read the given character.
*
* @param input
* Pointer to the location within the input buffer that the next character
* should be read from.
*
* @param remaining
* The number of bytes remaining in the input buffer.
*
* @return
* The codepoint that was read, or zero if the end of the input string has
* been reached.
*/
static int guac_iconv_read_normalized(guac_iconv_read* reader,
const char** input, int remaining) {
/* Read requested character */
const char* input_start = *input;
int value = reader(input, remaining);
/* Automatically translate CRLF pairs to simple newlines */
if (value == '\r') {
/* Peek ahead by one character, adjusting remaining bytes relative to
* last read */
int peek_remaining = remaining - (*input - input_start);
const char* peek_input = *input;
int peek_value = reader(&peek_input, peek_remaining);
/* Consider read value to be a newline if we have encountered a "\r\n"
* (CRLF) pair */
if (peek_value == '\n') {
value = '\n';
*input = peek_input;
}
}
return value;
}
int GUAC_READ_UTF8_NORMALIZED(const char** input, int remaining) {
return guac_iconv_read_normalized(GUAC_READ_UTF8, input, remaining);
}
int GUAC_READ_UTF16_NORMALIZED(const char** input, int remaining) {
return guac_iconv_read_normalized(GUAC_READ_UTF16, input, remaining);
}
int GUAC_READ_CP1252_NORMALIZED(const char** input, int remaining) {
return guac_iconv_read_normalized(GUAC_READ_CP1252, input, remaining);
}
int GUAC_READ_ISO8859_1_NORMALIZED(const char** input, int remaining) {
return guac_iconv_read_normalized(GUAC_READ_ISO8859_1, input, remaining);
}
void GUAC_WRITE_UTF8(char** output, int remaining, int value) { void GUAC_WRITE_UTF8(char** output, int remaining, int value) {
*output += guac_utf8_write(value, *output, remaining); *output += guac_utf8_write(value, *output, remaining);
} }
@ -254,53 +190,3 @@ void GUAC_WRITE_ISO8859_1(char** output, int remaining, int value) {
(*output)++; (*output)++;
} }
/**
* Invokes the given writer function, automatically writing newline characters
* ('\n') as CRLF ("\r\n"). All other charaters are written verbatim.
*
* @param writer
* The writer to use to write the given character.
*
* @param output
* Pointer to the location within the output buffer that the next character
* should be written.
*
* @param remaining
* The number of bytes remaining in the output buffer.
*
* @param value
* The codepoint of the character to write.
*/
static void guac_iconv_write_crlf(guac_iconv_write* writer, char** output,
int remaining, int value) {
if (value != '\n') {
writer(output, remaining, value);
return;
}
char* output_start = *output;
writer(output, remaining, '\r');
remaining -= *output - output_start;
if (remaining > 0)
writer(output, remaining, '\n');
}
void GUAC_WRITE_UTF8_CRLF(char** output, int remaining, int value) {
guac_iconv_write_crlf(GUAC_WRITE_UTF8, output, remaining, value);
}
void GUAC_WRITE_UTF16_CRLF(char** output, int remaining, int value) {
guac_iconv_write_crlf(GUAC_WRITE_UTF16, output, remaining, value);
}
void GUAC_WRITE_CP1252_CRLF(char** output, int remaining, int value) {
guac_iconv_write_crlf(GUAC_WRITE_CP1252, output, remaining, value);
}
void GUAC_WRITE_ISO8859_1_CRLF(char** output, int remaining, int value) {
guac_iconv_write_crlf(GUAC_WRITE_ISO8859_1, output, remaining, value);
}

View File

@ -97,15 +97,15 @@ int guac_common_json_write_string(guac_user* user,
const char* current = str; const char* current = str;
for (; *current != '\0'; current++) { for (; *current != '\0'; current++) {
/* Escape all quotes and back-slashes */ /* Escape all quotes */
if (*current == '"' || *current == '\\') { if (*current == '"') {
/* Write any string content up to current character */ /* Write any string content up to current character */
if (current != str) if (current != str)
blob_written |= guac_common_json_write(user, stream, blob_written |= guac_common_json_write(user, stream,
json_state, str, current - str); json_state, str, current - str);
/* Escape the character that was just read */ /* Escape the quote that was just read */
blob_written |= guac_common_json_write(user, stream, blob_written |= guac_common_json_write(user, stream,
json_state, "\\", 1); json_state, "\\", 1);

View File

@ -17,11 +17,10 @@
* under the License. * under the License.
*/ */
#include "guacamole/client.h" #include "common/recording.h"
#include "guacamole/protocol.h"
#include "guacamole/recording.h" #include <guacamole/client.h>
#include "guacamole/socket.h" #include <guacamole/socket.h>
#include "guacamole/timestamp.h"
#ifdef __MINGW32__ #ifdef __MINGW32__
#include <direct.h> #include <direct.h>
@ -63,7 +62,7 @@
* The file descriptor of the open data file if open succeeded, or -1 on * The file descriptor of the open data file if open succeeded, or -1 on
* failure. * failure.
*/ */
static int guac_recording_open(const char* path, static int guac_common_recording_open(const char* path,
const char* name, char* basename, int basename_size) { const char* name, char* basename, int basename_size) {
int i; int i;
@ -83,7 +82,7 @@ static int guac_recording_open(const char* path,
/* Attempt to open recording */ /* Attempt to open recording */
int fd = open(basename, int fd = open(basename,
O_CREAT | O_EXCL | O_WRONLY, O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP); S_IRUSR | S_IWUSR);
/* Continuously retry with alternate names on failure */ /* Continuously retry with alternate names on failure */
if (fd == -1) { if (fd == -1) {
@ -102,7 +101,7 @@ static int guac_recording_open(const char* path,
/* Retry with newly-suffixed filename */ /* Retry with newly-suffixed filename */
fd = open(basename, fd = open(basename,
O_CREAT | O_EXCL | O_WRONLY, O_CREAT | O_EXCL | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP); S_IRUSR | S_IWUSR);
} }
@ -134,95 +133,39 @@ static int guac_recording_open(const char* path,
} }
guac_recording* guac_recording_create(guac_client* client, int guac_common_recording_create(guac_client* client, const char* path,
const char* path, const char* name, int create_path, const char* name, int create_path) {
int include_output, int include_mouse, int include_touch,
int include_keys) {
char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH];
/* Create path if it does not exist, fail if impossible */ /* Create path if it does not exist, fail if impossible */
#ifndef __MINGW32__ #ifndef __MINGW32__
if (create_path && mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP) if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) {
&& errno != EEXIST) {
#else #else
if (create_path && _mkdir(path) && errno != EEXIST) { if (create_path && _mkdir(path) && errno != EEXIST) {
#endif #endif
guac_client_log(client, GUAC_LOG_ERROR, guac_client_log(client, GUAC_LOG_ERROR,
"Creation of recording failed: %s", strerror(errno)); "Creation of recording failed: %s", strerror(errno));
return NULL; return 1;
} }
/* Attempt to open recording file */ /* Attempt to open recording file */
int fd = guac_recording_open(path, name, filename, sizeof(filename)); int fd = guac_common_recording_open(path, name, filename, sizeof(filename));
if (fd == -1) { if (fd == -1) {
guac_client_log(client, GUAC_LOG_ERROR, guac_client_log(client, GUAC_LOG_ERROR,
"Creation of recording failed: %s", strerror(errno)); "Creation of recording failed: %s", strerror(errno));
return NULL; return 1;
} }
/* Create recording structure with reference to underlying socket */ /* Replace client socket with wrapped socket */
guac_recording* recording = malloc(sizeof(guac_recording)); client->socket = guac_socket_tee(client->socket, guac_socket_open(fd));
recording->socket = guac_socket_open(fd);
recording->include_output = include_output;
recording->include_mouse = include_mouse;
recording->include_touch = include_touch;
recording->include_keys = include_keys;
/* Replace client socket with wrapped recording socket only if including
* output within the recording */
if (include_output)
client->socket = guac_socket_tee(client->socket, recording->socket);
/* Recording creation succeeded */ /* Recording creation succeeded */
guac_client_log(client, GUAC_LOG_INFO, guac_client_log(client, GUAC_LOG_INFO,
"Recording of session will be saved to \"%s\".", "Recording of session will be saved to \"%s\".",
filename); filename);
return recording; return 0;
}
void guac_recording_free(guac_recording* recording) {
/* If not including broadcast output, the output socket is not associated
* with the client, and must be freed manually */
if (!recording->include_output)
guac_socket_free(recording->socket);
/* Free recording itself */
free(recording);
}
void guac_recording_report_mouse(guac_recording* recording,
int x, int y, int button_mask) {
/* Report mouse location only if recording should contain mouse events */
if (recording->include_mouse)
guac_protocol_send_mouse(recording->socket, x, y, button_mask,
guac_timestamp_current());
}
void guac_recording_report_touch(guac_recording* recording,
int id, int x, int y, int x_radius, int y_radius,
double angle, double force) {
/* Report touches only if recording should contain touch events */
if (recording->include_touch)
guac_protocol_send_touch(recording->socket, id, x, y,
x_radius, y_radius, angle, force, guac_timestamp_current());
}
void guac_recording_report_key(guac_recording* recording,
int keysym, int pressed) {
/* Report key state only if recording should contain key events */
if (recording->include_keys)
guac_protocol_send_key(recording->socket, keysym, pressed,
guac_timestamp_current());
} }

View File

@ -78,6 +78,13 @@
#define cairo_format_stride_for_width(format, width) (width*4) #define cairo_format_stride_for_width(format, width) (width*4)
#endif #endif
/**
* The JPEG image quality ('quantization') setting to use. Range 0-100 where
* 100 is the highest quality/largest file size, and 0 is the lowest
* quality/smallest file size.
*/
#define GUAC_SURFACE_JPEG_IMAGE_QUALITY 90
/** /**
* The framerate which, if exceeded, indicates that JPEG is preferred. * The framerate which, if exceeded, indicates that JPEG is preferred.
*/ */
@ -89,6 +96,13 @@
*/ */
#define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096 #define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096
/**
* The WebP image quality ('quantization') setting to use. Range 0-100 where
* 100 is the highest quality/largest file size, and 0 is the lowest
* quality/smallest file size.
*/
#define GUAC_SURFACE_WEBP_IMAGE_QUALITY 90
/** /**
* The JPEG compression min block size. This defines the optimal rectangle block * The JPEG compression min block size. This defines the optimal rectangle block
* size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to * size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to
@ -103,28 +117,6 @@
*/ */
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 8 #define GUAC_SURFACE_WEBP_BLOCK_SIZE 8
void guac_common_surface_set_multitouch(guac_common_surface* surface,
int touches) {
pthread_mutex_lock(&surface->_lock);
surface->touches = touches;
guac_protocol_send_set_int(surface->socket, surface->layer,
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH, touches);
pthread_mutex_unlock(&surface->_lock);
}
void guac_common_surface_set_lossless(guac_common_surface* surface,
int lossless) {
pthread_mutex_lock(&surface->_lock);
surface->lossless = lossless;
pthread_mutex_unlock(&surface->_lock);
}
void guac_common_surface_move(guac_common_surface* surface, int x, int y) { void guac_common_surface_move(guac_common_surface* surface, int x, int y) {
pthread_mutex_lock(&surface->_lock); pthread_mutex_lock(&surface->_lock);
@ -282,31 +274,18 @@ static int __guac_common_surface_is_opaque(guac_common_surface* surface,
/** /**
* Returns whether the given rectangle should be combined into the existing * Returns whether the given rectangle should be combined into the existing
* dirty rectangle, to be eventually flushed as image data, or would be best * dirty rectangle, to be eventually flushed as a "png" instruction.
* kept independent of the current rectangle.
* *
* @param surface * @param surface The surface to be queried.
* The surface being updated. * @param rect The update rectangle.
* * @param rect_only Non-zero if this update, by its nature, contains only
* @param rect * metainformation about the update's rectangle, zero if
* The bounding rectangle of the update being made to the surface. * the update also contains image data.
* * @return Non-zero if the update should be combined with any existing update,
* @param rect_only * zero otherwise.
* Non-zero if this update, by its nature, contains only metainformation
* about the update's bounding rectangle, zero if the update also contains
* image data.
*
* @return
* Non-zero if the update should be combined with any existing update, zero
* otherwise.
*/ */
static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) { static int __guac_common_should_combine(guac_common_surface* surface, const guac_common_rect* rect, int rect_only) {
/* Always favor combining updates if surface is currently a purely
* server-side scratch area */
if (!surface->realized)
return 1;
if (surface->dirty) { if (surface->dirty) {
int combined_cost, dirty_cost, update_cost; int combined_cost, dirty_cost, update_cost;
@ -543,10 +522,6 @@ static int __guac_common_surface_png_optimality(guac_common_surface* surface,
static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface, static int __guac_common_surface_should_use_jpeg(guac_common_surface* surface,
const guac_common_rect* rect) { const guac_common_rect* rect) {
/* Do not use JPEG if lossless quality is required */
if (surface->lossless)
return 0;
/* Calculate the average framerate for the given rect */ /* Calculate the average framerate for the given rect */
int framerate = __guac_common_surface_calculate_framerate(surface, rect); int framerate = __guac_common_surface_calculate_framerate(surface, rect);
@ -1691,36 +1666,6 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface,
} }
/**
* Returns an appropriate quality between 0 and 100 for lossy encoding
* depending on the current processing lag calculated for the given client.
*
* @param client
* The client for which the lossy quality is being calculated.
*
* @return
* A value between 0 and 100 inclusive which seems appropriate for the
* client based on lag measurements.
*/
static int guac_common_surface_suggest_quality(guac_client* client) {
int lag = guac_client_get_processing_lag(client);
/* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */
int quality = 90 - (lag - 20);
/* Do not exceed 90 for quality */
if (quality > 90)
return 90;
/* Do not go below 30 for quality */
if (quality < 30)
return 30;
return quality;
}
/** /**
* Flushes the bitmap update currently described by the dirty rectangle within * Flushes the bitmap update currently described by the dirty rectangle within
* the given surface directly via an "img" instruction as JPEG data. The * the given surface directly via an "img" instruction as JPEG data. The
@ -1757,7 +1702,7 @@ static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) {
/* Send JPEG for rect */ /* Send JPEG for rect */
guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer, guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer,
surface->dirty_rect.x, surface->dirty_rect.y, rect, surface->dirty_rect.x, surface->dirty_rect.y, rect,
guac_common_surface_suggest_quality(surface->client)); GUAC_SURFACE_JPEG_IMAGE_QUALITY);
cairo_surface_destroy(rect); cairo_surface_destroy(rect);
surface->realized = 1; surface->realized = 1;
@ -1819,8 +1764,7 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface,
/* Send WebP for rect */ /* Send WebP for rect */
guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer, guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer,
surface->dirty_rect.x, surface->dirty_rect.y, rect, surface->dirty_rect.x, surface->dirty_rect.y, rect,
guac_common_surface_suggest_quality(surface->client), GUAC_SURFACE_WEBP_IMAGE_QUALITY, 0);
surface->lossless ? 1 : 0);
cairo_surface_destroy(rect); cairo_surface_destroy(rect);
surface->realized = 1; surface->realized = 1;
@ -2008,11 +1952,6 @@ void guac_common_surface_dup(guac_common_surface* surface, guac_user* user,
guac_protocol_send_move(socket, surface->layer, guac_protocol_send_move(socket, surface->layer,
surface->parent, surface->x, surface->y, surface->z); surface->parent, surface->x, surface->y, surface->z);
/* Synchronize multi-touch support level */
guac_protocol_send_set_int(surface->socket, surface->layer,
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH,
surface->touches);
} }
/* Sync size to new socket */ /* Sync size to new socket */

View File

@ -1,76 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
#
# Unit tests for libguac_common
#
check_PROGRAMS = test_common
TESTS = $(check_PROGRAMS)
noinst_HEADERS = \
iconv/convert-test-data.h
test_common_SOURCES = \
iconv/convert.c \
iconv/convert-test-data.c \
rect/clip_and_split.c \
rect/constrain.c \
rect/expand_to_grid.c \
rect/extend.c \
rect/init.c \
rect/intersects.c \
string/count_occurrences.c \
string/split.c
test_common_CFLAGS = \
-Werror -Wall -pedantic \
@COMMON_INCLUDE@
test_common_LDADD = \
@COMMON_LTLIB@ \
@CUNIT_LIBS@
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_common_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_common_SOURCES) > $@
nodist_test_common_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh

View File

@ -1,153 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/iconv.h"
#include "convert-test-data.h"
encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS] = {
/*
* UTF-8
*/
{
"UTF-8",
GUAC_READ_UTF8, GUAC_READ_UTF8_NORMALIZED,
GUAC_WRITE_UTF8, GUAC_WRITE_UTF8_CRLF,
.test_mixed = TEST_STRING(
"pap\xC3\xA0 \xC3\xA8 bello\n"
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
"pap\xC3\xA0 \xC3\xA8 bello\n"
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
"pap\xC3\xA0 \xC3\xA8 bello"
),
.test_unix = TEST_STRING(
"pap\xC3\xA0 \xC3\xA8 bello\n"
"pap\xC3\xA0 \xC3\xA8 bello\n"
"pap\xC3\xA0 \xC3\xA8 bello\n"
"pap\xC3\xA0 \xC3\xA8 bello\n"
"pap\xC3\xA0 \xC3\xA8 bello"
),
.test_windows = TEST_STRING(
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
"pap\xC3\xA0 \xC3\xA8 bello\r\n"
"pap\xC3\xA0 \xC3\xA8 bello"
)
},
/*
* UTF-16
*/
{
"UTF-16",
GUAC_READ_UTF16, GUAC_READ_UTF16_NORMALIZED,
GUAC_WRITE_UTF16, GUAC_WRITE_UTF16_CRLF,
.test_mixed = TEST_STRING(
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
"\x00"
),
.test_unix = TEST_STRING(
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
"\x00"
),
.test_windows = TEST_STRING(
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
"p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
"\x00"
)
},
/*
* ISO 8859-1
*/
{
"ISO 8859-1",
GUAC_READ_ISO8859_1, GUAC_READ_ISO8859_1_NORMALIZED,
GUAC_WRITE_ISO8859_1, GUAC_WRITE_ISO8859_1_CRLF,
.test_mixed = TEST_STRING(
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello"
),
.test_unix = TEST_STRING(
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello"
),
.test_windows = TEST_STRING(
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello"
)
},
/*
* CP-1252
*/
{
"CP-1252",
GUAC_READ_CP1252, GUAC_READ_CP1252_NORMALIZED,
GUAC_WRITE_CP1252, GUAC_WRITE_CP1252_CRLF,
.test_mixed = TEST_STRING(
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello"
),
.test_unix = TEST_STRING(
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello\n"
"pap\xE0 \xE8 bello"
),
.test_windows = TEST_STRING(
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello\r\n"
"pap\xE0 \xE8 bello"
)
}
};

View File

@ -1,121 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/iconv.h"
/**
* Representation of test string data and its length in bytes.
*/
typedef struct test_string {
/**
* The raw content of the test string.
*/
unsigned char* buffer;
/**
* The number of bytes within the test string, including null terminator.
*/
int size;
} test_string;
/**
* Convenience macro which statically-initializes a test_string with the given
* string value, automatically calculating its size in bytes.
*
* @param value
* The string value.
*/
#define TEST_STRING(value) { \
.buffer = (unsigned char*) (value), \
.size = sizeof(value) \
}
/**
* The parameters applicable to a unit test for a particular encoding supported
* by guac_iconv().
*/
typedef struct encoding_test_parameters {
/**
* The human-readable name of this encoding. This will be logged to the
* test suite log to assist with debugging test failures.
*/
const char* name;
/**
* Reader function which reads using this encoding and does not perform any
* transformation on newline characters.
*/
guac_iconv_read* reader;
/**
* Reader function which reads using this encoding and automatically
* normalizes newline sequences to Unix-style newline characters.
*/
guac_iconv_read* reader_normalized;
/**
* Writer function which writes using this encoding and does not perform
* any transformation on newline characters.
*/
guac_iconv_write* writer;
/**
* Writer function which writes using this encoding, but writes newline
* characters as CRLF sequences.
*/
guac_iconv_write* writer_crlf;
/**
* A test string having both Windows- and Unix-style line endings. Except
* for the line endings, the characters represented within this test string
* must be identical to all other test strings.
*/
test_string test_mixed;
/**
* A test string having only Unix-style line endings. Except for the line
* endings, the characters represented within this test string must be
* identical to all other test strings.
*/
test_string test_unix;
/**
* A test string having only Windows-style line endings. Except for the
* line endings, the characters represented within this test string must be
* identical to all other test strings.
*/
test_string test_windows;
} encoding_test_parameters;
/**
* The total number of encodings supported by guac_iconv().
*/
#define NUM_SUPPORTED_ENCODINGS 4
/**
* Test parameters for each supported encoding. The test strings included each
* consist of five repeated lines of "papà è bello", omitting the line ending
* of the final line.
*/
extern encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS];

View File

@ -1,129 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/iconv.h"
#include "convert-test-data.h"
#include <CUnit/CUnit.h>
#include <stdio.h>
/**
* Tests that conversion between character sets using the given guac_iconv_read
* and guac_iconv_write implementations matches expectations.
*
* @param reader
* The guac_iconv_read implementation to use to read the input string.
*
* @param in_string
* A pointer to the test_string structure describing the input string being
* tested.
*
* @param writer
* The guac_iconv_write implementation to use to write the output string
* (the converted input string).
*
* @param out_string
* A pointer to the test_string structure describing the expected result of
* the conversion.
*/
static void verify_conversion(
guac_iconv_read* reader, test_string* in_string,
guac_iconv_write* writer, test_string* out_string) {
char output[4096];
char input[4096];
const char* current_input = input;
char* current_output = output;
memcpy(input, in_string->buffer, in_string->size);
guac_iconv(reader, &current_input, sizeof(input),
writer, &current_output, sizeof(output));
/* Verify output length */
CU_ASSERT_EQUAL(out_string->size, current_output - output);
/* Verify entire input read */
CU_ASSERT_EQUAL(in_string->size, current_input - input);
/* Verify output content */
CU_ASSERT_EQUAL(0, memcmp(output, out_string->buffer, out_string->size));
}
/**
* Test which verifies that every supported encoding can be correctly converted
* to every other supported encoding, with all line endings preserved verbatim
* (not normalized).
*/
void test_iconv__preserve() {
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
encoding_test_parameters* from = &test_params[i];
encoding_test_parameters* to = &test_params[j];
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
verify_conversion(from->reader, &from->test_mixed,
to->writer, &to->test_mixed);
}
}
}
/**
* Test which verifies that every supported encoding can be correctly converted
* to every other supported encoding, normalizing all line endings to
* Unix-style line endings.
*/
void test_iconv__normalize_unix() {
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
encoding_test_parameters* from = &test_params[i];
encoding_test_parameters* to = &test_params[j];
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
verify_conversion(from->reader_normalized, &from->test_mixed,
to->writer, &to->test_unix);
}
}
}
/**
* Test which verifies that every supported encoding can be correctly converted
* to every other supported encoding, normalizing all line endings to
* Windows-style line endings.
*/
void test_iconv__normalize_crlf() {
for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
encoding_test_parameters* from = &test_params[i];
encoding_test_parameters* to = &test_params[j];
printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
verify_conversion(from->reader_normalized, &from->test_mixed,
to->writer_crlf, &to->test_windows);
}
}
}

View File

@ -1,156 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_common_rect_clip_and_split() divides a
* rectangle into subrectangles after removing a "hole" rectangle.
*/
void test_rect__clip_and_split() {
int res;
guac_common_rect cut;
guac_common_rect min;
guac_common_rect rect;
guac_common_rect_init(&min, 10, 10, 10, 10);
/* Clip top */
guac_common_rect_init(&rect, 10, 5, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(5, cut.y);
CU_ASSERT_EQUAL(10, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(5, rect.height);
/* Clip bottom */
guac_common_rect_init(&rect, 10, 15, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(20, cut.y);
CU_ASSERT_EQUAL(10, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(15, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(5, rect.height);
/* Clip left */
guac_common_rect_init(&rect, 5, 10, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(5, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Clip right */
guac_common_rect_init(&rect, 15, 10, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(20, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(15, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(5, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/*
* Test a rectangle which completely covers the hole.
* Clip and split until done.
*/
guac_common_rect_init(&rect, 5, 5, 20, 20);
/* Clip top */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(5, cut.y);
CU_ASSERT_EQUAL(20, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(5, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(20, rect.width);
CU_ASSERT_EQUAL(15, rect.height);
/* Clip left */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(15, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(15, rect.width);
CU_ASSERT_EQUAL(15, rect.height);
/* Clip bottom */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(20, cut.y);
CU_ASSERT_EQUAL(15, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(15, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Clip right */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(20, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Make sure nothing is left to do */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(0, res);
}

View File

@ -1,43 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_common_rect_constrain() restricts a given
* rectangle to arbitrary bounds.
*/
void test_rect__constrain() {
guac_common_rect max;
guac_common_rect rect;
guac_common_rect_init(&rect, -10, -10, 110, 110);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_constrain(&rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(100, rect.width);
CU_ASSERT_EQUAL(100, rect.height);
}

View File

@ -1,71 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies guac_common_rect_expand_to_grid() properly shifts and
* resizes rectangles to fit an NxN grid.
*/
void test_rect__expand_to_grid() {
int cell_size = 16;
guac_common_rect max;
guac_common_rect rect;
/* Simple adjustment */
guac_common_rect_init(&rect, 0, 0, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
/* Adjustment with moving of rect */
guac_common_rect_init(&rect, 75, 75, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(max.width - 32, rect.x);
CU_ASSERT_EQUAL(max.height - 32, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
guac_common_rect_init(&rect, -5, -5, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
/* Adjustment with moving and clamping of rect */
guac_common_rect_init(&rect, 0, 0, 25, 15);
guac_common_rect_init(&max, 0, 5, 32, 15);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(max.x, rect.x);
CU_ASSERT_EQUAL(max.y, rect.y);
CU_ASSERT_EQUAL(max.width, rect.width);
CU_ASSERT_EQUAL(max.height, rect.height);
}

View File

@ -1,42 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_common_rect_extend() expands the given
* rectangle as necessary to contain at least the given bounds.
*/
void test_rect__extend() {
guac_common_rect max;
guac_common_rect rect;
guac_common_rect_init(&rect, 10, 10, 90, 90);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_extend(&rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(100, rect.width);
CU_ASSERT_EQUAL(100, rect.height);
}

View File

@ -1,91 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies intersection testing via guac_common_rect_intersects().
*/
void test_rect__intersects() {
int res;
guac_common_rect min;
guac_common_rect rect;
guac_common_rect_init(&min, 10, 10, 10, 10);
/* Rectangle intersection - empty
* rectangle is outside */
guac_common_rect_init(&rect, 25, 25, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(0, res);
/* Rectangle intersection - complete
* rectangle is completely inside */
guac_common_rect_init(&rect, 11, 11, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects UL */
guac_common_rect_init(&rect, 8, 8, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - partial
* rectangle intersects LR */
guac_common_rect_init(&rect, 18, 18, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - complete
* rect intersects along UL but inside */
guac_common_rect_init(&rect, 10, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects along L but outside */
guac_common_rect_init(&rect, 5, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - complete
* rectangle intersects along LR but rest is inside */
guac_common_rect_init(&rect, 15, 15, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects along R but rest is outside */
guac_common_rect_init(&rect, 20, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - partial
* rectangle encloses min; which is a partial intersection */
guac_common_rect_init(&rect, 5, 5, 20, 20);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
}

View File

@ -1,33 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "common/string.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_count_occurrences() counts the number of
* occurrences of an arbitrary character within a given string.
*/
void test_string__guac_count_occurrences() {
CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's'));
CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i'));
CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's'));
}

View File

@ -1,11 +1,11 @@
What is guacd? What is guacd?
============== ==============
[guacd](https://github.com/apache/guacamole-server/) is the native [guacd](https://github.com/apache/incubator/guacamole-server/) is the native
server-side proxy used by the [Apache Guacamole web server-side proxy used by the [Apache Guacamole web
application](http://guacamole.apache.org/). If you wish to deploy application](http://guacamole.incubator.apache.org/). If you wish to deploy
Guacamole, or an application using the [Guacamole core Guacamole, or an application using the [Guacamole core
APIs](http://guacamole.apache.org/api-documentation), you will need a APIs](http://guacamole.incubator.apache.org/api-documentation), you will need a
copy of guacd running. copy of guacd running.
How to use this image How to use this image

View File

@ -1,115 +0,0 @@
#!/bin/sh -e
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
##
## @fn build-all.sh
##
## Builds the source of guacamole-server and its various core protocol library
## dependencies.
##
# Pre-populate build control variables such that the custom build prefix is
# used for C headers, locating libraries, etc.
export CFLAGS="-I${PREFIX_DIR}/include"
export LDFLAGS="-L${PREFIX_DIR}/lib"
export PKG_CONFIG_PATH="${PREFIX_DIR}/lib/pkgconfig"
# Ensure thread stack size will be 8 MB (glibc's default on Linux) rather than
# 128 KB (musl's default)
export LDFLAGS="$LDFLAGS -Wl,-z,stack-size=8388608"
##
## Builds and installs the source at the given git repository, automatically
## switching to the version of the source at the tag/commit that matches the
## given pattern.
##
## @param URL
## The URL of the git repository that the source should be downloaded from.
##
## @param PATTERN
## The Perl-compatible regular expression that the tag must match. If no
## tag matches the regular expression, the pattern is assumed to be an
## exact reference to a commit, branch, etc. acceptable by git checkout.
##
## @param ...
## Any additional command-line options that should be provided to CMake or
## the configure script.
##
install_from_git() {
URL="$1"
PATTERN="$2"
shift 2
# Calculate top-level directory name of resulting repository from the
# provided URL
REPO_DIR="$(basename "$URL" .git)"
# Allow dependencies to be manually omitted with the tag/commit pattern "NO"
if [ "$PATTERN" = "NO" ]; then
echo "NOT building $REPO_DIR (explicitly skipped)"
return
fi
# Clone repository and change to top-level directory of source
cd /tmp
git clone "$URL"
cd $REPO_DIR/
# Locate tag/commit based on provided pattern
VERSION="$(git tag -l --sort=-v:refname | grep -Px -m1 "$PATTERN" \
|| echo "$PATTERN")"
# Switch to desired version of source
echo "Building $REPO_DIR @ $VERSION ..."
git -c advice.detachedHead=false checkout "$VERSION"
# Configure build using CMake or GNU Autotools, whichever happens to be
# used by the library being built
if [ -e CMakeLists.txt ]; then
cmake -DCMAKE_INSTALL_PREFIX:PATH="$PREFIX_DIR" "$@" .
else
[ -e configure ] || autoreconf -fi
./configure --prefix="$PREFIX_DIR" "$@"
fi
# Build and install
make && make install
}
#
# Build and install core protocol library dependencies
#
install_from_git "https://github.com/FreeRDP/FreeRDP" "$WITH_FREERDP" $FREERDP_OPTS
install_from_git "https://github.com/libssh2/libssh2" "$WITH_LIBSSH2" $LIBSSH2_OPTS
install_from_git "https://github.com/seanmiddleditch/libtelnet" "$WITH_LIBTELNET" $LIBTELNET_OPTS
install_from_git "https://github.com/LibVNC/libvncserver" "$WITH_LIBVNCCLIENT" $LIBVNCCLIENT_OPTS
install_from_git "https://libwebsockets.org/repo/libwebsockets" "$WITH_LIBWEBSOCKETS" $LIBWEBSOCKETS_OPTS
#
# Build guacamole-server
#
cd "$BUILD_DIR"
autoreconf -fi && ./configure --prefix="$PREFIX_DIR" $GUACAMOLE_SERVER_OPTS
make && make install

View File

@ -0,0 +1,62 @@
#!/bin/sh -e
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
##
## @fn build-guacd.sh
##
## Builds the source of guacamole-server, automatically creating any required
## symbolic links for the proper loading of FreeRDP plugins.
##
## @param BUILD_DIR
## The directory which currently contains the guacamole-server source and
## in which the build should be performed.
##
BUILD_DIR="$1"
##
## Locates the directory in which the FreeRDP libraries (.so files) are
## located, printing the result to STDOUT.
##
where_is_freerdp() {
dirname `rpm -ql freerdp-libs | grep 'libfreerdp.*\.so' | head -n1`
}
#
# Build guacamole-server
#
cd "$BUILD_DIR"
autoreconf -fi
./configure
make
make install
ldconfig
#
# Add FreeRDP plugins to proper path
#
FREERDP_DIR=`where_is_freerdp`
FREERDP_PLUGIN_DIR="$FREERDP_DIR/freerdp"
mkdir -p "$FREERDP_PLUGIN_DIR"
ln -s /usr/local/lib/freerdp/*.so "$FREERDP_PLUGIN_DIR"

View File

@ -1,51 +0,0 @@
#!/bin/sh -e
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
##
## @fn list-dependencies.sh
##
## Lists the Alpine Linux package names for all library dependencies of the
## given binaries. Each package is only listed once, even if multiple binaries
## provided by the same package are given.
##
## @param ...
## The full paths to all binaries being checked.
##
while [ -n "$1" ]; do
# For all non-Guacamole library dependencies
ldd "$1" | grep -v 'libguac' | awk '/=>/{print $(NF-1)}' \
| while read LIBRARY; do
# List the package providing that library, if any
apk info -W "$LIBRARY" 2> /dev/null \
| grep 'is owned by' | grep -o '[^ ]*$' || true
done
# Next binary
shift
# Strip the "-VERSION" suffix from each package name, listing each resulting
# package uniquely ("apk add" cannot handle package names that include the
# version number)
done | sed 's/\(.*\)-[0-9]\+\..*$/\1/' | sort -u

40
src/guacd/.gitignore vendored
View File

@ -2,14 +2,42 @@
# Compiled init script # Compiled init script
init.d/guacd init.d/guacd
# Compiled systemd unit
systemd/guacd.service
# Compiled proxy # Compiled proxy
guacd guacd
guacd.exe guacd.exe
# Documentation (built from .in files) # Object code
man/guacd.8 *.o
man/guacd.conf.5 *.so
*.lo
*.la
# Backup files
*~
# Release files
*.tar.gz
# Files currently being edited by vim or vi
*.swp
# automake/autoconf
.deps/
.libs/
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
m4/
config.guess
config.log
config.status
config.sub
configure
depcomp
install-sh
libtool
ltmain.sh
missing

View File

@ -16,12 +16,6 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
# #
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
@ -32,7 +26,6 @@ man_MANS = \
man/guacd.conf.5 man/guacd.conf.5
noinst_HEADERS = \ noinst_HEADERS = \
conf.h \
conf-args.h \ conf-args.h \
conf-file.h \ conf-file.h \
conf-parse.h \ conf-parse.h \
@ -66,13 +59,12 @@ guacd_LDFLAGS = \
@PTHREAD_LIBS@ \ @PTHREAD_LIBS@ \
@SSL_LIBS@ @SSL_LIBS@
EXTRA_DIST = \ EXTRA_DIST = \
init.d/guacd.in \ init.d/guacd.in \
systemd/guacd.service.in \ man/guacd.8 \
man/guacd.8.in \ man/guacd.conf.5
man/guacd.conf.5.in
CLEANFILES = $(init_SCRIPTS) $(systemd_UNITS) CLEANFILES = $(init_SCRIPTS)
# Init script # Init script
if ENABLE_INIT if ENABLE_INIT
@ -84,12 +76,3 @@ init.d/guacd: init.d/guacd.in
chmod +x init.d/guacd chmod +x init.d/guacd
endif endif
# Systemd service
if ENABLE_SYSTEMD
systemddir = @systemd_dir@
systemd_DATA = systemd/guacd.service
systemd/guacd.service: systemd/guacd.service.in
sed -e 's,[@]sbindir[@],$(sbindir),g' < systemd/guacd.service.in > systemd/guacd.service
endif

View File

@ -19,8 +19,8 @@
#include "config.h" #include "config.h"
#include "conf.h"
#include "conf-args.h" #include "conf-args.h"
#include "conf-file.h"
#include "conf-parse.h" #include "conf-parse.h"
#include <getopt.h> #include <getopt.h>
@ -32,7 +32,7 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
/* Parse arguments */ /* Parse arguments */
int opt; int opt;
while ((opt = getopt(argc, argv, "l:b:p:L:C:K:fv")) != -1) { while ((opt = getopt(argc, argv, "l:b:p:L:C:K:f")) != -1) {
/* -l: Bind port */ /* -l: Bind port */
if (opt == 'l') { if (opt == 'l') {
@ -51,11 +51,6 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
config->foreground = 1; config->foreground = 1;
} }
/* -v: Print version and exit */
else if (opt == 'v') {
config->print_version = 1;
}
/* -p: PID file */ /* -p: PID file */
else if (opt == 'p') { else if (opt == 'p') {
free(config->pidfile); free(config->pidfile);
@ -110,8 +105,7 @@ int guacd_conf_parse_args(guacd_config* config, int argc, char** argv) {
" [-C CERTIFICATE_FILE]" " [-C CERTIFICATE_FILE]"
" [-K PEM_FILE]" " [-K PEM_FILE]"
#endif #endif
" [-f]" " [-f]\n", argv[0]);
" [-v]\n", argv[0]);
return 1; return 1;
} }

View File

@ -22,7 +22,7 @@
#include "config.h" #include "config.h"
#include "conf.h" #include "conf-file.h"
/** /**
* Parses the given arguments into the given configuration. Zero is returned on * Parses the given arguments into the given configuration. Zero is returned on

View File

@ -19,7 +19,6 @@
#include "config.h" #include "config.h"
#include "conf.h"
#include "conf-file.h" #include "conf-file.h"
#include "conf-parse.h" #include "conf-parse.h"
@ -176,11 +175,10 @@ guacd_config* guacd_conf_load() {
return NULL; return NULL;
/* Load defaults */ /* Load defaults */
conf->bind_host = strdup(GUACD_DEFAULT_BIND_HOST); conf->bind_host = NULL;
conf->bind_port = strdup(GUACD_DEFAULT_BIND_PORT); conf->bind_port = strdup("4822");
conf->pidfile = NULL; conf->pidfile = NULL;
conf->foreground = 0; conf->foreground = 0;
conf->print_version = 0;
conf->max_log_level = GUAC_LOG_INFO; conf->max_log_level = GUAC_LOG_INFO;
#ifdef ENABLE_SSL #ifdef ENABLE_SSL
@ -197,7 +195,6 @@ guacd_config* guacd_conf_load() {
if (retval != 0) { if (retval != 0) {
fprintf(stderr, "Unable to parse \"" GUACD_CONF_FILE "\".\n"); fprintf(stderr, "Unable to parse \"" GUACD_CONF_FILE "\".\n");
free(conf);
return NULL; return NULL;
} }
@ -206,7 +203,6 @@ guacd_config* guacd_conf_load() {
/* Notify of errors preventing reading */ /* Notify of errors preventing reading */
else if (errno != ENOENT) { else if (errno != ENOENT) {
fprintf(stderr, "Unable to open \"" GUACD_CONF_FILE "\": %s\n", strerror(errno)); fprintf(stderr, "Unable to open \"" GUACD_CONF_FILE "\": %s\n", strerror(errno));
free(conf);
return NULL; return NULL;
} }

View File

@ -22,7 +22,51 @@
#include "config.h" #include "config.h"
#include "conf.h" #include <guacamole/client.h>
/**
* The contents of a guacd configuration file.
*/
typedef struct guacd_config {
/**
* The host to bind on.
*/
char* bind_host;
/**
* The port to bind on.
*/
char* bind_port;
/**
* The file to write the PID in, if any.
*/
char* pidfile;
/**
* Whether guacd should run in the foreground.
*/
int foreground;
#ifdef ENABLE_SSL
/**
* SSL certificate file.
*/
char* cert_file;
/**
* SSL private key file.
*/
char* key_file;
#endif
/**
* The maximum log level to be logged by guacd.
*/
guac_client_log_level max_log_level;
} guacd_config;
/** /**
* Reads the given file descriptor, parsing its contents into the guacd_config. * Reads the given file descriptor, parsing its contents into the guacd_config.

View File

@ -19,7 +19,6 @@
#include "config.h" #include "config.h"
#include "conf.h"
#include "conf-parse.h" #include "conf-parse.h"
#include <guacamole/client.h> #include <guacamole/client.h>

View File

@ -1,89 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUACD_CONF_H
#define GUACD_CONF_H
#include "config.h"
#include <guacamole/client.h>
/**
* The default host that guacd should bind to, if no other host is explicitly
* specified.
*/
#define GUACD_DEFAULT_BIND_HOST "localhost"
/**
* The default port that guacd should bind to, if no other port is explicitly
* specified.
*/
#define GUACD_DEFAULT_BIND_PORT "4822"
/**
* The contents of a guacd configuration file.
*/
typedef struct guacd_config {
/**
* The host to bind on.
*/
char* bind_host;
/**
* The port to bind on.
*/
char* bind_port;
/**
* The file to write the PID in, if any.
*/
char* pidfile;
/**
* Whether guacd should run in the foreground.
*/
int foreground;
/**
* Whether guacd should simply print its version information and exit.
*/
int print_version;
#ifdef ENABLE_SSL
/**
* SSL certificate file.
*/
char* cert_file;
/**
* SSL private key file.
*/
char* key_file;
#endif
/**
* The maximum log level to be logged by guacd.
*/
guac_client_log_level max_log_level;
} guacd_config;
#endif

View File

@ -278,13 +278,10 @@ static int guacd_route_connection(guacd_proc_map* map, guac_socket* socket) {
proc = guacd_proc_map_retrieve(map, identifier); proc = guacd_proc_map_retrieve(map, identifier);
new_process = 0; new_process = 0;
/* Warn and ward off client if requested connection does not exist */ /* Warn if requested connection does not exist */
if (proc == NULL) { if (proc == NULL)
guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist", identifier); guacd_log(GUAC_LOG_INFO, "Connection \"%s\" does not exist.",
guac_protocol_send_error(socket, "No such connection.", identifier);
GUAC_PROTOCOL_STATUS_RESOURCE_NOT_FOUND);
}
else else
guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"", guacd_log(GUAC_LOG_INFO, "Joining existing connection \"%s\"",
identifier); identifier);

View File

@ -19,10 +19,9 @@
#include "config.h" #include "config.h"
#include "conf.h" #include "connection.h"
#include "conf-args.h" #include "conf-args.h"
#include "conf-file.h" #include "conf-file.h"
#include "connection.h"
#include "log.h" #include "log.h"
#include "proc-map.h" #include "proc-map.h"
@ -280,13 +279,6 @@ int main(int argc, char* argv[]) {
if (config == NULL || guacd_conf_parse_args(config, argc, argv)) if (config == NULL || guacd_conf_parse_args(config, argc, argv))
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
/* If requested, simply print version and exit, without initializing the
* logging system, etc. */
if (config->print_version) {
printf("Guacamole proxy daemon (guacd) version " VERSION "\n");
exit(EXIT_SUCCESS);
}
/* Init logging as early as possible */ /* Init logging as early as possible */
guacd_log_level = config->max_log_level; guacd_log_level = config->max_log_level;
openlog(GUACD_LOG_NAME, LOG_PID, LOG_DAEMON); openlog(GUACD_LOG_NAME, LOG_PID, LOG_DAEMON);
@ -304,6 +296,20 @@ int main(int argc, char* argv[]) {
} }
/* Get socket */
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) {
guacd_log(GUAC_LOG_ERROR, "Error opening socket: %s", strerror(errno));
exit(EXIT_FAILURE);
}
/* Allow socket reuse */
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
(void*) &opt_on, sizeof(opt_on))) {
guacd_log(GUAC_LOG_WARNING, "Unable to set socket options for reuse: %s",
strerror(errno));
}
/* Attempt binding of each address until success */ /* Attempt binding of each address until success */
current_address = addresses; current_address = addresses;
while (current_address != NULL) { while (current_address != NULL) {
@ -319,47 +325,27 @@ int main(int argc, char* argv[]) {
guacd_log(GUAC_LOG_ERROR, "Unable to resolve host: %s", guacd_log(GUAC_LOG_ERROR, "Unable to resolve host: %s",
gai_strerror(retval)); gai_strerror(retval));
/* Get socket */
socket_fd = socket(current_address->ai_family, SOCK_STREAM, 0);
if (socket_fd < 0) {
guacd_log(GUAC_LOG_ERROR, "Error opening socket: %s", strerror(errno));
/* Unable to get a socket for the resolved address family, try next */
current_address = current_address->ai_next;
continue;
}
/* Allow socket reuse */
if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
(void*) &opt_on, sizeof(opt_on))) {
guacd_log(GUAC_LOG_WARNING, "Unable to set socket options for reuse: %s",
strerror(errno));
}
/* Attempt to bind socket to address */ /* Attempt to bind socket to address */
if (bind(socket_fd, if (bind(socket_fd,
current_address->ai_addr, current_address->ai_addr,
current_address->ai_addrlen) == 0) { current_address->ai_addrlen) == 0) {
guacd_log(GUAC_LOG_DEBUG, "Successfully bound " guacd_log(GUAC_LOG_DEBUG, "Successfully bound socket to "
"%s socket to host %s, port %s", "host %s, port %s", bound_address, bound_port);
(current_address->ai_family == AF_INET) ? "AF_INET" : "AF_INET6",
bound_address, bound_port);
/* Done if successful bind */ /* Done if successful bind */
break; break;
} }
/* Otherwise log information regarding bind failure */ /* Otherwise log information regarding bind failure */
close(socket_fd); else
socket_fd = -1; guacd_log(GUAC_LOG_DEBUG, "Unable to bind socket to "
guacd_log(GUAC_LOG_DEBUG, "Unable to bind %s socket to " "host %s, port %s: %s",
"host %s, port %s: %s", bound_address, bound_port, strerror(errno));
(current_address->ai_family == AF_INET) ? "AF_INET" : "AF_INET6",
bound_address, bound_port, strerror(errno));
/* Try next address */
current_address = current_address->ai_next; current_address = current_address->ai_next;
} }
/* If unable to bind to anything, fail */ /* If unable to bind to anything, fail */
@ -381,15 +367,10 @@ int main(int argc, char* argv[]) {
CRYPTO_set_locking_callback(guacd_openssl_locking_callback); CRYPTO_set_locking_callback(guacd_openssl_locking_callback);
#endif #endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L /* Init SSL */
/* Init OpenSSL for OpenSSL Versions < 1.1.0 */
SSL_library_init(); SSL_library_init();
SSL_load_error_strings(); SSL_load_error_strings();
ssl_context = SSL_CTX_new(SSLv23_server_method()); ssl_context = SSL_CTX_new(SSLv23_server_method());
#else
/* Set up OpenSSL for OpenSSL Versions >= 1.1.0 */
ssl_context = SSL_CTX_new(TLS_server_method());
#endif
/* Load key */ /* Load key */
if (config->key_file != NULL) { if (config->key_file != NULL) {

View File

@ -133,7 +133,7 @@ void guacd_log_guac_error(guac_client_log_level level, const char* message) {
void guacd_log_handshake_failure() { void guacd_log_handshake_failure() {
if (guac_error == GUAC_STATUS_CLOSED) if (guac_error == GUAC_STATUS_CLOSED)
guacd_log(GUAC_LOG_DEBUG, guacd_log(GUAC_LOG_INFO,
"Guacamole connection closed during handshake"); "Guacamole connection closed during handshake");
else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR) else if (guac_error == GUAC_STATUS_PROTOCOL_ERROR)
guacd_log(GUAC_LOG_ERROR, guacd_log(GUAC_LOG_ERROR,

View File

@ -16,7 +16,7 @@
.\" specific language governing permissions and limitations .\" specific language governing permissions and limitations
.\" under the License. .\" under the License.
.\" .\"
.TH guacd 8 "1 Jun 2017" "version @PACKAGE_VERSION@" "Apache Guacamole" .TH guacd 8 "1 Jun 2017" "version 0.9.13-incubating" "Guacamole"
. .
.SH NAME .SH NAME
guacd \- Guacamole proxy daemon guacd \- Guacamole proxy daemon
@ -30,7 +30,6 @@ guacd \- Guacamole proxy daemon
[\fB-C\fR \fICERTIFICATE FILE\fR] [\fB-C\fR \fICERTIFICATE FILE\fR]
[\fB-K\fR \fIKEY FILE\fR] [\fB-K\fR \fIKEY FILE\fR]
[\fB-f\fR] [\fB-f\fR]
[\fB-v\fR]
. .
.SH DESCRIPTION .SH DESCRIPTION
.B guacd .B guacd
@ -81,11 +80,6 @@ Causes
.B guacd .B guacd
to run in the foreground, rather than automatically forking into the to run in the foreground, rather than automatically forking into the
background. background.
.TP
\fB\-v\fR
Causes
.B guacd
to simply print its version information and exit.
. .
.SH SSL/TLS OPTIONS .SH SSL/TLS OPTIONS
If libssl was present at the time If libssl was present at the time
@ -119,3 +113,6 @@ this option is not given, communication with guacd must be unencrypted.
. .
.SH SEE ALSO .SH SEE ALSO
.BR guacd.conf (5) .BR guacd.conf (5)
.
.SH AUTHOR
Written by Michael Jumper <mike.jumper@guac-dev.org>

View File

@ -16,7 +16,7 @@
.\" specific language governing permissions and limitations .\" specific language governing permissions and limitations
.\" under the License. .\" under the License.
.\" .\"
.TH guacd.conf 5 "1 Jun 2017" "version @PACKAGE_VERSION@" "Apache Guacamole" .TH guacd.conf 5 "1 Jun 2017" "version 0.9.13-incubating" "Guacamole"
. .
.SH NAME .SH NAME
/etc/guacamole/guacd.conf \- Configuration file for guacd /etc/guacamole/guacd.conf \- Configuration file for guacd
@ -176,3 +176,6 @@ server_certificate = /etc/ssl/certs/guacd.crt
server_key = /etc/ssl/private/guacd.key server_key = /etc/ssl/private/guacd.key
.RE .RE
.fi .fi
.
.SH AUTHOR
Written by Michael Jumper <mike.jumper@guac-dev.org>

View File

@ -44,7 +44,7 @@ int guacd_send_fd(int sock, int fd) {
message.msg_iovlen = 1; message.msg_iovlen = 1;
/* Assign ancillary data buffer */ /* Assign ancillary data buffer */
char buffer[CMSG_SPACE(sizeof(fd))] = {0}; char buffer[CMSG_SPACE(sizeof(fd))];
message.msg_control = buffer; message.msg_control = buffer;
message.msg_controllen = sizeof(buffer); message.msg_controllen = sizeof(buffer);

View File

@ -33,15 +33,11 @@
#include <guacamole/user.h> #include <guacamole/user.h>
#include <errno.h> #include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h>
/** /**
* Parameters for the user thread. * Parameters for the user thread.
@ -142,151 +138,6 @@ static void guacd_proc_add_user(guacd_proc* proc, int fd, int owner) {
} }
/**
* Forcibly kills all processes within the current process group, including the
* current process and all child processes. This function is only safe to call
* if the process group ID has been correctly set. Calling this function within
* a process which does not have a PGID separate from the main guacd process
* can result in guacd itself being terminated.
*/
static void guacd_kill_current_proc_group() {
/* Forcibly kill all children within process group */
if (kill(0, SIGKILL))
guacd_log(GUAC_LOG_WARNING, "Unable to forcibly terminate "
"client process: %s ", strerror(errno));
}
/**
* The current status of a background attempt to free a guac_client instance.
*/
typedef struct guacd_client_free {
/**
* The guac_client instance being freed.
*/
guac_client* client;
/**
* The condition which is signalled whenever changes are made to the
* completed flag. The completed flag only changes from zero (not yet
* freed) to non-zero (successfully freed).
*/
pthread_cond_t completed_cond;
/**
* Mutex which must be acquired before any changes are made to the
* completed flag.
*/
pthread_mutex_t completed_mutex;
/**
* Whether the guac_client has been successfully freed. Initially, this
* will be zero, indicating that the free operation has not yet been
* attempted. If the client is eventually successfully freed, this will be
* set to a non-zero value. Changes to this flag are signalled through
* the completed_cond condition.
*/
int completed;
} guacd_client_free;
/**
* Thread which frees a given guac_client instance in the background. If the
* free operation succeeds, a flag is set on the provided structure, and the
* change in that flag is signalled with a pthread condition.
*
* At the time this function is provided to a pthread_create() call, the
* completed flag of the associated guacd_client_free structure MUST be
* initialized to zero, the pthread mutex and condition MUST both be
* initialized, and the client pointer must point to the guac_client being
* freed.
*
* @param data
* A pointer to a guacd_client_free structure describing the free
* operation.
*
* @return
* Always NULL.
*/
static void* guacd_client_free_thread(void* data) {
guacd_client_free* free_operation = (guacd_client_free*) data;
/* Attempt to free client (this may never return if the client is
* malfunctioning) */
guac_client_free(free_operation->client);
/* Signal that the client was successfully freed */
pthread_mutex_lock(&free_operation->completed_mutex);
free_operation->completed = 1;
pthread_cond_broadcast(&free_operation->completed_cond);
pthread_mutex_unlock(&free_operation->completed_mutex);
return NULL;
}
/**
* Attempts to free the given guac_client, restricting the time taken by the
* free handler of the guac_client to a finite number of seconds. If the free
* handler does not complete within the time alotted, this function returns
* and the intended free operation is left in an undefined state.
*
* @param client
* The guac_client instance to free.
*
* @param timeout
* The maximum amount of time to wait for the guac_client to be freed,
* in seconds.
*
* @return
* Zero if the guac_client was successfully freed within the time alotted,
* non-zero otherwise.
*/
static int guacd_timed_client_free(guac_client* client, int timeout) {
pthread_t client_free_thread;
guacd_client_free free_operation = {
.client = client,
.completed_cond = PTHREAD_COND_INITIALIZER,
.completed_mutex = PTHREAD_MUTEX_INITIALIZER,
.completed = 0
};
/* Get current time */
struct timeval current_time;
if (gettimeofday(&current_time, NULL))
return 1;
/* Calculate exact time that the free operation MUST complete by */
struct timespec deadline = {
.tv_sec = current_time.tv_sec + timeout,
.tv_nsec = current_time.tv_usec * 1000
};
/* The mutex associated with the pthread conditional and flag MUST be
* acquired before attempting to wait for the condition */
if (pthread_mutex_lock(&free_operation.completed_mutex))
return 1;
/* Free the client in a separate thread, so we can time the free operation */
if (!pthread_create(&client_free_thread, NULL,
guacd_client_free_thread, &free_operation)) {
/* Wait a finite amount of time for the free operation to finish */
(void) pthread_cond_timedwait(&free_operation.completed_cond,
&free_operation.completed_mutex, &deadline);
}
(void) pthread_mutex_unlock(&free_operation.completed_mutex);
/* Return status of free operation */
return !free_operation.completed;
}
/** /**
* Starts protocol-specific handling on the given process by loading the client * Starts protocol-specific handling on the given process by loading the client
* plugin for that protocol. This function does NOT return. It initializes the * plugin for that protocol. This function does NOT return. It initializes the
@ -303,18 +154,8 @@ static int guacd_timed_client_free(guac_client* client, int timeout) {
*/ */
static void guacd_exec_proc(guacd_proc* proc, const char* protocol) { static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
int result = 1;
/* Set process group ID to match PID */
if (setpgid(0, 0)) {
guacd_log(GUAC_LOG_ERROR, "Cannot set PGID for connection process: %s",
strerror(errno));
goto cleanup_process;
}
/* Init client for selected protocol */ /* Init client for selected protocol */
guac_client* client = proc->client; if (guac_client_load_plugin(proc->client, protocol)) {
if (guac_client_load_plugin(client, protocol)) {
/* Log error */ /* Log error */
if (guac_error == GUAC_STATUS_NOT_FOUND) if (guac_error == GUAC_STATUS_NOT_FOUND)
@ -324,15 +165,15 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
guacd_log_guac_error(GUAC_LOG_ERROR, guacd_log_guac_error(GUAC_LOG_ERROR,
"Unable to load client plugin"); "Unable to load client plugin");
goto cleanup_client; guac_client_free(proc->client);
close(proc->fd_socket);
free(proc);
exit(1);
} }
/* The first file descriptor is the owner */ /* The first file descriptor is the owner */
int owner = 1; int owner = 1;
/* Enable keep alive on the broadcast socket */
guac_socket_require_keep_alive(client->socket);
/* Add each received file descriptor as a new user */ /* Add each received file descriptor as a new user */
int received_fd; int received_fd;
while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) { while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) {
@ -343,48 +184,15 @@ static void guacd_exec_proc(guacd_proc* proc, const char* protocol) {
owner = 0; owner = 0;
} }
cleanup_client:
/* Request client to stop/disconnect */ /* Stop and free client */
guac_client_stop(client); guac_client_stop(proc->client);
guac_client_free(proc->client);
/* Attempt to free client cleanly */ /* Child is finished */
guacd_log(GUAC_LOG_DEBUG, "Requesting termination of client...");
result = guacd_timed_client_free(client, GUACD_CLIENT_FREE_TIMEOUT);
/* If client was unable to be freed, warn and forcibly kill */
if (result) {
guacd_log(GUAC_LOG_WARNING, "Client did not terminate in a timely "
"manner. Forcibly terminating client and any child "
"processes.");
guacd_kill_current_proc_group();
}
else
guacd_log(GUAC_LOG_DEBUG, "Client terminated successfully.");
/* Verify whether children were all properly reaped */
pid_t child_pid;
while ((child_pid = waitpid(0, NULL, WNOHANG)) > 0) {
guacd_log(GUAC_LOG_DEBUG, "Automatically reaped unreaped "
"(zombie) child process with PID %i.", child_pid);
}
/* If running children remain, warn and forcibly kill */
if (child_pid == 0) {
guacd_log(GUAC_LOG_WARNING, "Client reported successful termination, "
"but child processes remain. Forcibly terminating client and "
"child processes.");
guacd_kill_current_proc_group();
}
cleanup_process:
/* Free up all internal resources outside the client */
close(proc->fd_socket); close(proc->fd_socket);
free(proc); free(proc);
exit(0);
exit(result);
} }

View File

@ -40,14 +40,6 @@
*/ */
#define GUACD_USEC_TIMEOUT (GUACD_TIMEOUT*1000) #define GUACD_USEC_TIMEOUT (GUACD_TIMEOUT*1000)
/**
* The number of seconds to wait for any particular guac_client instance
* to be freed following disconnect. If the free operation does not complete
* within this period of time, the associated process will be forcibly
* terminated.
*/
#define GUACD_CLIENT_FREE_TIMEOUT 5
/** /**
* Process information of the internal remote desktop client. * Process information of the internal remote desktop client.
*/ */

View File

@ -1,29 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
[Unit]
Description=Guacamole Server
Documentation=man:guacd(8)
After=network.target
[Service]
User=daemon
ExecStart=@sbindir@/guacd -f
Restart=on-abnormal
[Install]
WantedBy=multi-user.target

View File

@ -3,6 +3,3 @@
guacenc guacenc
guacenc.exe guacenc.exe
# Documentation (built from .in files)
man/guacenc.1

View File

@ -16,12 +16,6 @@
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
# #
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign AUTOMAKE_OPTIONS = foreign
@ -32,7 +26,6 @@ man_MANS = \
noinst_HEADERS = \ noinst_HEADERS = \
buffer.h \ buffer.h \
cursor.h \
display.h \ display.h \
encode.h \ encode.h \
ffmpeg-compat.h \ ffmpeg-compat.h \
@ -48,7 +41,6 @@ noinst_HEADERS = \
guacenc_SOURCES = \ guacenc_SOURCES = \
buffer.c \ buffer.c \
cursor.c \
display.c \ display.c \
display-buffers.c \ display-buffers.c \
display-image-streams.c \ display-image-streams.c \
@ -67,7 +59,6 @@ guacenc_SOURCES = \
instruction-dispose.c \ instruction-dispose.c \
instruction-end.c \ instruction-end.c \
instruction-img.c \ instruction-img.c \
instruction-mouse.c \
instruction-move.c \ instruction-move.c \
instruction-rect.c \ instruction-rect.c \
instruction-shade.c \ instruction-shade.c \
@ -90,7 +81,6 @@ endif
guacenc_CFLAGS = \ guacenc_CFLAGS = \
-Werror -Wall \ -Werror -Wall \
@AVCODEC_CFLAGS@ \ @AVCODEC_CFLAGS@ \
@AVFORMAT_CFLAGS@ \
@AVUTIL_CFLAGS@ \ @AVUTIL_CFLAGS@ \
@LIBGUAC_INCLUDE@ \ @LIBGUAC_INCLUDE@ \
@SWSCALE_CFLAGS@ @SWSCALE_CFLAGS@
@ -98,15 +88,14 @@ guacenc_CFLAGS = \
guacenc_LDADD = \ guacenc_LDADD = \
@LIBGUAC_LTLIB@ @LIBGUAC_LTLIB@
guacenc_LDFLAGS = \ guacenc_LDFLAGS = \
@AVCODEC_LIBS@ \ @AVCODEC_LIBS@ \
@AVFORMAT_LIBS@ \ @AVUTIL_LIBS@ \
@AVUTIL_LIBS@ \ @CAIRO_LIBS@ \
@CAIRO_LIBS@ \ @JPEG_LIBS@ \
@JPEG_LIBS@ \ @SWSCALE_LIBS@ \
@SWSCALE_LIBS@ \
@WEBP_LIBS@ @WEBP_LIBS@
EXTRA_DIST = \ EXTRA_DIST = \
man/guacenc.1.in man/guacenc.1

View File

@ -1,59 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "buffer.h"
#include "cursor.h"
#include <stdlib.h>
guacenc_cursor* guacenc_cursor_alloc() {
/* Allocate new cursor */
guacenc_cursor* cursor = (guacenc_cursor*) malloc(sizeof(guacenc_cursor));
if (cursor == NULL)
return NULL;
/* Allocate associated buffer (image) */
cursor->buffer = guacenc_buffer_alloc();
if (cursor->buffer == NULL) {
free(cursor);
return NULL;
}
/* Do not initially render cursor, unless it moves */
cursor->x = cursor->y = -1;
return cursor;
}
void guacenc_cursor_free(guacenc_cursor* cursor) {
/* Ignore NULL cursors */
if (cursor == NULL)
return;
/* Free underlying buffer */
guacenc_buffer_free(cursor->buffer);
free(cursor);
}

View File

@ -1,87 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUACENC_CURSOR_H
#define GUACENC_CURSOR_H
#include "config.h"
#include "buffer.h"
#include <guacamole/protocol.h>
#include <guacamole/timestamp.h>
/**
* A mouse cursor, having a current location, hostspot, and associated cursor
* image.
*/
typedef struct guacenc_cursor {
/**
* The current X coordinate of the mouse cursor, in pixels. Valid values
* are non-negative. Negative values indicate that the cursor should not
* be rendered.
*/
int x;
/**
* The current Y coordinate of the mouse cursor, in pixels. Valid values
* are non-negative. Negative values indicate that the cursor should not
* be rendered.
*/
int y;
/**
* The X coordinate of the mouse cursor hotspot within the cursor image,
* in pixels.
*/
int hotspot_x;
/**
* The Y coordinate of the mouse cursor hotspot within the cursor image,
* in pixels.
*/
int hotspot_y;
/**
* The current mouse cursor image.
*/
guacenc_buffer* buffer;
} guacenc_cursor;
/**
* Allocates and initializes a new cursor object.
*
* @return
* A newly-allocated and initialized guacenc_cursor, or NULL if allocation
* fails.
*/
guacenc_cursor* guacenc_cursor_alloc();
/**
* Frees all memory associated with the given cursor object. If the cursor
* provided is NULL, this function has no effect.
*
* @param cursor
* The cursor to free, which may be NULL.
*/
void guacenc_cursor_free(guacenc_cursor* cursor);
#endif

View File

@ -20,12 +20,9 @@
#include "config.h" #include "config.h"
#include "display.h" #include "display.h"
#include "layer.h" #include "layer.h"
#include "log.h"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <guacamole/client.h>
#include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -78,50 +75,6 @@ static int guacenc_display_layer_comparator(const void* a, const void* b) {
} }
/**
* Renders the mouse cursor on top of the frame buffer of the default layer of
* the given display.
*
* @param display
* The display whose mouse cursor should be rendered to the frame buffer
* of its default layer.
*
* @return
* Zero if rendering succeeds, non-zero otherwise.
*/
static int guacenc_display_render_cursor(guacenc_display* display) {
guacenc_cursor* cursor = display->cursor;
/* Do not render cursor if coordinates are negative */
if (cursor->x < 0 || cursor->y < 0)
return 0;
/* Retrieve default layer (guaranteed to not be NULL) */
guacenc_layer* def_layer = guacenc_display_get_layer(display, 0);
assert(def_layer != NULL);
/* Get source and destination buffers */
guacenc_buffer* src = cursor->buffer;
guacenc_buffer* dst = def_layer->frame;
/* Render cursor to layer */
if (src->width > 0 && src->height > 0) {
cairo_set_source_surface(dst->cairo, src->surface,
cursor->x - cursor->hotspot_x,
cursor->y - cursor->hotspot_y);
cairo_rectangle(dst->cairo,
cursor->x - cursor->hotspot_x,
cursor->y - cursor->hotspot_y,
src->width, src->height);
cairo_fill(dst->cairo);
}
/* Always succeeds */
return 0;
}
int guacenc_display_flatten(guacenc_display* display) { int guacenc_display_flatten(guacenc_display* display) {
int i; int i;
@ -198,8 +151,7 @@ int guacenc_display_flatten(guacenc_display* display) {
} }
/* Render cursor on top of everything else */ return 0;
return guacenc_display_render_cursor(display);
} }

View File

@ -18,7 +18,6 @@
*/ */
#include "config.h" #include "config.h"
#include "cursor.h"
#include "display.h" #include "display.h"
#include "video.h" #include "video.h"
@ -98,9 +97,6 @@ guacenc_display* guacenc_display_alloc(const char* path, const char* codec,
/* Associate display with video output */ /* Associate display with video output */
display->output = video; display->output = video;
/* Allocate special-purpose cursor layer */
display->cursor = guacenc_cursor_alloc();
return display; return display;
} }
@ -128,9 +124,6 @@ int guacenc_display_free(guacenc_display* display) {
for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++) for (i = 0; i < GUACENC_DISPLAY_MAX_STREAMS; i++)
guacenc_image_stream_free(display->image_streams[i]); guacenc_image_stream_free(display->image_streams[i]);
/* Free cursor */
guacenc_cursor_free(display->cursor);
free(display); free(display);
return retval; return retval;

View File

@ -22,12 +22,10 @@
#include "config.h" #include "config.h"
#include "buffer.h" #include "buffer.h"
#include "cursor.h"
#include "image-stream.h" #include "image-stream.h"
#include "layer.h" #include "layer.h"
#include "video.h" #include "video.h"
#include <cairo/cairo.h>
#include <guacamole/protocol.h> #include <guacamole/protocol.h>
#include <guacamole/timestamp.h> #include <guacamole/timestamp.h>
@ -54,11 +52,6 @@
*/ */
typedef struct guacenc_display { typedef struct guacenc_display {
/**
* The current mouse cursor state.
*/
guacenc_cursor* cursor;
/** /**
* All currently-allocated buffers. The index of the buffer corresponds to * All currently-allocated buffers. The index of the buffer corresponds to
* its position within this array, where -1 is the 0th entry. If a buffer * its position within this array, where -1 is the 0th entry. If a buffer

View File

@ -63,11 +63,8 @@ static int guacenc_read_instructions(guacenc_display* display,
/* Continuously read and handle all instructions */ /* Continuously read and handle all instructions */
while (!guac_parser_read(parser, socket, -1)) { while (!guac_parser_read(parser, socket, -1)) {
if (guacenc_handle_instruction(display, parser->opcode, guacenc_handle_instruction(display, parser->opcode,
parser->argc, parser->argv)) { parser->argc, parser->argv);
guacenc_log(GUAC_LOG_DEBUG, "Handling of \"%s\" instruction "
"failed.", parser->opcode);
}
} }
/* Fail on read/parse error */ /* Fail on read/parse error */

View File

@ -51,41 +51,8 @@
*/ */
static int guacenc_write_packet(guacenc_video* video, void* data, int size) { static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
int ret; /* Write data, logging any errors */
if (fwrite(data, 1, size, video->output) == 0) {
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0)
AVPacket pkt;
/* Have to create a packet around the encoded data we have */
av_init_packet(&pkt);
if (video->context->coded_frame->pts != AV_NOPTS_VALUE) {
pkt.pts = av_rescale_q(video->context->coded_frame->pts,
video->context->time_base,
video->output_stream->time_base);
}
if (video->context->coded_frame->key_frame) {
pkt->flags |= AV_PKT_FLAG_KEY;
}
pkt.data = data;
pkt.size = size;
pkt.stream_index = video->output_stream->index;
ret = av_interleaved_write_frame(video->container_format_context, &pkt);
#else
/* We know data is already a packet if we're using a newer libavcodec */
AVPacket* pkt = (AVPacket*) data;
av_packet_rescale_ts(pkt, video->context->time_base, video->output_stream->time_base);
pkt->stream_index = video->output_stream->index;
ret = av_interleaved_write_frame(video->container_format_context, pkt);
#endif
if (ret != 0) {
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame " guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
"#%" PRId64 ": %s", video->next_pts, strerror(errno)); "#%" PRId64 ": %s", video->next_pts, strerror(errno));
return -1; return -1;
@ -95,7 +62,8 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes", guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes",
video->next_pts, size); video->next_pts, size);
return ret; return 0;
} }
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
@ -135,15 +103,6 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
#else #else
/* For libavcodec < 57.37.100: input/output was not decoupled and static
* allocation of AVPacket was supported.
*
* NOTE: Since dynamic allocation of AVPacket was added before this point (in
* 57.12.100) and static allocation was deprecated later (in 58.133.100), it is
* convenient to tie static vs. dynamic allocation to the old vs. new I/O
* mechanism and avoid further complicating the version comparison logic. */
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 37, 100)
/* Init video packet */ /* Init video packet */
AVPacket packet; AVPacket packet;
av_init_packet(&packet); av_init_packet(&packet);
@ -152,6 +111,8 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
packet.data = NULL; packet.data = NULL;
packet.size = 0; packet.size = 0;
/* For libavcodec < 57.37.100: input/output was not decoupled */
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100)
/* Write frame to video */ /* Write frame to video */
int got_data; int got_data;
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) { if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
@ -162,12 +123,10 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
/* Write corresponding data to file */ /* Write corresponding data to file */
if (got_data) { if (got_data) {
guacenc_write_packet(video, (void*) &packet, packet.size); guacenc_write_packet(video, packet.data, packet.size);
av_packet_unref(&packet); av_packet_unref(&packet);
} }
#else #else
/* Write frame to video */ /* Write frame to video */
int result = avcodec_send_frame(video->context, frame); int result = avcodec_send_frame(video->context, frame);
@ -182,25 +141,18 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
return -1; return -1;
} }
AVPacket* packet = av_packet_alloc();
if (packet == NULL)
return -1;
/* Flush all available packets */ /* Flush all available packets */
int got_data = 0; int got_data = 0;
while (avcodec_receive_packet(video->context, packet) == 0) { while (avcodec_receive_packet(video->context, &packet) == 0) {
/* Data was received */ /* Data was received */
got_data = 1; got_data = 1;
/* Attempt to write data to output file */ /* Attempt to write data to output file */
guacenc_write_packet(video, (void*) packet, packet->size); guacenc_write_packet(video, packet.data, packet.size);
av_packet_unref(packet); av_packet_unref(&packet);
} }
av_packet_free(&packet);
#endif #endif
/* Frame may have been queued for later writing / reordering */ /* Frame may have been queued for later writing / reordering */
@ -213,54 +165,3 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
#endif #endif
} }
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
int pix_fmt, AVRational time_base) {
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 33, 100)
stream->codec->bit_rate = bitrate;
stream->codec->width = width;
stream->codec->height = height;
stream->codec->gop_size = gop_size;
stream->codec->qmax = qmax;
stream->codec->qmin = qmin;
stream->codec->pix_fmt = pix_fmt;
stream->codec->time_base = time_base;
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(55, 44, 100)
stream->time_base = time_base;
#endif
return stream->codec;
#else
AVCodecContext* context = avcodec_alloc_context3(codec);
if (context) {
context->bit_rate = bitrate;
context->width = width;
context->height = height;
context->gop_size = gop_size;
context->qmax = qmax;
context->qmin = qmin;
context->pix_fmt = pix_fmt;
context->time_base = time_base;
stream->time_base = time_base;
}
return context;
#endif
}
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
const AVCodec *codec, AVDictionary **options,
AVStream* stream) {
int ret = avcodec_open2(avcodec_context, codec, options);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
/* Copy stream parameters to the muxer */
int codecpar_ret = avcodec_parameters_from_context(stream->codecpar, avcodec_context);
if (codecpar_ret < 0)
return codecpar_ret;
#endif
return ret;
}

View File

@ -52,16 +52,6 @@
#define av_packet_unref av_free_packet #define av_packet_unref av_free_packet
#endif #endif
/* For libavcodec <= 56.41.100: Global header flag didn't have AV_ prefix.
* Guacenc defines its own flag here to avoid conflicts with libavcodec
* macros.
*/
#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(56,41,100)
#define GUACENC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER
#else
#define GUACENC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER
#endif
/* For libavutil < 51.42.0: AV_PIX_FMT_* was PIX_FMT_* */ /* For libavutil < 51.42.0: AV_PIX_FMT_* was PIX_FMT_* */
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0) #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0)
#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32 #define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
@ -88,78 +78,5 @@
*/ */
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame); int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
/**
* Creates and sets up the AVCodecContext for the appropriate version of
* libavformat installed. The AVCodecContext will be built, but the AVStream
* will also be affected by having its time_base field set to the value passed
* into this function.
*
* @param stream
* The open AVStream.
*
* @param codec
* The codec used on the AVStream.
*
* @param bitrate
* The target bitrate for the encoded video
*
* @param width
* The target width for the encoded video.
*
* @param height
* The target height for the encoded video.
*
* @param gop_size
* The size of the Group of Pictures.
*
* @param qmax
* The max value of the quantizer.
*
* @param qmin
* The min value of the quantizer.
*
* @param pix_fmt
* The target pixel format for the encoded video.
*
* @param time_base
* The target time base for the encoded video.
*
* @return
* The pointer to the configured AVCodecContext.
*
*/
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
int pix_fmt, AVRational time_base);
/**
* A wrapper for avcodec_open2(). Because libavformat ver 57.33.100 and greater
* use stream->codecpar rather than stream->codec to handle information to the
* codec, there needs to be an additional step in that version. So this
* wrapper handles that. Otherwise, it's the same as avcodec_open2().
*
* @param avcodec_context
* The context to initialize.
*
* @param codec
* The codec to open this context for. If a non-NULL codec has been
* previously passed to avcodec_alloc_context3() or for this context, then
* this parameter MUST be either NULL or equal to the previously passed
* codec.
*
* @param options
* A dictionary filled with AVCodecContext and codec-private options. On
* return this object will be filled with options that were not found.
*
* @param stream
* The stream for the codec context.
*
* @return
* Zero on success, a negative value on error.
*/
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
const AVCodec *codec, AVDictionary **options,
AVStream* stream);
#endif #endif

View File

@ -25,7 +25,6 @@
#include "parse.h" #include "parse.h"
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <getopt.h> #include <getopt.h>
#include <stdbool.h> #include <stdbool.h>
@ -76,14 +75,8 @@ int main(int argc, char* argv[]) {
guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) " guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) "
"version " VERSION); "version " VERSION);
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
/* Prepare libavcodec */ /* Prepare libavcodec */
avcodec_register_all(); avcodec_register_all();
#endif
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
/* Track number of overall failures */ /* Track number of overall failures */
int total_files = argc - optind; int total_files = argc - optind;

View File

@ -18,7 +18,6 @@
*/ */
#include "config.h" #include "config.h"
#include "display.h"
#include "image-stream.h" #include "image-stream.h"
#include "jpeg.h" #include "jpeg.h"
#include "log.h" #include "log.h"
@ -140,7 +139,6 @@ int guacenc_image_stream_end(guacenc_image_stream* stream,
/* Draw surface to buffer */ /* Draw surface to buffer */
if (buffer->cairo != NULL) { if (buffer->cairo != NULL) {
cairo_set_operator(buffer->cairo, guacenc_display_cairo_operator(stream->mask));
cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y); cairo_set_source_surface(buffer->cairo, surface, stream->x, stream->y);
cairo_rectangle(buffer->cairo, stream->x, stream->y, width, height); cairo_rectangle(buffer->cairo, stream->x, stream->y, width, height);
cairo_fill(buffer->cairo); cairo_fill(buffer->cairo);

View File

@ -18,8 +18,6 @@
*/ */
#include "config.h" #include "config.h"
#include "buffer.h"
#include "cursor.h"
#include "display.h" #include "display.h"
#include "log.h" #include "log.h"
@ -38,32 +36,16 @@ int guacenc_handle_cursor(guacenc_display* display, int argc, char** argv) {
/* Parse arguments */ /* Parse arguments */
int hotspot_x = atoi(argv[0]); int hotspot_x = atoi(argv[0]);
int hotspot_y = atoi(argv[1]); int hotspot_y = atoi(argv[1]);
int sindex = atoi(argv[2]); int src_index = atoi(argv[2]);
int sx = atoi(argv[3]); int src_x = atoi(argv[3]);
int sy = atoi(argv[4]); int src_y = atoi(argv[4]);
int width = atoi(argv[5]); int src_w = atoi(argv[5]);
int height = atoi(argv[6]); int src_h = atoi(argv[6]);
/* Pull buffer of source layer/buffer */ /* Nothing to do with cursor (yet) */
guacenc_buffer* src = guacenc_display_get_related_buffer(display, sindex); guacenc_log(GUAC_LOG_DEBUG, "Ignoring cursor: hotspot (%i, %i) "
if (src == NULL) "src_layer=%i (%i, %i) %ix%i", hotspot_x, hotspot_y,
return 1; src_index, src_x, src_y, src_w, src_h);
/* Update cursor hotspot */
guacenc_cursor* cursor = display->cursor;
cursor->hotspot_x = hotspot_x;
cursor->hotspot_y = hotspot_y;
/* Resize cursor to exactly fit */
guacenc_buffer_resize(cursor->buffer, width, height);
/* Copy rectangle from source to cursor */
guacenc_buffer* dst = cursor->buffer;
if (src->surface != NULL && dst->cairo != NULL) {
cairo_set_operator(dst->cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(dst->cairo, src->surface, sx, sy);
cairo_paint(dst->cairo);
}
return 0; return 0;

View File

@ -1,56 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "cursor.h"
#include "display.h"
#include "log.h"
#include "parse.h"
#include <guacamole/client.h>
#include <stdlib.h>
int guacenc_handle_mouse(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */
if (argc < 2) {
guacenc_log(GUAC_LOG_WARNING, "\"mouse\" instruction incomplete");
return 1;
}
/* Parse arguments */
int x = atoi(argv[0]);
int y = atoi(argv[1]);
/* Update cursor properties */
guacenc_cursor* cursor = display->cursor;
cursor->x = x;
cursor->y = y;
/* If no timestamp provided, nothing further to do */
if (argc < 4)
return 0;
/* Leverage timestamp to render frame */
guac_timestamp timestamp = guacenc_parse_timestamp(argv[3]);
return guacenc_display_sync(display, timestamp);
}

View File

@ -38,13 +38,13 @@ int guacenc_handle_size(guacenc_display* display, int argc, char** argv) {
int width = atoi(argv[1]); int width = atoi(argv[1]);
int height = atoi(argv[2]); int height = atoi(argv[2]);
/* Retrieve requested layer/buffer */ /* Retrieve requested layer */
guacenc_buffer* buffer = guacenc_display_get_related_buffer(display, index); guacenc_layer* layer = guacenc_display_get_layer(display, index);
if (buffer == NULL) if (layer == NULL)
return 1; return 1;
/* Resize layer/buffer */ /* Resize layer */
return guacenc_buffer_resize(buffer, width, height); return guacenc_buffer_resize(layer->buffer, width, height);
} }

View File

@ -20,7 +20,6 @@
#include "config.h" #include "config.h"
#include "display.h" #include "display.h"
#include "log.h" #include "log.h"
#include "parse.h"
#include <guacamole/client.h> #include <guacamole/client.h>
#include <guacamole/timestamp.h> #include <guacamole/timestamp.h>
@ -28,6 +27,40 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdlib.h> #include <stdlib.h>
/**
* Parses a guac_timestamp from the given string. The string is assumed to
* consist solely of decimal digits with an optional leading minus sign. If the
* given string contains other characters, the behavior of this function is
* undefined.
*
* @param str
* The string to parse, which must contain only decimal digits and an
* optional leading minus sign.
*
* @return
* A guac_timestamp having the same value as the provided string.
*/
static guac_timestamp guacenc_parse_timestamp(const char* str) {
int sign = 1;
int64_t num = 0;
for (; *str != '\0'; str++) {
/* Flip sign for each '-' encountered */
if (*str == '-')
sign = -sign;
/* If not '-', assume the character is a digit */
else
num = num * 10 + (*str - '0');
}
return (guac_timestamp) (num * sign);
}
int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) { int guacenc_handle_sync(guacenc_display* display, int argc, char** argv) {
/* Verify argument count */ /* Verify argument count */

View File

@ -30,7 +30,6 @@ guacenc_instruction_handler_mapping guacenc_instruction_handler_map[] = {
{"blob", guacenc_handle_blob}, {"blob", guacenc_handle_blob},
{"img", guacenc_handle_img}, {"img", guacenc_handle_img},
{"end", guacenc_handle_end}, {"end", guacenc_handle_end},
{"mouse", guacenc_handle_mouse},
{"sync", guacenc_handle_sync}, {"sync", guacenc_handle_sync},
{"cursor", guacenc_handle_cursor}, {"cursor", guacenc_handle_cursor},
{"copy", guacenc_handle_copy}, {"copy", guacenc_handle_copy},

View File

@ -114,11 +114,6 @@ guacenc_instruction_handler guacenc_handle_img;
*/ */
guacenc_instruction_handler guacenc_handle_end; guacenc_instruction_handler guacenc_handle_end;
/**
* Handler for the Guacamole "mouse" instruction.
*/
guacenc_instruction_handler guacenc_handle_mouse;
/** /**
* Handler for the Guacamole "sync" instruction. * Handler for the Guacamole "sync" instruction.
*/ */

View File

@ -16,7 +16,7 @@
.\" specific language governing permissions and limitations .\" specific language governing permissions and limitations
.\" under the License. .\" under the License.
.\" .\"
.TH guacenc 1 "26 Jan 2018" "version @PACKAGE_VERSION@" "Apache Guacamole" .TH guacenc 1 "1 Jun 2017" "version 0.9.13-incubating" "Guacamole"
. .
.SH NAME .SH NAME
guacenc \- Guacamole video encoder guacenc \- Guacamole video encoder
@ -38,7 +38,7 @@ is essentially an implementation of a Guacamole client which accepts
its input from files instead of a network connection, and renders directly to its input from files instead of a network connection, and renders directly to
video instead of to the user's screen. video instead of to the user's screen.
.P .P
Each \fIFILE\fR specified will be encoded as MPEG-4 video to a new Each \fIFILE\fR specified will be encoded as a raw MPEG-4 video stream to a new
file named \fIFILE\fR.m4v, encoded according to the other options specified. By file named \fIFILE\fR.m4v, encoded according to the other options specified. By
default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved
with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be
@ -76,5 +76,5 @@ Overrides the default behavior of
such that input files will be encoded even if they appear to be recordings of such that input files will be encoded even if they appear to be recordings of
in-progress Guacamole sessions. in-progress Guacamole sessions.
. .
.SH SEE ALSO .SH AUTHOR
.BR guaclog (1) Written by Michael Jumper <mike.jumper@guac-dev.org>

View File

@ -19,8 +19,6 @@
#include "config.h" #include "config.h"
#include <guacamole/timestamp.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <string.h> #include <string.h>
@ -69,24 +67,3 @@ int guacenc_parse_dimensions(char* arg, int* width, int* height) {
} }
guac_timestamp guacenc_parse_timestamp(const char* str) {
int sign = 1;
int64_t num = 0;
for (; *str != '\0'; str++) {
/* Flip sign for each '-' encountered */
if (*str == '-')
sign = -sign;
/* If not '-', assume the character is a digit */
else
num = num * 10 + (*str - '0');
}
return (guac_timestamp) (num * sign);
}

View File

@ -22,8 +22,6 @@
#include "config.h" #include "config.h"
#include <guacamole/timestamp.h>
/** /**
* Parses a string into a single integer. Only positive integers are accepted. * Parses a string into a single integer. Only positive integers are accepted.
* The input string may be modified during parsing. A value will be stored in * The input string may be modified during parsing. A value will be stored in
@ -65,21 +63,6 @@ int guacenc_parse_int(char* arg, int* i);
*/ */
int guacenc_parse_dimensions(char* arg, int* width, int* height); int guacenc_parse_dimensions(char* arg, int* width, int* height);
/**
* Parses a guac_timestamp from the given string. The string is assumed to
* consist solely of decimal digits with an optional leading minus sign. If the
* given string contains other characters, the behavior of this function is
* undefined.
*
* @param str
* The string to parse, which must contain only decimal digits and an
* optional leading minus sign.
*
* @return
* A guac_timestamp having the same value as the provided string.
*/
guac_timestamp guacenc_parse_timestamp(const char* str);
#endif #endif

View File

@ -25,9 +25,6 @@
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#ifndef AVFORMAT_AVFORMAT_H
#include <libavformat/avformat.h>
#endif
#include <libavutil/common.h> #include <libavutil/common.h>
#include <libavutil/imgutils.h> #include <libavutil/imgutils.h>
#include <libswscale/swscale.h> #include <libswscale/swscale.h>
@ -37,68 +34,42 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <unistd.h> #include <unistd.h>
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
int width, int height, int bitrate) { int width, int height, int bitrate) {
const AVOutputFormat *container_format;
AVFormatContext *container_format_context;
AVStream *video_stream;
int ret;
int failed_header = 0;
/* allocate the output media context */
avformat_alloc_output_context2(&container_format_context, NULL, NULL, path);
if (container_format_context == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to determine container from output file name");
goto fail_codec;
}
container_format = container_format_context->oformat;
/* Pull codec based on name */ /* Pull codec based on name */
const AVCodec* codec = avcodec_find_encoder_by_name(codec_name); AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
if (codec == NULL) { if (codec == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".", guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".",
codec_name); codec_name);
goto fail_codec; goto fail_codec;
} }
/* create stream */
video_stream = NULL;
video_stream = avformat_new_stream(container_format_context, codec);
if (video_stream == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Could not allocate encoder stream. Cannot continue.");
goto fail_format_context;
}
video_stream->id = container_format_context->nb_streams - 1;
/* Retrieve encoding context */ /* Retrieve encoding context */
AVCodecContext* avcodec_context = AVCodecContext* context = avcodec_alloc_context3(codec);
guacenc_build_avcodeccontext(video_stream, codec, bitrate, width, if (context == NULL) {
height, /*gop size*/ 10, /*qmax*/ 31, /*qmin*/ 2,
/*pix fmt*/ AV_PIX_FMT_YUV420P,
/*time base*/ (AVRational) { 1, GUACENC_VIDEO_FRAMERATE });
if (avcodec_context == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for " guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for "
"codec \"%s\".", codec_name); "codec \"%s\".", codec_name);
goto fail_context; goto fail_context;
} }
/* If format needs global headers, write them */ /* Init context with encoding parameters */
if (container_format_context->oformat->flags & AVFMT_GLOBALHEADER) { context->bit_rate = bitrate;
avcodec_context->flags |= GUACENC_FLAG_GLOBAL_HEADER; context->width = width;
} context->height = height;
context->time_base = (AVRational) { 1, GUACENC_VIDEO_FRAMERATE };
context->gop_size = 10;
context->max_b_frames = 1;
context->pix_fmt = AV_PIX_FMT_YUV420P;
/* Open codec for use */ /* Open codec for use */
if (guacenc_open_avcodec(avcodec_context, codec, NULL, video_stream) < 0) { if (avcodec_open2(context, codec, NULL) < 0) {
guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name); guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name);
goto fail_codec_open; goto fail_codec_open;
} }
@ -110,9 +81,9 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
} }
/* Copy necessary data for frame from context */ /* Copy necessary data for frame from context */
frame->format = avcodec_context->pix_fmt; frame->format = context->pix_fmt;
frame->width = avcodec_context->width; frame->width = context->width;
frame->height = avcodec_context->height; frame->height = context->height;
/* Allocate actual backing data for frame */ /* Allocate actual backing data for frame */
if (av_image_alloc(frame->data, frame->linesize, frame->width, if (av_image_alloc(frame->data, frame->linesize, frame->width,
@ -120,32 +91,31 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
goto fail_frame_data; goto fail_frame_data;
} }
/* Open output file, if the container needs it */ /* Open output file */
if (!(container_format->flags & AVFMT_NOFILE)) { int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
ret = avio_open(&container_format_context->pb, path, AVIO_FLAG_WRITE); if (fd == -1) {
if (ret < 0) { guacenc_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s",
guacenc_log(GUAC_LOG_ERROR, "Error occurred while opening output file."); path, strerror(errno));
goto fail_output_avio; goto fail_output_fd;
}
} }
/* write the stream header, if needed */ /* Create stream for output file */
ret = avformat_write_header(container_format_context, NULL); FILE* output = fdopen(fd, "wb");
if (ret < 0) { if (output == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Error occurred while writing output file header."); guacenc_log(GUAC_LOG_ERROR, "Failed to allocate stream for output "
failed_header = true; "file \"%s\": %s", path, strerror(errno));
goto fail_output_file; goto fail_output_file;
} }
/* Allocate video structure */ /* Allocate video structure */
guacenc_video* video = malloc(sizeof(guacenc_video)); guacenc_video* video = malloc(sizeof(guacenc_video));
if (video == NULL) if (video == NULL) {
goto fail_alloc_video; goto fail_video;
}
/* Init properties of video */ /* Init properties of video */
video->output_stream = video_stream; video->output = output;
video->context = avcodec_context; video->context = context;
video->container_format_context = container_format_context;
video->next_frame = frame; video->next_frame = frame;
video->width = width; video->width = width;
video->height = height; video->height = height;
@ -158,16 +128,13 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
return video; return video;
/* Free all allocated data in case of failure */ /* Free all allocated data in case of failure */
fail_alloc_video: fail_video:
fclose(output);
fail_output_file: fail_output_file:
avio_close(container_format_context->pb); close(fd);
/* Delete the file that was created if it was actually created */ fail_output_fd:
if (unlink(path) == -1 && errno != ENOENT)
guacenc_log(GUAC_LOG_WARNING, "Failed output file \"%s\" could not "
"be automatically deleted: %s", path, strerror(errno));
fail_output_avio:
av_freep(&frame->data[0]); av_freep(&frame->data[0]);
fail_frame_data: fail_frame_data:
@ -175,13 +142,7 @@ fail_frame_data:
fail_frame: fail_frame:
fail_codec_open: fail_codec_open:
avcodec_free_context(&avcodec_context); avcodec_free_context(&context);
fail_format_context:
/* failing to write the container implicitly frees the context */
if (!failed_header) {
avformat_free_context(container_format_context);
}
fail_context: fail_context:
fail_codec: fail_codec:
@ -267,11 +228,7 @@ int guacenc_video_advance_timeline(guacenc_video* video,
/* Flush frames to bring timeline in sync, duplicating if necessary */ /* Flush frames to bring timeline in sync, duplicating if necessary */
do { do {
if (guacenc_video_flush_frame(video)) { guacenc_video_flush_frame(video);
guacenc_log(GUAC_LOG_ERROR, "Unable to flush frame to video "
"stream.");
return 1;
}
} while (--elapsed != 0); } while (--elapsed != 0);
} }
@ -474,34 +431,26 @@ int guacenc_video_free(guacenc_video* video) {
/* Write final frame */ /* Write final frame */
guacenc_video_flush_frame(video); guacenc_video_flush_frame(video);
/* Init video packet for final flush of encoded data */
AVPacket packet;
av_init_packet(&packet);
/* Flush any unwritten frames */ /* Flush any unwritten frames */
int retval; int retval;
do { do {
retval = guacenc_video_write_frame(video, NULL); retval = guacenc_video_write_frame(video, NULL);
} while (retval > 0); } while (retval > 0);
/* write trailer, if needed */
if (video->container_format_context != NULL &&
video->output_stream != NULL) {
guacenc_log(GUAC_LOG_DEBUG, "Writing trailer: %s",
av_write_trailer(video->container_format_context) == 0 ?
"success" : "failure");
}
/* File is now completely written */ /* File is now completely written */
if (video->container_format_context != NULL) { fclose(video->output);
avio_close(video->container_format_context->pb);
}
/* Free frame encoding data */ /* Free frame encoding data */
av_freep(&video->next_frame->data[0]); av_freep(&video->next_frame->data[0]);
av_frame_free(&video->next_frame); av_frame_free(&video->next_frame);
/* Clean up encoding context */ /* Clean up encoding context */
if (video->context != NULL) { avcodec_close(video->context);
avcodec_close(video->context); avcodec_free_context(&(video->context));
avcodec_free_context(&(video->context));
}
free(video); free(video);
return 0; return 0;

View File

@ -26,14 +26,6 @@
#include <guacamole/timestamp.h> #include <guacamole/timestamp.h>
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#ifndef AVCODEC_AVCODEC_H
#include <libavcodec/avcodec.h>
#endif
#ifndef AVFORMAT_AVFORMAT_H
#include <libavformat/avformat.h>
#endif
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@ -50,11 +42,9 @@
typedef struct guacenc_video { typedef struct guacenc_video {
/** /**
* AVStream for video output. * Output file stream.
* Frames sent to this stream are written into
* the output file in the specified container format.
*/ */
AVStream* output_stream; FILE* output;
/** /**
* The open encoding context from libavcodec, created for the codec * The open encoding context from libavcodec, created for the codec
@ -62,12 +52,6 @@ typedef struct guacenc_video {
*/ */
AVCodecContext* context; AVCodecContext* context;
/**
* The open format context from libavformat, created for the file
* container specified when this guacenc_video was created.
*/
AVFormatContext* container_format_context;
/** /**
* The width of the video, in pixels. * The width of the video, in pixels.
*/ */

View File

@ -1,8 +0,0 @@
# Compiled guaclog
guaclog
guaclog.exe
# Documentation (built from .in files)
man/guaclog.1

View File

@ -1,59 +0,0 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
bin_PROGRAMS = guaclog
man_MANS = \
man/guaclog.1
noinst_HEADERS = \
guaclog.h \
instructions.h \
interpret.h \
keydef.h \
log.h \
state.h
guaclog_SOURCES = \
guaclog.c \
instructions.c \
instruction-key.c \
interpret.c \
keydef.c \
log.c \
state.c
guaclog_CFLAGS = \
-Werror -Wall \
@LIBGUAC_INCLUDE@
guaclog_LDADD = \
@LIBGUAC_LTLIB@
EXTRA_DIST = \
man/guaclog.1.in

Some files were not shown because too many files have changed in this diff Show More