]>
Commit | Line | Data |
---|---|---|
ab332e27 | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 The Squid Software Foundation and contributors |
bbc27441 AJ |
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. | |
ab332e27 | 7 | */ |
bbc27441 AJ |
8 | |
9 | /* DEBUG: section 84 Helper process maintenance */ | |
10 | ||
0272dd08 | 11 | #include "squid.h" |
7bbefa01 | 12 | #include "ConfigParser.h" |
602d9612 | 13 | #include "Debug.h" |
0272dd08 | 14 | #include "helper.h" |
24438ec5 | 15 | #include "helper/Reply.h" |
7bbefa01 | 16 | #include "rfc1738.h" |
ab332e27 | 17 | #include "SquidString.h" |
0272dd08 | 18 | |
ddc77a2e | 19 | Helper::Reply::Reply() : |
a56fcf0b | 20 | result(Helper::Unknown) |
7bbefa01 | 21 | { |
ddc77a2e CT |
22 | } |
23 | ||
24 | bool | |
25 | Helper::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; | |
7bbefa01 AJ |
35 | } |
36 | ||
37 | void | |
ddc77a2e | 38 | Helper::Reply::finalize() |
0272dd08 | 39 | { |
602fab5b | 40 | debugs(84, 3, "Parsing helper buffer"); |
0272dd08 | 41 | // check we have something to parse |
ddc77a2e | 42 | if (!other_.hasContent()) { |
aef3208d | 43 | // empty line response was the old URL-rewriter interface ERR response. |
2428ce02 | 44 | result = Helper::Error; |
e166785a | 45 | // for now ensure that legacy handlers are not presented with NULL strings. |
ddc77a2e | 46 | debugs(84, 3, "Zero length reply"); |
0272dd08 | 47 | return; |
e166785a | 48 | } |
0272dd08 | 49 | |
ddc77a2e CT |
50 | char *p = other_.content(); |
51 | size_t len = other_.contentSize(); | |
723fd4c1 | 52 | bool sawNA = false; |
0272dd08 | 53 | |
e166785a AJ |
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. | |
0272dd08 | 57 | if (len >= 2) { |
602fab5b | 58 | debugs(84, 3, "Buff length is larger than 2"); |
0272dd08 | 59 | // some helper formats (digest auth, URL-rewriter) just send a data string |
e166785a AJ |
60 | // we must also check for the ' ' character after the response token (if anything) |
61 | if (!strncmp(p,"OK",2) && (len == 2 || p[2] == ' ')) { | |
602fab5b | 62 | debugs(84, 3, "helper Result = OK"); |
2428ce02 | 63 | result = Helper::Okay; |
0272dd08 | 64 | p+=2; |
e166785a | 65 | } else if (!strncmp(p,"ERR",3) && (len == 3 || p[3] == ' ')) { |
602fab5b | 66 | debugs(84, 3, "helper Result = ERR"); |
2428ce02 | 67 | result = Helper::Error; |
0272dd08 | 68 | p+=3; |
e166785a | 69 | } else if (!strncmp(p,"BH",2) && (len == 2 || p[2] == ' ')) { |
602fab5b | 70 | debugs(84, 3, "helper Result = BH"); |
2428ce02 | 71 | result = Helper::BrokenHelper; |
0272dd08 AJ |
72 | p+=2; |
73 | } else if (!strncmp(p,"TT ",3)) { | |
74 | // NTLM challenge token | |
2428ce02 | 75 | result = Helper::TT; |
e166785a | 76 | p+=3; |
d850d8ee | 77 | // followed by an auth token |
7bbefa01 | 78 | char *w1 = strwordtok(NULL, &p); |
b7719af4 | 79 | if (w1 != NULL) { |
ddc77a2e CT |
80 | const char *authToken = w1; |
81 | notes.add("token",authToken); | |
b7719af4 AJ |
82 | } else { |
83 | // token field is mandatory on this response code | |
2428ce02 | 84 | result = Helper::BrokenHelper; |
fd7f26ea | 85 | notes.add("message","Missing 'token' data"); |
b7719af4 | 86 | } |
7bbefa01 | 87 | |
0272dd08 | 88 | } else if (!strncmp(p,"AF ",3)) { |
2f8abb64 | 89 | // NTLM/Negotiate OK response |
2428ce02 | 90 | result = Helper::Okay; |
e082eaba | 91 | p+=3; |
0b7a812f | 92 | // followed by: |
7bbefa01 | 93 | // an optional auth token and user field |
0b7a812f | 94 | // or, an optional username field |
7bbefa01 AJ |
95 | char *w1 = strwordtok(NULL, &p); |
96 | char *w2 = strwordtok(NULL, &p); | |
97 | if (w2 != NULL) { | |
98 | // Negotiate "token user" | |
ddc77a2e CT |
99 | const char *authToken = w1; |
100 | notes.add("token",authToken); | |
7bbefa01 | 101 | |
ddc77a2e CT |
102 | const char *user = w2; |
103 | notes.add("user",user); | |
7bbefa01 AJ |
104 | |
105 | } else if (w1 != NULL) { | |
106 | // NTLM "user" | |
ddc77a2e CT |
107 | const char *user = w1; |
108 | notes.add("user",user); | |
e082eaba | 109 | } |
0272dd08 AJ |
110 | } else if (!strncmp(p,"NA ",3)) { |
111 | // NTLM fail-closed ERR response | |
2428ce02 | 112 | result = Helper::Error; |
e166785a | 113 | p+=3; |
723fd4c1 | 114 | sawNA=true; |
0272dd08 AJ |
115 | } |
116 | ||
dacb64b9 | 117 | for (; xisspace(*p); ++p); // skip whitespace |
0272dd08 AJ |
118 | } |
119 | ||
ddc77a2e CT |
120 | other_.consume(p - other_.content()); |
121 | other_.consumeWhitespacePrefix(); | |
ab332e27 | 122 | |
723fd4c1 AJ |
123 | // Hack for backward-compatibility: Do not parse for kv-pairs on NA response |
124 | if (!sawNA) | |
125 | parseResponseKeys(); | |
7bbefa01 | 126 | |
723fd4c1 | 127 | // Hack for backward-compatibility: BH and NA used to be a text message... |
ddc77a2e CT |
128 | if (other_.hasContent() && (sawNA || result == Helper::BrokenHelper)) { |
129 | notes.add("message", other_.content()); | |
130 | other_.clean(); | |
ab332e27 AJ |
131 | } |
132 | } | |
133 | ||
acf2ce75 AJ |
134 | /// restrict key names to alphanumeric, hyphen, underscore characters |
135 | static bool | |
136 | isKeyNameChar(char c) | |
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 | ||
7bbefa01 | 154 | void |
24438ec5 | 155 | Helper::Reply::parseResponseKeys() |
ab332e27 | 156 | { |
7bbefa01 | 157 | // parse a "key=value" pair off the 'other()' buffer. |
ddc77a2e CT |
158 | while (other_.hasContent()) { |
159 | char *p = other_.content(); | |
160 | const char *key = p; | |
acf2ce75 | 161 | while (*p && isKeyNameChar(*p)) ++p; |
7bbefa01 AJ |
162 | if (*p != '=') |
163 | return; // done. Not a key. | |
164 | ||
24eac830 AJ |
165 | // whitespace between key and value is prohibited. |
166 | // workaround strwordtok() which skips whitespace prefix. | |
167 | if (xisspace(*(p+1))) | |
168 | return; // done. Not a key. | |
169 | ||
7bbefa01 AJ |
170 | *p = '\0'; |
171 | ++p; | |
172 | ||
7bbefa01 | 173 | // the value may be a quoted string or a token |
05e52854 | 174 | const bool urlDecode = (*p != '"'); // check before moving p. |
7bbefa01 | 175 | char *v = strwordtok(NULL, &p); |
05e52854 | 176 | if (v != NULL && urlDecode && (p-v) > 2) // 1-octet %-escaped requires 3 bytes |
7bbefa01 | 177 | rfc1738_unescape(v); |
7bbefa01 | 178 | |
cf9f0261 | 179 | notes.add(key, v ? v : ""); // value can be empty, but must not be NULL |
7bbefa01 | 180 | |
ddc77a2e CT |
181 | other_.consume(p - other_.content()); |
182 | other_.consumeWhitespacePrefix(); | |
ab332e27 | 183 | } |
0272dd08 AJ |
184 | } |
185 | ||
ddc77a2e CT |
186 | const MemBuf & |
187 | Helper::Reply::emptyBuf() const | |
188 | { | |
189 | static MemBuf empty; | |
190 | if (empty.isNull()) | |
191 | empty.init(1, 1); | |
192 | return empty; | |
193 | } | |
194 | ||
0272dd08 | 195 | std::ostream & |
24438ec5 | 196 | operator <<(std::ostream &os, const Helper::Reply &r) |
0272dd08 AJ |
197 | { |
198 | os << "{result="; | |
154be258 | 199 | switch (r.result) { |
2428ce02 | 200 | case Helper::Okay: |
0272dd08 AJ |
201 | os << "OK"; |
202 | break; | |
2428ce02 | 203 | case Helper::Error: |
0272dd08 | 204 | os << "ERR"; |
ab332e27 | 205 | break; |
2428ce02 | 206 | case Helper::BrokenHelper: |
0272dd08 AJ |
207 | os << "BH"; |
208 | break; | |
2428ce02 | 209 | case Helper::TT: |
0272dd08 AJ |
210 | os << "TT"; |
211 | break; | |
32fd6d8a CT |
212 | case Helper::TimedOut: |
213 | os << "Timeout"; | |
214 | break; | |
2428ce02 | 215 | case Helper::Unknown: |
0272dd08 AJ |
216 | os << "Unknown"; |
217 | break; | |
218 | } | |
219 | ||
7bbefa01 | 220 | // dump the helper key=pair "notes" list |
cf9f0261 | 221 | if (!r.notes.empty()) { |
7bbefa01 | 222 | os << ", notes={"; |
7e6ef752 | 223 | os << r.notes.toString("; "); |
7bbefa01 AJ |
224 | os << "}"; |
225 | } | |
226 | ||
ddc77a2e CT |
227 | MemBuf const &o = r.other(); |
228 | if (o.hasContent()) | |
229 | os << ", other: \"" << o.content() << '\"'; | |
0272dd08 AJ |
230 | |
231 | os << '}'; | |
232 | ||
233 | return os; | |
234 | } | |
f53969cc | 235 |