1 extern crate autocfg;
2 extern crate cc;
3 #[cfg(feature = "vendored")]
4 extern crate openssl_src;
5 extern crate pkg_config;
6 #[cfg(target_env = "msvc")]
7 extern crate vcpkg;
8 
9 use std::collections::HashSet;
10 use std::env;
11 use std::ffi::OsString;
12 use std::path::{Path, PathBuf};
13 
14 mod cfgs;
15 
16 #[cfg_attr(feature = "vendored", path = "find_vendored.rs")]
17 #[cfg_attr(not(feature = "vendored"), path = "find_normal.rs")]
18 mod find;
19 
20 enum Version {
21     Openssl11x,
22     Openssl10x,
23     Libressl,
24 }
25 
env_inner(name: &str) -> Option<OsString>26 fn env_inner(name: &str) -> Option<OsString> {
27     let var = env::var_os(name);
28     println!("cargo:rerun-if-env-changed={}", name);
29 
30     match var {
31         Some(ref v) => println!("{} = {}", name, v.to_string_lossy()),
32         None => println!("{} unset", name),
33     }
34 
35     var
36 }
37 
env(name: &str) -> Option<OsString>38 fn env(name: &str) -> Option<OsString> {
39     let prefix = env::var("TARGET").unwrap().to_uppercase().replace("-", "_");
40     let prefixed = format!("{}_{}", prefix, name);
41     env_inner(&prefixed).or_else(|| env_inner(name))
42 }
43 
main()44 fn main() {
45     check_rustc_versions();
46 
47     let target = env::var("TARGET").unwrap();
48 
49     let (lib_dir, include_dir) = find::get_openssl(&target);
50 
51     if !Path::new(&lib_dir).exists() {
52         panic!(
53             "OpenSSL library directory does not exist: {}",
54             lib_dir.to_string_lossy()
55         );
56     }
57     if !Path::new(&include_dir).exists() {
58         panic!(
59             "OpenSSL include directory does not exist: {}",
60             include_dir.to_string_lossy()
61         );
62     }
63 
64     println!(
65         "cargo:rustc-link-search=native={}",
66         lib_dir.to_string_lossy()
67     );
68     println!("cargo:include={}", include_dir.to_string_lossy());
69 
70     let version = validate_headers(&[include_dir.clone().into()]);
71 
72     let libs_env = env("OPENSSL_LIBS");
73     let libs = match libs_env.as_ref().and_then(|s| s.to_str()) {
74         Some(ref v) => v.split(":").collect(),
75         None => match version {
76             Version::Openssl10x if target.contains("windows") => vec!["ssleay32", "libeay32"],
77             Version::Openssl11x if target.contains("windows") => vec!["libssl", "libcrypto"],
78             _ => vec!["ssl", "crypto"],
79         },
80     };
81 
82     let kind = determine_mode(Path::new(&lib_dir), &libs);
83     for lib in libs.into_iter() {
84         println!("cargo:rustc-link-lib={}={}", kind, lib);
85     }
86 
87     if kind == "static" && target.contains("windows") {
88         println!("cargo:rustc-link-lib=dylib=gdi32");
89         println!("cargo:rustc-link-lib=dylib=user32");
90         println!("cargo:rustc-link-lib=dylib=crypt32");
91         println!("cargo:rustc-link-lib=dylib=ws2_32");
92         println!("cargo:rustc-link-lib=dylib=advapi32");
93     }
94 }
95 
check_rustc_versions()96 fn check_rustc_versions() {
97     let cfg = autocfg::new();
98 
99     if cfg.probe_rustc_version(1, 31) {
100         println!("cargo:rustc-cfg=const_fn");
101     }
102 }
103 
104 /// Validates the header files found in `include_dir` and then returns the
105 /// version string of OpenSSL.
validate_headers(include_dirs: &[PathBuf]) -> Version106 fn validate_headers(include_dirs: &[PathBuf]) -> Version {
107     // This `*-sys` crate only works with OpenSSL 1.0.1, 1.0.2, and 1.1.0. To
108     // correctly expose the right API from this crate, take a look at
109     // `opensslv.h` to see what version OpenSSL claims to be.
110     //
111     // OpenSSL has a number of build-time configuration options which affect
112     // various structs and such. Since OpenSSL 1.1.0 this isn't really a problem
113     // as the library is much more FFI-friendly, but 1.0.{1,2} suffer this problem.
114     //
115     // To handle all this conditional compilation we slurp up the configuration
116     // file of OpenSSL, `opensslconf.h`, and then dump out everything it defines
117     // as our own #[cfg] directives. That way the `ossl10x.rs` bindings can
118     // account for compile differences and such.
119     let mut gcc = cc::Build::new();
120     for include_dir in include_dirs {
121         gcc.include(include_dir);
122     }
123     let expanded = match gcc.file("build/expando.c").try_expand() {
124         Ok(expanded) => expanded,
125         Err(e) => {
126             panic!(
127                 "
128 Header expansion error:
129 {:?}
130 
131 Failed to find OpenSSL development headers.
132 
133 You can try fixing this setting the `OPENSSL_DIR` environment variable
134 pointing to your OpenSSL installation or installing OpenSSL headers package
135 specific to your distribution:
136 
137     # On Ubuntu
138     sudo apt-get install libssl-dev
139     # On Arch Linux
140     sudo pacman -S openssl
141     # On Fedora
142     sudo dnf install openssl-devel
143 
144 See rust-openssl README for more information:
145 
146     https://github.com/sfackler/rust-openssl#linux
147 ",
148                 e
149             );
150         }
151     };
152     let expanded = String::from_utf8(expanded).unwrap();
153 
154     let mut enabled = vec![];
155     let mut openssl_version = None;
156     let mut libressl_version = None;
157     for line in expanded.lines() {
158         let line = line.trim();
159 
160         let openssl_prefix = "RUST_VERSION_OPENSSL_";
161         let libressl_prefix = "RUST_VERSION_LIBRESSL_";
162         let conf_prefix = "RUST_CONF_";
163         if line.starts_with(openssl_prefix) {
164             let version = &line[openssl_prefix.len()..];
165             openssl_version = Some(parse_version(version));
166         } else if line.starts_with(libressl_prefix) {
167             let version = &line[libressl_prefix.len()..];
168             libressl_version = Some(parse_version(version));
169         } else if line.starts_with(conf_prefix) {
170             enabled.push(&line[conf_prefix.len()..]);
171         }
172     }
173 
174     for enabled in &enabled {
175         println!("cargo:rustc-cfg=osslconf=\"{}\"", enabled);
176     }
177     println!("cargo:conf={}", enabled.join(","));
178 
179     for cfg in cfgs::get(openssl_version, libressl_version) {
180         println!("cargo:rustc-cfg={}", cfg);
181     }
182 
183     if let Some(libressl_version) = libressl_version {
184         println!("cargo:libressl_version_number={:x}", libressl_version);
185 
186         let major = (libressl_version >> 28) as u8;
187         let minor = (libressl_version >> 20) as u8;
188         let fix = (libressl_version >> 12) as u8;
189         let (major, minor, fix) = match (major, minor, fix) {
190             (2, 5, 0) => ('2', '5', '0'),
191             (2, 5, 1) => ('2', '5', '1'),
192             (2, 5, 2) => ('2', '5', '2'),
193             (2, 5, _) => ('2', '5', 'x'),
194             (2, 6, 0) => ('2', '6', '0'),
195             (2, 6, 1) => ('2', '6', '1'),
196             (2, 6, 2) => ('2', '6', '2'),
197             (2, 6, _) => ('2', '6', 'x'),
198             (2, 7, _) => ('2', '7', 'x'),
199             (2, 8, 0) => ('2', '8', '0'),
200             (2, 8, 1) => ('2', '8', '1'),
201             (2, 8, _) => ('2', '8', 'x'),
202             (2, 9, 0) => ('2', '9', '0'),
203             (2, 9, _) => ('2', '9', 'x'),
204             (3, 0, 0) => ('3', '0', '0'),
205             (3, 0, 1) => ('3', '0', '1'),
206             (3, 0, _) => ('3', '0', 'x'),
207             (3, 1, _) => ('3', '1', 'x'),
208             (3, 2, _) => ('3', '2', 'x'),
209             _ => version_error(),
210         };
211 
212         println!("cargo:libressl=true");
213         println!("cargo:libressl_version={}{}{}", major, minor, fix);
214         println!("cargo:version=101");
215         Version::Libressl
216     } else {
217         let openssl_version = openssl_version.unwrap();
218         println!("cargo:version_number={:x}", openssl_version);
219 
220         if openssl_version >= 0x1_01_02_00_0 {
221             version_error()
222         } else if openssl_version >= 0x1_01_01_00_0 {
223             println!("cargo:version=111");
224             Version::Openssl11x
225         } else if openssl_version >= 0x1_01_00_06_0 {
226             println!("cargo:version=110");
227             println!("cargo:patch=f");
228             Version::Openssl11x
229         } else if openssl_version >= 0x1_01_00_00_0 {
230             println!("cargo:version=110");
231             Version::Openssl11x
232         } else if openssl_version >= 0x1_00_02_00_0 {
233             println!("cargo:version=102");
234             Version::Openssl10x
235         } else if openssl_version >= 0x1_00_01_00_0 {
236             println!("cargo:version=101");
237             Version::Openssl10x
238         } else {
239             version_error()
240         }
241     }
242 }
243 
version_error() -> !244 fn version_error() -> ! {
245     panic!(
246         "
247 
248 This crate is only compatible with OpenSSL 1.0.1 through 1.1.1, or LibreSSL 2.5
249 through 3.2.x, but a different version of OpenSSL was found. The build is now aborting
250 due to this version mismatch.
251 
252 "
253     );
254 }
255 
256 // parses a string that looks like "0x100020cfL"
257 #[allow(deprecated)] // trim_right_matches is now trim_end_matches
parse_version(version: &str) -> u64258 fn parse_version(version: &str) -> u64 {
259     // cut off the 0x prefix
260     assert!(version.starts_with("0x"));
261     let version = &version[2..];
262 
263     // and the type specifier suffix
264     let version = version.trim_right_matches(|c: char| match c {
265         '0'..='9' | 'a'..='f' | 'A'..='F' => false,
266         _ => true,
267     });
268 
269     u64::from_str_radix(version, 16).unwrap()
270 }
271 
272 /// Given a libdir for OpenSSL (where artifacts are located) as well as the name
273 /// of the libraries we're linking to, figure out whether we should link them
274 /// statically or dynamically.
determine_mode(libdir: &Path, libs: &[&str]) -> &'static str275 fn determine_mode(libdir: &Path, libs: &[&str]) -> &'static str {
276     // First see if a mode was explicitly requested
277     let kind = env("OPENSSL_STATIC");
278     match kind.as_ref().and_then(|s| s.to_str()).map(|s| &s[..]) {
279         Some("0") => return "dylib",
280         Some(_) => return "static",
281         None => {}
282     }
283 
284     // Next, see what files we actually have to link against, and see what our
285     // possibilities even are.
286     let files = libdir
287         .read_dir()
288         .unwrap()
289         .map(|e| e.unwrap())
290         .map(|e| e.file_name())
291         .filter_map(|e| e.into_string().ok())
292         .collect::<HashSet<_>>();
293     let can_static = libs
294         .iter()
295         .all(|l| files.contains(&format!("lib{}.a", l)) || files.contains(&format!("{}.lib", l)));
296     let can_dylib = libs.iter().all(|l| {
297         files.contains(&format!("lib{}.so", l))
298             || files.contains(&format!("{}.dll", l))
299             || files.contains(&format!("lib{}.dylib", l))
300     });
301     match (can_static, can_dylib) {
302         (true, false) => return "static",
303         (false, true) => return "dylib",
304         (false, false) => {
305             panic!(
306                 "OpenSSL libdir at `{}` does not contain the required files \
307                  to either statically or dynamically link OpenSSL",
308                 libdir.display()
309             );
310         }
311         (true, true) => {}
312     }
313 
314     // Ok, we've got not explicit preference and can *either* link statically or
315     // link dynamically. In the interest of "security upgrades" and/or "best
316     // practices with security libs", let's link dynamically.
317     "dylib"
318 }
319