package zeta.handler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpUtils;
import zeta.TaskServer;
import zeta.ZetaServlet;
import zeta.util.Base64;
import zeta.util.DatabaseUtils;
import zeta.util.Parameter;
import zeta.util.StreamUtils;
public class GetClientHandler implements GetHandler {
public GetClientHandler(ZetaServlet servlet) {
this.servlet = servlet;
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, SQLException, IOException {
TaskServer task = TaskServer.getTask(servlet, HttpUtils.parseQueryString(req.getQueryString()));
if (task == null) {
servlet.log("No valid task is defined.");
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
return;
}
String hostname = task.getParameter("hostname");
if (hostname == null) {
resp.setContentType("text/plain");
resp.setContentLength(0);
resp.getOutputStream().print("");
return;
}
hostname = hostname.toLowerCase();
String hostaddr = task.getParameter("hostaddr");
String osName = task.getParameter("os_name");
if (osName == null) {
osName = "";
}
String shortOsName = osName.trim();
int idx = shortOsName.indexOf(' ');
if (idx > 0) {
shortOsName = shortOsName.substring(0, idx);
}
String osVersion = task.getParameter("os_version");
if (osVersion == null) {
osVersion = "";
}
String osArch = task.getParameter("os_arch");
if (osArch == null) {
osArch = "";
}
String files = task.getParameter("files");
if (files == null) {
files = "";
}
String key = task.getParameter("key");
int processors = 1;
try {
processors = Integer.parseInt(task.getParameter("processors"));
} catch (NumberFormatException nfe) {
} catch (NullPointerException npe) {
}
int workstationId = 0;
Connection con = null;
Statement stmt = null;
try {
con = servlet.getConnection();
stmt = con.createStatement();
int serverId = servlet.getServerId();
boolean active = false;
if (System.currentTimeMillis()-lastRefreshSignature > 60*60*1000) {
if (!refreshSignature(stmt)) {
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
ZipOutputStream zip = new ZipOutputStream(out);
zip.setLevel(Deflater.BEST_COMPRESSION);
zip.putNextEntry(new ZipEntry("signature.txt"));
zip.flush();
zip.close();
resp.setContentType("application/octet-stream");
resp.setContentLength(out.size());
out.writeTo(resp.getOutputStream());
}
}
Timestamp lastUpdate = null;
if (key != null && key.length() > 0) {
key = new String(Base64.decode(key), "UTF-8");
if (key.length() > 200) {
key = key.substring(0, 200);
}
key = key.toLowerCase();
long t = getKeyTimestamp(key);
if (t > 0) {
lastUpdate = new Timestamp(t);
active = true;
} else {
ResultSet rs = stmt.executeQuery("SELECT last_update,id,active_YN FROM zeta.workstation WHERE server_id=" + serverId + " AND key='" + key + '\'');
if (rs.next()) {
lastUpdate = rs.getTimestamp(1);
if (lastUpdate == null) {
lastUpdate = new Timestamp(System.currentTimeMillis());
}
workstationId = rs.getInt(2);
String s = rs.getString(3);
active = (s != null && s.length() == 1 && s.charAt(0) == 'Y');
}
rs.close();
}
}
if (lastUpdate == null) {
active = Parameter.getValue(stmt, "new_workstation_is_active", Parameter.GLOBAL_PARAMETER, "Y", 24*3600*1000).equals("Y");
} else {
putKey(key);
}
if (!active) {
throw new ServletException("Workstation is not active!");
}
if (lastUpdate != null && lastUpdate.after(signatureLastUpdate)) {
int i = 4;
int l = programs.size();
while (i < l) {
if ((osArch.equals(programs.get(i-1)) || ((String)programs.get(i-1)).length() == 0) && (shortOsName.equals(programs.get(i-2)) || ((String)programs.get(i-2)).length() == 0)) {
int t = ((Integer)programs.get(i-4)).intValue();
if ((t == 0 || task.getId() == t) && files.indexOf("'" + programs.get(i-3) + "'") < 0) {
break;
}
}
i += 5;
}
if (i >= l) {
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
ZipOutputStream zip = new ZipOutputStream(out);
zip.setLevel(Deflater.BEST_COMPRESSION);
zip.putNextEntry(new ZipEntry("signature.txt"));
zip.flush();
zip.close();
resp.setContentType("application/octet-stream");
resp.setContentLength(out.size());
out.writeTo(resp.getOutputStream());
return; }
}
ByteArrayOutputStream out = null;
String content = null;
synchronized (contentLastZip1) {
if (lastZip1 != null || lastZip2 != null) {
StringBuffer buffer = new StringBuffer(1000);
buffer.append(shortOsName);
buffer.append(',');
buffer.append(osArch);
for (int i = 4, l = programs.size(); i < l; i += 5) {
if ((osArch.equals(programs.get(i-1)) || ((String)programs.get(i-1)).length() == 0) && (shortOsName.equals(programs.get(i-2)) || ((String)programs.get(i-2)).length() == 0)) {
int t = ((Integer)programs.get(i-4)).intValue();
if ((t == 0 || task.getId() == t) && (files.indexOf("'" + programs.get(i-3) + "'") < 0 || lastUpdate != null && lastUpdate.before((Timestamp)programs.get(i)))) {
buffer.append(',');
buffer.append(programs.get(i-3));
break;
}
}
}
content = buffer.toString();
if (content.equals(contentLastZip1)) {
out = lastZip1;
++usageLastZip1;
servlet.log("using " + usageLastZip1 + " lastZip1 (" + content + ')');
} else if (content.equals(contentLastZip2)) {
out = lastZip2;
++usageLastZip2;
servlet.log("using " + usageLastZip2 + " lastZip2 (" + content + ')');
}
}
if (out == null) {
refreshSignature(stmt); out = new ByteArrayOutputStream(512 * 1024);
ZipOutputStream zip = new ZipOutputStream(out);
zip.setLevel(Deflater.BEST_COMPRESSION);
zip.putNextEntry(new ZipEntry("signature.txt"));
signatureStream.writeTo(zip);
int i = (content != null)? 1+content.indexOf(',') : 0;
if (content != null && i > 0 && i < content.length() && content.indexOf(',', i) < 0) {
servlet.log("just signatures");
zip.flush();
zip.close();
out.close();
} else {
StringBuffer sql = new StringBuffer(1000);
sql.append("SELECT name,compressed_YN,program FROM zeta.program WHERE (task_id=0 OR task_id=");
sql.append(task.getId());
sql.append(") AND (task_id>0 OR name<>'signature.txt') AND (os_name='' OR os_name='");
sql.append(shortOsName);
sql.append("') AND (os_arch='' OR os_arch='");
sql.append(osArch);
sql.append("')");
if (lastUpdate != null && files.length() > 0) {
sql.append(" AND (last_update>='");
sql.append(lastUpdate);
sql.append("' OR name NOT IN (");
sql.append(files);
sql.append("))");
}
sql.append(" ORDER BY os_name");
StringBuffer buffer = new StringBuffer(1000);
buffer.append(shortOsName);
buffer.append(',');
buffer.append(osArch);
ResultSet rs = stmt.executeQuery(sql.toString());
while (rs.next()) {
String name = rs.getString(1);
zip.putNextEntry(new ZipEntry(name));
InputStream in = null;
if (rs.getString(2).equals("Y")) {
in = new ZipInputStream(rs.getBinaryStream(3));
((ZipInputStream)in).getNextEntry();
} else {
in = rs.getBinaryStream(3);
}
StreamUtils.writeData(in, zip, true, false);
buffer.append(',');
buffer.append(name);
}
rs.close();
content = buffer.toString();
zip.flush();
zip.close();
out.close();
i = 1+content.indexOf(',');
if (i > 0 && i < content.length() && content.indexOf(',', i) < 0) {
servlet.log("just signatures");
} else if (usageLastZip1 < usageLastZip2) {
usageLastZip1 = 1;
contentLastZip1 = content;
lastZip1 = out;
servlet.log("new content (" + content + ") in lastZip1");
} else {
usageLastZip2 = 1;
contentLastZip2 = content;
lastZip2 = out;
servlet.log("new content (" + content + ") in lastZip2");
}
}
}
}
DatabaseUtils.close(stmt);
stmt = null;
DatabaseUtils.close(con);
con = null;
resp.setContentType("application/octet-stream");
resp.setContentLength(out.size());
out.writeTo(resp.getOutputStream());
if (key != null && key.length() > 0 && active) {
int l = Math.min(500, files.length());
StringBuffer buffer = new StringBuffer(2*l);
for (int i = 0; i < l; ++i) {
char c = files.charAt(i);
if (c == '\'') {
buffer.append('\'');
}
buffer.append(c);
}
files = buffer.toString();
con = servlet.getConnection();
stmt = con.createStatement();
if (workstationId > 0) {
stmt.executeUpdate("UPDATE zeta.workstation SET (last_update,last_local_files,hostaddress,os_name,os_version,os_arch,processors)=(CURRENT TIMESTAMP,'"
+ files + "','" + hostaddr + "','" + osName + "','" + osVersion + "','" + osArch + "'," + processors
+ ") WHERE server_id=" + serverId + " AND id=" + workstationId);
} else {
stmt.executeUpdate("UPDATE zeta.workstation SET (last_update,last_local_files,os_name,os_version,os_arch,processors)=(CURRENT TIMESTAMP,'"
+ files + "','" + osName + "','" + osVersion + "','" + osArch + "'," + processors
+ ") WHERE server_id=" + serverId + " AND key='" + key + '\'');
}
putKey(key);
}
} finally {
DatabaseUtils.close(stmt);
DatabaseUtils.close(con);
}
}
private boolean refreshSignature(Statement stmt) throws IOException, ServletException, SQLException {
ResultSet rs = stmt.executeQuery("SELECT version,last_update FROM zeta.program WHERE task_id=0 AND name='signature.txt'");
if (rs.next()) {
synchronized (signatureVersion) {
String version = rs.getString(1);
signatureLastUpdate = rs.getTimestamp(2);
rs.close();
if (!signatureVersion.equals(version)) {
lastZip1 = lastZip2 = null;
usageLastZip1 = usageLastZip2 = 0;
rs = stmt.executeQuery("SELECT program FROM zeta.program WHERE task_id=0 AND name='signature.txt'");
if (!rs.next()) {
throw new ServletException("Missing digital signatures!");
}
signatureStream.reset();
StreamUtils.writeData(rs.getBinaryStream(1), signatureStream, true, false);
rs.close();
signatureVersion = version;
lastRefreshSignature = System.currentTimeMillis();
programs.clear();
rs = stmt.executeQuery("SELECT task_id,name,os_name,os_arch,last_update FROM zeta.program WHERE task_id>0 OR name<>'signature.txt' ORDER BY os_name");
while (rs.next()) {
programs.add(new Integer(rs.getInt(1)));
programs.add(rs.getString(2));
programs.add(rs.getString(3));
programs.add(rs.getString(4));
programs.add(rs.getTimestamp(5));
}
rs.close();
}
}
} else {
rs.close();
rs = stmt.executeQuery("SELECT COUNT(*) FROM zeta.program");
if (rs.next() && rs.getInt(1) > 0) {
rs.close();
throw new ServletException("Missing digital signatures!");
}
rs.close();
return false;
}
return true;
}
private static void putKey(String key) {
if (key != null && key.length() > 0) {
synchronized (lastKeys) {
if (lastKeys.size() >= MAX_QUEUE_KEY_SIZE) {
Object k = lastTimestampsOfKeys.remove(lastTimestampsOfKeys.firstKey());
lastKeys.remove(k);
}
Long timestamp = new Long(System.currentTimeMillis());
lastKeys.put(key, timestamp);
lastTimestampsOfKeys.put(timestamp, key);
}
}
}
static long getKeyTimestamp(String key) {
synchronized (lastKeys) {
Long timestamp = (Long)lastKeys.get(key);
return (timestamp == null)? 0 : timestamp.longValue();
}
}
private final static int MAX_QUEUE_KEY_SIZE = 1000;
private static Map lastKeys = new HashMap(MAX_QUEUE_KEY_SIZE);
private static TreeMap lastTimestampsOfKeys = new TreeMap();
long lastRefreshSignature = 0;
private String signatureVersion = "";
private Timestamp signatureLastUpdate = null;
private ByteArrayOutputStream signatureStream = new ByteArrayOutputStream(100 * 1024);
private List programs = new ArrayList(120);
private String contentLastZip1 = "";
private String contentLastZip2 = "";
private int usageLastZip1 = 0;
private int usageLastZip2 = 0;
private ByteArrayOutputStream lastZip1 = null;
private ByteArrayOutputStream lastZip2 = null;
private ZetaServlet servlet;
}