1 /++ 2 Bits and bobs that automate downloading SSL libraries and related necessities on Windows. 3 4 TODO: Replace with Windows Secure Channel SSL. 5 6 See_Also: 7 [kameloso.net] 8 9 Copyright: [JR](https://github.com/zorael) 10 License: [Boost Software License 1.0](https://www.boost.org/users/license.html) 11 12 Authors: 13 [JR](https://github.com/zorael) 14 +/ 15 module kameloso.ssldownloads; 16 17 version(Windows): 18 19 private: 20 21 import kameloso.kameloso : Kameloso; 22 import std.typecons : Flag, No, Yes; 23 24 public: 25 26 27 // downloadWindowsSSL 28 /++ 29 Downloads OpenSSL for Windows and/or a `cacert.pem` certificate bundle from 30 the cURL project, extracted from Mozilla Firefox. 31 32 If `--force` was not supplied, the configuration file is updated with "`cacert.pem`" 33 entered as `caBundle`. If it is supplied, the value is still changed but to the 34 absolute path to the file, and the configuration file is not implicitly updated. 35 (`--save` will have to be separately passed.) 36 37 Params: 38 instance = Reference to the current [kameloso.kameloso.Kameloso|Kameloso]. 39 shouldDownloadCacert = Whether or not `cacert.pem` should be downloaded. 40 shouldDownloadOpenSSL = Whether or not OpenSSL for Windows should be downloaded. 41 42 Returns: 43 `Yes.settingsTouched` if [kameloso.kameloso.Kameloso.settings|Kameloso.settings] 44 were touched and the configuration file should be updated; `No.settingsTouched` if not. 45 +/ 46 auto downloadWindowsSSL( 47 ref Kameloso instance, 48 const Flag!"shouldDownloadCacert" shouldDownloadCacert, 49 const Flag!"shouldDownloadOpenSSL" shouldDownloadOpenSSL) 50 { 51 import kameloso.common : logger; 52 import std.path : buildNormalizedPath; 53 54 static int downloadFile(const string url, const string what, const string saveAs) 55 { 56 import std.format : format; 57 import std.process : executeShell; 58 59 enum pattern = "Downloading %s from <l>%s</>..."; 60 logger.infof(pattern, what, url); 61 62 enum executePattern = `powershell -c "Invoke-WebRequest '%s' -OutFile '%s'"`; 63 immutable result = executeShell(executePattern.format(url, saveAs)); 64 65 if (result.status != 0) 66 { 67 enum errorPattern = "Download process failed with status <l>%d</>!"; 68 logger.errorf(errorPattern, result.status); 69 70 version(PrintStacktraces) 71 { 72 import std.stdio : stdout, writeln; 73 import std.string : chomp; 74 75 immutable output = result.output.chomp; 76 77 if (output.length) 78 { 79 writeln(output); 80 stdout.flush(); 81 } 82 } 83 } 84 85 return result.status; 86 } 87 88 Flag!"settingsTouched" retval; 89 90 if (shouldDownloadCacert) 91 { 92 import kameloso.string : doublyBackslashed; 93 import std.path : dirName; 94 95 enum cacertURL = "http://curl.se/ca/cacert.pem"; 96 immutable configDir = instance.settings.configFile.dirName; 97 immutable cacertFile = buildNormalizedPath(configDir, "cacert.pem"); 98 immutable result = downloadFile(cacertURL, "certificate bundle", cacertFile); 99 if (*instance.abort) return No.settingsTouched; 100 101 if (result == 0) 102 { 103 if (!instance.settings.force) 104 { 105 enum cacertPattern = "File saved as <l>%s</>; configuration updated."; 106 logger.infof(cacertPattern, cacertFile.doublyBackslashed); 107 instance.connSettings.caBundleFile = "cacert.pem"; // cacertFile 108 retval = Yes.settingsTouched; 109 } 110 else 111 { 112 enum cacertPattern = "File saved as <l>%s</>."; 113 logger.infof(cacertPattern, cacertFile.doublyBackslashed); 114 instance.connSettings.caBundleFile = cacertFile; // absolute path 115 //retval = Yes.settingsTouched; // let user supply --save 116 } 117 } 118 } 119 120 if (shouldDownloadOpenSSL) 121 { 122 import std.file : mkdirRecurse, tempDir; 123 import std.json : JSONException; 124 import std.process : ProcessException; 125 126 immutable temporaryDir = buildNormalizedPath(tempDir, "kameloso"); 127 mkdirRecurse(temporaryDir); 128 129 enum jsonURL = "https://raw.githubusercontent.com/slproweb/opensslhashes/master/win32_openssl_hashes.json"; 130 immutable jsonFile = buildNormalizedPath(temporaryDir, "win32_openssl_hashes.json"); 131 immutable result = downloadFile(jsonURL, "manifest", jsonFile); 132 if (*instance.abort) return No.settingsTouched; 133 if (result != 0) return retval; 134 135 try 136 { 137 import std.file : readText; 138 import std.json : parseJSON; 139 140 const hashesJSON = parseJSON(readText(jsonFile)); 141 142 foreach (immutable filename, fileEntryJSON; hashesJSON["files"].object) 143 { 144 import lu.string : beginsWith; 145 import std.algorithm.searching : endsWith; 146 147 version(Win64) 148 { 149 enum head = "Win64OpenSSL_Light-1_"; 150 } 151 else /*version(Win32)*/ 152 { 153 enum head = "Win32OpenSSL_Light-1_"; 154 } 155 156 if (filename.beginsWith(head) && filename.endsWith(".exe")) 157 { 158 import std.process : execute; 159 160 immutable exeFile = buildNormalizedPath(temporaryDir, filename); 161 immutable downloadResult = downloadFile(fileEntryJSON["url"].str, "OpenSSL installer", exeFile); 162 if (*instance.abort) return No.settingsTouched; 163 if (downloadResult != 0) break; 164 165 logger.info("Launching installer."); 166 cast(void)execute([ exeFile ]); 167 168 return retval; 169 } 170 } 171 172 logger.error("Could not find OpenSSL .exe to download"); 173 // Drop down and return 174 } 175 catch (JSONException e) 176 { 177 enum pattern = "Error parsing file containing OpenSSL download links: <l>%s"; 178 logger.errorf(pattern, e.msg); 179 } 180 catch (ProcessException e) 181 { 182 enum pattern = "Error starting installer: <l>%s"; 183 logger.errorf(pattern, e.msg); 184 } 185 } 186 187 return retval; 188 }