1 |
|
|
/* $OpenBSD: readconf.c,v 1.280 2017/10/21 23:06:24 millert Exp $ */ |
2 |
|
|
/* |
3 |
|
|
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 |
|
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 |
|
|
* All rights reserved |
6 |
|
|
* Functions for reading the configuration files. |
7 |
|
|
* |
8 |
|
|
* As far as I am concerned, the code I have written for this software |
9 |
|
|
* can be used freely for any purpose. Any derived versions of this |
10 |
|
|
* software must be clearly marked as such, and if the derived work is |
11 |
|
|
* incompatible with the protocol description in the RFC file, it must be |
12 |
|
|
* called by a name other than "ssh" or "Secure Shell". |
13 |
|
|
*/ |
14 |
|
|
|
15 |
|
|
#include <sys/types.h> |
16 |
|
|
#include <sys/stat.h> |
17 |
|
|
#include <sys/socket.h> |
18 |
|
|
#include <sys/wait.h> |
19 |
|
|
#include <sys/un.h> |
20 |
|
|
|
21 |
|
|
#include <netinet/in.h> |
22 |
|
|
#include <netinet/ip.h> |
23 |
|
|
|
24 |
|
|
#include <ctype.h> |
25 |
|
|
#include <errno.h> |
26 |
|
|
#include <fcntl.h> |
27 |
|
|
#include <glob.h> |
28 |
|
|
#include <netdb.h> |
29 |
|
|
#include <paths.h> |
30 |
|
|
#include <pwd.h> |
31 |
|
|
#include <signal.h> |
32 |
|
|
#include <stdio.h> |
33 |
|
|
#include <string.h> |
34 |
|
|
#include <unistd.h> |
35 |
|
|
#include <limits.h> |
36 |
|
|
#include <util.h> |
37 |
|
|
#include <vis.h> |
38 |
|
|
|
39 |
|
|
#include "xmalloc.h" |
40 |
|
|
#include "ssh.h" |
41 |
|
|
#include "compat.h" |
42 |
|
|
#include "cipher.h" |
43 |
|
|
#include "pathnames.h" |
44 |
|
|
#include "log.h" |
45 |
|
|
#include "sshkey.h" |
46 |
|
|
#include "misc.h" |
47 |
|
|
#include "readconf.h" |
48 |
|
|
#include "match.h" |
49 |
|
|
#include "kex.h" |
50 |
|
|
#include "mac.h" |
51 |
|
|
#include "uidswap.h" |
52 |
|
|
#include "myproposal.h" |
53 |
|
|
#include "digest.h" |
54 |
|
|
|
55 |
|
|
/* Format of the configuration file: |
56 |
|
|
|
57 |
|
|
# Configuration data is parsed as follows: |
58 |
|
|
# 1. command line options |
59 |
|
|
# 2. user-specific file |
60 |
|
|
# 3. system-wide file |
61 |
|
|
# Any configuration value is only changed the first time it is set. |
62 |
|
|
# Thus, host-specific definitions should be at the beginning of the |
63 |
|
|
# configuration file, and defaults at the end. |
64 |
|
|
|
65 |
|
|
# Host-specific declarations. These may override anything above. A single |
66 |
|
|
# host may match multiple declarations; these are processed in the order |
67 |
|
|
# that they are given in. |
68 |
|
|
|
69 |
|
|
Host *.ngs.fi ngs.fi |
70 |
|
|
User foo |
71 |
|
|
|
72 |
|
|
Host fake.com |
73 |
|
|
HostName another.host.name.real.org |
74 |
|
|
User blaah |
75 |
|
|
Port 34289 |
76 |
|
|
ForwardX11 no |
77 |
|
|
ForwardAgent no |
78 |
|
|
|
79 |
|
|
Host books.com |
80 |
|
|
RemoteForward 9999 shadows.cs.hut.fi:9999 |
81 |
|
|
Ciphers 3des-cbc |
82 |
|
|
|
83 |
|
|
Host fascist.blob.com |
84 |
|
|
Port 23123 |
85 |
|
|
User tylonen |
86 |
|
|
PasswordAuthentication no |
87 |
|
|
|
88 |
|
|
Host puukko.hut.fi |
89 |
|
|
User t35124p |
90 |
|
|
ProxyCommand ssh-proxy %h %p |
91 |
|
|
|
92 |
|
|
Host *.fr |
93 |
|
|
PublicKeyAuthentication no |
94 |
|
|
|
95 |
|
|
Host *.su |
96 |
|
|
Ciphers aes128-ctr |
97 |
|
|
PasswordAuthentication no |
98 |
|
|
|
99 |
|
|
Host vpn.fake.com |
100 |
|
|
Tunnel yes |
101 |
|
|
TunnelDevice 3 |
102 |
|
|
|
103 |
|
|
# Defaults for various options |
104 |
|
|
Host * |
105 |
|
|
ForwardAgent no |
106 |
|
|
ForwardX11 no |
107 |
|
|
PasswordAuthentication yes |
108 |
|
|
RSAAuthentication yes |
109 |
|
|
RhostsRSAAuthentication yes |
110 |
|
|
StrictHostKeyChecking yes |
111 |
|
|
TcpKeepAlive no |
112 |
|
|
IdentityFile ~/.ssh/identity |
113 |
|
|
Port 22 |
114 |
|
|
EscapeChar ~ |
115 |
|
|
|
116 |
|
|
*/ |
117 |
|
|
|
118 |
|
|
static int read_config_file_depth(const char *filename, struct passwd *pw, |
119 |
|
|
const char *host, const char *original_host, Options *options, |
120 |
|
|
int flags, int *activep, int depth); |
121 |
|
|
static int process_config_line_depth(Options *options, struct passwd *pw, |
122 |
|
|
const char *host, const char *original_host, char *line, |
123 |
|
|
const char *filename, int linenum, int *activep, int flags, int depth); |
124 |
|
|
|
125 |
|
|
/* Keyword tokens. */ |
126 |
|
|
|
127 |
|
|
typedef enum { |
128 |
|
|
oBadOption, |
129 |
|
|
oHost, oMatch, oInclude, |
130 |
|
|
oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, |
131 |
|
|
oGatewayPorts, oExitOnForwardFailure, |
132 |
|
|
oPasswordAuthentication, oRSAAuthentication, |
133 |
|
|
oChallengeResponseAuthentication, oXAuthLocation, |
134 |
|
|
oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, |
135 |
|
|
oCertificateFile, oAddKeysToAgent, oIdentityAgent, |
136 |
|
|
oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, |
137 |
|
|
oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, |
138 |
|
|
oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, |
139 |
|
|
oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, |
140 |
|
|
oUsePrivilegedPort, oLogFacility, oLogLevel, oCiphers, oMacs, |
141 |
|
|
oPubkeyAuthentication, |
142 |
|
|
oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, |
143 |
|
|
oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, |
144 |
|
|
oHostKeyAlgorithms, oBindAddress, oPKCS11Provider, |
145 |
|
|
oClearAllForwardings, oNoHostAuthenticationForLocalhost, |
146 |
|
|
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, |
147 |
|
|
oAddressFamily, oGssAuthentication, oGssDelegateCreds, |
148 |
|
|
oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, |
149 |
|
|
oSendEnv, oControlPath, oControlMaster, oControlPersist, |
150 |
|
|
oHashKnownHosts, |
151 |
|
|
oTunnel, oTunnelDevice, |
152 |
|
|
oLocalCommand, oPermitLocalCommand, oRemoteCommand, |
153 |
|
|
oVisualHostKey, |
154 |
|
|
oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass, |
155 |
|
|
oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, |
156 |
|
|
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, |
157 |
|
|
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, |
158 |
|
|
oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes, |
159 |
|
|
oPubkeyAcceptedKeyTypes, oProxyJump, |
160 |
|
|
oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported |
161 |
|
|
} OpCodes; |
162 |
|
|
|
163 |
|
|
/* Textual representations of the tokens. */ |
164 |
|
|
|
165 |
|
|
static struct { |
166 |
|
|
const char *name; |
167 |
|
|
OpCodes opcode; |
168 |
|
|
} keywords[] = { |
169 |
|
|
/* Deprecated options */ |
170 |
|
|
{ "protocol", oIgnore }, /* NB. silently ignored */ |
171 |
|
|
{ "cipher", oDeprecated }, |
172 |
|
|
{ "fallbacktorsh", oDeprecated }, |
173 |
|
|
{ "globalknownhostsfile2", oDeprecated }, |
174 |
|
|
{ "rhostsauthentication", oDeprecated }, |
175 |
|
|
{ "userknownhostsfile2", oDeprecated }, |
176 |
|
|
{ "useroaming", oDeprecated }, |
177 |
|
|
{ "usersh", oDeprecated }, |
178 |
|
|
|
179 |
|
|
/* Unsupported options */ |
180 |
|
|
{ "afstokenpassing", oUnsupported }, |
181 |
|
|
{ "kerberosauthentication", oUnsupported }, |
182 |
|
|
{ "kerberostgtpassing", oUnsupported }, |
183 |
|
|
|
184 |
|
|
/* Sometimes-unsupported options */ |
185 |
|
|
#if defined(GSSAPI) |
186 |
|
|
{ "gssapiauthentication", oGssAuthentication }, |
187 |
|
|
{ "gssapidelegatecredentials", oGssDelegateCreds }, |
188 |
|
|
# else |
189 |
|
|
{ "gssapiauthentication", oUnsupported }, |
190 |
|
|
{ "gssapidelegatecredentials", oUnsupported }, |
191 |
|
|
#endif |
192 |
|
|
#ifdef ENABLE_PKCS11 |
193 |
|
|
{ "smartcarddevice", oPKCS11Provider }, |
194 |
|
|
{ "pkcs11provider", oPKCS11Provider }, |
195 |
|
|
# else |
196 |
|
|
{ "smartcarddevice", oUnsupported }, |
197 |
|
|
{ "pkcs11provider", oUnsupported }, |
198 |
|
|
#endif |
199 |
|
|
{ "rsaauthentication", oUnsupported }, |
200 |
|
|
{ "rhostsrsaauthentication", oUnsupported }, |
201 |
|
|
{ "compressionlevel", oUnsupported }, |
202 |
|
|
|
203 |
|
|
{ "forwardagent", oForwardAgent }, |
204 |
|
|
{ "forwardx11", oForwardX11 }, |
205 |
|
|
{ "forwardx11trusted", oForwardX11Trusted }, |
206 |
|
|
{ "forwardx11timeout", oForwardX11Timeout }, |
207 |
|
|
{ "exitonforwardfailure", oExitOnForwardFailure }, |
208 |
|
|
{ "xauthlocation", oXAuthLocation }, |
209 |
|
|
{ "gatewayports", oGatewayPorts }, |
210 |
|
|
{ "useprivilegedport", oUsePrivilegedPort }, |
211 |
|
|
{ "passwordauthentication", oPasswordAuthentication }, |
212 |
|
|
{ "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, |
213 |
|
|
{ "kbdinteractivedevices", oKbdInteractiveDevices }, |
214 |
|
|
{ "pubkeyauthentication", oPubkeyAuthentication }, |
215 |
|
|
{ "dsaauthentication", oPubkeyAuthentication }, /* alias */ |
216 |
|
|
{ "hostbasedauthentication", oHostbasedAuthentication }, |
217 |
|
|
{ "challengeresponseauthentication", oChallengeResponseAuthentication }, |
218 |
|
|
{ "skeyauthentication", oChallengeResponseAuthentication }, /* alias */ |
219 |
|
|
{ "tisauthentication", oChallengeResponseAuthentication }, /* alias */ |
220 |
|
|
{ "identityfile", oIdentityFile }, |
221 |
|
|
{ "identityfile2", oIdentityFile }, /* obsolete */ |
222 |
|
|
{ "identitiesonly", oIdentitiesOnly }, |
223 |
|
|
{ "certificatefile", oCertificateFile }, |
224 |
|
|
{ "addkeystoagent", oAddKeysToAgent }, |
225 |
|
|
{ "identityagent", oIdentityAgent }, |
226 |
|
|
{ "hostname", oHostName }, |
227 |
|
|
{ "hostkeyalias", oHostKeyAlias }, |
228 |
|
|
{ "proxycommand", oProxyCommand }, |
229 |
|
|
{ "port", oPort }, |
230 |
|
|
{ "ciphers", oCiphers }, |
231 |
|
|
{ "macs", oMacs }, |
232 |
|
|
{ "remoteforward", oRemoteForward }, |
233 |
|
|
{ "localforward", oLocalForward }, |
234 |
|
|
{ "user", oUser }, |
235 |
|
|
{ "host", oHost }, |
236 |
|
|
{ "match", oMatch }, |
237 |
|
|
{ "escapechar", oEscapeChar }, |
238 |
|
|
{ "globalknownhostsfile", oGlobalKnownHostsFile }, |
239 |
|
|
{ "userknownhostsfile", oUserKnownHostsFile }, |
240 |
|
|
{ "connectionattempts", oConnectionAttempts }, |
241 |
|
|
{ "batchmode", oBatchMode }, |
242 |
|
|
{ "checkhostip", oCheckHostIP }, |
243 |
|
|
{ "stricthostkeychecking", oStrictHostKeyChecking }, |
244 |
|
|
{ "compression", oCompression }, |
245 |
|
|
{ "tcpkeepalive", oTCPKeepAlive }, |
246 |
|
|
{ "keepalive", oTCPKeepAlive }, /* obsolete */ |
247 |
|
|
{ "numberofpasswordprompts", oNumberOfPasswordPrompts }, |
248 |
|
|
{ "syslogfacility", oLogFacility }, |
249 |
|
|
{ "loglevel", oLogLevel }, |
250 |
|
|
{ "dynamicforward", oDynamicForward }, |
251 |
|
|
{ "preferredauthentications", oPreferredAuthentications }, |
252 |
|
|
{ "hostkeyalgorithms", oHostKeyAlgorithms }, |
253 |
|
|
{ "bindaddress", oBindAddress }, |
254 |
|
|
{ "clearallforwardings", oClearAllForwardings }, |
255 |
|
|
{ "enablesshkeysign", oEnableSSHKeysign }, |
256 |
|
|
{ "verifyhostkeydns", oVerifyHostKeyDNS }, |
257 |
|
|
{ "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost }, |
258 |
|
|
{ "rekeylimit", oRekeyLimit }, |
259 |
|
|
{ "connecttimeout", oConnectTimeout }, |
260 |
|
|
{ "addressfamily", oAddressFamily }, |
261 |
|
|
{ "serveraliveinterval", oServerAliveInterval }, |
262 |
|
|
{ "serveralivecountmax", oServerAliveCountMax }, |
263 |
|
|
{ "sendenv", oSendEnv }, |
264 |
|
|
{ "controlpath", oControlPath }, |
265 |
|
|
{ "controlmaster", oControlMaster }, |
266 |
|
|
{ "controlpersist", oControlPersist }, |
267 |
|
|
{ "hashknownhosts", oHashKnownHosts }, |
268 |
|
|
{ "include", oInclude }, |
269 |
|
|
{ "tunnel", oTunnel }, |
270 |
|
|
{ "tunneldevice", oTunnelDevice }, |
271 |
|
|
{ "localcommand", oLocalCommand }, |
272 |
|
|
{ "permitlocalcommand", oPermitLocalCommand }, |
273 |
|
|
{ "remotecommand", oRemoteCommand }, |
274 |
|
|
{ "visualhostkey", oVisualHostKey }, |
275 |
|
|
{ "kexalgorithms", oKexAlgorithms }, |
276 |
|
|
{ "ipqos", oIPQoS }, |
277 |
|
|
{ "requesttty", oRequestTTY }, |
278 |
|
|
{ "proxyusefdpass", oProxyUseFdpass }, |
279 |
|
|
{ "canonicaldomains", oCanonicalDomains }, |
280 |
|
|
{ "canonicalizefallbacklocal", oCanonicalizeFallbackLocal }, |
281 |
|
|
{ "canonicalizehostname", oCanonicalizeHostname }, |
282 |
|
|
{ "canonicalizemaxdots", oCanonicalizeMaxDots }, |
283 |
|
|
{ "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs }, |
284 |
|
|
{ "streamlocalbindmask", oStreamLocalBindMask }, |
285 |
|
|
{ "streamlocalbindunlink", oStreamLocalBindUnlink }, |
286 |
|
|
{ "revokedhostkeys", oRevokedHostKeys }, |
287 |
|
|
{ "fingerprinthash", oFingerprintHash }, |
288 |
|
|
{ "updatehostkeys", oUpdateHostkeys }, |
289 |
|
|
{ "hostbasedkeytypes", oHostbasedKeyTypes }, |
290 |
|
|
{ "pubkeyacceptedkeytypes", oPubkeyAcceptedKeyTypes }, |
291 |
|
|
{ "ignoreunknown", oIgnoreUnknown }, |
292 |
|
|
{ "proxyjump", oProxyJump }, |
293 |
|
|
|
294 |
|
|
{ NULL, oBadOption } |
295 |
|
|
}; |
296 |
|
|
|
297 |
|
|
/* |
298 |
|
|
* Adds a local TCP/IP port forward to options. Never returns if there is an |
299 |
|
|
* error. |
300 |
|
|
*/ |
301 |
|
|
|
302 |
|
|
void |
303 |
|
|
add_local_forward(Options *options, const struct Forward *newfwd) |
304 |
|
|
{ |
305 |
|
|
struct Forward *fwd; |
306 |
|
|
extern uid_t original_real_uid; |
307 |
|
|
int i; |
308 |
|
|
|
309 |
|
|
if (!bind_permitted(newfwd->listen_port, original_real_uid) && |
310 |
|
|
newfwd->listen_path == NULL) |
311 |
|
|
fatal("Privileged ports can only be forwarded by root."); |
312 |
|
|
/* Don't add duplicates */ |
313 |
|
|
for (i = 0; i < options->num_local_forwards; i++) { |
314 |
|
|
if (forward_equals(newfwd, options->local_forwards + i)) |
315 |
|
|
return; |
316 |
|
|
} |
317 |
|
|
options->local_forwards = xreallocarray(options->local_forwards, |
318 |
|
|
options->num_local_forwards + 1, |
319 |
|
|
sizeof(*options->local_forwards)); |
320 |
|
|
fwd = &options->local_forwards[options->num_local_forwards++]; |
321 |
|
|
|
322 |
|
|
fwd->listen_host = newfwd->listen_host; |
323 |
|
|
fwd->listen_port = newfwd->listen_port; |
324 |
|
|
fwd->listen_path = newfwd->listen_path; |
325 |
|
|
fwd->connect_host = newfwd->connect_host; |
326 |
|
|
fwd->connect_port = newfwd->connect_port; |
327 |
|
|
fwd->connect_path = newfwd->connect_path; |
328 |
|
|
} |
329 |
|
|
|
330 |
|
|
/* |
331 |
|
|
* Adds a remote TCP/IP port forward to options. Never returns if there is |
332 |
|
|
* an error. |
333 |
|
|
*/ |
334 |
|
|
|
335 |
|
|
void |
336 |
|
|
add_remote_forward(Options *options, const struct Forward *newfwd) |
337 |
|
|
{ |
338 |
|
|
struct Forward *fwd; |
339 |
|
|
int i; |
340 |
|
|
|
341 |
|
|
/* Don't add duplicates */ |
342 |
|
|
for (i = 0; i < options->num_remote_forwards; i++) { |
343 |
|
|
if (forward_equals(newfwd, options->remote_forwards + i)) |
344 |
|
|
return; |
345 |
|
|
} |
346 |
|
|
options->remote_forwards = xreallocarray(options->remote_forwards, |
347 |
|
|
options->num_remote_forwards + 1, |
348 |
|
|
sizeof(*options->remote_forwards)); |
349 |
|
|
fwd = &options->remote_forwards[options->num_remote_forwards++]; |
350 |
|
|
|
351 |
|
|
fwd->listen_host = newfwd->listen_host; |
352 |
|
|
fwd->listen_port = newfwd->listen_port; |
353 |
|
|
fwd->listen_path = newfwd->listen_path; |
354 |
|
|
fwd->connect_host = newfwd->connect_host; |
355 |
|
|
fwd->connect_port = newfwd->connect_port; |
356 |
|
|
fwd->connect_path = newfwd->connect_path; |
357 |
|
|
fwd->handle = newfwd->handle; |
358 |
|
|
fwd->allocated_port = 0; |
359 |
|
|
} |
360 |
|
|
|
361 |
|
|
static void |
362 |
|
|
clear_forwardings(Options *options) |
363 |
|
|
{ |
364 |
|
|
int i; |
365 |
|
|
|
366 |
|
|
for (i = 0; i < options->num_local_forwards; i++) { |
367 |
|
|
free(options->local_forwards[i].listen_host); |
368 |
|
|
free(options->local_forwards[i].listen_path); |
369 |
|
|
free(options->local_forwards[i].connect_host); |
370 |
|
|
free(options->local_forwards[i].connect_path); |
371 |
|
|
} |
372 |
|
|
if (options->num_local_forwards > 0) { |
373 |
|
|
free(options->local_forwards); |
374 |
|
|
options->local_forwards = NULL; |
375 |
|
|
} |
376 |
|
|
options->num_local_forwards = 0; |
377 |
|
|
for (i = 0; i < options->num_remote_forwards; i++) { |
378 |
|
|
free(options->remote_forwards[i].listen_host); |
379 |
|
|
free(options->remote_forwards[i].listen_path); |
380 |
|
|
free(options->remote_forwards[i].connect_host); |
381 |
|
|
free(options->remote_forwards[i].connect_path); |
382 |
|
|
} |
383 |
|
|
if (options->num_remote_forwards > 0) { |
384 |
|
|
free(options->remote_forwards); |
385 |
|
|
options->remote_forwards = NULL; |
386 |
|
|
} |
387 |
|
|
options->num_remote_forwards = 0; |
388 |
|
|
options->tun_open = SSH_TUNMODE_NO; |
389 |
|
|
} |
390 |
|
|
|
391 |
|
|
void |
392 |
|
|
add_certificate_file(Options *options, const char *path, int userprovided) |
393 |
|
|
{ |
394 |
|
|
int i; |
395 |
|
|
|
396 |
|
|
if (options->num_certificate_files >= SSH_MAX_CERTIFICATE_FILES) |
397 |
|
|
fatal("Too many certificate files specified (max %d)", |
398 |
|
|
SSH_MAX_CERTIFICATE_FILES); |
399 |
|
|
|
400 |
|
|
/* Avoid registering duplicates */ |
401 |
|
|
for (i = 0; i < options->num_certificate_files; i++) { |
402 |
|
|
if (options->certificate_file_userprovided[i] == userprovided && |
403 |
|
|
strcmp(options->certificate_files[i], path) == 0) { |
404 |
|
|
debug2("%s: ignoring duplicate key %s", __func__, path); |
405 |
|
|
return; |
406 |
|
|
} |
407 |
|
|
} |
408 |
|
|
|
409 |
|
|
options->certificate_file_userprovided[options->num_certificate_files] = |
410 |
|
|
userprovided; |
411 |
|
|
options->certificate_files[options->num_certificate_files++] = |
412 |
|
|
xstrdup(path); |
413 |
|
|
} |
414 |
|
|
|
415 |
|
|
void |
416 |
|
|
add_identity_file(Options *options, const char *dir, const char *filename, |
417 |
|
|
int userprovided) |
418 |
|
|
{ |
419 |
|
|
char *path; |
420 |
|
|
int i; |
421 |
|
|
|
422 |
|
|
if (options->num_identity_files >= SSH_MAX_IDENTITY_FILES) |
423 |
|
|
fatal("Too many identity files specified (max %d)", |
424 |
|
|
SSH_MAX_IDENTITY_FILES); |
425 |
|
|
|
426 |
|
|
if (dir == NULL) /* no dir, filename is absolute */ |
427 |
|
|
path = xstrdup(filename); |
428 |
|
|
else if (xasprintf(&path, "%s%s", dir, filename) >= PATH_MAX) |
429 |
|
|
fatal("Identity file path %s too long", path); |
430 |
|
|
|
431 |
|
|
/* Avoid registering duplicates */ |
432 |
|
|
for (i = 0; i < options->num_identity_files; i++) { |
433 |
|
|
if (options->identity_file_userprovided[i] == userprovided && |
434 |
|
|
strcmp(options->identity_files[i], path) == 0) { |
435 |
|
|
debug2("%s: ignoring duplicate key %s", __func__, path); |
436 |
|
|
free(path); |
437 |
|
|
return; |
438 |
|
|
} |
439 |
|
|
} |
440 |
|
|
|
441 |
|
|
options->identity_file_userprovided[options->num_identity_files] = |
442 |
|
|
userprovided; |
443 |
|
|
options->identity_files[options->num_identity_files++] = path; |
444 |
|
|
} |
445 |
|
|
|
446 |
|
|
int |
447 |
|
|
default_ssh_port(void) |
448 |
|
|
{ |
449 |
|
|
static int port; |
450 |
|
|
struct servent *sp; |
451 |
|
|
|
452 |
|
|
if (port == 0) { |
453 |
|
|
sp = getservbyname(SSH_SERVICE_NAME, "tcp"); |
454 |
|
|
port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; |
455 |
|
|
} |
456 |
|
|
return port; |
457 |
|
|
} |
458 |
|
|
|
459 |
|
|
/* |
460 |
|
|
* Execute a command in a shell. |
461 |
|
|
* Return its exit status or -1 on abnormal exit. |
462 |
|
|
*/ |
463 |
|
|
static int |
464 |
|
|
execute_in_shell(const char *cmd) |
465 |
|
|
{ |
466 |
|
|
char *shell; |
467 |
|
|
pid_t pid; |
468 |
|
|
int devnull, status; |
469 |
|
|
extern uid_t original_real_uid; |
470 |
|
|
|
471 |
|
|
if ((shell = getenv("SHELL")) == NULL) |
472 |
|
|
shell = _PATH_BSHELL; |
473 |
|
|
|
474 |
|
|
/* Need this to redirect subprocess stdin/out */ |
475 |
|
|
if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) |
476 |
|
|
fatal("open(/dev/null): %s", strerror(errno)); |
477 |
|
|
|
478 |
|
|
debug("Executing command: '%.500s'", cmd); |
479 |
|
|
|
480 |
|
|
/* Fork and execute the command. */ |
481 |
|
|
if ((pid = fork()) == 0) { |
482 |
|
|
char *argv[4]; |
483 |
|
|
|
484 |
|
|
/* Child. Permanently give up superuser privileges. */ |
485 |
|
|
permanently_drop_suid(original_real_uid); |
486 |
|
|
|
487 |
|
|
/* Redirect child stdin and stdout. Leave stderr */ |
488 |
|
|
if (dup2(devnull, STDIN_FILENO) == -1) |
489 |
|
|
fatal("dup2: %s", strerror(errno)); |
490 |
|
|
if (dup2(devnull, STDOUT_FILENO) == -1) |
491 |
|
|
fatal("dup2: %s", strerror(errno)); |
492 |
|
|
if (devnull > STDERR_FILENO) |
493 |
|
|
close(devnull); |
494 |
|
|
closefrom(STDERR_FILENO + 1); |
495 |
|
|
|
496 |
|
|
argv[0] = shell; |
497 |
|
|
argv[1] = "-c"; |
498 |
|
|
argv[2] = xstrdup(cmd); |
499 |
|
|
argv[3] = NULL; |
500 |
|
|
|
501 |
|
|
execv(argv[0], argv); |
502 |
|
|
error("Unable to execute '%.100s': %s", cmd, strerror(errno)); |
503 |
|
|
/* Die with signal to make this error apparent to parent. */ |
504 |
|
|
signal(SIGTERM, SIG_DFL); |
505 |
|
|
kill(getpid(), SIGTERM); |
506 |
|
|
_exit(1); |
507 |
|
|
} |
508 |
|
|
/* Parent. */ |
509 |
|
|
if (pid < 0) |
510 |
|
|
fatal("%s: fork: %.100s", __func__, strerror(errno)); |
511 |
|
|
|
512 |
|
|
close(devnull); |
513 |
|
|
|
514 |
|
|
while (waitpid(pid, &status, 0) == -1) { |
515 |
|
|
if (errno != EINTR && errno != EAGAIN) |
516 |
|
|
fatal("%s: waitpid: %s", __func__, strerror(errno)); |
517 |
|
|
} |
518 |
|
|
if (!WIFEXITED(status)) { |
519 |
|
|
error("command '%.100s' exited abnormally", cmd); |
520 |
|
|
return -1; |
521 |
|
|
} |
522 |
|
|
debug3("command returned status %d", WEXITSTATUS(status)); |
523 |
|
|
return WEXITSTATUS(status); |
524 |
|
|
} |
525 |
|
|
|
526 |
|
|
/* |
527 |
|
|
* Parse and execute a Match directive. |
528 |
|
|
*/ |
529 |
|
|
static int |
530 |
|
|
match_cfg_line(Options *options, char **condition, struct passwd *pw, |
531 |
|
|
const char *host_arg, const char *original_host, int post_canon, |
532 |
|
|
const char *filename, int linenum) |
533 |
|
|
{ |
534 |
|
|
char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria; |
535 |
|
|
const char *ruser; |
536 |
|
|
int r, port, this_result, result = 1, attributes = 0, negate; |
537 |
|
|
char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; |
538 |
|
|
|
539 |
|
|
/* |
540 |
|
|
* Configuration is likely to be incomplete at this point so we |
541 |
|
|
* must be prepared to use default values. |
542 |
|
|
*/ |
543 |
|
|
port = options->port <= 0 ? default_ssh_port() : options->port; |
544 |
|
|
ruser = options->user == NULL ? pw->pw_name : options->user; |
545 |
|
|
if (post_canon) { |
546 |
|
|
host = xstrdup(options->hostname); |
547 |
|
|
} else if (options->hostname != NULL) { |
548 |
|
|
/* NB. Please keep in sync with ssh.c:main() */ |
549 |
|
|
host = percent_expand(options->hostname, |
550 |
|
|
"h", host_arg, (char *)NULL); |
551 |
|
|
} else { |
552 |
|
|
host = xstrdup(host_arg); |
553 |
|
|
} |
554 |
|
|
|
555 |
|
|
debug2("checking match for '%s' host %s originally %s", |
556 |
|
|
cp, host, original_host); |
557 |
|
|
while ((oattrib = attrib = strdelim(&cp)) && *attrib != '\0') { |
558 |
|
|
criteria = NULL; |
559 |
|
|
this_result = 1; |
560 |
|
|
if ((negate = attrib[0] == '!')) |
561 |
|
|
attrib++; |
562 |
|
|
/* criteria "all" and "canonical" have no argument */ |
563 |
|
|
if (strcasecmp(attrib, "all") == 0) { |
564 |
|
|
if (attributes > 1 || |
565 |
|
|
((arg = strdelim(&cp)) != NULL && *arg != '\0')) { |
566 |
|
|
error("%.200s line %d: '%s' cannot be combined " |
567 |
|
|
"with other Match attributes", |
568 |
|
|
filename, linenum, oattrib); |
569 |
|
|
result = -1; |
570 |
|
|
goto out; |
571 |
|
|
} |
572 |
|
|
if (result) |
573 |
|
|
result = negate ? 0 : 1; |
574 |
|
|
goto out; |
575 |
|
|
} |
576 |
|
|
attributes++; |
577 |
|
|
if (strcasecmp(attrib, "canonical") == 0) { |
578 |
|
|
r = !!post_canon; /* force bitmask member to boolean */ |
579 |
|
|
if (r == (negate ? 1 : 0)) |
580 |
|
|
this_result = result = 0; |
581 |
|
|
debug3("%.200s line %d: %smatched '%s'", |
582 |
|
|
filename, linenum, |
583 |
|
|
this_result ? "" : "not ", oattrib); |
584 |
|
|
continue; |
585 |
|
|
} |
586 |
|
|
/* All other criteria require an argument */ |
587 |
|
|
if ((arg = strdelim(&cp)) == NULL || *arg == '\0') { |
588 |
|
|
error("Missing Match criteria for %s", attrib); |
589 |
|
|
result = -1; |
590 |
|
|
goto out; |
591 |
|
|
} |
592 |
|
|
if (strcasecmp(attrib, "host") == 0) { |
593 |
|
|
criteria = xstrdup(host); |
594 |
|
|
r = match_hostname(host, arg) == 1; |
595 |
|
|
if (r == (negate ? 1 : 0)) |
596 |
|
|
this_result = result = 0; |
597 |
|
|
} else if (strcasecmp(attrib, "originalhost") == 0) { |
598 |
|
|
criteria = xstrdup(original_host); |
599 |
|
|
r = match_hostname(original_host, arg) == 1; |
600 |
|
|
if (r == (negate ? 1 : 0)) |
601 |
|
|
this_result = result = 0; |
602 |
|
|
} else if (strcasecmp(attrib, "user") == 0) { |
603 |
|
|
criteria = xstrdup(ruser); |
604 |
|
|
r = match_pattern_list(ruser, arg, 0) == 1; |
605 |
|
|
if (r == (negate ? 1 : 0)) |
606 |
|
|
this_result = result = 0; |
607 |
|
|
} else if (strcasecmp(attrib, "localuser") == 0) { |
608 |
|
|
criteria = xstrdup(pw->pw_name); |
609 |
|
|
r = match_pattern_list(pw->pw_name, arg, 0) == 1; |
610 |
|
|
if (r == (negate ? 1 : 0)) |
611 |
|
|
this_result = result = 0; |
612 |
|
|
} else if (strcasecmp(attrib, "exec") == 0) { |
613 |
|
|
if (gethostname(thishost, sizeof(thishost)) == -1) |
614 |
|
|
fatal("gethostname: %s", strerror(errno)); |
615 |
|
|
strlcpy(shorthost, thishost, sizeof(shorthost)); |
616 |
|
|
shorthost[strcspn(thishost, ".")] = '\0'; |
617 |
|
|
snprintf(portstr, sizeof(portstr), "%d", port); |
618 |
|
|
|
619 |
|
|
cmd = percent_expand(arg, |
620 |
|
|
"L", shorthost, |
621 |
|
|
"d", pw->pw_dir, |
622 |
|
|
"h", host, |
623 |
|
|
"l", thishost, |
624 |
|
|
"n", original_host, |
625 |
|
|
"p", portstr, |
626 |
|
|
"r", ruser, |
627 |
|
|
"u", pw->pw_name, |
628 |
|
|
(char *)NULL); |
629 |
|
|
if (result != 1) { |
630 |
|
|
/* skip execution if prior predicate failed */ |
631 |
|
|
debug3("%.200s line %d: skipped exec " |
632 |
|
|
"\"%.100s\"", filename, linenum, cmd); |
633 |
|
|
free(cmd); |
634 |
|
|
continue; |
635 |
|
|
} |
636 |
|
|
r = execute_in_shell(cmd); |
637 |
|
|
if (r == -1) { |
638 |
|
|
fatal("%.200s line %d: match exec " |
639 |
|
|
"'%.100s' error", filename, |
640 |
|
|
linenum, cmd); |
641 |
|
|
} |
642 |
|
|
criteria = xstrdup(cmd); |
643 |
|
|
free(cmd); |
644 |
|
|
/* Force exit status to boolean */ |
645 |
|
|
r = r == 0; |
646 |
|
|
if (r == (negate ? 1 : 0)) |
647 |
|
|
this_result = result = 0; |
648 |
|
|
} else { |
649 |
|
|
error("Unsupported Match attribute %s", attrib); |
650 |
|
|
result = -1; |
651 |
|
|
goto out; |
652 |
|
|
} |
653 |
|
|
debug3("%.200s line %d: %smatched '%s \"%.100s\"' ", |
654 |
|
|
filename, linenum, this_result ? "": "not ", |
655 |
|
|
oattrib, criteria); |
656 |
|
|
free(criteria); |
657 |
|
|
} |
658 |
|
|
if (attributes == 0) { |
659 |
|
|
error("One or more attributes required for Match"); |
660 |
|
|
result = -1; |
661 |
|
|
goto out; |
662 |
|
|
} |
663 |
|
|
out: |
664 |
|
|
if (result != -1) |
665 |
|
|
debug2("match %sfound", result ? "" : "not "); |
666 |
|
|
*condition = cp; |
667 |
|
|
free(host); |
668 |
|
|
return result; |
669 |
|
|
} |
670 |
|
|
|
671 |
|
|
/* |
672 |
|
|
* Returns the number of the token pointed to by cp or oBadOption. |
673 |
|
|
*/ |
674 |
|
|
static OpCodes |
675 |
|
|
parse_token(const char *cp, const char *filename, int linenum, |
676 |
|
|
const char *ignored_unknown) |
677 |
|
|
{ |
678 |
|
|
int i; |
679 |
|
|
|
680 |
|
|
for (i = 0; keywords[i].name; i++) |
681 |
|
|
if (strcmp(cp, keywords[i].name) == 0) |
682 |
|
|
return keywords[i].opcode; |
683 |
|
|
if (ignored_unknown != NULL && |
684 |
|
|
match_pattern_list(cp, ignored_unknown, 1) == 1) |
685 |
|
|
return oIgnoredUnknownOption; |
686 |
|
|
error("%s: line %d: Bad configuration option: %s", |
687 |
|
|
filename, linenum, cp); |
688 |
|
|
return oBadOption; |
689 |
|
|
} |
690 |
|
|
|
691 |
|
|
/* Multistate option parsing */ |
692 |
|
|
struct multistate { |
693 |
|
|
char *key; |
694 |
|
|
int value; |
695 |
|
|
}; |
696 |
|
|
static const struct multistate multistate_flag[] = { |
697 |
|
|
{ "true", 1 }, |
698 |
|
|
{ "false", 0 }, |
699 |
|
|
{ "yes", 1 }, |
700 |
|
|
{ "no", 0 }, |
701 |
|
|
{ NULL, -1 } |
702 |
|
|
}; |
703 |
|
|
static const struct multistate multistate_yesnoask[] = { |
704 |
|
|
{ "true", 1 }, |
705 |
|
|
{ "false", 0 }, |
706 |
|
|
{ "yes", 1 }, |
707 |
|
|
{ "no", 0 }, |
708 |
|
|
{ "ask", 2 }, |
709 |
|
|
{ NULL, -1 } |
710 |
|
|
}; |
711 |
|
|
static const struct multistate multistate_strict_hostkey[] = { |
712 |
|
|
{ "true", SSH_STRICT_HOSTKEY_YES }, |
713 |
|
|
{ "false", SSH_STRICT_HOSTKEY_OFF }, |
714 |
|
|
{ "yes", SSH_STRICT_HOSTKEY_YES }, |
715 |
|
|
{ "no", SSH_STRICT_HOSTKEY_OFF }, |
716 |
|
|
{ "ask", SSH_STRICT_HOSTKEY_ASK }, |
717 |
|
|
{ "off", SSH_STRICT_HOSTKEY_OFF }, |
718 |
|
|
{ "accept-new", SSH_STRICT_HOSTKEY_NEW }, |
719 |
|
|
{ NULL, -1 } |
720 |
|
|
}; |
721 |
|
|
static const struct multistate multistate_yesnoaskconfirm[] = { |
722 |
|
|
{ "true", 1 }, |
723 |
|
|
{ "false", 0 }, |
724 |
|
|
{ "yes", 1 }, |
725 |
|
|
{ "no", 0 }, |
726 |
|
|
{ "ask", 2 }, |
727 |
|
|
{ "confirm", 3 }, |
728 |
|
|
{ NULL, -1 } |
729 |
|
|
}; |
730 |
|
|
static const struct multistate multistate_addressfamily[] = { |
731 |
|
|
{ "inet", AF_INET }, |
732 |
|
|
{ "inet6", AF_INET6 }, |
733 |
|
|
{ "any", AF_UNSPEC }, |
734 |
|
|
{ NULL, -1 } |
735 |
|
|
}; |
736 |
|
|
static const struct multistate multistate_controlmaster[] = { |
737 |
|
|
{ "true", SSHCTL_MASTER_YES }, |
738 |
|
|
{ "yes", SSHCTL_MASTER_YES }, |
739 |
|
|
{ "false", SSHCTL_MASTER_NO }, |
740 |
|
|
{ "no", SSHCTL_MASTER_NO }, |
741 |
|
|
{ "auto", SSHCTL_MASTER_AUTO }, |
742 |
|
|
{ "ask", SSHCTL_MASTER_ASK }, |
743 |
|
|
{ "autoask", SSHCTL_MASTER_AUTO_ASK }, |
744 |
|
|
{ NULL, -1 } |
745 |
|
|
}; |
746 |
|
|
static const struct multistate multistate_tunnel[] = { |
747 |
|
|
{ "ethernet", SSH_TUNMODE_ETHERNET }, |
748 |
|
|
{ "point-to-point", SSH_TUNMODE_POINTOPOINT }, |
749 |
|
|
{ "true", SSH_TUNMODE_DEFAULT }, |
750 |
|
|
{ "yes", SSH_TUNMODE_DEFAULT }, |
751 |
|
|
{ "false", SSH_TUNMODE_NO }, |
752 |
|
|
{ "no", SSH_TUNMODE_NO }, |
753 |
|
|
{ NULL, -1 } |
754 |
|
|
}; |
755 |
|
|
static const struct multistate multistate_requesttty[] = { |
756 |
|
|
{ "true", REQUEST_TTY_YES }, |
757 |
|
|
{ "yes", REQUEST_TTY_YES }, |
758 |
|
|
{ "false", REQUEST_TTY_NO }, |
759 |
|
|
{ "no", REQUEST_TTY_NO }, |
760 |
|
|
{ "force", REQUEST_TTY_FORCE }, |
761 |
|
|
{ "auto", REQUEST_TTY_AUTO }, |
762 |
|
|
{ NULL, -1 } |
763 |
|
|
}; |
764 |
|
|
static const struct multistate multistate_canonicalizehostname[] = { |
765 |
|
|
{ "true", SSH_CANONICALISE_YES }, |
766 |
|
|
{ "false", SSH_CANONICALISE_NO }, |
767 |
|
|
{ "yes", SSH_CANONICALISE_YES }, |
768 |
|
|
{ "no", SSH_CANONICALISE_NO }, |
769 |
|
|
{ "always", SSH_CANONICALISE_ALWAYS }, |
770 |
|
|
{ NULL, -1 } |
771 |
|
|
}; |
772 |
|
|
|
773 |
|
|
/* |
774 |
|
|
* Processes a single option line as used in the configuration files. This |
775 |
|
|
* only sets those values that have not already been set. |
776 |
|
|
*/ |
777 |
|
|
int |
778 |
|
|
process_config_line(Options *options, struct passwd *pw, const char *host, |
779 |
|
|
const char *original_host, char *line, const char *filename, |
780 |
|
|
int linenum, int *activep, int flags) |
781 |
|
|
{ |
782 |
|
|
return process_config_line_depth(options, pw, host, original_host, |
783 |
|
|
line, filename, linenum, activep, flags, 0); |
784 |
|
|
} |
785 |
|
|
|
786 |
|
|
#define WHITESPACE " \t\r\n" |
787 |
|
|
static int |
788 |
|
|
process_config_line_depth(Options *options, struct passwd *pw, const char *host, |
789 |
|
|
const char *original_host, char *line, const char *filename, |
790 |
|
|
int linenum, int *activep, int flags, int depth) |
791 |
|
|
{ |
792 |
|
|
char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; |
793 |
|
|
char **cpptr, fwdarg[256]; |
794 |
|
|
u_int i, *uintptr, max_entries = 0; |
795 |
|
|
int r, oactive, negated, opcode, *intptr, value, value2, cmdline = 0; |
796 |
|
|
int remotefwd, dynamicfwd; |
797 |
|
|
LogLevel *log_level_ptr; |
798 |
|
|
SyslogFacility *log_facility_ptr; |
799 |
|
|
long long val64; |
800 |
|
|
size_t len; |
801 |
|
|
struct Forward fwd; |
802 |
|
|
const struct multistate *multistate_ptr; |
803 |
|
|
struct allowed_cname *cname; |
804 |
|
|
glob_t gl; |
805 |
|
|
|
806 |
|
|
if (activep == NULL) { /* We are processing a command line directive */ |
807 |
|
|
cmdline = 1; |
808 |
|
|
activep = &cmdline; |
809 |
|
|
} |
810 |
|
|
|
811 |
|
|
/* Strip trailing whitespace. Allow \f (form feed) at EOL only */ |
812 |
|
|
if ((len = strlen(line)) == 0) |
813 |
|
|
return 0; |
814 |
|
|
for (len--; len > 0; len--) { |
815 |
|
|
if (strchr(WHITESPACE "\f", line[len]) == NULL) |
816 |
|
|
break; |
817 |
|
|
line[len] = '\0'; |
818 |
|
|
} |
819 |
|
|
|
820 |
|
|
s = line; |
821 |
|
|
/* Get the keyword. (Each line is supposed to begin with a keyword). */ |
822 |
|
|
if ((keyword = strdelim(&s)) == NULL) |
823 |
|
|
return 0; |
824 |
|
|
/* Ignore leading whitespace. */ |
825 |
|
|
if (*keyword == '\0') |
826 |
|
|
keyword = strdelim(&s); |
827 |
|
|
if (keyword == NULL || !*keyword || *keyword == '\n' || *keyword == '#') |
828 |
|
|
return 0; |
829 |
|
|
/* Match lowercase keyword */ |
830 |
|
|
lowercase(keyword); |
831 |
|
|
|
832 |
|
|
opcode = parse_token(keyword, filename, linenum, |
833 |
|
|
options->ignored_unknown); |
834 |
|
|
|
835 |
|
|
switch (opcode) { |
836 |
|
|
case oBadOption: |
837 |
|
|
/* don't panic, but count bad options */ |
838 |
|
|
return -1; |
839 |
|
|
case oIgnore: |
840 |
|
|
return 0; |
841 |
|
|
case oIgnoredUnknownOption: |
842 |
|
|
debug("%s line %d: Ignored unknown option \"%s\"", |
843 |
|
|
filename, linenum, keyword); |
844 |
|
|
return 0; |
845 |
|
|
case oConnectTimeout: |
846 |
|
|
intptr = &options->connection_timeout; |
847 |
|
|
parse_time: |
848 |
|
|
arg = strdelim(&s); |
849 |
|
|
if (!arg || *arg == '\0') |
850 |
|
|
fatal("%s line %d: missing time value.", |
851 |
|
|
filename, linenum); |
852 |
|
|
if (strcmp(arg, "none") == 0) |
853 |
|
|
value = -1; |
854 |
|
|
else if ((value = convtime(arg)) == -1) |
855 |
|
|
fatal("%s line %d: invalid time value.", |
856 |
|
|
filename, linenum); |
857 |
|
|
if (*activep && *intptr == -1) |
858 |
|
|
*intptr = value; |
859 |
|
|
break; |
860 |
|
|
|
861 |
|
|
case oForwardAgent: |
862 |
|
|
intptr = &options->forward_agent; |
863 |
|
|
parse_flag: |
864 |
|
|
multistate_ptr = multistate_flag; |
865 |
|
|
parse_multistate: |
866 |
|
|
arg = strdelim(&s); |
867 |
|
|
if (!arg || *arg == '\0') |
868 |
|
|
fatal("%s line %d: missing argument.", |
869 |
|
|
filename, linenum); |
870 |
|
|
value = -1; |
871 |
|
|
for (i = 0; multistate_ptr[i].key != NULL; i++) { |
872 |
|
|
if (strcasecmp(arg, multistate_ptr[i].key) == 0) { |
873 |
|
|
value = multistate_ptr[i].value; |
874 |
|
|
break; |
875 |
|
|
} |
876 |
|
|
} |
877 |
|
|
if (value == -1) |
878 |
|
|
fatal("%s line %d: unsupported option \"%s\".", |
879 |
|
|
filename, linenum, arg); |
880 |
|
|
if (*activep && *intptr == -1) |
881 |
|
|
*intptr = value; |
882 |
|
|
break; |
883 |
|
|
|
884 |
|
|
case oForwardX11: |
885 |
|
|
intptr = &options->forward_x11; |
886 |
|
|
goto parse_flag; |
887 |
|
|
|
888 |
|
|
case oForwardX11Trusted: |
889 |
|
|
intptr = &options->forward_x11_trusted; |
890 |
|
|
goto parse_flag; |
891 |
|
|
|
892 |
|
|
case oForwardX11Timeout: |
893 |
|
|
intptr = &options->forward_x11_timeout; |
894 |
|
|
goto parse_time; |
895 |
|
|
|
896 |
|
|
case oGatewayPorts: |
897 |
|
|
intptr = &options->fwd_opts.gateway_ports; |
898 |
|
|
goto parse_flag; |
899 |
|
|
|
900 |
|
|
case oExitOnForwardFailure: |
901 |
|
|
intptr = &options->exit_on_forward_failure; |
902 |
|
|
goto parse_flag; |
903 |
|
|
|
904 |
|
|
case oUsePrivilegedPort: |
905 |
|
|
intptr = &options->use_privileged_port; |
906 |
|
|
goto parse_flag; |
907 |
|
|
|
908 |
|
|
case oPasswordAuthentication: |
909 |
|
|
intptr = &options->password_authentication; |
910 |
|
|
goto parse_flag; |
911 |
|
|
|
912 |
|
|
case oKbdInteractiveAuthentication: |
913 |
|
|
intptr = &options->kbd_interactive_authentication; |
914 |
|
|
goto parse_flag; |
915 |
|
|
|
916 |
|
|
case oKbdInteractiveDevices: |
917 |
|
|
charptr = &options->kbd_interactive_devices; |
918 |
|
|
goto parse_string; |
919 |
|
|
|
920 |
|
|
case oPubkeyAuthentication: |
921 |
|
|
intptr = &options->pubkey_authentication; |
922 |
|
|
goto parse_flag; |
923 |
|
|
|
924 |
|
|
case oHostbasedAuthentication: |
925 |
|
|
intptr = &options->hostbased_authentication; |
926 |
|
|
goto parse_flag; |
927 |
|
|
|
928 |
|
|
case oChallengeResponseAuthentication: |
929 |
|
|
intptr = &options->challenge_response_authentication; |
930 |
|
|
goto parse_flag; |
931 |
|
|
|
932 |
|
|
case oGssAuthentication: |
933 |
|
|
intptr = &options->gss_authentication; |
934 |
|
|
goto parse_flag; |
935 |
|
|
|
936 |
|
|
case oGssDelegateCreds: |
937 |
|
|
intptr = &options->gss_deleg_creds; |
938 |
|
|
goto parse_flag; |
939 |
|
|
|
940 |
|
|
case oBatchMode: |
941 |
|
|
intptr = &options->batch_mode; |
942 |
|
|
goto parse_flag; |
943 |
|
|
|
944 |
|
|
case oCheckHostIP: |
945 |
|
|
intptr = &options->check_host_ip; |
946 |
|
|
goto parse_flag; |
947 |
|
|
|
948 |
|
|
case oVerifyHostKeyDNS: |
949 |
|
|
intptr = &options->verify_host_key_dns; |
950 |
|
|
multistate_ptr = multistate_yesnoask; |
951 |
|
|
goto parse_multistate; |
952 |
|
|
|
953 |
|
|
case oStrictHostKeyChecking: |
954 |
|
|
intptr = &options->strict_host_key_checking; |
955 |
|
|
multistate_ptr = multistate_strict_hostkey; |
956 |
|
|
goto parse_multistate; |
957 |
|
|
|
958 |
|
|
case oCompression: |
959 |
|
|
intptr = &options->compression; |
960 |
|
|
goto parse_flag; |
961 |
|
|
|
962 |
|
|
case oTCPKeepAlive: |
963 |
|
|
intptr = &options->tcp_keep_alive; |
964 |
|
|
goto parse_flag; |
965 |
|
|
|
966 |
|
|
case oNoHostAuthenticationForLocalhost: |
967 |
|
|
intptr = &options->no_host_authentication_for_localhost; |
968 |
|
|
goto parse_flag; |
969 |
|
|
|
970 |
|
|
case oNumberOfPasswordPrompts: |
971 |
|
|
intptr = &options->number_of_password_prompts; |
972 |
|
|
goto parse_int; |
973 |
|
|
|
974 |
|
|
case oRekeyLimit: |
975 |
|
|
arg = strdelim(&s); |
976 |
|
|
if (!arg || *arg == '\0') |
977 |
|
|
fatal("%.200s line %d: Missing argument.", filename, |
978 |
|
|
linenum); |
979 |
|
|
if (strcmp(arg, "default") == 0) { |
980 |
|
|
val64 = 0; |
981 |
|
|
} else { |
982 |
|
|
if (scan_scaled(arg, &val64) == -1) |
983 |
|
|
fatal("%.200s line %d: Bad number '%s': %s", |
984 |
|
|
filename, linenum, arg, strerror(errno)); |
985 |
|
|
if (val64 != 0 && val64 < 16) |
986 |
|
|
fatal("%.200s line %d: RekeyLimit too small", |
987 |
|
|
filename, linenum); |
988 |
|
|
} |
989 |
|
|
if (*activep && options->rekey_limit == -1) |
990 |
|
|
options->rekey_limit = val64; |
991 |
|
|
if (s != NULL) { /* optional rekey interval present */ |
992 |
|
|
if (strcmp(s, "none") == 0) { |
993 |
|
|
(void)strdelim(&s); /* discard */ |
994 |
|
|
break; |
995 |
|
|
} |
996 |
|
|
intptr = &options->rekey_interval; |
997 |
|
|
goto parse_time; |
998 |
|
|
} |
999 |
|
|
break; |
1000 |
|
|
|
1001 |
|
|
case oIdentityFile: |
1002 |
|
|
arg = strdelim(&s); |
1003 |
|
|
if (!arg || *arg == '\0') |
1004 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1005 |
|
|
if (*activep) { |
1006 |
|
|
intptr = &options->num_identity_files; |
1007 |
|
|
if (*intptr >= SSH_MAX_IDENTITY_FILES) |
1008 |
|
|
fatal("%.200s line %d: Too many identity files specified (max %d).", |
1009 |
|
|
filename, linenum, SSH_MAX_IDENTITY_FILES); |
1010 |
|
|
add_identity_file(options, NULL, |
1011 |
|
|
arg, flags & SSHCONF_USERCONF); |
1012 |
|
|
} |
1013 |
|
|
break; |
1014 |
|
|
|
1015 |
|
|
case oCertificateFile: |
1016 |
|
|
arg = strdelim(&s); |
1017 |
|
|
if (!arg || *arg == '\0') |
1018 |
|
|
fatal("%.200s line %d: Missing argument.", |
1019 |
|
|
filename, linenum); |
1020 |
|
|
if (*activep) { |
1021 |
|
|
intptr = &options->num_certificate_files; |
1022 |
|
|
if (*intptr >= SSH_MAX_CERTIFICATE_FILES) { |
1023 |
|
|
fatal("%.200s line %d: Too many certificate " |
1024 |
|
|
"files specified (max %d).", |
1025 |
|
|
filename, linenum, |
1026 |
|
|
SSH_MAX_CERTIFICATE_FILES); |
1027 |
|
|
} |
1028 |
|
|
add_certificate_file(options, arg, |
1029 |
|
|
flags & SSHCONF_USERCONF); |
1030 |
|
|
} |
1031 |
|
|
break; |
1032 |
|
|
|
1033 |
|
|
case oXAuthLocation: |
1034 |
|
|
charptr=&options->xauth_location; |
1035 |
|
|
goto parse_string; |
1036 |
|
|
|
1037 |
|
|
case oUser: |
1038 |
|
|
charptr = &options->user; |
1039 |
|
|
parse_string: |
1040 |
|
|
arg = strdelim(&s); |
1041 |
|
|
if (!arg || *arg == '\0') |
1042 |
|
|
fatal("%.200s line %d: Missing argument.", |
1043 |
|
|
filename, linenum); |
1044 |
|
|
if (*activep && *charptr == NULL) |
1045 |
|
|
*charptr = xstrdup(arg); |
1046 |
|
|
break; |
1047 |
|
|
|
1048 |
|
|
case oGlobalKnownHostsFile: |
1049 |
|
|
cpptr = (char **)&options->system_hostfiles; |
1050 |
|
|
uintptr = &options->num_system_hostfiles; |
1051 |
|
|
max_entries = SSH_MAX_HOSTS_FILES; |
1052 |
|
|
parse_char_array: |
1053 |
|
|
if (*activep && *uintptr == 0) { |
1054 |
|
|
while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1055 |
|
|
if ((*uintptr) >= max_entries) |
1056 |
|
|
fatal("%s line %d: " |
1057 |
|
|
"too many authorized keys files.", |
1058 |
|
|
filename, linenum); |
1059 |
|
|
cpptr[(*uintptr)++] = xstrdup(arg); |
1060 |
|
|
} |
1061 |
|
|
} |
1062 |
|
|
return 0; |
1063 |
|
|
|
1064 |
|
|
case oUserKnownHostsFile: |
1065 |
|
|
cpptr = (char **)&options->user_hostfiles; |
1066 |
|
|
uintptr = &options->num_user_hostfiles; |
1067 |
|
|
max_entries = SSH_MAX_HOSTS_FILES; |
1068 |
|
|
goto parse_char_array; |
1069 |
|
|
|
1070 |
|
|
case oHostName: |
1071 |
|
|
charptr = &options->hostname; |
1072 |
|
|
goto parse_string; |
1073 |
|
|
|
1074 |
|
|
case oHostKeyAlias: |
1075 |
|
|
charptr = &options->host_key_alias; |
1076 |
|
|
goto parse_string; |
1077 |
|
|
|
1078 |
|
|
case oPreferredAuthentications: |
1079 |
|
|
charptr = &options->preferred_authentications; |
1080 |
|
|
goto parse_string; |
1081 |
|
|
|
1082 |
|
|
case oBindAddress: |
1083 |
|
|
charptr = &options->bind_address; |
1084 |
|
|
goto parse_string; |
1085 |
|
|
|
1086 |
|
|
case oPKCS11Provider: |
1087 |
|
|
charptr = &options->pkcs11_provider; |
1088 |
|
|
goto parse_string; |
1089 |
|
|
|
1090 |
|
|
case oProxyCommand: |
1091 |
|
|
charptr = &options->proxy_command; |
1092 |
|
|
/* Ignore ProxyCommand if ProxyJump already specified */ |
1093 |
|
|
if (options->jump_host != NULL) |
1094 |
|
|
charptr = &options->jump_host; /* Skip below */ |
1095 |
|
|
parse_command: |
1096 |
|
|
if (s == NULL) |
1097 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1098 |
|
|
len = strspn(s, WHITESPACE "="); |
1099 |
|
|
if (*activep && *charptr == NULL) |
1100 |
|
|
*charptr = xstrdup(s + len); |
1101 |
|
|
return 0; |
1102 |
|
|
|
1103 |
|
|
case oProxyJump: |
1104 |
|
|
if (s == NULL) { |
1105 |
|
|
fatal("%.200s line %d: Missing argument.", |
1106 |
|
|
filename, linenum); |
1107 |
|
|
} |
1108 |
|
|
len = strspn(s, WHITESPACE "="); |
1109 |
|
|
if (parse_jump(s + len, options, *activep) == -1) { |
1110 |
|
|
fatal("%.200s line %d: Invalid ProxyJump \"%s\"", |
1111 |
|
|
filename, linenum, s + len); |
1112 |
|
|
} |
1113 |
|
|
return 0; |
1114 |
|
|
|
1115 |
|
|
case oPort: |
1116 |
|
|
intptr = &options->port; |
1117 |
|
|
parse_int: |
1118 |
|
|
arg = strdelim(&s); |
1119 |
|
|
if (!arg || *arg == '\0') |
1120 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1121 |
|
|
if (arg[0] < '0' || arg[0] > '9') |
1122 |
|
|
fatal("%.200s line %d: Bad number.", filename, linenum); |
1123 |
|
|
|
1124 |
|
|
/* Octal, decimal, or hex format? */ |
1125 |
|
|
value = strtol(arg, &endofnumber, 0); |
1126 |
|
|
if (arg == endofnumber) |
1127 |
|
|
fatal("%.200s line %d: Bad number.", filename, linenum); |
1128 |
|
|
if (*activep && *intptr == -1) |
1129 |
|
|
*intptr = value; |
1130 |
|
|
break; |
1131 |
|
|
|
1132 |
|
|
case oConnectionAttempts: |
1133 |
|
|
intptr = &options->connection_attempts; |
1134 |
|
|
goto parse_int; |
1135 |
|
|
|
1136 |
|
|
case oCiphers: |
1137 |
|
|
arg = strdelim(&s); |
1138 |
|
|
if (!arg || *arg == '\0') |
1139 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1140 |
|
|
if (*arg != '-' && !ciphers_valid(*arg == '+' ? arg + 1 : arg)) |
1141 |
|
|
fatal("%.200s line %d: Bad SSH2 cipher spec '%s'.", |
1142 |
|
|
filename, linenum, arg ? arg : "<NONE>"); |
1143 |
|
|
if (*activep && options->ciphers == NULL) |
1144 |
|
|
options->ciphers = xstrdup(arg); |
1145 |
|
|
break; |
1146 |
|
|
|
1147 |
|
|
case oMacs: |
1148 |
|
|
arg = strdelim(&s); |
1149 |
|
|
if (!arg || *arg == '\0') |
1150 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1151 |
|
|
if (*arg != '-' && !mac_valid(*arg == '+' ? arg + 1 : arg)) |
1152 |
|
|
fatal("%.200s line %d: Bad SSH2 Mac spec '%s'.", |
1153 |
|
|
filename, linenum, arg ? arg : "<NONE>"); |
1154 |
|
|
if (*activep && options->macs == NULL) |
1155 |
|
|
options->macs = xstrdup(arg); |
1156 |
|
|
break; |
1157 |
|
|
|
1158 |
|
|
case oKexAlgorithms: |
1159 |
|
|
arg = strdelim(&s); |
1160 |
|
|
if (!arg || *arg == '\0') |
1161 |
|
|
fatal("%.200s line %d: Missing argument.", |
1162 |
|
|
filename, linenum); |
1163 |
|
|
if (*arg != '-' && |
1164 |
|
|
!kex_names_valid(*arg == '+' ? arg + 1 : arg)) |
1165 |
|
|
fatal("%.200s line %d: Bad SSH2 KexAlgorithms '%s'.", |
1166 |
|
|
filename, linenum, arg ? arg : "<NONE>"); |
1167 |
|
|
if (*activep && options->kex_algorithms == NULL) |
1168 |
|
|
options->kex_algorithms = xstrdup(arg); |
1169 |
|
|
break; |
1170 |
|
|
|
1171 |
|
|
case oHostKeyAlgorithms: |
1172 |
|
|
charptr = &options->hostkeyalgorithms; |
1173 |
|
|
parse_keytypes: |
1174 |
|
|
arg = strdelim(&s); |
1175 |
|
|
if (!arg || *arg == '\0') |
1176 |
|
|
fatal("%.200s line %d: Missing argument.", |
1177 |
|
|
filename, linenum); |
1178 |
|
|
if (*arg != '-' && |
1179 |
|
|
!sshkey_names_valid2(*arg == '+' ? arg + 1 : arg, 1)) |
1180 |
|
|
fatal("%s line %d: Bad key types '%s'.", |
1181 |
|
|
filename, linenum, arg ? arg : "<NONE>"); |
1182 |
|
|
if (*activep && *charptr == NULL) |
1183 |
|
|
*charptr = xstrdup(arg); |
1184 |
|
|
break; |
1185 |
|
|
|
1186 |
|
|
case oLogLevel: |
1187 |
|
|
log_level_ptr = &options->log_level; |
1188 |
|
|
arg = strdelim(&s); |
1189 |
|
|
value = log_level_number(arg); |
1190 |
|
|
if (value == SYSLOG_LEVEL_NOT_SET) |
1191 |
|
|
fatal("%.200s line %d: unsupported log level '%s'", |
1192 |
|
|
filename, linenum, arg ? arg : "<NONE>"); |
1193 |
|
|
if (*activep && *log_level_ptr == SYSLOG_LEVEL_NOT_SET) |
1194 |
|
|
*log_level_ptr = (LogLevel) value; |
1195 |
|
|
break; |
1196 |
|
|
|
1197 |
|
|
case oLogFacility: |
1198 |
|
|
log_facility_ptr = &options->log_facility; |
1199 |
|
|
arg = strdelim(&s); |
1200 |
|
|
value = log_facility_number(arg); |
1201 |
|
|
if (value == SYSLOG_FACILITY_NOT_SET) |
1202 |
|
|
fatal("%.200s line %d: unsupported log facility '%s'", |
1203 |
|
|
filename, linenum, arg ? arg : "<NONE>"); |
1204 |
|
|
if (*log_facility_ptr == -1) |
1205 |
|
|
*log_facility_ptr = (SyslogFacility) value; |
1206 |
|
|
break; |
1207 |
|
|
|
1208 |
|
|
case oLocalForward: |
1209 |
|
|
case oRemoteForward: |
1210 |
|
|
case oDynamicForward: |
1211 |
|
|
arg = strdelim(&s); |
1212 |
|
|
if (arg == NULL || *arg == '\0') |
1213 |
|
|
fatal("%.200s line %d: Missing port argument.", |
1214 |
|
|
filename, linenum); |
1215 |
|
|
|
1216 |
|
|
remotefwd = (opcode == oRemoteForward); |
1217 |
|
|
dynamicfwd = (opcode == oDynamicForward); |
1218 |
|
|
|
1219 |
|
|
if (!dynamicfwd) { |
1220 |
|
|
arg2 = strdelim(&s); |
1221 |
|
|
if (arg2 == NULL || *arg2 == '\0') { |
1222 |
|
|
if (remotefwd) |
1223 |
|
|
dynamicfwd = 1; |
1224 |
|
|
else |
1225 |
|
|
fatal("%.200s line %d: Missing target " |
1226 |
|
|
"argument.", filename, linenum); |
1227 |
|
|
} else { |
1228 |
|
|
/* construct a string for parse_forward */ |
1229 |
|
|
snprintf(fwdarg, sizeof(fwdarg), "%s:%s", arg, |
1230 |
|
|
arg2); |
1231 |
|
|
} |
1232 |
|
|
} |
1233 |
|
|
if (dynamicfwd) |
1234 |
|
|
strlcpy(fwdarg, arg, sizeof(fwdarg)); |
1235 |
|
|
|
1236 |
|
|
if (parse_forward(&fwd, fwdarg, dynamicfwd, remotefwd) == 0) |
1237 |
|
|
fatal("%.200s line %d: Bad forwarding specification.", |
1238 |
|
|
filename, linenum); |
1239 |
|
|
|
1240 |
|
|
if (*activep) { |
1241 |
|
|
if (remotefwd) { |
1242 |
|
|
add_remote_forward(options, &fwd); |
1243 |
|
|
} else { |
1244 |
|
|
add_local_forward(options, &fwd); |
1245 |
|
|
} |
1246 |
|
|
} |
1247 |
|
|
break; |
1248 |
|
|
|
1249 |
|
|
case oClearAllForwardings: |
1250 |
|
|
intptr = &options->clear_forwardings; |
1251 |
|
|
goto parse_flag; |
1252 |
|
|
|
1253 |
|
|
case oHost: |
1254 |
|
|
if (cmdline) |
1255 |
|
|
fatal("Host directive not supported as a command-line " |
1256 |
|
|
"option"); |
1257 |
|
|
*activep = 0; |
1258 |
|
|
arg2 = NULL; |
1259 |
|
|
while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1260 |
|
|
if ((flags & SSHCONF_NEVERMATCH) != 0) |
1261 |
|
|
break; |
1262 |
|
|
negated = *arg == '!'; |
1263 |
|
|
if (negated) |
1264 |
|
|
arg++; |
1265 |
|
|
if (match_pattern(host, arg)) { |
1266 |
|
|
if (negated) { |
1267 |
|
|
debug("%.200s line %d: Skipping Host " |
1268 |
|
|
"block because of negated match " |
1269 |
|
|
"for %.100s", filename, linenum, |
1270 |
|
|
arg); |
1271 |
|
|
*activep = 0; |
1272 |
|
|
break; |
1273 |
|
|
} |
1274 |
|
|
if (!*activep) |
1275 |
|
|
arg2 = arg; /* logged below */ |
1276 |
|
|
*activep = 1; |
1277 |
|
|
} |
1278 |
|
|
} |
1279 |
|
|
if (*activep) |
1280 |
|
|
debug("%.200s line %d: Applying options for %.100s", |
1281 |
|
|
filename, linenum, arg2); |
1282 |
|
|
/* Avoid garbage check below, as strdelim is done. */ |
1283 |
|
|
return 0; |
1284 |
|
|
|
1285 |
|
|
case oMatch: |
1286 |
|
|
if (cmdline) |
1287 |
|
|
fatal("Host directive not supported as a command-line " |
1288 |
|
|
"option"); |
1289 |
|
|
value = match_cfg_line(options, &s, pw, host, original_host, |
1290 |
|
|
flags & SSHCONF_POSTCANON, filename, linenum); |
1291 |
|
|
if (value < 0) |
1292 |
|
|
fatal("%.200s line %d: Bad Match condition", filename, |
1293 |
|
|
linenum); |
1294 |
|
|
*activep = (flags & SSHCONF_NEVERMATCH) ? 0 : value; |
1295 |
|
|
break; |
1296 |
|
|
|
1297 |
|
|
case oEscapeChar: |
1298 |
|
|
intptr = &options->escape_char; |
1299 |
|
|
arg = strdelim(&s); |
1300 |
|
|
if (!arg || *arg == '\0') |
1301 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1302 |
|
|
if (strcmp(arg, "none") == 0) |
1303 |
|
|
value = SSH_ESCAPECHAR_NONE; |
1304 |
|
|
else if (arg[1] == '\0') |
1305 |
|
|
value = (u_char) arg[0]; |
1306 |
|
|
else if (arg[0] == '^' && arg[2] == 0 && |
1307 |
|
|
(u_char) arg[1] >= 64 && (u_char) arg[1] < 128) |
1308 |
|
|
value = (u_char) arg[1] & 31; |
1309 |
|
|
else { |
1310 |
|
|
fatal("%.200s line %d: Bad escape character.", |
1311 |
|
|
filename, linenum); |
1312 |
|
|
/* NOTREACHED */ |
1313 |
|
|
value = 0; /* Avoid compiler warning. */ |
1314 |
|
|
} |
1315 |
|
|
if (*activep && *intptr == -1) |
1316 |
|
|
*intptr = value; |
1317 |
|
|
break; |
1318 |
|
|
|
1319 |
|
|
case oAddressFamily: |
1320 |
|
|
intptr = &options->address_family; |
1321 |
|
|
multistate_ptr = multistate_addressfamily; |
1322 |
|
|
goto parse_multistate; |
1323 |
|
|
|
1324 |
|
|
case oEnableSSHKeysign: |
1325 |
|
|
intptr = &options->enable_ssh_keysign; |
1326 |
|
|
goto parse_flag; |
1327 |
|
|
|
1328 |
|
|
case oIdentitiesOnly: |
1329 |
|
|
intptr = &options->identities_only; |
1330 |
|
|
goto parse_flag; |
1331 |
|
|
|
1332 |
|
|
case oServerAliveInterval: |
1333 |
|
|
intptr = &options->server_alive_interval; |
1334 |
|
|
goto parse_time; |
1335 |
|
|
|
1336 |
|
|
case oServerAliveCountMax: |
1337 |
|
|
intptr = &options->server_alive_count_max; |
1338 |
|
|
goto parse_int; |
1339 |
|
|
|
1340 |
|
|
case oSendEnv: |
1341 |
|
|
while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1342 |
|
|
if (strchr(arg, '=') != NULL) |
1343 |
|
|
fatal("%s line %d: Invalid environment name.", |
1344 |
|
|
filename, linenum); |
1345 |
|
|
if (!*activep) |
1346 |
|
|
continue; |
1347 |
|
|
if (options->num_send_env >= MAX_SEND_ENV) |
1348 |
|
|
fatal("%s line %d: too many send env.", |
1349 |
|
|
filename, linenum); |
1350 |
|
|
options->send_env[options->num_send_env++] = |
1351 |
|
|
xstrdup(arg); |
1352 |
|
|
} |
1353 |
|
|
break; |
1354 |
|
|
|
1355 |
|
|
case oControlPath: |
1356 |
|
|
charptr = &options->control_path; |
1357 |
|
|
goto parse_string; |
1358 |
|
|
|
1359 |
|
|
case oControlMaster: |
1360 |
|
|
intptr = &options->control_master; |
1361 |
|
|
multistate_ptr = multistate_controlmaster; |
1362 |
|
|
goto parse_multistate; |
1363 |
|
|
|
1364 |
|
|
case oControlPersist: |
1365 |
|
|
/* no/false/yes/true, or a time spec */ |
1366 |
|
|
intptr = &options->control_persist; |
1367 |
|
|
arg = strdelim(&s); |
1368 |
|
|
if (!arg || *arg == '\0') |
1369 |
|
|
fatal("%.200s line %d: Missing ControlPersist" |
1370 |
|
|
" argument.", filename, linenum); |
1371 |
|
|
value = 0; |
1372 |
|
|
value2 = 0; /* timeout */ |
1373 |
|
|
if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) |
1374 |
|
|
value = 0; |
1375 |
|
|
else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) |
1376 |
|
|
value = 1; |
1377 |
|
|
else if ((value2 = convtime(arg)) >= 0) |
1378 |
|
|
value = 1; |
1379 |
|
|
else |
1380 |
|
|
fatal("%.200s line %d: Bad ControlPersist argument.", |
1381 |
|
|
filename, linenum); |
1382 |
|
|
if (*activep && *intptr == -1) { |
1383 |
|
|
*intptr = value; |
1384 |
|
|
options->control_persist_timeout = value2; |
1385 |
|
|
} |
1386 |
|
|
break; |
1387 |
|
|
|
1388 |
|
|
case oHashKnownHosts: |
1389 |
|
|
intptr = &options->hash_known_hosts; |
1390 |
|
|
goto parse_flag; |
1391 |
|
|
|
1392 |
|
|
case oTunnel: |
1393 |
|
|
intptr = &options->tun_open; |
1394 |
|
|
multistate_ptr = multistate_tunnel; |
1395 |
|
|
goto parse_multistate; |
1396 |
|
|
|
1397 |
|
|
case oTunnelDevice: |
1398 |
|
|
arg = strdelim(&s); |
1399 |
|
|
if (!arg || *arg == '\0') |
1400 |
|
|
fatal("%.200s line %d: Missing argument.", filename, linenum); |
1401 |
|
|
value = a2tun(arg, &value2); |
1402 |
|
|
if (value == SSH_TUNID_ERR) |
1403 |
|
|
fatal("%.200s line %d: Bad tun device.", filename, linenum); |
1404 |
|
|
if (*activep) { |
1405 |
|
|
options->tun_local = value; |
1406 |
|
|
options->tun_remote = value2; |
1407 |
|
|
} |
1408 |
|
|
break; |
1409 |
|
|
|
1410 |
|
|
case oLocalCommand: |
1411 |
|
|
charptr = &options->local_command; |
1412 |
|
|
goto parse_command; |
1413 |
|
|
|
1414 |
|
|
case oPermitLocalCommand: |
1415 |
|
|
intptr = &options->permit_local_command; |
1416 |
|
|
goto parse_flag; |
1417 |
|
|
|
1418 |
|
|
case oRemoteCommand: |
1419 |
|
|
charptr = &options->remote_command; |
1420 |
|
|
goto parse_command; |
1421 |
|
|
|
1422 |
|
|
case oVisualHostKey: |
1423 |
|
|
intptr = &options->visual_host_key; |
1424 |
|
|
goto parse_flag; |
1425 |
|
|
|
1426 |
|
|
case oInclude: |
1427 |
|
|
if (cmdline) |
1428 |
|
|
fatal("Include directive not supported as a " |
1429 |
|
|
"command-line option"); |
1430 |
|
|
value = 0; |
1431 |
|
|
while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1432 |
|
|
/* |
1433 |
|
|
* Ensure all paths are anchored. User configuration |
1434 |
|
|
* files may begin with '~/' but system configurations |
1435 |
|
|
* must not. If the path is relative, then treat it |
1436 |
|
|
* as living in ~/.ssh for user configurations or |
1437 |
|
|
* /etc/ssh for system ones. |
1438 |
|
|
*/ |
1439 |
|
|
if (*arg == '~' && (flags & SSHCONF_USERCONF) == 0) |
1440 |
|
|
fatal("%.200s line %d: bad include path %s.", |
1441 |
|
|
filename, linenum, arg); |
1442 |
|
|
if (*arg != '/' && *arg != '~') { |
1443 |
|
|
xasprintf(&arg2, "%s/%s", |
1444 |
|
|
(flags & SSHCONF_USERCONF) ? |
1445 |
|
|
"~/" _PATH_SSH_USER_DIR : SSHDIR, arg); |
1446 |
|
|
} else |
1447 |
|
|
arg2 = xstrdup(arg); |
1448 |
|
|
memset(&gl, 0, sizeof(gl)); |
1449 |
|
|
r = glob(arg2, GLOB_TILDE, NULL, &gl); |
1450 |
|
|
if (r == GLOB_NOMATCH) { |
1451 |
|
|
debug("%.200s line %d: include %s matched no " |
1452 |
|
|
"files",filename, linenum, arg2); |
1453 |
|
|
free(arg2); |
1454 |
|
|
continue; |
1455 |
|
|
} else if (r != 0 || gl.gl_pathc < 0) |
1456 |
|
|
fatal("%.200s line %d: glob failed for %s.", |
1457 |
|
|
filename, linenum, arg2); |
1458 |
|
|
free(arg2); |
1459 |
|
|
oactive = *activep; |
1460 |
|
|
for (i = 0; i < (u_int)gl.gl_pathc; i++) { |
1461 |
|
|
debug3("%.200s line %d: Including file %s " |
1462 |
|
|
"depth %d%s", filename, linenum, |
1463 |
|
|
gl.gl_pathv[i], depth, |
1464 |
|
|
oactive ? "" : " (parse only)"); |
1465 |
|
|
r = read_config_file_depth(gl.gl_pathv[i], |
1466 |
|
|
pw, host, original_host, options, |
1467 |
|
|
flags | SSHCONF_CHECKPERM | |
1468 |
|
|
(oactive ? 0 : SSHCONF_NEVERMATCH), |
1469 |
|
|
activep, depth + 1); |
1470 |
|
|
if (r != 1 && errno != ENOENT) { |
1471 |
|
|
fatal("Can't open user config file " |
1472 |
|
|
"%.100s: %.100s", gl.gl_pathv[i], |
1473 |
|
|
strerror(errno)); |
1474 |
|
|
} |
1475 |
|
|
/* |
1476 |
|
|
* don't let Match in includes clobber the |
1477 |
|
|
* containing file's Match state. |
1478 |
|
|
*/ |
1479 |
|
|
*activep = oactive; |
1480 |
|
|
if (r != 1) |
1481 |
|
|
value = -1; |
1482 |
|
|
} |
1483 |
|
|
globfree(&gl); |
1484 |
|
|
} |
1485 |
|
|
if (value != 0) |
1486 |
|
|
return value; |
1487 |
|
|
break; |
1488 |
|
|
|
1489 |
|
|
case oIPQoS: |
1490 |
|
|
arg = strdelim(&s); |
1491 |
|
|
if ((value = parse_ipqos(arg)) == -1) |
1492 |
|
|
fatal("%s line %d: Bad IPQoS value: %s", |
1493 |
|
|
filename, linenum, arg); |
1494 |
|
|
arg = strdelim(&s); |
1495 |
|
|
if (arg == NULL) |
1496 |
|
|
value2 = value; |
1497 |
|
|
else if ((value2 = parse_ipqos(arg)) == -1) |
1498 |
|
|
fatal("%s line %d: Bad IPQoS value: %s", |
1499 |
|
|
filename, linenum, arg); |
1500 |
|
|
if (*activep) { |
1501 |
|
|
options->ip_qos_interactive = value; |
1502 |
|
|
options->ip_qos_bulk = value2; |
1503 |
|
|
} |
1504 |
|
|
break; |
1505 |
|
|
|
1506 |
|
|
case oRequestTTY: |
1507 |
|
|
intptr = &options->request_tty; |
1508 |
|
|
multistate_ptr = multistate_requesttty; |
1509 |
|
|
goto parse_multistate; |
1510 |
|
|
|
1511 |
|
|
case oIgnoreUnknown: |
1512 |
|
|
charptr = &options->ignored_unknown; |
1513 |
|
|
goto parse_string; |
1514 |
|
|
|
1515 |
|
|
case oProxyUseFdpass: |
1516 |
|
|
intptr = &options->proxy_use_fdpass; |
1517 |
|
|
goto parse_flag; |
1518 |
|
|
|
1519 |
|
|
case oCanonicalDomains: |
1520 |
|
|
value = options->num_canonical_domains != 0; |
1521 |
|
|
while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1522 |
|
|
const char *errstr; |
1523 |
|
|
if (!valid_domain(arg, 1, &errstr)) { |
1524 |
|
|
fatal("%s line %d: %s", filename, linenum, |
1525 |
|
|
errstr); |
1526 |
|
|
} |
1527 |
|
|
if (!*activep || value) |
1528 |
|
|
continue; |
1529 |
|
|
if (options->num_canonical_domains >= MAX_CANON_DOMAINS) |
1530 |
|
|
fatal("%s line %d: too many hostname suffixes.", |
1531 |
|
|
filename, linenum); |
1532 |
|
|
options->canonical_domains[ |
1533 |
|
|
options->num_canonical_domains++] = xstrdup(arg); |
1534 |
|
|
} |
1535 |
|
|
break; |
1536 |
|
|
|
1537 |
|
|
case oCanonicalizePermittedCNAMEs: |
1538 |
|
|
value = options->num_permitted_cnames != 0; |
1539 |
|
|
while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1540 |
|
|
/* Either '*' for everything or 'list:list' */ |
1541 |
|
|
if (strcmp(arg, "*") == 0) |
1542 |
|
|
arg2 = arg; |
1543 |
|
|
else { |
1544 |
|
|
lowercase(arg); |
1545 |
|
|
if ((arg2 = strchr(arg, ':')) == NULL || |
1546 |
|
|
arg2[1] == '\0') { |
1547 |
|
|
fatal("%s line %d: " |
1548 |
|
|
"Invalid permitted CNAME \"%s\"", |
1549 |
|
|
filename, linenum, arg); |
1550 |
|
|
} |
1551 |
|
|
*arg2 = '\0'; |
1552 |
|
|
arg2++; |
1553 |
|
|
} |
1554 |
|
|
if (!*activep || value) |
1555 |
|
|
continue; |
1556 |
|
|
if (options->num_permitted_cnames >= MAX_CANON_DOMAINS) |
1557 |
|
|
fatal("%s line %d: too many permitted CNAMEs.", |
1558 |
|
|
filename, linenum); |
1559 |
|
|
cname = options->permitted_cnames + |
1560 |
|
|
options->num_permitted_cnames++; |
1561 |
|
|
cname->source_list = xstrdup(arg); |
1562 |
|
|
cname->target_list = xstrdup(arg2); |
1563 |
|
|
} |
1564 |
|
|
break; |
1565 |
|
|
|
1566 |
|
|
case oCanonicalizeHostname: |
1567 |
|
|
intptr = &options->canonicalize_hostname; |
1568 |
|
|
multistate_ptr = multistate_canonicalizehostname; |
1569 |
|
|
goto parse_multistate; |
1570 |
|
|
|
1571 |
|
|
case oCanonicalizeMaxDots: |
1572 |
|
|
intptr = &options->canonicalize_max_dots; |
1573 |
|
|
goto parse_int; |
1574 |
|
|
|
1575 |
|
|
case oCanonicalizeFallbackLocal: |
1576 |
|
|
intptr = &options->canonicalize_fallback_local; |
1577 |
|
|
goto parse_flag; |
1578 |
|
|
|
1579 |
|
|
case oStreamLocalBindMask: |
1580 |
|
|
arg = strdelim(&s); |
1581 |
|
|
if (!arg || *arg == '\0') |
1582 |
|
|
fatal("%.200s line %d: Missing StreamLocalBindMask argument.", filename, linenum); |
1583 |
|
|
/* Parse mode in octal format */ |
1584 |
|
|
value = strtol(arg, &endofnumber, 8); |
1585 |
|
|
if (arg == endofnumber || value < 0 || value > 0777) |
1586 |
|
|
fatal("%.200s line %d: Bad mask.", filename, linenum); |
1587 |
|
|
options->fwd_opts.streamlocal_bind_mask = (mode_t)value; |
1588 |
|
|
break; |
1589 |
|
|
|
1590 |
|
|
case oStreamLocalBindUnlink: |
1591 |
|
|
intptr = &options->fwd_opts.streamlocal_bind_unlink; |
1592 |
|
|
goto parse_flag; |
1593 |
|
|
|
1594 |
|
|
case oRevokedHostKeys: |
1595 |
|
|
charptr = &options->revoked_host_keys; |
1596 |
|
|
goto parse_string; |
1597 |
|
|
|
1598 |
|
|
case oFingerprintHash: |
1599 |
|
|
intptr = &options->fingerprint_hash; |
1600 |
|
|
arg = strdelim(&s); |
1601 |
|
|
if (!arg || *arg == '\0') |
1602 |
|
|
fatal("%.200s line %d: Missing argument.", |
1603 |
|
|
filename, linenum); |
1604 |
|
|
if ((value = ssh_digest_alg_by_name(arg)) == -1) |
1605 |
|
|
fatal("%.200s line %d: Invalid hash algorithm \"%s\".", |
1606 |
|
|
filename, linenum, arg); |
1607 |
|
|
if (*activep && *intptr == -1) |
1608 |
|
|
*intptr = value; |
1609 |
|
|
break; |
1610 |
|
|
|
1611 |
|
|
case oUpdateHostkeys: |
1612 |
|
|
intptr = &options->update_hostkeys; |
1613 |
|
|
multistate_ptr = multistate_yesnoask; |
1614 |
|
|
goto parse_multistate; |
1615 |
|
|
|
1616 |
|
|
case oHostbasedKeyTypes: |
1617 |
|
|
charptr = &options->hostbased_key_types; |
1618 |
|
|
goto parse_keytypes; |
1619 |
|
|
|
1620 |
|
|
case oPubkeyAcceptedKeyTypes: |
1621 |
|
|
charptr = &options->pubkey_key_types; |
1622 |
|
|
goto parse_keytypes; |
1623 |
|
|
|
1624 |
|
|
case oAddKeysToAgent: |
1625 |
|
|
intptr = &options->add_keys_to_agent; |
1626 |
|
|
multistate_ptr = multistate_yesnoaskconfirm; |
1627 |
|
|
goto parse_multistate; |
1628 |
|
|
|
1629 |
|
|
case oIdentityAgent: |
1630 |
|
|
charptr = &options->identity_agent; |
1631 |
|
|
goto parse_string; |
1632 |
|
|
|
1633 |
|
|
case oDeprecated: |
1634 |
|
|
debug("%s line %d: Deprecated option \"%s\"", |
1635 |
|
|
filename, linenum, keyword); |
1636 |
|
|
return 0; |
1637 |
|
|
|
1638 |
|
|
case oUnsupported: |
1639 |
|
|
error("%s line %d: Unsupported option \"%s\"", |
1640 |
|
|
filename, linenum, keyword); |
1641 |
|
|
return 0; |
1642 |
|
|
|
1643 |
|
|
default: |
1644 |
|
|
fatal("%s: Unimplemented opcode %d", __func__, opcode); |
1645 |
|
|
} |
1646 |
|
|
|
1647 |
|
|
/* Check that there is no garbage at end of line. */ |
1648 |
|
|
if ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
1649 |
|
|
fatal("%.200s line %d: garbage at end of line; \"%.200s\".", |
1650 |
|
|
filename, linenum, arg); |
1651 |
|
|
} |
1652 |
|
|
return 0; |
1653 |
|
|
} |
1654 |
|
|
|
1655 |
|
|
/* |
1656 |
|
|
* Reads the config file and modifies the options accordingly. Options |
1657 |
|
|
* should already be initialized before this call. This never returns if |
1658 |
|
|
* there is an error. If the file does not exist, this returns 0. |
1659 |
|
|
*/ |
1660 |
|
|
int |
1661 |
|
|
read_config_file(const char *filename, struct passwd *pw, const char *host, |
1662 |
|
|
const char *original_host, Options *options, int flags) |
1663 |
|
|
{ |
1664 |
|
|
int active = 1; |
1665 |
|
|
|
1666 |
|
|
return read_config_file_depth(filename, pw, host, original_host, |
1667 |
|
|
options, flags, &active, 0); |
1668 |
|
|
} |
1669 |
|
|
|
1670 |
|
|
#define READCONF_MAX_DEPTH 16 |
1671 |
|
|
static int |
1672 |
|
|
read_config_file_depth(const char *filename, struct passwd *pw, |
1673 |
|
|
const char *host, const char *original_host, Options *options, |
1674 |
|
|
int flags, int *activep, int depth) |
1675 |
|
|
{ |
1676 |
|
|
FILE *f; |
1677 |
|
|
char line[4096]; |
1678 |
|
|
int linenum; |
1679 |
|
|
int bad_options = 0; |
1680 |
|
|
|
1681 |
|
|
if (depth < 0 || depth > READCONF_MAX_DEPTH) |
1682 |
|
|
fatal("Too many recursive configuration includes"); |
1683 |
|
|
|
1684 |
|
|
if ((f = fopen(filename, "r")) == NULL) |
1685 |
|
|
return 0; |
1686 |
|
|
|
1687 |
|
|
if (flags & SSHCONF_CHECKPERM) { |
1688 |
|
|
struct stat sb; |
1689 |
|
|
|
1690 |
|
|
if (fstat(fileno(f), &sb) == -1) |
1691 |
|
|
fatal("fstat %s: %s", filename, strerror(errno)); |
1692 |
|
|
if (((sb.st_uid != 0 && sb.st_uid != getuid()) || |
1693 |
|
|
(sb.st_mode & 022) != 0)) |
1694 |
|
|
fatal("Bad owner or permissions on %s", filename); |
1695 |
|
|
} |
1696 |
|
|
|
1697 |
|
|
debug("Reading configuration data %.200s", filename); |
1698 |
|
|
|
1699 |
|
|
/* |
1700 |
|
|
* Mark that we are now processing the options. This flag is turned |
1701 |
|
|
* on/off by Host specifications. |
1702 |
|
|
*/ |
1703 |
|
|
linenum = 0; |
1704 |
|
|
while (fgets(line, sizeof(line), f)) { |
1705 |
|
|
/* Update line number counter. */ |
1706 |
|
|
linenum++; |
1707 |
|
|
if (strlen(line) == sizeof(line) - 1) |
1708 |
|
|
fatal("%s line %d too long", filename, linenum); |
1709 |
|
|
if (process_config_line_depth(options, pw, host, original_host, |
1710 |
|
|
line, filename, linenum, activep, flags, depth) != 0) |
1711 |
|
|
bad_options++; |
1712 |
|
|
} |
1713 |
|
|
fclose(f); |
1714 |
|
|
if (bad_options > 0) |
1715 |
|
|
fatal("%s: terminating, %d bad configuration options", |
1716 |
|
|
filename, bad_options); |
1717 |
|
|
return 1; |
1718 |
|
|
} |
1719 |
|
|
|
1720 |
|
|
/* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ |
1721 |
|
|
int |
1722 |
|
|
option_clear_or_none(const char *o) |
1723 |
|
|
{ |
1724 |
|
|
return o == NULL || strcasecmp(o, "none") == 0; |
1725 |
|
|
} |
1726 |
|
|
|
1727 |
|
|
/* |
1728 |
|
|
* Initializes options to special values that indicate that they have not yet |
1729 |
|
|
* been set. Read_config_file will only set options with this value. Options |
1730 |
|
|
* are processed in the following order: command line, user config file, |
1731 |
|
|
* system config file. Last, fill_default_options is called. |
1732 |
|
|
*/ |
1733 |
|
|
|
1734 |
|
|
void |
1735 |
|
|
initialize_options(Options * options) |
1736 |
|
|
{ |
1737 |
|
|
memset(options, 'X', sizeof(*options)); |
1738 |
|
|
options->forward_agent = -1; |
1739 |
|
|
options->forward_x11 = -1; |
1740 |
|
|
options->forward_x11_trusted = -1; |
1741 |
|
|
options->forward_x11_timeout = -1; |
1742 |
|
|
options->stdio_forward_host = NULL; |
1743 |
|
|
options->stdio_forward_port = 0; |
1744 |
|
|
options->clear_forwardings = -1; |
1745 |
|
|
options->exit_on_forward_failure = -1; |
1746 |
|
|
options->xauth_location = NULL; |
1747 |
|
|
options->fwd_opts.gateway_ports = -1; |
1748 |
|
|
options->fwd_opts.streamlocal_bind_mask = (mode_t)-1; |
1749 |
|
|
options->fwd_opts.streamlocal_bind_unlink = -1; |
1750 |
|
|
options->use_privileged_port = -1; |
1751 |
|
|
options->pubkey_authentication = -1; |
1752 |
|
|
options->challenge_response_authentication = -1; |
1753 |
|
|
options->gss_authentication = -1; |
1754 |
|
|
options->gss_deleg_creds = -1; |
1755 |
|
|
options->password_authentication = -1; |
1756 |
|
|
options->kbd_interactive_authentication = -1; |
1757 |
|
|
options->kbd_interactive_devices = NULL; |
1758 |
|
|
options->hostbased_authentication = -1; |
1759 |
|
|
options->batch_mode = -1; |
1760 |
|
|
options->check_host_ip = -1; |
1761 |
|
|
options->strict_host_key_checking = -1; |
1762 |
|
|
options->compression = -1; |
1763 |
|
|
options->tcp_keep_alive = -1; |
1764 |
|
|
options->port = -1; |
1765 |
|
|
options->address_family = -1; |
1766 |
|
|
options->connection_attempts = -1; |
1767 |
|
|
options->connection_timeout = -1; |
1768 |
|
|
options->number_of_password_prompts = -1; |
1769 |
|
|
options->ciphers = NULL; |
1770 |
|
|
options->macs = NULL; |
1771 |
|
|
options->kex_algorithms = NULL; |
1772 |
|
|
options->hostkeyalgorithms = NULL; |
1773 |
|
|
options->num_identity_files = 0; |
1774 |
|
|
options->num_certificate_files = 0; |
1775 |
|
|
options->hostname = NULL; |
1776 |
|
|
options->host_key_alias = NULL; |
1777 |
|
|
options->proxy_command = NULL; |
1778 |
|
|
options->jump_user = NULL; |
1779 |
|
|
options->jump_host = NULL; |
1780 |
|
|
options->jump_port = -1; |
1781 |
|
|
options->jump_extra = NULL; |
1782 |
|
|
options->user = NULL; |
1783 |
|
|
options->escape_char = -1; |
1784 |
|
|
options->num_system_hostfiles = 0; |
1785 |
|
|
options->num_user_hostfiles = 0; |
1786 |
|
|
options->local_forwards = NULL; |
1787 |
|
|
options->num_local_forwards = 0; |
1788 |
|
|
options->remote_forwards = NULL; |
1789 |
|
|
options->num_remote_forwards = 0; |
1790 |
|
|
options->log_facility = SYSLOG_FACILITY_NOT_SET; |
1791 |
|
|
options->log_level = SYSLOG_LEVEL_NOT_SET; |
1792 |
|
|
options->preferred_authentications = NULL; |
1793 |
|
|
options->bind_address = NULL; |
1794 |
|
|
options->pkcs11_provider = NULL; |
1795 |
|
|
options->enable_ssh_keysign = - 1; |
1796 |
|
|
options->no_host_authentication_for_localhost = - 1; |
1797 |
|
|
options->identities_only = - 1; |
1798 |
|
|
options->rekey_limit = - 1; |
1799 |
|
|
options->rekey_interval = -1; |
1800 |
|
|
options->verify_host_key_dns = -1; |
1801 |
|
|
options->server_alive_interval = -1; |
1802 |
|
|
options->server_alive_count_max = -1; |
1803 |
|
|
options->num_send_env = 0; |
1804 |
|
|
options->control_path = NULL; |
1805 |
|
|
options->control_master = -1; |
1806 |
|
|
options->control_persist = -1; |
1807 |
|
|
options->control_persist_timeout = 0; |
1808 |
|
|
options->hash_known_hosts = -1; |
1809 |
|
|
options->tun_open = -1; |
1810 |
|
|
options->tun_local = -1; |
1811 |
|
|
options->tun_remote = -1; |
1812 |
|
|
options->local_command = NULL; |
1813 |
|
|
options->permit_local_command = -1; |
1814 |
|
|
options->remote_command = NULL; |
1815 |
|
|
options->add_keys_to_agent = -1; |
1816 |
|
|
options->identity_agent = NULL; |
1817 |
|
|
options->visual_host_key = -1; |
1818 |
|
|
options->ip_qos_interactive = -1; |
1819 |
|
|
options->ip_qos_bulk = -1; |
1820 |
|
|
options->request_tty = -1; |
1821 |
|
|
options->proxy_use_fdpass = -1; |
1822 |
|
|
options->ignored_unknown = NULL; |
1823 |
|
|
options->num_canonical_domains = 0; |
1824 |
|
|
options->num_permitted_cnames = 0; |
1825 |
|
|
options->canonicalize_max_dots = -1; |
1826 |
|
|
options->canonicalize_fallback_local = -1; |
1827 |
|
|
options->canonicalize_hostname = -1; |
1828 |
|
|
options->revoked_host_keys = NULL; |
1829 |
|
|
options->fingerprint_hash = -1; |
1830 |
|
|
options->update_hostkeys = -1; |
1831 |
|
|
options->hostbased_key_types = NULL; |
1832 |
|
|
options->pubkey_key_types = NULL; |
1833 |
|
|
} |
1834 |
|
|
|
1835 |
|
|
/* |
1836 |
|
|
* A petite version of fill_default_options() that just fills the options |
1837 |
|
|
* needed for hostname canonicalization to proceed. |
1838 |
|
|
*/ |
1839 |
|
|
void |
1840 |
|
|
fill_default_options_for_canonicalization(Options *options) |
1841 |
|
|
{ |
1842 |
|
|
if (options->canonicalize_max_dots == -1) |
1843 |
|
|
options->canonicalize_max_dots = 1; |
1844 |
|
|
if (options->canonicalize_fallback_local == -1) |
1845 |
|
|
options->canonicalize_fallback_local = 1; |
1846 |
|
|
if (options->canonicalize_hostname == -1) |
1847 |
|
|
options->canonicalize_hostname = SSH_CANONICALISE_NO; |
1848 |
|
|
} |
1849 |
|
|
|
1850 |
|
|
/* |
1851 |
|
|
* Called after processing other sources of option data, this fills those |
1852 |
|
|
* options for which no value has been specified with their default values. |
1853 |
|
|
*/ |
1854 |
|
|
void |
1855 |
|
|
fill_default_options(Options * options) |
1856 |
|
|
{ |
1857 |
|
|
if (options->forward_agent == -1) |
1858 |
|
|
options->forward_agent = 0; |
1859 |
|
|
if (options->forward_x11 == -1) |
1860 |
|
|
options->forward_x11 = 0; |
1861 |
|
|
if (options->forward_x11_trusted == -1) |
1862 |
|
|
options->forward_x11_trusted = 0; |
1863 |
|
|
if (options->forward_x11_timeout == -1) |
1864 |
|
|
options->forward_x11_timeout = 1200; |
1865 |
|
|
/* |
1866 |
|
|
* stdio forwarding (-W) changes the default for these but we defer |
1867 |
|
|
* setting the values so they can be overridden. |
1868 |
|
|
*/ |
1869 |
|
|
if (options->exit_on_forward_failure == -1) |
1870 |
|
|
options->exit_on_forward_failure = |
1871 |
|
|
options->stdio_forward_host != NULL ? 1 : 0; |
1872 |
|
|
if (options->clear_forwardings == -1) |
1873 |
|
|
options->clear_forwardings = |
1874 |
|
|
options->stdio_forward_host != NULL ? 1 : 0; |
1875 |
|
|
if (options->clear_forwardings == 1) |
1876 |
|
|
clear_forwardings(options); |
1877 |
|
|
|
1878 |
|
|
if (options->xauth_location == NULL) |
1879 |
|
|
options->xauth_location = _PATH_XAUTH; |
1880 |
|
|
if (options->fwd_opts.gateway_ports == -1) |
1881 |
|
|
options->fwd_opts.gateway_ports = 0; |
1882 |
|
|
if (options->fwd_opts.streamlocal_bind_mask == (mode_t)-1) |
1883 |
|
|
options->fwd_opts.streamlocal_bind_mask = 0177; |
1884 |
|
|
if (options->fwd_opts.streamlocal_bind_unlink == -1) |
1885 |
|
|
options->fwd_opts.streamlocal_bind_unlink = 0; |
1886 |
|
|
if (options->use_privileged_port == -1) |
1887 |
|
|
options->use_privileged_port = 0; |
1888 |
|
|
if (options->pubkey_authentication == -1) |
1889 |
|
|
options->pubkey_authentication = 1; |
1890 |
|
|
if (options->challenge_response_authentication == -1) |
1891 |
|
|
options->challenge_response_authentication = 1; |
1892 |
|
|
if (options->gss_authentication == -1) |
1893 |
|
|
options->gss_authentication = 0; |
1894 |
|
|
if (options->gss_deleg_creds == -1) |
1895 |
|
|
options->gss_deleg_creds = 0; |
1896 |
|
|
if (options->password_authentication == -1) |
1897 |
|
|
options->password_authentication = 1; |
1898 |
|
|
if (options->kbd_interactive_authentication == -1) |
1899 |
|
|
options->kbd_interactive_authentication = 1; |
1900 |
|
|
if (options->hostbased_authentication == -1) |
1901 |
|
|
options->hostbased_authentication = 0; |
1902 |
|
|
if (options->batch_mode == -1) |
1903 |
|
|
options->batch_mode = 0; |
1904 |
|
|
if (options->check_host_ip == -1) |
1905 |
|
|
options->check_host_ip = 1; |
1906 |
|
|
if (options->strict_host_key_checking == -1) |
1907 |
|
|
options->strict_host_key_checking = SSH_STRICT_HOSTKEY_ASK; |
1908 |
|
|
if (options->compression == -1) |
1909 |
|
|
options->compression = 0; |
1910 |
|
|
if (options->tcp_keep_alive == -1) |
1911 |
|
|
options->tcp_keep_alive = 1; |
1912 |
|
|
if (options->port == -1) |
1913 |
|
|
options->port = 0; /* Filled in ssh_connect. */ |
1914 |
|
|
if (options->address_family == -1) |
1915 |
|
|
options->address_family = AF_UNSPEC; |
1916 |
|
|
if (options->connection_attempts == -1) |
1917 |
|
|
options->connection_attempts = 1; |
1918 |
|
|
if (options->number_of_password_prompts == -1) |
1919 |
|
|
options->number_of_password_prompts = 3; |
1920 |
|
|
/* options->hostkeyalgorithms, default set in myproposals.h */ |
1921 |
|
|
if (options->add_keys_to_agent == -1) |
1922 |
|
|
options->add_keys_to_agent = 0; |
1923 |
|
|
if (options->num_identity_files == 0) { |
1924 |
|
|
add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_RSA, 0); |
1925 |
|
|
add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_DSA, 0); |
1926 |
|
|
add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ECDSA, 0); |
1927 |
|
|
add_identity_file(options, "~/", |
1928 |
|
|
_PATH_SSH_CLIENT_ID_ED25519, 0); |
1929 |
|
|
} |
1930 |
|
|
if (options->escape_char == -1) |
1931 |
|
|
options->escape_char = '~'; |
1932 |
|
|
if (options->num_system_hostfiles == 0) { |
1933 |
|
|
options->system_hostfiles[options->num_system_hostfiles++] = |
1934 |
|
|
xstrdup(_PATH_SSH_SYSTEM_HOSTFILE); |
1935 |
|
|
options->system_hostfiles[options->num_system_hostfiles++] = |
1936 |
|
|
xstrdup(_PATH_SSH_SYSTEM_HOSTFILE2); |
1937 |
|
|
} |
1938 |
|
|
if (options->num_user_hostfiles == 0) { |
1939 |
|
|
options->user_hostfiles[options->num_user_hostfiles++] = |
1940 |
|
|
xstrdup(_PATH_SSH_USER_HOSTFILE); |
1941 |
|
|
options->user_hostfiles[options->num_user_hostfiles++] = |
1942 |
|
|
xstrdup(_PATH_SSH_USER_HOSTFILE2); |
1943 |
|
|
} |
1944 |
|
|
if (options->log_level == SYSLOG_LEVEL_NOT_SET) |
1945 |
|
|
options->log_level = SYSLOG_LEVEL_INFO; |
1946 |
|
|
if (options->log_facility == SYSLOG_FACILITY_NOT_SET) |
1947 |
|
|
options->log_facility = SYSLOG_FACILITY_USER; |
1948 |
|
|
if (options->no_host_authentication_for_localhost == - 1) |
1949 |
|
|
options->no_host_authentication_for_localhost = 0; |
1950 |
|
|
if (options->identities_only == -1) |
1951 |
|
|
options->identities_only = 0; |
1952 |
|
|
if (options->enable_ssh_keysign == -1) |
1953 |
|
|
options->enable_ssh_keysign = 0; |
1954 |
|
|
if (options->rekey_limit == -1) |
1955 |
|
|
options->rekey_limit = 0; |
1956 |
|
|
if (options->rekey_interval == -1) |
1957 |
|
|
options->rekey_interval = 0; |
1958 |
|
|
if (options->verify_host_key_dns == -1) |
1959 |
|
|
options->verify_host_key_dns = 0; |
1960 |
|
|
if (options->server_alive_interval == -1) |
1961 |
|
|
options->server_alive_interval = 0; |
1962 |
|
|
if (options->server_alive_count_max == -1) |
1963 |
|
|
options->server_alive_count_max = 3; |
1964 |
|
|
if (options->control_master == -1) |
1965 |
|
|
options->control_master = 0; |
1966 |
|
|
if (options->control_persist == -1) { |
1967 |
|
|
options->control_persist = 0; |
1968 |
|
|
options->control_persist_timeout = 0; |
1969 |
|
|
} |
1970 |
|
|
if (options->hash_known_hosts == -1) |
1971 |
|
|
options->hash_known_hosts = 0; |
1972 |
|
|
if (options->tun_open == -1) |
1973 |
|
|
options->tun_open = SSH_TUNMODE_NO; |
1974 |
|
|
if (options->tun_local == -1) |
1975 |
|
|
options->tun_local = SSH_TUNID_ANY; |
1976 |
|
|
if (options->tun_remote == -1) |
1977 |
|
|
options->tun_remote = SSH_TUNID_ANY; |
1978 |
|
|
if (options->permit_local_command == -1) |
1979 |
|
|
options->permit_local_command = 0; |
1980 |
|
|
if (options->visual_host_key == -1) |
1981 |
|
|
options->visual_host_key = 0; |
1982 |
|
|
if (options->ip_qos_interactive == -1) |
1983 |
|
|
options->ip_qos_interactive = IPTOS_LOWDELAY; |
1984 |
|
|
if (options->ip_qos_bulk == -1) |
1985 |
|
|
options->ip_qos_bulk = IPTOS_THROUGHPUT; |
1986 |
|
|
if (options->request_tty == -1) |
1987 |
|
|
options->request_tty = REQUEST_TTY_AUTO; |
1988 |
|
|
if (options->proxy_use_fdpass == -1) |
1989 |
|
|
options->proxy_use_fdpass = 0; |
1990 |
|
|
if (options->canonicalize_max_dots == -1) |
1991 |
|
|
options->canonicalize_max_dots = 1; |
1992 |
|
|
if (options->canonicalize_fallback_local == -1) |
1993 |
|
|
options->canonicalize_fallback_local = 1; |
1994 |
|
|
if (options->canonicalize_hostname == -1) |
1995 |
|
|
options->canonicalize_hostname = SSH_CANONICALISE_NO; |
1996 |
|
|
if (options->fingerprint_hash == -1) |
1997 |
|
|
options->fingerprint_hash = SSH_FP_HASH_DEFAULT; |
1998 |
|
|
if (options->update_hostkeys == -1) |
1999 |
|
|
options->update_hostkeys = 0; |
2000 |
|
|
if (kex_assemble_names(KEX_CLIENT_ENCRYPT, &options->ciphers) != 0 || |
2001 |
|
|
kex_assemble_names(KEX_CLIENT_MAC, &options->macs) != 0 || |
2002 |
|
|
kex_assemble_names(KEX_CLIENT_KEX, &options->kex_algorithms) != 0 || |
2003 |
|
|
kex_assemble_names(KEX_DEFAULT_PK_ALG, |
2004 |
|
|
&options->hostbased_key_types) != 0 || |
2005 |
|
|
kex_assemble_names(KEX_DEFAULT_PK_ALG, |
2006 |
|
|
&options->pubkey_key_types) != 0) |
2007 |
|
|
fatal("%s: kex_assemble_names failed", __func__); |
2008 |
|
|
|
2009 |
|
|
#define CLEAR_ON_NONE(v) \ |
2010 |
|
|
do { \ |
2011 |
|
|
if (option_clear_or_none(v)) { \ |
2012 |
|
|
free(v); \ |
2013 |
|
|
v = NULL; \ |
2014 |
|
|
} \ |
2015 |
|
|
} while(0) |
2016 |
|
|
CLEAR_ON_NONE(options->local_command); |
2017 |
|
|
CLEAR_ON_NONE(options->remote_command); |
2018 |
|
|
CLEAR_ON_NONE(options->proxy_command); |
2019 |
|
|
CLEAR_ON_NONE(options->control_path); |
2020 |
|
|
CLEAR_ON_NONE(options->revoked_host_keys); |
2021 |
|
|
/* options->identity_agent distinguishes NULL from 'none' */ |
2022 |
|
|
/* options->user will be set in the main program if appropriate */ |
2023 |
|
|
/* options->hostname will be set in the main program if appropriate */ |
2024 |
|
|
/* options->host_key_alias should not be set by default */ |
2025 |
|
|
/* options->preferred_authentications will be set in ssh */ |
2026 |
|
|
} |
2027 |
|
|
|
2028 |
|
|
struct fwdarg { |
2029 |
|
|
char *arg; |
2030 |
|
|
int ispath; |
2031 |
|
|
}; |
2032 |
|
|
|
2033 |
|
|
/* |
2034 |
|
|
* parse_fwd_field |
2035 |
|
|
* parses the next field in a port forwarding specification. |
2036 |
|
|
* sets fwd to the parsed field and advances p past the colon |
2037 |
|
|
* or sets it to NULL at end of string. |
2038 |
|
|
* returns 0 on success, else non-zero. |
2039 |
|
|
*/ |
2040 |
|
|
static int |
2041 |
|
|
parse_fwd_field(char **p, struct fwdarg *fwd) |
2042 |
|
|
{ |
2043 |
|
|
char *ep, *cp = *p; |
2044 |
|
|
int ispath = 0; |
2045 |
|
|
|
2046 |
|
|
if (*cp == '\0') { |
2047 |
|
|
*p = NULL; |
2048 |
|
|
return -1; /* end of string */ |
2049 |
|
|
} |
2050 |
|
|
|
2051 |
|
|
/* |
2052 |
|
|
* A field escaped with square brackets is used literally. |
2053 |
|
|
* XXX - allow ']' to be escaped via backslash? |
2054 |
|
|
*/ |
2055 |
|
|
if (*cp == '[') { |
2056 |
|
|
/* find matching ']' */ |
2057 |
|
|
for (ep = cp + 1; *ep != ']' && *ep != '\0'; ep++) { |
2058 |
|
|
if (*ep == '/') |
2059 |
|
|
ispath = 1; |
2060 |
|
|
} |
2061 |
|
|
/* no matching ']' or not at end of field. */ |
2062 |
|
|
if (ep[0] != ']' || (ep[1] != ':' && ep[1] != '\0')) |
2063 |
|
|
return -1; |
2064 |
|
|
/* NUL terminate the field and advance p past the colon */ |
2065 |
|
|
*ep++ = '\0'; |
2066 |
|
|
if (*ep != '\0') |
2067 |
|
|
*ep++ = '\0'; |
2068 |
|
|
fwd->arg = cp + 1; |
2069 |
|
|
fwd->ispath = ispath; |
2070 |
|
|
*p = ep; |
2071 |
|
|
return 0; |
2072 |
|
|
} |
2073 |
|
|
|
2074 |
|
|
for (cp = *p; *cp != '\0'; cp++) { |
2075 |
|
|
switch (*cp) { |
2076 |
|
|
case '\\': |
2077 |
|
|
memmove(cp, cp + 1, strlen(cp + 1) + 1); |
2078 |
|
|
if (*cp == '\0') |
2079 |
|
|
return -1; |
2080 |
|
|
break; |
2081 |
|
|
case '/': |
2082 |
|
|
ispath = 1; |
2083 |
|
|
break; |
2084 |
|
|
case ':': |
2085 |
|
|
*cp++ = '\0'; |
2086 |
|
|
goto done; |
2087 |
|
|
} |
2088 |
|
|
} |
2089 |
|
|
done: |
2090 |
|
|
fwd->arg = *p; |
2091 |
|
|
fwd->ispath = ispath; |
2092 |
|
|
*p = cp; |
2093 |
|
|
return 0; |
2094 |
|
|
} |
2095 |
|
|
|
2096 |
|
|
/* |
2097 |
|
|
* parse_forward |
2098 |
|
|
* parses a string containing a port forwarding specification of the form: |
2099 |
|
|
* dynamicfwd == 0 |
2100 |
|
|
* [listenhost:]listenport|listenpath:connecthost:connectport|connectpath |
2101 |
|
|
* listenpath:connectpath |
2102 |
|
|
* dynamicfwd == 1 |
2103 |
|
|
* [listenhost:]listenport |
2104 |
|
|
* returns number of arguments parsed or zero on error |
2105 |
|
|
*/ |
2106 |
|
|
int |
2107 |
|
|
parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remotefwd) |
2108 |
|
|
{ |
2109 |
|
|
struct fwdarg fwdargs[4]; |
2110 |
|
|
char *p, *cp; |
2111 |
|
|
int i; |
2112 |
|
|
|
2113 |
|
|
memset(fwd, 0, sizeof(*fwd)); |
2114 |
|
|
memset(fwdargs, 0, sizeof(fwdargs)); |
2115 |
|
|
|
2116 |
|
|
cp = p = xstrdup(fwdspec); |
2117 |
|
|
|
2118 |
|
|
/* skip leading spaces */ |
2119 |
|
|
while (isspace((u_char)*cp)) |
2120 |
|
|
cp++; |
2121 |
|
|
|
2122 |
|
|
for (i = 0; i < 4; ++i) { |
2123 |
|
|
if (parse_fwd_field(&cp, &fwdargs[i]) != 0) |
2124 |
|
|
break; |
2125 |
|
|
} |
2126 |
|
|
|
2127 |
|
|
/* Check for trailing garbage */ |
2128 |
|
|
if (cp != NULL && *cp != '\0') { |
2129 |
|
|
i = 0; /* failure */ |
2130 |
|
|
} |
2131 |
|
|
|
2132 |
|
|
switch (i) { |
2133 |
|
|
case 1: |
2134 |
|
|
if (fwdargs[0].ispath) { |
2135 |
|
|
fwd->listen_path = xstrdup(fwdargs[0].arg); |
2136 |
|
|
fwd->listen_port = PORT_STREAMLOCAL; |
2137 |
|
|
} else { |
2138 |
|
|
fwd->listen_host = NULL; |
2139 |
|
|
fwd->listen_port = a2port(fwdargs[0].arg); |
2140 |
|
|
} |
2141 |
|
|
fwd->connect_host = xstrdup("socks"); |
2142 |
|
|
break; |
2143 |
|
|
|
2144 |
|
|
case 2: |
2145 |
|
|
if (fwdargs[0].ispath && fwdargs[1].ispath) { |
2146 |
|
|
fwd->listen_path = xstrdup(fwdargs[0].arg); |
2147 |
|
|
fwd->listen_port = PORT_STREAMLOCAL; |
2148 |
|
|
fwd->connect_path = xstrdup(fwdargs[1].arg); |
2149 |
|
|
fwd->connect_port = PORT_STREAMLOCAL; |
2150 |
|
|
} else if (fwdargs[1].ispath) { |
2151 |
|
|
fwd->listen_host = NULL; |
2152 |
|
|
fwd->listen_port = a2port(fwdargs[0].arg); |
2153 |
|
|
fwd->connect_path = xstrdup(fwdargs[1].arg); |
2154 |
|
|
fwd->connect_port = PORT_STREAMLOCAL; |
2155 |
|
|
} else { |
2156 |
|
|
fwd->listen_host = xstrdup(fwdargs[0].arg); |
2157 |
|
|
fwd->listen_port = a2port(fwdargs[1].arg); |
2158 |
|
|
fwd->connect_host = xstrdup("socks"); |
2159 |
|
|
} |
2160 |
|
|
break; |
2161 |
|
|
|
2162 |
|
|
case 3: |
2163 |
|
|
if (fwdargs[0].ispath) { |
2164 |
|
|
fwd->listen_path = xstrdup(fwdargs[0].arg); |
2165 |
|
|
fwd->listen_port = PORT_STREAMLOCAL; |
2166 |
|
|
fwd->connect_host = xstrdup(fwdargs[1].arg); |
2167 |
|
|
fwd->connect_port = a2port(fwdargs[2].arg); |
2168 |
|
|
} else if (fwdargs[2].ispath) { |
2169 |
|
|
fwd->listen_host = xstrdup(fwdargs[0].arg); |
2170 |
|
|
fwd->listen_port = a2port(fwdargs[1].arg); |
2171 |
|
|
fwd->connect_path = xstrdup(fwdargs[2].arg); |
2172 |
|
|
fwd->connect_port = PORT_STREAMLOCAL; |
2173 |
|
|
} else { |
2174 |
|
|
fwd->listen_host = NULL; |
2175 |
|
|
fwd->listen_port = a2port(fwdargs[0].arg); |
2176 |
|
|
fwd->connect_host = xstrdup(fwdargs[1].arg); |
2177 |
|
|
fwd->connect_port = a2port(fwdargs[2].arg); |
2178 |
|
|
} |
2179 |
|
|
break; |
2180 |
|
|
|
2181 |
|
|
case 4: |
2182 |
|
|
fwd->listen_host = xstrdup(fwdargs[0].arg); |
2183 |
|
|
fwd->listen_port = a2port(fwdargs[1].arg); |
2184 |
|
|
fwd->connect_host = xstrdup(fwdargs[2].arg); |
2185 |
|
|
fwd->connect_port = a2port(fwdargs[3].arg); |
2186 |
|
|
break; |
2187 |
|
|
default: |
2188 |
|
|
i = 0; /* failure */ |
2189 |
|
|
} |
2190 |
|
|
|
2191 |
|
|
free(p); |
2192 |
|
|
|
2193 |
|
|
if (dynamicfwd) { |
2194 |
|
|
if (!(i == 1 || i == 2)) |
2195 |
|
|
goto fail_free; |
2196 |
|
|
} else { |
2197 |
|
|
if (!(i == 3 || i == 4)) { |
2198 |
|
|
if (fwd->connect_path == NULL && |
2199 |
|
|
fwd->listen_path == NULL) |
2200 |
|
|
goto fail_free; |
2201 |
|
|
} |
2202 |
|
|
if (fwd->connect_port <= 0 && fwd->connect_path == NULL) |
2203 |
|
|
goto fail_free; |
2204 |
|
|
} |
2205 |
|
|
|
2206 |
|
|
if ((fwd->listen_port < 0 && fwd->listen_path == NULL) || |
2207 |
|
|
(!remotefwd && fwd->listen_port == 0)) |
2208 |
|
|
goto fail_free; |
2209 |
|
|
if (fwd->connect_host != NULL && |
2210 |
|
|
strlen(fwd->connect_host) >= NI_MAXHOST) |
2211 |
|
|
goto fail_free; |
2212 |
|
|
/* XXX - if connecting to a remote socket, max sun len may not match this host */ |
2213 |
|
|
if (fwd->connect_path != NULL && |
2214 |
|
|
strlen(fwd->connect_path) >= PATH_MAX_SUN) |
2215 |
|
|
goto fail_free; |
2216 |
|
|
if (fwd->listen_host != NULL && |
2217 |
|
|
strlen(fwd->listen_host) >= NI_MAXHOST) |
2218 |
|
|
goto fail_free; |
2219 |
|
|
if (fwd->listen_path != NULL && |
2220 |
|
|
strlen(fwd->listen_path) >= PATH_MAX_SUN) |
2221 |
|
|
goto fail_free; |
2222 |
|
|
|
2223 |
|
|
return (i); |
2224 |
|
|
|
2225 |
|
|
fail_free: |
2226 |
|
|
free(fwd->connect_host); |
2227 |
|
|
fwd->connect_host = NULL; |
2228 |
|
|
free(fwd->connect_path); |
2229 |
|
|
fwd->connect_path = NULL; |
2230 |
|
|
free(fwd->listen_host); |
2231 |
|
|
fwd->listen_host = NULL; |
2232 |
|
|
free(fwd->listen_path); |
2233 |
|
|
fwd->listen_path = NULL; |
2234 |
|
|
return (0); |
2235 |
|
|
} |
2236 |
|
|
|
2237 |
|
|
int |
2238 |
|
|
parse_jump(const char *s, Options *o, int active) |
2239 |
|
|
{ |
2240 |
|
|
char *orig, *sdup, *cp; |
2241 |
|
|
char *host = NULL, *user = NULL; |
2242 |
|
|
int ret = -1, port = -1, first; |
2243 |
|
|
|
2244 |
|
|
active &= o->proxy_command == NULL && o->jump_host == NULL; |
2245 |
|
|
|
2246 |
|
|
orig = sdup = xstrdup(s); |
2247 |
|
|
first = active; |
2248 |
|
|
do { |
2249 |
|
|
if ((cp = strrchr(sdup, ',')) == NULL) |
2250 |
|
|
cp = sdup; /* last */ |
2251 |
|
|
else |
2252 |
|
|
*cp++ = '\0'; |
2253 |
|
|
|
2254 |
|
|
if (first) { |
2255 |
|
|
/* First argument and configuration is active */ |
2256 |
|
|
if (parse_ssh_uri(cp, &user, &host, &port) == -1 || |
2257 |
|
|
parse_user_host_port(cp, &user, &host, &port) != 0) |
2258 |
|
|
goto out; |
2259 |
|
|
} else { |
2260 |
|
|
/* Subsequent argument or inactive configuration */ |
2261 |
|
|
if (parse_ssh_uri(cp, NULL, NULL, NULL) == -1 || |
2262 |
|
|
parse_user_host_port(cp, NULL, NULL, NULL) != 0) |
2263 |
|
|
goto out; |
2264 |
|
|
} |
2265 |
|
|
first = 0; /* only check syntax for subsequent hosts */ |
2266 |
|
|
} while (cp != sdup); |
2267 |
|
|
/* success */ |
2268 |
|
|
if (active) { |
2269 |
|
|
o->jump_user = user; |
2270 |
|
|
o->jump_host = host; |
2271 |
|
|
o->jump_port = port; |
2272 |
|
|
o->proxy_command = xstrdup("none"); |
2273 |
|
|
user = host = NULL; |
2274 |
|
|
if ((cp = strrchr(s, ',')) != NULL && cp != s) { |
2275 |
|
|
o->jump_extra = xstrdup(s); |
2276 |
|
|
o->jump_extra[cp - s] = '\0'; |
2277 |
|
|
} |
2278 |
|
|
} |
2279 |
|
|
ret = 0; |
2280 |
|
|
out: |
2281 |
|
|
free(orig); |
2282 |
|
|
free(user); |
2283 |
|
|
free(host); |
2284 |
|
|
return ret; |
2285 |
|
|
} |
2286 |
|
|
|
2287 |
|
|
int |
2288 |
|
|
parse_ssh_uri(const char *uri, char **userp, char **hostp, int *portp) |
2289 |
|
|
{ |
2290 |
|
|
char *path; |
2291 |
|
|
int r; |
2292 |
|
|
|
2293 |
|
|
r = parse_uri("ssh", uri, userp, hostp, portp, &path); |
2294 |
|
|
if (r == 0 && path != NULL) |
2295 |
|
|
r = -1; /* path not allowed */ |
2296 |
|
|
return r; |
2297 |
|
|
} |
2298 |
|
|
|
2299 |
|
|
/* XXX the following is a near-vebatim copy from servconf.c; refactor */ |
2300 |
|
|
static const char * |
2301 |
|
|
fmt_multistate_int(int val, const struct multistate *m) |
2302 |
|
|
{ |
2303 |
|
|
u_int i; |
2304 |
|
|
|
2305 |
|
|
for (i = 0; m[i].key != NULL; i++) { |
2306 |
|
|
if (m[i].value == val) |
2307 |
|
|
return m[i].key; |
2308 |
|
|
} |
2309 |
|
|
return "UNKNOWN"; |
2310 |
|
|
} |
2311 |
|
|
|
2312 |
|
|
static const char * |
2313 |
|
|
fmt_intarg(OpCodes code, int val) |
2314 |
|
|
{ |
2315 |
|
|
if (val == -1) |
2316 |
|
|
return "unset"; |
2317 |
|
|
switch (code) { |
2318 |
|
|
case oAddressFamily: |
2319 |
|
|
return fmt_multistate_int(val, multistate_addressfamily); |
2320 |
|
|
case oVerifyHostKeyDNS: |
2321 |
|
|
case oUpdateHostkeys: |
2322 |
|
|
return fmt_multistate_int(val, multistate_yesnoask); |
2323 |
|
|
case oStrictHostKeyChecking: |
2324 |
|
|
return fmt_multistate_int(val, multistate_strict_hostkey); |
2325 |
|
|
case oControlMaster: |
2326 |
|
|
return fmt_multistate_int(val, multistate_controlmaster); |
2327 |
|
|
case oTunnel: |
2328 |
|
|
return fmt_multistate_int(val, multistate_tunnel); |
2329 |
|
|
case oRequestTTY: |
2330 |
|
|
return fmt_multistate_int(val, multistate_requesttty); |
2331 |
|
|
case oCanonicalizeHostname: |
2332 |
|
|
return fmt_multistate_int(val, multistate_canonicalizehostname); |
2333 |
|
|
case oFingerprintHash: |
2334 |
|
|
return ssh_digest_alg_name(val); |
2335 |
|
|
default: |
2336 |
|
|
switch (val) { |
2337 |
|
|
case 0: |
2338 |
|
|
return "no"; |
2339 |
|
|
case 1: |
2340 |
|
|
return "yes"; |
2341 |
|
|
default: |
2342 |
|
|
return "UNKNOWN"; |
2343 |
|
|
} |
2344 |
|
|
} |
2345 |
|
|
} |
2346 |
|
|
|
2347 |
|
|
static const char * |
2348 |
|
|
lookup_opcode_name(OpCodes code) |
2349 |
|
|
{ |
2350 |
|
|
u_int i; |
2351 |
|
|
|
2352 |
|
|
for (i = 0; keywords[i].name != NULL; i++) |
2353 |
|
|
if (keywords[i].opcode == code) |
2354 |
|
|
return(keywords[i].name); |
2355 |
|
|
return "UNKNOWN"; |
2356 |
|
|
} |
2357 |
|
|
|
2358 |
|
|
static void |
2359 |
|
|
dump_cfg_int(OpCodes code, int val) |
2360 |
|
|
{ |
2361 |
|
|
printf("%s %d\n", lookup_opcode_name(code), val); |
2362 |
|
|
} |
2363 |
|
|
|
2364 |
|
|
static void |
2365 |
|
|
dump_cfg_fmtint(OpCodes code, int val) |
2366 |
|
|
{ |
2367 |
|
|
printf("%s %s\n", lookup_opcode_name(code), fmt_intarg(code, val)); |
2368 |
|
|
} |
2369 |
|
|
|
2370 |
|
|
static void |
2371 |
|
|
dump_cfg_string(OpCodes code, const char *val) |
2372 |
|
|
{ |
2373 |
|
|
if (val == NULL) |
2374 |
|
|
return; |
2375 |
|
|
printf("%s %s\n", lookup_opcode_name(code), val); |
2376 |
|
|
} |
2377 |
|
|
|
2378 |
|
|
static void |
2379 |
|
|
dump_cfg_strarray(OpCodes code, u_int count, char **vals) |
2380 |
|
|
{ |
2381 |
|
|
u_int i; |
2382 |
|
|
|
2383 |
|
|
for (i = 0; i < count; i++) |
2384 |
|
|
printf("%s %s\n", lookup_opcode_name(code), vals[i]); |
2385 |
|
|
} |
2386 |
|
|
|
2387 |
|
|
static void |
2388 |
|
|
dump_cfg_strarray_oneline(OpCodes code, u_int count, char **vals) |
2389 |
|
|
{ |
2390 |
|
|
u_int i; |
2391 |
|
|
|
2392 |
|
|
printf("%s", lookup_opcode_name(code)); |
2393 |
|
|
for (i = 0; i < count; i++) |
2394 |
|
|
printf(" %s", vals[i]); |
2395 |
|
|
printf("\n"); |
2396 |
|
|
} |
2397 |
|
|
|
2398 |
|
|
static void |
2399 |
|
|
dump_cfg_forwards(OpCodes code, u_int count, const struct Forward *fwds) |
2400 |
|
|
{ |
2401 |
|
|
const struct Forward *fwd; |
2402 |
|
|
u_int i; |
2403 |
|
|
|
2404 |
|
|
/* oDynamicForward */ |
2405 |
|
|
for (i = 0; i < count; i++) { |
2406 |
|
|
fwd = &fwds[i]; |
2407 |
|
|
if (code == oDynamicForward && fwd->connect_host != NULL && |
2408 |
|
|
strcmp(fwd->connect_host, "socks") != 0) |
2409 |
|
|
continue; |
2410 |
|
|
if (code == oLocalForward && fwd->connect_host != NULL && |
2411 |
|
|
strcmp(fwd->connect_host, "socks") == 0) |
2412 |
|
|
continue; |
2413 |
|
|
printf("%s", lookup_opcode_name(code)); |
2414 |
|
|
if (fwd->listen_port == PORT_STREAMLOCAL) |
2415 |
|
|
printf(" %s", fwd->listen_path); |
2416 |
|
|
else if (fwd->listen_host == NULL) |
2417 |
|
|
printf(" %d", fwd->listen_port); |
2418 |
|
|
else { |
2419 |
|
|
printf(" [%s]:%d", |
2420 |
|
|
fwd->listen_host, fwd->listen_port); |
2421 |
|
|
} |
2422 |
|
|
if (code != oDynamicForward) { |
2423 |
|
|
if (fwd->connect_port == PORT_STREAMLOCAL) |
2424 |
|
|
printf(" %s", fwd->connect_path); |
2425 |
|
|
else if (fwd->connect_host == NULL) |
2426 |
|
|
printf(" %d", fwd->connect_port); |
2427 |
|
|
else { |
2428 |
|
|
printf(" [%s]:%d", |
2429 |
|
|
fwd->connect_host, fwd->connect_port); |
2430 |
|
|
} |
2431 |
|
|
} |
2432 |
|
|
printf("\n"); |
2433 |
|
|
} |
2434 |
|
|
} |
2435 |
|
|
|
2436 |
|
|
void |
2437 |
|
|
dump_client_config(Options *o, const char *host) |
2438 |
|
|
{ |
2439 |
|
|
int i; |
2440 |
|
|
char buf[8]; |
2441 |
|
|
|
2442 |
|
|
/* This is normally prepared in ssh_kex2 */ |
2443 |
|
|
if (kex_assemble_names(KEX_DEFAULT_PK_ALG, &o->hostkeyalgorithms) != 0) |
2444 |
|
|
fatal("%s: kex_assemble_names failed", __func__); |
2445 |
|
|
|
2446 |
|
|
/* Most interesting options first: user, host, port */ |
2447 |
|
|
dump_cfg_string(oUser, o->user); |
2448 |
|
|
dump_cfg_string(oHostName, host); |
2449 |
|
|
dump_cfg_int(oPort, o->port); |
2450 |
|
|
|
2451 |
|
|
/* Flag options */ |
2452 |
|
|
dump_cfg_fmtint(oAddressFamily, o->address_family); |
2453 |
|
|
dump_cfg_fmtint(oBatchMode, o->batch_mode); |
2454 |
|
|
dump_cfg_fmtint(oCanonicalizeFallbackLocal, o->canonicalize_fallback_local); |
2455 |
|
|
dump_cfg_fmtint(oCanonicalizeHostname, o->canonicalize_hostname); |
2456 |
|
|
dump_cfg_fmtint(oChallengeResponseAuthentication, o->challenge_response_authentication); |
2457 |
|
|
dump_cfg_fmtint(oCheckHostIP, o->check_host_ip); |
2458 |
|
|
dump_cfg_fmtint(oCompression, o->compression); |
2459 |
|
|
dump_cfg_fmtint(oControlMaster, o->control_master); |
2460 |
|
|
dump_cfg_fmtint(oEnableSSHKeysign, o->enable_ssh_keysign); |
2461 |
|
|
dump_cfg_fmtint(oClearAllForwardings, o->clear_forwardings); |
2462 |
|
|
dump_cfg_fmtint(oExitOnForwardFailure, o->exit_on_forward_failure); |
2463 |
|
|
dump_cfg_fmtint(oFingerprintHash, o->fingerprint_hash); |
2464 |
|
|
dump_cfg_fmtint(oForwardAgent, o->forward_agent); |
2465 |
|
|
dump_cfg_fmtint(oForwardX11, o->forward_x11); |
2466 |
|
|
dump_cfg_fmtint(oForwardX11Trusted, o->forward_x11_trusted); |
2467 |
|
|
dump_cfg_fmtint(oGatewayPorts, o->fwd_opts.gateway_ports); |
2468 |
|
|
#ifdef GSSAPI |
2469 |
|
|
dump_cfg_fmtint(oGssAuthentication, o->gss_authentication); |
2470 |
|
|
dump_cfg_fmtint(oGssDelegateCreds, o->gss_deleg_creds); |
2471 |
|
|
#endif /* GSSAPI */ |
2472 |
|
|
dump_cfg_fmtint(oHashKnownHosts, o->hash_known_hosts); |
2473 |
|
|
dump_cfg_fmtint(oHostbasedAuthentication, o->hostbased_authentication); |
2474 |
|
|
dump_cfg_fmtint(oIdentitiesOnly, o->identities_only); |
2475 |
|
|
dump_cfg_fmtint(oKbdInteractiveAuthentication, o->kbd_interactive_authentication); |
2476 |
|
|
dump_cfg_fmtint(oNoHostAuthenticationForLocalhost, o->no_host_authentication_for_localhost); |
2477 |
|
|
dump_cfg_fmtint(oPasswordAuthentication, o->password_authentication); |
2478 |
|
|
dump_cfg_fmtint(oPermitLocalCommand, o->permit_local_command); |
2479 |
|
|
dump_cfg_fmtint(oProxyUseFdpass, o->proxy_use_fdpass); |
2480 |
|
|
dump_cfg_fmtint(oPubkeyAuthentication, o->pubkey_authentication); |
2481 |
|
|
dump_cfg_fmtint(oRequestTTY, o->request_tty); |
2482 |
|
|
dump_cfg_fmtint(oStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); |
2483 |
|
|
dump_cfg_fmtint(oStrictHostKeyChecking, o->strict_host_key_checking); |
2484 |
|
|
dump_cfg_fmtint(oTCPKeepAlive, o->tcp_keep_alive); |
2485 |
|
|
dump_cfg_fmtint(oTunnel, o->tun_open); |
2486 |
|
|
dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port); |
2487 |
|
|
dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns); |
2488 |
|
|
dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); |
2489 |
|
|
dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys); |
2490 |
|
|
|
2491 |
|
|
/* Integer options */ |
2492 |
|
|
dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots); |
2493 |
|
|
dump_cfg_int(oConnectionAttempts, o->connection_attempts); |
2494 |
|
|
dump_cfg_int(oForwardX11Timeout, o->forward_x11_timeout); |
2495 |
|
|
dump_cfg_int(oNumberOfPasswordPrompts, o->number_of_password_prompts); |
2496 |
|
|
dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max); |
2497 |
|
|
dump_cfg_int(oServerAliveInterval, o->server_alive_interval); |
2498 |
|
|
|
2499 |
|
|
/* String options */ |
2500 |
|
|
dump_cfg_string(oBindAddress, o->bind_address); |
2501 |
|
|
dump_cfg_string(oCiphers, o->ciphers ? o->ciphers : KEX_CLIENT_ENCRYPT); |
2502 |
|
|
dump_cfg_string(oControlPath, o->control_path); |
2503 |
|
|
dump_cfg_string(oHostKeyAlgorithms, o->hostkeyalgorithms); |
2504 |
|
|
dump_cfg_string(oHostKeyAlias, o->host_key_alias); |
2505 |
|
|
dump_cfg_string(oHostbasedKeyTypes, o->hostbased_key_types); |
2506 |
|
|
dump_cfg_string(oIdentityAgent, o->identity_agent); |
2507 |
|
|
dump_cfg_string(oKbdInteractiveDevices, o->kbd_interactive_devices); |
2508 |
|
|
dump_cfg_string(oKexAlgorithms, o->kex_algorithms ? o->kex_algorithms : KEX_CLIENT_KEX); |
2509 |
|
|
dump_cfg_string(oLocalCommand, o->local_command); |
2510 |
|
|
dump_cfg_string(oRemoteCommand, o->remote_command); |
2511 |
|
|
dump_cfg_string(oLogLevel, log_level_name(o->log_level)); |
2512 |
|
|
dump_cfg_string(oMacs, o->macs ? o->macs : KEX_CLIENT_MAC); |
2513 |
|
|
#ifdef ENABLE_PKCS11 |
2514 |
|
|
dump_cfg_string(oPKCS11Provider, o->pkcs11_provider); |
2515 |
|
|
#endif |
2516 |
|
|
dump_cfg_string(oPreferredAuthentications, o->preferred_authentications); |
2517 |
|
|
dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types); |
2518 |
|
|
dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys); |
2519 |
|
|
dump_cfg_string(oXAuthLocation, o->xauth_location); |
2520 |
|
|
|
2521 |
|
|
/* Forwards */ |
2522 |
|
|
dump_cfg_forwards(oDynamicForward, o->num_local_forwards, o->local_forwards); |
2523 |
|
|
dump_cfg_forwards(oLocalForward, o->num_local_forwards, o->local_forwards); |
2524 |
|
|
dump_cfg_forwards(oRemoteForward, o->num_remote_forwards, o->remote_forwards); |
2525 |
|
|
|
2526 |
|
|
/* String array options */ |
2527 |
|
|
dump_cfg_strarray(oIdentityFile, o->num_identity_files, o->identity_files); |
2528 |
|
|
dump_cfg_strarray_oneline(oCanonicalDomains, o->num_canonical_domains, o->canonical_domains); |
2529 |
|
|
dump_cfg_strarray_oneline(oGlobalKnownHostsFile, o->num_system_hostfiles, o->system_hostfiles); |
2530 |
|
|
dump_cfg_strarray_oneline(oUserKnownHostsFile, o->num_user_hostfiles, o->user_hostfiles); |
2531 |
|
|
dump_cfg_strarray(oSendEnv, o->num_send_env, o->send_env); |
2532 |
|
|
|
2533 |
|
|
/* Special cases */ |
2534 |
|
|
|
2535 |
|
|
/* oConnectTimeout */ |
2536 |
|
|
if (o->connection_timeout == -1) |
2537 |
|
|
printf("connecttimeout none\n"); |
2538 |
|
|
else |
2539 |
|
|
dump_cfg_int(oConnectTimeout, o->connection_timeout); |
2540 |
|
|
|
2541 |
|
|
/* oTunnelDevice */ |
2542 |
|
|
printf("tunneldevice"); |
2543 |
|
|
if (o->tun_local == SSH_TUNID_ANY) |
2544 |
|
|
printf(" any"); |
2545 |
|
|
else |
2546 |
|
|
printf(" %d", o->tun_local); |
2547 |
|
|
if (o->tun_remote == SSH_TUNID_ANY) |
2548 |
|
|
printf(":any"); |
2549 |
|
|
else |
2550 |
|
|
printf(":%d", o->tun_remote); |
2551 |
|
|
printf("\n"); |
2552 |
|
|
|
2553 |
|
|
/* oCanonicalizePermittedCNAMEs */ |
2554 |
|
|
if ( o->num_permitted_cnames > 0) { |
2555 |
|
|
printf("canonicalizePermittedcnames"); |
2556 |
|
|
for (i = 0; i < o->num_permitted_cnames; i++) { |
2557 |
|
|
printf(" %s:%s", o->permitted_cnames[i].source_list, |
2558 |
|
|
o->permitted_cnames[i].target_list); |
2559 |
|
|
} |
2560 |
|
|
printf("\n"); |
2561 |
|
|
} |
2562 |
|
|
|
2563 |
|
|
/* oControlPersist */ |
2564 |
|
|
if (o->control_persist == 0 || o->control_persist_timeout == 0) |
2565 |
|
|
dump_cfg_fmtint(oControlPersist, o->control_persist); |
2566 |
|
|
else |
2567 |
|
|
dump_cfg_int(oControlPersist, o->control_persist_timeout); |
2568 |
|
|
|
2569 |
|
|
/* oEscapeChar */ |
2570 |
|
|
if (o->escape_char == SSH_ESCAPECHAR_NONE) |
2571 |
|
|
printf("escapechar none\n"); |
2572 |
|
|
else { |
2573 |
|
|
vis(buf, o->escape_char, VIS_WHITE, 0); |
2574 |
|
|
printf("escapechar %s\n", buf); |
2575 |
|
|
} |
2576 |
|
|
|
2577 |
|
|
/* oIPQoS */ |
2578 |
|
|
printf("ipqos %s ", iptos2str(o->ip_qos_interactive)); |
2579 |
|
|
printf("%s\n", iptos2str(o->ip_qos_bulk)); |
2580 |
|
|
|
2581 |
|
|
/* oRekeyLimit */ |
2582 |
|
|
printf("rekeylimit %llu %d\n", |
2583 |
|
|
(unsigned long long)o->rekey_limit, o->rekey_interval); |
2584 |
|
|
|
2585 |
|
|
/* oStreamLocalBindMask */ |
2586 |
|
|
printf("streamlocalbindmask 0%o\n", |
2587 |
|
|
o->fwd_opts.streamlocal_bind_mask); |
2588 |
|
|
|
2589 |
|
|
/* oProxyCommand / oProxyJump */ |
2590 |
|
|
if (o->jump_host == NULL) |
2591 |
|
|
dump_cfg_string(oProxyCommand, o->proxy_command); |
2592 |
|
|
else { |
2593 |
|
|
/* Check for numeric addresses */ |
2594 |
|
|
i = strchr(o->jump_host, ':') != NULL || |
2595 |
|
|
strspn(o->jump_host, "1234567890.") == strlen(o->jump_host); |
2596 |
|
|
snprintf(buf, sizeof(buf), "%d", o->jump_port); |
2597 |
|
|
printf("proxyjump %s%s%s%s%s%s%s%s%s\n", |
2598 |
|
|
/* optional additional jump spec */ |
2599 |
|
|
o->jump_extra == NULL ? "" : o->jump_extra, |
2600 |
|
|
o->jump_extra == NULL ? "" : ",", |
2601 |
|
|
/* optional user */ |
2602 |
|
|
o->jump_user == NULL ? "" : o->jump_user, |
2603 |
|
|
o->jump_user == NULL ? "" : "@", |
2604 |
|
|
/* opening [ if hostname is numeric */ |
2605 |
|
|
i ? "[" : "", |
2606 |
|
|
/* mandatory hostname */ |
2607 |
|
|
o->jump_host, |
2608 |
|
|
/* closing ] if hostname is numeric */ |
2609 |
|
|
i ? "]" : "", |
2610 |
|
|
/* optional port number */ |
2611 |
|
|
o->jump_port <= 0 ? "" : ":", |
2612 |
|
|
o->jump_port <= 0 ? "" : buf); |
2613 |
|
|
} |
2614 |
|
|
} |