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.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 int nohomewarn = 0;
@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 boolean genuid = false;
@Expose
private LdapMapping mapping;
@Expose
private String ldapAttrMountOpts;
// 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 String shareHomeMountOpts;
@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;
}
if ( Util.isEmpty( mapping.homemount ) && !"homedirectory".equalsIgnoreCase( mapping.localhome ) ) {
mapping.homemount = "homeDirectory";
}
if (!this.plainldap) {
mapping.uidnumber = null;
mapping.localhome = null;
}
return true;
}
@Override
protected boolean execute()
{
TarArchiveOutputStream outArchive = null;
final File keyFile = new File( "/opt/ldadp/configs/" + this.moduleid + ".key.pem" );
final File certFile = new File( "/opt/ldadp/configs/" + this.moduleid + ".crt.pem" );
final File caFile = new File( "/opt/ldadp/configs/" + this.moduleid + ".ca-bundle.pem" );
final String uri = "ldaps://" + this.proxyip + ":" + this.proxyport + "/";
final String clientCacertPath = "/etc/ldap/proxy-" + this.moduleid + ".pem";
final String subject = "/C=DE/ST=Nowhere/L=Springfield/O=Dis/CN=" + this.proxyip;
String caPath = "";
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.matches( "subject\\s*=.*CN\\s*=\\s*" + proxyip + "($|\\s)" ) ) {
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"
+ "genuidnumber=%s\n"
+ "uidmapstore=/opt/ldadp/pid/map-%d.bin\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,
Boolean.toString( this.genuid ),
this.moduleid,
this.mapping.toString(),
this.proxyport,
certFile,
keyFile );
// Generic ldap config
StringBuilder ldapConf = new StringBuilder();
addConfLine( ldapConf, "LDAP_URI", uri );
addConfLine( ldapConf, "LDAP_BASE", this.searchbase );
addConfLine( ldapConf, "LDAP_CACERT", clientCacertPath );
addConfLine( ldapConf, "LDAP_ATTR_MOUNT_OPTS", this.ldapAttrMountOpts );
// Sharemode config
addConfLine( ldapConf, "SHARE_HOME_MOUNT_OPTS", this.shareHomeMountOpts );
addConfLine( ldapConf, "SHARE_REMAP_MODE", this.shareRemapMode );
addConfLine( ldapConf, "SHARE_CREATE_MISSING_REMAP", this.shareRemapCreate );
addConfLine( ldapConf, "SHARE_HOME_DRIVE", this.shareHomeDrive );
addConfLine( ldapConf, "SHARE_DOCUMENTS", this.shareDocuments );
addConfLine( ldapConf, "SHARE_DOWNLOADS", this.shareDownloads );
addConfLine( ldapConf, "SHARE_DESKTOP", this.shareDesktop );
addConfLine( ldapConf, "SHARE_MEDIA", this.shareMedia );
addConfLine( ldapConf, "SHARE_OTHER", this.shareOther );
addConfLine( ldapConf, "SHARE_DOMAIN", this.shareDomain );
addConfLine( ldapConf, "SHARE_CREDENTIAL_PASSTHROUGH", this.credentialPassthrough );
addConfLine( ldapConf, "SHARE_NO_HOME_WARN", this.nohomewarn );
if ( this.shares != null && !this.shares.isEmpty() ) {
int i = 0;
for ( Share s : this.shares ) {
++i;
addConfLine( ldapConf, "SHARE_LINE_" + i,
String.format( "%s\t%s\t%s\t%s\t%s", s.share, s.letter, s.shortcut, s.user, 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, clientCacertPath, certFile, 0644 ) ) {
status.error = "Could not add ldap-proxy.pem to module";
return false;
}
boolean ret = Archive.tarCreateFileFromString( outArchive, "/opt/openslx/pam/slx-ldap.d/conf-" + this.moduleid,
ldapConf.toString(), 0644 );
if ( !ret ) {
status.error = "Could not add ldap configs to module";
}
return ret;
} finally {
Util.multiClose( outArchive );
}
}
private void addConfLine( StringBuilder sb, String varName, int value )
{
addConfLine( sb, varName, Integer.toString( value ) );
}
private void addConfLine( StringBuilder sb, String varName, String value )
{
sb.append( varName );
sb.append( "='" );
sb.append( escapeBashString( value ) );
sb.append( "'\n" );
}
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;
}
}