##// END OF EJS Templates
sslutil: move code examining _canloaddefaultcerts out of _defaultcacerts...
Gregory Szorc -
r29107:c8fbfb91 default
parent child Browse files
Show More
@@ -1,335 +1,338 b''
1 1 # sslutil.py - SSL handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import os
13 13 import ssl
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from . import (
18 18 error,
19 19 util,
20 20 )
21 21
22 22 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
23 23 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
24 24 # all exposed via the "ssl" module.
25 25 #
26 26 # Depending on the version of Python being used, SSL/TLS support is either
27 27 # modern/secure or legacy/insecure. Many operations in this module have
28 28 # separate code paths depending on support in Python.
29 29
30 30 hassni = getattr(ssl, 'HAS_SNI', False)
31 31
32 32 try:
33 33 OP_NO_SSLv2 = ssl.OP_NO_SSLv2
34 34 OP_NO_SSLv3 = ssl.OP_NO_SSLv3
35 35 except AttributeError:
36 36 OP_NO_SSLv2 = 0x1000000
37 37 OP_NO_SSLv3 = 0x2000000
38 38
39 39 try:
40 40 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
41 41 # SSL/TLS features are available.
42 42 SSLContext = ssl.SSLContext
43 43 modernssl = True
44 44 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
45 45 except AttributeError:
46 46 modernssl = False
47 47 _canloaddefaultcerts = False
48 48
49 49 # We implement SSLContext using the interface from the standard library.
50 50 class SSLContext(object):
51 51 # ssl.wrap_socket gained the "ciphers" named argument in 2.7.
52 52 _supportsciphers = sys.version_info >= (2, 7)
53 53
54 54 def __init__(self, protocol):
55 55 # From the public interface of SSLContext
56 56 self.protocol = protocol
57 57 self.check_hostname = False
58 58 self.options = 0
59 59 self.verify_mode = ssl.CERT_NONE
60 60
61 61 # Used by our implementation.
62 62 self._certfile = None
63 63 self._keyfile = None
64 64 self._certpassword = None
65 65 self._cacerts = None
66 66 self._ciphers = None
67 67
68 68 def load_cert_chain(self, certfile, keyfile=None, password=None):
69 69 self._certfile = certfile
70 70 self._keyfile = keyfile
71 71 self._certpassword = password
72 72
73 73 def load_default_certs(self, purpose=None):
74 74 pass
75 75
76 76 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
77 77 if capath:
78 78 raise error.Abort('capath not supported')
79 79 if cadata:
80 80 raise error.Abort('cadata not supported')
81 81
82 82 self._cacerts = cafile
83 83
84 84 def set_ciphers(self, ciphers):
85 85 if not self._supportsciphers:
86 86 raise error.Abort('setting ciphers not supported')
87 87
88 88 self._ciphers = ciphers
89 89
90 90 def wrap_socket(self, socket, server_hostname=None, server_side=False):
91 91 # server_hostname is unique to SSLContext.wrap_socket and is used
92 92 # for SNI in that context. So there's nothing for us to do with it
93 93 # in this legacy code since we don't support SNI.
94 94
95 95 args = {
96 96 'keyfile': self._keyfile,
97 97 'certfile': self._certfile,
98 98 'server_side': server_side,
99 99 'cert_reqs': self.verify_mode,
100 100 'ssl_version': self.protocol,
101 101 'ca_certs': self._cacerts,
102 102 }
103 103
104 104 if self._supportsciphers:
105 105 args['ciphers'] = self._ciphers
106 106
107 107 return ssl.wrap_socket(socket, **args)
108 108
109 109 def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
110 110 ca_certs=None, serverhostname=None):
111 111 """Add SSL/TLS to a socket.
112 112
113 113 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
114 114 choices based on what security options are available.
115 115
116 116 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
117 117 the following additional arguments:
118 118
119 119 * serverhostname - The expected hostname of the remote server. If the
120 120 server (and client) support SNI, this tells the server which certificate
121 121 to use.
122 122 """
123 123 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
124 124 # that both ends support, including TLS protocols. On legacy stacks,
125 125 # the highest it likely goes in TLS 1.0. On modern stacks, it can
126 126 # support TLS 1.2.
127 127 #
128 128 # The PROTOCOL_TLSv* constants select a specific TLS version
129 129 # only (as opposed to multiple versions). So the method for
130 130 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
131 131 # disable protocols via SSLContext.options and OP_NO_* constants.
132 132 # However, SSLContext.options doesn't work unless we have the
133 133 # full/real SSLContext available to us.
134 134 #
135 135 # SSLv2 and SSLv3 are broken. We ban them outright.
136 136 if modernssl:
137 137 protocol = ssl.PROTOCOL_SSLv23
138 138 else:
139 139 protocol = ssl.PROTOCOL_TLSv1
140 140
141 141 # TODO use ssl.create_default_context() on modernssl.
142 142 sslcontext = SSLContext(protocol)
143 143
144 144 # This is a no-op on old Python.
145 145 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
146 146
147 147 # This still works on our fake SSLContext.
148 148 sslcontext.verify_mode = cert_reqs
149 149
150 150 if certfile is not None:
151 151 def password():
152 152 f = keyfile or certfile
153 153 return ui.getpass(_('passphrase for %s: ') % f, '')
154 154 sslcontext.load_cert_chain(certfile, keyfile, password)
155 155
156 156 if ca_certs is not None:
157 157 sslcontext.load_verify_locations(cafile=ca_certs)
158 158 else:
159 159 # This is a no-op on old Python.
160 160 sslcontext.load_default_certs()
161 161
162 162 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
163 163 # check if wrap_socket failed silently because socket had been
164 164 # closed
165 165 # - see http://bugs.python.org/issue13721
166 166 if not sslsocket.cipher():
167 167 raise error.Abort(_('ssl connection failed'))
168 168 return sslsocket
169 169
170 170 def _verifycert(cert, hostname):
171 171 '''Verify that cert (in socket.getpeercert() format) matches hostname.
172 172 CRLs is not handled.
173 173
174 174 Returns error message if any problems are found and None on success.
175 175 '''
176 176 if not cert:
177 177 return _('no certificate received')
178 178 dnsname = hostname.lower()
179 179 def matchdnsname(certname):
180 180 return (certname == dnsname or
181 181 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
182 182
183 183 san = cert.get('subjectAltName', [])
184 184 if san:
185 185 certnames = [value.lower() for key, value in san if key == 'DNS']
186 186 for name in certnames:
187 187 if matchdnsname(name):
188 188 return None
189 189 if certnames:
190 190 return _('certificate is for %s') % ', '.join(certnames)
191 191
192 192 # subject is only checked when subjectAltName is empty
193 193 for s in cert.get('subject', []):
194 194 key, value = s[0]
195 195 if key == 'commonName':
196 196 try:
197 197 # 'subject' entries are unicode
198 198 certname = value.lower().encode('ascii')
199 199 except UnicodeEncodeError:
200 200 return _('IDN in certificate not supported')
201 201 if matchdnsname(certname):
202 202 return None
203 203 return _('certificate is for %s') % certname
204 204 return _('no commonName or subjectAltName found in certificate')
205 205
206 206
207 207 # CERT_REQUIRED means fetch the cert from the server all the time AND
208 208 # validate it against the CA store provided in web.cacerts.
209 209
210 210 def _plainapplepython():
211 211 """return true if this seems to be a pure Apple Python that
212 212 * is unfrozen and presumably has the whole mercurial module in the file
213 213 system
214 214 * presumably is an Apple Python that uses Apple OpenSSL which has patches
215 215 for using system certificate store CAs in addition to the provided
216 216 cacerts file
217 217 """
218 218 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
219 219 return False
220 220 exe = os.path.realpath(sys.executable).lower()
221 221 return (exe.startswith('/usr/bin/python') or
222 222 exe.startswith('/system/library/frameworks/python.framework/'))
223 223
224 224 def _defaultcacerts():
225 """return path to CA certificates; None for system's store; ! to disable"""
225 """return path to default CA certificates or None."""
226 226 if _plainapplepython():
227 227 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
228 228 if os.path.exists(dummycert):
229 229 return dummycert
230 if _canloaddefaultcerts:
230
231 231 return None
232 return '!'
233 232
234 233 def sslkwargs(ui, host):
235 234 """Determine arguments to pass to wrapsocket().
236 235
237 236 ``host`` is the hostname being connected to.
238 237 """
239 238 kws = {'ui': ui}
240 239
241 240 # If a host key fingerprint is on file, it is the only thing that matters
242 241 # and CA certs don't come into play.
243 242 hostfingerprint = ui.config('hostfingerprints', host)
244 243 if hostfingerprint:
245 244 return kws
246 245
247 246 # dispatch sets web.cacerts=! when --insecure is used.
248 247 cacerts = ui.config('web', 'cacerts')
249 248 if cacerts == '!':
250 249 return kws
251 250
252 251 # If a value is set in the config, validate against a path and load
253 252 # and require those certs.
254 253 if cacerts:
255 254 cacerts = util.expandpath(cacerts)
256 255 if not os.path.exists(cacerts):
257 256 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
258 257
259 258 kws.update({'ca_certs': cacerts,
260 259 'cert_reqs': ssl.CERT_REQUIRED})
261 260 return kws
262 261
263 262 # No CAs in config. See if we can load defaults.
264 263 cacerts = _defaultcacerts()
265 if cacerts and cacerts != '!':
264 if cacerts:
266 265 ui.debug('using %s to enable OS X system CA\n' % cacerts)
266 else:
267 if not _canloaddefaultcerts:
268 cacerts = '!'
269
267 270 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
268 271
269 272 if cacerts != '!':
270 273 kws.update({'ca_certs': cacerts,
271 274 'cert_reqs': ssl.CERT_REQUIRED,
272 275 })
273 276 return kws
274 277
275 278 class validator(object):
276 279 def __init__(self, ui, host):
277 280 self.ui = ui
278 281 self.host = host
279 282
280 283 def __call__(self, sock, strict=False):
281 284 host = self.host
282 285
283 286 if not sock.cipher(): # work around http://bugs.python.org/issue13721
284 287 raise error.Abort(_('%s ssl connection error') % host)
285 288 try:
286 289 peercert = sock.getpeercert(True)
287 290 peercert2 = sock.getpeercert()
288 291 except AttributeError:
289 292 raise error.Abort(_('%s ssl connection error') % host)
290 293
291 294 if not peercert:
292 295 raise error.Abort(_('%s certificate error: '
293 296 'no certificate received') % host)
294 297
295 298 # If a certificate fingerprint is pinned, use it and only it to
296 299 # validate the remote cert.
297 300 hostfingerprints = self.ui.configlist('hostfingerprints', host)
298 301 peerfingerprint = util.sha1(peercert).hexdigest()
299 302 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
300 303 for x in xrange(0, len(peerfingerprint), 2)])
301 304 if hostfingerprints:
302 305 fingerprintmatch = False
303 306 for hostfingerprint in hostfingerprints:
304 307 if peerfingerprint.lower() == \
305 308 hostfingerprint.replace(':', '').lower():
306 309 fingerprintmatch = True
307 310 break
308 311 if not fingerprintmatch:
309 312 raise error.Abort(_('certificate for %s has unexpected '
310 313 'fingerprint %s') % (host, nicefingerprint),
311 314 hint=_('check hostfingerprint configuration'))
312 315 self.ui.debug('%s certificate matched fingerprint %s\n' %
313 316 (host, nicefingerprint))
314 317 return
315 318
316 319 # No pinned fingerprint. Establish trust by looking at the CAs.
317 320 cacerts = self.ui.config('web', 'cacerts')
318 321 if cacerts != '!':
319 322 msg = _verifycert(peercert2, host)
320 323 if msg:
321 324 raise error.Abort(_('%s certificate error: %s') % (host, msg),
322 325 hint=_('configure hostfingerprint %s or use '
323 326 '--insecure to connect insecurely') %
324 327 nicefingerprint)
325 328 self.ui.debug('%s certificate successfully verified\n' % host)
326 329 elif strict:
327 330 raise error.Abort(_('%s certificate with fingerprint %s not '
328 331 'verified') % (host, nicefingerprint),
329 332 hint=_('check hostfingerprints or web.cacerts '
330 333 'config setting'))
331 334 else:
332 335 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
333 336 'verified (check hostfingerprints or web.cacerts '
334 337 'config setting)\n') %
335 338 (host, nicefingerprint))
@@ -1,531 +1,531 b''
1 1 from __future__ import absolute_import
2 2
3 3 import errno
4 4 import os
5 5 import re
6 6 import socket
7 7 import stat
8 8 import subprocess
9 9 import sys
10 10 import tempfile
11 11
12 12 tempprefix = 'hg-hghave-'
13 13
14 14 checks = {
15 15 "true": (lambda: True, "yak shaving"),
16 16 "false": (lambda: False, "nail clipper"),
17 17 }
18 18
19 19 def check(name, desc):
20 20 """Registers a check function for a feature."""
21 21 def decorator(func):
22 22 checks[name] = (func, desc)
23 23 return func
24 24 return decorator
25 25
26 26 def checkvers(name, desc, vers):
27 27 """Registers a check function for each of a series of versions.
28 28
29 29 vers can be a list or an iterator"""
30 30 def decorator(func):
31 31 def funcv(v):
32 32 def f():
33 33 return func(v)
34 34 return f
35 35 for v in vers:
36 36 v = str(v)
37 37 f = funcv(v)
38 38 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
39 39 return func
40 40 return decorator
41 41
42 42 def checkfeatures(features):
43 43 result = {
44 44 'error': [],
45 45 'missing': [],
46 46 'skipped': [],
47 47 }
48 48
49 49 for feature in features:
50 50 negate = feature.startswith('no-')
51 51 if negate:
52 52 feature = feature[3:]
53 53
54 54 if feature not in checks:
55 55 result['missing'].append(feature)
56 56 continue
57 57
58 58 check, desc = checks[feature]
59 59 try:
60 60 available = check()
61 61 except Exception:
62 62 result['error'].append('hghave check failed: %s' % feature)
63 63 continue
64 64
65 65 if not negate and not available:
66 66 result['skipped'].append('missing feature: %s' % desc)
67 67 elif negate and available:
68 68 result['skipped'].append('system supports %s' % desc)
69 69
70 70 return result
71 71
72 72 def require(features):
73 73 """Require that features are available, exiting if not."""
74 74 result = checkfeatures(features)
75 75
76 76 for missing in result['missing']:
77 77 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
78 78 for msg in result['skipped']:
79 79 sys.stderr.write('skipped: %s\n' % msg)
80 80 for msg in result['error']:
81 81 sys.stderr.write('%s\n' % msg)
82 82
83 83 if result['missing']:
84 84 sys.exit(2)
85 85
86 86 if result['skipped'] or result['error']:
87 87 sys.exit(1)
88 88
89 89 def matchoutput(cmd, regexp, ignorestatus=False):
90 90 """Return the match object if cmd executes successfully and its output
91 91 is matched by the supplied regular expression.
92 92 """
93 93 r = re.compile(regexp)
94 94 try:
95 95 p = subprocess.Popen(
96 96 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
97 97 except OSError as e:
98 98 if e.errno != errno.ENOENT:
99 99 raise
100 100 ret = -1
101 101 ret = p.wait()
102 102 s = p.stdout.read()
103 103 return (ignorestatus or not ret) and r.search(s)
104 104
105 105 @check("baz", "GNU Arch baz client")
106 106 def has_baz():
107 107 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
108 108
109 109 @check("bzr", "Canonical's Bazaar client")
110 110 def has_bzr():
111 111 try:
112 112 import bzrlib
113 113 return bzrlib.__doc__ is not None
114 114 except ImportError:
115 115 return False
116 116
117 117 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
118 118 def has_bzr_range(v):
119 119 major, minor = v.split('.')[0:2]
120 120 try:
121 121 import bzrlib
122 122 return (bzrlib.__doc__ is not None
123 123 and bzrlib.version_info[:2] >= (int(major), int(minor)))
124 124 except ImportError:
125 125 return False
126 126
127 127 @check("chg", "running with chg")
128 128 def has_chg():
129 129 return 'CHGHG' in os.environ
130 130
131 131 @check("cvs", "cvs client/server")
132 132 def has_cvs():
133 133 re = r'Concurrent Versions System.*?server'
134 134 return matchoutput('cvs --version 2>&1', re) and not has_msys()
135 135
136 136 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
137 137 def has_cvs112():
138 138 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
139 139 return matchoutput('cvs --version 2>&1', re) and not has_msys()
140 140
141 141 @check("cvsnt", "cvsnt client/server")
142 142 def has_cvsnt():
143 143 re = r'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
144 144 return matchoutput('cvsnt --version 2>&1', re)
145 145
146 146 @check("darcs", "darcs client")
147 147 def has_darcs():
148 148 return matchoutput('darcs --version', r'2\.[2-9]', True)
149 149
150 150 @check("mtn", "monotone client (>= 1.0)")
151 151 def has_mtn():
152 152 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
153 153 'mtn --version', r'monotone 0\.', True)
154 154
155 155 @check("eol-in-paths", "end-of-lines in paths")
156 156 def has_eol_in_paths():
157 157 try:
158 158 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
159 159 os.close(fd)
160 160 os.remove(path)
161 161 return True
162 162 except (IOError, OSError):
163 163 return False
164 164
165 165 @check("execbit", "executable bit")
166 166 def has_executablebit():
167 167 try:
168 168 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
169 169 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
170 170 try:
171 171 os.close(fh)
172 172 m = os.stat(fn).st_mode & 0o777
173 173 new_file_has_exec = m & EXECFLAGS
174 174 os.chmod(fn, m ^ EXECFLAGS)
175 175 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
176 176 finally:
177 177 os.unlink(fn)
178 178 except (IOError, OSError):
179 179 # we don't care, the user probably won't be able to commit anyway
180 180 return False
181 181 return not (new_file_has_exec or exec_flags_cannot_flip)
182 182
183 183 @check("icasefs", "case insensitive file system")
184 184 def has_icasefs():
185 185 # Stolen from mercurial.util
186 186 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
187 187 os.close(fd)
188 188 try:
189 189 s1 = os.stat(path)
190 190 d, b = os.path.split(path)
191 191 p2 = os.path.join(d, b.upper())
192 192 if path == p2:
193 193 p2 = os.path.join(d, b.lower())
194 194 try:
195 195 s2 = os.stat(p2)
196 196 return s2 == s1
197 197 except OSError:
198 198 return False
199 199 finally:
200 200 os.remove(path)
201 201
202 202 @check("fifo", "named pipes")
203 203 def has_fifo():
204 204 if getattr(os, "mkfifo", None) is None:
205 205 return False
206 206 name = tempfile.mktemp(dir='.', prefix=tempprefix)
207 207 try:
208 208 os.mkfifo(name)
209 209 os.unlink(name)
210 210 return True
211 211 except OSError:
212 212 return False
213 213
214 214 @check("killdaemons", 'killdaemons.py support')
215 215 def has_killdaemons():
216 216 return True
217 217
218 218 @check("cacheable", "cacheable filesystem")
219 219 def has_cacheable_fs():
220 220 from mercurial import util
221 221
222 222 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
223 223 os.close(fd)
224 224 try:
225 225 return util.cachestat(path).cacheable()
226 226 finally:
227 227 os.remove(path)
228 228
229 229 @check("lsprof", "python lsprof module")
230 230 def has_lsprof():
231 231 try:
232 232 import _lsprof
233 233 _lsprof.Profiler # silence unused import warning
234 234 return True
235 235 except ImportError:
236 236 return False
237 237
238 238 def gethgversion():
239 239 m = matchoutput('hg --version --quiet 2>&1', r'(\d+)\.(\d+)')
240 240 if not m:
241 241 return (0, 0)
242 242 return (int(m.group(1)), int(m.group(2)))
243 243
244 244 @checkvers("hg", "Mercurial >= %s",
245 245 list([(1.0 * x) / 10 for x in range(9, 40)]))
246 246 def has_hg_range(v):
247 247 major, minor = v.split('.')[0:2]
248 248 return gethgversion() >= (int(major), int(minor))
249 249
250 250 @check("hg08", "Mercurial >= 0.8")
251 251 def has_hg08():
252 252 if checks["hg09"][0]():
253 253 return True
254 254 return matchoutput('hg help annotate 2>&1', '--date')
255 255
256 256 @check("hg07", "Mercurial >= 0.7")
257 257 def has_hg07():
258 258 if checks["hg08"][0]():
259 259 return True
260 260 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
261 261
262 262 @check("hg06", "Mercurial >= 0.6")
263 263 def has_hg06():
264 264 if checks["hg07"][0]():
265 265 return True
266 266 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
267 267
268 268 @check("gettext", "GNU Gettext (msgfmt)")
269 269 def has_gettext():
270 270 return matchoutput('msgfmt --version', 'GNU gettext-tools')
271 271
272 272 @check("git", "git command line client")
273 273 def has_git():
274 274 return matchoutput('git --version 2>&1', r'^git version')
275 275
276 276 @check("docutils", "Docutils text processing library")
277 277 def has_docutils():
278 278 try:
279 279 import docutils.core
280 280 docutils.core.publish_cmdline # silence unused import
281 281 return True
282 282 except ImportError:
283 283 return False
284 284
285 285 def getsvnversion():
286 286 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
287 287 if not m:
288 288 return (0, 0)
289 289 return (int(m.group(1)), int(m.group(2)))
290 290
291 291 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
292 292 def has_svn_range(v):
293 293 major, minor = v.split('.')[0:2]
294 294 return getsvnversion() >= (int(major), int(minor))
295 295
296 296 @check("svn", "subversion client and admin tools")
297 297 def has_svn():
298 298 return matchoutput('svn --version 2>&1', r'^svn, version') and \
299 299 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
300 300
301 301 @check("svn-bindings", "subversion python bindings")
302 302 def has_svn_bindings():
303 303 try:
304 304 import svn.core
305 305 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
306 306 if version < (1, 4):
307 307 return False
308 308 return True
309 309 except ImportError:
310 310 return False
311 311
312 312 @check("p4", "Perforce server and client")
313 313 def has_p4():
314 314 return (matchoutput('p4 -V', r'Rev\. P4/') and
315 315 matchoutput('p4d -V', r'Rev\. P4D/'))
316 316
317 317 @check("symlink", "symbolic links")
318 318 def has_symlink():
319 319 if getattr(os, "symlink", None) is None:
320 320 return False
321 321 name = tempfile.mktemp(dir='.', prefix=tempprefix)
322 322 try:
323 323 os.symlink(".", name)
324 324 os.unlink(name)
325 325 return True
326 326 except (OSError, AttributeError):
327 327 return False
328 328
329 329 @check("hardlink", "hardlinks")
330 330 def has_hardlink():
331 331 from mercurial import util
332 332 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
333 333 os.close(fh)
334 334 name = tempfile.mktemp(dir='.', prefix=tempprefix)
335 335 try:
336 336 util.oslink(fn, name)
337 337 os.unlink(name)
338 338 return True
339 339 except OSError:
340 340 return False
341 341 finally:
342 342 os.unlink(fn)
343 343
344 344 @check("tla", "GNU Arch tla client")
345 345 def has_tla():
346 346 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
347 347
348 348 @check("gpg", "gpg client")
349 349 def has_gpg():
350 350 return matchoutput('gpg --version 2>&1', r'GnuPG')
351 351
352 352 @check("unix-permissions", "unix-style permissions")
353 353 def has_unix_permissions():
354 354 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
355 355 try:
356 356 fname = os.path.join(d, 'foo')
357 357 for umask in (0o77, 0o07, 0o22):
358 358 os.umask(umask)
359 359 f = open(fname, 'w')
360 360 f.close()
361 361 mode = os.stat(fname).st_mode
362 362 os.unlink(fname)
363 363 if mode & 0o777 != ~umask & 0o666:
364 364 return False
365 365 return True
366 366 finally:
367 367 os.rmdir(d)
368 368
369 369 @check("unix-socket", "AF_UNIX socket family")
370 370 def has_unix_socket():
371 371 return getattr(socket, 'AF_UNIX', None) is not None
372 372
373 373 @check("root", "root permissions")
374 374 def has_root():
375 375 return getattr(os, 'geteuid', None) and os.geteuid() == 0
376 376
377 377 @check("pyflakes", "Pyflakes python linter")
378 378 def has_pyflakes():
379 379 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
380 380 r"<stdin>:1: 're' imported but unused",
381 381 True)
382 382
383 383 @check("pygments", "Pygments source highlighting library")
384 384 def has_pygments():
385 385 try:
386 386 import pygments
387 387 pygments.highlight # silence unused import warning
388 388 return True
389 389 except ImportError:
390 390 return False
391 391
392 392 @check("outer-repo", "outer repo")
393 393 def has_outer_repo():
394 394 # failing for other reasons than 'no repo' imply that there is a repo
395 395 return not matchoutput('hg root 2>&1',
396 396 r'abort: no repository found', True)
397 397
398 398 @check("ssl", "ssl module available")
399 399 def has_ssl():
400 400 try:
401 401 import ssl
402 402 ssl.CERT_NONE
403 403 return True
404 404 except ImportError:
405 405 return False
406 406
407 407 @check("sslcontext", "python >= 2.7.9 ssl")
408 408 def has_sslcontext():
409 409 try:
410 410 import ssl
411 411 ssl.SSLContext
412 412 return True
413 413 except (ImportError, AttributeError):
414 414 return False
415 415
416 416 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
417 417 def has_defaultcacerts():
418 418 from mercurial import sslutil
419 return sslutil._defaultcacerts() != '!'
419 return sslutil._defaultcacerts() or sslutil._canloaddefaultcerts
420 420
421 421 @check("windows", "Windows")
422 422 def has_windows():
423 423 return os.name == 'nt'
424 424
425 425 @check("system-sh", "system() uses sh")
426 426 def has_system_sh():
427 427 return os.name != 'nt'
428 428
429 429 @check("serve", "platform and python can manage 'hg serve -d'")
430 430 def has_serve():
431 431 return os.name != 'nt' # gross approximation
432 432
433 433 @check("test-repo", "running tests from repository")
434 434 def has_test_repo():
435 435 t = os.environ["TESTDIR"]
436 436 return os.path.isdir(os.path.join(t, "..", ".hg"))
437 437
438 438 @check("tic", "terminfo compiler and curses module")
439 439 def has_tic():
440 440 try:
441 441 import curses
442 442 curses.COLOR_BLUE
443 443 return matchoutput('test -x "`which tic`"', '')
444 444 except ImportError:
445 445 return False
446 446
447 447 @check("msys", "Windows with MSYS")
448 448 def has_msys():
449 449 return os.getenv('MSYSTEM')
450 450
451 451 @check("aix", "AIX")
452 452 def has_aix():
453 453 return sys.platform.startswith("aix")
454 454
455 455 @check("osx", "OS X")
456 456 def has_osx():
457 457 return sys.platform == 'darwin'
458 458
459 459 @check("osxpackaging", "OS X packaging tools")
460 460 def has_osxpackaging():
461 461 try:
462 462 return (matchoutput('pkgbuild', 'Usage: pkgbuild ', ignorestatus=1)
463 463 and matchoutput(
464 464 'productbuild', 'Usage: productbuild ',
465 465 ignorestatus=1)
466 466 and matchoutput('lsbom', 'Usage: lsbom', ignorestatus=1)
467 467 and matchoutput(
468 468 'xar --help', 'Usage: xar', ignorestatus=1))
469 469 except ImportError:
470 470 return False
471 471
472 472 @check("docker", "docker support")
473 473 def has_docker():
474 474 pat = r'A self-sufficient runtime for'
475 475 if matchoutput('docker --help', pat):
476 476 if 'linux' not in sys.platform:
477 477 # TODO: in theory we should be able to test docker-based
478 478 # package creation on non-linux using boot2docker, but in
479 479 # practice that requires extra coordination to make sure
480 480 # $TESTTEMP is going to be visible at the same path to the
481 481 # boot2docker VM. If we figure out how to verify that, we
482 482 # can use the following instead of just saying False:
483 483 # return 'DOCKER_HOST' in os.environ
484 484 return False
485 485
486 486 return True
487 487 return False
488 488
489 489 @check("debhelper", "debian packaging tools")
490 490 def has_debhelper():
491 491 dpkg = matchoutput('dpkg --version',
492 492 "Debian `dpkg' package management program")
493 493 dh = matchoutput('dh --help',
494 494 'dh is a part of debhelper.', ignorestatus=True)
495 495 dh_py2 = matchoutput('dh_python2 --help',
496 496 'other supported Python versions')
497 497 return dpkg and dh and dh_py2
498 498
499 499 @check("absimport", "absolute_import in __future__")
500 500 def has_absimport():
501 501 import __future__
502 502 from mercurial import util
503 503 return util.safehasattr(__future__, "absolute_import")
504 504
505 505 @check("py3k", "running with Python 3.x")
506 506 def has_py3k():
507 507 return 3 == sys.version_info[0]
508 508
509 509 @check("py3exe", "a Python 3.x interpreter is available")
510 510 def has_python3exe():
511 511 return 'PYTHON3' in os.environ
512 512
513 513 @check("pure", "running with pure Python code")
514 514 def has_pure():
515 515 return any([
516 516 os.environ.get("HGMODULEPOLICY") == "py",
517 517 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
518 518 ])
519 519
520 520 @check("slow", "allow slow tests")
521 521 def has_slow():
522 522 return os.environ.get('HGTEST_SLOW') == 'slow'
523 523
524 524 @check("hypothesis", "Hypothesis automated test generation")
525 525 def has_hypothesis():
526 526 try:
527 527 import hypothesis
528 528 hypothesis.given
529 529 return True
530 530 except ImportError:
531 531 return False
General Comments 0
You need to be logged in to leave comments. Login now