• R/O
  • SSH
  • HTTPS

ogup: 提交


Commit MetaInfo

修訂4 (tree)
時間2020-12-24 18:48:14
作者mateuszviste

Log Message

imported ver 20190227 to tags

Change Summary

差異

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