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 
20 #include <algorithm>
21 
23  result(Helper::Unknown)
24 {
25 }
26 
27 bool
28 Helper::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 
40 void
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 
138 static 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 
158 void
159 Helper::Reply::CheckReceivedKey(const SBuf &key, const SBuf &value)
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 
209 void
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
232  rfc1738_unescape(v);
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 
245 const MemBuf &
247 {
248  static MemBuf empty;
249  if (empty.isNull())
250  empty.init(1, 1);
251  return empty;
252 }
253 
254 std::ostream &
255 Helper::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 
Reply()
Creates a NULL reply.
Definition: Reply.cc:22
static bool isKeyNameChar(char c)
restrict key names to alphanumeric, hyphen, underscore characters
Definition: Reply.cc:139
const_reverse_iterator rbegin() const
Definition: SBuf.h:595
static void CheckReceivedKey(const SBuf &, const SBuf &)
warns admin about problematic key=value pairs
Definition: Reply.cc:159
const MemBuf & emptyBuf() const
Return an empty MemBuf.
Definition: Reply.cc:246
@ Error
Definition: ResultCode.h:19
bool isEmpty() const
Definition: SBuf.h:435
@ Unknown
Definition: ResultCode.h:17
void print(std::ostream &os, const char *nameValueSeparator, const char *entryTerminator) const
Definition: Notes.cc:295
bool hasContent() const
Definition: MemBuf.h:54
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:93
Definition: SBuf.h:93
std::ostream & operator<<(std::ostream &, const Reply &)
Definition: Reply.cc:255
bool empty() const
Definition: Notes.h:260
int isNull() const
Definition: MemBuf.cc:145
const MemBuf & other() const
Definition: Reply.h:42
void rfc1738_unescape(char *url)
Definition: rfc1738.c:146
bool accumulate(const char *buf, size_t len)
Definition: Reply.cc:28
Helper::ResultCode result
The helper response 'result' field.
Definition: Reply.h:59
char * strwordtok(char *buf, char **t)
Definition: String.cc:314
Definition: MemBuf.h:23
NotePairs notes
Definition: Reply.h:62
void parseResponseKeys()
Definition: Reply.cc:210
@ Okay
Definition: ResultCode.h:18
helper protocol primitives
Definition: ChildConfig.h:12
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
void finalize()
Definition: Reply.cc:41
char * content()
start of the added data
Definition: MemBuf.h:41
#define Important(id)
Definition: Messages.h:93
#define DBG_IMPORTANT
Definition: Stream.h:38
@ BrokenHelper
Definition: ResultCode.h:20
ssize_t mb_size_t
Definition: MemBuf.h:17
#define xisspace(x)
Definition: xis.h:15
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
@ TimedOut
Definition: ResultCode.h:21

 

Introduction

Documentation

Support

Miscellaneous