1 use std::ffi::OsString;
2 use std::path::{Path, PathBuf};
3 use std::process::{self, Command};
4 
5 use super::env;
6 
get_opensslnull7 pub fn get_openssl(target: &str) -> (Vec<PathBuf>, PathBuf) {
8     let lib_dir = env("OPENSSL_LIB_DIR").map(PathBuf::from);
9     let include_dir = env("OPENSSL_INCLUDE_DIR").map(PathBuf::from);
10 
11     match (lib_dir, include_dir) {
12         (Some(lib_dir), Some(include_dir)) => (vec![lib_dir], include_dir),
13         (lib_dir, include_dir) => {
14             let openssl_dir = env("OPENSSL_DIR").unwrap_or_else(|| find_openssl_dir(target));
15             let openssl_dir = Path::new(&openssl_dir);
16             let lib_dir = lib_dir.map(|d| vec![d]).unwrap_or_else(|| {
17                 let mut lib_dirs = vec![];
18                 // OpenSSL 3.0 now puts it's libraries in lib64/ by default,
19                 // check for both it and lib/.
20                 if openssl_dir.join("lib64").exists() {
21                     lib_dirs.push(openssl_dir.join("lib64"));
22                 }
23                 if openssl_dir.join("lib").exists() {
24                     lib_dirs.push(openssl_dir.join("lib"));
25                 }
26                 lib_dirs
27             });
28             let include_dir = include_dir.unwrap_or_else(|| openssl_dir.join("include"));
29             (lib_dir, include_dir)
30         }
31     }
32 }
33 
resolve_with_wellknown_homebrew_locationnull34 fn resolve_with_wellknown_homebrew_location(dir: &str) -> Option<PathBuf> {
35     let versions = ["openssl@3", "openssl@1.1"];
36 
37     // Check up default aarch 64 Homebrew installation location first
38     // for quick resolution if possible.
39     //  `pkg-config` on brew doesn't necessarily contain settings for openssl apparently.
40     for version in &versions {
41         let homebrew = Path::new(dir).join(format!("opt/{}", version));
42         if homebrew.exists() {
43             return Some(homebrew);
44         }
45     }
46 
47     for version in &versions {
48         // Calling `brew --prefix <package>` command usually slow and
49         // takes seconds, and will be used only as a last resort.
50         let output = execute_command_and_get_output("brew", &["--prefix", version]);
51         if let Some(ref output) = output {
52             let homebrew = Path::new(&output);
53             if homebrew.exists() {
54                 return Some(homebrew.to_path_buf());
55             }
56         }
57     }
58 
59     None
60 }
61 
resolve_with_wellknown_locationnull62 fn resolve_with_wellknown_location(dir: &str) -> Option<PathBuf> {
63     let root_dir = Path::new(dir);
64     let include_openssl = root_dir.join("include/openssl");
65     if include_openssl.exists() {
66         Some(root_dir.to_path_buf())
67     } else {
68         None
69     }
70 }
71 
find_openssl_dirnull72 fn find_openssl_dir(target: &str) -> OsString {
73     let host = env::var("HOST").unwrap();
74 
75     if host == target && target.ends_with("-apple-darwin") {
76         let homebrew_dir = match target {
77             "aarch64-apple-darwin" => "/opt/homebrew",
78             _ => "/usr/local",
79         };
80 
81         if let Some(dir) = resolve_with_wellknown_homebrew_location(homebrew_dir) {
82             return dir.into();
83         } else if let Some(dir) = resolve_with_wellknown_location("/opt/pkg") {
84             // pkgsrc
85             return dir.into();
86         } else if let Some(dir) = resolve_with_wellknown_location("/opt/local") {
87             // MacPorts
88             return dir.into();
89         }
90     }
91 
92     try_pkg_config();
93     try_vcpkg();
94 
95     // FreeBSD and OpenBSD ship with Libre|OpenSSL but don't include a pkg-config file
96     if host == target && (target.contains("freebsd") || target.contains("openbsd")) {
97         return OsString::from("/usr");
98     }
99 
100     // DragonFly has libressl (or openssl) in ports, but this doesn't include a pkg-config file
101     if host == target && target.contains("dragonfly") {
102         return OsString::from("/usr/local");
103     }
104 
105     let mut msg = format!(
106         "
107 
108 Could not find directory of OpenSSL installation, and this `-sys` crate cannot
109 proceed without this knowledge. If OpenSSL is installed and this crate had
110 trouble finding it,  you can set the `OPENSSL_DIR` environment variable for the
111 compilation process.
112 
113 Make sure you also have the development packages of openssl installed.
114 For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
115 
116 If you're in a situation where you think the directory *should* be found
117 automatically, please open a bug at https://github.com/sfackler/rust-openssl
118 and include information about your system as well as this message.
119 
120 $HOST = {}
121 $TARGET = {}
122 openssl-sys = {}
123 
124 ",
125         host,
126         target,
127         env!("CARGO_PKG_VERSION")
128     );
129 
130     if host.contains("apple-darwin") && target.contains("apple-darwin") {
131         let system = Path::new("/usr/lib/libssl.0.9.8.dylib");
132         if system.exists() {
133             msg.push_str(
134                 "
135 
136 openssl-sys crate build failed: no supported version of OpenSSL found.
137 
138 Ways to fix it:
139 - Use the `vendored` feature of openssl-sys crate to build OpenSSL from source.
140 - Use Homebrew to install the `openssl` package.
141 
142 ",
143             );
144         }
145     }
146 
147     if host.contains("unknown-linux")
148         && target.contains("unknown-linux-gnu")
149         && Command::new("pkg-config").output().is_err()
150     {
151         msg.push_str(
152             "
153 It looks like you're compiling on Linux and also targeting Linux. Currently this
154 requires the `pkg-config` utility to find OpenSSL but unfortunately `pkg-config`
155 could not be found. If you have OpenSSL installed you can likely fix this by
156 installing `pkg-config`.
157 
158 ",
159         );
160     }
161 
162     if host.contains("windows") && target.contains("windows-gnu") {
163         msg.push_str(
164             "
165 It looks like you're compiling for MinGW but you may not have either OpenSSL or
166 pkg-config installed. You can install these two dependencies with:
167 
168 pacman -S openssl-devel pkg-config
169 
170 and try building this crate again.
171 
172 ",
173         );
174     }
175 
176     if host.contains("windows") && target.contains("windows-msvc") {
177         msg.push_str(
178             "
179 It looks like you're compiling for MSVC but we couldn't detect an OpenSSL
180 installation. If there isn't one installed then you can try the rust-openssl
181 README for more information about how to download precompiled binaries of
182 OpenSSL:
183 
184 https://github.com/sfackler/rust-openssl#windows
185 
186 ",
187         );
188     }
189 
190     panic!("{}", msg);
191 }
192 
193 /// Attempt to find OpenSSL through pkg-config.
194 ///
195 /// Note that if this succeeds then the function does not return as pkg-config
196 /// typically tells us all the information that we need.
try_pkg_confignull197 fn try_pkg_config() {
198     let target = env::var("TARGET").unwrap();
199     let host = env::var("HOST").unwrap();
200 
201     // FIXME we really shouldn't be automatically enabling this
202     if target.contains("windows-gnu") && host.contains("windows") {
203         env::set_var("PKG_CONFIG_ALLOW_CROSS", "1");
204     } else if target.contains("windows-msvc") {
205         // MSVC targets use vcpkg instead.
206         return;
207     }
208 
209     let lib = match pkg_config::Config::new()
210         .print_system_libs(false)
211         .probe("openssl")
212     {
213         Ok(lib) => lib,
214         Err(e) => {
215             println!("run pkg_config fail: {:?}", e);
216             return;
217         }
218     };
219 
220     super::postprocess(&lib.include_paths);
221 
222     for include in lib.include_paths.iter() {
223         println!("cargo:include={}", include.display());
224     }
225 
226     process::exit(0);
227 }
228 
229 /// Attempt to find OpenSSL through vcpkg.
230 ///
231 /// Note that if this succeeds then the function does not return as vcpkg
232 /// should emit all of the cargo metadata that we need.
try_vcpkgnull233 fn try_vcpkg() {
234     let target = env::var("TARGET").unwrap();
235     if !target.contains("windows") {
236         return;
237     }
238 
239     // vcpkg will not emit any metadata if it can not find libraries
240     // appropriate for the target triple with the desired linkage.
241 
242     let lib = match vcpkg::Config::new()
243         .emit_includes(true)
244         .find_package("openssl")
245     {
246         Ok(lib) => lib,
247         Err(e) => {
248             println!("note: vcpkg did not find openssl: {}", e);
249             return;
250         }
251     };
252 
253     super::postprocess(&lib.include_paths);
254 
255     println!("cargo:rustc-link-lib=user32");
256     println!("cargo:rustc-link-lib=gdi32");
257     println!("cargo:rustc-link-lib=crypt32");
258 
259     process::exit(0);
260 }
261 
execute_command_and_get_outputnull262 fn execute_command_and_get_output(cmd: &str, args: &[&str]) -> Option<String> {
263     let out = Command::new(cmd).args(args).output();
264     if let Ok(ref r1) = out {
265         if r1.status.success() {
266             let r2 = String::from_utf8(r1.stdout.clone());
267             if let Ok(r3) = r2 {
268                 return Some(r3.trim().to_string());
269             }
270         }
271     }
272 
273     None
274 }
275