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