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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors