summaryrefslogblamecommitdiffstats
path: root/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java
blob: 3e91cfc5fc04d1a8b9081217acb4e03b536f1be5 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                 
                                    
                           


                                         
                             
                           
                      
                     




                                                       

                                                          
                               


                                            
                                                       
                                                                  
                                                                 
                                                
                                             

                                                  
                                                        
                                                  
                             
                             
                                                                 


                                              



                                          
                                                                               


                                                                                                                        
 

                                                                     
                                    
                                         
                                             

         


                                                                                

                                                             


                                                               

                                                         



                                                          








                                                    


                                                     
 



                                                       







                                                         
                                                                    




                                                                   
                                    









                                                                                                      
                         
                                                         
                                                     
                                                                         

                                                                       

                                                                              

                                          
                 



                                                                                   





                                                                                

                                                                                   


                                  
 


                                                                                                              
         














                                                                                                                               
 





                                                                            

                                                                    
                     















                                                                                                                              
                                                                                                    
                                                                                                                  







                                                                                                                                 











                                                                                              

                                                     

                                                                                               
         
        
                                                                 

                                                       
                     
                                                                                     

                                                     

                                                

                                                       








                                                                        
                 
                                     








                                                                                                        
 
                                                                



                                                                        



                                                       
                                                
                                                            

                                                             
                                                                            
                                                                                                               
                                 

                                                                                                     
                                                                  



                                                                                        



                                                
                                     

         





                                                                   
                                                                                          
                                                           
                                                            
 
                                                                                        

                                                                         
                                                                                                      
                                                                              

         


                                                              
                                                      

                                                                                                     

         


                                                      



                                                                                                                    


                                                    





                                                                                                            
 
package org.openslx.bwlp.sat.web;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.log4j.Logger;
import org.kamranzafar.jtar.TarEntry;
import org.kamranzafar.jtar.TarHeader;
import org.kamranzafar.jtar.TarOutputStream;
import org.openslx.bwlp.sat.database.mappers.DbLecture;
import org.openslx.bwlp.sat.database.mappers.DbLecture.LaunchData;
import org.openslx.bwlp.sat.database.mappers.DbLecture.RunScript;
import org.openslx.bwlp.sat.fileserv.FileServer;
import org.openslx.bwlp.thrift.iface.NetRule;
import org.openslx.bwlp.thrift.iface.NetShare;
import org.openslx.bwlp.thrift.iface.NetShareAuth;
import org.openslx.bwlp.thrift.iface.TNotFoundException;
import org.openslx.util.GrowingThreadPoolExecutor;
import org.openslx.util.Json;
import org.openslx.util.Util;
import org.openslx.util.vm.UnsupportedVirtualizerFormatException;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;

import fi.iki.elonen.NanoHTTPD;

public class WebServer extends NanoHTTPD {

	private static final Logger LOGGER = Logger.getLogger(WebServer.class);
	
	private static final ThreadPoolExecutor tpe = 
			new GrowingThreadPoolExecutor(1, 8, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(16));

	private static final Serializer serializer = new Persister();

	public WebServer(int port) {
		super("127.0.0.1", port);
		super.maxRequestSize = 65535;
	}

	/**
	 * Extract request source ip address. Honors the x-forwarded-for header.
	 * 
	 * @param headers
	 *            map of headers as supplied by nanohttpd
	 * @return IP address, or empty string if unknown
	 */
	private String extractIp(Map<String, String> headers) {
		if (headers == null || headers.isEmpty())
			return "";
		String ip;
		ip = headers.get("remote-addr");
		if (ip != null && !ip.equals("127.0.0.1"))
			return ip;
		ip = headers.get("x-forwarded-for");
		if (ip == null || ip.isEmpty())
			return "";
		final int i = ip.lastIndexOf(',');
		if (i == -1)
			return ip.trim();
		return ip.substring(i + 1).trim();
	}

	@Override
	public Response serve(IHTTPSession session) {
		String uri = session.getUri();

		if (uri == null || uri.length() == 0) {
			return internalServerError();
		}

		// Sanitize
		if (uri.contains("//")) {
			uri = uri.replaceAll("//+", "/");
		}

		try {
			return handle(session, uri);
		} catch (Throwable t) {
			LOGGER.debug("Could not handle request", t);
			return internalServerError();
		}
	}

	private Response handle(IHTTPSession session, String uri) {
		// Our special stuff
		String[] parts = uri.replaceFirst("^/+", "").split("/+");
		// /vmchooser/*
		if (parts.length > 1 && parts[0].equals("vmchooser")) {
			if (parts[1].equals("list")) {
				try {
					return serveVmChooserList(session.getParms());
				} catch (Exception e) {
					LOGGER.debug("problem while retrieving the vmChooserList", e);
					return internalServerError();
				}
			}
			if (parts[1].equals("lecture")) {
				if (parts.length < 4)
					return badRequest("Bad Request");
				if (parts[3].equals("metadata"))
					return serveMetaData(parts[2]);
				if (parts[3].equals("netrules"))
					return serveLectureNetRules(parts[2]);
			}
			return notFound();
		}
		if (uri.startsWith("/status/fileserver")) {
			return serveStatus();
		}
		if (session.getMethod() == Method.POST && uri.startsWith("/do/")) {
			try {
				session.parseBody(null);
			} catch (IOException | ResponseException e) {
				LOGGER.debug("could not parse request body", e);
				return internalServerError();
			}
			return WebRpc.handle(uri.substring(4), session.getParms());
		}

		return notFound();
	}

