
/* Gnome Encfs Manager (GEncfsM)
 * Copyright (C) 2011 Moritz Molch <mail@moritzmolch.de>
 
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using GLib;
using Gee;
using Posix;

namespace GnomeEncfsManager {
    class EncfsWrapper2 {
       public enum Streams {
            STD_IN = 0,
            STD_OUT,
            STD_ERR,
            NUM_STREAMS
        }
        
        private static Pid mountpid = -1;
        private static bool killed = false;
        
        public delegate void MountCallback(bool success, string message);
        public static void mount(Stash s, string password, MountCallback callback) {
        	killed = false;
        	mountpid = -1;
            if (!s.isAvailable) {
                callback(false, _("The stash is not avaliable"));
                return;
            }
            
            //mkdir (s.mountDir, 0700);
            if (!FileUtils.test(s.mountDir, FileTest.IS_DIR)) {
                try {
                    string exit_stderr;
                    int exit_status;
	                Process.spawn_command_line_sync( "mkdir -m 700 -p \""+s.mountDir+"\"" , null, out exit_stderr, out exit_status );
	                if (exit_status != 0) {
	                    callback(false, _(exit_stderr));
	                    return;
	                }
	            } catch (Error e) {
	                callback(false, "Mount directory: " + e.message);
        	        return;
	            }
	        }
            
            try {
	            FileEnumerator enumerator = File.new_for_path(s.mountDir).enumerate_children (
		            "standard::*",
		            FileQueryInfoFlags.NOFOLLOW_SYMLINKS, 
		            null);

	            if (enumerator.next_file(null) != null) {
                    callback(false, _("The mount folder is not empty"));
                    return;
	            }
	        } catch (Error e) {
	            callback(false, "Mount directory: " + e.message);
    	        return;
	        }

            
            int fd[3];
            ArrayList<string> argv = new ArrayList<string> ();
            ArrayList<string> env = new ArrayList<string> ();
			string[] vars = Environment.list_variables();
			foreach (string xvar in vars) {
				env.add(xvar+"="+Environment.get_variable(xvar));
			}
			
            if (s.options.get_string("encfs_bin") != "") {
	            argv.add(s.options.get_string("encfs_bin"));
	        } else {
	        	argv.add("encfs");
	        }
            argv.add("-S");
            GLib.stdout.printf("\n=== %s: "+argv[0]+" -S", "Command");
            
            if ((s.unmountWhenIdle) && (s.idleTimeout > 0)) {
                argv.add("--idle="+s.idleTimeout.to_string());
                GLib.stdout.printf(" --idle="+s.idleTimeout.to_string());
            }
            
            if (s.options.get_string("additional_encfs_options") != "") {
            	try {
		        	string[] opts;
		        	Shell.parse_argv(s.options.get_string("additional_encfs_options"), out opts);
				    foreach (string opt in opts) {
				    	argv.add(opt);
				    	GLib.stdout.printf(" "+opt);
				    }
	            } catch (Error e) {
	                callback(false, e.message);
        	        return;
	            }
            }
            
            argv.add(s.cryptDir);
            GLib.stdout.printf(" \"%s\"", s.cryptDir);
            argv.add(s.mountDir);
            GLib.stdout.printf(" \"%s\"", s.mountDir);

            if (s.allowOther) {
                argv.add("--");
                argv.add("-o");
                argv.add("allow_other");
                GLib.stdout.printf(" -- -o allow_other");
            }

            GLib.stdout.printf("\n");
            
            if (s.encfs6config != "") {
                if (s.encfs6config == ".encfs5") {
                
                } else if (s.encfs6config == ".encfs6.xml") {
    
                } else {
                    env.add("ENCFS6_CONFIG=" + s.encfs6config);
                }
            }

        
            try {
                Process.spawn_async_with_pipes( s.cryptDir, // working directory
                                                arrayList2Array(argv),
                                                arrayList2Array(env), // environment
                                                SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
                                                null, // spawnchild setup
                                                out mountpid,
                                                out fd[Streams.STD_IN],
                                                out fd[Streams.STD_OUT],
                                                out fd[Streams.STD_ERR]);
                                                
                                                
		        var msg = new StringBuilder();
		        
		        uint timerID = 0;
		        
		        ChildWatch.add(mountpid, (pid, status) => {
		            Source.remove(timerID);
		            string *pout = GLib.malloc(1024);
		            memset(pout, 0, 1024);
		            while (Posix.read(fd[Streams.STD_OUT], pout, 1024) > 0) {
		                msg.append(pout);
		            }
		            while (Posix.read(fd[Streams.STD_ERR], pout, 1024) > 0) {
		                msg.append(pout);
		            }
		            GLib.free(pout);
		            GLib.stdout.printf(msg.str);
		        
		            Process.close_pid(pid);
		            mountpid = -1;
		            if (status == 0) {
		                callback(true, "");
		            }
		            else {
                        if (!s.options.get_bool("keep_mount_directory")) {
        		            Posix.rmdir(s.mountDir);
        		        }
		                if (killed) {
		                    callback(false, _("Encfs was killed"));
		                }
		                else {
		                    callback(false, msg.str[0:-1]);
		                }
		            }
		        });
		        
		        
		        // kill encfs if it takes too long to complete
		        timerID = Timeout.add(45000, () => {
		            killed = true;
		            Posix.kill(mountpid, 9);
		            return false;
		        });
		        
		        string password_x = password+"\n";
		        ssize_t xxx = Posix.write(fd[Streams.STD_IN], password_x, password_x.length);
		        //password_x.unref();
		        xxx++; // stupid compiler warning
            } catch (Error e) {
                GLib.stderr.printf(e.message+"\n");
                callback( false, e.message );
            }
        }
        
        public static void mount_cancel() {
        	if (mountpid > -1) {
		    	killed = true;
		    	Posix.kill(mountpid, 9);
		    }
        }
        
        public static string get_info(Stash s) {
        	string[] streams = new string[2];
            ArrayList<string> argv = new ArrayList<string> ();
            ArrayList<string> env = new ArrayList<string> ();
			string[] vars = Environment.list_variables();
			foreach (string xvar in vars) {
				env.add(xvar+"="+Environment.get_variable(xvar));
			}
            
            if (s.options.get_string("encfsctl_bin") != "") {
	            argv.add(s.options.get_string("encfsctl_bin"));
	        } else {
	        	argv.add("encfsctl");
	        }

            argv.add("info");
            try {
                FileUtils.read_link(s.cryptDir);
                argv.add(FileUtils.read_link(s.cryptDir));
            } catch (Error e) {
                argv.add(s.cryptDir);
            }
            
            GLib.stdout.printf("\n=== %s: ", _("Command"));
            foreach (string xx in argv) {
                GLib.stdout.printf("%s ", xx);
            }
            GLib.stdout.printf("\n");
        
            if (s.encfs6config != "") {
                if (s.encfs6config == ".encfs5") {
                
                } else if (s.encfs6config == ".encfs6.xml") {
    
                } else {
                    env.add("ENCFS6_CONFIG=" + s.encfs6config);
                }
            }
            
            try {
                Process.spawn_sync( s.cryptDir, // working directory
                                    arrayList2Array(argv),
                                    arrayList2Array(env), // environment
                                    SpawnFlags.SEARCH_PATH,
                                    null, // spawnchild setup
                                    out streams[0],
                                    out streams[1],
                                    null );
            } catch (Error e) {
                GLib.stderr.printf(e.message+"\n");
            }
            
            GLib.stdout.printf("%s", streams[0]);
            return streams[0][1:-2];
        }
        
        private static Pid changepasswordpid = -1;
        public static void change_password(Stash s, string old_password, string new_password, IntStringCallback callback) {
        	changepasswordpid = -1;
            int fd[3];
            ArrayList<string> argv = new ArrayList<string> ();
            ArrayList<string> env = new ArrayList<string> ();
			string[] vars = Environment.list_variables();
			foreach (string xvar in vars) {
				env.add(xvar+"="+Environment.get_variable(xvar));
			}
            
            if (s.options.get_string("encfsctl_bin") != "") {
	            argv.add(s.options.get_string("encfsctl_bin"));
	        } else {
	        	argv.add("encfsctl");
	        }
	        
            argv.add("autopasswd");
            argv.add(s.cryptDir);

            GLib.stdout.printf("\n=== %s: ", _("Command"));
            foreach (string xx in argv) {
                GLib.stdout.printf("%s ", xx);
            }
            GLib.stdout.printf("\n");

            if (s.encfs6config != "") {
                if (s.encfs6config == ".encfs5") {
                
                } else if (s.encfs6config == ".encfs6.xml") {
    
                } else {
                    env.add("ENCFS6_CONFIG=" + s.encfs6config);
                }
            }

        
            try {
                Process.spawn_async_with_pipes( s.cryptDir, // working directory
                                                arrayList2Array(argv),
                                                arrayList2Array(env), // environment
                                                SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
                                                null, // spawnchild setup
                                                out changepasswordpid,
                                                out fd[Streams.STD_IN],
                                                out fd[Streams.STD_OUT],
                                                out fd[Streams.STD_ERR]);
                                                
	        	var msg = new StringBuilder();
		        ChildWatch.add(changepasswordpid, (pid, status) => {
		            string *pout = GLib.malloc(1024);
		            memset(pout, 0, 1024);
		            while (Posix.read(fd[Streams.STD_OUT], pout, 1024) > 0) {
		                msg.append(pout);
		            }
		            while (Posix.read(fd[Streams.STD_ERR], pout, 1024) > 0) {
		                msg.append(pout);
		            }
		            GLib.free(pout);
		            GLib.stdout.printf(msg.str);
		        
		            Process.close_pid(pid);
		            changepasswordpid = -1;
		            callback(Process.exit_status(status), msg.str);

		        });
		        
		        string old_password_x = old_password+"\n";
		        string new_password_x = new_password+"\n";
		        
		        ssize_t xxx = Posix.write(fd[Streams.STD_IN], old_password_x, old_password_x.length);
            	xxx = Posix.write(fd[Streams.STD_IN], new_password_x, new_password_x.length);
		        
            } catch (Error e) {
                GLib.stderr.printf(e.message+"\n");
                callback( -1, e.message );
            }
        }
        
        public static bool unmount(Stash s) {
            bool ret_val = true;
            int exit_status;
        	string[] streams = new string[2];
            ArrayList<string> argv = new ArrayList<string> ();
            ArrayList<string> env = new ArrayList<string> ();
			string[] vars = Environment.list_variables();
			foreach (string xvar in vars) {
				env.add(xvar+"="+Environment.get_variable(xvar));
			}

            if (s.options.get_string("fusermount_bin") != "") {
	            argv.add(s.options.get_string("fusermount_bin"));
	        } else {
	        	argv.add("fusermount");
	        }

            argv.add("-u");
            argv.add(s.mountDir);
        
            GLib.stdout.printf("\n=== %s: ", _("Command"));
            foreach (string xx in argv) {
                GLib.stdout.printf("%s ", xx);
            }
            GLib.stdout.printf("\n");
            
            try {
                Process.spawn_sync( null, // working directory
                                    arrayList2Array(argv),
                                    arrayList2Array(env), // environment
                                    SpawnFlags.SEARCH_PATH,
                                    null, // spawnchild setup
                                    out streams[0],
                                    out streams[1],
                                    out exit_status );
            } catch (Error e) {
                GLib.stderr.printf(e.message+"\n");
                ret_val = false;
            }
            
            if (exit_status != 0) {
                ret_val = false;
                try {
                    string[] spawn_args = { "notify-send",
                                            "-u", "low",
                                            "-a", _("Gnome Encfs Manager"),
                                            "-i", "gnome-encfs-manager",
                                            Path.get_basename(s.mountDir), streams[1][0:-1] };
                    Process.spawn_sync( null, spawn_args, null, SpawnFlags.SEARCH_PATH, null );
                } catch (Error e) {}
            }
            GLib.stdout.printf("%s", streams[1]);
            
            return ret_val;
        }
        
        
        
        private static Pid createpid = -1;
        private static bool createkilled = false;
        public static void create(Stash s, string password, bool disable_filename_encryption, IntStringCallback callback) {
        	createkilled = false;
        	createpid = -1;
            
            //mkdir (s.mountDir, 0700);
            try {
                string exit_stderr;
                int exit_status;
	            Process.spawn_command_line_sync( "mkdir -m 700 -p \""+s.mountDir+"\"" , null, out exit_stderr, out exit_status );
	            if (exit_status != 0) {
	                callback(1, _(exit_stderr));
	                return;
	            }
	            
	            Process.spawn_command_line_sync( "mkdir -m 700 -p \""+s.cryptDir+"\"" , null, null, null );
	            if (exit_status != 0) {
	                callback(1, _(exit_stderr));
	                return;
	            }

	        }
	        catch (Error e) {
	            callback(1, e.message);
    	        return;
	        }
	        
            try {
	            FileEnumerator enumerator = File.new_for_path(s.mountDir).enumerate_children (
		            "standard::*",
		            FileQueryInfoFlags.NOFOLLOW_SYMLINKS, 
		            null);
	            //FileInfo info = null;
	            if (enumerator.next_file(null) != null) {
                    callback(1, _("The mount folder is not empty"));
                    return;
	            }
	        } catch (Error e) {
	            callback(1, "Mount directory: " + e.message);
    	        return;
	        }

            
            int fd[3];
            ArrayList<string> argv = new ArrayList<string> ();
            ArrayList<string> env = new ArrayList<string> ();
			string[] vars = Environment.list_variables();
			foreach (string xvar in vars) {
				env.add(xvar+"="+Environment.get_variable(xvar));
			}
        	argv.add("encfs");
            argv.add("-S");
            argv.add(s.cryptDir);
            argv.add(s.mountDir);
            GLib.stdout.printf("\n=== Command: ");
            foreach (string st in argv) {
            	GLib.stdout.printf(st+" ");
            }
			GLib.stdout.printf("\n");
			
            /*if (s.encfs6config != "") {
            	env.add("ENCFS6_CONFIG=" + s.encfs6config);
            }*/
			
            try {
                Process.spawn_async_with_pipes( null, // working directory
                                                arrayList2Array(argv),
                                                arrayList2Array(env), // environment
                                                SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
                                                null, // spawnchild setup
                                                out createpid,
                                                out fd[Streams.STD_IN],
                                                out fd[Streams.STD_OUT],
                                                out fd[Streams.STD_ERR]);
                                                
                                                
		        var msg = new StringBuilder();

		        ChildWatch.add(createpid, (pid, status) => {
		            string *pout = GLib.malloc(2048);
		            memset(pout, 0, 2048);
		            while (Posix.read(fd[Streams.STD_OUT], pout, 2048) > 0) {
		                msg.append(pout);
		            }
		            while (Posix.read(fd[Streams.STD_ERR], pout, 2048) > 0) {
		                msg.append(pout);
		            }
		            GLib.free(pout);
		            GLib.stdout.printf(msg.str);
		        
		            Process.close_pid(pid);
		            createpid = -1;
		            if (status == 0) {
		                if ((s.encfs6config != "") && (s.encfs6config != ".encfs6.xml")) {
		                    if (FileUtils.rename(s.cryptDir+"/.encfs6.xml", s.encfs6config) != 0 ) {
		                        callback(-1, _("Error renaming config file"));
		                    }
		                }
		                callback(Process.exit_status(status), "");
		            }
		            else {
		                Posix.rmdir(s.mountDir);
		                Posix.rmdir(s.cryptDir);
		                if (createkilled) {
		                    callback(status, _("Encfs was killed"));
		                }
		                else {
		                    callback(status, msg.str[0:-1]);
		                }
		            }
		        });
		        
		        string password_x = password+"\n";
		        
		        ssize_t xxx;
				if (disable_filename_encryption) {
					xxx = Posix.write(fd[Streams.STD_IN], "x\n", 2);
					xxx = Posix.write(fd[Streams.STD_IN], "1\n", 2);
					xxx = Posix.write(fd[Streams.STD_IN], "256\n", 4);
					xxx = Posix.write(fd[Streams.STD_IN], "1024\n", 5);
					xxx = Posix.write(fd[Streams.STD_IN], "2\n", 2);
					xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
					xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
					xxx = Posix.write(fd[Streams.STD_IN], "y\n", 2);
					xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
					xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
					xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
					
				    xxx = Posix.write(fd[Streams.STD_IN], password_x, password_x.length);
				    xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
	        	} else {
				    xxx = Posix.write(fd[Streams.STD_IN], "p\n", 2);
				    xxx = Posix.write(fd[Streams.STD_IN], password_x, password_x.length);
				    xxx = Posix.write(fd[Streams.STD_IN], "\n", 1);
		        }
		        xxx++; // stupid compiler warning
            } catch (Error e) {
                GLib.stderr.printf(e.message+"\n");
                callback( 1, e.message );
            }
        }

        public static void create_cancel() {
        	if (createpid > -1) {
		    	createkilled = true;
		    	Posix.kill(createpid, 9);
		    }
        }
    }
}
