summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--data/ad/common-account9
-rw-r--r--data/ad/common-auth12
-rw-r--r--data/ad/common-password11
-rw-r--r--data/ad/common-session22
-rw-r--r--data/ad/common-session-noninteractive17
-rw-r--r--data/ad/mountscript49
-rw-r--r--data/ad/nsswitch.conf14
-rw-r--r--data/pxemenu.template78
-rw-r--r--pom.xml57
-rwxr-xr-xscripts/ldadp-launcher86
-rwxr-xr-xscripts/mount-store82
-rw-r--r--src/main/java/org/openslx/satserver/util/Archive.java217
-rw-r--r--src/main/java/org/openslx/satserver/util/Constants.java8
-rw-r--r--src/main/java/org/openslx/satserver/util/Util.java66
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/CompileIPxe.java83
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/CreateAdConfig.java161
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/DeleteFile.java57
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/DownloadFile.java91
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/DownloadText.java82
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/DummyTask.java58
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/LdadpLauncher.java79
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/LdapSearch.java122
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/LinkConfigTgz.java61
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/ListArchive.java116
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/LocalAddressesList.java76
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/LsTask.java80
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/MountVmStore.java84
-rw-r--r--src/main/java/org/openslx/taskmanager/tasks/RecompressArchive.java193
29 files changed, 2080 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4beb56d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.project
+.settings/
+.classpath
+*.swp
+*~
+*.tmp
+*.class
+target/
+
diff --git a/data/ad/common-account b/data/ad/common-account
new file mode 100644
index 0000000..e06e539
--- /dev/null
+++ b/data/ad/common-account
@@ -0,0 +1,9 @@
+account [success=2 new_authtok_reqd=done default=ignore] pam_unix.so
+account [success=1 default=ignore] pam_ldap.so
+# here's the fallback if no module succeeds
+account requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+account required pam_permit.so
+
diff --git a/data/ad/common-auth b/data/ad/common-auth
new file mode 100644
index 0000000..c83e66e
--- /dev/null
+++ b/data/ad/common-auth
@@ -0,0 +1,12 @@
+auth [success=2 default=ignore] pam_unix.so nullok_secure
+auth [success=1 default=ignore] pam_ldap.so use_first_pass
+# here's the fallback if no module succeeds
+auth requisite pam_deny.so
+auth optional pam_script.so expose=1
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+auth required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+auth optional pam_cap.so
+
diff --git a/data/ad/common-password b/data/ad/common-password
new file mode 100644
index 0000000..4cda16c
--- /dev/null
+++ b/data/ad/common-password
@@ -0,0 +1,11 @@
+password [success=2 default=ignore] pam_unix.so obscure sha512
+password [success=1 user_unknown=ignore default=die] pam_ldap.so use_authtok try_first_pass
+# here's the fallback if no module succeeds
+password requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+password required pam_permit.so
+# and here are more per-package modules (the "Additional" block)
+password optional pam_gnome_keyring.so
+
diff --git a/data/ad/common-session b/data/ad/common-session
new file mode 100644
index 0000000..942af33
--- /dev/null
+++ b/data/ad/common-session
@@ -0,0 +1,22 @@
+session [default=1] pam_permit.so
+# here's the fallback if no module succeeds
+session requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+session required pam_permit.so
+# The pam_umask module will set the umask according to the system default in
+# /etc/login.defs and user settings, solving the problem of different
+# umask settings with different shells, display managers, remote sessions etc.
+# See "man pam_umask".
+session optional pam_umask.so
+session required pam_systemd.so
+session optional pam_env.so readenv=1
+session optional pam_env.so readenv=1 envfile=/etc/default/locale
+# and here are more per-package modules (the "Additional" block)
+session required pam_unix.so
+session optional pam_ldap.so
+session sufficient pam_script.so
+session optional pam_xdg_support.so
+session optional pam_ck_connector.so nox11
+
diff --git a/data/ad/common-session-noninteractive b/data/ad/common-session-noninteractive
new file mode 100644
index 0000000..0279a53
--- /dev/null
+++ b/data/ad/common-session-noninteractive
@@ -0,0 +1,17 @@
+session [default=1] pam_permit.so
+# here's the fallback if no module succeeds
+session requisite pam_deny.so
+# prime the stack with a positive return value if there isn't one already;
+# this avoids us returning an error just because nothing sets a success code
+# since the modules above will each just jump around
+session required pam_permit.so
+# The pam_umask module will set the umask according to the system default in
+# /etc/login.defs and user settings, solving the problem of different
+# umask settings with different shells, display managers, remote sessions etc.
+# See "man pam_umask".
+session optional pam_umask.so
+# and here are more per-package modules (the "Additional" block)
+session required pam_unix.so
+session optional pam_ldap.so
+session optional pam_xdg_support.so
+
diff --git a/data/ad/mountscript b/data/ad/mountscript
new file mode 100644
index 0000000..2256904
--- /dev/null
+++ b/data/ad/mountscript
@@ -0,0 +1,49 @@
+###################################################################
+#
+# This script is a part of the pam_script_ses_open script
+# and is not stand-alone!
+#
+
+if ! grep -q "^${PAM_USER}:" "/etc/passwd"; then
+
+ # determine fileserver and share for home directories
+ touch "/tmp/ldapsearch.${PAM_USER}"
+ chmod 0600 "/tmp/ldapsearch.${PAM_USER}"
+ ldapsearch -x -LLL uid="${PAM_USER}" homeMount > "/tmp/ldapsearch.${PAM_USER}" || \
+ { slxlog "pam-ad-ldapquery" "Could not query LDAP server for parameters of user '${PAM_USER}'."; exit 1; }
+ VOLUME=$(cat "/tmp/ldapsearch.${PAM_USER}" | grep ^homeMount | head -n 1 | cut -d" " -f2)
+ [ -z "${VOLUME}" ] && slxlog "pam-ad-ldapvolume" "LDAP server did not provide 'homeMount'. Aborting mount for ${PAM_USER}." && exit 1
+
+ MOUNT_OPTS="-t cifs -o uid=${USER_UID},gid=${USER_GID},forceuid,forcegid,sec=ntlm"
+
+ SIGNAL=$(mktemp)
+ rm -f -- "${SIGNAL}"
+
+ export USER="${PAM_USER}"
+ export PASSWD="${PAM_AUTHTOK}"
+
+ ( mount ${MOUNT_OPTS} "${VOLUME}" "${PERSISTENT_HOME_DIR}" > "/tmp/home.$PAM_USER" 2>&1 || touch "${SIGNAL}" ) &
+ MOUNT_PID=$!
+ for COUNTER in 1 2 4 4; do
+ kill -0 "${MOUNT_PID}" 2>/dev/null || break
+ sleep "${COUNTER}"
+ done
+
+ if [ -e "${SIGNAL}" ]; then
+ slxlog "pam-reutlingen" "Mount of '${FILESERVER}/${PAM_USER}' to '${PERSISTENT_HOME_DIR}' failed. (Args: ${MOUNT_OPTS})" "/tmp/home.$PAM_USER"
+ sleep 1
+ rm -f -- "${SIGNAL}"
+ elif kill -9 "${MOUNT_PID}" 2>/dev/null; then
+ slxlog "pam-reutlingen" "Mount of '${FILESERVER}/${PAM_USER}' to '${PERSISTENT_HOME_DIR}' timed out. (Args: ${MOUNT_OPTS})" "/tmp/home.$PAM_USER"
+ sleep 1
+ else
+ PERSISTENT_OK=yes
+ chmod -R u+rw "${PERSISTENT_HOME_DIR}" 2>/dev/null
+ fi
+
+ unset USER
+ unset PASSWD
+
+ rm -f -- "/tmp/home.$PAM_USER"
+fi
+
diff --git a/data/ad/nsswitch.conf b/data/ad/nsswitch.conf
new file mode 100644
index 0000000..1909d49
--- /dev/null
+++ b/data/ad/nsswitch.conf
@@ -0,0 +1,14 @@
+passwd: compat ldap
+group: compat ldap
+shadow: compat
+
+hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4
+networks: files
+
+protocols: db files
+services: db files
+ethers: db files
+rpc: db files
+
+netgroup: nis
+
diff --git a/data/pxemenu.template b/data/pxemenu.template
new file mode 100644
index 0000000..2fcc88e
--- /dev/null
+++ b/data/pxemenu.template
@@ -0,0 +1,78 @@
+DEFAULT vesamenu.c32
+
+NOESCAPE 1
+PROMPT 0
+
+MENU BACKGROUND openslx.png
+MENU WIDTH 78
+MENU MARGIN 9
+MENU PASSWORDMARGIN 9
+MENU ROWS 10
+MENU TABMSGROW 16
+MENU CMDLINEROW 16
+MENU ENDROW -1
+MENU PASSWORDROW 16
+MENU TIMEOUTROW 20
+MENU HELPMSGROW 16
+MENU HELPMSGENDROW -1
+MENU HSHIFT 0
+MENU VSHIFT 7
+
+menu color screen 37;40 #80ffffff #00000000 std
+menu color border 37;40 #40000000 #ff8093a1 std
+menu color title 1;37;40 #ffff8b00 #ff8093a1 std
+menu color unsel 37;40 #fff0f0f0 #ff8093a1 std
+menu color hotkey 1;37;40 #ffff8b00 #ff8093a1 std
+menu color sel 7;37;40 #ff1c2a33 #667799bb all
+menu color hotsel 1;7;37;40 #ffff8b00 #667799bb all
+menu color disabled 1;37;40 #ffff8b00 #ff8093a1 std
+menu color scrollbar 37;40 #40000000 #ee000000 std
+menu color tabmsg 37;40 #ffff8b00 #ff8093a1 std
+menu color cmdmark 1;37;40 #ffff8b00 #ff8093a1 std
+menu color cmdline 37;40 #fff0f0f0 #ff8093a1 std
+menu color pwdborder 37;40 #40000000 #ff8093a1 std
+menu color pwdheader 37;40 #ffff8b00 #ff8093a1 std
+menu color pwdentry 37;40 #ffff8b00 #ff8093a1 std
+menu color timeout_msg 37;40 #fff0f0f0 #ff8093a1 std
+menu color timeout 1;37;40 #ffff8b00 #ff8093a1 std
+menu color help 37;40 #ff1c2a33 #00000000 none
+MENU MSGCOLOR #ff1c2a33 #00000000 none
+
+
+TIMEOUT %timeout%
+TOTALTIMEOUT %totaltimeout%
+MENU TITLE bwLehrpool BETA VERSION
+MENU CLEAR
+ONTIMEOUT %default%
+
+
+LABEL shutdown
+ MENU HIDE
+ KERNEL kernel-shutdown
+ APPEND initrd=initramfs-shutdown quiet
+
+
+LABEL net
+ MENU LABEL ^bwLehrpool-Umgebung starten
+ KERNEL http://%ip%/boot/default/kernel
+ INITRD http://%ip%/boot/default/initramfs-stage31
+ APPEND slxsrv=%ip% slxbase=boot/default vga=current quiet splash
+ IPAPPEND 3
+ %default-net%
+
+
+LABEL hdd
+ MENU LABEL ^Lokales System starten
+ LOCALBOOT 0
+ %default-hdd%
+
+
+LABEL openslx-debug
+ MENU LABEL ^bwLehrpool-Umgebung starten (nosplash, debug)
+ KERNEL http://%ip%/boot/default/kernel
+ INITRD http://%ip%/boot/default/initramfs-stage31
+ APPEND slxsrv=%ip% slxbase=boot/default
+ IPAPPEND 3
+
+%custom%
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..270801b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,57 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.openslx.satserver</groupId>
+ <artifactId>taskmanager-tasks-bwlp</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>taskmanager-tasks-bwlp</name>
+ <url>http://maven.apache.org</url>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.1</version>
+ <configuration>
+ <source>1.7</source>
+ <target>1.7</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.openslx.taskmanager</groupId>
+ <artifactId>taskmanager-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-compress</artifactId>
+ <version>1.8</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/scripts/ldadp-launcher b/scripts/ldadp-launcher
new file mode 100755
index 0000000..be73ec7
--- /dev/null
+++ b/scripts/ldadp-launcher
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+if [ $# -lt 2 ]; then
+ echo "Invalid parameter count: '$#'"
+ exit 1
+fi
+
+BASE="$1"
+shift
+
+if [ -z "$BASE" ] || [ ! -r "$BASE" ] || [ ! -d "$BASE" ]; then
+ echo "Basedir invalid: '$BASE'"
+ exit 2
+fi
+cd "$BASE"
+
+[ "x$1" == "x--" ] && shift
+
+# At this point $@ should only be ldadp instance ids
+
+inArray () {
+ local e
+ for e in "${@:2}"; do
+ [[ "x$e" == "x$1" ]] && return 0
+ done
+ return 1
+}
+
+isInstance () {
+ if [ $# -ne 1 ]; then
+ echo "isInstance needs 1 param, got $#"
+ return 0
+ fi
+ [ -L "/proc/$1/exe" ] && [ -r "/proc/$1/exe" ] && [[ "$(readlink -f "/proc/$1/exe")" == *ldadp ]] && return 0
+ return 1
+}
+
+launch () {
+ local CONFIG="${BASE}/configs/${1}.cfg"
+ if [ ! -r "$CONFIG" ]; then
+ echo "Told to start ldadp for module '${1}', but no config file found!"
+ return 1
+ fi
+ echo "Launching #$1"
+ "${BASE}/ldadp" -n "$CONFIG" &
+ local P=$!
+ sleep 1
+ if ! kill -0 "$P" 2>/dev/null; then
+ echo "...FAILED!"
+ return 1
+ fi
+ echo -n "$P" > "${BASE}/pid/${1}"
+ return 0
+}
+
+for FILE in $(find "$BASE/pid" -type f -regextype posix-extended -regex "(^|.*/)[0-9]+$"); do
+ ID=$(basename $FILE)
+ PID=$(<$FILE)
+ if ! [[ "$PID" =~ ^[0-9]+$ ]]; then
+ echo "Invalid PID file: '$FILE'"
+ unlink "$FILE"
+ fi
+ inArray $ID "$@" && isInstance $PID && continue # Should be running, is running, nothing to do
+ #
+ unlink "$FILE"
+ if inArray $ID; then
+ # Should be running, is not running, launch!
+ launch $ID
+ elif isInstance $PID; then
+ # Is running, should not, kill
+ kill $PID
+ sleep 1
+ isInstance $PID && kill -9 $PID
+ fi
+done
+
+while [ $# -gt 0 ]; do
+ ID=$1
+ shift
+ FILE="${BASE}/pid/${ID}"
+ [ -r "$FILE" ] && isInstance $(<$FILE) && continue
+ launch $ID
+done
+
+exit 0
+
diff --git a/scripts/mount-store b/scripts/mount-store
new file mode 100755
index 0000000..1291d1a
--- /dev/null
+++ b/scripts/mount-store
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+if [ $# -lt 2 ]; then
+ echo "Bad call to $0." >&2
+ echo "Expected: $0 images <source> [username] [password]" >&2
+ exit 1
+fi
+
+WHAT="$1"
+SOURCE="$2"
+USERNAME="$3"
+PASSWORD="$4"
+
+# Currently WHAT can only be images (the central store for all images),
+# but maybe there will be other storage types in the future.
+case "$WHAT" in
+images)
+ DEST="/opt/openslx/nfs"
+ ;;
+*)
+ echo "Invalid/Unknown mount type: '$WHAT'." >&2
+ exit 1
+ ;;
+esac
+
+# Sanity checks: Destination exists?
+if [ ! -d "$DEST" ]; then
+ mkdir -p "$DEST"
+ chown root:images "$DEST"
+ chmod 0775 "$DEST"
+fi
+
+# Still a no?
+if [ ! -d "$DEST" ]; then
+ echo "Mount point '$DEST' does not exist and could not be created!" >&2
+ echo "This should not happen and means this server is severely messed up. :(" >&2
+ exit 1
+fi
+
+# Already mounted?
+if awk '{print $2}' "/proc/mounts" | grep -q "^${DEST}\$"; then
+ echo "Trying to unmount '$DEST'..."
+ umount -v "$DEST"
+ RET=$?
+ if [ "$RET" -ne "0" ]; then
+ echo "Cannot unmount '$DEST'!" >&2
+ exit "$RET"
+ fi
+fi
+
+# Unmount and not requested to mount (local mode)
+if [[ "${SOURCE}" == "null" ]]; then
+ rm -f "${DEST}/.notmounted"
+ exit 0
+fi
+
+touch "${DEST}/.notmounted"
+
+# Mount!
+
+if grep -E -q '^[^/].+:.+' <<<$SOURCE; then
+ # seems to be NFS
+ mount -t nfs -o rw,async,nolock,fg,ac,retry=1,timeo=600 "$SOURCE" "$DEST"
+ RET=$?
+elif grep -E -q '^//' <<<$SOURCE; then
+ # seens to be SMB
+ export USER="$USERNAME"
+ export PASSWD="$PASSWORD"
+ mount -t cifs -o rw,uid=0,gid=12345,forceuid,forcegid,file_mode=0664,dir_mode=0775,sec=ntlm "$SOURCE" "$DEST"
+ RET=$?
+ unset USER PASSWD
+else
+ echo "Unknown mount type: $SOURCE"
+ exit 1
+fi
+
+if [ "$RET" == "0" ]; then
+ chmod -R ug+rw "$DEST"
+fi
+
+exit $RET
+
diff --git a/src/main/java/org/openslx/satserver/util/Archive.java b/src/main/java/org/openslx/satserver/util/Archive.java
new file mode 100644
index 0000000..dcc1380
--- /dev/null
+++ b/src/main/java/org/openslx/satserver/util/Archive.java
@@ -0,0 +1,217 @@
+package org.openslx.satserver.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.ArchiveStreamFactory;
+import org.apache.commons.compress.archivers.ar.ArArchiveEntry;
+import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry;
+import org.apache.commons.compress.archivers.dump.DumpArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
+import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
+import org.apache.commons.io.FileUtils;
+
+public class Archive
+{
+
+ /**
+ * Create input stream from given archive file. The archive format will be guessed
+ * from the firt couple of bytes in the file, which should work for most archive
+ * formats (see apache commons-compress docs for detailed information)
+ *
+ * @param fileName archive to open for reading
+ * @return ArchiveInputStream stream to read archive from
+ * @throws FileNotFoundException
+ * @throws ArchiveException
+ * @throws IllegalArgumentException
+ */
+ public static ArchiveInputStream getArchiveInputStream( String fileName )
+ throws FileNotFoundException, ArchiveException, IllegalArgumentException
+ {
+ // Get an input stream for the archive handler
+ // 1) Get input stream from file
+ InputStream fileStream = new BufferedInputStream( new FileInputStream( fileName ) );
+ // 2) Might be compressed, so try to get a decompressed stream
+ InputStream archiveStream;
+ try {
+ archiveStream = new BufferedInputStream( new CompressorStreamFactory().createCompressorInputStream( fileStream ) );
+ } catch ( Exception e ) {
+ // Might be uncompressed archive, like tar - just try file input stream
+ archiveStream = fileStream;
+ }
+ // 3) Try to create archive input stream - if it succeeds we can read the archive :)
+ return new ArchiveStreamFactory().createArchiveInputStream( archiveStream );
+ }
+
+ /**
+ * Create tar archive with the given name. Optional compression method is
+ * determined by the file extension.
+ *
+ * @param outputFile name of archive to create; can be relative or absolute path
+ * @return {@link TarArchiveOutputStream} to write archive entries to
+ * @throws IOException
+ */
+ @SuppressWarnings( "resource" )
+ public static TarArchiveOutputStream createTarArchive( String outputFile ) throws IOException
+ {
+ OutputStream outputStream = new BufferedOutputStream( new FileOutputStream( outputFile ) );
+ OutputStream compressedOutputStream;
+ if ( outputFile.endsWith( ".tar.gz" ) || outputFile.endsWith( ".tgz" ) ) {
+ compressedOutputStream = new BufferedOutputStream( new GzipCompressorOutputStream( outputStream ) );
+ } else if ( outputFile.endsWith( ".tar.bz2" ) || outputFile.endsWith( ".tbz" ) ) {
+ compressedOutputStream = new BufferedOutputStream( new BZip2CompressorOutputStream( outputStream ) );
+ } else if ( outputFile.endsWith( ".tar.xz" ) || outputFile.endsWith( ".txz" ) ) {
+ compressedOutputStream = new BufferedOutputStream( new XZCompressorOutputStream( outputStream ) );
+ } else if ( outputFile.endsWith( ".tar.Z" ) || outputFile.endsWith( ".tar.z" ) || outputFile.endsWith( ".tz" ) ) {
+ compressedOutputStream = new BufferedOutputStream( new XZCompressorOutputStream( outputStream ) );
+ } else if ( outputFile.endsWith( ".tar.lzma" ) ) {
+ compressedOutputStream = new BufferedOutputStream( new XZCompressorOutputStream( outputStream ) );
+ } else if ( outputFile.endsWith( ".tar" ) ) {
+ compressedOutputStream = outputStream;
+ } else {
+ outputStream.close();
+ return null;
+ }
+ try {
+ return new TarArchiveOutputStream( compressedOutputStream );
+ } catch ( Throwable t ) {
+ compressedOutputStream.close();
+ return null;
+ }
+ }
+
+ public static TarArchiveEntry createTarArchiveEntry( ArchiveEntry inEntry, int defaultUser, int defaultGroup, int defaultDirMode, int defaultFileMode )
+ {
+ if ( inEntry instanceof TarArchiveEntry ) {
+ // Source is tar - easy
+ return (TarArchiveEntry)inEntry;
+ }
+ final TarArchiveEntry outEntry = new TarArchiveEntry( inEntry.getName() );
+ outEntry.setSize( inEntry.getSize() );
+ if ( inEntry instanceof ArArchiveEntry ) {
+ // Source is ar - has most of the stuff tar supports; transform
+ outEntry.setIds( ( (ArArchiveEntry)inEntry ).getUserId(), ( (ArArchiveEntry)inEntry ).getGroupId() );
+ outEntry.setMode( ( (ArArchiveEntry)inEntry ).getMode() );
+ outEntry.setModTime( ( (ArArchiveEntry)inEntry ).getLastModifiedDate() );
+ } else if ( inEntry instanceof DumpArchiveEntry ) {
+ // Source is dump (whatever the fuck that is) - has most of the stuff tar supports; transform
+ outEntry.setIds( ( (DumpArchiveEntry)inEntry ).getUserId(), ( (DumpArchiveEntry)inEntry ).getGroupId() );
+ outEntry.setMode( ( (DumpArchiveEntry)inEntry ).getMode() );
+ outEntry.setModTime( ( (DumpArchiveEntry)inEntry ).getLastModifiedDate() );
+ } else if ( inEntry instanceof CpioArchiveEntry ) {
+ // Source is cpio - has most of the stuff tar supports; transform
+ outEntry.setIds( (int) ( (CpioArchiveEntry)inEntry ).getUID(), (int) ( (CpioArchiveEntry)inEntry ).getGID() );
+ outEntry.setMode( (int) ( ( (CpioArchiveEntry)inEntry ).getMode() & 07777 ) );
+ outEntry.setModTime( ( (CpioArchiveEntry)inEntry ).getLastModifiedDate() );
+ } else if ( inEntry instanceof ZipArchiveEntry ) {
+ // Source is ZIP - might not have unix stuff
+ outEntry.setIds( defaultUser, defaultGroup );
+ int mode = ( (ZipArchiveEntry)inEntry ).getUnixMode();
+ if ( mode != 0 ) { // Has unix permissions
+ outEntry.setMode( mode );
+ } else if ( inEntry.isDirectory() ) { // Use default passed in
+ outEntry.setMode( defaultDirMode );
+ } else { // Use default passed in
+ outEntry.setMode( defaultFileMode );
+ }
+ outEntry.setModTime( ( (ZipArchiveEntry)inEntry ).getLastModifiedDate() );
+ } else {
+ outEntry.setIds( defaultUser, defaultGroup );
+ if ( inEntry.isDirectory() ) {
+ outEntry.setMode( defaultDirMode );
+ } else {
+ outEntry.setMode( defaultFileMode );
+ Calendar cal = GregorianCalendar.getInstance();
+ cal.set( 2014, 4, 2, 13, 57 );
+ outEntry.setModTime( cal.getTime() );
+ }
+ }
+ return outEntry;
+ }
+
+ public static boolean tarAddFile( TarArchiveOutputStream tar, String inArchiveFileName, File sourceFile, int mode )
+ {
+ if ( !sourceFile.exists() || !sourceFile.isFile() )
+ return false;
+ InputStream in;
+ try {
+ in = new FileInputStream( sourceFile );
+ } catch ( Exception e ) {
+ e.printStackTrace();
+ return false;
+ }
+ TarArchiveEntry entry = new TarArchiveEntry( inArchiveFileName );
+ long size = FileUtils.sizeOf( sourceFile );
+ entry.setIds( 0, 0 );
+ entry.setMode( mode );
+ entry.setSize( size );
+ try {
+ tar.putArchiveEntry( entry );
+ Util.streamCopy( in, tar, size );
+ tar.closeArchiveEntry();
+ } catch ( IOException e ) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ Util.multiClose( in );
+ }
+ return true;
+ }
+
+ public static boolean tarCreateFileFromString( TarArchiveOutputStream tar, String fileName, String contents, int mode )
+ {
+ return tarCreateFileFromString( tar, fileName, contents.getBytes( StandardCharsets.UTF_8 ), mode );
+ }
+
+ public static boolean tarCreateFileFromString( TarArchiveOutputStream tar, String fileName, byte[] contents, int mode )
+ {
+ TarArchiveEntry entry = new TarArchiveEntry( fileName );
+ entry.setIds( 0, 0 );
+ entry.setMode( mode );
+ entry.setSize( contents.length );
+ try {
+ tar.putArchiveEntry( entry );
+ tar.write( contents );
+ tar.closeArchiveEntry();
+ } catch ( IOException e ) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean tarCreateSymlink( TarArchiveOutputStream tar, String target, String linkName )
+ {
+ TarArchiveEntry entry = new TarArchiveEntry( linkName, TarArchiveEntry.LF_SYMLINK );
+ entry.setIds( 0, 0 );
+ entry.setMode( 0777 );
+ entry.setLinkName( target );
+ try {
+ tar.putArchiveEntry( entry );
+ tar.closeArchiveEntry();
+ } catch ( IOException e ) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/org/openslx/satserver/util/Constants.java b/src/main/java/org/openslx/satserver/util/Constants.java
new file mode 100644
index 0000000..b8394f0
--- /dev/null
+++ b/src/main/java/org/openslx/satserver/util/Constants.java
@@ -0,0 +1,8 @@
+package org.openslx.satserver.util;
+
+public class Constants
+{
+
+ public static final String BASEDIR = System.getProperty( "user.dir" );
+
+}
diff --git a/src/main/java/org/openslx/satserver/util/Util.java b/src/main/java/org/openslx/satserver/util/Util.java
new file mode 100644
index 0000000..a6d354a
--- /dev/null
+++ b/src/main/java/org/openslx/satserver/util/Util.java
@@ -0,0 +1,66 @@
+package org.openslx.satserver.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Util
+{
+
+ /**
+ * Check if the given string starts with any of the additionally passed strings.
+ * Ugly boilerplate.
+ *
+ * @param stringToCheck a string we want to check the starting of
+ * @param compareTo list of strings we compare stringToCheck to
+ * @return true if stringToCheck starts with any of the strings in compareTo
+ */
+ public static boolean startsWith( String stringToCheck, String... compareTo )
+ {
+ for ( String check : compareTo ) {
+ if ( stringToCheck.startsWith( check ) )
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Close all given Closables. Can handle null references.
+ * @param streams one or more closables/streams
+ */
+ public static void multiClose( Closeable... streams )
+ {
+ if ( streams == null )
+ return;
+ for ( Closeable stream : streams ) {
+ if ( stream != null ) {
+ try {
+ stream.close();
+ } catch ( IOException e ) {
+ // Ignore - nothing meaningful to do
+ }
+ }
+ }
+ }
+
+ public static boolean streamCopy( InputStream in, OutputStream out, long bytes )
+ {
+ byte buffer[] = new byte[ 7900 ];
+ while ( bytes > 0 ) {
+ try {
+ int ret = in.read( buffer, 0, (int) ( bytes > buffer.length ? buffer.length : bytes ) );
+ if ( ret == -1 )
+ return false;
+ bytes -= ret;
+ out.write( buffer, 0, ret );
+ } catch ( IOException e ) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/CompileIPxe.java b/src/main/java/org/openslx/taskmanager/tasks/CompileIPxe.java
new file mode 100644
index 0000000..c0534c7
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/CompileIPxe.java
@@ -0,0 +1,83 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.FileUtils;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class CompileIPxe extends AbstractTask
+{
+
+ @Expose
+ private String ip = null;
+ @Expose
+ private String defaultentry = null;
+ @Expose
+ private int timeout = 0;
+ @Expose
+ private String custom = null;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( this.status );
+ if ( this.ip == null ) {
+ status.error = "No IP address set.";
+ return false;
+ }
+ if ( this.defaultentry == null )
+ this.defaultentry = "net";
+ if ( this.custom == null )
+ this.custom = "";
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ // Prepare menu
+ String template;
+ try {
+ template = FileUtils.readFileToString( new File( "./data/pxemenu.template" ), StandardCharsets.UTF_8 );
+ } catch ( IOException e ) {
+ status.error = e.toString();
+ return false;
+ }
+ // Substitutions
+ template = template.replaceAll( "%ip%", this.ip );
+ template = template.replaceAll( "%timeout%", Integer.toString( this.timeout * 10 ) );
+ template = template.replaceAll( "%totaltimeout%", Integer.toString( this.timeout * 40 ) );
+ template = template.replaceAll( "%default%", this.defaultentry );
+ template = template.replaceAll( "%custom%", this.custom );
+ // Default selection net
+ if ( this.defaultentry.equals( "net" ) )
+ template = template.replaceAll( "%default-net%", "MENU DEFAULT" );
+ else
+ template = template.replaceAll( "%default-net%", "" );
+ // Default selection hdd
+ if ( this.defaultentry.equals( "hdd" ) )
+ template = template.replaceAll( "%default-hdd%", "MENU DEFAULT" );
+ else
+ template = template.replaceAll( "%default-hdd%", "" );
+ // Write out
+ try {
+ FileUtils.writeStringToFile( new File( "/srv/openslx/tftp/pxelinux.cfg/default" ), template, StandardCharsets.UTF_8 );
+ } catch ( IOException e ) {
+ status.error = e.toString();
+ return false;
+ }
+ return true;
+ }
+
+ class Output
+ {
+ protected String error = null;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/CreateAdConfig.java b/src/main/java/org/openslx/taskmanager/tasks/CreateAdConfig.java
new file mode 100644
index 0000000..71ef249
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/CreateAdConfig.java
@@ -0,0 +1,161 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.io.FileUtils;
+import org.openslx.satserver.util.Archive;
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class CreateAdConfig extends AbstractTask
+{
+ @Expose
+ private int moduleid = 0;
+ @Expose
+ private String filename = null;
+ @Expose
+ private String server = null;
+ @Expose
+ private String searchbase = null;
+ @Expose
+ private String binddn = null;
+ @Expose
+ private String bindpw = null;
+ @Expose
+ private String proxyip = null;
+ @Expose
+ private int proxyport = 0;
+ @Expose
+ private String home = null;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ // TODO: Check path is allowed
+ this.setStatusObject( this.status );
+ if ( filename == null || server == null || searchbase == null || binddn == null || bindpw == null || proxyip == null || proxyport == 0 || moduleid == 0 ) {
+ status.error = "Missing argument to task";
+ return false;
+ }
+ if ( this.home == null )
+ this.home = "";
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ TarArchiveOutputStream outArchive = null;
+ try {
+ // ldadp config
+ String ldadpConf = String.format(
+ "[%s]\n"
+ + "binddn=%s\n"
+ + "bindpw=%s\n"
+ + "base=%s\n"
+ + "port=%s\n"
+ + "home=%s\n"
+ + "\n",
+ this.server,
+ this.binddn,
+ this.bindpw,
+ this.searchbase,
+ this.proxyport,
+ this.home );
+ String fileName = "/opt/ldadp/configs/" + this.moduleid + ".cfg";
+ try {
+ Files.deleteIfExists( Paths.get( filename ) );
+ } catch ( IOException e1 ) {
+ }
+ try {
+ FileUtils.writeStringToFile( new File( fileName ), ldadpConf, StandardCharsets.UTF_8 );
+ } catch ( IOException e ) {
+ status.error = e.toString();
+ return false;
+ }
+ try {
+ outArchive = Archive.createTarArchive( this.filename );
+ } catch ( IOException e ) {
+ status.error = "Could not create archive at " + this.filename;
+ return false;
+ }
+ // Generic ldap config
+ String ldapConf = String
+ .format(
+ "URI ldap://%s:%d/\n"
+ + "BASE %s\n"
+ + "BIND_TIMELIMIT 10\n"
+ + "TIMELIMIT 30\n"
+ + "nss_base_passwd %s\n"
+ + "nss_base_group %s\n"
+ + "nss_initgroups_ignoreusers avahi,avahi-autoipd,backup,bin,colord,daemon,dnsmasq,games,gnats,hplip,irc,kernoops,libuuid,lightdm,list,lp,mail,man,messagebus,news,proxy,pulse,root,rtkit,saned,speech-dispatcher,sshd,sync,sys,syslog,usbmux,uucp,whoopsie,www-data\n",
+ this.proxyip, this.proxyport,
+ this.searchbase,
+ this.searchbase,
+ this.searchbase
+ );
+ // nslcd config
+ String nslcdConf = String
+ .format(
+ "URI ldap://%s:%d/\n"
+ + "BASE %s\n"
+ + "BIND_TIMELIMIT 10\n"
+ + "TIMELIMIT 30\n"
+ + "scope sub\n"
+ + "nss_initgroups_ignoreusers avahi,avahi-autoipd,backup,bin,colord,daemon,dnsmasq,games,gnats,hplip,irc,kernoops,libuuid,lightdm,list,lp,mail,man,messagebus,news,proxy,pulse,root,rtkit,saned,speech-dispatcher,sshd,sync,sys,syslog,usbmux,uucp,whoopsie,www-data\n",
+ this.proxyip, this.proxyport,
+ this.searchbase,
+ this.searchbase,
+ this.searchbase
+ );
+ // nsswitch.conf with ldap enabled
+ if ( !Archive.tarAddFile( outArchive, "/etc/nsswitch.conf", new File( "./data/ad/nsswitch.conf" ), 0644 ) ) {
+ status.error = "Could not add nsswitch.conf to module";
+ return false;
+ }
+ // All the pam.d common-XXXX files
+ for ( String file : new String[] { "common-auth", "common-account", "common-session", "common-session-noninteractive", "common-password" } ) {
+ if ( !Archive.tarAddFile( outArchive, "/etc/pam.d/" + file, new File( "./data/ad/" + file ), 0644 ) ) {
+ status.error = "Could not add " + file + " to module";
+ return false;
+ }
+ }
+ // Home if present
+ if ( this.home.length() != 0
+ && !Archive.tarAddFile( outArchive, "/opt/openslx/scripts/pam_script_mount_persistent", new File( "./data/ad/mountscript" ), 0644 ) ) {
+ status.error = "Could not add mount script to module";
+ return false;
+ }
+ boolean ret = Archive.tarCreateFileFromString( outArchive, "/etc/ldap.conf", ldapConf, 0644 )
+ && Archive.tarCreateFileFromString( outArchive, "/etc/nslcd.conf", nslcdConf, 0644 )
+ && Archive.tarCreateSymlink( outArchive, "/etc/ldap.conf", "/etc/ldap/ldap.conf" )
+ && Archive.tarCreateSymlink( outArchive, "/etc/ldap.conf", "/etc/openldap/ldap.conf" )
+ && Archive.tarCreateSymlink( outArchive, "../nslcd.service", "/etc/systemd/system/basic.target.wants/nslcd.service" );
+ if ( !ret ) {
+ status.error = "Could not add ldap configs to module";
+ }
+ return ret;
+ } finally {
+ Util.multiClose( outArchive );
+ }
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/DeleteFile.java b/src/main/java/org/openslx/taskmanager/tasks/DeleteFile.java
new file mode 100644
index 0000000..8c0f9a1
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/DeleteFile.java
@@ -0,0 +1,57 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.FilenameUtils;
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class DeleteFile extends AbstractTask
+{
+
+ protected static final String[] ALLOWED_DIRS =
+ { "/tmp/", "/opt/openslx/configs/" };
+
+ @Expose
+ private String file = null;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( status );
+ this.file = FilenameUtils.normalize( this.file );
+ if ( this.file == null || !Util.startsWith( this.file, ALLOWED_DIRS ) ) {
+ status.error = "File not in allowed directory";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ try {
+ Files.deleteIfExists( Paths.get( this.file ) );
+ } catch ( IOException e1 ) {
+ status.error = e1.toString();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/DownloadFile.java b/src/main/java/org/openslx/taskmanager/tasks/DownloadFile.java
new file mode 100644
index 0000000..d901c9f
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/DownloadFile.java
@@ -0,0 +1,91 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class DownloadFile extends AbstractTask
+{
+
+ @Expose
+ private String url = null;
+ @Expose
+ private String destination = null;
+
+ private Output status = new Output();
+
+ private static final String[] ALLOWED_DIRS =
+ { "/srv/openslx/www/boot/" };
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( status );
+ if ( this.url == null ) {
+ status.error = "No URL given.";
+ return false;
+ }
+ this.destination = FilenameUtils.normalize( this.destination );
+ if ( this.destination == null || !Util.startsWith( this.destination, ALLOWED_DIRS ) || this.destination.endsWith( "/" ) ) {
+ status.error = "File not in allowed directory";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ URLConnection connection = null;
+ BufferedInputStream in = null;
+ FileOutputStream fout = null;
+ try {
+ File dest = new File( this.destination );
+ FileUtils.forceMkdir( new File( dest.getParent() ) );
+ FileUtils.deleteQuietly( dest );
+ connection = new URL( this.url ).openConnection();
+ in = new BufferedInputStream( connection.getInputStream() );
+ fout = new FileOutputStream( dest );
+ status.size = connection.getContentLengthLong();
+
+ final byte data[] = new byte[ 9000 ];
+ int count;
+ while ( ( count = in.read( data, 0, data.length ) ) != -1 ) {
+ fout.write( data, 0, count );
+ status.complete += count;
+ if ( status.size > 0 )
+ status.progress = (int) ( 100l * status.complete / status.size );
+ }
+ return true;
+ } catch ( IOException e ) {
+ status.error = "Download error: " + e.toString();
+ FileUtils.deleteQuietly( new File( this.destination ) );
+ return false;
+ } finally {
+ Util.multiClose( in, fout );
+ }
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ protected long size = -1;
+ protected long complete = 0;
+ protected int progress = 0;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/DownloadText.java b/src/main/java/org/openslx/taskmanager/tasks/DownloadText.java
new file mode 100644
index 0000000..11b30cf
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/DownloadText.java
@@ -0,0 +1,82 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class DownloadText extends AbstractTask
+{
+
+ @Expose
+ private String url = null;
+
+ private Output status = new Output();
+
+ private static final long MAX_SIZE = 10000;
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( status );
+ if ( this.url == null ) {
+ status.error = "No URL given.";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ URLConnection connection = null;
+ BufferedInputStream in = null;
+ try {
+ connection = new URL( this.url ).openConnection();
+ in = new BufferedInputStream( connection.getInputStream() );
+ status.size = connection.getContentLength();
+ if ( status.size > MAX_SIZE ) {
+ status.error = "Remote file too large: " + status.size + " bytes!";
+ return false;
+ }
+ StringBuilder sb = new StringBuilder( Math.max( 8, status.size ) );
+
+ final byte data[] = new byte[ 9000 ];
+ int count;
+ while ( ( count = in.read( data, 0, data.length ) ) != -1 ) {
+ sb.append( new String( data, 0, count, StandardCharsets.UTF_8 ) );
+ status.complete += count;
+ if ( status.complete > MAX_SIZE ) {
+ status.error = "Remote file too large: > " + status.size + " bytes!";
+ return false;
+ }
+ }
+ status.content = sb.toString();
+ return true;
+ } catch ( IOException e ) {
+ status.error = "Download error: " + e.toString();
+ return false;
+ } finally {
+ Util.multiClose( in );
+ }
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ protected String content = null;
+ protected int size = -1;
+ protected int complete = 0;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/DummyTask.java b/src/main/java/org/openslx/taskmanager/tasks/DummyTask.java
new file mode 100644
index 0000000..9524f2f
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/DummyTask.java
@@ -0,0 +1,58 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.apache.log4j.Logger;
+import org.openslx.taskmanager.api.AbstractTask;
+
+public class DummyTask extends AbstractTask
+{
+
+ private static final Logger log = Logger.getLogger( DummyTask.class );
+
+ private DummyTaskStatus status = new DummyTaskStatus();
+
+ @Override
+ protected boolean initTask()
+ {
+ log.debug( "Initialized DummyTask with ID " + this.getId() );
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ setStatusObject( status );
+ Process process = null;
+ try {
+ // Create process
+ process = Runtime.getRuntime().exec( "dummy", null, new File("/") );
+
+ // Read its output
+ BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
+ String line;
+ while ( ( line = reader.readLine() ) != null ) {
+ line = reader.readLine();
+ if ( line.matches( "^\\d+%$" ) )
+ this.status.progress = Integer.parseInt( line.substring( 0, line.length() - 1 ) );
+ }
+ } catch ( IOException e ) {
+ log.warn( "Process of task " + this.getId() + " died." );
+ } finally {
+ if ( process != null )
+ process.destroy();
+ }
+ return this.status.progress == 100;
+ }
+
+ class DummyTaskStatus
+ {
+
+ protected int progress = 0;
+
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/LdadpLauncher.java b/src/main/java/org/openslx/taskmanager/tasks/LdadpLauncher.java
new file mode 100644
index 0000000..123ab1a
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/LdadpLauncher.java
@@ -0,0 +1,79 @@
+package org.openslx.taskmanager.tasks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.openslx.satserver.util.Constants;
+import org.openslx.taskmanager.api.SystemCommandTask;
+
+import com.google.gson.annotations.Expose;
+
+public class LdadpLauncher extends SystemCommandTask
+{
+ @Expose
+ private int[] ids = null;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( this.status );
+ if ( ids == null ) {
+ status.addMessage( "No ids passed to task." );
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected String[] initCommandLine()
+ {
+ List<String> args = new ArrayList<>();
+ args.addAll( Arrays.asList( new String[] {
+ "/usr/bin/sudo",
+ "-n",
+ "-u", "ldadp", Constants.BASEDIR + "/scripts/ldadp-launcher",
+ "/opt/ldadp",
+ "--"
+ } ) );
+ for ( int i = 0; i < ids.length; ++i ) {
+ args.add( Integer.toString( this.ids[i] ) );
+ }
+ return args.toArray( new String[ args.size() ] );
+ }
+
+ @Override
+ protected boolean processEnded( int exitCode )
+ {
+ return exitCode == 0;
+ }
+
+ @Override
+ protected void processStdOut( String line )
+ {
+ status.addMessage( line );
+ }
+
+ @Override
+ protected void processStdErr( String line )
+ {
+ status.addMessage( line );
+ }
+
+ class Output
+ {
+ private String messages = null;
+
+ private void addMessage( String str )
+ {
+ if ( messages == null ) {
+ messages = str;
+ } else {
+ messages += "\n" + str;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/LdapSearch.java b/src/main/java/org/openslx/taskmanager/tasks/LdapSearch.java
new file mode 100644
index 0000000..f861b6a
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/LdapSearch.java
@@ -0,0 +1,122 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+
+import org.apache.commons.io.FileUtils;
+import org.openslx.taskmanager.api.SystemCommandTask;
+
+import com.google.gson.annotations.Expose;
+
+public class LdapSearch extends SystemCommandTask
+{
+
+ @Expose
+ private String server = null;
+ @Expose
+ private String searchbase = null;
+ @Expose
+ private String binddn = null;
+ @Expose
+ private String bindpw = null;
+
+ private String fifo = null;
+
+ private volatile int userCount = 0;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( this.status );
+ if ( this.server == null || this.searchbase == null || this.binddn == null ) {
+ status.messages = "Missing parameter";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected String[] initCommandLine()
+ {
+ if ( this.bindpw == null )
+ this.bindpw = "";
+ this.fifo = String.format( "/tmp/bwlp-%s-%s.ldap", System.currentTimeMillis(), new Random().nextInt() );
+ File pwFile = new File( this.fifo );
+ FileUtils.deleteQuietly( pwFile );
+ try {
+ pwFile.createNewFile();
+ pwFile.setReadable( false, false );
+ pwFile.setReadable( true, true );
+ FileUtils.writeStringToFile( pwFile, this.bindpw, StandardCharsets.UTF_8 );
+ } catch ( IOException e ) {
+ FileUtils.deleteQuietly( pwFile );
+ status.messages = e.toString();
+ return null;
+ }
+ status.addMessage( "Trying to find 4 random AD users to verify everything is all right..." );
+
+ return new String[] {
+ "ldapsearch",
+ "-x", // Simple auth
+ "-LLL", // No additional stuff
+ "-y", this.fifo, // Password from file
+ "-H", "ldap://" + this.server + ":3268/", // Host
+ "-b", this.searchbase, // SB
+ "-D", this.binddn, // DN
+ "-l", "4", // Time limit in seconds
+ "-z", "4", // Max number of results
+ "-o", "ldif-wrap=no", // Turn off retarded line wrapping done by ldapsearch
+ "(&(objectClass=user)(objectClass=person)(sAMAccountName=*))",
+ "sAMAccountName" // Only one attribute
+ };
+ }
+
+ @Override
+ protected boolean processEnded( int exitCode )
+ {
+ FileUtils.deleteQuietly( new File( this.fifo ) );
+ if ( exitCode == 4 ) // Means size limit exceeded, ignore
+ exitCode = 0;
+ if ( exitCode != 0 )
+ status.addMessage( "Exit code is " + exitCode );
+ if ( exitCode == 0 && this.userCount < 4 )
+ status.addMessage( "Found less than 4 users. Are you sure you got the right credentials." );
+ return this.userCount >= 4;
+ }
+
+ @Override
+ protected void processStdOut( String line )
+ {
+ if ( line.contains( "sAMAccountName: " ) ) {
+ status.addMessage( "Found AD user " + line.substring( 16 ) + " :-)" );
+ this.userCount++;
+ }
+ }
+
+ @Override
+ protected void processStdErr( String line )
+ {
+ if ( line.contains( "Size limit exceeded" ) )
+ return;
+ status.addMessage( "Error: " + line );
+ }
+
+ class Output
+ {
+ private String messages = null;
+
+ private void addMessage( String str )
+ {
+ if ( messages == null ) {
+ messages = str;
+ } else {
+ messages += "\n" + str;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/LinkConfigTgz.java b/src/main/java/org/openslx/taskmanager/tasks/LinkConfigTgz.java
new file mode 100644
index 0000000..686cb9b
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/LinkConfigTgz.java
@@ -0,0 +1,61 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.FilenameUtils;
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class LinkConfigTgz extends AbstractTask
+{
+
+ protected static final String[] ALLOWED_DIRS =
+ { "/tmp/", "/opt/openslx/configs/" };
+
+ @Expose
+ private String destination = null;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( status );
+ this.destination = FilenameUtils.normalize( this.destination );
+ if ( this.destination == null || !Util.startsWith( this.destination, ALLOWED_DIRS ) ) {
+ status.error = "File not in allowed directory";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ try {
+ Files.deleteIfExists( Paths.get( "/srv/openslx/www/boot/default/config.tgz" ) );
+ } catch ( IOException e1 ) {
+ }
+ try {
+ Files.createSymbolicLink( Paths.get( "/srv/openslx/www/boot/default/config.tgz" ), Paths.get( this.destination ) );
+ } catch ( IOException e ) {
+ status.error = e.toString();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/ListArchive.java b/src/main/java/org/openslx/taskmanager/tasks/ListArchive.java
new file mode 100644
index 0000000..33bb83a
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/ListArchive.java
@@ -0,0 +1,116 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.io.FilenameUtils;
+import org.openslx.satserver.util.Archive;
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class ListArchive extends AbstractTask
+{
+
+ @Expose
+ private String file;
+
+ /*
+ * Own vars/constants not being deserialized
+ */
+
+ protected static final String[] ALLOWED_DIRS =
+ { "/tmp/", "/opt/openslx/" };
+
+ private Output status = new Output();
+
+ /*
+ * Code
+ */
+
+ @Override
+ protected boolean execute()
+ {
+ ArchiveInputStream archive;
+ try {
+ archive = Archive.getArchiveInputStream( this.file );
+ } catch ( FileNotFoundException e1 ) {
+ status.error = e1.getMessage();
+ status.errorCode = Output.ErrorCode.NOT_FOUND;
+ return false;
+ } catch ( IllegalArgumentException e1 ) {
+ status.error = e1.getMessage();
+ status.errorCode = Output.ErrorCode.UNKNOWN_ERROR;
+ return false;
+ } catch ( ArchiveException e1 ) {
+ status.error = e1.getMessage();
+ status.errorCode = Output.ErrorCode.UNKNOWN_FORMAT;
+ return false;
+ }
+
+ status.entries = new ArrayList<>();
+
+ ArchiveEntry archiveEntry;
+ try {
+ // Iterate over every entry
+ while ( ( archiveEntry = archive.getNextEntry() ) != null ) {
+ if ( !archive.canReadEntryData( archiveEntry ) )
+ continue;
+ Output.Entry entry = new Output.Entry();
+ entry.name = archiveEntry.getName();
+ entry.isdir = archiveEntry.isDirectory();
+ entry.size = archiveEntry.getSize();
+ status.entries.add( entry );
+ }
+ } catch ( IOException e ) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( this.status );
+ if ( this.file == null || this.file.length() == 0 || this.file.charAt( 0 ) != '/' ) {
+ status.error = "Need absolute path.";
+ status.errorCode = Output.ErrorCode.INVALID_DIRECTORY;
+ return false;
+ }
+ this.file = FilenameUtils.normalize( this.file );
+ if ( this.file == null || !Util.startsWith( this.file, ALLOWED_DIRS ) ) {
+ status.error = "File not in allowed directory";
+ status.errorCode = Output.ErrorCode.INVALID_DIRECTORY;
+ return false;
+ }
+ return true;
+ }
+
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+
+ protected enum ErrorCode
+ {
+ NOT_FOUND, UNKNOWN_FORMAT, UNKNOWN_ERROR, INVALID_DIRECTORY
+ };
+
+ protected ErrorCode errorCode = null;
+ protected List<Entry> entries = null;
+
+ public static class Entry
+ {
+ protected String name = null;
+ protected boolean isdir = false;
+ protected long size = -1;
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/LocalAddressesList.java b/src/main/java/org/openslx/taskmanager/tasks/LocalAddressesList.java
new file mode 100644
index 0000000..2bfb20e
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/LocalAddressesList.java
@@ -0,0 +1,76 @@
+package org.openslx.taskmanager.tasks;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.openslx.taskmanager.api.AbstractTask;
+
+public class LocalAddressesList extends AbstractTask
+{
+ private static final Logger LOG = Logger.getLogger( LocalAddressesList.class );
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ return true;
+ }
+
+ @Override
+ protected boolean execute()
+ {
+ this.setStatusObject( status );
+ List<Output.Entry> list = new ArrayList<>();
+ try {
+ for ( Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+ NetworkInterface intf = en.nextElement();
+ for ( Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+ InetAddress addr = enumIpAddr.nextElement();
+ Output.Entry entry = new Output.Entry();
+ entry.iface = intf.getName();
+ entry.ip = addr.getHostAddress();
+ if ( addr instanceof Inet4Address ) {
+ entry.type = "ipv4";
+ } else if ( addr instanceof Inet6Address ) {
+ entry.type = "ipv6";
+ } else {
+ entry.type = addr.getClass().getSimpleName();
+ }
+ list.add( entry );
+ }
+ }
+ } catch ( SocketException e ) {
+ LOG.info( " (error retrieving network interface list)" );
+ this.status.error = "Error retrieving network interface list (" + e.getMessage() + ")";
+ return false;
+ }
+ this.status.addresses = list;
+ return true;
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ protected List<Entry> addresses = null;
+
+ protected static class Entry
+ {
+ protected String ip = null;
+ protected String iface = null;
+ protected String type = null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/LsTask.java b/src/main/java/org/openslx/taskmanager/tasks/LsTask.java
new file mode 100644
index 0000000..2e01167
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/LsTask.java
@@ -0,0 +1,80 @@
+package org.openslx.taskmanager.tasks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openslx.taskmanager.api.SystemCommandTask;
+
+import com.google.gson.annotations.Expose;
+
+public class LsTask extends SystemCommandTask
+{
+
+ /*
+ * Serialize
+ */
+ @Expose
+ private String directory = null;
+
+ /*
+ * Private, no serialization
+ */
+ private Output status = new Output();
+ private List<String> files = new ArrayList<>();
+
+ @Override
+ protected boolean initTask()
+ {
+ if ( directory == null ) {
+ status.error ="Missing directory argument";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected String[] initCommandLine()
+ {
+ return new String[] { "/bin/ls", directory };
+ }
+
+ @Override
+ protected void processStarted()
+ {
+ setStatusObject( status );
+ }
+
+ @Override
+ protected boolean processEnded( int exitCode )
+ {
+ if ( exitCode != 0 ) {
+ status.files = null;
+ return false;
+ }
+ status.files = this.files;
+ return true;
+ }
+
+ @Override
+ protected void processStdOut( String line )
+ {
+ this.files.add( line );
+ }
+
+ @Override
+ protected void processStdErr( String line )
+ {
+ status.error = line;
+ }
+
+ /**
+ * Output - contains additional status data of this task
+ */
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+ protected List<String> files = null;
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/MountVmStore.java b/src/main/java/org/openslx/taskmanager/tasks/MountVmStore.java
new file mode 100644
index 0000000..a77387d
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/MountVmStore.java
@@ -0,0 +1,84 @@
+package org.openslx.taskmanager.tasks;
+
+import org.openslx.satserver.util.Constants;
+import org.openslx.taskmanager.api.SystemCommandTask;
+
+import com.google.gson.annotations.Expose;
+
+public class MountVmStore extends SystemCommandTask
+{
+ @Expose
+ private String address = null;
+ @Expose
+ private String type = null;
+ @Expose
+ private String username = null;
+ @Expose
+ private String password = null;
+
+ private Output status = new Output();
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( this.status );
+ if ( this.address == null || this.type == null ) {
+ status.addMessage( "Address or type not given." );
+ return false;
+ }
+ if ( this.username == null )
+ this.username = "";
+ if ( this.password == null )
+ this.password = "";
+ return true;
+ }
+
+ @Override
+ protected String[] initCommandLine()
+ {
+ return new String[] {
+ "/usr/bin/sudo",
+ "-n",
+ "-u", "root", Constants.BASEDIR + "/scripts/mount-store",
+ "images",
+ this.address,
+ this.username,
+ this.password
+ };
+ }
+
+ @Override
+ protected boolean processEnded( int exitCode )
+ {
+ if ( exitCode != 0 )
+ status.addMessage( "Failed with exit code " + exitCode );
+ return exitCode == 0;
+ }
+
+ @Override
+ protected void processStdOut( String line )
+ {
+ status.addMessage( line );
+ }
+
+ @Override
+ protected void processStdErr( String line )
+ {
+ status.addMessage( line );
+ }
+
+ class Output
+ {
+ private String messages = null;
+
+ private void addMessage( String str )
+ {
+ if ( messages == null ) {
+ messages = str;
+ } else {
+ messages += "\n" + str;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/taskmanager/tasks/RecompressArchive.java b/src/main/java/org/openslx/taskmanager/tasks/RecompressArchive.java
new file mode 100644
index 0000000..d428f03
--- /dev/null
+++ b/src/main/java/org/openslx/taskmanager/tasks/RecompressArchive.java
@@ -0,0 +1,193 @@
+package org.openslx.taskmanager.tasks;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.openslx.satserver.util.Archive;
+import org.openslx.satserver.util.Util;
+import org.openslx.taskmanager.api.AbstractTask;
+
+import com.google.gson.annotations.Expose;
+
+public class RecompressArchive extends AbstractTask
+{
+
+ @Expose
+ private String[] inputFiles;
+
+ @Expose
+ private String outputFile;
+
+ /*
+ * Own vars/constants not being deserialized
+ */
+
+ protected static final String[] ALLOWED_DIRS =
+ { "/tmp/", "/opt/openslx/configs/" };
+
+ private Output status = new Output();
+
+ /*
+ * Code
+ */
+
+ @Override
+ protected boolean execute()
+ {
+ if ( execute2() ) {
+ return true;
+ }
+ FileUtils.deleteQuietly( new File( this.outputFile ) );
+ return false;
+ }
+
+ private boolean execute2()
+ {
+ // Open output file archive
+ TarArchiveOutputStream outArchive = null;
+ try {
+ try {
+ FileUtils.forceMkdir( new File( new File( this.outputFile ).getParent() ) );
+ outArchive = Archive.createTarArchive( this.outputFile );
+ } catch ( IOException e2 ) {
+ status.error = e2.getMessage();
+ status.errorCode = Output.ErrorCode.WRITE_FAILED;
+ return false;
+ }
+
+ // Open input file archives, one by one
+ for ( final String inputFile : this.inputFiles ) {
+ ArchiveInputStream inArchive = null;
+ try {
+ try {
+ inArchive = Archive.getArchiveInputStream( inputFile );
+ } catch ( FileNotFoundException e1 ) {
+ status.error = "(" + inputFile + ") " + e1.getMessage();
+ status.errorCode = Output.ErrorCode.NOT_FOUND;
+ return false;
+ } catch ( IllegalArgumentException e1 ) {
+ status.error = "(" + inputFile + ") " + e1.getMessage();
+ status.errorCode = Output.ErrorCode.UNKNOWN_ERROR;
+ return false;
+ } catch ( ArchiveException e1 ) {
+ status.error = "(" + inputFile + ") " + e1.getMessage();
+ status.errorCode = Output.ErrorCode.UNKNOWN_FORMAT;
+ return false;
+ }
+ // It's open
+ ArchiveEntry inEntry;
+
+ // Remember all files added, so we can issue warnings for duplicate entries
+ Set<String> entries = new HashSet<>();
+ // Iterate over every entry
+ while ( ( inEntry = inArchive.getNextEntry() ) != null ) {
+ if ( !inArchive.canReadEntryData( inEntry ) )
+ continue; // Skip unreadable entries
+ // Construct TarArchiveEntry - we want unix stuff like uid/gid, links, file/dir mode, so try to get from source archive
+ TarArchiveEntry outEntry = Archive.createTarArchiveEntry( inEntry, 0, 0, 0755, 0644 );
+ // Ask lib if the entry can be written
+ if ( !outArchive.canWriteEntryData( outEntry ) ) {
+ status.warnings.add( "Commons-compress says entry '" + outEntry.getName() + "' cannot be written." );
+ continue;
+ }
+ // Dupcheck
+ if ( entries.contains( outEntry.getName() ) ) {
+ status.warnings.add( "Duplicate entry: " + outEntry.getName() );
+ } else {
+ entries.add( outEntry.getName() );
+ }
+ // Actually add entry now
+ outArchive.putArchiveEntry( outEntry );
+ if ( !Util.streamCopy( inArchive, outArchive, outEntry.getSize() ) ) {
+ status.error = "(" + inputFile + ") Could not copy entry '" + inEntry.getName() + "' to destination archive.";
+ status.errorCode = Output.ErrorCode.WRITE_FAILED;
+ return false;
+ }
+ outArchive.closeArchiveEntry();
+ } // End entry
+ } catch ( IOException e ) {
+ return false;
+ } finally {
+ Util.multiClose( inArchive );
+ }
+ } // End of loop over input archives
+
+ return true;
+ } finally {
+ Util.multiClose( outArchive );
+ }
+ }
+
+ @Override
+ protected boolean initTask()
+ {
+ this.setStatusObject( this.status );
+ // Input files
+ if ( this.inputFiles == null ) {
+ status.error = "Input: No archives given";
+ status.errorCode = Output.ErrorCode.UNKNOWN_ERROR;
+ return false;
+ }
+ for ( int i = 0; i < this.inputFiles.length; ++i ) {
+ String inputFile = this.inputFiles[i];
+ if ( inputFile == null || inputFile.length() == 0 || inputFile.charAt( 0 ) != '/' ) {
+ status.error = "Input: Need absolute path (got: " + inputFile + ")";
+ status.errorCode = Output.ErrorCode.INVALID_DIRECTORY;
+ return false;
+ }
+ inputFile = FilenameUtils.normalize( inputFile );
+ if ( inputFile == null || !Util.startsWith( inputFile, ALLOWED_DIRS ) ) {
+ status.error = "Input: File not in allowed directory";
+ status.errorCode = Output.ErrorCode.INVALID_DIRECTORY;
+ return false;
+ }
+ this.inputFiles[i] = inputFile;
+ }
+ // Output file
+ if ( this.outputFile == null || this.outputFile.length() == 0 || this.outputFile.charAt( 0 ) != '/' ) {
+ status.error = "Output: Need absolute path.";
+ status.errorCode = Output.ErrorCode.INVALID_DIRECTORY;
+ return false;
+ }
+ this.outputFile = FilenameUtils.normalize( this.outputFile );
+ if ( !Util.startsWith( this.outputFile, ALLOWED_DIRS ) ) {
+ status.error = "Output: File not in allowed directory";
+ status.errorCode = Output.ErrorCode.INVALID_DIRECTORY;
+ return false;
+ }
+ return true;
+ }
+
+ @SuppressWarnings( "unused" )
+ private static class Output
+ {
+ protected String error = null;
+
+ protected enum ErrorCode
+ {
+ NOT_FOUND, UNKNOWN_FORMAT, UNKNOWN_ERROR, INVALID_DIRECTORY, WRITE_FAILED
+ };
+
+ protected ErrorCode errorCode = null;
+ protected List<String> warnings = null;
+
+ public static class Entry
+ {
+ protected String name = null;
+ protected boolean isdir = false;
+ protected long size = -1;
+ }
+ }
+
+}