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