// // OpenVPN Administrator // // Author(s): Everaldo Canuto // // (C) 2006 Everaldo Canuto // (C) 2006 The Gang // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // using System; using System.IO; using Microsoft.Win32; using System.Diagnostics; using System.ComponentModel; using System.Collections; using System.Collections.Specialized; using System.Text.RegularExpressions; using Mono.Unix; namespace OpenVPN.Admin { public delegate void ConnectionActivateDelegate(System.IO.StreamWriter input, bool askname); public class Configuration { #region Fields private string name; private string oldname; private string filename; private string description = ""; private NameValueCollection entries; private static bool win32; private static string config_ext; private static string config_dir; private static string exe_path; private static string log_dir; private static string pid_dir; private static string dir_char; private static string openssl = "openssl"; //private static string sslconfig = ""; #endregion #region Static methods static Configuration() { if (System.Environment.OSVersion.Platform != PlatformID.Win32NT) { Configuration.win32 = false; Configuration.config_ext = "conf"; Configuration.config_dir = "/etc/openvpn"; Configuration.exe_path = "openvpn"; Configuration.log_dir = "/var/log/openvpn"; Configuration.pid_dir = "/var/run"; } else { RegistryKey registry = Registry.LocalMachine.OpenSubKey("Software\\OpenVPN"); if (registry != null) { Configuration.win32 = true; Configuration.config_ext = (string) registry.GetValue("config_ext"); Configuration.config_dir = (string) registry.GetValue("config_dir"); Configuration.exe_path = (string) registry.GetValue("exe_path"); Configuration.log_dir = (string) registry.GetValue("log_dir"); Configuration.pid_dir = Configuration.log_dir; string path = System.IO.Path.GetDirectoryName(Configuration.exe_path) +System.IO.Path.DirectorySeparatorChar.ToString() +"openssl."; if (File.Exists(path + "exe")) Configuration.openssl = path + "exe"; registry.Close(); } } Configuration.dir_char = Path.DirectorySeparatorChar.ToString(); } private static string FileFromEntry(string entry) { return Configuration.config_dir + Path.DirectorySeparatorChar + entry + "." + Configuration.config_ext; } private static string FileFromKey(string key) { return Configuration.config_dir + Path.DirectorySeparatorChar + key + ".key"; } private static string[] GetConfigFiles() { return Directory.GetFiles(Configuration.config_dir, "*."+Configuration.config_ext); } private static string[] GetConfigEntries() { string[] entries = {}; try { entries = Directory.GetFiles(Configuration.config_dir, "*." + Configuration.config_ext); } catch {} for (int i = 0; i < entries.Length; i++) { entries[i] = Path.GetFileNameWithoutExtension(entries[i]); } return entries; } private static string[] GetConfigKeys() { string[] entries = {}; try { entries = Directory.GetFiles(Configuration.config_dir, "*.key"); } catch {} for (int i = 0; i < entries.Length; i++) { entries[i] = Path.GetFileNameWithoutExtension(entries[i]); } return entries; } /// /// Get file contents, if file is lock then copy file to new file and /// get contents of this new file. /// /// /// This routine are use to solve a problem in Win32 enviroment, Win32 /// lock files when open. /// /// Path of file to get contents. /// public static string GetFileContents(string path, bool firstline) { if (File.Exists(path)) { try { using (StreamReader sr = new StreamReader(path)) { return (firstline ? sr.ReadLine() : sr.ReadToEnd()); } } catch { string newfile = path + ".temp"; File.Copy(path, newfile); try { using (StreamReader sr = new StreamReader(newfile)) { return (firstline ? sr.ReadLine() : sr.ReadToEnd()); } } finally { File.Delete(newfile); } } } else { return ""; } } public static string GetFileContents(string path) { return Configuration.GetFileContents(path, false); } public static string GetStatusFromEntry(string entry) { string file = Configuration.pid_dir + Path.DirectorySeparatorChar + "openvpn." + entry + ".status"; string contents = Configuration.GetFileContents(file); string a="", b="", c="", d=""; if (!File.Exists(file)) return "Inactive"; if ((contents == null) || (contents == "")) return "Cannot retrieve connection status"; MatchCollection matches = Regex.Matches(contents, @"bytes\,(\d+)"); try { a = matches[0].Result("$1"); b = matches[1].Result("$1"); c = matches[2].Result("$1"); d = matches[3].Result("$1"); } catch {} return String.Format("TUN/TAP ({0},{1}) TCP/UDP ({2},{3})", a, b, c, d); } #endregion #region Static properties public static string[] Entries { get { return Configuration.GetConfigEntries(); } } public static string[] Files { get { return Configuration.GetConfigFiles(); } } public static string[] Keys { get { return Configuration.GetConfigKeys(); } } #endregion #region Constructors and destructors public Configuration(string entry) { entries = new NameValueCollection(); this.filename = Configuration.FileFromEntry(entry); this.name = entry; this.oldname = entry; if (File.Exists(this.filename)) { this.Load(); } } #endregion #region Public methods public void SetBool(string key, bool value) { if (value) { if (this.entries[key] == null) { this.entries.Add(key, ""); } } else { this.entries.Remove(key); } } public bool GetBool(string key) { return (this.entries[key] != null); } private void Load() { string line, name, value; string[] split; char[] space_delim = {' '}; char[] twopt_delim = {':'}; using (StreamReader sr = new StreamReader(this.FileName)) { while (sr.Peek() >= 0) { line = sr.ReadLine().Trim(); if ((line.Length > 0) && (line[0] != '#') && (line[0] != ';')) { split = line.Split(space_delim, 2); name = (split.Length > 0) ? split[0].Trim() : ""; value = (split.Length > 1) ? split[1].Trim() : ""; if (this.entries[name] == null) { this.entries.Add(name, value); } } else if (line.StartsWith("# description:")) { split = line.Split(twopt_delim, 2); this.description = (split.Length > 1) ? split[1].Trim() : ""; } } } } public void Save() { string value; this.filename = Configuration.FileFromEntry(this.name); using (StreamWriter sw = new StreamWriter(this.FileName)) { if ((this.description != "") && (this.description != null)) { sw.WriteLine("# description: " + this.description); } foreach (string key in this.entries.AllKeys) { value = this.entries[key]; sw.WriteLine(key + ((value == "") ? "" : (" " + value))); } } if (this.name != this.oldname) { File.Delete(Configuration.FileFromEntry(this.oldname)); this.oldname = this.name; } } public static void Delete(string entry) { File.Delete(Configuration.FileFromEntry(entry)); } public static CertificationInfo CertificateInfo(string key) { return new CertificationInfo(Configuration.config_dir + Path.DirectorySeparatorChar + key + ".crt"); } public static void AddKey(string file) { if (File.Exists(file)) { File.Copy(file, Configuration.config_dir + Path.DirectorySeparatorChar + Path.GetFileName(file), true); } } public static void DeleteKey(string key) { File.Delete(Configuration.FileFromKey(key)); File.Delete(Configuration.config_dir + Path.DirectorySeparatorChar + key + ".crt"); } public static void ClearKey(string key, string passphrase) { string args = String.Format("rsa -passin pass:{0} -in {1} -out {1}", passphrase, Configuration.FileFromKey(key)); Process process = new Process(); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardInput = true; process.StartInfo.CreateNoWindow = true; process.StartInfo.WorkingDirectory = Configuration.config_dir; process.StartInfo.FileName = Configuration.openssl; process.StartInfo.Arguments = args; process.EnableRaisingEvents = true; process.Start(); process.WaitForExit(); if (process.StandardError.ReadLine().Trim() != "writing RSA key") throw new Exception("Invalid passphrase"); } public static void ChangeKey(string key, string passphrase, string newpassphase) { string args = String.Format("rsa -des3 -passin pass:{0} -passout pass:{1} -in {2} -out {2}", passphrase, newpassphase, Configuration.FileFromKey(key)); Process process = new Process(); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardInput = true; process.StartInfo.CreateNoWindow = true; process.StartInfo.WorkingDirectory = Configuration.config_dir; process.StartInfo.FileName = Configuration.openssl; process.StartInfo.Arguments = args; process.EnableRaisingEvents = true; process.Start(); process.WaitForExit(); if (process.StandardError.ReadLine().Trim() != "writing RSA key") throw new Exception(Catalog.GetString("Invalid passphrase")); } public static bool Activate(string entry, ConnectionActivateDelegate input) { string contents; string filename = Configuration.FileFromEntry(entry); string path = Configuration.config_dir; string pid = pid_dir + dir_char + "openvpn." + entry + ".pid"; string log = log_dir + dir_char + entry + ".log"; string status = pid_dir + dir_char + "openvpn." + entry + ".status"; string args; if (Configuration.win32) args = String.Format("--service x 0 --writepid \"{0}\" --config \"{1}\" --log \"{2}\" --status \"{3}\" 1 --status-version 2 --cd \"{4}\"", pid, filename, log, status, path); else args = String.Format("--daemon --writepid \"{0}\" --config \"{1}\" --log \"{2}\" --status \"{3}\" 1 --status-version 2 --cd \"{4}\"", pid, filename, log, status, path); Directory.CreateDirectory(Configuration.log_dir); Process process = new Process(); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = ! Configuration.win32; process.StartInfo.RedirectStandardInput = true; process.StartInfo.CreateNoWindow = true; process.StartInfo.FileName = exe_path; process.StartInfo.Arguments = args; process.EnableRaisingEvents = false; process.Start(); process.WaitForExit(3000); contents = Configuration.GetFileContents(log); if (contents.IndexOf("Enter Auth Username") > 0) { input(process.StandardInput, true); } process.WaitForExit(2000); contents = Configuration.GetFileContents(log); if ((contents.IndexOf("Enter PEM pass phrase:") > 0) || (contents.IndexOf("Enter Private Key Pass") > 0) ) { input(process.StandardInput, false); } process.WaitForExit(5000); return File.Exists(pid); } public static void Deactivate(string entry) { string pidfile = pid_dir + dir_char + "openvpn." + entry + ".pid"; string stafile = pid_dir + dir_char + "openvpn." + entry + ".status"; int pid = Int32.Parse(Configuration.GetFileContents(pidfile, true)); Mono.Unix.Native.Syscall.kill(pid, Mono.Unix.Native.Signum.SIGTERM); File.Delete(pidfile); try { File.Delete(stafile); } catch { System.Threading.Thread.Sleep(2000); File.Delete(stafile); } } public static string LogContents(string entry) { return Configuration.GetFileContents(log_dir+dir_char+entry+".log"); } public static bool IsLocked(string entry) { return File.Exists(config_dir+dir_char+entry+".lock"); } #endregion #region Properties public string Name { get { return this.name; } set { this.name = value; } } public string FileName { get { return this.filename; } } public string Description { get { return this.description; } set { this.description = value; } } public bool Active { get { return File.Exists(Configuration.pid_dir+"/"+"openvpn."+this.Name+".pid"); } } public string this[string key] { get { string val; switch (key.ToLower()) { case "name": val = this.Name; break; case "filename": val = this.FileName; break; case "active": val = this.Active ? "Active" : "Inactive"; break; default: val = this.entries[key]; break; } return (val != null) ? val : ""; } set { if (value.Trim() == "") { this.entries.Remove(key); } else { if (this.entries[key] == null) { this.entries.Add(key, value); } else { this.entries[key] = value; } } } } //public string Proxy //{ //} public static string ConfigDir { get { return Configuration.config_dir; } } #endregion } }