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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors