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