• R/O
  • SSH
  • HTTPS

ogup: Commit


Commit MetaInfo

Revision8 (tree)
Time2020-12-24 18:51:39
Authormateuszviste

Log Message

imported ver 20200113 to trunk

Change Summary

Incremental Difference

--- trunk/cmods/csv.c (nonexistent)
+++ trunk/cmods/csv.c (revision 8)
@@ -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++] = (char)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+}
--- trunk/frontend/down/gophermap.c (nonexistent)
+++ trunk/frontend/down/gophermap.c (revision 8)
@@ -0,0 +1,90 @@
1+/*
2+ * OGUP "recently down" 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+static char *howold(long s) {
19+ static char buf[64];
20+ if (s < 120) {
21+ sprintf(buf, "%ld seconds", s);
22+ } else if (s < 3600) {
23+ sprintf(buf, "%ld minutes", s / 60);
24+ } else if (s < 7200) {
25+ sprintf(buf, "%ld h and %ld minutes", s / 3600, (s % 3600) / 60);
26+ } else if (s < 172800) {
27+ sprintf(buf, "%ld hours", s / 3600);
28+ } else {
29+ sprintf(buf, "%ld days", s / 86400);
30+ }
31+ return(buf);
32+}
33+
34+
35+static char *printtime(struct tm *t) {
36+ static char buf[64];
37+ sprintf(buf, "%d-%02d-%02d %02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min);
38+ return(buf);
39+}
40+
41+
42+int main(void) {
43+ time_t now, then;
44+ struct tm *t;
45+ char csvbuff[128];
46+ char *ptrs[4];
47+ char serv[32];
48+ long downcount = 0;
49+ FILE *f;
50+
51+ printf("i\n"
52+ "iThis page lists gopherspace nodes that have been observed by the OGUP to\n"
53+ "icollapse recently. All timestamps are given in terran UTC time.\n"
54+ "i\n");
55+
56+ f = fopen("../ogupdb.dat", "rb");
57+ if (f == NULL) {
58+ printf("3ERROR: fopen() failure\n");
59+ return(0);
60+ }
61+
62+ now = time(NULL);
63+ for (;;) {
64+ char *howlongago;
65+ int i = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f);
66+ if (i != 0) break;
67+ then = atol(ptrs[2]);
68+ if (then <= 1) continue;
69+ howlongago = howold(now - then);
70+ t = gmtime(&then);
71+ if (atol(ptrs[1]) != 70) {
72+ snprintf(serv, sizeof(serv), "%s:%s", ptrs[0], ptrs[1]);
73+ } else {
74+ snprintf(serv, sizeof(serv), "%s", ptrs[0]);
75+ }
76+ printf("i%-24s went down %s ago (%s)\n", ptrs[0], howlongago, printtime(t));
77+ downcount++;
78+ }
79+
80+ fclose(f);
81+
82+ if (downcount == 0) {
83+ printf("iNothing to list! All known gopherspace servers seem healthy.\n");
84+ }
85+
86+ printf("i\n"
87+ "i--- [EOF] --------------------------------------------------------------\n");
88+
89+ return(0);
90+}
--- trunk/frontend/list/gophermap.c (nonexistent)
+++ trunk/frontend/list/gophermap.c (revision 8)
@@ -0,0 +1,100 @@
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, unsigned long *countactive, unsigned long *countpending, unsigned long *countdown) {
19+ FILE *f;
20+ unsigned long count;
21+ char buff[64];
22+ char *ptrs[4];
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+ fclose(f);
32+ sprintf(errstr, "fstat() failed on db count file");
33+ return(0);
34+ }
35+ *modtime = sb.st_mtime;
36+ csvreadline(buff, sizeof(buff), ptrs, 4, f);
37+ fclose(f);
38+ count = atol(ptrs[0]);
39+ *countactive = atol(ptrs[1]);
40+ *countpending = atol(ptrs[2]);
41+ *countdown = atol(ptrs[3]);
42+
43+ if (count == 0) sprintf(errstr, "empty db");
44+
45+ return(count);
46+}
47+
48+
49+int main(void) {
50+ unsigned long count, countactive, countpending, countdown;
51+ time_t lastupdate;
52+ struct tm *lastupdatetm;
53+ char errstr[64];
54+ char csvbuff[128];
55+ char *ptrs[4];
56+ FILE *f;
57+ int i;
58+
59+ printf("i\n");
60+
61+ count = getcount("../ogupdb.cnt", &lastupdate, errstr, &countactive, &countpending, &countdown);
62+ lastupdatetm = gmtime(&lastupdate);
63+
64+ if (count == 0) {
65+ printf("3ERROR: %s\n", errstr);
66+ } else {
67+ printf("iThe Observable Gopherspace Universe Project knows about:\n"
68+ "i%5lu servers total, of which:\n"
69+ "i%5lu have been validated as operational\n"
70+ "i%5lu are down\n"
71+ "i%5lu are pending validation\n"
72+ "i\n"
73+ "iLast update: %04d-%02d-%02d %02d:%02d UTC\n", count, countactive, countdown, countpending, lastupdatetm->tm_year + 1900, lastupdatetm->tm_mon + 1, lastupdatetm->tm_mday, lastupdatetm->tm_hour, lastupdatetm->tm_min);
74+ }
75+
76+ printf("i\n");
77+
78+ f = fopen("../ogupdb.dat", "rb");
79+ if (f == NULL) {
80+ printf("3ERROR: fopen() failure\n");
81+ return(0);
82+ }
83+
84+ for (;;) {
85+ i = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f);
86+ if (i != 0) break;
87+ if (atol(ptrs[2]) != 0) continue; /* ignore hosts that were offline last time I checked */
88+ if (atol(ptrs[1]) != 70) {
89+ printf("1%s:%s\t\t%s\t%s\n", ptrs[0], ptrs[1], ptrs[0], ptrs[1]);
90+ } else {
91+ printf("1%s\t\t%s\t%s\n", ptrs[0], ptrs[0], ptrs[1]);
92+ }
93+ }
94+
95+ fclose(f);
96+
97+ printf("i\ni--- [EOF] -----------------------------\n");
98+
99+ return(0);
100+}
--- trunk/frontend/rnd/gophermap.c (nonexistent)
+++ trunk/frontend/rnd/gophermap.c (revision 8)
@@ -0,0 +1,92 @@
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+#define DEBUG 0
18+
19+
20+/* rewinds file fd to nearest start of line */
21+static void rewindtosol(FILE *fd) {
22+ int c;
23+ int iterations = 0;
24+ for (;;) {
25+ iterations++;
26+ if (ftell(fd) == 0) break;
27+ c = fgetc(fd);
28+ if (c == '\n') break;
29+ fseek(fd, -2, SEEK_CUR);
30+ if (iterations > 400) break; /* you never know */
31+ }
32+}
33+
34+
35+int main(void) {
36+ char csvbuff[128];
37+ char *ptrs[4];
38+ FILE *f;
39+ int i, count, csvres;
40+ long fsize, randpos;
41+
42+ printf("i\n");
43+
44+ f = fopen("../ogupdb.dat", "rb");
45+ if (f == NULL) {
46+ printf("3ERROR: fopen() failure\n");
47+ return(0);
48+ }
49+ fseek(f, 0, SEEK_END);
50+ fsize = ftell(f);
51+
52+ if (fsize < 16) {
53+ printf("3ERROR: fsize too low (%ld)\n", fsize);
54+ fclose(f);
55+ return(0);
56+ }
57+
58+ srand(time(NULL));
59+
60+ count = 0;
61+ for (i = 0; i < 50; i++) {
62+
63+ randpos = rand() % (fsize - 2);
64+ fseek(f, randpos, SEEK_SET);
65+
66+ /* rewind back to nearest \n or start of file */
67+ rewindtosol(f);
68+
69+ /* read entry */
70+ csvres = csvreadline(csvbuff, sizeof(csvbuff), ptrs, 4, f);
71+ if (csvres != 0) {
72+ if (DEBUG) printf("icsvreadline() != 0\n");
73+ continue;
74+ }
75+ /* ignore hosts that were offline last time I checked */
76+ if (atol(ptrs[2]) != 0) {
77+ if (DEBUG) printf("iatol(ptrs[2]) != 0 (%ld, '%s')\n", atol(ptrs[2]), ptrs[0]);
78+ continue;
79+ }
80+ if (atol(ptrs[1]) != 70) {
81+ printf("1%s:%s\t\t%s\t%s\n", ptrs[0], ptrs[1], ptrs[0], ptrs[1]);
82+ } else {
83+ printf("1%s\t\t%s\t%s\n", ptrs[0], ptrs[0], ptrs[1]);
84+ }
85+ count++;
86+ if (count == 5) break;
87+ }
88+
89+ fclose(f);
90+
91+ return(0);
92+}
--- trunk/frontend/gophermap.c (nonexistent)
+++ trunk/frontend/gophermap.c (revision 8)
@@ -0,0 +1,79 @@
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+#include "../cmods/csv.c"
16+
17+
18+unsigned long getcount(const char *fname, time_t *modtime, char *errstr, unsigned long *countactive, unsigned long *countpending, unsigned long *countdown) {
19+ FILE *f;
20+ unsigned long count;
21+ char buff[64];
22+ char *ptrs[4];
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+ fclose(f);
32+ sprintf(errstr, "fstat() failed on db count file");
33+ return(0);
34+ }
35+ *modtime = sb.st_mtime;
36+ csvreadline(buff, sizeof(buff), ptrs, 4, f);
37+ fclose(f);
38+ count = atol(ptrs[0]);
39+ *countactive = atol(ptrs[1]);
40+ *countpending = atol(ptrs[2]);
41+ *countdown = atol(ptrs[3]);
42+
43+ if (count == 0) sprintf(errstr, "empty db");
44+
45+ return(count);
46+}
47+
48+
49+int main(void) {
50+ unsigned long count, countactive, countpending, countdown;
51+ time_t lastupdate;
52+ struct tm *lastupdatetm;
53+ char errstr[64];
54+
55+ printf("i\n"
56+ "i Observable Gopherspace Universe Project\n"
57+ "i\n"
58+ "iWelcome to the gopher site of the Observable Gopherspace Universe\n"
59+ "iProject (OGUP)! The OGUP is about maintaining and publishing an\n"
60+ "iindependent list of servers available in the gopherspace.\n"
61+ "i\n"
62+ "1List of servers\tlist\n"
63+ "1Show 5 random servers\trnd\n"
64+ "1Recently down servers\tdown\n"
65+ "1About the OGUP\tabout\n"
66+ "1Support the project!\tsupport\n"
67+ "i\n");
68+
69+ count = getcount("ogupdb.cnt", &lastupdate, errstr, &countactive, &countpending, &countdown);
70+ lastupdatetm = gmtime(&lastupdate);
71+
72+ if (count == 0) {
73+ printf("3ERROR: %s\n", errstr);
74+ } else {
75+ printf("iAs of %04d-%02d-%02d, the OGUP knows about %lu active servers.\n", lastupdatetm->tm_year + 1900, lastupdatetm->tm_mon + 1, lastupdatetm->tm_mday, countactive);
76+ }
77+
78+ return(0);
79+}
--- trunk/gopherjoker/Makefile (nonexistent)
+++ trunk/gopherjoker/Makefile (revision 8)
@@ -0,0 +1,17 @@
1+#
2+# build gopherjoker
3+#
4+# Observable Gopherspace Universe Project
5+# Copyright (C) 2019-2020 Mateusz Viste
6+#
7+
8+CC = clang
9+CFLAGS = -g -O2 -Wall -Wextra -pedantic -std=gnu89
10+CFLAGS += -Weverything -Wno-disabled-macro-expansion -Wno-padded
11+
12+all: gopherjoker
13+
14+gopherjoker: gopherjoker.o
15+
16+clean:
17+ rm -f gopherjoker *.o
--- trunk/gopherjoker/gopherjoker.c (nonexistent)
+++ trunk/gopherjoker/gopherjoker.c (revision 8)
@@ -0,0 +1,666 @@
1+/*
2+ * gopherjoker, part of the Observable Gopherspace Universe Project
3+ * Copyright (C) 2019-2020 Mateusz Viste
4+ *
5+ * 2020-01-13: added recent history so joker won't revisit places too often
6+ * 2020-01-13: variety of minor fixes and casts to shut clang warnings
7+ * 2019-03-09: hosts with no menu entries are kept in db - but as IP only
8+ * 2019-02-27: host is removed if its main menu has no reference to itself
9+ * 2019-02-20: fixed null ptr dereference, added a 'down' count to countfile
10+ * 2019-02-19: countfile contains 3 values: total, active and pending servers
11+ * 2019-02-18: first public release
12+ *
13+ * Redistribution and use in source and binary forms, with or without
14+ * modification, are permitted provided that the following conditions are met:
15+ * 1. Redistributions of source code must retain the above copyright notice,
16+ * this list of conditions and the following disclaimer.
17+ *
18+ * 2. Redistributions in binary form must reproduce the above copyright
19+ * notice, this list of conditions and the following disclaimer in the
20+ * documentati.on and/or other materials provided with the distribution.
21+ *
22+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+ * POSSIBILITY OF SUCH DAMAGE.
33+ */
34+
35+#include <arpa/inet.h> /* inet_pton() */
36+#include <ctype.h> /* tolower() */
37+#include <errno.h>
38+#include <fcntl.h>
39+#include <netdb.h> /* gethostbyname() */
40+#include <stdio.h> /* printf(), fopen(), ... */
41+#include <stdlib.h> /* calloc(), rand() */
42+#include <string.h> /* strcpy() */
43+#include <sys/types.h>
44+#include <sys/socket.h>
45+#include <time.h> /* ctime(), time_t */
46+#include <unistd.h> /* sleep() */
47+
48+#include "../cmods/csv.c"
49+
50+struct gopherlist {
51+ time_t failedsince;
52+ struct gopherlist *next;
53+ struct gopherlist *prev;
54+ unsigned short port;
55+ char *selector;
56+ char fqdn[1];
57+};
58+
59+#define WAITPERIOD 20
60+#define SAVEPERIOD 3600
61+#define NEWHOSTSLIST "/tmp/gjoker_newhosts.csv"
62+#define MAXFAILTIME (3600 * 24 * 60)
63+#define TTLINIT 64
64+
65+
66+/* used to avoid going back to same url too often */
67+#define RECENT_HISTORY_SZ 64
68+static uint32_t glob_recenthistory[RECENT_HISTORY_SZ];
69+static int glob_recenthistoryptr = 0;
70+
71+
72+/**************** FUNCTIONS ****************/
73+
74+
75+/* rotate an uint32_t value by 1 bit left */
76+static void rotl(uint32_t *u) {
77+ uint32_t bit;
78+ bit = *u & 1;
79+ *u <<= 1;
80+ *u |= bit;
81+}
82+
83+
84+/* compute hash of a glist node (ie. a host, port, selector tuple) - used to
85+ * determine the identity of a glist entry. this is a somewhat specialized
86+ * version of a BSD sum. */
87+static uint32_t gnodehash(const struct gopherlist *node) {
88+ uint32_t res = 0;
89+ int i;
90+ /* start with port */
91+ res = node->port;
92+ /* add host (case-insensitive) */
93+ for (i = 0; node->fqdn[i] != 0; i++) {
94+ rotl(&res);
95+ res ^= (uint32_t)tolower(node->fqdn[i]);
96+ }
97+ /* add selector */
98+ for (i = 0; (node->selector != NULL) && (node->selector[i] != 0); i++) {
99+ rotl(&res);
100+ res ^= (uint32_t)node->selector[i];
101+ }
102+ return(res);
103+}
104+
105+
106+/* returns 0 on success (glist added to recent history), -1 on error (entry
107+ * already present) */
108+static int add_glist_to_recent_history_if_not_present(const struct gopherlist *node) {
109+ uint32_t hash;
110+ int i;
111+ if (node == NULL) return(-1);
112+ hash = gnodehash(node);
113+ /* is it in history already? */
114+ for (i = 0; i < RECENT_HISTORY_SZ; i++) {
115+ if (glob_recenthistory[i] == hash) return(-1);
116+ }
117+ /* add it */
118+ glob_recenthistory[glob_recenthistoryptr] = hash;
119+ glob_recenthistoryptr++; /* advance ptr */
120+ glob_recenthistoryptr %= RECENT_HISTORY_SZ;
121+ return(0);
122+}
123+
124+
125+static void glist_free(struct gopherlist *glist) {
126+ struct gopherlist *victim;
127+ while (glist) {
128+ free(glist->selector);
129+ victim = glist;
130+ glist = glist->next;
131+ free(victim);
132+ }
133+}
134+
135+
136+/* returns pointer to dbfile name */
137+static int parseargs(int argc, char **argv, char **dbfile, char **dbcount) {
138+ int i;
139+ *dbfile = NULL;
140+ *dbcount = NULL;
141+ for (i = 1; i < argc; i++) {
142+ if ((argv[i][0] != '-') && ((*dbfile == NULL) || (*dbcount == NULL))) {
143+ if (*dbfile == NULL) {
144+ *dbfile = argv[i];
145+ } else {
146+ *dbcount = argv[i];
147+ }
148+ } else {
149+ return(-1);
150+ }
151+ }
152+ return(0);
153+}
154+
155+
156+static struct gopherlist *glist_findhost(struct gopherlist *glist, const char *host, const unsigned short port) {
157+ struct gopherlist *node;
158+ for (node = glist; node != NULL; node = node->next) {
159+ if ((node->port == port) && (strcasecmp(node->fqdn, host) == 0)) return(node);
160+ }
161+ return(NULL);
162+}
163+
164+
165+/* add new host to glist, unless said host:port pair already exists there.
166+ * returns pointer to the new (or already existing) struct. NULL on error. */
167+static struct gopherlist *glist_addnewhost(struct gopherlist **glist, unsigned long *glistlen, char *newhost, unsigned short newport, time_t failedsince) {
168+ struct gopherlist *node;
169+
170+ /* is entry in list already? */
171+ node = glist_findhost(*glist, newhost, newport);
172+ if (node != NULL) return(node);
173+
174+ /* add it */
175+ node = calloc(1, sizeof(struct gopherlist) + strlen(newhost));
176+ if (node == NULL) return(NULL);
177+ /* */
178+ strcpy(node->fqdn, newhost);
179+ node->port = newport;
180+ node->failedsince = failedsince;
181+ node->prev = NULL;
182+ node->next = *glist;
183+ if (*glist != NULL) (*glist)->prev = node;
184+ *glist = node;
185+ *glistlen += 1;
186+ return(*glist);
187+}
188+
189+
190+static struct gopherlist *loaddb(const char *fname, unsigned long *glistlen) {
191+ FILE *fd;
192+ int i;
193+ char *ptrs[4];
194+ char lbuf[256];
195+ struct gopherlist *res = NULL;
196+ *glistlen = 0;
197+ fd = fopen(fname, "rb");
198+ if (fd == NULL) return(NULL);
199+ for (;;) {
200+ i = csvreadline(lbuf, sizeof(lbuf), ptrs, 4, fd);
201+ if (i != 0) break;
202+ /* TSV line structure
203+ * 0 hostname
204+ * 1 port
205+ * 2 failedsince (time_t) */
206+
207+ if (glist_addnewhost(&res, glistlen, ptrs[0], (unsigned short)atoi(ptrs[1]), atol(ptrs[2])) == NULL) {
208+ /* on error, free list and quit */
209+ glist_free(res);
210+ *glistlen = 0;
211+ return(NULL);
212+ }
213+ }
214+ fclose(fd);
215+ return(res);
216+}
217+
218+
219+static unsigned long savedb(const char *fname, const char *fcount, struct gopherlist *glist) {
220+ unsigned long count = 0, countactive = 0, countpending = 0, countdown = 0;
221+ FILE *f;
222+ struct gopherlist *node;
223+ f = fopen(fname, "wb");
224+ if (f == NULL) return(0);
225+ for (node = glist; node != NULL; node = node->next) {
226+ count++;
227+ if (node->failedsince == 0) countactive++;
228+ if (node->failedsince == 1) countpending++;
229+ if (node->failedsince > 1) countdown++;
230+ fprintf(f, "%s,%u,%ld\n", node->fqdn, node->port, node->failedsince);
231+ }
232+ fclose(f);
233+ f = fopen(fcount, "wb");
234+ if (f == NULL) return(0);
235+ fprintf(f, "%lu,%lu,%lu,%lu\n", count, countactive, countpending, countdown);
236+ fclose(f);
237+ return(count);
238+}
239+
240+
241+static struct gopherlist *dropnode(struct gopherlist *glist, struct gopherlist *node, unsigned long *glistlen) {
242+ if (node->prev == NULL) {
243+ glist = node->next;
244+ glist->prev = NULL;
245+ *glistlen -= 1;
246+ return(glist);
247+ }
248+ node->prev->next = node->next;
249+ if (node->next != NULL) {
250+ node->next->prev = node->prev;
251+ }
252+ *glistlen -= 1;
253+ return(glist);
254+}
255+
256+
257+static struct gopherlist *loadextrahosts(struct gopherlist *glist, const char *fname, unsigned long *glistlen) {
258+ FILE *f;
259+ char *ptrs[2];
260+ char buff[64];
261+ f = fopen(fname, "rb");
262+ if (f == NULL) return(glist);
263+
264+ while (csvreadline(buff, sizeof(buff), ptrs, 2, f) == 0) {
265+ int port = atoi(ptrs[1]);
266+ if ((port < 1) || (port > 0xffff)) port = 70;
267+ if (glist_addnewhost(&glist, glistlen, ptrs[0], (unsigned short)port, 0) == NULL) break;
268+ }
269+ fclose(f);
270+
271+ return(glist);
272+}
273+
274+
275+/* pick a random node from glist, but avoid recently picked entries */
276+static struct gopherlist *pickrandhostfromlist(struct gopherlist *glist, unsigned long glistlen) {
277+ static long choice;
278+ long i;
279+ int try;
280+ if (glistlen == 0) return(NULL);
281+ for (try = 0; try < 5; try++) {
282+ choice += rand();
283+ choice %= glistlen;
284+ for (i = 0; i < choice; i++) {
285+ if (glist == NULL) return(NULL);
286+ glist = glist->next;
287+ }
288+ if (add_glist_to_recent_history_if_not_present(glist) == 0) {
289+ return(glist);
290+ } else if (glist != NULL) {
291+ printf("NOTICE: picked selector %s:%u'%s' at first, but rejected as it is found in recent history already\n", glist->fqdn, glist->port, glist->selector);
292+ }
293+ }
294+ return(NULL);
295+}
296+
297+
298+static int connect_nonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
299+ int r;
300+ struct timeval t;
301+ fd_set selset;
302+ for (;;) {
303+ r = connect(s, addr, addrlen);
304+ if (r == 0) return(0);
305+ if (errno != EINPROGRESS) return(r);
306+ FD_ZERO(&selset);
307+ FD_SET(s, &selset);
308+ t.tv_sec = timeout;
309+ t.tv_usec = 0;
310+ r = select(s + 1, NULL, &selset, NULL, &t);
311+ if (r < 0) return(-3);
312+ r = connect(s, addr, addrlen);
313+ return(r);
314+ }
315+}
316+
317+
318+static long gopher_fetch(char *buff, size_t buffsz, const char *host, unsigned short port, const char *selector, char *ipstr) {
319+ int sock = -1;
320+ size_t len;
321+ int flags;
322+ time_t timeout;
323+ struct addrinfo *addr, *addrptr;
324+
325+ /* resolve host & connect */
326+ if (getaddrinfo(host, NULL, NULL, &addr) != 0) return(-1);
327+ for (addrptr = addr; addrptr != NULL; addrptr = addrptr->ai_next) {
328+ struct sockaddr_in *sin;
329+ sock = socket(addrptr->ai_family, addrptr->ai_socktype, addrptr->ai_protocol);
330+ if (sock < 0) continue;
331+ /* set port */
332+ sin = (void *)(addrptr->ai_addr);
333+ sin->sin_port = htons(port);
334+ /* set socket as non-blocking */
335+ flags = fcntl(sock, F_GETFL);
336+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
337+ /* try to connect */
338+ if (connect_nonblocking(sock, addrptr->ai_addr, addrptr->ai_addrlen, 10) == 0) {
339+ /* fill ipstr and continue */
340+ inet_ntop(addrptr->ai_family, addrptr->ai_addr, ipstr, addrptr->ai_addrlen);
341+ break;
342+ } else { /* close sock and try next option (if any) */
343+ close(sock);
344+ sock = -1;
345+ }
346+ }
347+ freeaddrinfo(addr);
348+ if (sock < 0) return(-2);
349+
350+ /* send selector, terminated by a CR/LF pair */
351+ if (selector != NULL) send(sock, selector, strlen(selector), MSG_MORE);
352+ send(sock, "\r\n", 2, 0);
353+
354+ /* fetch answer until end of transmission or timeout */
355+ len = 0;
356+ timeout = time(NULL) + 10;
357+ for (;;) {
358+ size_t spaceleft = buffsz - len;
359+ ssize_t rlen;
360+ if (time(NULL) > timeout) {
361+ close(sock);
362+ return(-3);
363+ }
364+ if (spaceleft == 0) break; /* I'm stuffed thank you */
365+ rlen = recv(sock, buff + len, spaceleft, 0);
366+ if (rlen == 0) break; /* orderly shutdown */
367+ if (rlen < 0) { /* sock error */
368+ if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) continue;
369+ close(sock);
370+ return(-4);
371+ }
372+ len += (size_t)rlen;
373+ }
374+
375+ /* close sock and quit */
376+ close(sock);
377+ return((long)len);
378+}
379+
380+
381+/* read a gopher line entry and fill host, port and selector accordingly.
382+ * returns 0 on success, non-zero otherwise. */
383+static int readgline(char *host, unsigned short hostsz, unsigned short *port, char *selector, unsigned short selectorsz, const char *menu, long menulen) {
384+ char portstr[8];
385+ unsigned short i = 0, t;
386+ /* skip first tabs (description) */
387+ for (;;) {
388+ i++;
389+ /* printf("i=%d ['%c']\n", i, menu[i]); */
390+ if (menu[i] == '\t') break;
391+ if (menu[i] == '\r') return(-2);
392+ if (menu[i] == '\n') return(-3);
393+ if (i >= menulen) return(-4);
394+ }
395+ /* read selector */
396+ for (t = 0; ; t++) {
397+ if (t >= selectorsz) return(-5);
398+ i++;
399+ if (i >= menulen) return(-6);
400+ if (menu[i] == '\r') return(-7);
401+ if (menu[i] == '\n') return(-8);
402+ selector[t] = menu[i];
403+ if (selector[t] == '\t') {
404+ selector[t] = 0;
405+ break;
406+ }
407+ }
408+ /* read hostname */
409+ for (t = 0; ; t++) {
410+ if (t >= hostsz) return(-9);
411+ i++;
412+ if (i >= menulen) return(-10);
413+ if (menu[i] == '\r') return(-11);
414+ if (menu[i] == '\n') return(-12);
415+ host[t] = menu[i];
416+ if (host[t] == '\t') {
417+ host[t] = 0;
418+ break;
419+ }
420+ }
421+ /* read port */
422+ for (t = 0; ; t++) {
423+ if (t >= sizeof(portstr)) return(-13);
424+ i++;
425+ if (i >= menulen) return(-14);
426+ portstr[t] = menu[i];
427+ if ((portstr[t] == '\t') || (portstr[t] == '\n')) {
428+ int tport;
429+ portstr[t] = 0;
430+ tport = atoi(portstr);
431+ if ((tport < 1) || (tport > 0xffff)) return(-15);
432+ *port = (unsigned short)tport;
433+ break;
434+ }
435+ }
436+ return(0);
437+}
438+
439+
440+static int ishostvalid(const char *host) {
441+ unsigned short i;
442+ for (i = 0; host[i] != 0; i++) {
443+ if ((host[i] >= 'a') && (host[i] <= 'z')) continue;
444+ if ((host[i] >= 'A') && (host[i] <= 'Z')) continue;
445+ if ((host[i] >= '0') && (host[i] <= '9')) continue;
446+ if (host[i] == '-') continue;
447+ if (host[i] == '.') continue;
448+ return(-1);
449+ }
450+ if (i < 3) return(-10); /* host len should be at least 3 chars long */
451+ return(0);
452+}
453+
454+
455+/* parses a gopher menu and remembers all the hosts present in its links */
456+static struct gopherlist *menu2gopherlist(const char *menu, long menulen) {
457+ long i;
458+ struct gopherlist *res = NULL;
459+ struct gopherlist *node;
460+
461+ /* DEBUG */
462+ printf("------------------------------------------------\n");
463+ for (i = 0; i < menulen; i++) printf("%c", menu[i]);
464+ printf("------------------------------------------------\n");
465+
466+ /* iterate line by line */
467+ for (i = 0; i < menulen; i++) {
468+ if (menu[i] == '1') {
469+ char host[64];
470+ char selector[128];
471+ unsigned short port;
472+
473+ if (readgline(host, sizeof(host), &port, selector, sizeof(selector), menu + i, menulen - i) == 0) {
474+ /* validate host name */
475+ if (ishostvalid(host) != 0) continue;
476+ /* */
477+ node = calloc(1, sizeof(struct gopherlist) + strlen(host));
478+ if (node == NULL) {
479+ glist_free(res);
480+ printf("ERR: OUT OF MEMORY\n");
481+ return(NULL);
482+ }
483+ node->port = port;
484+ strcpy(node->fqdn, host);
485+ node->next = res;
486+ node->selector = strdup(selector);
487+ if (res != NULL) res->prev = node;
488+ res = node;
489+ }
490+ }
491+ /* skip to next line */
492+ for (;;) {
493+ i++;
494+ if (i >= menulen) break;
495+ if (menu[i] == '\n') break;
496+ }
497+ }
498+
499+ return(res);
500+}
501+
502+
503+static void gnode_free(struct gopherlist **node) {
504+ if (*node == NULL) return;
505+ free((*node)->selector);
506+ free(*node);
507+ *node = NULL;
508+}
509+
510+
511+static struct gopherlist *gnodedup(struct gopherlist *node) {
512+ struct gopherlist *res;
513+ if (node == NULL) return(NULL);
514+ res = calloc(1, sizeof(struct gopherlist) + strlen(node->fqdn));
515+ if (res == NULL) return(NULL);
516+
517+ memcpy(res, node, sizeof(struct gopherlist) + strlen(node->fqdn));
518+ if (node->selector != NULL) res->selector = strdup(node->selector);
519+ return(res);
520+}
521+
522+
523+/**************** MAIN ****************/
524+
525+int main(int argc, char **argv) {
526+ time_t nextaction = 0;
527+ time_t nextdbsave = 0;
528+ struct gopherlist *glist, *mlist, *gnode;
529+ struct gopherlist *curhost = NULL;
530+ char curhost_ipaddr[INET6_ADDRSTRLEN];
531+ int ttl = 0;
532+ char buff[0xffff];
533+ long bufflen;
534+ char *dbfile, *dbfilecnt;
535+ unsigned long glistlen;
536+ unsigned long i;
537+
538+ if (parseargs(argc, argv, &dbfile, &dbfilecnt) != 0) {
539+ printf("usage: gopherjoker dbfile.csv dbcount.txt\n");
540+ return(1);
541+ }
542+
543+ /* load db file */
544+ glist = loaddb(dbfile, &glistlen);
545+
546+ /* init random engine */
547+ srand((unsigned int)time(NULL));
548+
549+ for (;;) {
550+
551+ printf("\n\n\n\n\n");
552+
553+ /* do not browse too fast */
554+ while (time(NULL) < nextaction) sleep(1);
555+ nextaction = time(NULL) + WAITPERIOD;
556+
557+ /* if extra manual hosts required to be added, do it now */
558+ glist = loadextrahosts(glist, NEWHOSTSLIST, &glistlen);
559+
560+ /* save the db once every hour */
561+ if (time(NULL) > nextdbsave) {
562+ unsigned long savedbcount;
563+ printf("dumping hosts lists to %s\n", dbfile);
564+ savedbcount = savedb(dbfile, dbfilecnt, glist);
565+ if (savedbcount != glistlen) {
566+ printf("ERR: savedbcount != glistlen @ %d (%lu != %lu)\n", __LINE__, savedbcount, glistlen);
567+ }
568+ nextdbsave = time(NULL) + SAVEPERIOD;
569+ }
570+
571+ /* if ttl expired, pick a new host to browse */
572+ if ((--ttl == 0) || (curhost == NULL)) {
573+ printf("TTL expired -> picking new host\n");
574+ curhost = pickrandhostfromlist(glist, glistlen);
575+ curhost = gnodedup(curhost);
576+ ttl = TTLINIT;
577+ }
578+
579+ printf("TTL=%d | glistlen=%ld\n", ttl, glistlen);
580+
581+ /* if no host, continue */
582+ if (curhost == NULL) {
583+ printf("hosts list empty. add some through " NEWHOSTSLIST ".\n");
584+ continue;
585+ } else {
586+ printf("fetching %s:%u/1%s ...\n", curhost->fqdn, curhost->port, curhost->selector);
587+ }
588+
589+ /* fetch selector */
590+ bufflen = gopher_fetch(buff, sizeof(buff), curhost->fqdn, curhost->port, curhost->selector, curhost_ipaddr);
591+ if (bufflen < 1) { /* fail */
592+ printf("failed\n");
593+ /* if it was about root selector, see if it's time to drop the server */
594+ if (curhost->selector == NULL) {
595+ gnode = glist_findhost(glist, curhost->fqdn, curhost->port);
596+ if (gnode->failedsince == 0) {
597+ /* server was working at some point in the past, but not anymore */
598+ gnode->failedsince = time(NULL);
599+ printf("server %s:%u went down -> flaged as failed since now (%s)\n", gnode->fqdn, gnode->port, ctime(&(gnode->failedsince)));
600+ } else if (time(NULL) > curhost->failedsince + MAXFAILTIME) {
601+ /* remove server from the list */
602+ printf("server removed due to long-time failure: %s:%u (failed since %s)\n", gnode->fqdn, gnode->port, ctime(&(gnode->failedsince)));
603+ glist = dropnode(glist, gnode, &glistlen);
604+ gnode_free(&gnode);
605+ }
606+ }
607+ gnode_free(&curhost);
608+ continue;
609+ }
610+
611+ printf("ok (%ld bytes)\n", bufflen);
612+
613+ /* menu to glist */
614+ mlist = menu2gopherlist(buff, bufflen);
615+ if (mlist == NULL) {
616+ printf("ERR: no entries found in menu\n");
617+ /* if no '1' menu entries at all AND this was its main menu AND I got
618+ * at least 64 bytes of data, then add server's IP address */
619+ if ((curhost->selector == NULL) && (bufflen >= 64)) {
620+ printf(" (keeping server in list as %s)\n", curhost_ipaddr);
621+ glist_addnewhost(&glist, &glistlen, curhost_ipaddr, curhost->port, 0);
622+ }
623+ /* */
624+ glist_free(mlist);
625+ gnode_free(&curhost);
626+ continue;
627+ }
628+
629+ /* try adding hosts to global glist (and count 'em) */
630+ i = 0;
631+ for (gnode = mlist; gnode != NULL; gnode = gnode->next) {
632+ i++;
633+ glist_addnewhost(&glist, &glistlen, gnode->fqdn, gnode->port, 1);
634+ }
635+
636+ /* if main menu, then check that host is pointing to himself */
637+ if (curhost->selector == NULL) {
638+ /* make sure the server points to itself */
639+ if (glist_findhost(mlist, curhost->fqdn, curhost->port) == NULL) {
640+ printf("ERR: main menu contains no link to self, dropping hostname '%s'\n", curhost->fqdn);
641+ gnode = glist_findhost(glist, curhost->fqdn, curhost->port);
642+ glist = dropnode(glist, gnode, &glistlen);
643+ glist_free(mlist);
644+ gnode_free(&curhost);
645+ continue;
646+ }
647+ }
648+
649+ /* mark host as 'okay' */
650+ gnode = glist_findhost(glist, curhost->fqdn, curhost->port);
651+ gnode->failedsince = 0;
652+
653+ /* choose a random entry from mlist */
654+ gnode = pickrandhostfromlist(mlist, i);
655+ if (gnode == NULL) {
656+ printf("pickrandhostfromlist() could not find a node candidate in list\n");
657+ gnode_free(&curhost);
658+ glist_free(mlist);
659+ continue;
660+ }
661+ curhost = gnodedup(gnode);
662+
663+ glist_free(mlist);
664+ }
665+ /* end of program */
666+}
--- trunk/buildall.sh (nonexistent)
+++ trunk/buildall.sh (revision 8)
@@ -0,0 +1,13 @@
1+#!/bin/sh
2+
3+CC="gcc"
4+CFLAGS="-Wall -Wextra -pedantic -O2 -std=gnu89"
5+
6+$CC $CFLAGS frontend/gophermap.c -o frontend/gophermap.cgi
7+$CC $CFLAGS frontend/down/gophermap.c -o frontend/down/gophermap.cgi
8+$CC $CFLAGS frontend/list/gophermap.c -o frontend/list/gophermap.cgi
9+$CC $CFLAGS frontend/rnd/gophermap.c -o frontend/rnd/gophermap.cgi
10+
11+cd gopherjoker
12+make
13+cd ..
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
--- trunk/cleanall.sh (nonexistent)
+++ trunk/cleanall.sh (revision 8)
@@ -0,0 +1,10 @@
1+#!/bin/sh
2+
3+rm -f frontend/gophermap.cgi
4+rm -f frontend/down/gophermap.cgi
5+rm -f frontend/list/gophermap.cgi
6+rm -f frontend/rnd/gophermap.cgi
7+
8+cd gopherjoker
9+make clean
10+cd ..
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Show on old repository browser