]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1996-2025 The Squid Software Foundation and contributors | |
3 | * | |
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. | |
7 | */ | |
8 | ||
9 | #ifndef SQUID_SRC_ACL_CHECKLIST_H | |
10 | #define SQUID_SRC_ACL_CHECKLIST_H | |
11 | ||
12 | #include "acl/Acl.h" | |
13 | #include "acl/InnerNode.h" | |
14 | #include "cbdata.h" | |
15 | ||
16 | #include <optional> | |
17 | #include <stack> | |
18 | #include <vector> | |
19 | ||
20 | class HttpRequest; | |
21 | ||
22 | /// ACL checklist callback | |
23 | typedef void ACLCB(Acl::Answer, void *); | |
24 | ||
25 | /** \ingroup ACLAPI | |
26 | Base class for maintaining Squid and transaction state for access checks. | |
27 | Provides basic ACL checking methods. Its only child, ACLFilledChecklist, | |
28 | keeps the actual state data. The split is necessary to avoid exposing | |
29 | all ACL-related code to virtually Squid data types. */ | |
30 | class ACLChecklist | |
31 | { | |
32 | ||
33 | public: | |
34 | ||
35 | /// a function that initiates asynchronous ACL checks; see goAsync() | |
36 | using AsyncStarter = void (ACLFilledChecklist &, const Acl::Node &); | |
37 | ||
38 | public: | |
39 | ACLChecklist(); | |
40 | virtual ~ACLChecklist(); | |
41 | ||
42 | /** | |
43 | * Perform a blocking (immediate) check for a list of allow/deny rules. | |
44 | * Each rule comes with a list of ACLs. | |
45 | * | |
46 | * The first rule where all ACLs match wins. If there is such a rule, | |
47 | * the result becomes that rule keyword (ACCESS_ALLOWED or ACCESS_DENIED). | |
48 | * | |
49 | * If there are rules but all ACL lists mismatch, an implicit rule is used | |
50 | * Its result is the negation of the keyword of the last seen rule. | |
51 | * | |
52 | * Some ACLs may stop the check prematurely by setting an exceptional | |
53 | * check result (e.g., ACCESS_AUTH_REQUIRED) instead of declaring a | |
54 | * match or mismatch. | |
55 | * | |
56 | * Some ACLs may require an async lookup which is prohibited by this | |
57 | * method. In this case, the exceptional check result of ACCESS_DUNNO is | |
58 | * immediately returned. | |
59 | * | |
60 | * If there are no rules to check at all, the result becomes ACCESS_DUNNO. | |
61 | */ | |
62 | Acl::Answer const & fastCheck(); | |
63 | ||
64 | /** | |
65 | * Perform a blocking (immediate) check whether a list of ACLs matches. | |
66 | * This method is meant to be used with squid.conf ACL-driven options that | |
67 | * lack allow/deny keywords and are tested one ACL list at a time. Whether | |
68 | * the checks for other occurrences of the same option continue after this | |
69 | * call is up to the caller and option semantics. | |
70 | * | |
71 | * If all ACLs match, the result becomes ACCESS_ALLOWED. | |
72 | * | |
73 | * If all ACLs mismatch, the result becomes ACCESS_DENIED. | |
74 | * | |
75 | * Some ACLs may stop the check prematurely by setting an exceptional | |
76 | * check result (e.g., ACCESS_AUTH_REQUIRED) instead of declaring a | |
77 | * match or mismatch. | |
78 | * | |
79 | * Some ACLs may require an async lookup which is prohibited by this | |
80 | * method. In this case, the exceptional check result of ACCESS_DUNNO is | |
81 | * immediately returned. | |
82 | * | |
83 | * If there are no ACLs to check at all, the result becomes ACCESS_ALLOWED. | |
84 | */ | |
85 | const Acl::Answer &fastCheck(const ACLList *); | |
86 | ||
87 | /// If slow lookups are allowed, switches into "async in progress" state. | |
88 | /// Otherwise, returns false; the caller is expected to handle the failure. | |
89 | bool goAsync(AsyncStarter, const Acl::Node &); | |
90 | ||
91 | /// Matches (or resumes matching of) a child node at pos while maintaining | |
92 | /// resumption breadcrumbs if a [grand]child node goes async. | |
93 | bool matchChild(const Acl::InnerNode *parent, Acl::Nodes::const_iterator pos); | |
94 | ||
95 | /// Whether we should continue to match tree nodes or stop/pause. | |
96 | bool keepMatching() const { return !finished() && !asyncInProgress(); } | |
97 | ||
98 | /// whether markFinished() was called | |
99 | bool finished() const { return finished_; } | |
100 | /// async call has been started and has not finished (or failed) yet | |
101 | bool asyncInProgress() const { return asyncStage_ != asyncNone; } | |
102 | /// called when no more ACLs should be checked; sets the final answer and | |
103 | /// prints a debugging message explaining the reason for that answer | |
104 | void markFinished(const Acl::Answer &newAnswer, const char *reason); | |
105 | ||
106 | const Acl::Answer ¤tAnswer() const { return answer_; } | |
107 | ||
108 | /// whether the action is banned or not | |
109 | bool bannedAction(const Acl::Answer &action) const; | |
110 | /// add action to the list of banned actions | |
111 | void banAction(const Acl::Answer &action); | |
112 | ||
113 | // XXX: ACLs that need request or reply have to use ACLFilledChecklist and | |
114 | // should do their own checks so that we do not have to povide these two | |
115 | // for ACL::checklistMatches to use | |
116 | virtual bool hasRequest() const = 0; | |
117 | virtual bool hasReply() const = 0; | |
118 | virtual bool hasAle() const = 0; | |
119 | /// assigns uninitialized adapted_request and url ALE components | |
120 | virtual void syncAle(HttpRequest *adaptedRequest, const char *logUri) const = 0; | |
121 | /// warns if there are uninitialized ALE components and fills them | |
122 | virtual void verifyAle() const = 0; | |
123 | ||
124 | /// change the current ACL list | |
125 | void changeAcl(const acl_access *); | |
126 | ||
127 | /// remember the name of the last ACL being evaluated | |
128 | void setLastCheckedName(const SBuf &name) { lastCheckedName_ = name; } | |
129 | ||
130 | protected: | |
131 | /** | |
132 | * Start a non-blocking (async) check for a list of allow/deny rules. | |
133 | * Each rule comes with a list of ACLs. | |
134 | * | |
135 | * The callback specified will be called with the result of the check. | |
136 | * | |
137 | * The first rule where all ACLs match wins. If there is such a rule, | |
138 | * the result becomes that rule keyword (ACCESS_ALLOWED or ACCESS_DENIED). | |
139 | * | |
140 | * If there are rules but all ACL lists mismatch, an implicit rule is used. | |
141 | * Its result is the negation of the keyword of the last seen rule. | |
142 | * | |
143 | * Some ACLs may stop the check prematurely by setting an exceptional | |
144 | * check result (e.g., ACCESS_AUTH_REQUIRED) instead of declaring a | |
145 | * match or mismatch. | |
146 | * | |
147 | * If there are no rules to check at all, the result becomes ACCESS_DUNNO. | |
148 | * Calling this method with no rules to check wastes a lot of CPU cycles | |
149 | * and will result in a DBG_CRITICAL debugging message. | |
150 | */ | |
151 | void nonBlockingCheck(ACLCB * callback, void *callback_data); | |
152 | ||
153 | private: | |
154 | /// Calls non-blocking check callback with the answer and destroys self. | |
155 | /// If abortReason is provided, sets the final answer to ACCESS_DUNNO. | |
156 | void checkCallback(const char *abortReason); | |
157 | ||
158 | void matchAndFinish(); | |
159 | ||
160 | /// \copydoc changeAcl() | |
161 | /// \returns old accessList pointer (that may be nil) | |
162 | Acl::TreePointer swapAcl(const acl_access *); | |
163 | ||
164 | Acl::TreePointer accessList; | |
165 | ||
166 | public: | |
167 | ||
168 | ACLCB *callback; | |
169 | void *callback_data; | |
170 | ||
171 | /// Resumes non-blocking check started by nonBlockingCheck() and | |
172 | /// suspended until some async operation updated Squid state. | |
173 | void resumeNonBlockingCheck(); | |
174 | ||
175 | private: /* internal methods */ | |
176 | /// Position of a child node within an Acl::Node tree. | |
177 | class Breadcrumb | |
178 | { | |
179 | public: | |
180 | Breadcrumb(): parent(nullptr) {} | |
181 | Breadcrumb(const Acl::InnerNode *aParent, Acl::Nodes::const_iterator aPos): parent(aParent), position(aPos) {} | |
182 | bool operator ==(const Breadcrumb &b) const { return parent == b.parent && (!parent || position == b.position); } | |
183 | bool operator !=(const Breadcrumb &b) const { return !this->operator ==(b); } | |
184 | void clear() { parent = nullptr; } | |
185 | RefCount<const Acl::InnerNode> parent; ///< intermediate node in the ACL tree | |
186 | Acl::Nodes::const_iterator position; ///< child position inside parent | |
187 | }; | |
188 | ||
189 | /// possible outcomes when trying to match a single ACL node in a list | |
190 | typedef enum { nmrMatch, nmrMismatch, nmrFinished, nmrNeedsAsync } | |
191 | NodeMatchingResult; | |
192 | ||
193 | /// prepare for checking ACLs; called once per check | |
194 | void preCheck(const char *what); | |
195 | bool prepNonBlocking(); | |
196 | void completeNonBlocking(); | |
197 | void calcImplicitAnswer(); | |
198 | ||
199 | bool asyncCaller_; ///< whether the caller supports async/slow ACLs | |
200 | bool occupied_; ///< whether a check (fast or non-blocking) is in progress | |
201 | bool finished_; | |
202 | Acl::Answer answer_; | |
203 | ||
204 | enum AsyncStage { asyncNone, asyncStarting, asyncRunning, asyncFailed }; | |
205 | AsyncStage asyncStage_; | |
206 | Breadcrumb matchLoc_; ///< location of the node running matches() now | |
207 | Breadcrumb asyncLoc_; ///< currentNode_ that called goAsync() | |
208 | unsigned asyncLoopDepth_; ///< how many times the current async state has resumed | |
209 | ||
210 | bool callerGone(); | |
211 | ||
212 | /// suspended (due to an async lookup) matches() in the ACL tree | |
213 | std::stack<Breadcrumb> matchPath; | |
214 | /// the list of actions which must ignored during acl checks | |
215 | std::vector<Acl::Answer> bannedActions_; | |
216 | ||
217 | /// the name of the last evaluated ACL (if any ACLs were evaluated) | |
218 | std::optional<SBuf> lastCheckedName_; | |
219 | }; | |
220 | ||
221 | #endif /* SQUID_SRC_ACL_CHECKLIST_H */ | |
222 |