1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
/* $Id: main.cpp 1649 2009-10-19 14:35:01Z terpstra $
*
* main.cpp - Transform a database snapshot to useful output
*
* Copyright (C) 2002 - Wesley W. Terpstra
*
* License: GPL
*
* Authors: 'Wesley W. Terpstra' <wesley@terpstra.ca>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define _FILE_OFFSET_BITS 64
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include "commands.h"
#include "parse.h"
#include <XmlEscape.h>
#include <unistd.h> // chdir
/* #define DEBUG 1 */
using namespace std;
void error(
const string& main,
const string& sub,
const string& suggest,
const string& header)
{
cout << "Status: 200 OK\r\n";
cout << header; /* optional additional header */
cout << "Content-Type: text/html\r\n\r\n";
cout << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
<< "<html>\r\n"
<< " <head><title>Lurker - "
<< xmlEscape << main
<< "</title></head>\r\n"
<< " <body>\r\n"
<< " <h1>Lurker - failed to render page:</h1>\r\n"
<< " <h2>"
<< xmlEscape << main << " (" << xmlEscape << sub << "):</h2><p>\r\n"
<< xmlEscape << suggest << "\r\n"
<< " <p><hr>\r\n"
<< " </body>\r\n"
<< "</html>\r\n";
exit(1);
}
void tokenize(
const string& str,
vector<string>& tokens,
const string& delimiters)
{
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos)
{
tokens.push_back(str.substr(lastPos, pos - lastPos));
lastPos = str.find_first_not_of(delimiters, pos);
pos = str.find_first_of(delimiters, lastPos);
}
}
Request parse_request(const string& param)
{
string::size_type dot1 = param.rfind('.');
if (dot1 == string::npos || dot1 == 0)
error(_("Missing extension"), param,
_("An extension for the request was required, but missing"));
string::size_type dot2 = param.rfind('.', dot1-1);
// This is an attempt at backwards compatability.
// Previously lurker had no language code in the URL, but still had
// period delimited fields in message ids. The last part is always 8
// characters long, so we assume english if the country code is
// not of length in [2, 8).
// NOTE: some search URLs will still be broken (those with .s)
if (dot2 != string::npos && (dot1 - dot2 < 2+1 || dot1 - dot2 >= 8+1))
dot2 = string::npos;
Request out;
if (dot2 == string::npos)
{
out.options = param.substr(0, dot1);
out.language = "en";
out.ext = param.substr(dot1+1, string::npos);
}
else
{
out.options = param.substr(0, dot2);
out.language = param.substr(dot2+1, (dot1-dot2) - 1);
out.ext = param.substr(dot1+1, string::npos);
}
if (out.ext.length() < 3 || out.ext.length() > 6)
error(_("Bogus extension"), out.ext,
_("The extension is not 3-6 characters long."));
for (string::size_type i = 0; i < out.ext.length(); ++i)
if ((out.ext[i] < 'a' || out.ext[i] > 'z') &&
(out.ext[i] < '0' || out.ext[i] > '9'))
error(_("Bogus extension"), out.ext,
_("Not simple lower-case alphanumeric letters."));
if (!lstring::locale_normalize(out.language))
error(_("Bogus locale"), out.language,
_("The specified locale is not valid."));
return out;
}
int main(int argc, char** argv)
{
string config, frontend, document, request, host, port, cgipath, https, ok;
const char* tmp;
#if 0
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
bind_textdomain_codeset(PACKAGE, "utf-8");
#endif
// Every document about CGI agrees these exist:
if ((tmp = getenv("SERVER_NAME" )) != 0) host = tmp;
if ((tmp = getenv("SERVER_PORT" )) != 0) port = tmp;
if ((tmp = getenv("SCRIPT_NAME" )) != 0) cgipath = tmp;
// Many CGI 'standards' seem to agree this one exists for https:
if ((tmp = getenv("HTTPS" )) != 0) https = tmp;
// CGI guarantees this in case called as an error document
if ((tmp = getenv("REDIRECT_URL")) != 0) request = tmp;
// ... however, as we aren't always called that way, try this too:
if ((tmp = getenv("REQUEST_URI" )) != 0) request = tmp;
// get an over-ridden config location
if ((tmp = getenv("REDIRECT_LURKER_CONFIG")) != 0) config = tmp;
if ((tmp = getenv("LURKER_CONFIG")) != 0) config = tmp;
// get the frontend location
if ((tmp = getenv("REDIRECT_LURKER_FRONTEND")) != 0) document = tmp;
if ((tmp = getenv("LURKER_FRONTEND")) != 0) document = tmp;
if (request == "" || host == "" || port == "" || cgipath == "")
error(_("Not invoked correctly"),
_("CGI environment variables missing"),
_("The lurker.cgi must be run as a CGI script. See the "
"INSTALL file distributed with lurker for help setting "
"up your webserver to run lurker.cgi. Lurker.cgi reads "
"the environment variables REDIRECT_URL or REQUEST_URI "
"to determine the missing file requested by the user. "
"Also, SERVER_NAME, SERVER_PORT, and SCRIPT_NAME are "
"used to build absolute redirected URLs."));
// be nice: use a default config file
if (config == "") config = DEFAULT_CONFIG_FILE;
Config cfg;
if (cfg.load(config.c_str()) != 0)
error(_("Cannot open config file"), "Config::load",
cfg.getError() +
_("\nPerhaps you should set the LURKER_CONFIG "
"environment variable to select the correct "
"config file location. See the INSTALL file for "
"help on configuring your webserver."));
if (document == "" && cfg.frontends.size() > 1)
error(_("No frontend specified"), "LURKER_FRONTEND",
_("The lurker config file lists multiple frontends, "
"however, the environment variable LURKER_FRONTEND "
"does not specify which to use. See the INSTALL file "
"for help on configuring your webserver."));
// be nice: if only one frontend, use it by default:
if (document == "" && cfg.frontends.size() == 1)
{
document = cfg.frontends.begin()->first;
}
else
{
// Simplify the path to the requested document
if ((ok = simplifyPath(document)) != "")
error(_("Bad document request"), document,
_("The path '") + ok + _("' could not be resolved while "
"attempting to determine which frontend the document "
"belongs to. Perhaps a directory does not exist?"));
}
// Look for the matching front-end
frontend = "";
Config::Frontends::const_iterator i, e;
for (i = cfg.frontends.begin(), e = cfg.frontends.end(); i != e; ++i)
{
// Either document IS the frontend path or it is a file
// contained in the frontend path.
if (i->first == document.substr(0, i->first.length()) ||
i->first + "/" == document.substr(0, i->first.length()+1))
{
frontend = i->first;
break;
}
}
if (frontend == "")
error(_("No matching frontend"), document,
_("The frontend specified in the webserver "
"configuration does not match any frontend in the "
"lurker config file."));
cfg.set_permissions(cfg.frontends[frontend]);
if (chdir(frontend.c_str()) != 0)
error(_("Cannot chdir"), frontend + ":" + strerror(errno),
_("The specified frontend path could "
"not be entered. Check the path and permissions."));
auto_ptr<ESort::Reader> db(ESort::Reader::opendb(cfg.dbdir + "/db"));
if (!db.get())
error(_("Cannot open database snapshot"), strerror(errno),
_("The configured database 'dbdir' in the config file "
"could not be opened. Typically this means that it is "
"not readable by the user which the cgi is invoked as. "
"We suggest making dbdir and all files in it readable "
"by everyone since you are serving them on a website "
"anyways."));
request = decipherHalf(request);
vector<string> tokens;
tokenize(request, tokens, "/");
if (tokens.size() < 2)
error(_("Request malformed"), "tokenize(request)",
_("The request does not have at least two directory "
"components. It must be like ..../command/param.xml"));
string param = tokens[tokens.size()-1];
string command = tokens[tokens.size()-2];
string server;
if (document != frontend &&
frontend + '/' + command + '/' + param != document)
error(_("Requested document is in error"), document,
_("The requested document does not match the file "
"lurker intends to generate: ")
+ frontend + '/' + command + '/' + param);
if (https == "on")
{
server = "https://" + host;
if (port != "443") server += ":" + port;
}
else
{
server = "http://" + host;
if (port != "80") server += ":" + port;
}
cfg.cgiUrl = server + cgipath;
string::size_type psplit;
if ((psplit = cfg.cgiUrl.rfind('/')) != string::npos)
cfg.cgiUrl.resize(psplit);
vector<string>::size_type tok;
cfg.docUrl = server;
for (tok = 0; tok < tokens.size()-2; ++tok)
cfg.docUrl += "/" + tokens[tok];
// flush all request data in case user made it huge to be an ass
tokens.clear();
request = "";
config = "";
cfg.command = command;
if (command == "message") return handle_message(cfg, db.get(), param);
else if (command == "thread") return handle_thread (cfg, db.get(), param);
else if (command == "mindex") return handle_mindex (cfg, db.get(), param);
else if (command == "splash") return handle_splash (cfg, db.get(), param);
else if (command == "search") return handle_search (cfg, db.get(), param);
else if (command == "attach") return handle_attach (cfg, db.get(), param);
else if (command == "mbox") return handle_mbox (cfg, db.get(), param);
else if (command == "list") return handle_list (cfg, db.get(), param);
else if (command == "zap") return handle_zap (cfg, db.get(), param);
else
error(_("Bad command"), command,
_("The requested command is not supported."));
}
|