diff options
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% + @@ -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; + } + } + +} |