##// END OF EJS Templates
configitems: register the 'hostsecurity.*:fingerprints' config
Boris Feld -
r34776:17919e90 default
parent child Browse files
Show More
@@ -1,1008 +1,1012 b''
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
10 import functools
11 import re
11 import re
12
12
13 from . import (
13 from . import (
14 encoding,
14 encoding,
15 error,
15 error,
16 )
16 )
17
17
18 def loadconfigtable(ui, extname, configtable):
18 def loadconfigtable(ui, extname, configtable):
19 """update config item known to the ui with the extension ones"""
19 """update config item known to the ui with the extension ones"""
20 for section, items in configtable.items():
20 for section, items in configtable.items():
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 knownkeys = set(knownitems)
22 knownkeys = set(knownitems)
23 newkeys = set(items)
23 newkeys = set(items)
24 for key in sorted(knownkeys & newkeys):
24 for key in sorted(knownkeys & newkeys):
25 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg = "extension '%s' overwrite config item '%s.%s'"
26 msg %= (extname, section, key)
26 msg %= (extname, section, key)
27 ui.develwarn(msg, config='warn-config')
27 ui.develwarn(msg, config='warn-config')
28
28
29 knownitems.update(items)
29 knownitems.update(items)
30
30
31 class configitem(object):
31 class configitem(object):
32 """represent a known config item
32 """represent a known config item
33
33
34 :section: the official config section where to find this item,
34 :section: the official config section where to find this item,
35 :name: the official name within the section,
35 :name: the official name within the section,
36 :default: default value for this item,
36 :default: default value for this item,
37 :alias: optional list of tuples as alternatives,
37 :alias: optional list of tuples as alternatives,
38 :generic: this is a generic definition, match name using regular expression.
38 :generic: this is a generic definition, match name using regular expression.
39 """
39 """
40
40
41 def __init__(self, section, name, default=None, alias=(),
41 def __init__(self, section, name, default=None, alias=(),
42 generic=False, priority=0):
42 generic=False, priority=0):
43 self.section = section
43 self.section = section
44 self.name = name
44 self.name = name
45 self.default = default
45 self.default = default
46 self.alias = list(alias)
46 self.alias = list(alias)
47 self.generic = generic
47 self.generic = generic
48 self.priority = priority
48 self.priority = priority
49 self._re = None
49 self._re = None
50 if generic:
50 if generic:
51 self._re = re.compile(self.name)
51 self._re = re.compile(self.name)
52
52
53 class itemregister(dict):
53 class itemregister(dict):
54 """A specialized dictionary that can handle wild-card selection"""
54 """A specialized dictionary that can handle wild-card selection"""
55
55
56 def __init__(self):
56 def __init__(self):
57 super(itemregister, self).__init__()
57 super(itemregister, self).__init__()
58 self._generics = set()
58 self._generics = set()
59
59
60 def update(self, other):
60 def update(self, other):
61 super(itemregister, self).update(other)
61 super(itemregister, self).update(other)
62 self._generics.update(other._generics)
62 self._generics.update(other._generics)
63
63
64 def __setitem__(self, key, item):
64 def __setitem__(self, key, item):
65 super(itemregister, self).__setitem__(key, item)
65 super(itemregister, self).__setitem__(key, item)
66 if item.generic:
66 if item.generic:
67 self._generics.add(item)
67 self._generics.add(item)
68
68
69 def get(self, key):
69 def get(self, key):
70 if key in self:
70 if key in self:
71 return self[key]
71 return self[key]
72
72
73 # search for a matching generic item
73 # search for a matching generic item
74 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
74 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
75 for item in generics:
75 for item in generics:
76 if item._re.match(key):
76 if item._re.match(key):
77 return item
77 return item
78
78
79 # fallback to dict get
79 # fallback to dict get
80 return super(itemregister, self).get(key)
80 return super(itemregister, self).get(key)
81
81
82 coreitems = {}
82 coreitems = {}
83
83
84 def _register(configtable, *args, **kwargs):
84 def _register(configtable, *args, **kwargs):
85 item = configitem(*args, **kwargs)
85 item = configitem(*args, **kwargs)
86 section = configtable.setdefault(item.section, itemregister())
86 section = configtable.setdefault(item.section, itemregister())
87 if item.name in section:
87 if item.name in section:
88 msg = "duplicated config item registration for '%s.%s'"
88 msg = "duplicated config item registration for '%s.%s'"
89 raise error.ProgrammingError(msg % (item.section, item.name))
89 raise error.ProgrammingError(msg % (item.section, item.name))
90 section[item.name] = item
90 section[item.name] = item
91
91
92 # special value for case where the default is derived from other values
92 # special value for case where the default is derived from other values
93 dynamicdefault = object()
93 dynamicdefault = object()
94
94
95 # Registering actual config items
95 # Registering actual config items
96
96
97 def getitemregister(configtable):
97 def getitemregister(configtable):
98 return functools.partial(_register, configtable)
98 return functools.partial(_register, configtable)
99
99
100 coreconfigitem = getitemregister(coreitems)
100 coreconfigitem = getitemregister(coreitems)
101
101
102 coreconfigitem('alias', '.*',
102 coreconfigitem('alias', '.*',
103 default=None,
103 default=None,
104 generic=True,
104 generic=True,
105 )
105 )
106 coreconfigitem('annotate', 'nodates',
106 coreconfigitem('annotate', 'nodates',
107 default=False,
107 default=False,
108 )
108 )
109 coreconfigitem('annotate', 'showfunc',
109 coreconfigitem('annotate', 'showfunc',
110 default=False,
110 default=False,
111 )
111 )
112 coreconfigitem('annotate', 'unified',
112 coreconfigitem('annotate', 'unified',
113 default=None,
113 default=None,
114 )
114 )
115 coreconfigitem('annotate', 'git',
115 coreconfigitem('annotate', 'git',
116 default=False,
116 default=False,
117 )
117 )
118 coreconfigitem('annotate', 'ignorews',
118 coreconfigitem('annotate', 'ignorews',
119 default=False,
119 default=False,
120 )
120 )
121 coreconfigitem('annotate', 'ignorewsamount',
121 coreconfigitem('annotate', 'ignorewsamount',
122 default=False,
122 default=False,
123 )
123 )
124 coreconfigitem('annotate', 'ignoreblanklines',
124 coreconfigitem('annotate', 'ignoreblanklines',
125 default=False,
125 default=False,
126 )
126 )
127 coreconfigitem('annotate', 'ignorewseol',
127 coreconfigitem('annotate', 'ignorewseol',
128 default=False,
128 default=False,
129 )
129 )
130 coreconfigitem('annotate', 'nobinary',
130 coreconfigitem('annotate', 'nobinary',
131 default=False,
131 default=False,
132 )
132 )
133 coreconfigitem('annotate', 'noprefix',
133 coreconfigitem('annotate', 'noprefix',
134 default=False,
134 default=False,
135 )
135 )
136 coreconfigitem('auth', 'cookiefile',
136 coreconfigitem('auth', 'cookiefile',
137 default=None,
137 default=None,
138 )
138 )
139 # bookmarks.pushing: internal hack for discovery
139 # bookmarks.pushing: internal hack for discovery
140 coreconfigitem('bookmarks', 'pushing',
140 coreconfigitem('bookmarks', 'pushing',
141 default=list,
141 default=list,
142 )
142 )
143 # bundle.mainreporoot: internal hack for bundlerepo
143 # bundle.mainreporoot: internal hack for bundlerepo
144 coreconfigitem('bundle', 'mainreporoot',
144 coreconfigitem('bundle', 'mainreporoot',
145 default='',
145 default='',
146 )
146 )
147 # bundle.reorder: experimental config
147 # bundle.reorder: experimental config
148 coreconfigitem('bundle', 'reorder',
148 coreconfigitem('bundle', 'reorder',
149 default='auto',
149 default='auto',
150 )
150 )
151 coreconfigitem('censor', 'policy',
151 coreconfigitem('censor', 'policy',
152 default='abort',
152 default='abort',
153 )
153 )
154 coreconfigitem('chgserver', 'idletimeout',
154 coreconfigitem('chgserver', 'idletimeout',
155 default=3600,
155 default=3600,
156 )
156 )
157 coreconfigitem('chgserver', 'skiphash',
157 coreconfigitem('chgserver', 'skiphash',
158 default=False,
158 default=False,
159 )
159 )
160 coreconfigitem('cmdserver', 'log',
160 coreconfigitem('cmdserver', 'log',
161 default=None,
161 default=None,
162 )
162 )
163 coreconfigitem('color', '.*',
163 coreconfigitem('color', '.*',
164 default=None,
164 default=None,
165 generic=True,
165 generic=True,
166 )
166 )
167 coreconfigitem('color', 'mode',
167 coreconfigitem('color', 'mode',
168 default='auto',
168 default='auto',
169 )
169 )
170 coreconfigitem('color', 'pagermode',
170 coreconfigitem('color', 'pagermode',
171 default=dynamicdefault,
171 default=dynamicdefault,
172 )
172 )
173 coreconfigitem('commands', 'status.relative',
173 coreconfigitem('commands', 'status.relative',
174 default=False,
174 default=False,
175 )
175 )
176 coreconfigitem('commands', 'status.skipstates',
176 coreconfigitem('commands', 'status.skipstates',
177 default=[],
177 default=[],
178 )
178 )
179 coreconfigitem('commands', 'status.verbose',
179 coreconfigitem('commands', 'status.verbose',
180 default=False,
180 default=False,
181 )
181 )
182 coreconfigitem('commands', 'update.check',
182 coreconfigitem('commands', 'update.check',
183 default=None,
183 default=None,
184 )
184 )
185 coreconfigitem('commands', 'update.requiredest',
185 coreconfigitem('commands', 'update.requiredest',
186 default=False,
186 default=False,
187 )
187 )
188 coreconfigitem('committemplate', '.*',
188 coreconfigitem('committemplate', '.*',
189 default=None,
189 default=None,
190 generic=True,
190 generic=True,
191 )
191 )
192 coreconfigitem('debug', 'dirstate.delaywrite',
192 coreconfigitem('debug', 'dirstate.delaywrite',
193 default=0,
193 default=0,
194 )
194 )
195 coreconfigitem('defaults', '.*',
195 coreconfigitem('defaults', '.*',
196 default=None,
196 default=None,
197 generic=True,
197 generic=True,
198 )
198 )
199 coreconfigitem('devel', 'all-warnings',
199 coreconfigitem('devel', 'all-warnings',
200 default=False,
200 default=False,
201 )
201 )
202 coreconfigitem('devel', 'bundle2.debug',
202 coreconfigitem('devel', 'bundle2.debug',
203 default=False,
203 default=False,
204 )
204 )
205 coreconfigitem('devel', 'cache-vfs',
205 coreconfigitem('devel', 'cache-vfs',
206 default=None,
206 default=None,
207 )
207 )
208 coreconfigitem('devel', 'check-locks',
208 coreconfigitem('devel', 'check-locks',
209 default=False,
209 default=False,
210 )
210 )
211 coreconfigitem('devel', 'check-relroot',
211 coreconfigitem('devel', 'check-relroot',
212 default=False,
212 default=False,
213 )
213 )
214 coreconfigitem('devel', 'default-date',
214 coreconfigitem('devel', 'default-date',
215 default=None,
215 default=None,
216 )
216 )
217 coreconfigitem('devel', 'deprec-warn',
217 coreconfigitem('devel', 'deprec-warn',
218 default=False,
218 default=False,
219 )
219 )
220 coreconfigitem('devel', 'disableloaddefaultcerts',
220 coreconfigitem('devel', 'disableloaddefaultcerts',
221 default=False,
221 default=False,
222 )
222 )
223 coreconfigitem('devel', 'warn-empty-changegroup',
223 coreconfigitem('devel', 'warn-empty-changegroup',
224 default=False,
224 default=False,
225 )
225 )
226 coreconfigitem('devel', 'legacy.exchange',
226 coreconfigitem('devel', 'legacy.exchange',
227 default=list,
227 default=list,
228 )
228 )
229 coreconfigitem('devel', 'servercafile',
229 coreconfigitem('devel', 'servercafile',
230 default='',
230 default='',
231 )
231 )
232 coreconfigitem('devel', 'serverexactprotocol',
232 coreconfigitem('devel', 'serverexactprotocol',
233 default='',
233 default='',
234 )
234 )
235 coreconfigitem('devel', 'serverrequirecert',
235 coreconfigitem('devel', 'serverrequirecert',
236 default=False,
236 default=False,
237 )
237 )
238 coreconfigitem('devel', 'strip-obsmarkers',
238 coreconfigitem('devel', 'strip-obsmarkers',
239 default=True,
239 default=True,
240 )
240 )
241 coreconfigitem('devel', 'warn-config',
241 coreconfigitem('devel', 'warn-config',
242 default=None,
242 default=None,
243 )
243 )
244 coreconfigitem('devel', 'warn-config-default',
244 coreconfigitem('devel', 'warn-config-default',
245 default=None,
245 default=None,
246 )
246 )
247 coreconfigitem('devel', 'user.obsmarker',
247 coreconfigitem('devel', 'user.obsmarker',
248 default=None,
248 default=None,
249 )
249 )
250 coreconfigitem('diff', 'nodates',
250 coreconfigitem('diff', 'nodates',
251 default=False,
251 default=False,
252 )
252 )
253 coreconfigitem('diff', 'showfunc',
253 coreconfigitem('diff', 'showfunc',
254 default=False,
254 default=False,
255 )
255 )
256 coreconfigitem('diff', 'unified',
256 coreconfigitem('diff', 'unified',
257 default=None,
257 default=None,
258 )
258 )
259 coreconfigitem('diff', 'git',
259 coreconfigitem('diff', 'git',
260 default=False,
260 default=False,
261 )
261 )
262 coreconfigitem('diff', 'ignorews',
262 coreconfigitem('diff', 'ignorews',
263 default=False,
263 default=False,
264 )
264 )
265 coreconfigitem('diff', 'ignorewsamount',
265 coreconfigitem('diff', 'ignorewsamount',
266 default=False,
266 default=False,
267 )
267 )
268 coreconfigitem('diff', 'ignoreblanklines',
268 coreconfigitem('diff', 'ignoreblanklines',
269 default=False,
269 default=False,
270 )
270 )
271 coreconfigitem('diff', 'ignorewseol',
271 coreconfigitem('diff', 'ignorewseol',
272 default=False,
272 default=False,
273 )
273 )
274 coreconfigitem('diff', 'nobinary',
274 coreconfigitem('diff', 'nobinary',
275 default=False,
275 default=False,
276 )
276 )
277 coreconfigitem('diff', 'noprefix',
277 coreconfigitem('diff', 'noprefix',
278 default=False,
278 default=False,
279 )
279 )
280 coreconfigitem('email', 'bcc',
280 coreconfigitem('email', 'bcc',
281 default=None,
281 default=None,
282 )
282 )
283 coreconfigitem('email', 'cc',
283 coreconfigitem('email', 'cc',
284 default=None,
284 default=None,
285 )
285 )
286 coreconfigitem('email', 'charsets',
286 coreconfigitem('email', 'charsets',
287 default=list,
287 default=list,
288 )
288 )
289 coreconfigitem('email', 'from',
289 coreconfigitem('email', 'from',
290 default=None,
290 default=None,
291 )
291 )
292 coreconfigitem('email', 'method',
292 coreconfigitem('email', 'method',
293 default='smtp',
293 default='smtp',
294 )
294 )
295 coreconfigitem('email', 'reply-to',
295 coreconfigitem('email', 'reply-to',
296 default=None,
296 default=None,
297 )
297 )
298 coreconfigitem('experimental', 'allowdivergence',
298 coreconfigitem('experimental', 'allowdivergence',
299 default=False,
299 default=False,
300 )
300 )
301 coreconfigitem('experimental', 'archivemetatemplate',
301 coreconfigitem('experimental', 'archivemetatemplate',
302 default=dynamicdefault,
302 default=dynamicdefault,
303 )
303 )
304 coreconfigitem('experimental', 'bundle-phases',
304 coreconfigitem('experimental', 'bundle-phases',
305 default=False,
305 default=False,
306 )
306 )
307 coreconfigitem('experimental', 'bundle2-advertise',
307 coreconfigitem('experimental', 'bundle2-advertise',
308 default=True,
308 default=True,
309 )
309 )
310 coreconfigitem('experimental', 'bundle2-output-capture',
310 coreconfigitem('experimental', 'bundle2-output-capture',
311 default=False,
311 default=False,
312 )
312 )
313 coreconfigitem('experimental', 'bundle2.pushback',
313 coreconfigitem('experimental', 'bundle2.pushback',
314 default=False,
314 default=False,
315 )
315 )
316 coreconfigitem('experimental', 'bundle2lazylocking',
316 coreconfigitem('experimental', 'bundle2lazylocking',
317 default=False,
317 default=False,
318 )
318 )
319 coreconfigitem('experimental', 'bundlecomplevel',
319 coreconfigitem('experimental', 'bundlecomplevel',
320 default=None,
320 default=None,
321 )
321 )
322 coreconfigitem('experimental', 'changegroup3',
322 coreconfigitem('experimental', 'changegroup3',
323 default=False,
323 default=False,
324 )
324 )
325 coreconfigitem('experimental', 'clientcompressionengines',
325 coreconfigitem('experimental', 'clientcompressionengines',
326 default=list,
326 default=list,
327 )
327 )
328 coreconfigitem('experimental', 'copytrace',
328 coreconfigitem('experimental', 'copytrace',
329 default='on',
329 default='on',
330 )
330 )
331 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
331 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
332 default=100,
332 default=100,
333 )
333 )
334 coreconfigitem('experimental', 'crecordtest',
334 coreconfigitem('experimental', 'crecordtest',
335 default=None,
335 default=None,
336 )
336 )
337 coreconfigitem('experimental', 'editortmpinhg',
337 coreconfigitem('experimental', 'editortmpinhg',
338 default=False,
338 default=False,
339 )
339 )
340 coreconfigitem('experimental', 'maxdeltachainspan',
340 coreconfigitem('experimental', 'maxdeltachainspan',
341 default=-1,
341 default=-1,
342 )
342 )
343 coreconfigitem('experimental', 'mmapindexthreshold',
343 coreconfigitem('experimental', 'mmapindexthreshold',
344 default=None,
344 default=None,
345 )
345 )
346 coreconfigitem('experimental', 'nonnormalparanoidcheck',
346 coreconfigitem('experimental', 'nonnormalparanoidcheck',
347 default=False,
347 default=False,
348 )
348 )
349 coreconfigitem('experimental', 'stabilization',
349 coreconfigitem('experimental', 'stabilization',
350 default=list,
350 default=list,
351 alias=[('experimental', 'evolution')],
351 alias=[('experimental', 'evolution')],
352 )
352 )
353 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
353 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
354 default=False,
354 default=False,
355 alias=[('experimental', 'evolution.bundle-obsmarker')],
355 alias=[('experimental', 'evolution.bundle-obsmarker')],
356 )
356 )
357 coreconfigitem('experimental', 'stabilization.track-operation',
357 coreconfigitem('experimental', 'stabilization.track-operation',
358 default=True,
358 default=True,
359 alias=[('experimental', 'evolution.track-operation')]
359 alias=[('experimental', 'evolution.track-operation')]
360 )
360 )
361 coreconfigitem('experimental', 'exportableenviron',
361 coreconfigitem('experimental', 'exportableenviron',
362 default=list,
362 default=list,
363 )
363 )
364 coreconfigitem('experimental', 'extendedheader.index',
364 coreconfigitem('experimental', 'extendedheader.index',
365 default=None,
365 default=None,
366 )
366 )
367 coreconfigitem('experimental', 'extendedheader.similarity',
367 coreconfigitem('experimental', 'extendedheader.similarity',
368 default=False,
368 default=False,
369 )
369 )
370 coreconfigitem('experimental', 'format.compression',
370 coreconfigitem('experimental', 'format.compression',
371 default='zlib',
371 default='zlib',
372 )
372 )
373 coreconfigitem('experimental', 'graphshorten',
373 coreconfigitem('experimental', 'graphshorten',
374 default=False,
374 default=False,
375 )
375 )
376 coreconfigitem('experimental', 'graphstyle.parent',
376 coreconfigitem('experimental', 'graphstyle.parent',
377 default=dynamicdefault,
377 default=dynamicdefault,
378 )
378 )
379 coreconfigitem('experimental', 'graphstyle.missing',
379 coreconfigitem('experimental', 'graphstyle.missing',
380 default=dynamicdefault,
380 default=dynamicdefault,
381 )
381 )
382 coreconfigitem('experimental', 'graphstyle.grandparent',
382 coreconfigitem('experimental', 'graphstyle.grandparent',
383 default=dynamicdefault,
383 default=dynamicdefault,
384 )
384 )
385 coreconfigitem('experimental', 'hook-track-tags',
385 coreconfigitem('experimental', 'hook-track-tags',
386 default=False,
386 default=False,
387 )
387 )
388 coreconfigitem('experimental', 'httppostargs',
388 coreconfigitem('experimental', 'httppostargs',
389 default=False,
389 default=False,
390 )
390 )
391 coreconfigitem('experimental', 'manifestv2',
391 coreconfigitem('experimental', 'manifestv2',
392 default=False,
392 default=False,
393 )
393 )
394 coreconfigitem('experimental', 'mergedriver',
394 coreconfigitem('experimental', 'mergedriver',
395 default=None,
395 default=None,
396 )
396 )
397 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
397 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
398 default=False,
398 default=False,
399 )
399 )
400 coreconfigitem('experimental', 'rebase.multidest',
400 coreconfigitem('experimental', 'rebase.multidest',
401 default=False,
401 default=False,
402 )
402 )
403 coreconfigitem('experimental', 'revertalternateinteractivemode',
403 coreconfigitem('experimental', 'revertalternateinteractivemode',
404 default=True,
404 default=True,
405 )
405 )
406 coreconfigitem('experimental', 'revlogv2',
406 coreconfigitem('experimental', 'revlogv2',
407 default=None,
407 default=None,
408 )
408 )
409 coreconfigitem('experimental', 'spacemovesdown',
409 coreconfigitem('experimental', 'spacemovesdown',
410 default=False,
410 default=False,
411 )
411 )
412 coreconfigitem('experimental', 'treemanifest',
412 coreconfigitem('experimental', 'treemanifest',
413 default=False,
413 default=False,
414 )
414 )
415 # Deprecated, remove after 4.4 release
415 # Deprecated, remove after 4.4 release
416 coreconfigitem('experimental', 'updatecheck',
416 coreconfigitem('experimental', 'updatecheck',
417 default=None,
417 default=None,
418 )
418 )
419 coreconfigitem('extensions', '.*',
419 coreconfigitem('extensions', '.*',
420 default=None,
420 default=None,
421 generic=True,
421 generic=True,
422 )
422 )
423 coreconfigitem('extdata', '.*',
423 coreconfigitem('extdata', '.*',
424 default=None,
424 default=None,
425 generic=True,
425 generic=True,
426 )
426 )
427 coreconfigitem('format', 'aggressivemergedeltas',
427 coreconfigitem('format', 'aggressivemergedeltas',
428 default=False,
428 default=False,
429 )
429 )
430 coreconfigitem('format', 'chunkcachesize',
430 coreconfigitem('format', 'chunkcachesize',
431 default=None,
431 default=None,
432 )
432 )
433 coreconfigitem('format', 'dotencode',
433 coreconfigitem('format', 'dotencode',
434 default=True,
434 default=True,
435 )
435 )
436 coreconfigitem('format', 'generaldelta',
436 coreconfigitem('format', 'generaldelta',
437 default=False,
437 default=False,
438 )
438 )
439 coreconfigitem('format', 'manifestcachesize',
439 coreconfigitem('format', 'manifestcachesize',
440 default=None,
440 default=None,
441 )
441 )
442 coreconfigitem('format', 'maxchainlen',
442 coreconfigitem('format', 'maxchainlen',
443 default=None,
443 default=None,
444 )
444 )
445 coreconfigitem('format', 'obsstore-version',
445 coreconfigitem('format', 'obsstore-version',
446 default=None,
446 default=None,
447 )
447 )
448 coreconfigitem('format', 'usefncache',
448 coreconfigitem('format', 'usefncache',
449 default=True,
449 default=True,
450 )
450 )
451 coreconfigitem('format', 'usegeneraldelta',
451 coreconfigitem('format', 'usegeneraldelta',
452 default=True,
452 default=True,
453 )
453 )
454 coreconfigitem('format', 'usestore',
454 coreconfigitem('format', 'usestore',
455 default=True,
455 default=True,
456 )
456 )
457 coreconfigitem('hooks', '.*',
457 coreconfigitem('hooks', '.*',
458 default=dynamicdefault,
458 default=dynamicdefault,
459 generic=True,
459 generic=True,
460 )
460 )
461 coreconfigitem('hgweb-paths', '.*',
461 coreconfigitem('hgweb-paths', '.*',
462 default=list,
462 default=list,
463 generic=True,
463 generic=True,
464 )
464 )
465 coreconfigitem('hostfingerprints', '.*',
465 coreconfigitem('hostfingerprints', '.*',
466 default=list,
466 default=list,
467 generic=True,
467 generic=True,
468 )
468 )
469 coreconfigitem('hostsecurity', 'ciphers',
469 coreconfigitem('hostsecurity', 'ciphers',
470 default=None,
470 default=None,
471 )
471 )
472 coreconfigitem('hostsecurity', 'disabletls10warning',
472 coreconfigitem('hostsecurity', 'disabletls10warning',
473 default=False,
473 default=False,
474 )
474 )
475 coreconfigitem('hostsecurity', 'minimumprotocol',
475 coreconfigitem('hostsecurity', 'minimumprotocol',
476 default=dynamicdefault,
476 default=dynamicdefault,
477 )
477 )
478 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
478 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
479 default=dynamicdefault,
479 default=dynamicdefault,
480 generic=True,
480 generic=True,
481 )
481 )
482 coreconfigitem('hostsecurity', '.*:ciphers$',
482 coreconfigitem('hostsecurity', '.*:ciphers$',
483 default=dynamicdefault,
483 default=dynamicdefault,
484 generic=True,
484 generic=True,
485 )
485 )
486 coreconfigitem('hostsecurity', '.*:fingerprints$',
487 default=list,
488 generic=True,
489 )
486 coreconfigitem('http_proxy', 'always',
490 coreconfigitem('http_proxy', 'always',
487 default=False,
491 default=False,
488 )
492 )
489 coreconfigitem('http_proxy', 'host',
493 coreconfigitem('http_proxy', 'host',
490 default=None,
494 default=None,
491 )
495 )
492 coreconfigitem('http_proxy', 'no',
496 coreconfigitem('http_proxy', 'no',
493 default=list,
497 default=list,
494 )
498 )
495 coreconfigitem('http_proxy', 'passwd',
499 coreconfigitem('http_proxy', 'passwd',
496 default=None,
500 default=None,
497 )
501 )
498 coreconfigitem('http_proxy', 'user',
502 coreconfigitem('http_proxy', 'user',
499 default=None,
503 default=None,
500 )
504 )
501 coreconfigitem('logtoprocess', 'commandexception',
505 coreconfigitem('logtoprocess', 'commandexception',
502 default=None,
506 default=None,
503 )
507 )
504 coreconfigitem('logtoprocess', 'commandfinish',
508 coreconfigitem('logtoprocess', 'commandfinish',
505 default=None,
509 default=None,
506 )
510 )
507 coreconfigitem('logtoprocess', 'command',
511 coreconfigitem('logtoprocess', 'command',
508 default=None,
512 default=None,
509 )
513 )
510 coreconfigitem('logtoprocess', 'develwarn',
514 coreconfigitem('logtoprocess', 'develwarn',
511 default=None,
515 default=None,
512 )
516 )
513 coreconfigitem('logtoprocess', 'uiblocked',
517 coreconfigitem('logtoprocess', 'uiblocked',
514 default=None,
518 default=None,
515 )
519 )
516 coreconfigitem('merge', 'checkunknown',
520 coreconfigitem('merge', 'checkunknown',
517 default='abort',
521 default='abort',
518 )
522 )
519 coreconfigitem('merge', 'checkignored',
523 coreconfigitem('merge', 'checkignored',
520 default='abort',
524 default='abort',
521 )
525 )
522 coreconfigitem('merge', 'followcopies',
526 coreconfigitem('merge', 'followcopies',
523 default=True,
527 default=True,
524 )
528 )
525 coreconfigitem('merge', 'preferancestor',
529 coreconfigitem('merge', 'preferancestor',
526 default=lambda: ['*'],
530 default=lambda: ['*'],
527 )
531 )
528 coreconfigitem('pager', 'attend-.*',
532 coreconfigitem('pager', 'attend-.*',
529 default=dynamicdefault,
533 default=dynamicdefault,
530 generic=True,
534 generic=True,
531 )
535 )
532 coreconfigitem('pager', 'ignore',
536 coreconfigitem('pager', 'ignore',
533 default=list,
537 default=list,
534 )
538 )
535 coreconfigitem('pager', 'pager',
539 coreconfigitem('pager', 'pager',
536 default=dynamicdefault,
540 default=dynamicdefault,
537 )
541 )
538 coreconfigitem('patch', 'eol',
542 coreconfigitem('patch', 'eol',
539 default='strict',
543 default='strict',
540 )
544 )
541 coreconfigitem('patch', 'fuzz',
545 coreconfigitem('patch', 'fuzz',
542 default=2,
546 default=2,
543 )
547 )
544 coreconfigitem('paths', 'default',
548 coreconfigitem('paths', 'default',
545 default=None,
549 default=None,
546 )
550 )
547 coreconfigitem('paths', 'default-push',
551 coreconfigitem('paths', 'default-push',
548 default=None,
552 default=None,
549 )
553 )
550 coreconfigitem('paths', '.*',
554 coreconfigitem('paths', '.*',
551 default=None,
555 default=None,
552 generic=True,
556 generic=True,
553 )
557 )
554 coreconfigitem('phases', 'checksubrepos',
558 coreconfigitem('phases', 'checksubrepos',
555 default='follow',
559 default='follow',
556 )
560 )
557 coreconfigitem('phases', 'new-commit',
561 coreconfigitem('phases', 'new-commit',
558 default='draft',
562 default='draft',
559 )
563 )
560 coreconfigitem('phases', 'publish',
564 coreconfigitem('phases', 'publish',
561 default=True,
565 default=True,
562 )
566 )
563 coreconfigitem('profiling', 'enabled',
567 coreconfigitem('profiling', 'enabled',
564 default=False,
568 default=False,
565 )
569 )
566 coreconfigitem('profiling', 'format',
570 coreconfigitem('profiling', 'format',
567 default='text',
571 default='text',
568 )
572 )
569 coreconfigitem('profiling', 'freq',
573 coreconfigitem('profiling', 'freq',
570 default=1000,
574 default=1000,
571 )
575 )
572 coreconfigitem('profiling', 'limit',
576 coreconfigitem('profiling', 'limit',
573 default=30,
577 default=30,
574 )
578 )
575 coreconfigitem('profiling', 'nested',
579 coreconfigitem('profiling', 'nested',
576 default=0,
580 default=0,
577 )
581 )
578 coreconfigitem('profiling', 'output',
582 coreconfigitem('profiling', 'output',
579 default=None,
583 default=None,
580 )
584 )
581 coreconfigitem('profiling', 'showmax',
585 coreconfigitem('profiling', 'showmax',
582 default=0.999,
586 default=0.999,
583 )
587 )
584 coreconfigitem('profiling', 'showmin',
588 coreconfigitem('profiling', 'showmin',
585 default=dynamicdefault,
589 default=dynamicdefault,
586 )
590 )
587 coreconfigitem('profiling', 'sort',
591 coreconfigitem('profiling', 'sort',
588 default='inlinetime',
592 default='inlinetime',
589 )
593 )
590 coreconfigitem('profiling', 'statformat',
594 coreconfigitem('profiling', 'statformat',
591 default='hotpath',
595 default='hotpath',
592 )
596 )
593 coreconfigitem('profiling', 'type',
597 coreconfigitem('profiling', 'type',
594 default='stat',
598 default='stat',
595 )
599 )
596 coreconfigitem('progress', 'assume-tty',
600 coreconfigitem('progress', 'assume-tty',
597 default=False,
601 default=False,
598 )
602 )
599 coreconfigitem('progress', 'changedelay',
603 coreconfigitem('progress', 'changedelay',
600 default=1,
604 default=1,
601 )
605 )
602 coreconfigitem('progress', 'clear-complete',
606 coreconfigitem('progress', 'clear-complete',
603 default=True,
607 default=True,
604 )
608 )
605 coreconfigitem('progress', 'debug',
609 coreconfigitem('progress', 'debug',
606 default=False,
610 default=False,
607 )
611 )
608 coreconfigitem('progress', 'delay',
612 coreconfigitem('progress', 'delay',
609 default=3,
613 default=3,
610 )
614 )
611 coreconfigitem('progress', 'disable',
615 coreconfigitem('progress', 'disable',
612 default=False,
616 default=False,
613 )
617 )
614 coreconfigitem('progress', 'estimateinterval',
618 coreconfigitem('progress', 'estimateinterval',
615 default=60.0,
619 default=60.0,
616 )
620 )
617 coreconfigitem('progress', 'format',
621 coreconfigitem('progress', 'format',
618 default=lambda: ['topic', 'bar', 'number', 'estimate'],
622 default=lambda: ['topic', 'bar', 'number', 'estimate'],
619 )
623 )
620 coreconfigitem('progress', 'refresh',
624 coreconfigitem('progress', 'refresh',
621 default=0.1,
625 default=0.1,
622 )
626 )
623 coreconfigitem('progress', 'width',
627 coreconfigitem('progress', 'width',
624 default=dynamicdefault,
628 default=dynamicdefault,
625 )
629 )
626 coreconfigitem('push', 'pushvars.server',
630 coreconfigitem('push', 'pushvars.server',
627 default=False,
631 default=False,
628 )
632 )
629 coreconfigitem('server', 'bundle1',
633 coreconfigitem('server', 'bundle1',
630 default=True,
634 default=True,
631 )
635 )
632 coreconfigitem('server', 'bundle1gd',
636 coreconfigitem('server', 'bundle1gd',
633 default=None,
637 default=None,
634 )
638 )
635 coreconfigitem('server', 'bundle1.pull',
639 coreconfigitem('server', 'bundle1.pull',
636 default=None,
640 default=None,
637 )
641 )
638 coreconfigitem('server', 'bundle1gd.pull',
642 coreconfigitem('server', 'bundle1gd.pull',
639 default=None,
643 default=None,
640 )
644 )
641 coreconfigitem('server', 'bundle1.push',
645 coreconfigitem('server', 'bundle1.push',
642 default=None,
646 default=None,
643 )
647 )
644 coreconfigitem('server', 'bundle1gd.push',
648 coreconfigitem('server', 'bundle1gd.push',
645 default=None,
649 default=None,
646 )
650 )
647 coreconfigitem('server', 'compressionengines',
651 coreconfigitem('server', 'compressionengines',
648 default=list,
652 default=list,
649 )
653 )
650 coreconfigitem('server', 'concurrent-push-mode',
654 coreconfigitem('server', 'concurrent-push-mode',
651 default='strict',
655 default='strict',
652 )
656 )
653 coreconfigitem('server', 'disablefullbundle',
657 coreconfigitem('server', 'disablefullbundle',
654 default=False,
658 default=False,
655 )
659 )
656 coreconfigitem('server', 'maxhttpheaderlen',
660 coreconfigitem('server', 'maxhttpheaderlen',
657 default=1024,
661 default=1024,
658 )
662 )
659 coreconfigitem('server', 'preferuncompressed',
663 coreconfigitem('server', 'preferuncompressed',
660 default=False,
664 default=False,
661 )
665 )
662 coreconfigitem('server', 'uncompressed',
666 coreconfigitem('server', 'uncompressed',
663 default=True,
667 default=True,
664 )
668 )
665 coreconfigitem('server', 'uncompressedallowsecret',
669 coreconfigitem('server', 'uncompressedallowsecret',
666 default=False,
670 default=False,
667 )
671 )
668 coreconfigitem('server', 'validate',
672 coreconfigitem('server', 'validate',
669 default=False,
673 default=False,
670 )
674 )
671 coreconfigitem('server', 'zliblevel',
675 coreconfigitem('server', 'zliblevel',
672 default=-1,
676 default=-1,
673 )
677 )
674 coreconfigitem('smtp', 'host',
678 coreconfigitem('smtp', 'host',
675 default=None,
679 default=None,
676 )
680 )
677 coreconfigitem('smtp', 'local_hostname',
681 coreconfigitem('smtp', 'local_hostname',
678 default=None,
682 default=None,
679 )
683 )
680 coreconfigitem('smtp', 'password',
684 coreconfigitem('smtp', 'password',
681 default=None,
685 default=None,
682 )
686 )
683 coreconfigitem('smtp', 'port',
687 coreconfigitem('smtp', 'port',
684 default=dynamicdefault,
688 default=dynamicdefault,
685 )
689 )
686 coreconfigitem('smtp', 'tls',
690 coreconfigitem('smtp', 'tls',
687 default='none',
691 default='none',
688 )
692 )
689 coreconfigitem('smtp', 'username',
693 coreconfigitem('smtp', 'username',
690 default=None,
694 default=None,
691 )
695 )
692 coreconfigitem('sparse', 'missingwarning',
696 coreconfigitem('sparse', 'missingwarning',
693 default=True,
697 default=True,
694 )
698 )
695 coreconfigitem('templates', '.*',
699 coreconfigitem('templates', '.*',
696 default=None,
700 default=None,
697 generic=True,
701 generic=True,
698 )
702 )
699 coreconfigitem('trusted', 'groups',
703 coreconfigitem('trusted', 'groups',
700 default=list,
704 default=list,
701 )
705 )
702 coreconfigitem('trusted', 'users',
706 coreconfigitem('trusted', 'users',
703 default=list,
707 default=list,
704 )
708 )
705 coreconfigitem('ui', '_usedassubrepo',
709 coreconfigitem('ui', '_usedassubrepo',
706 default=False,
710 default=False,
707 )
711 )
708 coreconfigitem('ui', 'allowemptycommit',
712 coreconfigitem('ui', 'allowemptycommit',
709 default=False,
713 default=False,
710 )
714 )
711 coreconfigitem('ui', 'archivemeta',
715 coreconfigitem('ui', 'archivemeta',
712 default=True,
716 default=True,
713 )
717 )
714 coreconfigitem('ui', 'askusername',
718 coreconfigitem('ui', 'askusername',
715 default=False,
719 default=False,
716 )
720 )
717 coreconfigitem('ui', 'clonebundlefallback',
721 coreconfigitem('ui', 'clonebundlefallback',
718 default=False,
722 default=False,
719 )
723 )
720 coreconfigitem('ui', 'clonebundleprefers',
724 coreconfigitem('ui', 'clonebundleprefers',
721 default=list,
725 default=list,
722 )
726 )
723 coreconfigitem('ui', 'clonebundles',
727 coreconfigitem('ui', 'clonebundles',
724 default=True,
728 default=True,
725 )
729 )
726 coreconfigitem('ui', 'color',
730 coreconfigitem('ui', 'color',
727 default='auto',
731 default='auto',
728 )
732 )
729 coreconfigitem('ui', 'commitsubrepos',
733 coreconfigitem('ui', 'commitsubrepos',
730 default=False,
734 default=False,
731 )
735 )
732 coreconfigitem('ui', 'debug',
736 coreconfigitem('ui', 'debug',
733 default=False,
737 default=False,
734 )
738 )
735 coreconfigitem('ui', 'debugger',
739 coreconfigitem('ui', 'debugger',
736 default=None,
740 default=None,
737 )
741 )
738 coreconfigitem('ui', 'fallbackencoding',
742 coreconfigitem('ui', 'fallbackencoding',
739 default=None,
743 default=None,
740 )
744 )
741 coreconfigitem('ui', 'forcecwd',
745 coreconfigitem('ui', 'forcecwd',
742 default=None,
746 default=None,
743 )
747 )
744 coreconfigitem('ui', 'forcemerge',
748 coreconfigitem('ui', 'forcemerge',
745 default=None,
749 default=None,
746 )
750 )
747 coreconfigitem('ui', 'formatdebug',
751 coreconfigitem('ui', 'formatdebug',
748 default=False,
752 default=False,
749 )
753 )
750 coreconfigitem('ui', 'formatjson',
754 coreconfigitem('ui', 'formatjson',
751 default=False,
755 default=False,
752 )
756 )
753 coreconfigitem('ui', 'formatted',
757 coreconfigitem('ui', 'formatted',
754 default=None,
758 default=None,
755 )
759 )
756 coreconfigitem('ui', 'graphnodetemplate',
760 coreconfigitem('ui', 'graphnodetemplate',
757 default=None,
761 default=None,
758 )
762 )
759 coreconfigitem('ui', 'http2debuglevel',
763 coreconfigitem('ui', 'http2debuglevel',
760 default=None,
764 default=None,
761 )
765 )
762 coreconfigitem('ui', 'interactive',
766 coreconfigitem('ui', 'interactive',
763 default=None,
767 default=None,
764 )
768 )
765 coreconfigitem('ui', 'interface',
769 coreconfigitem('ui', 'interface',
766 default=None,
770 default=None,
767 )
771 )
768 coreconfigitem('ui', 'interface.chunkselector',
772 coreconfigitem('ui', 'interface.chunkselector',
769 default=None,
773 default=None,
770 )
774 )
771 coreconfigitem('ui', 'logblockedtimes',
775 coreconfigitem('ui', 'logblockedtimes',
772 default=False,
776 default=False,
773 )
777 )
774 coreconfigitem('ui', 'logtemplate',
778 coreconfigitem('ui', 'logtemplate',
775 default=None,
779 default=None,
776 )
780 )
777 coreconfigitem('ui', 'merge',
781 coreconfigitem('ui', 'merge',
778 default=None,
782 default=None,
779 )
783 )
780 coreconfigitem('ui', 'mergemarkers',
784 coreconfigitem('ui', 'mergemarkers',
781 default='basic',
785 default='basic',
782 )
786 )
783 coreconfigitem('ui', 'mergemarkertemplate',
787 coreconfigitem('ui', 'mergemarkertemplate',
784 default=('{node|short} '
788 default=('{node|short} '
785 '{ifeq(tags, "tip", "", '
789 '{ifeq(tags, "tip", "", '
786 'ifeq(tags, "", "", "{tags} "))}'
790 'ifeq(tags, "", "", "{tags} "))}'
787 '{if(bookmarks, "{bookmarks} ")}'
791 '{if(bookmarks, "{bookmarks} ")}'
788 '{ifeq(branch, "default", "", "{branch} ")}'
792 '{ifeq(branch, "default", "", "{branch} ")}'
789 '- {author|user}: {desc|firstline}')
793 '- {author|user}: {desc|firstline}')
790 )
794 )
791 coreconfigitem('ui', 'nontty',
795 coreconfigitem('ui', 'nontty',
792 default=False,
796 default=False,
793 )
797 )
794 coreconfigitem('ui', 'origbackuppath',
798 coreconfigitem('ui', 'origbackuppath',
795 default=None,
799 default=None,
796 )
800 )
797 coreconfigitem('ui', 'paginate',
801 coreconfigitem('ui', 'paginate',
798 default=True,
802 default=True,
799 )
803 )
800 coreconfigitem('ui', 'patch',
804 coreconfigitem('ui', 'patch',
801 default=None,
805 default=None,
802 )
806 )
803 coreconfigitem('ui', 'portablefilenames',
807 coreconfigitem('ui', 'portablefilenames',
804 default='warn',
808 default='warn',
805 )
809 )
806 coreconfigitem('ui', 'promptecho',
810 coreconfigitem('ui', 'promptecho',
807 default=False,
811 default=False,
808 )
812 )
809 coreconfigitem('ui', 'quiet',
813 coreconfigitem('ui', 'quiet',
810 default=False,
814 default=False,
811 )
815 )
812 coreconfigitem('ui', 'quietbookmarkmove',
816 coreconfigitem('ui', 'quietbookmarkmove',
813 default=False,
817 default=False,
814 )
818 )
815 coreconfigitem('ui', 'remotecmd',
819 coreconfigitem('ui', 'remotecmd',
816 default='hg',
820 default='hg',
817 )
821 )
818 coreconfigitem('ui', 'report_untrusted',
822 coreconfigitem('ui', 'report_untrusted',
819 default=True,
823 default=True,
820 )
824 )
821 coreconfigitem('ui', 'rollback',
825 coreconfigitem('ui', 'rollback',
822 default=True,
826 default=True,
823 )
827 )
824 coreconfigitem('ui', 'slash',
828 coreconfigitem('ui', 'slash',
825 default=False,
829 default=False,
826 )
830 )
827 coreconfigitem('ui', 'ssh',
831 coreconfigitem('ui', 'ssh',
828 default='ssh',
832 default='ssh',
829 )
833 )
830 coreconfigitem('ui', 'statuscopies',
834 coreconfigitem('ui', 'statuscopies',
831 default=False,
835 default=False,
832 )
836 )
833 coreconfigitem('ui', 'strict',
837 coreconfigitem('ui', 'strict',
834 default=False,
838 default=False,
835 )
839 )
836 coreconfigitem('ui', 'style',
840 coreconfigitem('ui', 'style',
837 default='',
841 default='',
838 )
842 )
839 coreconfigitem('ui', 'supportcontact',
843 coreconfigitem('ui', 'supportcontact',
840 default=None,
844 default=None,
841 )
845 )
842 coreconfigitem('ui', 'textwidth',
846 coreconfigitem('ui', 'textwidth',
843 default=78,
847 default=78,
844 )
848 )
845 coreconfigitem('ui', 'timeout',
849 coreconfigitem('ui', 'timeout',
846 default='600',
850 default='600',
847 )
851 )
848 coreconfigitem('ui', 'traceback',
852 coreconfigitem('ui', 'traceback',
849 default=False,
853 default=False,
850 )
854 )
851 coreconfigitem('ui', 'tweakdefaults',
855 coreconfigitem('ui', 'tweakdefaults',
852 default=False,
856 default=False,
853 )
857 )
854 coreconfigitem('ui', 'usehttp2',
858 coreconfigitem('ui', 'usehttp2',
855 default=False,
859 default=False,
856 )
860 )
857 coreconfigitem('ui', 'username',
861 coreconfigitem('ui', 'username',
858 alias=[('ui', 'user')]
862 alias=[('ui', 'user')]
859 )
863 )
860 coreconfigitem('ui', 'verbose',
864 coreconfigitem('ui', 'verbose',
861 default=False,
865 default=False,
862 )
866 )
863 coreconfigitem('verify', 'skipflags',
867 coreconfigitem('verify', 'skipflags',
864 default=None,
868 default=None,
865 )
869 )
866 coreconfigitem('web', 'allowbz2',
870 coreconfigitem('web', 'allowbz2',
867 default=False,
871 default=False,
868 )
872 )
869 coreconfigitem('web', 'allowgz',
873 coreconfigitem('web', 'allowgz',
870 default=False,
874 default=False,
871 )
875 )
872 coreconfigitem('web', 'allowpull',
876 coreconfigitem('web', 'allowpull',
873 default=True,
877 default=True,
874 )
878 )
875 coreconfigitem('web', 'allow_push',
879 coreconfigitem('web', 'allow_push',
876 default=list,
880 default=list,
877 )
881 )
878 coreconfigitem('web', 'allowzip',
882 coreconfigitem('web', 'allowzip',
879 default=False,
883 default=False,
880 )
884 )
881 coreconfigitem('web', 'cache',
885 coreconfigitem('web', 'cache',
882 default=True,
886 default=True,
883 )
887 )
884 coreconfigitem('web', 'contact',
888 coreconfigitem('web', 'contact',
885 default=None,
889 default=None,
886 )
890 )
887 coreconfigitem('web', 'deny_push',
891 coreconfigitem('web', 'deny_push',
888 default=list,
892 default=list,
889 )
893 )
890 coreconfigitem('web', 'guessmime',
894 coreconfigitem('web', 'guessmime',
891 default=False,
895 default=False,
892 )
896 )
893 coreconfigitem('web', 'hidden',
897 coreconfigitem('web', 'hidden',
894 default=False,
898 default=False,
895 )
899 )
896 coreconfigitem('web', 'labels',
900 coreconfigitem('web', 'labels',
897 default=list,
901 default=list,
898 )
902 )
899 coreconfigitem('web', 'logoimg',
903 coreconfigitem('web', 'logoimg',
900 default='hglogo.png',
904 default='hglogo.png',
901 )
905 )
902 coreconfigitem('web', 'logourl',
906 coreconfigitem('web', 'logourl',
903 default='https://mercurial-scm.org/',
907 default='https://mercurial-scm.org/',
904 )
908 )
905 coreconfigitem('web', 'accesslog',
909 coreconfigitem('web', 'accesslog',
906 default='-',
910 default='-',
907 )
911 )
908 coreconfigitem('web', 'address',
912 coreconfigitem('web', 'address',
909 default='',
913 default='',
910 )
914 )
911 coreconfigitem('web', 'allow_archive',
915 coreconfigitem('web', 'allow_archive',
912 default=list,
916 default=list,
913 )
917 )
914 coreconfigitem('web', 'allow_read',
918 coreconfigitem('web', 'allow_read',
915 default=list,
919 default=list,
916 )
920 )
917 coreconfigitem('web', 'baseurl',
921 coreconfigitem('web', 'baseurl',
918 default=None,
922 default=None,
919 )
923 )
920 coreconfigitem('web', 'cacerts',
924 coreconfigitem('web', 'cacerts',
921 default=None,
925 default=None,
922 )
926 )
923 coreconfigitem('web', 'certificate',
927 coreconfigitem('web', 'certificate',
924 default=None,
928 default=None,
925 )
929 )
926 coreconfigitem('web', 'collapse',
930 coreconfigitem('web', 'collapse',
927 default=False,
931 default=False,
928 )
932 )
929 coreconfigitem('web', 'csp',
933 coreconfigitem('web', 'csp',
930 default=None,
934 default=None,
931 )
935 )
932 coreconfigitem('web', 'deny_read',
936 coreconfigitem('web', 'deny_read',
933 default=list,
937 default=list,
934 )
938 )
935 coreconfigitem('web', 'descend',
939 coreconfigitem('web', 'descend',
936 default=True,
940 default=True,
937 )
941 )
938 coreconfigitem('web', 'description',
942 coreconfigitem('web', 'description',
939 default="",
943 default="",
940 )
944 )
941 coreconfigitem('web', 'encoding',
945 coreconfigitem('web', 'encoding',
942 default=lambda: encoding.encoding,
946 default=lambda: encoding.encoding,
943 )
947 )
944 coreconfigitem('web', 'errorlog',
948 coreconfigitem('web', 'errorlog',
945 default='-',
949 default='-',
946 )
950 )
947 coreconfigitem('web', 'ipv6',
951 coreconfigitem('web', 'ipv6',
948 default=False,
952 default=False,
949 )
953 )
950 coreconfigitem('web', 'maxchanges',
954 coreconfigitem('web', 'maxchanges',
951 default=10,
955 default=10,
952 )
956 )
953 coreconfigitem('web', 'maxfiles',
957 coreconfigitem('web', 'maxfiles',
954 default=10,
958 default=10,
955 )
959 )
956 coreconfigitem('web', 'maxshortchanges',
960 coreconfigitem('web', 'maxshortchanges',
957 default=60,
961 default=60,
958 )
962 )
959 coreconfigitem('web', 'motd',
963 coreconfigitem('web', 'motd',
960 default='',
964 default='',
961 )
965 )
962 coreconfigitem('web', 'name',
966 coreconfigitem('web', 'name',
963 default=dynamicdefault,
967 default=dynamicdefault,
964 )
968 )
965 coreconfigitem('web', 'port',
969 coreconfigitem('web', 'port',
966 default=8000,
970 default=8000,
967 )
971 )
968 coreconfigitem('web', 'prefix',
972 coreconfigitem('web', 'prefix',
969 default='',
973 default='',
970 )
974 )
971 coreconfigitem('web', 'push_ssl',
975 coreconfigitem('web', 'push_ssl',
972 default=True,
976 default=True,
973 )
977 )
974 coreconfigitem('web', 'refreshinterval',
978 coreconfigitem('web', 'refreshinterval',
975 default=20,
979 default=20,
976 )
980 )
977 coreconfigitem('web', 'staticurl',
981 coreconfigitem('web', 'staticurl',
978 default=None,
982 default=None,
979 )
983 )
980 coreconfigitem('web', 'stripes',
984 coreconfigitem('web', 'stripes',
981 default=1,
985 default=1,
982 )
986 )
983 coreconfigitem('web', 'style',
987 coreconfigitem('web', 'style',
984 default='paper',
988 default='paper',
985 )
989 )
986 coreconfigitem('web', 'templates',
990 coreconfigitem('web', 'templates',
987 default=None,
991 default=None,
988 )
992 )
989 coreconfigitem('web', 'view',
993 coreconfigitem('web', 'view',
990 default='served',
994 default='served',
991 )
995 )
992 coreconfigitem('worker', 'backgroundclose',
996 coreconfigitem('worker', 'backgroundclose',
993 default=dynamicdefault,
997 default=dynamicdefault,
994 )
998 )
995 # Windows defaults to a limit of 512 open files. A buffer of 128
999 # Windows defaults to a limit of 512 open files. A buffer of 128
996 # should give us enough headway.
1000 # should give us enough headway.
997 coreconfigitem('worker', 'backgroundclosemaxqueue',
1001 coreconfigitem('worker', 'backgroundclosemaxqueue',
998 default=384,
1002 default=384,
999 )
1003 )
1000 coreconfigitem('worker', 'backgroundcloseminfilecount',
1004 coreconfigitem('worker', 'backgroundcloseminfilecount',
1001 default=2048,
1005 default=2048,
1002 )
1006 )
1003 coreconfigitem('worker', 'backgroundclosethreadcount',
1007 coreconfigitem('worker', 'backgroundclosethreadcount',
1004 default=4,
1008 default=4,
1005 )
1009 )
1006 coreconfigitem('worker', 'numcpus',
1010 coreconfigitem('worker', 'numcpus',
1007 default=None,
1011 default=None,
1008 )
1012 )
@@ -1,865 +1,864 b''
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import ssl
15 import ssl
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 error,
19 error,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 )
22 )
23
23
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
26 # all exposed via the "ssl" module.
26 # all exposed via the "ssl" module.
27 #
27 #
28 # Depending on the version of Python being used, SSL/TLS support is either
28 # Depending on the version of Python being used, SSL/TLS support is either
29 # modern/secure or legacy/insecure. Many operations in this module have
29 # modern/secure or legacy/insecure. Many operations in this module have
30 # separate code paths depending on support in Python.
30 # separate code paths depending on support in Python.
31
31
32 configprotocols = {
32 configprotocols = {
33 'tls1.0',
33 'tls1.0',
34 'tls1.1',
34 'tls1.1',
35 'tls1.2',
35 'tls1.2',
36 }
36 }
37
37
38 hassni = getattr(ssl, 'HAS_SNI', False)
38 hassni = getattr(ssl, 'HAS_SNI', False)
39
39
40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
41 # against doesn't support them.
41 # against doesn't support them.
42 supportedprotocols = {'tls1.0'}
42 supportedprotocols = {'tls1.0'}
43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
44 supportedprotocols.add('tls1.1')
44 supportedprotocols.add('tls1.1')
45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
46 supportedprotocols.add('tls1.2')
46 supportedprotocols.add('tls1.2')
47
47
48 try:
48 try:
49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
50 # SSL/TLS features are available.
50 # SSL/TLS features are available.
51 SSLContext = ssl.SSLContext
51 SSLContext = ssl.SSLContext
52 modernssl = True
52 modernssl = True
53 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
53 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
54 except AttributeError:
54 except AttributeError:
55 modernssl = False
55 modernssl = False
56 _canloaddefaultcerts = False
56 _canloaddefaultcerts = False
57
57
58 # We implement SSLContext using the interface from the standard library.
58 # We implement SSLContext using the interface from the standard library.
59 class SSLContext(object):
59 class SSLContext(object):
60 def __init__(self, protocol):
60 def __init__(self, protocol):
61 # From the public interface of SSLContext
61 # From the public interface of SSLContext
62 self.protocol = protocol
62 self.protocol = protocol
63 self.check_hostname = False
63 self.check_hostname = False
64 self.options = 0
64 self.options = 0
65 self.verify_mode = ssl.CERT_NONE
65 self.verify_mode = ssl.CERT_NONE
66
66
67 # Used by our implementation.
67 # Used by our implementation.
68 self._certfile = None
68 self._certfile = None
69 self._keyfile = None
69 self._keyfile = None
70 self._certpassword = None
70 self._certpassword = None
71 self._cacerts = None
71 self._cacerts = None
72 self._ciphers = None
72 self._ciphers = None
73
73
74 def load_cert_chain(self, certfile, keyfile=None, password=None):
74 def load_cert_chain(self, certfile, keyfile=None, password=None):
75 self._certfile = certfile
75 self._certfile = certfile
76 self._keyfile = keyfile
76 self._keyfile = keyfile
77 self._certpassword = password
77 self._certpassword = password
78
78
79 def load_default_certs(self, purpose=None):
79 def load_default_certs(self, purpose=None):
80 pass
80 pass
81
81
82 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
82 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
83 if capath:
83 if capath:
84 raise error.Abort(_('capath not supported'))
84 raise error.Abort(_('capath not supported'))
85 if cadata:
85 if cadata:
86 raise error.Abort(_('cadata not supported'))
86 raise error.Abort(_('cadata not supported'))
87
87
88 self._cacerts = cafile
88 self._cacerts = cafile
89
89
90 def set_ciphers(self, ciphers):
90 def set_ciphers(self, ciphers):
91 self._ciphers = ciphers
91 self._ciphers = ciphers
92
92
93 def wrap_socket(self, socket, server_hostname=None, server_side=False):
93 def wrap_socket(self, socket, server_hostname=None, server_side=False):
94 # server_hostname is unique to SSLContext.wrap_socket and is used
94 # server_hostname is unique to SSLContext.wrap_socket and is used
95 # for SNI in that context. So there's nothing for us to do with it
95 # for SNI in that context. So there's nothing for us to do with it
96 # in this legacy code since we don't support SNI.
96 # in this legacy code since we don't support SNI.
97
97
98 args = {
98 args = {
99 'keyfile': self._keyfile,
99 'keyfile': self._keyfile,
100 'certfile': self._certfile,
100 'certfile': self._certfile,
101 'server_side': server_side,
101 'server_side': server_side,
102 'cert_reqs': self.verify_mode,
102 'cert_reqs': self.verify_mode,
103 'ssl_version': self.protocol,
103 'ssl_version': self.protocol,
104 'ca_certs': self._cacerts,
104 'ca_certs': self._cacerts,
105 'ciphers': self._ciphers,
105 'ciphers': self._ciphers,
106 }
106 }
107
107
108 return ssl.wrap_socket(socket, **args)
108 return ssl.wrap_socket(socket, **args)
109
109
110 def _hostsettings(ui, hostname):
110 def _hostsettings(ui, hostname):
111 """Obtain security settings for a hostname.
111 """Obtain security settings for a hostname.
112
112
113 Returns a dict of settings relevant to that hostname.
113 Returns a dict of settings relevant to that hostname.
114 """
114 """
115 s = {
115 s = {
116 # Whether we should attempt to load default/available CA certs
116 # Whether we should attempt to load default/available CA certs
117 # if an explicit ``cafile`` is not defined.
117 # if an explicit ``cafile`` is not defined.
118 'allowloaddefaultcerts': True,
118 'allowloaddefaultcerts': True,
119 # List of 2-tuple of (hash algorithm, hash).
119 # List of 2-tuple of (hash algorithm, hash).
120 'certfingerprints': [],
120 'certfingerprints': [],
121 # Path to file containing concatenated CA certs. Used by
121 # Path to file containing concatenated CA certs. Used by
122 # SSLContext.load_verify_locations().
122 # SSLContext.load_verify_locations().
123 'cafile': None,
123 'cafile': None,
124 # Whether certificate verification should be disabled.
124 # Whether certificate verification should be disabled.
125 'disablecertverification': False,
125 'disablecertverification': False,
126 # Whether the legacy [hostfingerprints] section has data for this host.
126 # Whether the legacy [hostfingerprints] section has data for this host.
127 'legacyfingerprint': False,
127 'legacyfingerprint': False,
128 # PROTOCOL_* constant to use for SSLContext.__init__.
128 # PROTOCOL_* constant to use for SSLContext.__init__.
129 'protocol': None,
129 'protocol': None,
130 # String representation of minimum protocol to be used for UI
130 # String representation of minimum protocol to be used for UI
131 # presentation.
131 # presentation.
132 'protocolui': None,
132 'protocolui': None,
133 # ssl.CERT_* constant used by SSLContext.verify_mode.
133 # ssl.CERT_* constant used by SSLContext.verify_mode.
134 'verifymode': None,
134 'verifymode': None,
135 # Defines extra ssl.OP* bitwise options to set.
135 # Defines extra ssl.OP* bitwise options to set.
136 'ctxoptions': None,
136 'ctxoptions': None,
137 # OpenSSL Cipher List to use (instead of default).
137 # OpenSSL Cipher List to use (instead of default).
138 'ciphers': None,
138 'ciphers': None,
139 }
139 }
140
140
141 # Allow minimum TLS protocol to be specified in the config.
141 # Allow minimum TLS protocol to be specified in the config.
142 def validateprotocol(protocol, key):
142 def validateprotocol(protocol, key):
143 if protocol not in configprotocols:
143 if protocol not in configprotocols:
144 raise error.Abort(
144 raise error.Abort(
145 _('unsupported protocol from hostsecurity.%s: %s') %
145 _('unsupported protocol from hostsecurity.%s: %s') %
146 (key, protocol),
146 (key, protocol),
147 hint=_('valid protocols: %s') %
147 hint=_('valid protocols: %s') %
148 ' '.join(sorted(configprotocols)))
148 ' '.join(sorted(configprotocols)))
149
149
150 # We default to TLS 1.1+ where we can because TLS 1.0 has known
150 # We default to TLS 1.1+ where we can because TLS 1.0 has known
151 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
151 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
152 # TLS 1.0+ via config options in case a legacy server is encountered.
152 # TLS 1.0+ via config options in case a legacy server is encountered.
153 if 'tls1.1' in supportedprotocols:
153 if 'tls1.1' in supportedprotocols:
154 defaultprotocol = 'tls1.1'
154 defaultprotocol = 'tls1.1'
155 else:
155 else:
156 # Let people know they are borderline secure.
156 # Let people know they are borderline secure.
157 # We don't document this config option because we want people to see
157 # We don't document this config option because we want people to see
158 # the bold warnings on the web site.
158 # the bold warnings on the web site.
159 # internal config: hostsecurity.disabletls10warning
159 # internal config: hostsecurity.disabletls10warning
160 if not ui.configbool('hostsecurity', 'disabletls10warning'):
160 if not ui.configbool('hostsecurity', 'disabletls10warning'):
161 ui.warn(_('warning: connecting to %s using legacy security '
161 ui.warn(_('warning: connecting to %s using legacy security '
162 'technology (TLS 1.0); see '
162 'technology (TLS 1.0); see '
163 'https://mercurial-scm.org/wiki/SecureConnections for '
163 'https://mercurial-scm.org/wiki/SecureConnections for '
164 'more info\n') % hostname)
164 'more info\n') % hostname)
165 defaultprotocol = 'tls1.0'
165 defaultprotocol = 'tls1.0'
166
166
167 key = 'minimumprotocol'
167 key = 'minimumprotocol'
168 protocol = ui.config('hostsecurity', key, defaultprotocol)
168 protocol = ui.config('hostsecurity', key, defaultprotocol)
169 validateprotocol(protocol, key)
169 validateprotocol(protocol, key)
170
170
171 key = '%s:minimumprotocol' % hostname
171 key = '%s:minimumprotocol' % hostname
172 protocol = ui.config('hostsecurity', key, protocol)
172 protocol = ui.config('hostsecurity', key, protocol)
173 validateprotocol(protocol, key)
173 validateprotocol(protocol, key)
174
174
175 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
175 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
176 # We always print a "connection security to %s is disabled..." message when
176 # We always print a "connection security to %s is disabled..." message when
177 # --insecure is used. So no need to print anything more here.
177 # --insecure is used. So no need to print anything more here.
178 if ui.insecureconnections:
178 if ui.insecureconnections:
179 protocol = 'tls1.0'
179 protocol = 'tls1.0'
180
180
181 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
181 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
182
182
183 ciphers = ui.config('hostsecurity', 'ciphers')
183 ciphers = ui.config('hostsecurity', 'ciphers')
184 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
184 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
185 s['ciphers'] = ciphers
185 s['ciphers'] = ciphers
186
186
187 # Look for fingerprints in [hostsecurity] section. Value is a list
187 # Look for fingerprints in [hostsecurity] section. Value is a list
188 # of <alg>:<fingerprint> strings.
188 # of <alg>:<fingerprint> strings.
189 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
189 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname)
190 [])
191 for fingerprint in fingerprints:
190 for fingerprint in fingerprints:
192 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
191 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
193 raise error.Abort(_('invalid fingerprint for %s: %s') % (
192 raise error.Abort(_('invalid fingerprint for %s: %s') % (
194 hostname, fingerprint),
193 hostname, fingerprint),
195 hint=_('must begin with "sha1:", "sha256:", '
194 hint=_('must begin with "sha1:", "sha256:", '
196 'or "sha512:"'))
195 'or "sha512:"'))
197
196
198 alg, fingerprint = fingerprint.split(':', 1)
197 alg, fingerprint = fingerprint.split(':', 1)
199 fingerprint = fingerprint.replace(':', '').lower()
198 fingerprint = fingerprint.replace(':', '').lower()
200 s['certfingerprints'].append((alg, fingerprint))
199 s['certfingerprints'].append((alg, fingerprint))
201
200
202 # Fingerprints from [hostfingerprints] are always SHA-1.
201 # Fingerprints from [hostfingerprints] are always SHA-1.
203 for fingerprint in ui.configlist('hostfingerprints', hostname):
202 for fingerprint in ui.configlist('hostfingerprints', hostname):
204 fingerprint = fingerprint.replace(':', '').lower()
203 fingerprint = fingerprint.replace(':', '').lower()
205 s['certfingerprints'].append(('sha1', fingerprint))
204 s['certfingerprints'].append(('sha1', fingerprint))
206 s['legacyfingerprint'] = True
205 s['legacyfingerprint'] = True
207
206
208 # If a host cert fingerprint is defined, it is the only thing that
207 # If a host cert fingerprint is defined, it is the only thing that
209 # matters. No need to validate CA certs.
208 # matters. No need to validate CA certs.
210 if s['certfingerprints']:
209 if s['certfingerprints']:
211 s['verifymode'] = ssl.CERT_NONE
210 s['verifymode'] = ssl.CERT_NONE
212 s['allowloaddefaultcerts'] = False
211 s['allowloaddefaultcerts'] = False
213
212
214 # If --insecure is used, don't take CAs into consideration.
213 # If --insecure is used, don't take CAs into consideration.
215 elif ui.insecureconnections:
214 elif ui.insecureconnections:
216 s['disablecertverification'] = True
215 s['disablecertverification'] = True
217 s['verifymode'] = ssl.CERT_NONE
216 s['verifymode'] = ssl.CERT_NONE
218 s['allowloaddefaultcerts'] = False
217 s['allowloaddefaultcerts'] = False
219
218
220 if ui.configbool('devel', 'disableloaddefaultcerts'):
219 if ui.configbool('devel', 'disableloaddefaultcerts'):
221 s['allowloaddefaultcerts'] = False
220 s['allowloaddefaultcerts'] = False
222
221
223 # If both fingerprints and a per-host ca file are specified, issue a warning
222 # If both fingerprints and a per-host ca file are specified, issue a warning
224 # because users should not be surprised about what security is or isn't
223 # because users should not be surprised about what security is or isn't
225 # being performed.
224 # being performed.
226 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
225 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
227 if s['certfingerprints'] and cafile:
226 if s['certfingerprints'] and cafile:
228 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
227 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
229 'fingerprints defined; using host fingerprints for '
228 'fingerprints defined; using host fingerprints for '
230 'verification)\n') % hostname)
229 'verification)\n') % hostname)
231
230
232 # Try to hook up CA certificate validation unless something above
231 # Try to hook up CA certificate validation unless something above
233 # makes it not necessary.
232 # makes it not necessary.
234 if s['verifymode'] is None:
233 if s['verifymode'] is None:
235 # Look at per-host ca file first.
234 # Look at per-host ca file first.
236 if cafile:
235 if cafile:
237 cafile = util.expandpath(cafile)
236 cafile = util.expandpath(cafile)
238 if not os.path.exists(cafile):
237 if not os.path.exists(cafile):
239 raise error.Abort(_('path specified by %s does not exist: %s') %
238 raise error.Abort(_('path specified by %s does not exist: %s') %
240 ('hostsecurity.%s:verifycertsfile' % hostname,
239 ('hostsecurity.%s:verifycertsfile' % hostname,
241 cafile))
240 cafile))
242 s['cafile'] = cafile
241 s['cafile'] = cafile
243 else:
242 else:
244 # Find global certificates file in config.
243 # Find global certificates file in config.
245 cafile = ui.config('web', 'cacerts')
244 cafile = ui.config('web', 'cacerts')
246
245
247 if cafile:
246 if cafile:
248 cafile = util.expandpath(cafile)
247 cafile = util.expandpath(cafile)
249 if not os.path.exists(cafile):
248 if not os.path.exists(cafile):
250 raise error.Abort(_('could not find web.cacerts: %s') %
249 raise error.Abort(_('could not find web.cacerts: %s') %
251 cafile)
250 cafile)
252 elif s['allowloaddefaultcerts']:
251 elif s['allowloaddefaultcerts']:
253 # CAs not defined in config. Try to find system bundles.
252 # CAs not defined in config. Try to find system bundles.
254 cafile = _defaultcacerts(ui)
253 cafile = _defaultcacerts(ui)
255 if cafile:
254 if cafile:
256 ui.debug('using %s for CA file\n' % cafile)
255 ui.debug('using %s for CA file\n' % cafile)
257
256
258 s['cafile'] = cafile
257 s['cafile'] = cafile
259
258
260 # Require certificate validation if CA certs are being loaded and
259 # Require certificate validation if CA certs are being loaded and
261 # verification hasn't been disabled above.
260 # verification hasn't been disabled above.
262 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
261 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
263 s['verifymode'] = ssl.CERT_REQUIRED
262 s['verifymode'] = ssl.CERT_REQUIRED
264 else:
263 else:
265 # At this point we don't have a fingerprint, aren't being
264 # At this point we don't have a fingerprint, aren't being
266 # explicitly insecure, and can't load CA certs. Connecting
265 # explicitly insecure, and can't load CA certs. Connecting
267 # is insecure. We allow the connection and abort during
266 # is insecure. We allow the connection and abort during
268 # validation (once we have the fingerprint to print to the
267 # validation (once we have the fingerprint to print to the
269 # user).
268 # user).
270 s['verifymode'] = ssl.CERT_NONE
269 s['verifymode'] = ssl.CERT_NONE
271
270
272 assert s['protocol'] is not None
271 assert s['protocol'] is not None
273 assert s['ctxoptions'] is not None
272 assert s['ctxoptions'] is not None
274 assert s['verifymode'] is not None
273 assert s['verifymode'] is not None
275
274
276 return s
275 return s
277
276
278 def protocolsettings(protocol):
277 def protocolsettings(protocol):
279 """Resolve the protocol for a config value.
278 """Resolve the protocol for a config value.
280
279
281 Returns a 3-tuple of (protocol, options, ui value) where the first
280 Returns a 3-tuple of (protocol, options, ui value) where the first
282 2 items are values used by SSLContext and the last is a string value
281 2 items are values used by SSLContext and the last is a string value
283 of the ``minimumprotocol`` config option equivalent.
282 of the ``minimumprotocol`` config option equivalent.
284 """
283 """
285 if protocol not in configprotocols:
284 if protocol not in configprotocols:
286 raise ValueError('protocol value not supported: %s' % protocol)
285 raise ValueError('protocol value not supported: %s' % protocol)
287
286
288 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
287 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
289 # that both ends support, including TLS protocols. On legacy stacks,
288 # that both ends support, including TLS protocols. On legacy stacks,
290 # the highest it likely goes is TLS 1.0. On modern stacks, it can
289 # the highest it likely goes is TLS 1.0. On modern stacks, it can
291 # support TLS 1.2.
290 # support TLS 1.2.
292 #
291 #
293 # The PROTOCOL_TLSv* constants select a specific TLS version
292 # The PROTOCOL_TLSv* constants select a specific TLS version
294 # only (as opposed to multiple versions). So the method for
293 # only (as opposed to multiple versions). So the method for
295 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
294 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
296 # disable protocols via SSLContext.options and OP_NO_* constants.
295 # disable protocols via SSLContext.options and OP_NO_* constants.
297 # However, SSLContext.options doesn't work unless we have the
296 # However, SSLContext.options doesn't work unless we have the
298 # full/real SSLContext available to us.
297 # full/real SSLContext available to us.
299 if supportedprotocols == {'tls1.0'}:
298 if supportedprotocols == {'tls1.0'}:
300 if protocol != 'tls1.0':
299 if protocol != 'tls1.0':
301 raise error.Abort(_('current Python does not support protocol '
300 raise error.Abort(_('current Python does not support protocol '
302 'setting %s') % protocol,
301 'setting %s') % protocol,
303 hint=_('upgrade Python or disable setting since '
302 hint=_('upgrade Python or disable setting since '
304 'only TLS 1.0 is supported'))
303 'only TLS 1.0 is supported'))
305
304
306 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
305 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
307
306
308 # WARNING: returned options don't work unless the modern ssl module
307 # WARNING: returned options don't work unless the modern ssl module
309 # is available. Be careful when adding options here.
308 # is available. Be careful when adding options here.
310
309
311 # SSLv2 and SSLv3 are broken. We ban them outright.
310 # SSLv2 and SSLv3 are broken. We ban them outright.
312 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
311 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
313
312
314 if protocol == 'tls1.0':
313 if protocol == 'tls1.0':
315 # Defaults above are to use TLS 1.0+
314 # Defaults above are to use TLS 1.0+
316 pass
315 pass
317 elif protocol == 'tls1.1':
316 elif protocol == 'tls1.1':
318 options |= ssl.OP_NO_TLSv1
317 options |= ssl.OP_NO_TLSv1
319 elif protocol == 'tls1.2':
318 elif protocol == 'tls1.2':
320 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
319 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
321 else:
320 else:
322 raise error.Abort(_('this should not happen'))
321 raise error.Abort(_('this should not happen'))
323
322
324 # Prevent CRIME.
323 # Prevent CRIME.
325 # There is no guarantee this attribute is defined on the module.
324 # There is no guarantee this attribute is defined on the module.
326 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
325 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
327
326
328 return ssl.PROTOCOL_SSLv23, options, protocol
327 return ssl.PROTOCOL_SSLv23, options, protocol
329
328
330 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
329 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
331 """Add SSL/TLS to a socket.
330 """Add SSL/TLS to a socket.
332
331
333 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
332 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
334 choices based on what security options are available.
333 choices based on what security options are available.
335
334
336 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
335 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
337 the following additional arguments:
336 the following additional arguments:
338
337
339 * serverhostname - The expected hostname of the remote server. If the
338 * serverhostname - The expected hostname of the remote server. If the
340 server (and client) support SNI, this tells the server which certificate
339 server (and client) support SNI, this tells the server which certificate
341 to use.
340 to use.
342 """
341 """
343 if not serverhostname:
342 if not serverhostname:
344 raise error.Abort(_('serverhostname argument is required'))
343 raise error.Abort(_('serverhostname argument is required'))
345
344
346 for f in (keyfile, certfile):
345 for f in (keyfile, certfile):
347 if f and not os.path.exists(f):
346 if f and not os.path.exists(f):
348 raise error.Abort(_('certificate file (%s) does not exist; '
347 raise error.Abort(_('certificate file (%s) does not exist; '
349 'cannot connect to %s') % (f, serverhostname),
348 'cannot connect to %s') % (f, serverhostname),
350 hint=_('restore missing file or fix references '
349 hint=_('restore missing file or fix references '
351 'in Mercurial config'))
350 'in Mercurial config'))
352
351
353 settings = _hostsettings(ui, serverhostname)
352 settings = _hostsettings(ui, serverhostname)
354
353
355 # We can't use ssl.create_default_context() because it calls
354 # We can't use ssl.create_default_context() because it calls
356 # load_default_certs() unless CA arguments are passed to it. We want to
355 # load_default_certs() unless CA arguments are passed to it. We want to
357 # have explicit control over CA loading because implicitly loading
356 # have explicit control over CA loading because implicitly loading
358 # CAs may undermine the user's intent. For example, a user may define a CA
357 # CAs may undermine the user's intent. For example, a user may define a CA
359 # bundle with a specific CA cert removed. If the system/default CA bundle
358 # bundle with a specific CA cert removed. If the system/default CA bundle
360 # is loaded and contains that removed CA, you've just undone the user's
359 # is loaded and contains that removed CA, you've just undone the user's
361 # choice.
360 # choice.
362 sslcontext = SSLContext(settings['protocol'])
361 sslcontext = SSLContext(settings['protocol'])
363
362
364 # This is a no-op unless using modern ssl.
363 # This is a no-op unless using modern ssl.
365 sslcontext.options |= settings['ctxoptions']
364 sslcontext.options |= settings['ctxoptions']
366
365
367 # This still works on our fake SSLContext.
366 # This still works on our fake SSLContext.
368 sslcontext.verify_mode = settings['verifymode']
367 sslcontext.verify_mode = settings['verifymode']
369
368
370 if settings['ciphers']:
369 if settings['ciphers']:
371 try:
370 try:
372 sslcontext.set_ciphers(settings['ciphers'])
371 sslcontext.set_ciphers(settings['ciphers'])
373 except ssl.SSLError as e:
372 except ssl.SSLError as e:
374 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
373 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
375 hint=_('change cipher string (%s) in config') %
374 hint=_('change cipher string (%s) in config') %
376 settings['ciphers'])
375 settings['ciphers'])
377
376
378 if certfile is not None:
377 if certfile is not None:
379 def password():
378 def password():
380 f = keyfile or certfile
379 f = keyfile or certfile
381 return ui.getpass(_('passphrase for %s: ') % f, '')
380 return ui.getpass(_('passphrase for %s: ') % f, '')
382 sslcontext.load_cert_chain(certfile, keyfile, password)
381 sslcontext.load_cert_chain(certfile, keyfile, password)
383
382
384 if settings['cafile'] is not None:
383 if settings['cafile'] is not None:
385 try:
384 try:
386 sslcontext.load_verify_locations(cafile=settings['cafile'])
385 sslcontext.load_verify_locations(cafile=settings['cafile'])
387 except ssl.SSLError as e:
386 except ssl.SSLError as e:
388 if len(e.args) == 1: # pypy has different SSLError args
387 if len(e.args) == 1: # pypy has different SSLError args
389 msg = e.args[0]
388 msg = e.args[0]
390 else:
389 else:
391 msg = e.args[1]
390 msg = e.args[1]
392 raise error.Abort(_('error loading CA file %s: %s') % (
391 raise error.Abort(_('error loading CA file %s: %s') % (
393 settings['cafile'], msg),
392 settings['cafile'], msg),
394 hint=_('file is empty or malformed?'))
393 hint=_('file is empty or malformed?'))
395 caloaded = True
394 caloaded = True
396 elif settings['allowloaddefaultcerts']:
395 elif settings['allowloaddefaultcerts']:
397 # This is a no-op on old Python.
396 # This is a no-op on old Python.
398 sslcontext.load_default_certs()
397 sslcontext.load_default_certs()
399 caloaded = True
398 caloaded = True
400 else:
399 else:
401 caloaded = False
400 caloaded = False
402
401
403 try:
402 try:
404 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
403 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
405 except ssl.SSLError as e:
404 except ssl.SSLError as e:
406 # If we're doing certificate verification and no CA certs are loaded,
405 # If we're doing certificate verification and no CA certs are loaded,
407 # that is almost certainly the reason why verification failed. Provide
406 # that is almost certainly the reason why verification failed. Provide
408 # a hint to the user.
407 # a hint to the user.
409 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
408 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
410 # only show this warning if modern ssl is available.
409 # only show this warning if modern ssl is available.
411 # The exception handler is here to handle bugs around cert attributes:
410 # The exception handler is here to handle bugs around cert attributes:
412 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
411 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
413 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
412 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
414 # non-empty list, but the following conditional is otherwise True.
413 # non-empty list, but the following conditional is otherwise True.
415 try:
414 try:
416 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
415 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
417 modernssl and not sslcontext.get_ca_certs()):
416 modernssl and not sslcontext.get_ca_certs()):
418 ui.warn(_('(an attempt was made to load CA certificates but '
417 ui.warn(_('(an attempt was made to load CA certificates but '
419 'none were loaded; see '
418 'none were loaded; see '
420 'https://mercurial-scm.org/wiki/SecureConnections '
419 'https://mercurial-scm.org/wiki/SecureConnections '
421 'for how to configure Mercurial to avoid this '
420 'for how to configure Mercurial to avoid this '
422 'error)\n'))
421 'error)\n'))
423 except ssl.SSLError:
422 except ssl.SSLError:
424 pass
423 pass
425 # Try to print more helpful error messages for known failures.
424 # Try to print more helpful error messages for known failures.
426 if util.safehasattr(e, 'reason'):
425 if util.safehasattr(e, 'reason'):
427 # This error occurs when the client and server don't share a
426 # This error occurs when the client and server don't share a
428 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
427 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
429 # outright. Hopefully the reason for this error is that we require
428 # outright. Hopefully the reason for this error is that we require
430 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
429 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
431 # reason, try to emit an actionable warning.
430 # reason, try to emit an actionable warning.
432 if e.reason == 'UNSUPPORTED_PROTOCOL':
431 if e.reason == 'UNSUPPORTED_PROTOCOL':
433 # We attempted TLS 1.0+.
432 # We attempted TLS 1.0+.
434 if settings['protocolui'] == 'tls1.0':
433 if settings['protocolui'] == 'tls1.0':
435 # We support more than just TLS 1.0+. If this happens,
434 # We support more than just TLS 1.0+. If this happens,
436 # the likely scenario is either the client or the server
435 # the likely scenario is either the client or the server
437 # is really old. (e.g. server doesn't support TLS 1.0+ or
436 # is really old. (e.g. server doesn't support TLS 1.0+ or
438 # client doesn't support modern TLS versions introduced
437 # client doesn't support modern TLS versions introduced
439 # several years from when this comment was written).
438 # several years from when this comment was written).
440 if supportedprotocols != {'tls1.0'}:
439 if supportedprotocols != {'tls1.0'}:
441 ui.warn(_(
440 ui.warn(_(
442 '(could not communicate with %s using security '
441 '(could not communicate with %s using security '
443 'protocols %s; if you are using a modern Mercurial '
442 'protocols %s; if you are using a modern Mercurial '
444 'version, consider contacting the operator of this '
443 'version, consider contacting the operator of this '
445 'server; see '
444 'server; see '
446 'https://mercurial-scm.org/wiki/SecureConnections '
445 'https://mercurial-scm.org/wiki/SecureConnections '
447 'for more info)\n') % (
446 'for more info)\n') % (
448 serverhostname,
447 serverhostname,
449 ', '.join(sorted(supportedprotocols))))
448 ', '.join(sorted(supportedprotocols))))
450 else:
449 else:
451 ui.warn(_(
450 ui.warn(_(
452 '(could not communicate with %s using TLS 1.0; the '
451 '(could not communicate with %s using TLS 1.0; the '
453 'likely cause of this is the server no longer '
452 'likely cause of this is the server no longer '
454 'supports TLS 1.0 because it has known security '
453 'supports TLS 1.0 because it has known security '
455 'vulnerabilities; see '
454 'vulnerabilities; see '
456 'https://mercurial-scm.org/wiki/SecureConnections '
455 'https://mercurial-scm.org/wiki/SecureConnections '
457 'for more info)\n') % serverhostname)
456 'for more info)\n') % serverhostname)
458 else:
457 else:
459 # We attempted TLS 1.1+. We can only get here if the client
458 # We attempted TLS 1.1+. We can only get here if the client
460 # supports the configured protocol. So the likely reason is
459 # supports the configured protocol. So the likely reason is
461 # the client wants better security than the server can
460 # the client wants better security than the server can
462 # offer.
461 # offer.
463 ui.warn(_(
462 ui.warn(_(
464 '(could not negotiate a common security protocol (%s+) '
463 '(could not negotiate a common security protocol (%s+) '
465 'with %s; the likely cause is Mercurial is configured '
464 'with %s; the likely cause is Mercurial is configured '
466 'to be more secure than the server can support)\n') % (
465 'to be more secure than the server can support)\n') % (
467 settings['protocolui'], serverhostname))
466 settings['protocolui'], serverhostname))
468 ui.warn(_('(consider contacting the operator of this '
467 ui.warn(_('(consider contacting the operator of this '
469 'server and ask them to support modern TLS '
468 'server and ask them to support modern TLS '
470 'protocol versions; or, set '
469 'protocol versions; or, set '
471 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
470 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
472 'use of legacy, less secure protocols when '
471 'use of legacy, less secure protocols when '
473 'communicating with this server)\n') %
472 'communicating with this server)\n') %
474 serverhostname)
473 serverhostname)
475 ui.warn(_(
474 ui.warn(_(
476 '(see https://mercurial-scm.org/wiki/SecureConnections '
475 '(see https://mercurial-scm.org/wiki/SecureConnections '
477 'for more info)\n'))
476 'for more info)\n'))
478
477
479 elif (e.reason == 'CERTIFICATE_VERIFY_FAILED' and
478 elif (e.reason == 'CERTIFICATE_VERIFY_FAILED' and
480 pycompat.iswindows):
479 pycompat.iswindows):
481
480
482 ui.warn(_('(the full certificate chain may not be available '
481 ui.warn(_('(the full certificate chain may not be available '
483 'locally; see "hg help debugssl")\n'))
482 'locally; see "hg help debugssl")\n'))
484 raise
483 raise
485
484
486 # check if wrap_socket failed silently because socket had been
485 # check if wrap_socket failed silently because socket had been
487 # closed
486 # closed
488 # - see http://bugs.python.org/issue13721
487 # - see http://bugs.python.org/issue13721
489 if not sslsocket.cipher():
488 if not sslsocket.cipher():
490 raise error.Abort(_('ssl connection failed'))
489 raise error.Abort(_('ssl connection failed'))
491
490
492 sslsocket._hgstate = {
491 sslsocket._hgstate = {
493 'caloaded': caloaded,
492 'caloaded': caloaded,
494 'hostname': serverhostname,
493 'hostname': serverhostname,
495 'settings': settings,
494 'settings': settings,
496 'ui': ui,
495 'ui': ui,
497 }
496 }
498
497
499 return sslsocket
498 return sslsocket
500
499
501 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
500 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
502 requireclientcert=False):
501 requireclientcert=False):
503 """Wrap a socket for use by servers.
502 """Wrap a socket for use by servers.
504
503
505 ``certfile`` and ``keyfile`` specify the files containing the certificate's
504 ``certfile`` and ``keyfile`` specify the files containing the certificate's
506 public and private keys, respectively. Both keys can be defined in the same
505 public and private keys, respectively. Both keys can be defined in the same
507 file via ``certfile`` (the private key must come first in the file).
506 file via ``certfile`` (the private key must come first in the file).
508
507
509 ``cafile`` defines the path to certificate authorities.
508 ``cafile`` defines the path to certificate authorities.
510
509
511 ``requireclientcert`` specifies whether to require client certificates.
510 ``requireclientcert`` specifies whether to require client certificates.
512
511
513 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
512 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
514 """
513 """
515 # This function is not used much by core Mercurial, so the error messaging
514 # This function is not used much by core Mercurial, so the error messaging
516 # doesn't have to be as detailed as for wrapsocket().
515 # doesn't have to be as detailed as for wrapsocket().
517 for f in (certfile, keyfile, cafile):
516 for f in (certfile, keyfile, cafile):
518 if f and not os.path.exists(f):
517 if f and not os.path.exists(f):
519 raise error.Abort(_('referenced certificate file (%s) does not '
518 raise error.Abort(_('referenced certificate file (%s) does not '
520 'exist') % f)
519 'exist') % f)
521
520
522 protocol, options, _protocolui = protocolsettings('tls1.0')
521 protocol, options, _protocolui = protocolsettings('tls1.0')
523
522
524 # This config option is intended for use in tests only. It is a giant
523 # This config option is intended for use in tests only. It is a giant
525 # footgun to kill security. Don't define it.
524 # footgun to kill security. Don't define it.
526 exactprotocol = ui.config('devel', 'serverexactprotocol')
525 exactprotocol = ui.config('devel', 'serverexactprotocol')
527 if exactprotocol == 'tls1.0':
526 if exactprotocol == 'tls1.0':
528 protocol = ssl.PROTOCOL_TLSv1
527 protocol = ssl.PROTOCOL_TLSv1
529 elif exactprotocol == 'tls1.1':
528 elif exactprotocol == 'tls1.1':
530 if 'tls1.1' not in supportedprotocols:
529 if 'tls1.1' not in supportedprotocols:
531 raise error.Abort(_('TLS 1.1 not supported by this Python'))
530 raise error.Abort(_('TLS 1.1 not supported by this Python'))
532 protocol = ssl.PROTOCOL_TLSv1_1
531 protocol = ssl.PROTOCOL_TLSv1_1
533 elif exactprotocol == 'tls1.2':
532 elif exactprotocol == 'tls1.2':
534 if 'tls1.2' not in supportedprotocols:
533 if 'tls1.2' not in supportedprotocols:
535 raise error.Abort(_('TLS 1.2 not supported by this Python'))
534 raise error.Abort(_('TLS 1.2 not supported by this Python'))
536 protocol = ssl.PROTOCOL_TLSv1_2
535 protocol = ssl.PROTOCOL_TLSv1_2
537 elif exactprotocol:
536 elif exactprotocol:
538 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
537 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
539 exactprotocol)
538 exactprotocol)
540
539
541 if modernssl:
540 if modernssl:
542 # We /could/ use create_default_context() here since it doesn't load
541 # We /could/ use create_default_context() here since it doesn't load
543 # CAs when configured for client auth. However, it is hard-coded to
542 # CAs when configured for client auth. However, it is hard-coded to
544 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
543 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
545 sslcontext = SSLContext(protocol)
544 sslcontext = SSLContext(protocol)
546 sslcontext.options |= options
545 sslcontext.options |= options
547
546
548 # Improve forward secrecy.
547 # Improve forward secrecy.
549 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
548 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
550 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
549 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
551
550
552 # Use the list of more secure ciphers if found in the ssl module.
551 # Use the list of more secure ciphers if found in the ssl module.
553 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
552 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
554 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
553 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
555 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
554 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
556 else:
555 else:
557 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
556 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
558
557
559 if requireclientcert:
558 if requireclientcert:
560 sslcontext.verify_mode = ssl.CERT_REQUIRED
559 sslcontext.verify_mode = ssl.CERT_REQUIRED
561 else:
560 else:
562 sslcontext.verify_mode = ssl.CERT_NONE
561 sslcontext.verify_mode = ssl.CERT_NONE
563
562
564 if certfile or keyfile:
563 if certfile or keyfile:
565 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
564 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
566
565
567 if cafile:
566 if cafile:
568 sslcontext.load_verify_locations(cafile=cafile)
567 sslcontext.load_verify_locations(cafile=cafile)
569
568
570 return sslcontext.wrap_socket(sock, server_side=True)
569 return sslcontext.wrap_socket(sock, server_side=True)
571
570
572 class wildcarderror(Exception):
571 class wildcarderror(Exception):
573 """Represents an error parsing wildcards in DNS name."""
572 """Represents an error parsing wildcards in DNS name."""
574
573
575 def _dnsnamematch(dn, hostname, maxwildcards=1):
574 def _dnsnamematch(dn, hostname, maxwildcards=1):
576 """Match DNS names according RFC 6125 section 6.4.3.
575 """Match DNS names according RFC 6125 section 6.4.3.
577
576
578 This code is effectively copied from CPython's ssl._dnsname_match.
577 This code is effectively copied from CPython's ssl._dnsname_match.
579
578
580 Returns a bool indicating whether the expected hostname matches
579 Returns a bool indicating whether the expected hostname matches
581 the value in ``dn``.
580 the value in ``dn``.
582 """
581 """
583 pats = []
582 pats = []
584 if not dn:
583 if not dn:
585 return False
584 return False
586
585
587 pieces = dn.split(r'.')
586 pieces = dn.split(r'.')
588 leftmost = pieces[0]
587 leftmost = pieces[0]
589 remainder = pieces[1:]
588 remainder = pieces[1:]
590 wildcards = leftmost.count('*')
589 wildcards = leftmost.count('*')
591 if wildcards > maxwildcards:
590 if wildcards > maxwildcards:
592 raise wildcarderror(
591 raise wildcarderror(
593 _('too many wildcards in certificate DNS name: %s') % dn)
592 _('too many wildcards in certificate DNS name: %s') % dn)
594
593
595 # speed up common case w/o wildcards
594 # speed up common case w/o wildcards
596 if not wildcards:
595 if not wildcards:
597 return dn.lower() == hostname.lower()
596 return dn.lower() == hostname.lower()
598
597
599 # RFC 6125, section 6.4.3, subitem 1.
598 # RFC 6125, section 6.4.3, subitem 1.
600 # The client SHOULD NOT attempt to match a presented identifier in which
599 # The client SHOULD NOT attempt to match a presented identifier in which
601 # the wildcard character comprises a label other than the left-most label.
600 # the wildcard character comprises a label other than the left-most label.
602 if leftmost == '*':
601 if leftmost == '*':
603 # When '*' is a fragment by itself, it matches a non-empty dotless
602 # When '*' is a fragment by itself, it matches a non-empty dotless
604 # fragment.
603 # fragment.
605 pats.append('[^.]+')
604 pats.append('[^.]+')
606 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
605 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
607 # RFC 6125, section 6.4.3, subitem 3.
606 # RFC 6125, section 6.4.3, subitem 3.
608 # The client SHOULD NOT attempt to match a presented identifier
607 # The client SHOULD NOT attempt to match a presented identifier
609 # where the wildcard character is embedded within an A-label or
608 # where the wildcard character is embedded within an A-label or
610 # U-label of an internationalized domain name.
609 # U-label of an internationalized domain name.
611 pats.append(re.escape(leftmost))
610 pats.append(re.escape(leftmost))
612 else:
611 else:
613 # Otherwise, '*' matches any dotless string, e.g. www*
612 # Otherwise, '*' matches any dotless string, e.g. www*
614 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
613 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
615
614
616 # add the remaining fragments, ignore any wildcards
615 # add the remaining fragments, ignore any wildcards
617 for frag in remainder:
616 for frag in remainder:
618 pats.append(re.escape(frag))
617 pats.append(re.escape(frag))
619
618
620 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
619 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
621 return pat.match(hostname) is not None
620 return pat.match(hostname) is not None
622
621
623 def _verifycert(cert, hostname):
622 def _verifycert(cert, hostname):
624 '''Verify that cert (in socket.getpeercert() format) matches hostname.
623 '''Verify that cert (in socket.getpeercert() format) matches hostname.
625 CRLs is not handled.
624 CRLs is not handled.
626
625
627 Returns error message if any problems are found and None on success.
626 Returns error message if any problems are found and None on success.
628 '''
627 '''
629 if not cert:
628 if not cert:
630 return _('no certificate received')
629 return _('no certificate received')
631
630
632 dnsnames = []
631 dnsnames = []
633 san = cert.get('subjectAltName', [])
632 san = cert.get('subjectAltName', [])
634 for key, value in san:
633 for key, value in san:
635 if key == 'DNS':
634 if key == 'DNS':
636 try:
635 try:
637 if _dnsnamematch(value, hostname):
636 if _dnsnamematch(value, hostname):
638 return
637 return
639 except wildcarderror as e:
638 except wildcarderror as e:
640 return e.args[0]
639 return e.args[0]
641
640
642 dnsnames.append(value)
641 dnsnames.append(value)
643
642
644 if not dnsnames:
643 if not dnsnames:
645 # The subject is only checked when there is no DNS in subjectAltName.
644 # The subject is only checked when there is no DNS in subjectAltName.
646 for sub in cert.get('subject', []):
645 for sub in cert.get('subject', []):
647 for key, value in sub:
646 for key, value in sub:
648 # According to RFC 2818 the most specific Common Name must
647 # According to RFC 2818 the most specific Common Name must
649 # be used.
648 # be used.
650 if key == 'commonName':
649 if key == 'commonName':
651 # 'subject' entries are unicode.
650 # 'subject' entries are unicode.
652 try:
651 try:
653 value = value.encode('ascii')
652 value = value.encode('ascii')
654 except UnicodeEncodeError:
653 except UnicodeEncodeError:
655 return _('IDN in certificate not supported')
654 return _('IDN in certificate not supported')
656
655
657 try:
656 try:
658 if _dnsnamematch(value, hostname):
657 if _dnsnamematch(value, hostname):
659 return
658 return
660 except wildcarderror as e:
659 except wildcarderror as e:
661 return e.args[0]
660 return e.args[0]
662
661
663 dnsnames.append(value)
662 dnsnames.append(value)
664
663
665 if len(dnsnames) > 1:
664 if len(dnsnames) > 1:
666 return _('certificate is for %s') % ', '.join(dnsnames)
665 return _('certificate is for %s') % ', '.join(dnsnames)
667 elif len(dnsnames) == 1:
666 elif len(dnsnames) == 1:
668 return _('certificate is for %s') % dnsnames[0]
667 return _('certificate is for %s') % dnsnames[0]
669 else:
668 else:
670 return _('no commonName or subjectAltName found in certificate')
669 return _('no commonName or subjectAltName found in certificate')
671
670
672 def _plainapplepython():
671 def _plainapplepython():
673 """return true if this seems to be a pure Apple Python that
672 """return true if this seems to be a pure Apple Python that
674 * is unfrozen and presumably has the whole mercurial module in the file
673 * is unfrozen and presumably has the whole mercurial module in the file
675 system
674 system
676 * presumably is an Apple Python that uses Apple OpenSSL which has patches
675 * presumably is an Apple Python that uses Apple OpenSSL which has patches
677 for using system certificate store CAs in addition to the provided
676 for using system certificate store CAs in addition to the provided
678 cacerts file
677 cacerts file
679 """
678 """
680 if (not pycompat.isdarwin or util.mainfrozen() or
679 if (not pycompat.isdarwin or util.mainfrozen() or
681 not pycompat.sysexecutable):
680 not pycompat.sysexecutable):
682 return False
681 return False
683 exe = os.path.realpath(pycompat.sysexecutable).lower()
682 exe = os.path.realpath(pycompat.sysexecutable).lower()
684 return (exe.startswith('/usr/bin/python') or
683 return (exe.startswith('/usr/bin/python') or
685 exe.startswith('/system/library/frameworks/python.framework/'))
684 exe.startswith('/system/library/frameworks/python.framework/'))
686
685
687 _systemcacertpaths = [
686 _systemcacertpaths = [
688 # RHEL, CentOS, and Fedora
687 # RHEL, CentOS, and Fedora
689 '/etc/pki/tls/certs/ca-bundle.trust.crt',
688 '/etc/pki/tls/certs/ca-bundle.trust.crt',
690 # Debian, Ubuntu, Gentoo
689 # Debian, Ubuntu, Gentoo
691 '/etc/ssl/certs/ca-certificates.crt',
690 '/etc/ssl/certs/ca-certificates.crt',
692 ]
691 ]
693
692
694 def _defaultcacerts(ui):
693 def _defaultcacerts(ui):
695 """return path to default CA certificates or None.
694 """return path to default CA certificates or None.
696
695
697 It is assumed this function is called when the returned certificates
696 It is assumed this function is called when the returned certificates
698 file will actually be used to validate connections. Therefore this
697 file will actually be used to validate connections. Therefore this
699 function may print warnings or debug messages assuming this usage.
698 function may print warnings or debug messages assuming this usage.
700
699
701 We don't print a message when the Python is able to load default
700 We don't print a message when the Python is able to load default
702 CA certs because this scenario is detected at socket connect time.
701 CA certs because this scenario is detected at socket connect time.
703 """
702 """
704 # The "certifi" Python package provides certificates. If it is installed
703 # The "certifi" Python package provides certificates. If it is installed
705 # and usable, assume the user intends it to be used and use it.
704 # and usable, assume the user intends it to be used and use it.
706 try:
705 try:
707 import certifi
706 import certifi
708 certs = certifi.where()
707 certs = certifi.where()
709 if os.path.exists(certs):
708 if os.path.exists(certs):
710 ui.debug('using ca certificates from certifi\n')
709 ui.debug('using ca certificates from certifi\n')
711 return certs
710 return certs
712 except (ImportError, AttributeError):
711 except (ImportError, AttributeError):
713 pass
712 pass
714
713
715 # On Windows, only the modern ssl module is capable of loading the system
714 # On Windows, only the modern ssl module is capable of loading the system
716 # CA certificates. If we're not capable of doing that, emit a warning
715 # CA certificates. If we're not capable of doing that, emit a warning
717 # because we'll get a certificate verification error later and the lack
716 # because we'll get a certificate verification error later and the lack
718 # of loaded CA certificates will be the reason why.
717 # of loaded CA certificates will be the reason why.
719 # Assertion: this code is only called if certificates are being verified.
718 # Assertion: this code is only called if certificates are being verified.
720 if pycompat.iswindows:
719 if pycompat.iswindows:
721 if not _canloaddefaultcerts:
720 if not _canloaddefaultcerts:
722 ui.warn(_('(unable to load Windows CA certificates; see '
721 ui.warn(_('(unable to load Windows CA certificates; see '
723 'https://mercurial-scm.org/wiki/SecureConnections for '
722 'https://mercurial-scm.org/wiki/SecureConnections for '
724 'how to configure Mercurial to avoid this message)\n'))
723 'how to configure Mercurial to avoid this message)\n'))
725
724
726 return None
725 return None
727
726
728 # Apple's OpenSSL has patches that allow a specially constructed certificate
727 # Apple's OpenSSL has patches that allow a specially constructed certificate
729 # to load the system CA store. If we're running on Apple Python, use this
728 # to load the system CA store. If we're running on Apple Python, use this
730 # trick.
729 # trick.
731 if _plainapplepython():
730 if _plainapplepython():
732 dummycert = os.path.join(
731 dummycert = os.path.join(
733 os.path.dirname(pycompat.fsencode(__file__)), 'dummycert.pem')
732 os.path.dirname(pycompat.fsencode(__file__)), 'dummycert.pem')
734 if os.path.exists(dummycert):
733 if os.path.exists(dummycert):
735 return dummycert
734 return dummycert
736
735
737 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
736 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
738 # load system certs, we're out of luck.
737 # load system certs, we're out of luck.
739 if pycompat.isdarwin:
738 if pycompat.isdarwin:
740 # FUTURE Consider looking for Homebrew or MacPorts installed certs
739 # FUTURE Consider looking for Homebrew or MacPorts installed certs
741 # files. Also consider exporting the keychain certs to a file during
740 # files. Also consider exporting the keychain certs to a file during
742 # Mercurial install.
741 # Mercurial install.
743 if not _canloaddefaultcerts:
742 if not _canloaddefaultcerts:
744 ui.warn(_('(unable to load CA certificates; see '
743 ui.warn(_('(unable to load CA certificates; see '
745 'https://mercurial-scm.org/wiki/SecureConnections for '
744 'https://mercurial-scm.org/wiki/SecureConnections for '
746 'how to configure Mercurial to avoid this message)\n'))
745 'how to configure Mercurial to avoid this message)\n'))
747 return None
746 return None
748
747
749 # / is writable on Windows. Out of an abundance of caution make sure
748 # / is writable on Windows. Out of an abundance of caution make sure
750 # we're not on Windows because paths from _systemcacerts could be installed
749 # we're not on Windows because paths from _systemcacerts could be installed
751 # by non-admin users.
750 # by non-admin users.
752 assert not pycompat.iswindows
751 assert not pycompat.iswindows
753
752
754 # Try to find CA certificates in well-known locations. We print a warning
753 # Try to find CA certificates in well-known locations. We print a warning
755 # when using a found file because we don't want too much silent magic
754 # when using a found file because we don't want too much silent magic
756 # for security settings. The expectation is that proper Mercurial
755 # for security settings. The expectation is that proper Mercurial
757 # installs will have the CA certs path defined at install time and the
756 # installs will have the CA certs path defined at install time and the
758 # installer/packager will make an appropriate decision on the user's
757 # installer/packager will make an appropriate decision on the user's
759 # behalf. We only get here and perform this setting as a feature of
758 # behalf. We only get here and perform this setting as a feature of
760 # last resort.
759 # last resort.
761 if not _canloaddefaultcerts:
760 if not _canloaddefaultcerts:
762 for path in _systemcacertpaths:
761 for path in _systemcacertpaths:
763 if os.path.isfile(path):
762 if os.path.isfile(path):
764 ui.warn(_('(using CA certificates from %s; if you see this '
763 ui.warn(_('(using CA certificates from %s; if you see this '
765 'message, your Mercurial install is not properly '
764 'message, your Mercurial install is not properly '
766 'configured; see '
765 'configured; see '
767 'https://mercurial-scm.org/wiki/SecureConnections '
766 'https://mercurial-scm.org/wiki/SecureConnections '
768 'for how to configure Mercurial to avoid this '
767 'for how to configure Mercurial to avoid this '
769 'message)\n') % path)
768 'message)\n') % path)
770 return path
769 return path
771
770
772 ui.warn(_('(unable to load CA certificates; see '
771 ui.warn(_('(unable to load CA certificates; see '
773 'https://mercurial-scm.org/wiki/SecureConnections for '
772 'https://mercurial-scm.org/wiki/SecureConnections for '
774 'how to configure Mercurial to avoid this message)\n'))
773 'how to configure Mercurial to avoid this message)\n'))
775
774
776 return None
775 return None
777
776
778 def validatesocket(sock):
777 def validatesocket(sock):
779 """Validate a socket meets security requirements.
778 """Validate a socket meets security requirements.
780
779
781 The passed socket must have been created with ``wrapsocket()``.
780 The passed socket must have been created with ``wrapsocket()``.
782 """
781 """
783 host = sock._hgstate['hostname']
782 host = sock._hgstate['hostname']
784 ui = sock._hgstate['ui']
783 ui = sock._hgstate['ui']
785 settings = sock._hgstate['settings']
784 settings = sock._hgstate['settings']
786
785
787 try:
786 try:
788 peercert = sock.getpeercert(True)
787 peercert = sock.getpeercert(True)
789 peercert2 = sock.getpeercert()
788 peercert2 = sock.getpeercert()
790 except AttributeError:
789 except AttributeError:
791 raise error.Abort(_('%s ssl connection error') % host)
790 raise error.Abort(_('%s ssl connection error') % host)
792
791
793 if not peercert:
792 if not peercert:
794 raise error.Abort(_('%s certificate error: '
793 raise error.Abort(_('%s certificate error: '
795 'no certificate received') % host)
794 'no certificate received') % host)
796
795
797 if settings['disablecertverification']:
796 if settings['disablecertverification']:
798 # We don't print the certificate fingerprint because it shouldn't
797 # We don't print the certificate fingerprint because it shouldn't
799 # be necessary: if the user requested certificate verification be
798 # be necessary: if the user requested certificate verification be
800 # disabled, they presumably already saw a message about the inability
799 # disabled, they presumably already saw a message about the inability
801 # to verify the certificate and this message would have printed the
800 # to verify the certificate and this message would have printed the
802 # fingerprint. So printing the fingerprint here adds little to no
801 # fingerprint. So printing the fingerprint here adds little to no
803 # value.
802 # value.
804 ui.warn(_('warning: connection security to %s is disabled per current '
803 ui.warn(_('warning: connection security to %s is disabled per current '
805 'settings; communication is susceptible to eavesdropping '
804 'settings; communication is susceptible to eavesdropping '
806 'and tampering\n') % host)
805 'and tampering\n') % host)
807 return
806 return
808
807
809 # If a certificate fingerprint is pinned, use it and only it to
808 # If a certificate fingerprint is pinned, use it and only it to
810 # validate the remote cert.
809 # validate the remote cert.
811 peerfingerprints = {
810 peerfingerprints = {
812 'sha1': hashlib.sha1(peercert).hexdigest(),
811 'sha1': hashlib.sha1(peercert).hexdigest(),
813 'sha256': hashlib.sha256(peercert).hexdigest(),
812 'sha256': hashlib.sha256(peercert).hexdigest(),
814 'sha512': hashlib.sha512(peercert).hexdigest(),
813 'sha512': hashlib.sha512(peercert).hexdigest(),
815 }
814 }
816
815
817 def fmtfingerprint(s):
816 def fmtfingerprint(s):
818 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
817 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
819
818
820 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
819 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
821
820
822 if settings['certfingerprints']:
821 if settings['certfingerprints']:
823 for hash, fingerprint in settings['certfingerprints']:
822 for hash, fingerprint in settings['certfingerprints']:
824 if peerfingerprints[hash].lower() == fingerprint:
823 if peerfingerprints[hash].lower() == fingerprint:
825 ui.debug('%s certificate matched fingerprint %s:%s\n' %
824 ui.debug('%s certificate matched fingerprint %s:%s\n' %
826 (host, hash, fmtfingerprint(fingerprint)))
825 (host, hash, fmtfingerprint(fingerprint)))
827 if settings['legacyfingerprint']:
826 if settings['legacyfingerprint']:
828 ui.warn(_('(SHA-1 fingerprint for %s found in legacy '
827 ui.warn(_('(SHA-1 fingerprint for %s found in legacy '
829 '[hostfingerprints] section; '
828 '[hostfingerprints] section; '
830 'if you trust this fingerprint, remove the old '
829 'if you trust this fingerprint, remove the old '
831 'SHA-1 fingerprint from [hostfingerprints] and '
830 'SHA-1 fingerprint from [hostfingerprints] and '
832 'add the following entry to the new '
831 'add the following entry to the new '
833 '[hostsecurity] section: %s:fingerprints=%s)\n') %
832 '[hostsecurity] section: %s:fingerprints=%s)\n') %
834 (host, host, nicefingerprint))
833 (host, host, nicefingerprint))
835 return
834 return
836
835
837 # Pinned fingerprint didn't match. This is a fatal error.
836 # Pinned fingerprint didn't match. This is a fatal error.
838 if settings['legacyfingerprint']:
837 if settings['legacyfingerprint']:
839 section = 'hostfingerprint'
838 section = 'hostfingerprint'
840 nice = fmtfingerprint(peerfingerprints['sha1'])
839 nice = fmtfingerprint(peerfingerprints['sha1'])
841 else:
840 else:
842 section = 'hostsecurity'
841 section = 'hostsecurity'
843 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
842 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
844 raise error.Abort(_('certificate for %s has unexpected '
843 raise error.Abort(_('certificate for %s has unexpected '
845 'fingerprint %s') % (host, nice),
844 'fingerprint %s') % (host, nice),
846 hint=_('check %s configuration') % section)
845 hint=_('check %s configuration') % section)
847
846
848 # Security is enabled but no CAs are loaded. We can't establish trust
847 # Security is enabled but no CAs are loaded. We can't establish trust
849 # for the cert so abort.
848 # for the cert so abort.
850 if not sock._hgstate['caloaded']:
849 if not sock._hgstate['caloaded']:
851 raise error.Abort(
850 raise error.Abort(
852 _('unable to verify security of %s (no loaded CA certificates); '
851 _('unable to verify security of %s (no loaded CA certificates); '
853 'refusing to connect') % host,
852 'refusing to connect') % host,
854 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
853 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
855 'how to configure Mercurial to avoid this error or set '
854 'how to configure Mercurial to avoid this error or set '
856 'hostsecurity.%s:fingerprints=%s to trust this server') %
855 'hostsecurity.%s:fingerprints=%s to trust this server') %
857 (host, nicefingerprint))
856 (host, nicefingerprint))
858
857
859 msg = _verifycert(peercert2, host)
858 msg = _verifycert(peercert2, host)
860 if msg:
859 if msg:
861 raise error.Abort(_('%s certificate error: %s') % (host, msg),
860 raise error.Abort(_('%s certificate error: %s') % (host, msg),
862 hint=_('set hostsecurity.%s:certfingerprints=%s '
861 hint=_('set hostsecurity.%s:certfingerprints=%s '
863 'config setting or use --insecure to connect '
862 'config setting or use --insecure to connect '
864 'insecurely') %
863 'insecurely') %
865 (host, nicefingerprint))
864 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now