Introduction
Introduction Statistics Contact Development Disclaimer Help
pubsub_cgi.c - pubsubhubbubblub - pubsubhubbub client implementation
git clone git://git.codemadness.org/pubsubhubbubblub
Log
Files
Refs
README
LICENSE
---
pubsub_cgi.c (11386B)
---
1 #include <sys/stat.h>
2
3 #include <ctype.h>
4 #include <err.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <time.h>
11 #include <unistd.h>
12
13 #ifdef __OpenBSD__
14 #include <unistd.h>
15 #else
16 #define pledge(p1,p2) 0
17 #define unveil(p1,p2) 0
18 #endif
19
20 #include "hmac_sha1.h"
21
22 static const char *relpath = "/pubsub/";
23
24 #define DATADIR "/pubsub-data"
25
26 static const char *configdir = DATADIR "/config";
27 static const char *datadir = DATADIR "/feeds";
28 static const char *tmpdir = DATADIR "/tmp";
29 static const char *logfile = DATADIR "/log";
30 static time_t now;
31
32 char *
33 readfile(const char *path)
34 {
35 static char buf[256];
36 FILE *fp;
37
38 if (!(fp = fopen(path, "rb")))
39 goto err;
40 if (!fgets(buf, sizeof(buf), fp))
41 goto err;
42 fclose(fp);
43 buf[strcspn(buf, "\n")] = '\0';
44 return buf;
45
46 err:
47 if (fp)
48 fclose(fp);
49 return NULL;
50 }
51
52 int
53 hexdigit(int c)
54 {
55 if (c >= '0' && c <= '9')
56 return c - '0';
57 else if (c >= 'A' && c <= 'F')
58 return c - 'A' + 10;
59 else if (c >= 'a' && c <= 'f')
60 return c - 'a' + 10;
61
62 return 0;
63 }
64
65 /* decode until NUL separator or end of "key". */
66 int
67 decodeparamuntilend(char *buf, size_t bufsiz, const char *s, int end)
68 {
69 size_t i;
70
71 if (!bufsiz)
72 return -1;
73
74 for (i = 0; *s && *s != end; s++) {
75 switch (*s) {
76 case '%':
77 if (i + 3 >= bufsiz)
78 return -1;
79 if (!isxdigit((unsigned char)*(s+1)) ||
80 !isxdigit((unsigned char)*(s+2)))
81 return -1;
82 buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+…
83 s += 2;
84 break;
85 case '+':
86 if (i + 1 >= bufsiz)
87 return -1;
88 buf[i++] = ' ';
89 break;
90 default:
91 if (i + 1 >= bufsiz)
92 return -1;
93 buf[i++] = *s;
94 break;
95 }
96 }
97 buf[i] = '\0';
98
99 return i;
100 }
101
102 /* decode until NUL separator or end of "key". */
103 int
104 decodeparam(char *buf, size_t bufsiz, const char *s)
105 {
106 return decodeparamuntilend(buf, bufsiz, s, '&');
107 }
108
109 char *
110 getparam(const char *query, const char *s)
111 {
112 const char *p, *last = NULL;
113 size_t len;
114
115 len = strlen(s);
116 for (p = query; (p = strstr(p, s)); p += len) {
117 if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1…
118 last = p + len + 1;
119 }
120
121 return (char *)last;
122 }
123
124 const char *
125 httpstatusmsg(int code)
126 {
127 switch (code) {
128 case 200: return "200 OK";
129 case 202: return "202 Accepted";
130 case 400: return "400 Bad Request";
131 case 403: return "403 Forbidden";
132 case 404: return "404 Not Found";
133 case 500: return "500 Internal Server Error";
134 }
135 return NULL;
136 }
137
138 void
139 httpstatus(int code)
140 {
141 const char *msg;
142
143 if ((msg = httpstatusmsg(code)))
144 printf("Status: %s\r\n", msg);
145 }
146
147 void
148 httperror(int code, const char *s)
149 {
150 httpstatus(code);
151 fputs("Content-Type: text/plain; charset=utf-8\r\n", stdout);
152 fputs("\r\n", stdout);
153 if (s)
154 printf("%s: %s\r\n", httpstatusmsg(code), s);
155 else
156 printf("%s\r\n", httpstatusmsg(code));
157 exit(0);
158 }
159
160 void
161 badrequest(const char *s)
162 {
163 httperror(400, s);
164 }
165
166 void
167 forbidden(const char *s)
168 {
169 httperror(403, s);
170 }
171
172 void
173 notfound(const char *s)
174 {
175 httperror(404, s);
176 }
177
178 void
179 servererror(const char *s)
180 {
181 httperror(500, s);
182 }
183
184 void
185 logrequest(const char *feedname, const char *filename, const char *signa…
186 {
187 FILE *fp;
188
189 /* file format: timestamp TAB feedname TAB data-filename */
190 if (!(fp = fopen(logfile, "a")))
191 servererror("cannot write data");
192 fprintf(fp, "%lld\t", (long long)now);
193 fputs(feedname, fp);
194 fputs("\t", fp);
195 fputs(filename, fp);
196 fputs("\t", fp);
197 fputs(signature, fp);
198 fputs("\n", fp);
199 fclose(fp);
200 }
201
202 char *
203 contenttypetoext(const char *s)
204 {
205 return "xml"; /* for now just support XML, for RSS and Atom */
206 }
207
208 int
209 main(void)
210 {
211 FILE *fpdata;
212 char challenge[256], mode[32] = "", signature[128] = "";
213 char requesturi[4096], requesturidecoded[4096];
214 char feedname[256], token[256] = "";
215 char filename[PATH_MAX], tmpfilename[PATH_MAX];
216 char configpath[PATH_MAX], feedpath[PATH_MAX], secretpath[PATH_M…
217 char tokenpath[PATH_MAX];
218 char *contentlength = "", *contenttype = "", *method = "GET", *q…
219 char *p, *fileext, *tmp;
220 char buf[4096];
221 size_t n, total;
222 long long ll;
223 int i, j, fd, r;
224 /* HMAC */
225 SHA_CTX ctx;
226 unsigned char key_opad[65]; /* outer padding - key XORd with opa…
227 unsigned char *key;
228 size_t key_len;
229 unsigned char digest[SHA_DIGEST_LENGTH];
230 unsigned char inputdigest[SHA_DIGEST_LENGTH];
231
232 if (unveil(DATADIR, "rwc") == -1)
233 err(1, "unveil");
234 if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
235 err(1, "pledge");
236
237 if ((tmp = getenv("CONTENT_TYPE")))
238 contenttype = tmp;
239 if ((tmp = getenv("CONTENT_LENGTH")))
240 contentlength = tmp;
241 if ((tmp = getenv("REQUEST_METHOD")))
242 method = tmp;
243 if ((tmp = getenv("QUERY_STRING")))
244 query = tmp;
245
246 /* "8. Authenticated Content Distribution" */
247 if ((p = getenv("HTTP_X_HUB_SIGNATURE"))) {
248 r = snprintf(signature, sizeof(signature), "%s", p);
249 if (r < 0 || (size_t)r >= sizeof(signature))
250 badrequest("invalid signature (truncated)");
251
252 /* accept sha1=digest or sha=digest */
253 if ((tmp = strstr(signature, "sha1=")))
254 tmp += sizeof("sha1=") - 1;
255 else if ((tmp = strstr(signature, "sha=")))
256 tmp += sizeof("sha=") - 1;
257 if (tmp) {
258 for (p = tmp, i = 0; *p; p++, i++) {
259 if (!isxdigit((unsigned char)*p))
260 break;
261 }
262 }
263 if (tmp && !*p && i == (SHA_DIGEST_LENGTH * 2)) {
264 for (i = 0, j = 0, p = tmp; i < SHA_DIGEST_LENGT…
265 inputdigest[i] = (hexdigit(p[j]) << 4) |
266 hexdigit(p[j + 1]);
267 }
268 } else {
269 badrequest("invalid hash format");
270 }
271 }
272
273 if (!(p = getenv("REQUEST_URI")))
274 p = "";
275 snprintf(requesturi, sizeof(requesturi), "%s", p);
276 if ((p = strchr(requesturi, '?')))
277 *p = '\0'; /* remove query string */
278
279 if (decodeparamuntilend(requesturidecoded, sizeof(requesturideco…
280 badrequest("request URI");
281
282 p = requesturidecoded;
283 if (strncmp(p, relpath, strlen(relpath)))
284 forbidden("invalid relative path");
285 p += strlen(relpath);
286
287 /* first part of path of request URI is the feedname, last part …
288 if ((tmp = strchr(p, '/'))) {
289 *tmp = '\0'; /* temporary NUL terminate */
290
291 r = snprintf(feedname, sizeof(feedname), "%s", p);
292 if (r < 0 || (size_t)r >= sizeof(feedname))
293 servererror("path truncated");
294
295 r = snprintf(token, sizeof(token), "%s", tmp + 1);
296 if (r < 0 || (size_t)r >= sizeof(token))
297 servererror("path truncated");
298
299 *tmp = '/'; /* restore NUL byte to '/' */
300 } else {
301 r = snprintf(feedname, sizeof(feedname), "%s", p);
302 if (r < 0 || (size_t)r >= sizeof(feedname))
303 servererror("path truncated");
304 }
305 if (strstr(feedname, ".."))
306 badrequest("invalid feed name");
307
308 /* check if configdir of feedname exists, else skip request and …
309 r = snprintf(configpath, sizeof(configpath), "%s/%s", configdir,…
310 if (r < 0 || (size_t)r >= sizeof(configpath))
311 servererror("path truncated");
312 if (access(configpath, X_OK) == -1)
313 notfound("feed entrypoint does not exist");
314
315 r = snprintf(tokenpath, sizeof(tokenpath), "%s/%s/token", config…
316 if (r < 0 || (size_t)r >= sizeof(tokenpath))
317 servererror("path truncated");
318 if ((tmp = readfile(tokenpath))) {
319 if (strcmp(tmp, token))
320 forbidden("missing or incorrect token in path");
321 }
322
323 if (!strcasecmp(method, "POST")) {
324 if (!feedname[0])
325 badrequest("feed name part of path is missing");
326
327 /* read secret, initialize for HMAC and data signature v…
328 r = snprintf(secretpath, sizeof(secretpath), "%s/%s/secr…
329 if (r < 0 || (size_t)r >= sizeof(secretpath))
330 servererror("path truncated");
331 key = readfile(secretpath);
332 if (key && !signature[0])
333 forbidden("requires signature header X-Hub-Signa…
334
335 if (key) {
336 key_len = strlen(key);
337 hmac_sha1_init(&ctx, key, key_len, key_opad, siz…
338 }
339
340 /* temporary file with random characters */
341 if ((now = time(NULL)) == (time_t)-1)
342 servererror("cannot get current time");
343 r = snprintf(tmpfilename, sizeof(tmpfilename), "%s/%s/%l…
344 if (r < 0 || (size_t)r >= sizeof(tmpfilename))
345 servererror("path truncated");
346
347 if ((fd = mkstemp(tmpfilename)) == -1)
348 servererror("cannot create tmpfilename");
349 if (!(fpdata = fdopen(fd, "wb")))
350 servererror(tmpfilename);
351
352 total = 0;
353 while ((n = fread(buf, 1, sizeof(buf), stdin)) == sizeof…
354 if (fwrite(buf, 1, n, fpdata) != n)
355 break;
356 if (key)
357 SHA1_Update(&ctx, buf, n); /* hash data …
358 total += n;
359 }
360 if (n) {
361 fwrite(buf, 1, n, fpdata);
362 if (key)
363 SHA1_Update(&ctx, buf, n);
364 total += n;
365 }
366 if (ferror(stdin)) {
367 fclose(fpdata);
368 unlink(tmpfilename);
369 servererror("cannot process POST message: read e…
370 }
371 if (fflush(fpdata) || ferror(fpdata)) {
372 fclose(fpdata);
373 unlink(tmpfilename);
374 servererror("cannot process POST message: write …
375 }
376 fclose(fpdata);
377 chmod(tmpfilename, 0644);
378
379 /* if Content-Length is set then check if it matches */
380 if (contentlength[0]) {
381 ll = strtoll(contentlength, NULL, 10);
382 if (ll < 0 || (size_t)ll != total) {
383 unlink(tmpfilename);
384 badrequest("Content-Length does not matc…
385 }
386 }
387
388 if (key) {
389 /* finalize signature digest */
390 hmac_sha1_final(&ctx, key_opad, digest);
391
392 /* compare digest */
393 if (memcmp(inputdigest, digest, sizeof(digest)))…
394 unlink(tmpfilename);
395 forbidden("invalid digest for data");
396 }
397 }
398
399 /* use part of basename of the random temp file as the f…
400 if (!(tmp = strrchr(tmpfilename, '/')))
401 servererror("invalid path"); /* cannot happen */
402 r = snprintf(feedpath, sizeof(feedpath), "%s/%s", datadi…
403 if (r < 0 || (size_t)r >= sizeof(feedpath))
404 servererror("path truncated");
405 fileext = contenttypetoext(contenttype);
406 r = snprintf(filename, sizeof(filename), "%s/%s%s%s", fe…
407 fileext[0] ? "." : "", fileext);
408 if (r < 0 || (size_t)r >= sizeof(filename))
409 servererror("path truncated");
410
411 if ((r = rename(tmpfilename, filename)) != 0) {
412 unlink(filename);
413 unlink(tmpfilename);
414 servererror("cannot process POST message: failed…
415 }
416 chmod(filename, 0644);
417
418 httpstatus(200);
419 fputs("Content-Type: text/plain; charset=utf-8\r\n", std…
420 fputs("\r\n", stdout);
421
422 /* output stored file: feedname, basename of the file */
423 if ((tmp = strrchr(filename, '/')))
424 tmp++;
425 else
426 tmp = "";
427 printf("%s/%s\n", feedname, tmp);
428
429 /* write to a log file, this could be a pipe or used wit…
430 logrequest(feedname, tmp, signature);
431
432 return 0;
433 }
434
435 if ((p = getparam(query, "hub.mode"))) {
436 if (decodeparam(mode, sizeof(mode), p) == -1)
437 badrequest("hub.mode");
438 }
439
440 if (!strcmp(mode, "subscribe") || !strcmp(mode, "unsubscribe")) {
441 if ((p = getparam(query, "hub.challenge"))) {
442 if (decodeparam(challenge, sizeof(challenge), p)…
443 badrequest("hub.challenge");
444 }
445 if (!challenge[0])
446 badrequest("hub.challenge is required, but is mi…
447
448 httpstatus(202);
449 fputs("Content-Type: text/plain; charset=utf-8\r\n", std…
450 fputs("\r\n", stdout);
451 printf("%s\r\n", challenge);
452 return 0;
453 } else if (mode[0]) {
454 badrequest("hub.mode: only subscribe or unsubscribe is s…
455 }
456
457 httpstatus(200);
458 fputs("Content-Type: text/plain; charset=utf-8\r\n", stdout);
459 fputs("\r\n", stdout);
460 printf("pubsubhubbubblub running perfectly and flapping gracious…
461
462 return 0;
463 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.