Signed-off-by: Matthias Fischer matthias.fischer@ipfire.org --- lfs/squid | 5 + src/patches/squid/squid-3.5-14114.patch | 46 ++++++++ src/patches/squid/squid-3.5-14115.patch | 197 ++++++++++++++++++++++++++++++++ src/patches/squid/squid-3.5-14116.patch | 38 ++++++ src/patches/squid/squid-3.5-14117.patch | 152 ++++++++++++++++++++++++ src/patches/squid/squid-3.5-14118.patch | 55 +++++++++ 6 files changed, 493 insertions(+) create mode 100644 src/patches/squid/squid-3.5-14114.patch create mode 100644 src/patches/squid/squid-3.5-14115.patch create mode 100644 src/patches/squid/squid-3.5-14116.patch create mode 100644 src/patches/squid/squid-3.5-14117.patch create mode 100644 src/patches/squid/squid-3.5-14118.patch
diff --git a/lfs/squid b/lfs/squid index dbe79cb98..0642532d0 100644 --- a/lfs/squid +++ b/lfs/squid @@ -85,6 +85,11 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14111.patch cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14112.patch cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14113.patch + cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14114.patch + cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14115.patch + cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14116.patch + cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14117.patch + cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid/squid-3.5-14118.patch cd $(DIR_APP) && patch -Np0 -i $(DIR_SRC)/src/patches/squid-3.5.22-fix-max-file-descriptors.patch
cd $(DIR_APP) && autoreconf -vfi diff --git a/src/patches/squid/squid-3.5-14114.patch b/src/patches/squid/squid-3.5-14114.patch new file mode 100644 index 000000000..098500429 --- /dev/null +++ b/src/patches/squid/squid-3.5-14114.patch @@ -0,0 +1,46 @@ +------------------------------------------------------------ +revno: 14114 +revision-id: squid3@treenet.co.nz-20161130154205-c9z1bhqzuh3rafl3 +parent: squid3@treenet.co.nz-20161115075728-2xj2621oh5bwn8wn +committer: Amos Jeffries squid3@treenet.co.nz +branch nick: 3.5 +timestamp: Thu 2016-12-01 04:42:05 +1300 +message: + Improve debugs warnings when loading signing certs fails +------------------------------------------------------------ +# Bazaar merge directive format 2 (Bazaar 0.90) +# revision_id: squid3@treenet.co.nz-20161130154205-c9z1bhqzuh3rafl3 +# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# testament_sha1: e760bf590489a354e314f19dd158b063d23ef7a7 +# timestamp: 2016-11-30 15:51:47 +0000 +# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# base_revision_id: squid3@treenet.co.nz-20161115075728-\ +# 2xj2621oh5bwn8wn +# +# Begin patch +=== modified file 'src/ssl/support.cc' +--- src/ssl/support.cc 2016-10-09 14:30:11 +0000 ++++ src/ssl/support.cc 2016-11-30 15:42:05 +0000 +@@ -2011,10 +2011,17 @@ + pem_password_cb *cb = ::Config.Program.ssl_password ? &ssl_ask_password_cb : NULL; + pkey.reset(readSslPrivateKey(keyFilename, cb)); + cert.reset(readSslX509CertificatesChain(certFilename, chain.get())); +- if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) { +- pkey.reset(NULL); +- cert.reset(NULL); +- } ++ if (!cert) { ++ debugs(83, DBG_IMPORTANT, "WARNING: missing cert in '" << certFilename << "'"); ++ } else if (!pkey) { ++ debugs(83, DBG_IMPORTANT, "WARNING: missing private key in '" << keyFilename << "'"); ++ } else if (!X509_check_private_key(cert.get(), pkey.get())) { ++ debugs(83, DBG_IMPORTANT, "WARNING: X509_check_private_key() failed to verify signing cert"); ++ } else ++ return; // everything is okay ++ ++ pkey.reset(NULL); ++ cert.reset(NULL); + } + + bool Ssl::generateUntrustedCert(X509_Pointer &untrustedCert, EVP_PKEY_Pointer &untrustedPkey, X509_Pointer const &cert, EVP_PKEY_Pointer const & pkey) + diff --git a/src/patches/squid/squid-3.5-14115.patch b/src/patches/squid/squid-3.5-14115.patch new file mode 100644 index 000000000..4e5e3cf2b --- /dev/null +++ b/src/patches/squid/squid-3.5-14115.patch @@ -0,0 +1,197 @@ +------------------------------------------------------------ +revno: 14115 +revision-id: squid3@treenet.co.nz-20161130215630-c42qucqar9bi9a1k +parent: squid3@treenet.co.nz-20161130154205-c9z1bhqzuh3rafl3 +fixes bug: http://bugs.squid-cache.org/show_bug.cgi?id=4004 +author: Christos Tsantilas chtsanti@users.sourceforge.net +committer: Amos Jeffries squid3@treenet.co.nz +branch nick: 3.5 +timestamp: Thu 2016-12-01 10:56:30 +1300 +message: + Bug 4004 partial: Fix segfault via Ftp::Client::readControlReply + + Added nil dereference checks for Ftp::Client::ctrl.conn, including: + - Ftp::Client::handlePasvReply() and handleEpsvReply() that dereference + ctrl.conn in DBG_IMPORTANT messages. + - Many functions inside FtpClient.cc and FtpGateway.cc files. + + TODO: We need to find a better way to handle nil ctrl.conn. It is only + a matter of time when we forget to add another dereference check or + discover a place we missed during this change. + + Also disabled forwarding of EPRT and PORT commands to origin servers. + Squid support for those commands is broken and their forwarding may + cause segfaults (bug #4004). Active FTP is still supported, of course. + + This is a Measurement Factory project +------------------------------------------------------------ +# Bazaar merge directive format 2 (Bazaar 0.90) +# revision_id: squid3@treenet.co.nz-20161130215630-c42qucqar9bi9a1k +# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# testament_sha1: 345883c1b5a5cd221e9d0e68b254df7d955372ad +# timestamp: 2016-11-30 22:42:02 +0000 +# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# base_revision_id: squid3@treenet.co.nz-20161130154205-\ +# c9z1bhqzuh3rafl3 +# +# Begin patch +=== modified file 'src/clients/FtpClient.cc' +--- src/clients/FtpClient.cc 2016-08-05 14:59:33 +0000 ++++ src/clients/FtpClient.cc 2016-11-30 21:56:30 +0000 +@@ -442,6 +442,11 @@ + char *buf; + debugs(9, 3, status()); + ++ if (!Comm::IsConnOpen(ctrl.conn)) { ++ debugs(9, 5, "The control connection to the remote end is closed"); ++ return false; ++ } ++ + if (code != 227) { + debugs(9, 2, "PASV not supported by remote end"); + return false; +@@ -473,6 +478,11 @@ + char *buf; + debugs(9, 3, status()); + ++ if (!Comm::IsConnOpen(ctrl.conn)) { ++ debugs(9, 5, "The control connection to the remote end is closed"); ++ return false; ++ } ++ + if (code != 229 && code != 522) { + if (code == 200) { + /* handle broken servers (RFC 2428 says OK code for EPSV MUST be 229 not 200) */ +@@ -733,6 +743,11 @@ + void + Ftp::Client::connectDataChannel() + { ++ if (!Comm::IsConnOpen(ctrl.conn)) { ++ debugs(9, 5, "The control connection to the remote end is closed"); ++ return; ++ } ++ + safe_free(ctrl.last_command); + + safe_free(ctrl.last_reply); + +=== modified file 'src/clients/FtpGateway.cc' +--- src/clients/FtpGateway.cc 2016-01-31 05:39:09 +0000 ++++ src/clients/FtpGateway.cc 2016-11-30 21:56:30 +0000 +@@ -212,7 +212,9 @@ + static FTPSM ftpReadMdtm; + static FTPSM ftpSendSize; + static FTPSM ftpReadSize; ++#if 0 + static FTPSM ftpSendEPRT; ++#endif + static FTPSM ftpReadEPRT; + static FTPSM ftpSendPORT; + static FTPSM ftpReadPORT; +@@ -450,6 +452,11 @@ + void + Ftp::Gateway::listenForDataChannel(const Comm::ConnectionPointer &conn) + { ++ if (!Comm::IsConnOpen(ctrl.conn)) { ++ debugs(9, 5, "The control connection to the remote end is closed"); ++ return; ++ } ++ + assert(!Comm::IsConnOpen(data.conn)); + + typedef CommCbMemFunT<Gateway, CommAcceptCbParams> AcceptDialer; +@@ -1183,7 +1190,7 @@ + + checkUrlpath(); + buildTitleUrl(); +- debugs(9, 5, HERE << "FD " << ctrl.conn->fd << " : host=" << request->GetHost() << ++ debugs(9, 5, "FD " << (ctrl.conn != NULL ? ctrl.conn->fd : -1) << " : host=" << request->GetHost() << + ", path=" << request->urlpath << ", user=" << user << ", passwd=" << password); + state = BEGIN; + Ftp::Client::start(); +@@ -1750,7 +1757,9 @@ + if (ftpState->handlePasvReply(srvAddr)) + ftpState->connectDataChannel(); + else { +- ftpSendEPRT(ftpState); ++ ftpFail(ftpState); ++ // Currently disabled, does not work correctly: ++ // ftpSendEPRT(ftpState); + return; + } + } +@@ -1790,6 +1799,11 @@ + } + safe_free(ftpState->data.host); + ++ if (!Comm::IsConnOpen(ftpState->ctrl.conn)) { ++ debugs(9, 5, "The control connection to the remote end is closed"); ++ return; ++ } ++ + /* + * Set up a listen socket on the same local address as the + * control connection. +@@ -1875,9 +1889,14 @@ + ftpRestOrList(ftpState); + } + ++#if 0 + static void + ftpSendEPRT(Ftp::Gateway * ftpState) + { ++ /* check the server control channel is still available */ ++ if (!ftpState || !ftpState->haveControlChannel("ftpSendEPRT")) ++ return; ++ + if (Config.Ftp.epsv_all && ftpState->flags.epsv_all_sent) { + debugs(9, DBG_IMPORTANT, "FTP does not allow EPRT method after 'EPSV ALL' has been sent."); + return; +@@ -1913,6 +1932,7 @@ + ftpState->writeCommand(cbuf); + ftpState->state = Ftp::Client::SENT_EPRT; + } ++#endif + + static void + ftpReadEPRT(Ftp::Gateway * ftpState) +@@ -1939,10 +1959,8 @@ + { + debugs(9, 3, HERE); + +- if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { +- abortAll("entry aborted when accepting data conn"); +- data.listenConn->close(); +- data.listenConn = NULL; ++ if (!Comm::IsConnOpen(ctrl.conn)) { /*Close handlers will cleanup*/ ++ debugs(9, 5, "The control connection to the remote end is closed"); + return; + } + +@@ -1955,6 +1973,14 @@ + return; + } + ++ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { ++ abortAll("entry aborted when accepting data conn"); ++ data.listenConn->close(); ++ data.listenConn = NULL; ++ io.conn->close(); ++ return; ++ } ++ + /* data listening conn is no longer even open. abort. */ + if (!Comm::IsConnOpen(data.listenConn)) { + data.listenConn = NULL; // ensure that it's cleared and not just closed. +@@ -2705,8 +2731,8 @@ + Ftp::Gateway::completeForwarding() + { + if (fwd == NULL || flags.completed_forwarding) { +- debugs(9, 3, HERE << "completeForwarding avoids " << +- "double-complete on FD " << ctrl.conn->fd << ", Data FD " << data.conn->fd << ++ debugs(9, 3, "avoid double-complete on FD " << ++ (ctrl.conn != NULL ? ctrl.conn->fd : -1) << ", Data FD " << data.conn->fd << + ", this " << this << ", fwd " << fwd); + return; + } + diff --git a/src/patches/squid/squid-3.5-14116.patch b/src/patches/squid/squid-3.5-14116.patch new file mode 100644 index 000000000..c92d8b80c --- /dev/null +++ b/src/patches/squid/squid-3.5-14116.patch @@ -0,0 +1,38 @@ +------------------------------------------------------------ +revno: 14116 +revision-id: squid3@treenet.co.nz-20161130223332-zcaxll4prj3kag1b +parent: squid3@treenet.co.nz-20161130215630-c42qucqar9bi9a1k +fixes bug: http://bugs.squid-cache.org/show_bug.cgi?id=3533 +author: Garri Djavadyan garryd@comnet.uz +committer: Amos Jeffries squid3@treenet.co.nz +branch nick: 3.5 +timestamp: Thu 2016-12-01 11:33:32 +1300 +message: + Bug 3533: Cache still valid after HTTP/1.1 303 See Other + + RFC7231 does not mention 303 response as non-cacheable. + So, assuming that means it *is* cacheable. +------------------------------------------------------------ +# Bazaar merge directive format 2 (Bazaar 0.90) +# revision_id: squid3@treenet.co.nz-20161130223332-zcaxll4prj3kag1b +# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# testament_sha1: c90320c95a4b64c8d18794fbe5df526fe0f9f702 +# timestamp: 2016-11-30 22:42:05 +0000 +# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# base_revision_id: squid3@treenet.co.nz-20161130215630-\ +# c42qucqar9bi9a1k +# +# Begin patch +=== modified file 'src/http.cc' +--- src/http.cc 2016-10-30 09:45:03 +0000 ++++ src/http.cc 2016-11-30 22:33:32 +0000 +@@ -203,6 +203,8 @@ + + case Http::scFound: + ++ case Http::scSeeOther: ++ + case Http::scGone: + + case Http::scNotFound: + diff --git a/src/patches/squid/squid-3.5-14117.patch b/src/patches/squid/squid-3.5-14117.patch new file mode 100644 index 000000000..23d5376f2 --- /dev/null +++ b/src/patches/squid/squid-3.5-14117.patch @@ -0,0 +1,152 @@ +------------------------------------------------------------ +revno: 14117 +revision-id: squid3@treenet.co.nz-20161130232039-z18ikhhcf3j185my +parent: squid3@treenet.co.nz-20161130223332-zcaxll4prj3kag1b +fixes bug: http://bugs.squid-cache.org/show_bug.cgi?id=4007 +author: Stephen Baynes sbaynes@mail.com, Amos Jeffries squid3@treenet.co.nz +committer: Amos Jeffries squid3@treenet.co.nz +branch nick: 3.5 +timestamp: Thu 2016-12-01 12:20:39 +1300 +message: + Bug 4007: Hang on DNS query with dead-end CNAME + + DNS lookup recursion no longer occurs. ipcacheParse() return values are no + longer useful. + + Also, cleanup the debugging output. +------------------------------------------------------------ +# Bazaar merge directive format 2 (Bazaar 0.90) +# revision_id: squid3@treenet.co.nz-20161130232039-z18ikhhcf3j185my +# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# testament_sha1: 9059c7a07e5366bd2eac606c72f875077766ed34 +# timestamp: 2016-11-30 23:27:11 +0000 +# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# base_revision_id: squid3@treenet.co.nz-20161130223332-\ +# zcaxll4prj3kag1b +# +# Begin patch +=== modified file 'src/ipcache.cc' +--- src/ipcache.cc 2016-01-01 00:14:27 +0000 ++++ src/ipcache.cc 2016-11-30 23:20:39 +0000 +@@ -123,7 +123,6 @@ + static FREE ipcacheFreeEntry; + static IDNSCB ipcacheHandleReply; + static int ipcacheExpiredEntry(ipcache_entry *); +-static int ipcacheParse(ipcache_entry *, const rfc1035_rr *, int, const char *error); + static ipcache_entry *ipcache_get(const char *); + static void ipcacheLockEntry(ipcache_entry *); + static void ipcacheStatPrint(ipcache_entry *, StoreEntry *); +@@ -328,8 +327,7 @@ + ipcacheUnlockEntry(i); + } + +-/// \ingroup IPCacheAPI +-static int ++static void + ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message) + { + int k; +@@ -350,25 +348,25 @@ + i->addrs.count = 0; + + if (nr < 0) { +- debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'"); ++ debugs(14, 3, "Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'"); + i->error_message = xstrdup(error_message); +- return -1; ++ return; + } + + if (nr == 0) { +- debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name << "'"); ++ debugs(14, 3, "No DNS records in response to '" << name << "'"); + i->error_message = xstrdup("No DNS records"); +- return -1; ++ return; + } + +- debugs(14, 3, "ipcacheParse: " << nr << " answers for '" << name << "'"); ++ debugs(14, 3, nr << " answers for '" << name << "'"); + assert(answers); + + for (k = 0; k < nr; ++k) { + + if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) { + if (answers[k].rdlength != sizeof(struct in6_addr)) { +- debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv6 address in response to '" << name << "'"); ++ debugs(14, DBG_IMPORTANT, MYNAME << "Invalid IPv6 address in response to '" << name << "'"); + continue; + } + ++na; +@@ -378,7 +376,7 @@ + + if (answers[k].type == RFC1035_TYPE_A) { + if (answers[k].rdlength != sizeof(struct in_addr)) { +- debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv4 address in response to '" << name << "'"); ++ debugs(14, DBG_IMPORTANT, MYNAME << "Invalid IPv4 address in response to '" << name << "'"); + continue; + } + ++na; +@@ -394,14 +392,14 @@ + } + + // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common. +- debugs(14, 9, HERE << "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) ); ++ debugs(14, 9, "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) ); + } + if (na == 0) { +- debugs(14, DBG_IMPORTANT, "ipcacheParse: No Address records in response to '" << name << "'"); ++ debugs(14, DBG_IMPORTANT, MYNAME << "No Address records in response to '" << name << "'"); + i->error_message = xstrdup("No Address records"); + if (cname_found) + ++IpcacheStats.cname_only; +- return 0; ++ return; + } + + i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(na, sizeof(Ip::Address))); +@@ -419,7 +417,7 @@ + memcpy(&temp, answers[k].rdata, sizeof(struct in_addr)); + i->addrs.in_addrs[j] = temp; + +- debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j]); ++ debugs(14, 3, name << " #" << j << " " << i->addrs.in_addrs[j]); + ++j; + + } else if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) { +@@ -430,7 +428,7 @@ + memcpy(&temp, answers[k].rdata, sizeof(struct in6_addr)); + i->addrs.in_addrs[j] = temp; + +- debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] ); ++ debugs(14, 3, name << " #" << j << " " << i->addrs.in_addrs[j] ); + ++j; + } + if (ttl == 0 || (int) answers[k].ttl < ttl) +@@ -453,8 +451,6 @@ + i->expires = squid_curtime + ttl; + + i->flags.negcached = false; +- +- return i->addrs.count; + } + + /// \ingroup IPCacheInternal +@@ -467,13 +463,9 @@ + const int age = i->age(); + statCounter.dns.svcTime.count(age); + +- int done = ipcacheParse(i, answers, na, error_message); +- +- /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */ +- if (done != 0 || error_message != NULL) { +- ipcacheAddEntry(i); +- ipcacheCallback(i, age); +- } ++ ipcacheParse(i, answers, na, error_message); ++ ipcacheAddEntry(i); ++ ipcacheCallback(i, age); + } + + /** + diff --git a/src/patches/squid/squid-3.5-14118.patch b/src/patches/squid/squid-3.5-14118.patch new file mode 100644 index 000000000..1e36294f3 --- /dev/null +++ b/src/patches/squid/squid-3.5-14118.patch @@ -0,0 +1,55 @@ +------------------------------------------------------------ +revno: 14118 +revision-id: squid3@treenet.co.nz-20161130233304-lk3q0bx8gn5l3l85 +parent: squid3@treenet.co.nz-20161130232039-z18ikhhcf3j185my +fixes bug: http://bugs.squid-cache.org/show_bug.cgi?id=3290 +author: Garri Djavadyan garryd@comnet.uz +committer: Amos Jeffries squid3@treenet.co.nz +branch nick: 3.5 +timestamp: Thu 2016-12-01 12:33:04 +1300 +message: + Bug 3290: authenticate_ttl not working for digest authentication +------------------------------------------------------------ +# Bazaar merge directive format 2 (Bazaar 0.90) +# revision_id: squid3@treenet.co.nz-20161130233304-lk3q0bx8gn5l3l85 +# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# testament_sha1: 50ff391db1484222ead5fb50b1bca0694c37ed4c +# timestamp: 2016-11-30 23:34:59 +0000 +# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.5 +# base_revision_id: squid3@treenet.co.nz-20161130232039-\ +# z18ikhhcf3j185my +# +# Begin patch +=== modified file 'src/auth/digest/Config.cc' +--- src/auth/digest/Config.cc 2016-11-14 10:54:34 +0000 ++++ src/auth/digest/Config.cc 2016-11-30 23:33:04 +0000 +@@ -1058,6 +1058,10 @@ + * the user agent won't change user name without warning. + */ + authDigestUserLinkNonce(digest_user, nonce); ++ ++ /* auth_user is now linked, we reset these values ++ * after external auth occurs anyway */ ++ auth_user->expiretime = current_time.tv_sec; + } else { + debugs(29, 9, "Found user '" << username << "' in the user cache as '" << auth_user << "'"); + digest_user = static_cast<Auth::Digest::User *>(auth_user.getRaw()); + +=== modified file 'src/auth/digest/UserRequest.cc' +--- src/auth/digest/UserRequest.cc 2016-01-01 00:14:27 +0000 ++++ src/auth/digest/UserRequest.cc 2016-11-30 23:33:04 +0000 +@@ -187,12 +187,7 @@ + auth_user->credentials(Auth::Ok); + + /* password was checked and did match */ +- debugs(29, 4, HERE << "user '" << auth_user->username() << "' validated OK"); +- +- /* auth_user is now linked, we reset these values +- * after external auth occurs anyway */ +- auth_user->expiretime = current_time.tv_sec; +- return; ++ debugs(29, 4, "user '" << auth_user->username() << "' validated OK"); + } + + Auth::Direction +