Fawkes/main.js

343 lines
9.0 KiB
JavaScript

/*
* Copyright (C) 2021 Thomas Van Acker
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*
* This project is hosted on https://git.bitscuit.be/bitscuit/Fawkes
*
*/
// Imports
const http = require("http");
const url = require("url");
const fs = require("fs");
const util = require("util");
const crypto = require("crypto");
const spawn = require("child_process").spawn;
const exec = require("child_process").exec;
// Vars
const hostname = "192.168.0.128"; // Enter your local IP address here
const port = 8001; // Enter a port number here
var files = {};
var fawkes = {};
// Create HTTP server
const server = http.createServer((req, res) => {
// Parse url
const requrl = url.parse(req.url);
var reqpath = requrl.pathname;
if(reqpath == "/") reqpath = "/index.html";
// Respond to requests
try{
// Return requested page
if(reqpath.startsWith("/dl/")){
res.statusCode = 200;
res.write(fs.readFileSync("fawkes/files/"+reqpath.substr(4)));
res.end();
}else{
res.statusCode = 200;
res.write(fs.readFileSync("html"+reqpath));
res.end();
}
}catch(error){
// 404 if page not found
res.statusCode = 404;
res.setHeader("Content-Type", "text/plain");
res.end("404 - Page Not Found");
}
});
// Create socket.io server
const io = require("socket.io")(server); // Can only be done when server is created
io.on("connection", (socket) => {
console.log("New user connected!");
// Give user unique ID
var userId = sha256("USER"+rand(0,9999999)+"TIME"+Date.now()).substr(0, 10);
console.log("userId = "+userId);
// Init directory structure
fs.mkdirSync("fawkes/files/"+userId);
// Init socket listeners
// Disconnect
socket.on("disconnect", () => {
console.log(userId+": disconnect");
// Remove all files from this user
try{
fs.rmSync("fawkes/files/"+userId, {recursive: true});
fs.rmSync("fawkes/files/"+userId+"cloaked", {recursive: true});
}catch(error){
console.log(userId+": couldn't delete some files");
}
});
// File upload
socket.on("FILE_START", (data) => {
console.log(userId+": file upload ("+data.name+", "+data.length+" bytes)");
// Check length
if(data.length > 20000000){
console.log(userId+": file too large (data.length = "+data.length+")");
socket.emit("FILE_ERROR", {name:data.name, error:"File too large. Max size = 20MB"});
return;
}
// Check if exists
if((userId+data.name) in files){
console.log(userId+": file already exists (data.name = "+data.name+")");
socket.emit("FILE_ERROR", {name:data.name, error:"File had already been uploaded."});
return;
}
// Create file
var name = data.name;
var path = "fawkes/files/"+userId+"/"+name+".png";
var file = {
length: data.length,
name: data.name,
path: path,
numSegments: data.length/100000 + 1,
nextSegment: 0
};
files[userId+data.name] = file;
// Ask client to begin upload
socket.emit("FILE_ACK", {name:file.name, nextSegment:file.nextSegment, numSegments:file.numSegments});
});
// File contents upload
socket.on("FILE_DATA", (data) => {
console.log(userId+": file data ("+data.name+", segment "+data.segment+", "+data.data.length+" bytes)");
// Check if file exists
if(!((userId+data.name) in files)){
console.log(userId+": received segment for unknown file (data.name = "+data.name+")");
socket.emit("FILE_ERROR", {name:data.name, error:"Received segment for unknown file."});
return;
}
// Get file
var file = files[userId+data.name];
// Check size
if(data.data.length > 100000){
console.log(userId+": segment too large (data.name = "+data.name+", data.data.length = "+data.data.length+")");
socket.emit("FILE_ERROR", {name:data.name, error:"Segment too large."});
return;
}
// Check segment number
if(data.segment != file.nextSegment || data.segment >= file.numSegments){
console.log(userId+": unexpected segment (data.name = "+data.name+", data.segment = "+data.segment+")");
socket.emit("FILE_ERROR", {name:data.name, error:"Unexpected segment."});
return;
}
// Write data
fs.appendFile(file.path, data.data, 'Binary', function(err){
if(err){
// Error
console.log(userId+": couldn't write to file (error = "+err+")");
socket.emit("FILE_ERROR", {name:data.name, error:"Internal server error. Couldn't write to file."});
}else{
file.nextSegment++;
if(file.nextSegment < file.numSegments-1){
// Ask for new segment
socket.emit("FILE_ACK", {name:file.name, nextSegment:file.nextSegment, numSegments:file.numSegments});
}else{
// When file fully uploaded
socket.emit("FILE_DONE", {name:file.name});
}
}
});
});
// Fawkes start
socket.on("FAWKES_START", () => {
// Start binary
var process = spawn("fawkes/protection", ["-d", "fawkes/files/"+userId, "--mode", "low"]);
// Update vars
fawkes[userId] = {
running: true,
images: [],
imageSentDone: []
};
// Add listeners
process.stdout.setEncoding("utf8");
process.stdout.on("data", function(data){
// Received output from process
data = data.toString();
console.log("fawkes stdout: "+data);
// Parse output
var lines = data.split("\n");
for(var l=0; l<lines.length; l++){
// For each line
var splices = lines[l].split(" ");
if(splices.length == 0) continue;
if(splices[0] == "Find"){
var numFaces = splices[1];
var imageId = splices[4].substr(0, splices[4].indexOf("."));
console.log("fawkes found "+numFaces+" faces in image "+imageId);
// Update file info
files[userId+imageId].numFaces = numFaces;
fawkes[userId].images.push(userId+imageId);
fawkes[userId].imageSentDone.push(numFaces == 0);
// Update state if no faces found
if(numFaces == 0){
var d = {name:imageId, face:0, numFaces:0};
console.log(d);
socket.emit("FAWKES_STATE", d);
}
}else if(splices[0] == "processing"){
// Processing next face
console.log("fawkes processing face "+splices[2]);
// Calculate image
var image;
var f = splices[2] - 1;
for(var i=0; i<fawkes[userId].images.length; i++){
console.log("f = "+f);
if(f < files[fawkes[userId].images[i]].numFaces){
image = fawkes[userId].images[i];
console.log("image = "+image);
break;
}else{
f -= files[fawkes[userId].images[i]].numFaces;
// If didn't send it yet, send 'done' message for this image
if(!fawkes[userId].imageSentDone[i]){
fawkes[userId].imageSentDone[i] = true;
var nf = files[fawkes[userId].images[i]].numFaces;
var d = {name:fawkes[userId].images[i].substr(userId.length), face:nf+1, numFaces:nf};
console.log(d);
socket.emit("FAWKES_STATE", d);
}
}
}
// Update client states
var d = {name:image.substr(userId.length), face:f+1, numFaces:files[image].numFaces};
console.log(d);
socket.emit("FAWKES_STATE", d);
}
}
});
process.stderr.setEncoding("utf8");
process.stderr.on("data", function(data){
// Received output from process
console.log("fawkes stderr: "+data);
});
process.on("close", function(code){
// Fawkes exited
console.log("Fawkes exited with code "+code);
// Check if success
if(code == 0){
// Create archive
console.log("creating archive...");
var archiveName = "fawkes-"+Date.now()+"-"+userId+".tar.gz";
var child = exec(
"mkdir fawkes/files/"+userId+"cloaked && "+
"mv fawkes/files/"+userId+"/*_cloaked* fawkes/files/"+userId+"cloaked/ && "+
"tar -czvf fawkes/files/"+archiveName+" -C fawkes/files/"+userId+"cloaked ."
, (error, stdout, stderr) => {
console.log("archive creation done: "+error+", "+stdout+", "+stderr);
// Zip done
if(error){
socket.emit("FAWKES_DONE", {success:false, message:"Internal server error. Couldn't create archive."});
}else{
socket.emit("FAWKES_DONE", {success:true, downloadUrl:"/dl/"+archiveName});
}
});
}else{
// Error
socket.emit("FAWKES_DONE", {success:false, message:"Internal server error. Fawkes exited with an exception."});
}
});
// Update file states
socket.emit("FAWKES_STARTED");
});
});
// Start HTTP server
server.listen(port, hostname, () => {
console.log("Server running at http://"+hostname+":"+port);
});
// Helper methods
function rand(min, max){
return Math.floor(Math.random()*(max-min) + min);
}
function sha256(message){
return crypto.createHash("sha256").update(message).digest("hex");
}