]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ip/Intercept.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / ip / Intercept.cc
CommitLineData
c8be6d7b 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
c8be6d7b 3 *
bbc27441
AJ
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
c8be6d7b 7 */
bbc27441
AJ
8
9/* DEBUG: section 89 NAT / IP Interception */
10
d25bbe26
AJ
11// Enable hack to workaround Solaris 10 IPFilter breakage
12#define BUILDING_SQUID_IP_INTERCEPT_CC 1
13
f7f3304a 14#include "squid.h"
40d34a62 15#include "comm/Connection.h"
b3166404 16#include "fde.h"
602d9612 17#include "ip/Intercept.h"
5c1be108 18#include "src/tools.h"
fc27cd70 19
1a30fdf5
AJ
20#include <cerrno>
21
c8be6d7b 22#if IPF_TRANSPARENT
34ec5c62 23
d25bbe26
AJ
24#if !defined(IPFILTER_VERSION)
25#define IPFILTER_VERSION 5000004
26#endif
27
4e3e244d
TK
28#if HAVE_SYS_PARAM_H
29#include <sys/param.h>
30#endif
d25bbe26
AJ
31#if HAVE_SYS_IOCCOM_H
32#include <sys/ioccom.h>
33#endif
c8be6d7b 34#if HAVE_SYS_IOCTL_H
35#include <sys/ioctl.h>
36#endif
d25bbe26
AJ
37#if HAVE_NETINET_IP6_H
38#include <netinet/ip6.h>
39#endif
e9e7c285 40#if HAVE_NETINET_TCP_H
c8be6d7b 41#include <netinet/tcp.h>
e9e7c285 42#endif
43#if HAVE_NET_IF_H
c8be6d7b 44#include <net/if.h>
e9e7c285 45#endif
b0dfd10b 46#if HAVE_IPL_H
dbc5782a 47#include <ipl.h>
48#elif HAVE_NETINET_IPL_H
49#include <netinet/ipl.h>
50#endif
d25bbe26
AJ
51#if USE_SOLARIS_IPFILTER_MINOR_T_HACK
52#undef minor_t
53#endif
c8be6d7b 54#if HAVE_IP_FIL_COMPAT_H
55#include <ip_fil_compat.h>
56#elif HAVE_NETINET_IP_FIL_COMPAT_H
57#include <netinet/ip_fil_compat.h>
58#elif HAVE_IP_COMPAT_H
59#include <ip_compat.h>
60#elif HAVE_NETINET_IP_COMPAT_H
61#include <netinet/ip_compat.h>
62#endif
63#if HAVE_IP_FIL_H
64#include <ip_fil.h>
65#elif HAVE_NETINET_IP_FIL_H
66#include <netinet/ip_fil.h>
67#endif
68#if HAVE_IP_NAT_H
69#include <ip_nat.h>
70#elif HAVE_NETINET_IP_NAT_H
71#include <netinet/ip_nat.h>
72#endif
34ec5c62
AJ
73
74#endif /* IPF_TRANSPARENT required headers */
c8be6d7b 75
76#if PF_TRANSPARENT
c8be6d7b 77#include <sys/socket.h>
78#include <sys/ioctl.h>
79#include <sys/fcntl.h>
80#include <net/if.h>
81#include <netinet/in.h>
b0dfd10b 82#if HAVE_NET_PF_PFVAR_H
ec9909b0
AJ
83#include <net/pf/pfvar.h>
84#endif /* HAVE_NET_PF_PFVAR_H */
b0dfd10b 85#if HAVE_NET_PFVAR_H
c8be6d7b 86#include <net/pfvar.h>
ec9909b0 87#endif /* HAVE_NET_PFVAR_H */
34ec5c62 88#endif /* PF_TRANSPARENT required headers */
5afac208
AJ
89
90#if LINUX_NETFILTER
074d6a40
AJ
91/* <climits> must be before including netfilter_ipv4.h */
92#include <climits>
ee80a919 93#include <linux/if.h>
c8be6d7b 94#include <linux/netfilter_ipv4.h>
ee80a919
AR
95#if HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
96/* 2013-07-01: Pablo the Netfilter maintainer is rejecting patches
97 * which will enable C++ compilers to build the Netfilter public headers.
98 * We can auto-detect its presence and attempt to use in case he ever
99 * changes his mind or things get cleaned up some other way.
100 * But until then are usually forced to hard-code the getsockopt() code
101 * for IPv6 NAT lookups.
102 */
103#include <linux/netfilter_ipv6/ip6_tables.h>
104#endif
105#if !defined(IP6T_SO_ORIGINAL_DST)
f53969cc 106#define IP6T_SO_ORIGINAL_DST 80 // stolen with prejudice from the above file.
ee80a919 107#endif
5afac208 108#endif /* LINUX_NETFILTER required headers */
c8be6d7b 109
0fc2952e 110// single global instance for access by other components.
b7ac5457 111Ip::Intercept Ip::Interceptor;
0fc2952e 112
04f87469 113void
b7ac5457 114Ip::Intercept::StopTransparency(const char *str)
26ac0430 115{
40d34a62 116 if (transparentActive_) {
788625af 117 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
40d34a62 118 transparentActive_ = 0;
788625af 119 }
04f87469
AJ
120}
121
122void
b7ac5457 123Ip::Intercept::StopInterception(const char *str)
26ac0430 124{
40d34a62 125 if (interceptActive_) {
788625af 126 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
40d34a62 127 interceptActive_ = 0;
788625af 128 }
04f87469 129}
0fc2952e 130
40d34a62
AJ
131bool
132Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
7b0a0d1f
AJ
133{
134#if LINUX_NETFILTER
ee80a919
AR
135 struct sockaddr_storage lookup;
136 socklen_t len = newConn->local.isIPv6() ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
137 newConn->local.getSockAddr(lookup, AF_UNSPEC);
7b0a0d1f
AJ
138
139 /** \par
140 * Try NAT lookup for REDIRECT or DNAT targets. */
ee80a919 141 if ( getsockopt(newConn->fd,
fa0b1a25
A
142 newConn->local.isIPv6() ? IPPROTO_IPV6 : IPPROTO_IP,
143 newConn->local.isIPv6() ? IP6T_SO_ORIGINAL_DST : SO_ORIGINAL_DST,
144 &lookup,
145 &len) != 0) {
26ac0430 146 if (!silent) {
b69e9ffa
AJ
147 int xerrno = errno;
148 debugs(89, DBG_IMPORTANT, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn << ": " << xstrerr(xerrno));
40d34a62 149 lastReported_ = squid_curtime;
7b0a0d1f 150 }
ee80a919 151 debugs(89, 9, "address: " << newConn);
40d34a62 152 return false;
26ac0430 153 } else {
40d34a62 154 newConn->local = lookup;
ee80a919 155 debugs(89, 5, "address NAT: " << newConn);
40d34a62 156 return true;
7b0a0d1f 157 }
7b0a0d1f 158#endif
40d34a62 159 return false;
7b0a0d1f
AJ
160}
161
40d34a62 162bool
ced8def3 163Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer &newConn, int)
7b0a0d1f 164{
b2192042
AJ
165#if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
166 (PF_TRANSPARENT && defined(SO_BINDANY)) || \
167 (IPFW_TRANSPARENT && defined(IP_BINDANY))
168
acaa7194
AJ
169 /* Trust the user configured properly. If not no harm done.
170 * We will simply attempt a bind outgoing on our own IP.
acaa7194 171 */
40d34a62
AJ
172 debugs(89, 5, HERE << "address TPROXY: " << newConn);
173 return true;
174#else
175 return false;
acaa7194 176#endif
7b0a0d1f
AJ
177}
178
40d34a62 179bool
ced8def3 180Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int)
d8b5bcbc
AJ
181{
182#if IPFW_TRANSPARENT
b2192042
AJ
183 /* The getsockname() call performed already provided the TCP packet details.
184 * There is no way to identify whether they came from NAT or not.
185 * Trust the user configured properly.
186 */
187 debugs(89, 5, HERE << "address NAT: " << newConn);
188 return true;
189#else
40d34a62 190 return false;
b2192042 191#endif
d8b5bcbc 192}
0fc2952e 193
40d34a62
AJ
194bool
195Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
3f38a55e 196{
f1e0717c 197#if IPF_TRANSPARENT /* --enable-ipf-transparent */
62e76326 198
c8be6d7b 199 struct natlookup natLookup;
200 static int natfd = -1;
c8be6d7b 201 int x;
3f38a55e 202
c74be4ce 203 // all fields must be set to 0
dcfb239f 204 memset(&natLookup, 0, sizeof(natLookup));
c74be4ce 205 // for NAT lookup set local and remote IP:port's
b2fb6c8a 206 if (newConn->remote.isIPv6()) {
4eaf432d 207#if HAVE_NATLOOKUP_NL_INIPADDR_IN6
b2fb6c8a 208 natLookup.nl_v = 6;
04e81e71
AJ
209 newConn->local.getInAddr(natLookup.nl_inipaddr.in6);
210 newConn->remote.getInAddr(natLookup.nl_outipaddr.in6);
8ea96604
SM
211 }
212 else {
b2fb6c8a 213 natLookup.nl_v = 4;
04e81e71
AJ
214 newConn->local.getInAddr(natLookup.nl_inipaddr.in4);
215 newConn->remote.getInAddr(natLookup.nl_outipaddr.in4);
b2fb6c8a 216 }
4eaf432d
AJ
217#else
218 // warn once every 10 at critical level, then push down a level each repeated event
219 static int warningLevel = DBG_CRITICAL;
220 debugs(89, warningLevel, "Your IPF (IPFilter) NAT does not support IPv6. Please upgrade it.");
221 warningLevel = (warningLevel + 1) % 10;
222 return false;
223 }
224 newConn->local.getInAddr(natLookup.nl_inip);
225 newConn->remote.getInAddr(natLookup.nl_outip);
04e81e71 226#endif
4dd643d5 227 natLookup.nl_inport = htons(newConn->local.port());
4dd643d5 228 natLookup.nl_outport = htons(newConn->remote.port());
c74be4ce 229 // ... and the TCP flag
c8be6d7b 230 natLookup.nl_flags = IPN_TCP;
62e76326 231
26ac0430 232 if (natfd < 0) {
62e76326 233 int save_errno;
234 enter_suid();
dbc5782a 235#ifdef IPNAT_NAME
dbc5782a 236 natfd = open(IPNAT_NAME, O_RDONLY, 0);
98a3bc99 237#else
62e76326 238 natfd = open(IPL_NAT, O_RDONLY, 0);
98a3bc99 239#endif
62e76326 240 save_errno = errno;
241 leave_suid();
242 errno = save_errno;
c8be6d7b 243 }
62e76326 244
26ac0430 245 if (natfd < 0) {
219f8edb 246 if (!silent) {
b69e9ffa
AJ
247 int xerrno = errno;
248 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerr(xerrno));
40d34a62
AJ
249 lastReported_ = squid_curtime;
250 return false;
d6026916 251 }
c8be6d7b 252 }
62e76326 253
dbc5782a 254#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
c74be4ce
AJ
255 struct ipfobj obj;
256 memset(&obj, 0, sizeof(obj));
257 obj.ipfo_rev = IPFILTER_VERSION;
258 obj.ipfo_size = sizeof(natLookup);
259 obj.ipfo_ptr = &natLookup;
260 obj.ipfo_type = IPFOBJ_NATLOOKUP;
261
dbc5782a 262 x = ioctl(natfd, SIOCGNATL, &obj);
dbc5782a 263#else
3f38a55e 264 /*
dbc5782a 265 * IP-Filter changed the type for SIOCGNATL between
266 * 3.3 and 3.4. It also changed the cmd value for
267 * SIOCGNATL, so at least we can detect it. We could
268 * put something in configure and use ifdefs here, but
269 * this seems simpler.
270 */
c74be4ce 271 static int siocgnatl_cmd = SIOCGNATL & 0xff;
26ac0430 272 if (63 == siocgnatl_cmd) {
62e76326 273 struct natlookup *nlp = &natLookup;
274 x = ioctl(natfd, SIOCGNATL, &nlp);
26ac0430 275 } else {
62e76326 276 x = ioctl(natfd, SIOCGNATL, &natLookup);
277 }
278
dbc5782a 279#endif
26ac0430 280 if (x < 0) {
b69e9ffa
AJ
281 int xerrno = errno;
282 if (xerrno != ESRCH) {
219f8edb 283 if (!silent) {
b69e9ffa 284 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerr(xerrno));
40d34a62 285 lastReported_ = squid_curtime;
d6026916 286 }
287
62e76326 288 close(natfd);
289 natfd = -1;
290 }
291
40d34a62
AJ
292 debugs(89, 9, HERE << "address: " << newConn);
293 return false;
26ac0430 294 } else {
4eaf432d 295#if HAVE_NATLOOKUP_NL_REALIPADDR_IN6
04e81e71
AJ
296 if (newConn->remote.isIPv6())
297 newConn->local = natLookup.nl_realipaddr.in6;
298 else
299 newConn->local = natLookup.nl_realipaddr.in4;
4eaf432d
AJ
300#else
301 newConn->local = natLookup.nl_realip;
04e81e71 302#endif
4dd643d5 303 newConn->local.port(ntohs(natLookup.nl_realport));
40d34a62
AJ
304 debugs(89, 5, HERE << "address NAT: " << newConn);
305 return true;
c8be6d7b 306 }
62e76326 307
219f8edb 308#endif /* --enable-ipf-transparent */
40d34a62 309 return false;
219f8edb 310}
f1e0717c 311
1125ea7b 312bool
b2192042 313Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
1125ea7b 314{
b2192042
AJ
315#if PF_TRANSPARENT /* --enable-pf-transparent */
316
317#if !USE_NAT_DEVPF
318 /* On recent PF versions the getsockname() call performed already provided
319 * the required TCP packet details.
320 * There is no way to identify whether they came from NAT or not.
321 *
322 * Trust the user configured properly.
1125ea7b 323 */
b2192042 324 debugs(89, 5, HERE << "address NAT divert-to: " << newConn);
1125ea7b 325 return true;
1125ea7b 326
b2192042 327#else /* USE_NAT_DEVPF / --with-nat-devpf */
62e76326 328
3f38a55e 329 struct pfioc_natlook nl;
330 static int pffd = -1;
62e76326 331
332 if (pffd < 0)
d14c6ef2 333 pffd = open("/dev/pf", O_RDONLY);
62e76326 334
26ac0430 335 if (pffd < 0) {
51f4d36b 336 if (!silent) {
b69e9ffa
AJ
337 int xerrno = errno;
338 debugs(89, DBG_IMPORTANT, MYNAME << "PF open failed: " << xstrerr(xerrno));
40d34a62 339 lastReported_ = squid_curtime;
d6026916 340 }
40d34a62 341 return false;
c8be6d7b 342 }
62e76326 343
c8be6d7b 344 memset(&nl, 0, sizeof(struct pfioc_natlook));
cc192b50 345
5a33064f
EG
346 if (newConn->remote.isIPv6()) {
347 newConn->remote.getInAddr(nl.saddr.v6);
348 newConn->local.getInAddr(nl.daddr.v6);
349 nl.af = AF_INET6;
350 } else {
351 newConn->remote.getInAddr(nl.saddr.v4);
352 newConn->local.getInAddr(nl.daddr.v4);
353 nl.af = AF_INET;
354 }
355
356 nl.sport = htons(newConn->remote.port());
4dd643d5 357 nl.dport = htons(newConn->local.port());
cc192b50 358
c8be6d7b 359 nl.proto = IPPROTO_TCP;
360 nl.direction = PF_OUT;
62e76326 361
26ac0430 362 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
b69e9ffa
AJ
363 int xerrno = errno;
364 if (xerrno != ENOENT) {
51f4d36b 365 if (!silent) {
b69e9ffa 366 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK): " << xstrerr(xerrno));
40d34a62 367 lastReported_ = squid_curtime;
d6026916 368 }
62e76326 369 close(pffd);
370 pffd = -1;
371 }
40d34a62
AJ
372 debugs(89, 9, HERE << "address: " << newConn);
373 return false;
26ac0430 374 } else {
5a33064f
EG
375 if (newConn->remote.isIPv6())
376 newConn->local = nl.rdaddr.v6;
377 else
378 newConn->local = nl.rdaddr.v4;
4dd643d5 379 newConn->local.port(ntohs(nl.rdport));
40d34a62
AJ
380 debugs(89, 5, HERE << "address NAT: " << newConn);
381 return true;
3f38a55e 382 }
b2192042 383#endif /* --with-nat-devpf */
51f4d36b 384#endif /* --enable-pf-transparent */
40d34a62 385 return false;
51f4d36b
AJ
386}
387
40d34a62
AJ
388bool
389Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
51f4d36b 390{
af6a12ee
AJ
391 /* --enable-linux-netfilter */
392 /* --enable-ipfw-transparent */
393 /* --enable-ipf-transparent */
394 /* --enable-pf-transparent */
51f4d36b
AJ
395#if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
396
51f4d36b
AJ
397#if 0
398 // Crop interception errors down to one per minute.
40d34a62 399 int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
51f4d36b
AJ
400#else
401 // Show all interception errors.
402 int silent = 0;
403#endif
404
40d34a62
AJ
405 debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
406
407 newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
e9172f79 408
9bad3e09 409 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
40d34a62 410 if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
b2192042 411 if (TproxyTransparent(newConn, silent)) return true;
9bad3e09
AJ
412 }
413
40d34a62 414 if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
e9172f79 415 /* NAT methods that use sock-opts to return client address */
40d34a62
AJ
416 if (NetfilterInterception(newConn, silent)) return true;
417 if (IpfwInterception(newConn, silent)) return true;
e9172f79
AJ
418
419 /* NAT methods that use ioctl to return client address AND destination address */
40d34a62
AJ
420 if (PfInterception(newConn, silent)) return true;
421 if (IpfInterception(newConn, silent)) return true;
51f4d36b 422 }
f1e0717c 423
51f4d36b 424#else /* none of the transparent options configured */
e9172f79 425 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
3f38a55e 426#endif
427
40d34a62 428 return false;
f1e0717c 429}
34ec5c62 430
263f84f0 431bool
b7ac5457 432Ip::Intercept::ProbeForTproxy(Ip::Address &test)
263f84f0 433{
b2192042
AJ
434 bool doneSuid = false;
435
436#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
437# define soLevel SOL_IP
438# define soFlag IP_TRANSPARENT
439
440#elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
441# define soLevel SOL_SOCKET
442# define soFlag SO_BINDANY
443 enter_suid();
444 doneSuid = true;
445
446#elif defined(IP_BINDANY) // FreeBSD with IPFW
447# define soLevel IPPROTO_IP
448# define soFlag IP_BINDANY
449 enter_suid();
450 doneSuid = true;
451
452#endif
453
454#if defined(soLevel) && defined(soFlag)
455
1125ea7b 456 debugs(3, 3, "Detect TPROXY support on port " << test);
263f84f0
AJ
457
458 int tos = 1;
459 int tmp_sock = -1;
460
263f84f0 461 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
4dd643d5 462 if (test.isIPv6()) {
263f84f0
AJ
463 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
464
465 struct sockaddr_in6 tmp_ip6;
b7ac5457 466 Ip::Address tmp = "::2";
4dd643d5
AJ
467 tmp.port(0);
468 tmp.getSockAddr(tmp_ip6);
263f84f0
AJ
469
470 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
b2192042 471 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
f54f527e 472 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
263f84f0
AJ
473
474 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
fb5bffa3 475 close(tmp_sock);
b2192042
AJ
476 if (doneSuid)
477 leave_suid();
263f84f0
AJ
478 return true;
479 }
480 if (tmp_sock >= 0) {
fb5bffa3 481 close(tmp_sock);
263f84f0
AJ
482 tmp_sock = -1;
483 }
484 }
485
4dd643d5 486 if ( test.isIPv6() && !test.setIPv4() ) {
263f84f0 487 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
b2192042
AJ
488 if (doneSuid)
489 leave_suid();
263f84f0
AJ
490 return false;
491 }
263f84f0
AJ
492
493 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
4dd643d5 494 if (test.isIPv4()) {
263f84f0
AJ
495 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
496
497 struct sockaddr_in tmp_ip4;
b7ac5457 498 Ip::Address tmp = "127.0.0.2";
4dd643d5
AJ
499 tmp.port(0);
500 tmp.getSockAddr(tmp_ip4);
263f84f0
AJ
501
502 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
b2192042 503 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
f54f527e 504 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
263f84f0
AJ
505
506 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
fb5bffa3 507 close(tmp_sock);
b2192042
AJ
508 if (doneSuid)
509 leave_suid();
263f84f0
AJ
510 return true;
511 }
512 if (tmp_sock >= 0) {
fb5bffa3 513 close(tmp_sock);
263f84f0
AJ
514 }
515 }
516
1125ea7b
MM
517#else
518 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
519
263f84f0 520#endif
b2192042
AJ
521 if (doneSuid)
522 leave_suid();
263f84f0
AJ
523 return false;
524}
f53969cc 525