	private Response serveStatus() {
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/json; charset=utf-8",
				Json.serialize(FileServer.instance().getStatus()));
	}
	
	private static void tarPutFile(TarOutputStream output, String fileName, String data) throws IOException
	{
		if (data == null)
			return;
		tarPutFile(output, fileName, data.getBytes(StandardCharsets.UTF_8));
	}
	
	private static void tarPutFile(TarOutputStream output, String fileName, byte[] data) throws IOException
	{
		if (data == null)
			return;
		output.putNextEntry(new TarEntry(TarHeader.createHeader(fileName, data.length, Util.unixTime(), false, 0644)));
		output.write(data);
	}

	/**
	 * Return meta data (eg. *.vmx) required to start the given lecture.
	 * 
	 * @param lectureId
	 * @return
	 */
	private Response serveMetaData(final String lectureId) {
		PipedInputStream sink = new PipedInputStream(10000);
		try {
			final TarOutputStream output = new TarOutputStream(new GZIPOutputStream(new PipedOutputStream(sink)));
			final LaunchData ld;
			try {
				ld = DbLecture.getClientLaunchData(lectureId);
			} catch (UnsupportedVirtualizerFormatException | TNotFoundException e) {
				// TODO better virt error handling
				return notFound();
			} catch (SQLException e) {
				return internalServerError();
			}
			// Meta is required, everything else is optional
			tpe.execute(new Runnable() {
				@Override
				public void run() {
					try {
						tarPutFile(output, "vmx", ld.vmx.getFilteredDefinitionArray());
						tarPutFile(output, "runscript", ld.legacyRunScript);
						tarPutFile(output, "netshares", serializeNetShares(ld.netShares));
						if (ld.runScript != null) {
							int cnt = 0;
							for (RunScript rs : ld.runScript) {
								tarPutFile(output, String.format("adminrun/%04d-%d-%d.%s",
										cnt++, rs.visibility,
										rs.passCreds ? 1 : 0, rs.extension), rs.content);
							}
						}
					} catch (IOException e) {
						LOGGER.warn("Error writing to tar stream", e);
					} finally {
						Util.safeClose(output);
					}
				}
			});
		} catch (IOException e1) {
			LOGGER.warn("Could not create tar output stream", e1);
			return internalServerError();
		} catch (RejectedExecutionException e2) {
			LOGGER.warn("Server overloaded; rejecting VM Metadata request", e2);
			return internalServerError();
		}
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/gzip",
				sink);
	}
	
	private Response serveLectureNetRules(String lectureId) {
		List<NetRule> list = new ArrayList<>();
		boolean defaultAllowed;
		try {
			defaultAllowed = DbLecture.getFirewallRules(lectureId, list);
		} catch (SQLException e) {
			return internalServerError();
		} catch (TNotFoundException e) {
			return notFound();
		}
		StringBuilder sb = new StringBuilder();
		for (NetRule rule : list) {
			sb.append(rule.direction.name());
			sb.append(' ');
			sb.append(rule.host);
			sb.append(' ');
			sb.append(rule.port);
			sb.append(' ');
			sb.append(defaultAllowed ? "REJECT" : "ACCEPT");
			sb.append('\n');
		}
		if (defaultAllowed) {
			sb.append("IN * 0 ACCEPT\n");
			sb.append("OUT * 0 ACCEPT\n");
		} else {
			sb.append("IN * 0 REJECT\n");
			sb.append("OUT * 0 REJECT\n");
		}
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/plain; charset=utf-8",
				sb.toString());
	}

	private String serializeNetShares(List<NetShare> list) {
		// openslx.exe expects shares in the following format
		//	<path> <letter> <shortcut> <username> <password>
		// letter is either a drive letter for Windows VMs,
		// or a mount point for Linux VMs.
		StringBuilder sb = new StringBuilder();
		if (!list.isEmpty()) {
			for (NetShare share : list) {
				sb.append(share.path);
				sb.append('\t');
				sb.append(share.mountpoint);
				sb.append('\t');
				sb.append(share.displayname);
				if (share.auth == NetShareAuth.LOGIN_USER) {
					// TODO how to mark that it should use the logged in user's credentials
				}
				if (share.auth == NetShareAuth.OTHER_USER && share.isSetUsername()) {
					sb.append('\t');
					sb.append(share.username);
					if (share.isSetPassword()) {
						sb.append('\t');
						sb.append(share.password); // TODO fixme
					}
				}
				sb.append("\n");
			}
		}
		return sb.toString();
	}

	/**
	 * Return full list of lectures matching given location(s).
	 * 
	 * @return
	 * @throws Exception
	 */
	private Response serveVmChooserList(Map<String, String> params) throws Exception {
		String locations = params.get("locations");
		boolean exams = params.containsKey("exams");

		VmChooserListXml listXml = DbLecture.getUsableListXml(exams, locations);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		serializer.write(listXml, baos);
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "text/xml; charset=utf-8",
				new ByteArrayInputStream(baos.toByteArray()));
	}

	/**
	 * Helper for returning "Internal Server Error" Status
	 */
	public static Response internalServerError() {
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.INTERNAL_ERROR, "text/plain",
				"Internal Server Error");
	}

	/**
	 * Helper for returning "404 Not Found" Status
	 */
	public static Response notFound() {
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, "text/plain", "Nicht gefunden!");
	}

	/**
	 * Helper for returning "Bad Request" Status
	 */
	public static Response badRequest(String message) {
		if (message == null) {
			message = "Schlechte Anfrage!";
		}
		return new NanoHTTPD.Response(NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", message);
	}
}