Squid Web Cache master
Loading...
Searching...
No Matches
Reply.cc
Go to the documentation of this file.
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/* DEBUG: section 84 Helper process maintenance */
10
11#include "squid.h"
12#include "ConfigParser.h"
13#include "debug/Messages.h"
14#include "debug/Stream.h"
15#include "helper.h"
16#include "helper/Reply.h"
17#include "rfc1738.h"
18#include "SquidString.h"
19
20#include <algorithm>
21
23 result(Helper::Unknown)
24{
25}
26
27bool
28Helper::Reply::accumulate(const char *buf, size_t len)
29{
30 if (other_.isNull())
31 other_.init(4*1024, 1*1024*1024);
32
33 if (other_.potentialSpaceSize() < static_cast<mb_size_t>(len))
34 return false; // no space left
35
36 other_.append(buf, len);
37 return true;
38}
39
40void
42{
43 debugs(84, 3, "Parsing helper buffer");
44 // check we have something to parse
45 if (!other_.hasContent()) {
46 // empty line response was the old URL-rewriter interface ERR response.
47 result = Helper::Error;
48 // for now ensure that legacy handlers are not presented with NULL strings.
49 debugs(84, 3, "Zero length reply");
50 return;
51 }
52
53 char *p = other_.content();
54 size_t len = other_.contentSize();
55 bool sawNA = false;
56
57 // optimization: do not consider parsing result code if the response is short.
58 // URL-rewriter may return relative URLs or empty response for a large portion
59 // of its replies.
60 if (len >= 2) {
61 debugs(84, 3, "Buff length is larger than 2");
62 // some helper formats (digest auth, URL-rewriter) just send a data string
63 // we must also check for the ' ' character after the response token (if anything)
64 if (!strncmp(p,"OK",2) && (len == 2 || p[2] == ' ')) {
65 debugs(84, 3, "helper Result = OK");
66 result = Helper::Okay;
67 p+=2;
68 } else if (!strncmp(p,"ERR",3) && (len == 3 || p[3] == ' ')) {
69 debugs(84, 3, "helper Result = ERR");
70 result = Helper::Error;
71 p+=3;
72 } else if (!strncmp(p,"BH",2) && (len == 2 || p[2] == ' ')) {
73 debugs(84, 3, "helper Result = BH");
74 result = Helper::BrokenHelper;
75 p+=2;
76 } else if (!strncmp(p,"TT ",3)) {
77 // NTLM challenge token
78 result = Helper::TT;
79 p+=3;
80 // followed by an auth token
81 char *w1 = strwordtok(nullptr, &p);
82 if (w1 != nullptr) {
83 const char *authToken = w1;
84 notes.add("token",authToken);
85 } else {
86 // token field is mandatory on this response code
87 result = Helper::BrokenHelper;
88 notes.add("message","Missing 'token' data");
89 }
90
91 } else if (!strncmp(p,"AF ",3)) {
92 // NTLM/Negotiate OK response
93 result = Helper::Okay;
94 p+=3;
95 // followed by:
96 // an optional auth token and user field
97 // or, an optional username field
98 char *w1 = strwordtok(nullptr, &p);
99 char *w2 = strwordtok(nullptr, &p);
100 if (w2 != nullptr) {
101 // Negotiate "token user"
102 const char *authToken = w1;
103 notes.add("token",authToken);
104
105 const char *user = w2;
106 notes.add("user",user);
107
108 } else if (w1 != nullptr) {
109 // NTLM "user"
110 const char *user = w1;
111 notes.add("user",user);
112 }
113 } else if (!strncmp(p,"NA ",3)) {
114 // NTLM fail-closed ERR response
115 result = Helper::Error;
116 p+=3;
117 sawNA=true;
118 }
119
120 for (; xisspace(*p); ++p); // skip whitespace
121 }
122
123 other_.consume(p - other_.content());
124 other_.consumeWhitespacePrefix();
125
126 // Hack for backward-compatibility: Do not parse for kv-pairs on NA response
127 if (!sawNA)
128 parseResponseKeys();
129
130 // Hack for backward-compatibility: BH and NA used to be a text message...
131 if (other_.hasContent() && (sawNA || result == Helper::BrokenHelper)) {
132 notes.add("message", other_.content());
133 other_.clean();
134 }
135}
136
138static bool
140{
141 if (c >= 'a' && c <= 'z')
142 return true;
143
144 if (c >= 'A' && c <= 'Z')
145 return true;
146
147 if (c >= '0' && c <= '9')
148 return true;
149
150 if (c == '-' || c == '_')
151 return true;
152
153 // prevent other characters matching the key=value
154 return false;
155}
156
158void
160{
161 // Squid recognizes these keys (by name) in some helper responses
162 static const std::vector<SBuf> recognized = {
163 SBuf("clt_conn_tag"),
164 SBuf("group"),
165 SBuf("ha1"),
166 SBuf("log"),
167 SBuf("message"),
168 SBuf("nonce"),
169 SBuf("password"),
170 SBuf("rewrite-url"),
171 SBuf("status"),
172 SBuf("store-id"),
173 SBuf("tag"),
174 SBuf("token"),
175 SBuf("url"),
176 SBuf("user")
177 };
178
179 // TODO: Merge with Notes::ReservedKeys(). That list has an entry that Squid
180 // sources do _not_ recognize today ("ttl"), and it is missing some
181 // recognized entries ("clt_conn_tag", "nonce", store-id", and "token").
182
183 if (key.isEmpty()) {
184 debugs(84, DBG_IMPORTANT, "WARNING: Deprecated from-helper annotation without a name: " <<
185 key << '=' << value <<
186 Debug::Extra << "advice: Name or remove this annotation");
187 // TODO: Skip/ignore these annotations.
188 return;
189 }
190
191 // We do not check custom keys for repetitions because Squid supports them:
192 // The "note" ACL checks all of them and %note prints all of them.
193 if (*key.rbegin() == '_')
194 return; // a custom key
195
196 // To simplify, we allow all recognized keys, even though some of them are
197 // only expected from certain helpers or even only in certain reply types.
198 // To simplify and optimize, we do not check recognized keys for repetitions
199 // because _some_ of them (e.g., "message") do support repetitions.
200 if (std::find(recognized.begin(), recognized.end(), key) != recognized.end())
201 return; // a Squid-recognized key
202
203 debugs(84, Important(69), "WARNING: Unsupported or unexpected from-helper annotation with a name reserved for Squid use: " <<
204 key << '=' << value <<
205 Debug::Extra << "advice: If this is a custom annotation, rename it to add a trailing underscore: " <<
206 key << '_');
207}
208
209void
211{
212 // parse a "key=value" pair off the 'other()' buffer.
213 while (other_.hasContent()) {
214 char *p = other_.content();
215 const char *key = p;
216 while (*p && isKeyNameChar(*p)) ++p;
217 if (*p != '=')
218 return; // done. Not a key.
219
220 // whitespace between key and value is prohibited.
221 // workaround strwordtok() which skips whitespace prefix.
222 if (xisspace(*(p+1)))
223 return; // done. Not a key.
224
225 *p = '\0';
226 ++p;
227
228 // the value may be a quoted string or a token
229 const bool urlDecode = (*p != '"'); // check before moving p.
230 char *v = strwordtok(nullptr, &p);
231 if (v != nullptr && urlDecode && (p-v) > 2) // 1-octet %-escaped requires 3 bytes
233
234 // TODO: Convert the above code to use Tokenizer and SBuf
235 const SBuf parsedKey(key);
236 const SBuf parsedValue(v); // allow empty values (!v or !*v)
237 CheckReceivedKey(parsedKey, parsedValue);
238 notes.add(parsedKey, parsedValue);
239
240 other_.consume(p - other_.content());
241 other_.consumeWhitespacePrefix();
242 }
243}
244
245const MemBuf &
247{
248 static MemBuf empty;
249 if (empty.isNull())
250 empty.init(1, 1);
251 return empty;
252}
253
254std::ostream &
255Helper::operator <<(std::ostream &os, const Reply &r)
256{
257 os << "{result=";
258 switch (r.result) {
259 case Okay:
260 os << "OK";
261 break;
262 case Error:
263 os << "ERR";
264 break;
265 case BrokenHelper:
266 os << "BH";
267 break;
268 case TT:
269 os << "TT";
270 break;
271 case TimedOut:
272 os << "Timeout";
273 break;
274 case Unknown:
275 os << "Unknown";
276 break;
277 }
278
279 // dump the helper key=pair "notes" list
280 if (!r.notes.empty()) {
281 os << ", notes={";
282 // This simple format matches what most helpers use and is sufficient
283 // for debugging nearly any helper response, but the result differs from
284 // raw helper responses when the helper quotes values or escapes special
285 // characters. See also: Helper::Reply::parseResponseKeys().
286 r.notes.print(os, "=", " ");
287 os << "}";
288 }
289
290 MemBuf const &o = r.other();
291 if (o.hasContent())
292 os << ", other: \"" << o.content() << '\"';
293
294 os << '}';
295
296 return os;
297}
298
ssize_t mb_size_t
Definition MemBuf.h:17
static bool isKeyNameChar(char c)
restrict key names to alphanumeric, hyphen, underscore characters
Definition Reply.cc:139
char * strwordtok(char *buf, char **t)
Definition String.cc:321
static std::ostream & Extra(std::ostream &)
Definition debug.cc:1316
const MemBuf & emptyBuf() const
Return an empty MemBuf.
Definition Reply.cc:246
NotePairs notes
Definition Reply.h:62
Helper::ResultCode result
The helper response 'result' field.
Definition Reply.h:59
Reply()
Creates a NULL reply.
Definition Reply.cc:22
const MemBuf & other() const
Definition Reply.h:42
static void CheckReceivedKey(const SBuf &, const SBuf &)
warns admin about problematic key=value pairs
Definition Reply.cc:159
void finalize()
Definition Reply.cc:41
bool accumulate(const char *buf, size_t len)
Definition Reply.cc:28
void parseResponseKeys()
Definition Reply.cc:210
void init(mb_size_t szInit, mb_size_t szMax)
Definition MemBuf.cc:93
char * content()
start of the added data
Definition MemBuf.h:41
int isNull() const
Definition MemBuf.cc:145
bool hasContent() const
Definition MemBuf.h:54
bool empty() const
Definition Notes.h:260
void print(std::ostream &os, const char *nameValueSeparator, const char *entryTerminator) const
Definition Notes.cc:300
Definition SBuf.h:94
bool isEmpty() const
Definition SBuf.h:435
const_reverse_iterator rbegin() const
Definition SBuf.h:595
#define Important(id)
Definition Messages.h:93
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
helper protocol primitives
Definition helper.h:39
@ Unknown
Definition ResultCode.h:17
@ BrokenHelper
Definition ResultCode.h:20
@ TimedOut
Definition ResultCode.h:21
std::ostream & operator<<(std::ostream &, const Reply &)
Definition Reply.cc:255
void rfc1738_unescape(char *url)
Definition rfc1738.c:146
#define xisspace(x)
Definition xis.h:15