• R/O
  • SSH
  • HTTPS

ogup: 提交


Commit MetaInfo

修訂2 (tree)
時間2020-12-24 18:47:17
作者mateuszviste

Log Message

imported ver 20190218 to tags

Change Summary

差異

--- tags/ogup-20190218/cmods/csv.c (nonexistent)
+++ tags/ogup-20190218/cmods/csv.c (revision 2)
@@ -0,0 +1,43 @@
1+/*
2+ * CSV-reading C module
3+ *
4+ * Copyright (C) 2019 Mateusz Viste
5+ *
6+ */
7+
8+/* reads a csv line from file. returns 0 on success, -1 on EOF or error. */
9+static int csvreadline(char *buf, unsigned short maxlinelen, char **ptrs, int maxptrs, FILE *fd) {
10+ int c;
11+ int i;
12+ int t;
13+ int blen = 0;
14+ /* read the line into buf */
15+ for (;;) {
16+ c = fgetc(fd);
17+ if ((c < 0) && (blen == 0)) return(-1);
18+ if ((c < 0) || (c == '\n')) break;
19+ if (c == '\r') continue;
20+ buf[blen++] = c;
21+ if (blen == maxlinelen) return(-1);
22+ }
23+ buf[blen] = 0;
24+
25+ /* set up pointers and terminate fields */
26+ for (i = 0; i < maxptrs; i++) ptrs[i] = buf + blen; /* preinit to empty */
27+ ptrs[0] = buf;
28+ t = 1;
29+ for (i = 0;; i++) {
30+ switch (buf[i]) {
31+ case ',':
32+ buf[i] = 0;
33+ ptrs[t] = buf + i + 1;
34+ if ((t + 1) < maxptrs) t++;
35+ break;
36+ case '\r':
37+ case '\n':
38+ case 0:
39+ buf[i] = 0;
40+ return(0);
41+ }
42+ }
43+}
--- tags/ogup-20190218/frontend/list/gophermap.c (nonexistent)
+++ tags/ogup-20190218/frontend/list/gophermap.c (revision 2)
@@ -0,0 +1,96 @@
1+/*
2+ * OGUP servers list gophermap
3+ *
4+ * Copyright (C) 2019 Mateusz Viste
5+ */
6+
7+
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <sys/stat.h>
11+#include <time.h>
12+#include <unistd.h>
13+
14+
15+#include "../../cmods/csv.c"
16+
17+
18+unsigned long getcount(const char *fname, time_t *modtime, char *errstr) {
19+ FILE *f;
20+ unsigned long count;
21+ char buff[32];
22+ int i;
23+ struct stat sb;
24+
25+ f = fopen(fname, "rb");
26+ if (f == NULL) {
27+ sprintf(errstr, "failed to open db file");
28+ return(0);
29+ }
30+ if (fstat(fileno(f), &sb) != 0) {
31+ sprintf(errstr, "fstat() failed on db count file");
32+ return(0);
33+ }
34+ *modtime = sb.st_mtime;
35+ i = fread(buff, 1, sizeof(buff) - 1, f);
36+ fclose(f);
37+ if (i < 1) {
38+ sprintf(errstr, "empty count file");
39+ return(0);
40+ }
41+ buff[i] = 0;
42+ count = atol(buff);
43+
44+ if (count == 0) sprintf(errstr, "empty db");
45+
46+ return(count);
47+}
48+
49+
50+int main(void) {
51+ unsigned long count;
52+ time_t lastupdate;
53+ struct tm *lastupdatetm;
54+ char errstr[64];
55+ char csvbuff[128];
56+ char *ptrs[4];
57+ FILE *f;
58+ int i;
59+
60+ printf("i\n");
61+
62+ count = getcount("../ogupdb.cnt", &lastupdate, errstr);
63+ lastupdatetm = gmtime(&lastupdate);
64+
65+ if (count == 0) {
66+ printf("3ERROR: %s\n", errstr);
67+ } else {
68+ printf("iAs of %04d-%02d-%02d, the Observable Gopherspace Universe Project knows\n"
69+ "iabout %lu servers.\n", lastupdatetm->tm_year + 1900, lastupdatetm->tm_mon + 1, lastupdatetm->tm_mday, count);
70+ }
71+
72+ printf("i\n");
73+
74+ f = fopen("../ogupdb.dat", "rb");
75+ if (f == NULL) {
76+ printf("3ERROR: fopen() failure\n");
77+ return(0);
78+ }
79+
80+ for (;;) {
81+ i = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f);
82+ if (i != 0) break;
83+ if (atol(ptrs[2]) != 0) continue; /* ignore hosts that were offline last time I checked */
84+ if (atol(ptrs[1]) != 70) {
85+ printf("1%s:%s\t\t%s\t%s\n", ptrs[0], ptrs[1], ptrs[0], ptrs[1]);
86+ } else {
87+ printf("1%s\t\t%s\t%s\n", ptrs[0], ptrs[0], ptrs[1]);
88+ }
89+ }
90+
91+ fclose(f);
92+
93+ printf("i\ni--- [EOF] -----------------------------\n");
94+
95+ return(0);
96+}
--- tags/ogup-20190218/frontend/rnd/gophermap.c (nonexistent)
+++ tags/ogup-20190218/frontend/rnd/gophermap.c (revision 2)
@@ -0,0 +1,82 @@
1+/*
2+ * OGUP servers list gophermap
3+ *
4+ * Copyright (C) 2019 Mateusz Viste
5+ */
6+
7+
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <time.h>
11+#include <unistd.h>
12+
13+
14+#include "../../cmods/csv.c"
15+
16+
17+/* rewinds file fd to nearest start of line */
18+static void rewindtosol(FILE *fd) {
19+ int c;
20+ int iterations = 0;
21+ for (;;) {
22+ iterations++;
23+ if (ftell(fd) == 0) break;
24+ c = fgetc(fd);
25+ if (c == '\n') break;
26+ fseek(fd, -2, SEEK_CUR);
27+ if (iterations > 400) break; /* you never know */
28+ }
29+}
30+
31+
32+int main(void) {
33+ char csvbuff[128];
34+ char *ptrs[4];
35+ FILE *f;
36+ int i, count, csvres;
37+ long fsize, randpos;
38+
39+ printf("i\n");
40+
41+ f = fopen("../ogupdb.dat", "rb");
42+ if (f == NULL) {
43+ printf("3ERROR: fopen() failure\n");
44+ return(0);
45+ }
46+ fseek(f, 0, SEEK_END);
47+ fsize = ftell(f);
48+
49+ if (fsize < 16) {
50+ printf("3ERROR: fsize too low (%ld)\n", fsize);
51+ fclose(f);
52+ return(0);
53+ }
54+
55+ srand(time(NULL));
56+
57+ count = 0;
58+ for (i = 0; i < 20; i++) {
59+
60+ randpos = rand() % (fsize - 2);
61+ fseek(f, randpos, SEEK_SET);
62+
63+ /* rewind back to nearest \n or start of file */
64+ rewindtosol(f);
65+
66+ /* read entry */
67+ csvres = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f);
68+ if (csvres != 0) continue;
69+ if (atol(ptrs[2]) != 0) continue; /* ignore hosts that were offline last time I checked */
70+ if (atol(ptrs[1]) != 70) {
71+ printf("1%s:%s\t\t%s\t%s\n", ptrs[0], ptrs[1], ptrs[0], ptrs[1]);
72+ } else {
73+ printf("1%s\t\t%s\t%s\n", ptrs[0], ptrs[0], ptrs[1]);
74+ }
75+ count++;
76+ if (count == 5) break;
77+ }
78+
79+ fclose(f);
80+
81+ return(0);
82+}
--- tags/ogup-20190218/frontend/buildmaps.sh (nonexistent)
+++ tags/ogup-20190218/frontend/buildmaps.sh (revision 2)
@@ -0,0 +1,9 @@
1+#!/bin/sh
2+
3+CFLAGS="-Wall -Wextra -pedantic -std=gnu89"
4+
5+gcc $CFLAGS gophermap.c -o gophermap.cgi
6+
7+gcc $CFLAGS list/gophermap.c -o list/gophermap.cgi
8+
9+gcc $CFLAGS rnd/gophermap.c -o rnd/gophermap.cgi
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
--- tags/ogup-20190218/frontend/gophermap.c (nonexistent)
+++ tags/ogup-20190218/frontend/gophermap.c (revision 2)
@@ -0,0 +1,76 @@
1+/*
2+ * OGUP main gophermap
3+ *
4+ * Copyright (C) 2019 Mateusz Viste
5+ */
6+
7+
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <sys/stat.h>
11+#include <time.h>
12+#include <unistd.h>
13+
14+
15+unsigned long getcount(const char *fname, time_t *modtime, char *errstr) {
16+ FILE *f;
17+ unsigned long count;
18+ char buff[32];
19+ int i;
20+ struct stat sb;
21+
22+ f = fopen(fname, "rb");
23+ if (f == NULL) {
24+ sprintf(errstr, "failed to open db file");
25+ return(0);
26+ }
27+ if (fstat(fileno(f), &sb) != 0) {
28+ sprintf(errstr, "fstat() failed on db count file");
29+ return(0);
30+ }
31+ *modtime = sb.st_mtime;
32+ i = fread(buff, 1, sizeof(buff) - 1, f);
33+ fclose(f);
34+ if (i < 1) {
35+ sprintf(errstr, "empty count file");
36+ return(0);
37+ }
38+ buff[i] = 0;
39+ count = atol(buff);
40+
41+ if (count == 0) sprintf(errstr, "empty db");
42+
43+ return(count);
44+}
45+
46+
47+int main(void) {
48+ unsigned long count;
49+ time_t lastupdate;
50+ struct tm *lastupdatetm;
51+ char errstr[64];
52+
53+ printf("i\n"
54+ "i Observable Gopherspace Universe Project\n"
55+ "i\n"
56+ "iWelcome to the gopher site of the Observable Gopherspace Universe\n"
57+ "iProject (OGUP)! The OGUP is about maintaining and publishing an\n"
58+ "iindependent list of servers available in the gopherspace.\n"
59+ "i\n"
60+ "1List of servers\tlist\n"
61+ "1Show 5 random servers\trnd\n"
62+ "1About the OGUP\tabout\n"
63+ "1Support the project!\tsupport\n"
64+ "i\n");
65+
66+ count = getcount("ogupdb.cnt", &lastupdate, errstr);
67+ lastupdatetm = gmtime(&lastupdate);
68+
69+ if (count == 0) {
70+ printf("3ERROR: %s\n", errstr);
71+ } else {
72+ printf("iAs of %04d-%02d-%02d, the OGUP knows about %lu servers.\n", lastupdatetm->tm_year + 1900, lastupdatetm->tm_mon + 1, lastupdatetm->tm_mday, count);
73+ }
74+
75+ return(0);
76+}
--- tags/ogup-20190218/gopherjoker/Makefile (nonexistent)
+++ tags/ogup-20190218/gopherjoker/Makefile (revision 2)
@@ -0,0 +1,13 @@
1+#
2+# build gopherjoker
3+#
4+# Observable Gopherspace Universe Project
5+# Copyright (C) 2019 Mateusz Viste
6+#
7+
8+CC ?= gcc
9+CFLAGS ?= -g -O0 -Wall -Wextra -pedantic -std=gnu89
10+
11+all: gopherjoker
12+
13+gopherjoker: gopherjoker.c
--- tags/ogup-20190218/gopherjoker/gopherjoker.c (nonexistent)
+++ tags/ogup-20190218/gopherjoker/gopherjoker.c (revision 2)
@@ -0,0 +1,557 @@
1+/*
2+ * gopherjoker, part of the Observable Gopherspace Universe Project
3+ * Copyright (C) 2019 Mateusz Viste
4+ *
5+ * 2019-02-18: first public release
6+ *
7+ * Redistribution and use in source and binary forms, with or without
8+ * modification, are permitted provided that the following conditions are met:
9+ * 1. Redistributions of source code must retain the above copyright notice,
10+ * this list of conditions and the following disclaimer.
11+ *
12+ * 2. Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+ * POSSIBILITY OF SUCH DAMAGE.
27+ */
28+
29+#include <errno.h>
30+#include <fcntl.h>
31+#include <netdb.h> /* gethostbyname() */
32+#include <stdio.h> /* printf(), fopen(), ... */
33+#include <stdlib.h> /* calloc(), rand() */
34+#include <string.h> /* strcpy() */
35+#include <sys/types.h>
36+#include <sys/socket.h>
37+#include <time.h> /* ctime(), time_t */
38+#include <unistd.h> /* sleep() */
39+
40+#include "../cmods/csv.c"
41+
42+struct gopherlist {
43+ time_t failedsince;
44+ struct gopherlist *next;
45+ struct gopherlist *prev;
46+ unsigned short port;
47+ char *selector;
48+ char fqdn[1];
49+};
50+
51+#define WAITPERIOD 10
52+#define SAVEPERIOD 3600
53+#define NEWHOSTSLIST "/tmp/gjoker_newhosts.csv"
54+#define MAXFAILTIME (3600 * 24 * 60)
55+#define TTLINIT 64
56+
57+
58+/**************** FUNCTIONS ****************/
59+
60+
61+static void glist_free(struct gopherlist *glist) {
62+ struct gopherlist *victim;
63+ while (glist) {
64+ free(glist->selector);
65+ victim = glist;
66+ glist = glist->next;
67+ free(victim);
68+ }
69+}
70+
71+
72+/* returns pointer to dbfile name */
73+static int parseargs(int argc, char **argv, char **dbfile, char **dbcount) {
74+ int i;
75+ *dbfile = NULL;
76+ *dbcount = NULL;
77+ for (i = 1; i < argc; i++) {
78+ if ((argv[i][0] != '-') && ((*dbfile == NULL) || (*dbcount == NULL))) {
79+ if (*dbfile == NULL) {
80+ *dbfile = argv[i];
81+ } else {
82+ *dbcount = argv[i];
83+ }
84+ } else {
85+ return(-1);
86+ }
87+ }
88+ return(0);
89+}
90+
91+
92+static struct gopherlist *glist_findhost(struct gopherlist *glist, const char *host, const unsigned short port) {
93+ struct gopherlist *node;
94+ for (node = glist; node != NULL; node = node->next) {
95+ if ((node->port == port) && (strcasecmp(node->fqdn, host) == 0)) return(node);
96+ }
97+ return(NULL);
98+}
99+
100+
101+/* add new host to glist, unless said host:port pair already exists there.
102+ * returns pointer to the new (or already existing) struct. NULL on error. */
103+static struct gopherlist *glist_addnewhost(struct gopherlist **glist, unsigned long *glistlen, char *newhost, unsigned short newport, time_t failedsince) {
104+ struct gopherlist *node;
105+
106+ /* is entry in list already? */
107+ node = glist_findhost(*glist, newhost, newport);
108+ if (node != NULL) return(node);
109+
110+ /* add it */
111+ node = calloc(1, sizeof(struct gopherlist) + strlen(newhost));
112+ if (node == NULL) return(NULL);
113+ /* */
114+ strcpy(node->fqdn, newhost);
115+ node->port = newport;
116+ node->failedsince = failedsince;
117+ node->prev = NULL;
118+ node->next = *glist;
119+ if (*glist != NULL) (*glist)->prev = node;
120+ *glist = node;
121+ *glistlen += 1;
122+ return(*glist);
123+}
124+
125+
126+static struct gopherlist *loaddb(const char *fname, unsigned long *glistlen) {
127+ FILE *fd;
128+ int i;
129+ char *ptrs[4];
130+ char lbuf[256];
131+ struct gopherlist *res = NULL;
132+ *glistlen = 0;
133+ fd = fopen(fname, "rb");
134+ if (fd == NULL) return(NULL);
135+ for (;;) {
136+ i = csvreadline(lbuf, sizeof(lbuf), ptrs, 4, fd);
137+ if (i != 0) break;
138+ /* TSV line structure
139+ * 0 hostname
140+ * 1 port
141+ * 2 failedsince (time_t) */
142+
143+ if (glist_addnewhost(&res, glistlen, ptrs[0], atoi(ptrs[1]), atol(ptrs[2])) == NULL) {
144+ /* on error, free list and quit */
145+ glist_free(res);
146+ *glistlen = 0;
147+ return(NULL);
148+ }
149+ }
150+ fclose(fd);
151+ return(res);
152+}
153+
154+
155+static unsigned long savedb(const char *fname, const char *fcount, struct gopherlist *glist) {
156+ unsigned long count = 0;
157+ FILE *f;
158+ struct gopherlist *node;
159+ f = fopen(fname, "wb");
160+ if (f == NULL) return(0);
161+ for (node = glist; node != NULL; node = node->next) {
162+ count++;
163+ fprintf(f, "%s,%u,%ld\n", node->fqdn, node->port, node->failedsince);
164+ }
165+ fclose(f);
166+ f = fopen(fcount, "wb");
167+ if (f == NULL) return(0);
168+ fprintf(f, "%lu\n", count);
169+ fclose(f);
170+ return(count);
171+}
172+
173+
174+static struct gopherlist *dropnode(struct gopherlist *glist, struct gopherlist *node, unsigned long *glistlen) {
175+ if (node->prev == NULL) {
176+ glist = node->next;
177+ } else {
178+ node->prev->next = node->next;
179+ }
180+ if (node->next != NULL) {
181+ node->next->prev = node->prev;
182+ }
183+ *glistlen -= 1;
184+ return(glist);
185+}
186+
187+
188+static struct gopherlist *loadextrahosts(struct gopherlist *glist, const char *fname, unsigned long *glistlen) {
189+ FILE *f;
190+ char *ptrs[2];
191+ char buff[64];
192+ f = fopen(fname, "rb");
193+ if (f == NULL) return(glist);
194+
195+ while (csvreadline(buff, sizeof(buff), ptrs, 2, f) == 0) {
196+ int port = atoi(ptrs[1]);
197+ if ((port < 1) || (port > 0xffff)) port = 70;
198+ if (glist_addnewhost(&glist, glistlen, ptrs[0], port, 0) == NULL) break;
199+ }
200+ fclose(f);
201+
202+ return(glist);
203+}
204+
205+
206+static struct gopherlist *pickrandhostfromlist(struct gopherlist *glist, unsigned long glistlen) {
207+ static long choice;
208+ long i;
209+ if (glistlen == 0) return(NULL);
210+ choice += rand();
211+ choice %= glistlen;
212+ for (i = 0; i < choice; i++) {
213+ if (glist == NULL) return(NULL);
214+ glist = glist->next;
215+ }
216+ return(glist);
217+}
218+
219+
220+static int connect_nonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
221+ int r;
222+ struct timeval t;
223+ fd_set selset;
224+ for (;;) {
225+ r = connect(s, addr, addrlen);
226+ if (r == 0) return(0);
227+ if (errno != EINPROGRESS) return(r);
228+ FD_ZERO(&selset);
229+ FD_SET(s, &selset);
230+ t.tv_sec = timeout;
231+ t.tv_usec = 0;
232+ r = select(s + 1, NULL, &selset, NULL, &t);
233+ if (r < 0) return(-3);
234+ r = connect(s, addr, addrlen);
235+ return(r);
236+ }
237+}
238+
239+
240+static long gopher_fetch(char *buff, unsigned short buffsz, const char *host, unsigned short port, const char *selector) {
241+ int sock;
242+ int len;
243+ int flags;
244+ time_t timeout;
245+ struct addrinfo *addr, *addrptr;
246+
247+ /* resolve host & connect */
248+ if (getaddrinfo(host, NULL, NULL, &addr) != 0) return(-1);
249+ for (addrptr = addr; addrptr != NULL; addrptr = addrptr->ai_next) {
250+ struct sockaddr_in *sin;
251+ sock = socket(addrptr->ai_family, addrptr->ai_socktype, addrptr->ai_protocol);
252+ if (sock < 0) continue;
253+ /* set port */
254+ sin = (void *)(addrptr->ai_addr);
255+ sin->sin_port = htons(port);
256+ /* set socket as non-blocking */
257+ flags = fcntl(sock, F_GETFL);
258+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
259+ /* try to connect */
260+ if (connect_nonblocking(sock, addrptr->ai_addr, addrptr->ai_addrlen, 10) == 0) {
261+ break;
262+ } else { /* close sock and try next option (if any) */
263+ close(sock);
264+ }
265+ }
266+ freeaddrinfo(addr);
267+ if (addrptr == NULL) return(-2);
268+
269+ /* send selector, terminated by a CR/LF pair */
270+ if (selector != NULL) send(sock, selector, strlen(selector), MSG_MORE);
271+ send(sock, "\r\n", 2, 0);
272+
273+ /* fetch answer until end of transmission or timeout */
274+ len = 0;
275+ timeout = time(NULL) + 10;
276+ for (;;) {
277+ unsigned short spaceleft = buffsz - len;
278+ int rlen;
279+ if (time(NULL) > timeout) {
280+ len = -3;
281+ break;
282+ }
283+ if (spaceleft == 0) break; /* I'm stuffed thank you */
284+ rlen = recv(sock, buff + len, spaceleft, 0);
285+ if (rlen == 0) break; /* orderly shutdown */
286+ if (rlen < 0) { /* sock error */
287+ if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) continue;
288+ len = -3;
289+ break;
290+ }
291+ len += rlen;
292+ }
293+
294+ /* close sock and quit */
295+ close(sock);
296+ return(len);
297+}
298+
299+
300+/* read a gopher line entry and fill host, port and selector accordingly.
301+ * returns 0 on success, non-zero otherwise. */
302+static int readgline(char *host, unsigned short hostsz, unsigned short *port, char *selector, unsigned short selectorsz, const char *menu, unsigned short menulen) {
303+ char portstr[8];
304+ unsigned short i, t;
305+ /* skip first tabs (description) */
306+ for (;;) {
307+ i++;
308+ /* printf("i=%d ['%c']\n", i, menu[i]); */
309+ if (menu[i] == '\t') break;
310+ if (menu[i] == '\r') return(-2);
311+ if (menu[i] == '\n') return(-3);
312+ if (i >= menulen) return(-4);
313+ }
314+ /* read selector */
315+ for (t = 0; ; t++) {
316+ if (t >= selectorsz) return(-5);
317+ i++;
318+ if (i >= menulen) return(-6);
319+ if (menu[i] == '\r') return(-7);
320+ if (menu[i] == '\n') return(-8);
321+ selector[t] = menu[i];
322+ if (selector[t] == '\t') {
323+ selector[t] = 0;
324+ break;
325+ }
326+ }
327+ /* read hostname */
328+ for (t = 0; ; t++) {
329+ if (t >= hostsz) return(-9);
330+ i++;
331+ if (i >= menulen) return(-10);
332+ if (menu[i] == '\r') return(-11);
333+ if (menu[i] == '\n') return(-12);
334+ host[t] = menu[i];
335+ if (host[t] == '\t') {
336+ host[t] = 0;
337+ break;
338+ }
339+ }
340+ /* read port */
341+ for (t = 0; ; t++) {
342+ if (t >= sizeof(portstr)) return(-13);
343+ i++;
344+ if (i >= menulen) return(-14);
345+ portstr[t] = menu[i];
346+ if ((portstr[t] == '\t') || (portstr[t] == '\n')) {
347+ int tport;
348+ portstr[t] = 0;
349+ tport = atoi(portstr);
350+ if ((tport < 1) || (tport > 0xffff)) return(-15);
351+ *port = tport;
352+ break;
353+ }
354+ }
355+ return(0);
356+}
357+
358+
359+static int ishostvalid(const char *host) {
360+ unsigned short i;
361+ for (i = 0; host[i] != 0; i++) {
362+ if ((host[i] >= 'a') && (host[i] <= 'z')) continue;
363+ if ((host[i] >= 'A') && (host[i] <= 'Z')) continue;
364+ if ((host[i] >= '0') && (host[i] <= '9')) continue;
365+ if (host[i] == '-') continue;
366+ if (host[i] == '.') continue;
367+ return(-1);
368+ }
369+ if (i < 3) return(-10); /* host len should be at least 3 chars long */
370+ return(0);
371+}
372+
373+
374+/* parses a gopher menu and remembers all the hosts presents in its links */
375+static struct gopherlist *menu2gopherlist(const char *menu, unsigned short menulen) {
376+ long i;
377+ struct gopherlist *res = NULL;
378+ struct gopherlist *node;
379+
380+ /* DEBUG */
381+ printf("------------------------------------------------\n");
382+ for (i = 0; i < menulen; i++) printf("%c", menu[i]);
383+ printf("------------------------------------------------\n\n\n\n\n");
384+
385+ /* iterate line by line */
386+ for (i = 0; i < menulen; i++) {
387+ if (menu[i] == '1') {
388+ char host[64];
389+ char selector[128];
390+ unsigned short port;
391+
392+ if (readgline(host, sizeof(host), &port, selector, sizeof(selector), menu + i, menulen - i) == 0) {
393+ /* validate host name */
394+ if (ishostvalid(host) != 0) continue;
395+ /* */
396+ node = calloc(1, sizeof(struct gopherlist) + strlen(host));
397+ if (node == NULL) {
398+ glist_free(res);
399+ printf("ERR: OUT OF MEMORY\n");
400+ return(NULL);
401+ }
402+ node->port = port;
403+ strcpy(node->fqdn, host);
404+ node->next = res;
405+ node->selector = strdup(selector);
406+ if (res != NULL) res->prev = node;
407+ res = node;
408+ }
409+ }
410+ /* skip to next line */
411+ for (;;) {
412+ i++;
413+ if (i >= menulen) break;
414+ if (menu[i] == '\n') break;
415+ }
416+ }
417+
418+ return(res);
419+}
420+
421+
422+static void gnode_free(struct gopherlist **node) {
423+ if (*node == NULL) return;
424+ free((*node)->selector);
425+ free(*node);
426+ *node = NULL;
427+}
428+
429+
430+static struct gopherlist *gnodedup(struct gopherlist *node) {
431+ struct gopherlist *res;
432+ if (node == NULL) return(NULL);
433+ res = calloc(1, sizeof(struct gopherlist) + strlen(node->fqdn));
434+ if (res == NULL) return(NULL);
435+
436+ memcpy(res, node, sizeof(struct gopherlist) + strlen(node->fqdn));
437+ if (node->selector != NULL) res->selector = strdup(node->selector);
438+ return(res);
439+}
440+
441+
442+/**************** MAIN ****************/
443+
444+int main(int argc, char **argv) {
445+ time_t nextaction = 0;
446+ time_t nextdbsave = 0;
447+ struct gopherlist *glist, *mlist, *gnode;
448+ struct gopherlist *curhost = NULL;
449+ int ttl = 0;
450+ char buff[0xffff];
451+ long bufflen;
452+ char *dbfile, *dbfilecnt;
453+ unsigned long glistlen;
454+ int i;
455+
456+ if (parseargs(argc, argv, &dbfile, &dbfilecnt) != 0) {
457+ printf("usage: gopherjoker dbfile.csv dbcount.txt\n");
458+ return(1);
459+ }
460+
461+ /* load db file */
462+ glist = loaddb(dbfile, &glistlen);
463+
464+ /* init random engine */
465+ srand(time(NULL));
466+
467+ for (;;) {
468+ /* do not browse too fast */
469+ while (time(NULL) < nextaction) sleep(1);
470+ nextaction = time(NULL) + WAITPERIOD;
471+
472+ /* if extra manual hosts required to be added, do it now */
473+ glist = loadextrahosts(glist, NEWHOSTSLIST, &glistlen);
474+
475+ /* save the db once every hour */
476+ if (time(NULL) > nextdbsave) {
477+ unsigned long savedbcount;
478+ printf("dumping hosts lists to %s\n", dbfile);
479+ savedbcount = savedb(dbfile, dbfilecnt, glist);
480+ if (savedbcount != glistlen) {
481+ printf("ERR: savedbcount != glistlen @ %d (%lu != %lu)\n", __LINE__, savedbcount, glistlen);
482+ }
483+ nextdbsave = time(NULL) + SAVEPERIOD;
484+ }
485+
486+ /* if ttl expired, pick a new host to browse */
487+ if ((--ttl == 0) || (curhost == NULL)) {
488+ printf("TTL expired -> picking new host\n");
489+ curhost = pickrandhostfromlist(glist, glistlen);
490+ curhost = gnodedup(curhost);
491+ ttl = TTLINIT;
492+ }
493+
494+ printf("TTL=%d | glistlen=%ld\n", ttl, glistlen);
495+
496+ /* if no host, continue */
497+ if (curhost == NULL) {
498+ printf("hosts list empty. add some through " NEWHOSTSLIST ".\n");
499+ continue;
500+ } else {
501+ printf("fetching %s:%u/1%s ...\n", curhost->fqdn, curhost->port, curhost->selector);
502+ }
503+
504+ /* fetch selector */
505+ bufflen = gopher_fetch(buff, sizeof(buff), curhost->fqdn, curhost->port, curhost->selector);
506+ if (bufflen < 1) { /* fail */
507+ printf("failed\n");
508+ /* if it was about root selector, see if it's time to drop the server */
509+ if (curhost->selector == NULL) {
510+ gnode = curhost;
511+ curhost = glist_findhost(glist, curhost->fqdn, curhost->port);
512+ gnode_free(&gnode);
513+ if (curhost->failedsince == 0) {
514+ curhost->failedsince = time(NULL);
515+ } else if (time(NULL) > curhost->failedsince + MAXFAILTIME) {
516+ /* remove server from the list */
517+ printf("server removed due to long-time failure: %s:%u (failed since %s)\n", curhost->fqdn, curhost->port, ctime(&(curhost->failedsince)));
518+ glist = dropnode(glist, curhost, &glistlen);
519+ }
520+ }
521+ gnode_free(&curhost);
522+ continue;
523+ }
524+
525+ printf("ok (%ld bytes)\n", bufflen);
526+ gnode = glist_findhost(glist, curhost->fqdn, curhost->port);
527+ gnode->failedsince = 0;
528+
529+ /* menu to glist */
530+ mlist = menu2gopherlist(buff, bufflen);
531+ if (mlist == NULL) {
532+ printf("ERR: no entries found in menu\n");
533+ gnode_free(&curhost);
534+ continue;
535+ }
536+
537+ /* try adding hosts to global glist (and count 'em) */
538+ i = 0;
539+ for (gnode = mlist; gnode != NULL; gnode = gnode->next) {
540+ i++;
541+ glist_addnewhost(&glist, &glistlen, gnode->fqdn, gnode->port, 1);
542+ }
543+
544+ /* choose a random entry from mlist */
545+ gnode = pickrandhostfromlist(mlist, i);
546+ if (gnode == NULL) {
547+ printf("ERR: pickrandhostfromlist() failed @ %d\n", __LINE__);
548+ gnode_free(&curhost);
549+ continue;
550+ }
551+ curhost = gnodedup(gnode);
552+
553+ glist_free(mlist);
554+ }
555+
556+ return(0);
557+}
Show on old repository browser