package org.openslx.taskmanager.tasks;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;
import org.openslx.satserver.util.Archive;
import org.openslx.satserver.util.Constants;
import org.openslx.satserver.util.Exec;
import org.openslx.satserver.util.Exec.ExecCallback;
import org.openslx.satserver.util.LdapMapping;
import org.openslx.satserver.util.Template;
import org.openslx.satserver.util.Util;
import org.openslx.taskmanager.api.AbstractTask;
import com.google.gson.annotations.Expose;
public class CreateLdapConfig extends AbstractTask
{
private static final Logger LOGGER = Logger.getLogger( CreateLdapConfig.class );
public static final String DEFAULT_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
protected static final String[] ALLOWED_DIRS =
{ "/tmp/", "/opt/openslx/configs/" };
@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 int adport = 0;
@Expose
private String home = null;
@Expose
private String homeattr = null;
@Expose
private String fingerprint;
@Expose
private String certificate;
@Expose
private boolean plainldap = false;
@Expose
private String fixnumeric = null;
@Expose
private LdapMapping mapping;
// Share mode stuff
@Expose
private int shareRemapMode;
@Expose
private int shareRemapCreate;
@Expose
private String shareHomeDrive;
@Expose
private int shareDocuments;
@Expose
private int shareDownloads;
@Expose
private int shareDesktop;
@Expose
private int shareMedia;
@Expose
private int shareOther;
@Expose
private List<Share> shares;
@Expose
private String shareDomain;
@Expose
private int credentialPassthrough;
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 || proxyip == null || proxyport == 0 || moduleid == 0 ) {
status.error = "Missing argument to task";
return false;
}
filename = FilenameUtils.normalize( filename );
if ( !Util.startsWith( filename, ALLOWED_DIRS ) ) {
status.error = "Illegal target directory " + filename;
return false;
}
for ( Field field : CreateLdapConfig.class.getDeclaredFields() ) {
if ( field.isAnnotationPresent( Expose.class ) && field.getType().equals( String.class ) ) {
field.setAccessible( true );
Object ret;
try {
ret = field.get( this );
} catch ( IllegalArgumentException | IllegalAccessException e1 ) {
ret = null;
LOGGER.warn( "Cannot get field " + field.getName() );
}
if ( ret == null ) {
try {
field.set( this, "" );
} catch ( IllegalArgumentException | IllegalAccessException e ) {
LOGGER.warn( "Cannot set field " + field.getName() );
}
}
}
}
if ( mapping == null ) {
mapping = new LdapMapping();
}
if ( Util.isEmpty( mapping.homemount ) && !Util.isEmpty( this.homeattr ) ) {
mapping.homemount = this.homeattr;
}
return true;
}
@Override
protected boolean execute()
{
TarArchiveOutputStream outArchive = null;
File keyFile = new File( "/opt/ldadp/configs/" + this.moduleid + ".key.pem" );
File certFile = new File( "/opt/ldadp/configs/" + this.moduleid + ".crt.pem" );
File caFile = new File( "/opt/ldadp/configs/" + this.moduleid + ".ca-bundle.pem" );
String uri = "ldaps://" + this.proxyip + ":" + this.proxyport + "/";
String cacertPath = "/etc/ldap-proxy.pem";
String caPath = "";
final String subject = "/C=DE/ST=Nowhere/L=Springfield/O=Dis/CN=" + this.proxyip;
try {
// If cert already exists, check if the subject (most importantly the CN) matches the desired one
if ( certFile.exists() ) {
final AtomicBoolean subjectStillGood = new AtomicBoolean( false );
Exec.sync( 4, new ExecCallback() {
@Override
public void processStdOut( String line )
{
if ( line.trim().endsWith( subject ) ) {
subjectStillGood.set( true );
}
}
@Override
public void processStdErr( String line )
{
}
}, "openssl", "x509", "-noout", "-in", certFile.getAbsolutePath(), "-subject" );
if ( !subjectStillGood.get() ) {
certFile.delete();
keyFile.delete();
}
}
// Generate keys if not existent
if ( !keyFile.exists() || !certFile.exists() ) {
int ret = Exec.sync( 20, "openssl", "req",
"-x509", "-new", "-newkey", "rsa:4096", "-keyout", keyFile.getAbsolutePath(), "-out", certFile.getAbsolutePath(),
"-days", "5000", "-nodes", "-subj", subject );
if ( ret == -1 ) {
status.error = "openssl process didn't finish in time.";
} else if ( ret == -2 ) {
status.error = "Internal error generating certificate.";
} else if ( ret != 0 ) {
status.error = "openssl exited with code " + ret;
}
if ( ret != 0 )
return false;
}
// Handle ca-bundle; write to file if custom one is passed
if ( this.fingerprint.length() > 20 && this.server.matches( "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$" ) ) {
// IP address seems to be given - resort to fingerprint if the certificate doesn't cover
// it.
} else if ( this.certificate.equals( "default" ) ) {
caPath = DEFAULT_CA_BUNDLE;
this.fingerprint = "";
} else if ( !this.certificate.isEmpty() && !this.certificate.equals( "false" ) ) {
// Write out
try {
FileUtils.writeStringToFile( caFile, this.certificate, StandardCharsets.UTF_8 );
} catch ( Exception e ) {
status.error = "Could not write trusted certificate(s) to file " + caFile.getAbsolutePath();
return false;
}
caPath = caFile.getAbsolutePath();
this.fingerprint = "";
}
// ldadp config
String ldadpConf = String.format(
"[%s]\n"
+ "binddn=%s\n"
+ "bindpw=%s\n"
+ "base=%s\n"
+ "home=%s\n"
+ "port=%s\n"
+ "fingerprint=%s\n"
+ "cabundle=%s\n"
+ "plainldap=%s\n"
+ "fixnumeric=%s\n"
+ "%s\n"
+ "[local]\n"
+ "port=%s\n"
+ "cert=%s\n"
+ "privkey=%s\n"
+ "\n",
this.server,
this.binddn,
this.bindpw,
this.searchbase,
this.home,
this.adport,
this.fingerprint,
caPath,
Boolean.toString( this.plainldap ),
this.fixnumeric == null ? "" : this.fixnumeric,
this.mapping.toString(),
this.proxyport,
certFile,
keyFile );
// Generic ldap config
final Template ldapConf = new Template( "./data/ad/ldap.conf.template" );
ldapConf.replace( "%URI%", uri );
ldapConf.replace( "%SEARCHBASE%", this.searchbase );
ldapConf.replace( "%CACERT%", cacertPath );
// sssd config
final Template sssdConf = new Template( "./data/ad/sssd.conf.template" );
sssdConf.replace( "%URI%", uri );
sssdConf.replace( "%SEARCHBASE%", this.searchbase );
sssdConf.replace( "%CACERT%", cacertPath );
// Sharemode config
String shareConf = String.format(
"SHARE_REMAP_MODE=%d\n"
+ "SHARE_CREATE_MISSING_REMAP=%d\n"
+ "SHARE_HOME_DRIVE='%s'\n"
+ "SHARE_DOCUMENTS=%d\n"
+ "SHARE_DOWNLOADS=%d\n"
+ "SHARE_DESKTOP=%d\n"
+ "SHARE_MEDIA=%d\n"
+ "SHARE_OTHER=%d\n"
+ "SHARE_DOMAIN='%s'\n"
+ "SHARE_CREDENTIAL_PASSTHROUGH=%d\n",
this.shareRemapMode,
this.shareRemapCreate,
escapeBashString( this.shareHomeDrive ),
this.shareDocuments,
this.shareDownloads,
this.shareDesktop,
this.shareMedia,
this.shareOther,
escapeBashString( this.shareDomain ),
this.credentialPassthrough
);
if ( this.shares != null && !this.shares.isEmpty() ) {
int i = 0;
for ( Share s : this.shares ) {
shareConf += String.format( "SHARE_LINE_%d='%s\t%s\t%s\t%s\t%s'\n",
++i, escapeBashString( s.share ), escapeBashString( s.letter ), escapeBashString( s.shortcut ),
escapeBashString( s.user ), escapeBashString( s.pass ) );
}
}
// Build tar/config
String ldadpConfigPath = "/opt/ldadp/configs/" + this.moduleid + ".cfg";
try {
Files.deleteIfExists( Paths.get( this.filename ) );
} catch ( IOException e1 ) {
}
try {
FileUtils.writeStringToFile( new File( ldadpConfigPath ), ldadpConf, StandardCharsets.UTF_8 );
if ( 0 != Exec.sync( 10,
"/usr/bin/sudo",
"-n",
"-u", "root",
Constants.BASEDIR + "/scripts/ldadp-setperms",
Integer.toString( this.moduleid ) ) )
status.error = "Warning: Could not chown/chmod ldadp config!";
} 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;
}
// The cert we just created
if ( !Archive.tarAddFile( outArchive, cacertPath, certFile, 0644 ) ) {
status.error = "Could not add ldap-proxy.pem to module";
return false;
}
// 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 ( !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.toString(), 0644 )
&& Archive.tarCreateFileFromString( outArchive, "/etc/sssd/sssd.conf", sssdConf.toString(), 0600 )
&& Archive.tarCreateFileFromString( outArchive, "/opt/openslx/inc/shares", shareConf, 0644 )
&& Archive.tarCreateSymlink( outArchive, "/etc/ldap.conf", "/etc/ldap/ldap.conf" )
&& Archive.tarCreateSymlink( outArchive, "/etc/ldap.conf", "/etc/openldap/ldap.conf" )
&& Archive.tarCreateSymlink( outArchive, "../sssd.service", "/etc/systemd/system/basic.target.wants/sssd.service" );
if ( !ret ) {
status.error = "Could not add ldap configs to module";
}
return ret;
} catch ( IOException e ) {
status.error = e.toString();
return false;
} finally {
Util.multiClose( outArchive );
}
}
private String escapeBashString( String str )
{
if ( str == null )
return "";
if ( str.indexOf( '\'' ) != -1 ) {
str = str.replace( "'", "'\"'\"'" );
}
if (str.indexOf( '\t' ) != -1) {
str = str.replace( "\t", " " );
}
return str;
}
/**
* Output - contains additional status data of this task
*/
@SuppressWarnings( "unused" )
private static class Output
{
protected String error = null;
}
private static class Share
{
protected String share;
protected String letter;
protected String shortcut;
protected String user;
protected String pass;
}
}