• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #![allow(
2     clippy::inconsistent_digit_grouping,
3     clippy::uninlined_format_args,
4     clippy::unusual_byte_groupings
5 )]
6 
7 extern crate autocfg;
8 #[cfg(feature = "bindgen")]
9 extern crate bindgen;
10 extern crate cc;
11 #[cfg(feature = "vendored")]
12 extern crate openssl_src;
13 extern crate pkg_config;
14 #[cfg(target_env = "msvc")]
15 extern crate vcpkg;
16 
17 use std::collections::HashSet;
18 use std::env;
19 use std::ffi::OsString;
20 use std::path::{Path, PathBuf};
21 mod cfgs;
22 
23 mod find_normal;
24 #[cfg(feature = "vendored")]
25 mod find_vendored;
26 mod run_bindgen;
27 
28 #[derive(PartialEq)]
29 enum Version {
30     Openssl3xx,
31     Openssl11x,
32     Openssl10x,
33     Libressl,
34     Boringssl,
35 }
36 
env_inner(name: &str) -> Option<OsString>37 fn env_inner(name: &str) -> Option<OsString> {
38     let var = env::var_os(name);
39     println!("cargo:rerun-if-env-changed={}", name);
40 
41     match var {
42         Some(ref v) => println!("{} = {}", name, v.to_string_lossy()),
43         None => println!("{} unset", name),
44     }
45 
46     var
47 }
48 
env(name: &str) -> Option<OsString>49 fn env(name: &str) -> Option<OsString> {
50     let prefix = env::var("TARGET").unwrap().to_uppercase().replace('-', "_");
51     let prefixed = format!("{}_{}", prefix, name);
52     env_inner(&prefixed).or_else(|| env_inner(name))
53 }
54 
find_openssl(target: &str) -> (Vec<PathBuf>, PathBuf)55 fn find_openssl(target: &str) -> (Vec<PathBuf>, PathBuf) {
56     #[cfg(feature = "vendored")]
57     {
58         // vendor if the feature is present, unless
59         // OPENSSL_NO_VENDOR exists and isn't `0`
60         if env("OPENSSL_NO_VENDOR").map_or(true, |s| s == "0") {
61             return find_vendored::get_openssl(target);
62         }
63     }
64     find_normal::get_openssl(target)
65 }
66 
check_ssl_kind()67 fn check_ssl_kind() {
68     if cfg!(feature = "unstable_boringssl") {
69         println!("cargo:rustc-cfg=boringssl");
70         println!("cargo:boringssl=true");
71         // BoringSSL does not have any build logic, exit early
72         std::process::exit(0);
73     }
74 }
75 
main()76 fn main() {
77     check_rustc_versions();
78 
79     check_ssl_kind();
80 
81     let target = env::var("TARGET").unwrap();
82 
83     let (lib_dirs, include_dir) = find_openssl(&target);
84 
85     if !lib_dirs.iter().all(|p| Path::new(p).exists()) {
86         panic!("OpenSSL library directory does not exist: {:?}", lib_dirs);
87     }
88     if !Path::new(&include_dir).exists() {
89         panic!(
90             "OpenSSL include directory does not exist: {}",
91             include_dir.to_string_lossy()
92         );
93     }
94 
95     for lib_dir in lib_dirs.iter() {
96         println!(
97             "cargo:rustc-link-search=native={}",
98             lib_dir.to_string_lossy()
99         );
100     }
101     println!("cargo:include={}", include_dir.to_string_lossy());
102 
103     let version = postprocess(&[include_dir]);
104 
105     let libs_env = env("OPENSSL_LIBS");
106     let libs = match libs_env.as_ref().and_then(|s| s.to_str()) {
107         Some(v) => {
108             if v.is_empty() {
109                 vec![]
110             } else {
111                 v.split(':').collect()
112             }
113         }
114         None => match version {
115             Version::Openssl10x if target.contains("windows") => vec!["ssleay32", "libeay32"],
116             Version::Openssl3xx | Version::Openssl11x if target.contains("windows-msvc") => {
117                 vec!["libssl", "libcrypto"]
118             }
119             _ => vec!["ssl", "crypto"],
120         },
121     };
122 
123     let kind = determine_mode(&lib_dirs, &libs);
124     for lib in libs.into_iter() {
125         println!("cargo:rustc-link-lib={}={}", kind, lib);
126     }
127 
128     if kind == "static" && target.contains("windows") {
129         println!("cargo:rustc-link-lib=dylib=gdi32");
130         println!("cargo:rustc-link-lib=dylib=user32");
131         println!("cargo:rustc-link-lib=dylib=crypt32");
132         println!("cargo:rustc-link-lib=dylib=ws2_32");
133         println!("cargo:rustc-link-lib=dylib=advapi32");
134     }
135 }
136 
check_rustc_versions()137 fn check_rustc_versions() {
138     let cfg = autocfg::new();
139 
140     if cfg.probe_rustc_version(1, 31) {
141         println!("cargo:rustc-cfg=const_fn");
142     }
143 }
144 
145 #[allow(clippy::let_and_return)]
postprocess(include_dirs: &[PathBuf]) -> Version146 fn postprocess(include_dirs: &[PathBuf]) -> Version {
147     let version = validate_headers(include_dirs);
148 
149     // Never run bindgen for BoringSSL, if it was needed we already ran it.
150     if version != Version::Boringssl {
151         #[cfg(feature = "bindgen")]
152         run_bindgen::run(&include_dirs);
153     }
154 
155     version
156 }
157 
158 /// Validates the header files found in `include_dir` and then returns the
159 /// version string of OpenSSL.
160 #[allow(clippy::manual_strip)] // we need to support pre-1.45.0
validate_headers(include_dirs: &[PathBuf]) -> Version161 fn validate_headers(include_dirs: &[PathBuf]) -> Version {
162     // This `*-sys` crate only works with OpenSSL 1.0.1, 1.0.2, 1.1.0, 1.1.1 and 3.0.0.
163     // To correctly expose the right API from this crate, take a look at
164     // `opensslv.h` to see what version OpenSSL claims to be.
165     //
166     // OpenSSL has a number of build-time configuration options which affect
167     // various structs and such. Since OpenSSL 1.1.0 this isn't really a problem
168     // as the library is much more FFI-friendly, but 1.0.{1,2} suffer this problem.
169     //
170     // To handle all this conditional compilation we slurp up the configuration
171     // file of OpenSSL, `opensslconf.h`, and then dump out everything it defines
172     // as our own #[cfg] directives. That way the `ossl10x.rs` bindings can
173     // account for compile differences and such.
174     println!("cargo:rerun-if-changed=build/expando.c");
175     let mut gcc = cc::Build::new();
176     for include_dir in include_dirs {
177         gcc.include(include_dir);
178     }
179     let expanded = match gcc.file("build/expando.c").try_expand() {
180         Ok(expanded) => expanded,
181         Err(e) => {
182             panic!(
183                 "
184 Header expansion error:
185 {:?}
186 
187 Failed to find OpenSSL development headers.
188 
189 You can try fixing this setting the `OPENSSL_DIR` environment variable
190 pointing to your OpenSSL installation or installing OpenSSL headers package
191 specific to your distribution:
192 
193     # On Ubuntu
194     sudo apt-get install libssl-dev
195     # On Arch Linux
196     sudo pacman -S openssl
197     # On Fedora
198     sudo dnf install openssl-devel
199     # On Alpine Linux
200     apk add openssl-dev
201 
202 See rust-openssl documentation for more information:
203 
204     https://docs.rs/openssl
205 ",
206                 e
207             );
208         }
209     };
210     let expanded = String::from_utf8(expanded).unwrap();
211 
212     let mut enabled = vec![];
213     let mut openssl_version = None;
214     let mut libressl_version = None;
215     let mut is_boringssl = false;
216     for line in expanded.lines() {
217         let line = line.trim();
218 
219         let openssl_prefix = "RUST_VERSION_OPENSSL_";
220         let new_openssl_prefix = "RUST_VERSION_NEW_OPENSSL_";
221         let libressl_prefix = "RUST_VERSION_LIBRESSL_";
222         let boringsl_prefix = "RUST_OPENSSL_IS_BORINGSSL";
223         let conf_prefix = "RUST_CONF_";
224         if line.starts_with(openssl_prefix) {
225             let version = &line[openssl_prefix.len()..];
226             openssl_version = Some(parse_version(version));
227         } else if line.starts_with(new_openssl_prefix) {
228             let version = &line[new_openssl_prefix.len()..];
229             openssl_version = Some(parse_new_version(version));
230         } else if line.starts_with(libressl_prefix) {
231             let version = &line[libressl_prefix.len()..];
232             libressl_version = Some(parse_version(version));
233         } else if line.starts_with(conf_prefix) {
234             enabled.push(&line[conf_prefix.len()..]);
235         } else if line.starts_with(boringsl_prefix) {
236             is_boringssl = true;
237         }
238     }
239 
240     if is_boringssl {
241         println!("cargo:rustc-cfg=boringssl");
242         println!("cargo:boringssl=true");
243         run_bindgen::run_boringssl(include_dirs);
244         return Version::Boringssl;
245     }
246 
247     // We set this for any non-BoringSSL lib.
248     println!("cargo:rustc-cfg=openssl");
249 
250     for enabled in &enabled {
251         println!("cargo:rustc-cfg=osslconf=\"{}\"", enabled);
252     }
253     println!("cargo:conf={}", enabled.join(","));
254 
255     for cfg in cfgs::get(openssl_version, libressl_version) {
256         println!("cargo:rustc-cfg={}", cfg);
257     }
258 
259     if let Some(libressl_version) = libressl_version {
260         println!("cargo:libressl_version_number={:x}", libressl_version);
261 
262         let major = (libressl_version >> 28) as u8;
263         let minor = (libressl_version >> 20) as u8;
264         let fix = (libressl_version >> 12) as u8;
265         let (major, minor, fix) = match (major, minor, fix) {
266             (2, 5, 0) => ('2', '5', '0'),
267             (2, 5, 1) => ('2', '5', '1'),
268             (2, 5, 2) => ('2', '5', '2'),
269             (2, 5, _) => ('2', '5', 'x'),
270             (2, 6, 0) => ('2', '6', '0'),
271             (2, 6, 1) => ('2', '6', '1'),
272             (2, 6, 2) => ('2', '6', '2'),
273             (2, 6, _) => ('2', '6', 'x'),
274             (2, 7, _) => ('2', '7', 'x'),
275             (2, 8, 0) => ('2', '8', '0'),
276             (2, 8, 1) => ('2', '8', '1'),
277             (2, 8, _) => ('2', '8', 'x'),
278             (2, 9, 0) => ('2', '9', '0'),
279             (2, 9, _) => ('2', '9', 'x'),
280             (3, 0, 0) => ('3', '0', '0'),
281             (3, 0, 1) => ('3', '0', '1'),
282             (3, 0, _) => ('3', '0', 'x'),
283             (3, 1, 0) => ('3', '1', '0'),
284             (3, 1, _) => ('3', '1', 'x'),
285             (3, 2, 0) => ('3', '2', '0'),
286             (3, 2, 1) => ('3', '2', '1'),
287             (3, 2, _) => ('3', '2', 'x'),
288             (3, 3, 0) => ('3', '3', '0'),
289             (3, 3, 1) => ('3', '3', '1'),
290             (3, 3, _) => ('3', '3', 'x'),
291             (3, 4, 0) => ('3', '4', '0'),
292             (3, 4, _) => ('3', '4', 'x'),
293             (3, 5, _) => ('3', '5', 'x'),
294             (3, 6, 0) => ('3', '6', '0'),
295             (3, 6, _) => ('3', '6', 'x'),
296             (3, 7, 0) => ('3', '7', '0'),
297             (3, 7, 1) => ('3', '7', '1'),
298             _ => version_error(),
299         };
300 
301         println!("cargo:libressl=true");
302         println!("cargo:libressl_version={}{}{}", major, minor, fix);
303         println!("cargo:version=101");
304         Version::Libressl
305     } else {
306         let openssl_version = openssl_version.unwrap();
307         println!("cargo:version_number={:x}", openssl_version);
308 
309         if openssl_version >= 0x4_00_00_00_0 {
310             version_error()
311         } else if openssl_version >= 0x3_00_00_00_0 {
312             Version::Openssl3xx
313         } else if openssl_version >= 0x1_01_01_00_0 {
314             println!("cargo:version=111");
315             Version::Openssl11x
316         } else if openssl_version >= 0x1_01_00_06_0 {
317             println!("cargo:version=110");
318             println!("cargo:patch=f");
319             Version::Openssl11x
320         } else if openssl_version >= 0x1_01_00_00_0 {
321             println!("cargo:version=110");
322             Version::Openssl11x
323         } else if openssl_version >= 0x1_00_02_00_0 {
324             println!("cargo:version=102");
325             Version::Openssl10x
326         } else if openssl_version >= 0x1_00_01_00_0 {
327             println!("cargo:version=101");
328             Version::Openssl10x
329         } else {
330             version_error()
331         }
332     }
333 }
334 
version_error() -> !335 fn version_error() -> ! {
336     panic!(
337         "
338 
339 This crate is only compatible with OpenSSL (version 1.0.1 through 1.1.1, or 3.0.0), or LibreSSL 2.5
340 through 3.7.1, but a different version of OpenSSL was found. The build is now aborting
341 due to this version mismatch.
342 
343 "
344     );
345 }
346 
347 // parses a string that looks like "0x100020cfL"
348 #[allow(deprecated)] // trim_right_matches is now trim_end_matches
349 #[allow(clippy::match_like_matches_macro)] // matches macro requires rust 1.42.0
parse_version(version: &str) -> u64350 fn parse_version(version: &str) -> u64 {
351     // cut off the 0x prefix
352     assert!(version.starts_with("0x"));
353     let version = &version[2..];
354 
355     // and the type specifier suffix
356     let version = version.trim_right_matches(|c: char| match c {
357         '0'..='9' | 'a'..='f' | 'A'..='F' => false,
358         _ => true,
359     });
360 
361     u64::from_str_radix(version, 16).unwrap()
362 }
363 
364 // parses a string that looks like 3_0_0
parse_new_version(version: &str) -> u64365 fn parse_new_version(version: &str) -> u64 {
366     println!("version: {}", version);
367     let mut it = version.split('_');
368     let major = it.next().unwrap().parse::<u64>().unwrap();
369     let minor = it.next().unwrap().parse::<u64>().unwrap();
370     let patch = it.next().unwrap().parse::<u64>().unwrap();
371 
372     (major << 28) | (minor << 20) | (patch << 4)
373 }
374 
375 /// Given a libdir for OpenSSL (where artifacts are located) as well as the name
376 /// of the libraries we're linking to, figure out whether we should link them
377 /// statically or dynamically.
determine_mode(libdirs: &[PathBuf], libs: &[&str]) -> &'static str378 fn determine_mode(libdirs: &[PathBuf], libs: &[&str]) -> &'static str {
379     // First see if a mode was explicitly requested
380     let kind = env("OPENSSL_STATIC");
381     match kind.as_ref().and_then(|s| s.to_str()) {
382         Some("0") => return "dylib",
383         Some(_) => return "static",
384         None => {}
385     }
386 
387     // Next, see what files we actually have to link against, and see what our
388     // possibilities even are.
389     let mut files = HashSet::new();
390     for dir in libdirs {
391         for path in dir
392             .read_dir()
393             .unwrap()
394             .map(|e| e.unwrap())
395             .map(|e| e.file_name())
396             .filter_map(|e| e.into_string().ok())
397         {
398             files.insert(path);
399         }
400     }
401     let can_static = libs
402         .iter()
403         .all(|l| files.contains(&format!("lib{}.a", l)) || files.contains(&format!("{}.lib", l)));
404     let can_dylib = libs.iter().all(|l| {
405         files.contains(&format!("lib{}.so", l))
406             || files.contains(&format!("{}.dll", l))
407             || files.contains(&format!("lib{}.dylib", l))
408     });
409     match (can_static, can_dylib) {
410         (true, false) => return "static",
411         (false, true) => return "dylib",
412         (false, false) => {
413             panic!(
414                 "OpenSSL libdir at `{:?}` does not contain the required files \
415                  to either statically or dynamically link OpenSSL",
416                 libdirs
417             );
418         }
419         (true, true) => {}
420     }
421 
422     // Ok, we've got not explicit preference and can *either* link statically or
423     // link dynamically. In the interest of "security upgrades" and/or "best
424     // practices with security libs", let's link dynamically.
425     "dylib"
426 }
427