1 /* retawq/resource.c - resource handling (network, cache, DNS, ...)
2 This file is part of retawq (<http://retawq.sourceforge.net/>), a network
3 client created by Arne Thomassen; retawq is basically released under certain
4 versions of the GNU General Public License and WITHOUT ANY WARRANTY.
5 Read the file COPYING for license details, README for program information.
6 Copyright (C) 2001-2006 Arne Thomassen <arne@arne-thomassen.de>
7 */
8
9 #include "stuff.h"
10 #include "resource.h"
11 #include "parser.h"
12
13 #if CAN_HANDLE_SIGNALS
14 #include <signal.h>
15 #endif
16
17 #if HAVE_DIRENT_H
18 #include <dirent.h>
19 #else
20 #define dirent direct
21 #if HAVE_SYS_NDIR_H
22 #include <sys/ndir.h>
23 #endif
24 #if HAVE_SYS_DIR_H
25 #include <sys/dir.h>
26 #endif
27 #if HAVE_NDIR_H
28 #include <ndir.h>
29 #endif
30 #endif
31
32 #if HAVE_SYS_SOCKET_H
33 #include <sys/socket.h>
34 #endif
35
36 #if HAVE_NETINET_IN_H
37 #include <netinet/in.h>
38 #endif
39
40 #if HAVE_NETDB_H
41 #include <netdb.h>
42 #endif
43
44 #if HAVE_ARPA_INET_H
45 #include <arpa/inet.h>
46 #endif
47
48 #if OPTION_TLS == TLS_GNUTLS
49
50 #include <gnutls/gnutls.h>
51
52 /* try to resolve identifier brain-damage... */
53 #define DO_GNUTLS_SUFFIX ( (GNUTLS_MAJOR_VERSION > 1) || \
54 ( (GNUTLS_MAJOR_VERSION == 1) && (GNUTLS_MINOR_VERSION > 1) ) || \
55 ( (GNUTLS_MAJOR_VERSION == 1) && (GNUTLS_MINOR_VERSION == 1) && \
56 (GNUTLS_MICRO_VERSION >= 11) ) )
57
58 #if DO_GNUTLS_SUFFIX
59 typedef gnutls_session_t tTlsSession;
60 typedef gnutls_transport_ptr_t my_gnutls_transport_ptr;
61 #else
62 typedef gnutls_session tTlsSession;
63 typedef gnutls_transport_ptr my_gnutls_transport_ptr;
64 #endif
65
66 #elif OPTION_TLS == TLS_OPENSSL
67
68 #include <openssl/ssl.h>
69 #include <openssl/err.h>
70 #include <openssl/rand.h>
71 typedef SSL* tTlsSession;
72
73 #elif OPTION_TLS == TLS_MATRIX
74
75 #include <matrixSsl.h>
76 typedef ssl_t* tTlsSession;
77
78 #elif OPTION_TLS == TLS_BUILTIN
79
80 struct __tTlsSession;
81 typedef struct __tTlsSession* tTlsSession;
82
83 #elif OPTION_TLS
84
85 #error "Bad OPTION_TLS value"
86
87 #endif
88
89 declare_local_i18n_buffer
90
91 #if OPTION_IPV6 && defined(AF_INET6)
92 /* "user wants" && "library can" */
93 #define USE_IPv6 1
94 #else
95 #define USE_IPv6 0
96 #endif
97
98
99 /** Strings */
100
101 static char strbuf[STRBUF_SIZE], strbuf2[STRBUF_SIZE];
102 #if CONFIG_DEBUG
103 static char debugstrbuf[STRBUF_SIZE];
104 #endif
105
106 static /*@observer@*/ const char strTcp[] = "tcp", strPop3[] = "pop3",
107 strPercsColonPercs[] = "%s:%s", strNewlineBr[] ="\n<br>",
108 strEndpNewlineEndBodyHtml[] = "</p>\n</body></html>",
109 strAxwfu[] = "application/x-www-form-urlencoded",
110 strHtmlPageTitle[] = "<html><head><title>%s</title></head>\n"
111 "<body>\n<h3 align=\"center\"><b>%s</b></h3>\n";
112 #define strBr (strNewlineBr + 1) /* ug */
113 #define strNewlineEndBodyHtml (strEndpNewlineEndBodyHtml + 4) /* ly */
114 #define strEndBodyHtml (strNewlineEndBodyHtml + 1) /* :-) */
115
116 const char strCrlf[] = "\r\n";
117 #if CONFIG_FTP
118 const char strList[] = "LIST", strRetr[] = "RETR";
119 #endif
120
121 #if CONFIG_FTP || OPTION_POP
122 static const char strNetcmdUser[] = "USER %s\r\n",
123 strNetcmdPass[] = "PASS %s\r\n";
124 #endif
125 #if (CONFIG_HTTP & HTTP_AUTH_DIGEST) || OPTION_COOKIES || OPTION_BUILTIN_DNS
126 static const char strDomain[] = "domain";
127 #endif
128
129 static const char strStopped[] = N_("Stopped"), strError[] = N_("error"),
130 strNoError[] = N_("(no error)"), strInitialization[] = N_("initialization"),
131 strProxyAuthentReq[] = N_("Proxy authentication required");
132 const char strRedirection[] = N_("redirection");
133
134 const char* const strResourceState[MAX_RESOURCE_STATE + 1] =
135 { N_("(resource state unknown - internal error)"), strError,
136 N_("Connecting"), N_("Exchanging messages"), N_("Receiving"),
137 N_("Document complete"), strStopped
138 };
139
140 const char* const strResourceError[MAX_RESOURCE_ERROR + 1] =
141 { strNoError, N_("Bad request kind (internal error)"),
142 N_("Can't create socket"), N_("Connection failed"),
143 N_("Connection refused"), N_("Specific network unreachable"),
144 N_("Can't lookup host name"), N_("Can't read/write file/directory"),
145 N_("Bad URL"), N_("Server closed connection"), N_("Internal handshake bug"),
146 N_("Port number invalid"), N_("Unknown scheme/protocol"),
147 N_("Time-out (server busy)"), N_("Server response invalid"),
148 strLoginFailed, N_("Redirection problem"),
149 N_("Can't calculate random numbers"), N_("TLS-related error"),
150 strProxyAuthentReq, N_("Can't create internal pipe"),
151 N_("Can't create new process"), N_("Forbidden by configuration"),
152 N_("Can't execute program"), N_("Scheme/protocol disabled"),
153 N_("Hostname missing"), N_("Too many open file descriptors"),
154 N_("Connection reset by peer")
155 };
156
157 #if OPTION_TLS
158 const char* const strTlsError[MAX_TLS_ERROR + 1] =
159 { strNoError, strUnknown, N_("session expired"), N_("fatal alert"),
160 N_("file"), N_("application data"), N_("memory"), N_("ciphers"),
161 N_("(de-)compression"), N_("(de-)cryption"), "SRP", N_("public key"),
162 N_("packet"), N_("library feature unimplemented"), N_("handshake"),
163 strInitialization
164 };
165 #endif
166
167 const char* const strResourceRequestState[MAX_RESOURCE_REQUEST_STATE + 1] =
168 { N_("(request state unknown - internal error)"), strError,
169 N_("Preparing"), N_("(Attached resource)"), strStopped,
170 N_("Looking up host name")
171 };
172
173
174 /** Resource protocols */
175
176 #define RPCFG(cfg, rp) (((cfg) * (rp)) + ((1 - (cfg)) * rpDisabled)) /* :-) */
177
178 const tRpData rp_data[rpMax + 1] =
179 { { strUnknown, rpUnknown, rpfUsesAuthority },
180 { strBracedDisabled, rpDisabled, rpfNone },
181 { strHttp, rpHttp, rpfIsHttplike | rpfUsesAuthority | rpfIsPathHierarchical |
182 rpfUiToupper },
183 { strFtp, RPCFG((CONFIG_FTP ? 1 : 0), __rpFtp), rpfIsFtplike |
184 rpfUsesAuthority | rpfIsPathHierarchical | rpfUiToupper },
185 { strLocal, rpLocal, rpfIsLocallike | rpfIsPathHierarchical },
186 { strAbout, rpAbout, rpfNone },
187 { strFinger, RPCFG(CONFIG_FINGER, __rpFinger), rpfUsesAuthority },
188 { strLocalCgi, RPCFG(OPTION_LOCAL_CGI, __rpLocalCgi), rpfIsLocallike |
189 rpfIsPathHierarchical },
190 { strNews, RPCFG(OPTION_NEWS, __rpNntp), rpfUsesAuthority | rpfUiAlternate |
191 rpfUiToupper },
192 { strPop, RPCFG(OPTION_POP, __rpPop), rpfUsesAuthority | rpfUiAlternate |
193 rpfUiToupper | rpfIsPoplike },
194 { strPops, RPCFG((OPTION_TLS ? 1 : 0) * OPTION_POP, __rpPops),
195 rpfUsesAuthority | rpfIsPoplike | rpfIsTlslike },
196 { strCvs, rpCvs, rpfUsesAuthority | rpfIsPathHierarchical | rpfUiToupper },
197 { strGopher, RPCFG(CONFIG_GOPHER, __rpGopher), rpfUsesAuthority },
198 { strInfo, rpInfo, rpfIsPathHierarchical },
199 { strMailto, RPCFG((CONFIG_MAILTO ? 1 : 0), __rpMailto), rpfNone },
200 { strJavascript, RPCFG(CONFIG_JAVASCRIPT, __rpJavascript), rpfNone },
201 { strHttps, RPCFG((OPTION_TLS ? 1 : 0), __rpHttps), rpfIsHttplike |
202 rpfIsTlslike | rpfUsesAuthority | rpfIsPathHierarchical },
203 { strFtps, RPCFG(((OPTION_TLS ? 1 : 0) * CONFIG_FTP), __rpFtps),
204 rpfIsFtplike | rpfIsTlslike | rpfUsesAuthority | rpfIsPathHierarchical },
205 { strExecextShell, RPCFG(((OPTION_EXECEXT & EXECEXT_SHELL) ? 1 : 0),
206 __rpExecextShell), rpfNone }
207 };
208
209 #undef RPCFG
210
211
212 /** Protocols, ports 'n' pipes (aka PPP:-) */
213
214 static const_after_init int ipprotocolnumber_tcp = IPPROTO_TCP; /* default */
215
216 static const_after_init tPortnumber portnumber_http, portnumber_ftp,
217 portnumber_finger, portnumber_cvs, portnumber_gopher, portnumber_nntp,
218 portnumber_pop3, portnumber_https;
219 /* (all these port numbers are stored in network byte order) */
220
221 #if CAN_HANDLE_SIGNALS
222 const_after_init int fd_any2main_read, fd_any2main_write;
223 #endif
224 #if OPTION_THREADING
225 static const_after_init int fd_dns2resource_read, fd_dns2resource_write,
226 fd_resource2dns_read, fd_resource2dns_write;
227 #endif
228 #if CONFIG_TG == TG_XCURSES
229 const_after_init int fd_xcurses2main_read, fd_xcurses2main_write;
230 #endif
231
232 #if OPTION_THREADING == 2
233 static pid_t pid_main = 0, pid_dns = 0; /* PIDs of our threads */
234 #endif
235
236
237 /** Socket addresses */
238
239 #if USE_IPv6
240 typedef struct sockaddr_storage tSockaddr;
241 #define HINT_AF (AF_UNSPEC)
242 #else
243 typedef struct sockaddr tSockaddr;
244 #define HINT_AF (AF_INET)
245 #endif
246
247 typedef signed char tSockaddrRating;
248 #define SOCKADDR_RATING_NONE (-1) /* (for "best rating" algorithms only) */
249 #define SOCKADDR_RATING_NEW (1) /* for new (not yet existing) sppi entries */
250 #define SOCKADDR_RATING_MAX (8)
251
252 #define MAXNUM_SOCKADDRS (8)
253 typedef signed char tSockaddrIndex; /* ("signed" for simplicity only) */
254 #define SOCKADDR_INDEX_INVALID (-1)
255 typedef unsigned char tSockaddrsBitfield;
256
257 my_enum1 enum
258 { sppifNone = 0, sppifCannotHttp11 = 0x01, __sppifFtpCannotEpsv = 0x02,
259 __sppifNntpCannotPostArticles = 0x04
260 } my_enum2(unsigned char) tSockaddrPortProtInfoFlags;
261 #if CONFIG_FTP
262 #define sppifFtpCannotEpsv (__sppifFtpCannotEpsv)
263 #endif
264 #if OPTION_NEWS
265 #define sppifNntpCannotPostArticles (__sppifNntpCannotPostArticles)
266 #endif
267
268 typedef struct tSockaddrPortProtInfo
269 { struct tSockaddrPortProtInfo* next;
270 const char* software_id;
271 tPortnumber portnumber; /* (in network byte order) */
272 tResourceProtocol protocol;
273 tSockaddrPortProtInfoFlags sppif;
274 tSockaddrRating rating; /* rates how connection attempts worked/failed */
275 } tSockaddrPortProtInfo;
276
277 typedef struct tSockaddrEntry
278 { tSockaddr addr;
279 struct tSockaddrEntry* next;
280 tSockaddrPortProtInfo* sppi;
281 size_t addrlen;
282 int address_family;
283 } tSockaddrEntry;
284
285 #define HASHTABLESIZE (101) /* (general) */
286 typedef unsigned short tHashIndex;
287
288 #define HASHTABLESIZE_SOCKADDRS (16)
289 static tSockaddrEntry* sockaddr_entry_head[HASHTABLESIZE_SOCKADDRS];
290
sockaddr2listhead(const tSockaddr * _s,size_t addrlen)291 static tSockaddrEntry** sockaddr2listhead(const tSockaddr* _s, size_t addrlen)
292 { const unsigned char* s = (const unsigned char*) _s;
293 unsigned char sum = 0;
294 while (addrlen > 0) { sum += *s++; addrlen--; } /* get a hash value */
295 sum ^= (sum >> 4); /* use all bits, for better hashing */
296 return(&(sockaddr_entry_head[sum & 15]));
297 }
298
299 #if USE_S2U
300
sockaddr2uistr(const tSockaddrEntry * entry,char * buf,size_t size)301 static void sockaddr2uistr(const tSockaddrEntry* entry, /*@out@*/ char* buf,
302 size_t size)
303 /* converts a network address to a UI string and puts that into <buf> */
304 { const tSockaddr* addr = &(entry->addr);
305 int address_family = entry->address_family;
306 *buf = '\0';
307 /* Try the library function with the lower bug probability first... */
308 #if HAVE_INET_NTOP
309 if (address_family == AF_INET)
310 { const struct sockaddr_in* a = (const struct sockaddr_in*) addr;
311 if ( (inet_ntop(address_family, (const void*) (&(a->sin_addr.s_addr)),
312 buf, size) != NULL) && (*buf != '\0') )
313 { debugmsg("inet_ntop(AF_INET)\n"); return; }
314 }
315 #if USE_IPv6
316 else if (address_family == AF_INET6)
317 { const struct sockaddr_in6* a = (const struct sockaddr_in6*) addr;
318 if ( (inet_ntop(address_family, (const void*) (&(a->sin6_addr.s6_addr)),
319 buf, size) != NULL) && (*buf != '\0') )
320 { debugmsg("inet_ntop(AF_INET6)\n"); return; }
321 }
322 #endif /* #if USE_IPv6 */
323 #endif /* #if HAVE_INET_NTOP */
324 #if HAVE_GETNAMEINFO
325 if ( (getnameinfo((const struct sockaddr*) addr, entry->addrlen, buf, size,
326 NULL, 0, NI_NUMERICHOST) == 0) && (*buf != '\0') )
327 { debugmsg("getnameinfo()\n"); return; }
328 #endif
329 }
330
331 #else
332
333 #define sockaddr2uistr(a, buf, c) *buf = '\0'
334
335 #endif
336
sockaddr2sppi(tSockaddrEntry * entry,tPortnumber portnumber,tResourceProtocol rp,tBoolean create_if_null)337 static tSockaddrPortProtInfo* sockaddr2sppi(tSockaddrEntry* entry,
338 tPortnumber portnumber, tResourceProtocol rp, tBoolean create_if_null)
339 { tSockaddrPortProtInfo* sppi = entry->sppi;
340 while (sppi != NULL)
341 { if ( (sppi->portnumber == portnumber) && (sppi->protocol == rp) ) break;
342 sppi = sppi->next;
343 }
344 if ( (sppi == NULL) && (create_if_null) )
345 { sppi = memory_allocate(sizeof(tSockaddrPortProtInfo), mapPermanent);
346 sppi->portnumber = portnumber; sppi->protocol = rp;
347 sppi->next = entry->sppi; entry->sppi = sppi;
348 #if CONFIG_DEBUG
349 { char buf[1024];
350 sockaddr2uistr(entry, buf, sizeof(buf));
351 sprint_safe(debugstrbuf, "sockaddr2sppi(): *%s*, %d, %d\n", buf,
352 ntohs(portnumber), rp);
353 debugmsg(debugstrbuf);
354 }
355 #endif
356 }
357 return(sppi);
358 }
359
360
361 /** DNS lookups */
362
363 enum { dlfNone = 0, dlfTryIpv6 = 0x01 };
364 typedef unsigned char tDnsLookupFlags;
365
366 typedef struct
367 { tSockaddrEntry sockaddrs[MAXNUM_SOCKADDRS]; /* the results of the lookup */
368 struct tCachedHostInformation* hostinfo;
369 /* the hostinfo for which the lookup is done; not for use by DNS handler */
370 const char* hostname; /* the name which shall be looked up */
371 tSockaddrIndex num; /* number of results */
372 tDnsLookupFlags flags;
373 } tDnsLookup;
374
store_sockaddr(tDnsLookup * dns_lookup,const tSockaddr * addr,int address_family,size_t addrlen)375 static void store_sockaddr(tDnsLookup* dns_lookup, const tSockaddr* addr,
376 int address_family, size_t addrlen)
377 { tSockaddrEntry* entry;
378 const size_t num = dns_lookup->num;
379 if (num >= MAXNUM_SOCKADDRS) return; /* got "enough" */
380 entry = &(dns_lookup->sockaddrs[num]);
381 entry->address_family = address_family; entry->addrlen = addrlen;
382 my_memcpy(&(entry->addr), addr, addrlen); dns_lookup->num++;
383 #if CONFIG_DEBUG
384 sockaddr2uistr(entry, strbuf, STRBUF_SIZE / 2);
385 sprint_safe(debugstrbuf, "store_sockaddr(%p): af=%d, *%s*\n", dns_lookup,
386 address_family, strbuf);
387 debugmsg(debugstrbuf);
388 #endif
389 }
390
391 #if !HAVE_GETADDRINFO
392
store_rawaddr(tDnsLookup * dns_lookup,int address_family,const void * data)393 static void store_rawaddr(tDnsLookup* dns_lookup, int address_family,
394 const void* data)
395 { tSockaddr addr;
396 size_t addrlen;
397 my_memclr_var(addr); ((struct sockaddr*)(&addr))->sa_family = address_family;
398 if (address_family == AF_INET)
399 { my_memcpy(&(((struct sockaddr_in*)(&addr))->sin_addr.s_addr), data, 4);
400 addrlen = sizeof(struct sockaddr_in);
401 }
402 #if USE_IPv6
403 else if (address_family == AF_INET6)
404 { my_memcpy(&(((struct sockaddr_in6*)(&addr))->sin6_addr.s6_addr), data, 16);
405 addrlen = sizeof(struct sockaddr_in6);
406 }
407 #endif
408 else return; /* "should not happen" */
409 store_sockaddr(dns_lookup, &addr, address_family, addrlen);
410 }
411
412 #define CONFIG_ADDR_LIST 1 /* CHECKME: use a configure script test? */
413
store_hostent(tDnsLookup * dns_lookup,const struct hostent * result)414 static void store_hostent(tDnsLookup* dns_lookup, const struct hostent* result)
415 { const int address_family = result->h_addrtype;
416 const size_t srcsize = result->h_length;
417 size_t addrlen;
418 const void* src;
419 void* dest;
420 tSockaddr addr;
421
422 if (address_family == AF_INET)
423 { if (srcsize != 4) return; /* "should not happen" */
424 dest = &(((struct sockaddr_in*)(&addr))->sin_addr.s_addr);
425 addrlen = sizeof(struct sockaddr_in);
426 }
427 #if USE_IPv6
428 else if (address_family == AF_INET6)
429 { if (srcsize != 16) return; /* "should not happen" */
430 dest = &(((struct sockaddr_in6*)(&addr))->sin6_addr.s6_addr);
431 addrlen = sizeof(struct sockaddr_in6);
432 }
433 #endif
434 else return; /* "should not happen" */
435
436 my_memclr_var(addr); ((struct sockaddr*)(&addr))->sa_family = address_family;
437 {
438 #if CONFIG_ADDR_LIST
439 size_t count = 0;
440 if (result->h_addr_list == NULL) return; /* "should not happen" */
441 while ( ( (src = result->h_addr_list[count++]) != NULL ) &&
442 (dns_lookup->num < MAXNUM_SOCKADDRS) )
443 #else
444 src = result->h_addr;
445 if (src != NULL) /* "should" be true */
446 #endif
447 { my_memcpy(dest, src, srcsize);
448 store_sockaddr(dns_lookup, &addr, address_family, addrlen);
449 }
450 }
451 }
452
453 #endif /* #if !HAVE_GETADDRINFO */
454
my_getaddrinfo(tDnsLookup * dns_lookup)455 static one_caller void my_getaddrinfo(tDnsLookup* dns_lookup)
456 /* performs a DNS hostname lookup; the monstrous size of the source code of
457 this function plus all those store_....() functions is only a result of the
458 attempt to make the DNS lookup as portable as possible; you could stop
459 reading after the getaddrinfo() stuff... */
460 { const char* hostname = dns_lookup->hostname;
461 #if HAVE_GETADDRINFO || HAVE_GETIPNODEBYNAME
462 int err;
463 #endif
464 #if HAVE_GETADDRINFO
465 static tBoolean did_init_hints = falsE;
466 static struct addrinfo hints;
467 /*const*/ struct addrinfo *orig_result, *result;
468 if (!did_init_hints)
469 { did_init_hints = truE; my_memclr_var(hints); hints.ai_family = HINT_AF;
470 hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = ipprotocolnumber_tcp;
471 }
472 #else
473 const struct hostent* result;
474 #endif
475
476 #if HAVE_GETADDRINFO
477
478 debugmsg("DNS: getaddrinfo()\n");
479 orig_result = NULL; /* (extra care for buggy libraries) */
480 err = getaddrinfo(hostname, NULL, &hints, &orig_result);
481 if ( (err == 0) && ( (result = orig_result) != NULL) )
482 { do
483 { const struct sockaddr* addr = result->ai_addr;
484 const size_t addrlen = result->ai_addrlen;
485 if ( (addr != NULL) && (addrlen > 0) && (addrlen <= sizeof(tSockaddr)) )
486 { const int address_family = result->ai_family;
487 if ( (address_family == AF_INET)
488 #if USE_IPv6
489 || (address_family == AF_INET6)
490 #endif
491 )
492 { /* (Especially if <hints> contains AF_UNSPEC, getaddrinfo() might
493 e.g. return an AF_UNIX address for hostname "localhost".) */
494 store_sockaddr(dns_lookup, (const tSockaddr*) addr, address_family,
495 addrlen);
496 if (dns_lookup->num >= MAXNUM_SOCKADDRS) break; /* got "enough" */
497 }
498 }
499 result = result->ai_next;
500 } while (result != NULL);
501 }
502 #if CONFIG_DEBUG
503 else if (err != 0) { debugmsg(gai_strerror(err)); debugmsg(strNewline); }
504 #endif
505 if (orig_result != NULL) freeaddrinfo(orig_result);
506
507 #elif HAVE_GETIPNODEBYNAME /* RFC2553; obsoleted by RFC3493, but... */
508
509 result = getipnodebyname(hostname, AF_INET, 0, &err);
510 if (result != NULL)
511 { debugmsg("DNS: getipnodebyname(AF_INET)\n");
512 store_hostent(dns_lookup, result); freehostent(result);
513 }
514 #if USE_IPv6
515 if (dns_lookup->num <= 0) /* wasn't IPv4, try IPv6 */
516 { result = getipnodebyname(hostname, AF_INET6, 0, &err);
517 if (result != NULL)
518 { debugmsg("DNS: getipnodebyname(AF_INET6)\n");
519 store_hostent(dns_lookup, result); freehostent(result);
520 }
521 }
522 #endif /* #if USE_IPv6 */
523
524 #else /* #if HAVE_GETADDRINFO / #elif HAVE_GETIPNODEBYNAME */
525
526 /* The nice getaddrinfo() and the not-so-nice getipnodebyname() aren't
527 available, so we have to work harder. We can't simply rely on
528 gethostbyname() because "the behavior of gethostbyname() when passed a
529 numeric address string is unspecified" (SUSv3); so we must try several
530 other functions in advance. (The word "unspecified" could at least in
531 theory mean that gethostbyname() returns a non-NULL value pointing to
532 rubbish. And there are so many buggy libraries around...) */
533
534 /* Check whether <hostname> is a numerical host address: */
535 #if HAVE_INET_PTON
536 #if USE_IPv6
537 if (dns_lookup->flags & dlfTryIpv6)
538 { char resbuf[16];
539 if (inet_pton(AF_INET6, hostname, resbuf) == 1)
540 { store_rawaddr(dns_lookup, AF_INET6, resbuf);
541 debugmsg("DNS: inet_pton(AF_INET6)\n");
542 return;
543 }
544 }
545 #endif /* #if USE_IPv6 */
546 { char resbuf[16]; /* (unnecessarily large, just for buggy libraries) */
547 if (inet_pton(AF_INET, hostname, resbuf) == 1)
548 { store_rawaddr(dns_lookup, AF_INET, resbuf);
549 debugmsg("DNS: inet_pton(AF_INET)\n");
550 return;
551 }
552 }
553 #elif HAVE_INET_ADDR /* #if HAVE_INET_PTON */
554 #ifndef INADDR_NONE
555 #define INADDR_NONE ((tUint32) (-1))
556 #endif
557 { tUint32 res = (tUint32) inet_addr(hostname);
558 /* (The correct formal type would be in_addr_t, but glibc before
559 2000-04-01 (and probably other libraries) doesn't have it...) */
560 if (res != INADDR_NONE)
561 { store_rawaddr(dns_lookup, AF_INET, &res);
562 debugmsg("DNS: inet_addr()\n");
563 return;
564 }
565 }
566 #endif /* #if HAVE_INET_PTON / #elif HAVE_INET_ADDR */
567
568 /* Seems that <hostname> isn't a numerical host address... */
569 result = gethostbyname(hostname);
570 if (result != NULL)
571 { debugmsg("DNS: gethostbyname()\n"); store_hostent(dns_lookup, result);
572 /* (no freehostent() here - gethostbyname() uses static memory) */
573 }
574 #if CONFIG_DEBUG
575 else
576 { char buf[100];
577 sprint_safe(buf, "h_errno: %d\n", h_errno); debugmsg(buf);
578 }
579 #endif
580
581 #endif /* #if HAVE_GETADDRINFO / #elif HAVE_GETIPNODEBYNAME */
582
583 }
584
585
586 /** DNS handler thread */
587
588 #if OPTION_THREADING == 0
589
590 /* nothing */
591
592 #elif OPTION_THREADING == 1
593
594 /* use the pthreads library */
595 #include <pthread.h>
596 #define THREADRETTYPE void*
597 #define THREADRETVAL NULL
598
599 #elif OPTION_THREADING == 2
600
601 /* use the Linux syscall clone() */
602 #include <sched.h>
603 #define THREADRETTYPE int
604 #define THREADRETVAL 0
605
606 #else /* #if OPTION_THREADING... */
607
608 #error "Bad multi-threading configuration; check option OPTION_THREADING!"
609
610 #endif /* #if OPTION_THREADING... */
611
612
613 #if OPTION_THREADING
614
615 typedef struct
616 { int reader, writer; /* (from the handler's point of view) */
617 } tDnsThreadData;
618
dns_handler_thread(void * _data)619 static THREADRETTYPE dns_handler_thread(void* _data)
620 /* This function is the DNS handler thread; it checks for DNS lookup demands
621 from the resource handler forever. Apart from its local variables, it only
622 accesses the tDnsLookup structure given by the resource handler (which won't
623 touch it while the lookup is running), so there aren't any multi-threading
624 race conditions possible. */
625 { const tDnsThreadData* data = (const tDnsThreadData*) _data;
626
627 #if HAVE_SIGFILLSET && (HAVE_SIGPROCMASK || (OPTION_THREADING == 1))
628
629 { /* CHECKME: blocking the signals isn't "really" the right thing. It would
630 be better to set signal handlers for this thread to SIG_IGN, but
631 handlers can't be set per-thread, only per-process. Also it's not
632 possible (portably) to find out which thread is signalled, e.g. SUSv3
633 doesn't require in the list in functions/xsh_chap02_04.html that
634 pthread_self() can be called in handlers. Look at all this mess...
635 "Unix multi-threading is rubbish, because it's me who designed it",
636 whispers the idiot. (Seems I'm not the only one with such problems, e.g.
637 see <http://www.ussg.iu.edu/hypermail/linux/kernel/0403.3/1098.html>.)
638 Even worse, certain signals must not be ignored/blocked by a thread
639 resp. process because it might be left in an undefined state otherwise,
640 says SUSv3. Add to this the buggy signal implementations of many
641 operating systems and wonder how this program can work at all... */
642
643 sigset_t set;
644 if (sigfillset(&set) == 0)
645 {
646 #if HAVE_SIGDELSET
647 static const int sigs[] =
648 {
649 #ifdef SIGTERM
650 SIGTERM, /* (some users might like this one) */
651 #endif
652 #ifdef SIGFPE
653 SIGFPE,
654 #endif
655 #ifdef SIGILL
656 SIGILL,
657 #endif
658 #ifdef SIGSEGV
659 SIGSEGV,
660 #endif
661 #ifdef SIGBUS
662 SIGBUS,
663 #endif
664 0 /* (to avoid trailing comma resp. empty array) */
665 };
666 unsigned char i;
667 for (i = 0; i < ARRAY_ELEMNUM(sigs) - 1; i++) /* ("-1" for the 0) */
668 (void) sigdelset(&set, sigs[i]);
669 #endif /* #if HAVE_SIGDELSET */
670
671 #if OPTION_THREADING == 1
672 (void) pthread_sigmask(SIG_BLOCK, &set, NULL);
673 #else
674 (void) sigprocmask(SIG_BLOCK, &set, NULL);
675 /* (unspecified for multi-threading processes in SUSv3, but no choice
676 available, just hope the best; this function call is only used under
677 Linux, so...) */
678 #endif
679 }
680 }
681
682 #else
683
684 /* It seems we have to accept some bad effects (mostly related to performance
685 and cosmetics, so possibly acceptable)... */
686
687 #endif
688
689 while (1)
690 { static tDnsLookup* dns_lookup;
691 static unsigned char count = 0;
692 unsigned short loopcount;
693 int err;
694
695 loopcount = 0;
696 loop:
697 err = read/*_pipe*/(data->reader, ((char*)(&dns_lookup)) + count,
698 sizeof(dns_lookup) - count); /* (can't use my_read() with FD register) */
699 if ( (err == -1) && (errno == EINTR) && (++loopcount < 10000) ) goto loop;
700 else if (err <= 0)
701 fatal_error((err == -1) ? errno : 0, _("read(DNS) failed"));
702 count += err;
703 if (count >= sizeof(dns_lookup)) /* got a complete pointer */
704 { my_getaddrinfo(dns_lookup); count = 0; writeloop0: loopcount = 0;
705 writeloop:
706 err = write/*_pipe*/(data->writer, ((char*)(&dns_lookup)) + count,
707 sizeof(dns_lookup) - count);
708 if ( (err == -1) && (errno == EINTR) && (++loopcount < 10000) )
709 goto writeloop;
710 else if (err <= 0)
711 fatal_error((err == -1) ? errno : 0, _("write(DNS) failed"));
712 count += err;
713 if (count < sizeof(dns_lookup)) goto writeloop0;
714 count = 0; /* wrote whole pointer, prepare for next round */
715 }
716 }
717 /*@notreached@*/
718 return(THREADRETVAL); /* avoid compiler warning for compilers which are too
719 stupid to recognize simple infinite loops... */
720 }
721
722 #endif /* #if OPTION_THREADING */
723
724
725 /** Helper functions I */
726
727 static tBoolean got_activity;
resource_preplex(void)728 void resource_preplex(void)
729 { got_activity = falsE;
730 }
731
resource_postplex(void)732 void resource_postplex(void)
733 { i18n_cleanup
734 /* IMPLEMENTME: if (got_activity) { .... } */
735 }
736
uri_put(tUriData * u)737 void uri_put(tUriData* u)
738 { if (u->refcount > 1) u->refcount--;
739 else
740 { __dealloc(u->uri); __dealloc(u->hostname); __dealloc(u->path);
741 __dealloc(u->query); __dealloc(u->post); __dealloc(u->username);
742 __dealloc(u->password); memory_deallocate(u);
743 }
744 }
745
sinking_data_cleanup(tSinkingData * sd)746 void sinking_data_cleanup(tSinkingData* sd)
747 { const tTransferData* td = sd->transfer_data;
748 const tSinkingDataFlags flags = sd->flags;
749 const tBoolean is_fd_valid = cond2boolean(flags & sdfIsDownloadFdValid);
750 #if CONFIG_DEBUG
751 sprint_safe(debugstrbuf, "sinking_data_cleanup(%p): flags=%d, fd=%d\n",
752 sd, flags, (is_fd_valid ? (sd->download_fd) : (-42)));
753 debugmsg(debugstrbuf);
754 #endif
755 while (td != NULL)
756 { const tTransferData* next = td->next;
757 if (td->need_unmap) my_munmap(td->data, td->size);
758 else memory_deallocate(td->data);
759 __dealloc(td->name); memory_deallocate(td); td = next;
760 }
761 if (is_fd_valid) my_close(sd->download_fd);
762 }
763
sinking_data_deallocate(tSinkingData ** _sd)764 void sinking_data_deallocate(tSinkingData** _sd)
765 { tSinkingData* sd = *_sd;
766 if (sd != NULL)
767 { sinking_data_cleanup(sd); memory_deallocate(sd); *_sd = NULL; }
768 }
769
770 #if OPTION_LOCAL_CGI || OPTION_TLS || OPTION_EXECEXT
may_use_fd2(void)771 static tBoolean may_use_fd2(void)
772 { static tBoolean did_calc = falsE, retval;
773 if (!did_calc)
774 { did_calc = truE;
775 if ( (lfdmbs(2)) &&
776 (
777 #if (TGC_IS_CURSES) && (CONFIG_TG != TG_XCURSES)
778 (!is_environed) || /* don't ruin the curses screen handling... */
779 #endif
780 (!my_isatty(fd_stderr)) ) )
781 retval = truE;
782 else retval = falsE;
783 }
784 return(retval);
785 }
786 #endif
787
788 #if OPTION_LOCAL_CGI || OPTION_EXECEXT
my_dup2(int fd1,int fd2)789 static tBoolean my_dup2(int fd1, int fd2)
790 /* returns whether it worked; IMPORTANT: this function is only intended for use
791 in child processes after a fork(); otherwise we could get a race condition
792 with retawq's other threads here because there's a window between close()
793 and fcntl() - even within the C library's dup2(), unless e.g. an atomic
794 dup2() in the OS is used */
795 { unsigned char loopcount = 0;
796 int err;
797 #if HAVE_DUP2
798 do
799 { err = dup2(fd1, fd2);
800 } while ( (err == -1) && ( (errno == EINTR) || (errno == EBUSY) ) &&
801 (++loopcount < 100) ); /* (EBUSY is for Linux) */
802 #else
803 my_close_pipe(fd2);
804 do
805 { err = fcntl(fd1, F_DUPFD, fd2);
806 } while ( (err == -1) && (errno == EINTR) && (++loopcount < 100) );
807 #endif
808 return(cond2boolean(err >= 0));
809 }
810 #endif
811
resource_set_error(tResource * resource,tResourceError error)812 static my_inline void resource_set_error(tResource* resource,
813 tResourceError error)
814 { if (resource->state != rsError)
815 { resource->state = rsError; resource->error = error; }
816 }
817
resource_request_set_error(tResourceRequest * request,tResourceError error)818 static void resource_request_set_error(tResourceRequest* request,
819 tResourceError error)
820 { tResource* resource = request->resource;
821 if (request->state != rrsError)
822 { request->state = rrsError; request->error = error; }
823 if (resource != NULL) resource_set_error(resource, error);
824 }
825
resource_start_saving(tResource * resource,const tCantent * cantent,int fd)826 void resource_start_saving(tResource* resource, const tCantent* cantent,int fd)
827 { const tContentblock* content = cantent->content;
828 /* save the resource as far as we already got it */
829 while (content != NULL)
830 { size_t usedsize = content->used;
831 if (usedsize > 0)
832 { if (my_write(fd, content->data, usedsize) < (ssize_t) usedsize)
833 goto do_close; /* IMPLEMENTME: alert user? */
834 }
835 content = content->next;
836 }
837 /* if there's more to come, remember the fd, otherwise close it */
838 if ( (resource == NULL) || (resource->flags & rfFinal) )
839 { do_close: my_close(fd); }
840 else
841 { tSaveAs* save_as = (tSaveAs*) __memory_allocate(sizeof(tSaveAs), mapOther);
842 make_fd_cloexec(fd); save_as->fd = fd;
843 save_as->next = resource->save_as; resource->save_as = save_as;
844 }
845 }
846
do_save_as(tResource * resource,const char * src,size_t size)847 static one_caller void do_save_as(tResource* resource, const char* src,
848 size_t size)
849 { tSaveAs* save_as = resource->save_as;
850 while (save_as != NULL)
851 { (void) my_write(save_as->fd, src, size);
852 /* IMPLEMENTME: check for errors, remove record, alert user? */
853 /* IMPROVEME: use a non-blocking algorithm? */
854 save_as = save_as->next;
855 }
856 }
857
stop_save_as(tResource * resource)858 static void stop_save_as(/*@notnull@*/ tResource* resource)
859 { tSaveAs* save_as = resource->save_as;
860 #if CONFIG_DEBUG
861 sprint_safe(debugstrbuf, "stop_save_as(%p,%p)\n", resource, save_as);
862 debugmsg(debugstrbuf);
863 #endif
864 while (save_as != NULL)
865 { tSaveAs* next = save_as->next;
866 my_close(save_as->fd);
867 memory_deallocate(save_as);
868 save_as = next;
869 }
870 resource->save_as = NULL;
871 }
872
873 #if CONFIG_HTTP & (HTTP_AUTH_BASIC | HTTP_PROXYAUTH)
base64_encode(const unsigned char * src)874 static __sallocator char* __callocator base64_encode(const unsigned char* src)
875 /* converts an arbitrary string to base64 encoding (RFC3548, 3.) */
876 { static /*@observer@*/ const unsigned char conv[] =
877 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
878 size_t len = strlen((const char*) src), destlen = ((len + 2) / 3) * 4 + 1;
879 unsigned char ch, *dest = __memory_allocate(destlen, mapString), *ptr = dest,
880 count = 0;
881 unsigned int convval = 0;
882 #define PUT(offset) *ptr++ = conv[(convval >> offset) & 63]
883 while ((ch = *src++) != '\0')
884 { convval |= ((unsigned int) ch);
885 if (++count < 3) convval <<= 8;
886 else { PUT(18); PUT(12); PUT(6); PUT(0); convval = 0; count = 0; }
887 }
888 if (count > 0) /* something left to do */
889 { if (count == 1) convval <<= 8;
890 PUT(18); PUT(12);
891 if (count == 2) PUT(6);
892 else *ptr++ = '=';
893 *ptr++ = '=';
894 }
895 #undef PUT
896 *ptr = '\0';
897 return((char*) dest);
898 }
899 #endif
900
901 /* #if (CONFIG_HTTP & HTTP_AUTH_DIGEST) || OPTION_POP || OPTION_TRAP || CONFIG_DEBUG */
902 #if CONFIG_DEBUG
903
md5_decode(tUint32 * dest,const tUint8 * src,size_t count)904 static one_caller void md5_decode(/*@out@*/ tUint32* dest, const tUint8* src,
905 size_t count)
906 { while (count-- > 0)
907 { *dest++ = ((tUint32) src[0]) | (((tUint32) src[1]) << 8) |
908 (((tUint32) src[2]) << 16) | (((tUint32) src[3]) << 24);
909 src += 4;
910 }
911 }
912
md5_encode(tUint8 * dest,const tUint32 * src,size_t count)913 static void md5_encode(/*@out@*/ tUint8* dest, const tUint32* src,
914 size_t count)
915 { while (count-- > 0)
916 { unsigned char cnt;
917 tUint32 value = *src++;
918 for (cnt = 0; cnt <= 3; cnt++) { *dest++ = value & 255; value >>= 8; }
919 }
920 }
921
922 #define md5_prepare(roundnum) do { currfunc = func[roundnum]; currshift = shift[roundnum]; xidxval = xidxvals[roundnum]; xidxoff = xidxoffs[roundnum]; } while (0)
923 #define md5_rotate(val, cnt) (((val) << (cnt)) | ((val) >> (32-(cnt))))
924 #define md5_operate(a, b, c, d) do { (a) += (currfunc(b, c, d)) + (x[xidxval & 15]) + sineval[stepcount]; (a) = md5_rotate((a), currshift[stepcount & 3]); (a) += (b); stepcount++; si--; xidxval += xidxoff; } while (0)
925
926 typedef tUint32 (*tMd5Function)(tUint32, tUint32, tUint32);
md5_F(tUint32 a,tUint32 b,tUint32 c)927 static tUint32 md5_F(tUint32 a, tUint32 b, tUint32 c)
928 { return(((a) & (b)) | ((~a) & (c))); }
md5_G(tUint32 a,tUint32 b,tUint32 c)929 static tUint32 md5_G(tUint32 a, tUint32 b, tUint32 c)
930 { return(((a) & (c)) | ((b) & (~c))); }
md5_H(tUint32 a,tUint32 b,tUint32 c)931 static tUint32 md5_H(tUint32 a, tUint32 b, tUint32 c)
932 { return((a) ^ (b) ^ (c)); }
md5_I(tUint32 a,tUint32 b,tUint32 c)933 static tUint32 md5_I(tUint32 a, tUint32 b, tUint32 c)
934 { return((b) ^ ((a) | (~c))); }
935
md5_digest_block(tUint32 * state,const tUint8 * data)936 static void md5_digest_block(tUint32* state, const tUint8* data)
937 /* Maybe slow, but small; speed doesn't matter much here because this is rarely
938 used, if ever, and only with rather small amounts of data. */
939 { static const tUint32 sineval[64] =
940 { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
941 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
942 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
943 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8,
944 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
945 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
946 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
947 0xd4ef3085, 0x4881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
948 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
949 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
950 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
951 };
952 static const unsigned char shift1[4] = {7,12,17,22}, shift2[4] = {5,9,14,20},
953 shift3[4] = {4,11,16,23}, shift4[4] = {6,10,15,21}, xidxvals[4] ={0,1,5,0},
954 xidxoffs[4] = {1,5,3,7}, *shift[4] = { shift1, shift2, shift3, shift4 };
955 static const tMd5Function func[4] = { md5_F, md5_G, md5_H, md5_I };
956 unsigned char stepcount = 0, si = 128, roundnum, count, xidxval, xidxoff;
957 const unsigned char* currshift;
958 tMd5Function currfunc;
959 tUint32 x[16], cs[4];
960
961 md5_decode(x, data, 16); my_memcpy(cs, state, sizeof(cs));
962 for (roundnum = 0; roundnum <= 3; roundnum++)
963 { md5_prepare(roundnum);
964 for (count = 0; count <= 15; count++)
965 md5_operate(cs[si & 3], cs[(si+1) & 3], cs[(si+2) & 3], cs[(si+3) & 3]);
966 }
967 for (count = 0; count <= 3; count++) state[count] += cs[count];
968 }
969
md5_digest(const tUint8 * data,size_t size,tUint8 * result)970 static void md5_digest(const tUint8* data, size_t size,
971 /*@out@*/ tUint8* result)
972 { tUint32 state[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
973 size_t numobits = size * 8, tsize;
974 tUint8 tailbuf[128], *tail = tailbuf;
975
976 while (size >= 64) { md5_digest_block(state, data); data += 64; size -= 64; }
977
978 if (size > 0) my_memcpy(tailbuf, data, size);
979 tsize = ( (size >= 56) ? 128 : 64 );
980 tailbuf[size++] = 0x80; my_memclr(tailbuf + size, tsize - size);
981 md5_encode(tailbuf + tsize - 8, &numobits, 1);
982 while (tsize >= 64) { md5_digest_block(state, tail); tail += 64; tsize-=64; }
983
984 md5_encode(result, state, 4);
985 }
986
987 #endif /* need-md5 */
988
989
990 /** Custom connections */
991
992 #if CONFIG_CUSTOM_CONN
993
994 #define doing_custom_conn(resource) ((resource)->flags & rfCustomConn)
995
__custom_conn_print(tResource * resource,const char * str,size_t len,tCustomConnPrintingKind ccpk)996 static void __custom_conn_print(tResource* resource, const char* str,
997 size_t len, tCustomConnPrintingKind ccpk)
998 { tCustomConnPrintingData data;
999 data.text = str; data.len = len; data.ccpk = ccpk;
1000 main_handle_custom_conn(resource, 0, &data);
1001 }
1002
custom_conn_print(tResource * resource,const char * str,tCustomConnPrintingKind ccpk)1003 static my_inline void custom_conn_print(tResource* resource, const char* str,
1004 tCustomConnPrintingKind ccpk)
1005 { tCustomConnPrintingData data;
1006 data.text = str; data.len = ( (ccpk == ccpkNetresp) ? strlen(str) : 0 );
1007 data.ccpk = ccpk; main_handle_custom_conn(resource, 0, &data);
1008 }
1009
custom_conn_unbusify(tResource * resource)1010 static __my_inline void custom_conn_unbusify(tResource* resource)
1011 /* what a name - "un-busy-i-fy"... :-) */
1012 { main_handle_custom_conn(resource, 1, NULL);
1013 }
1014
custom_conn_tell_msg(tResource * resource)1015 static __my_inline void custom_conn_tell_msg(tResource* resource)
1016 { main_handle_custom_conn(resource, 2, NULL);
1017 }
1018
custom_conn_tell_error(tResource * resource,tResourceError re)1019 static __my_inline void custom_conn_tell_error(tResource* resource,
1020 tResourceError re)
1021 { main_handle_custom_conn(resource, 3, &re);
1022 }
1023
custom_conn_tell_hash(tResource * resource)1024 static __my_inline void custom_conn_tell_hash(tResource* resource)
1025 { main_handle_custom_conn(resource, 4, NULL);
1026 }
1027
1028 #else
1029
1030 #define doing_custom_conn(resource) (falsE)
1031
1032 #endif
1033
1034
1035 /** Content blocks */
1036
1037 static __sallocator tContentblock* __callocator
contentblock_create(size_t size,unsigned char flags)1038 contentblock_create(size_t size, unsigned char flags)
1039 /* creates a block for content data */
1040 { tContentblock* retval = memory_allocate(sizeof(tContentblock),
1041 mapContentblock);
1042 if (flags & 1) retval->data = __memory_allocate(size, mapOther);
1043 retval->usable = size;
1044 #if CONFIG_DEBUG
1045 sprint_safe(debugstrbuf, "created contentblock: %p, %d\n", retval, size);
1046 debugmsg(debugstrbuf);
1047 #endif
1048 return(retval);
1049 }
1050
1051 #define HIGHEST_OCBS (131072)
1052
optimal_contentblocksize(size_t count)1053 static size_t optimal_contentblocksize(size_t count)
1054 /* calculates the optimal :-) size for a new content block of a resource,
1055 depending on how many bytes we already got for it; the assumption behind
1056 this is that e.g. if we already received 100,000 bytes there might be
1057 100,000 more to come, so we allocate bigger blocks, read in larger chunks
1058 (as far as the socket buffer of the operating system allows), reserve memory
1059 less frequently and thus hopefully improve performance... */
1060 { size_t retval;
1061 if (count <= 10000) retval = 4096;
1062 else if (count <= 40000) retval = 8192;
1063 else if (count <= 200000) retval = 32768;
1064 else retval = HIGHEST_OCBS; /* Downloading Linux kernel sources, eh? :-) */
1065 return(retval);
1066 }
1067
cantent_set_firstcontent(tCantent * cantent,tContentblock * content)1068 static void cantent_set_firstcontent(tCantent* cantent,
1069 tContentblock* content)
1070 { cantent->content = cantent->lastcontent = cantent->lhpp_content = content;
1071 cantent->lhpp_byte = 0; /* cantent->flags &= ~cafFullyParsed; */
1072 }
1073
cantent_append_new_contentblock(tCantent * cantent,size_t usablesize)1074 static tContentblock* cantent_append_new_contentblock(tCantent* cantent,
1075 size_t usablesize)
1076 { tContentblock* retval = contentblock_create(usablesize, 1);
1077 if (cantent->content == NULL) cantent_set_firstcontent(cantent, retval);
1078 else
1079 { if (cantent->lastcontent != NULL) cantent->lastcontent->next = retval;
1080 cantent->lastcontent = retval;
1081 }
1082 return(retval);
1083 }
1084
cantent_create(void)1085 tCantent* cantent_create(void)
1086 { tCantent* retval = (tCantent*) memory_allocate(sizeof(tCantent), mapCantent);
1087 return(retval);
1088 }
1089
cantent_deallocate_tree(tCantent * cantent)1090 static void cantent_deallocate_tree(tCantent* cantent)
1091 { const tHtmlNode* node = cantent->tree;
1092 while (node != NULL)
1093 { const tHtmlNode* next = node->next;
1094 deallocate_html_node(node); node = next;
1095 }
1096 cantent->tree = NULL;
1097 }
1098
contentblocklist_deallocate(const tContentblock * block)1099 static void contentblocklist_deallocate(const tContentblock* block)
1100 { while (block != NULL)
1101 { const tContentblock* next = block->next;
1102 #if CONFIG_DEBUG
1103 sprint_safe(debugstrbuf, "contentblocklist_deallocate(): %p\n", block);
1104 debugmsg(debugstrbuf);
1105 #endif
1106 __dealloc(block->data); memory_deallocate(block); block = next;
1107 }
1108 }
1109
cantent_put(tCantent * cantent)1110 void cantent_put(tCantent* cantent)
1111 { tContentblock* content;
1112 if (cantent->refcount > 1) { cantent->refcount--; return; }
1113 content = cantent->content;
1114 if (cantent->caf & cafNeedUnmap) my_munmap(content->data, content->used);
1115 else contentblocklist_deallocate(content);
1116 if (cantent->aenum > 0) /* deallocate active elements */
1117 { const tActiveElementBase* aebase = cantent->aebase;
1118 tActiveElementNumber _ae;
1119 for (_ae = 0; _ae < cantent->aenum; _ae++)
1120 deallocate_one_aebase(&(aebase[_ae]));
1121 __dealloc(cantent->aebase);
1122 }
1123 if (cantent->hfnum > 0) /* deallocate forms */
1124 { const tHtmlForm* form = cantent->form;
1125 tHtmlFormNumber count;
1126 for (count = 0; count < cantent->hfnum; count++)
1127 __dealloc(form[count].action_uri);
1128 __dealloc(cantent->form);
1129 }
1130 __dealloc(cantent->redirection); __dealloc(cantent->major_html_title);
1131 cantent_deallocate_tree(cantent); memory_deallocate(cantent);
1132 }
1133
cantent_provide_room(tCantent * cantent,char ** dest,size_t * size,size_t desired_size)1134 static void cantent_provide_room(tCantent* cantent, /*@out@*/ char** dest,
1135 /*@out@*/ size_t* size, size_t desired_size)
1136 { tContentblock* content = cantent->lastcontent;
1137 size_t usable, used;
1138 if (content != NULL) { usable = content->usable; used = content->used; }
1139 else { usable = used = 0; }
1140 if (usable <= used)
1141 { content = cantent_append_new_contentblock(cantent, MAX(desired_size,4096));
1142 usable = content->usable; used = content->used;
1143 }
1144 *dest = content->data + used; *size = usable - used;
1145 }
1146
cantent_collect_str(tCantent * cantent,const char * str)1147 void cantent_collect_str(tCantent* cantent, const char* str)
1148 { size_t todo = strlen(str), available, copysize;
1149 char* dest;
1150 loop:
1151 cantent_provide_room(cantent, &dest, &available, todo);
1152 copysize = MIN(todo, available); my_memcpy(dest, str, copysize);
1153 cantent->lastcontent->used += copysize;
1154 if (todo > copysize) { todo -= copysize; str += copysize; goto loop; }
1155 }
1156
cantent_collect_title(tCantent * cantent,const char * title)1157 void cantent_collect_title(tCantent* cantent, const char* title)
1158 { char* spfbuf;
1159 my_spf(strbuf, STRBUF_SIZE, &spfbuf, strHtmlPageTitle, title, title);
1160 cantent_collect_str(cantent, spfbuf); my_spf_cleanup(strbuf, spfbuf);
1161 }
1162
1163
1164 /** Host information cache; address lookups */
1165
1166 my_enum1 enum
1167 { hppifNone = 0, hppifUserQueryRunning = 0x01
1168 } my_enum2(unsigned char) tHostPortProtInfoFlags;
1169
1170 typedef struct tLoginData
1171 { struct tLoginData* next;
1172 const char *username, *password /* , *account */;
1173 } tLoginData;
1174
1175 typedef struct tHostPortProtInfo
1176 { struct tHostPortProtInfo* next;
1177 struct tCachedHostInformation* host;
1178 tLoginData* login_data;
1179 tPortnumber portnumber; /* (in network byte order) */
1180 tResourceProtocol protocol;
1181 tHostPortProtInfoFlags flags;
1182 } tHostPortProtInfo;
1183
1184 my_enum1 enum
1185 { chifNone = 0, __chifAddressLookupRunning = 0x01
1186 } my_enum2(unsigned char) tCachedHostInformationFlags;
1187
1188 #if CONFIG_ASYNC_DNS
1189 #define chifAddressLookupRunning (__chifAddressLookupRunning)
1190 #endif
1191
1192 #if OPTION_COOKIES
1193 struct tCookie;
1194 #endif
1195
1196 #if OPTION_NEWS
1197 struct tNewsGroupInformation;
1198 #endif
1199
1200 typedef struct tCachedHostInformation
1201 { struct tCachedHostInformation* next;
1202 tSockaddrEntry** sockaddrs;
1203 tHostPortProtInfo* hppi;
1204 const char* hostname;
1205 #if CONFIG_ASYNC_DNS
1206 tDhmGenericData* dhm_data; /* (just for dhmnfOnce on DNS lookup) */
1207 #endif
1208 #if OPTION_COOKIES
1209 struct tCookie* cookies; /* cookies for this host */
1210 #endif
1211 #if OPTION_NEWS
1212 struct tNewsGroupInformation* ngi;
1213 #endif
1214 /* time_t lookupfailuretime; */
1215 tCachedHostInformationFlags flags;
1216 tSockaddrIndex num_sockaddrs;
1217 #if OPTION_COOKIES
1218 unsigned char cookiecount; /* how many cookies we've stored for this host */
1219 #endif
1220 } tCachedHostInformation;
1221
1222 #define resource2actual_host(resource) ((resource)->actual_hppi->host)
1223 #define resource2textual_host(resource) ((resource)->textual_hppi->host)
1224
hppi_lookup(tCachedHostInformation * hostinfo,tPortnumber portnumber,tResourceProtocol protocol,tBoolean create_if_null)1225 static tHostPortProtInfo* hppi_lookup(tCachedHostInformation* hostinfo,
1226 tPortnumber portnumber, tResourceProtocol protocol, tBoolean create_if_null)
1227 { tHostPortProtInfo* hppi = hostinfo->hppi;
1228 while (hppi != NULL)
1229 { if ( (hppi->portnumber == portnumber) && (hppi->protocol == protocol) )
1230 goto out; /* found */
1231 hppi = hppi->next;
1232 }
1233 if ( (hppi == NULL) && (create_if_null) )
1234 { hppi = (tHostPortProtInfo*) memory_allocate(sizeof(tHostPortProtInfo),
1235 mapPermanent);
1236 hppi->next = hostinfo->hppi; hostinfo->hppi = hppi; hppi->host = hostinfo;
1237 hppi->portnumber = portnumber; hppi->protocol = protocol;
1238 }
1239 out:
1240 return(hppi);
1241 }
1242
hppi_login_set(tHostPortProtInfo * hppi,const char * username,const char * password)1243 static void hppi_login_set(tHostPortProtInfo* hppi, const char* username,
1244 const char* password)
1245 { tLoginData* ld;
1246 if ( (username == NULL) || (password == NULL) ) /* "should not happen" */
1247 return;
1248 ld = hppi->login_data;
1249 while (ld != NULL)
1250 { const char* u = ld->username;
1251 if ( (u != NULL) && (!strcmp(u, username)) )
1252 { const char* p = ld->password;
1253 if ( (p == NULL) || (strcmp(p, password)) )
1254 my_strdedup(ld->password, password);
1255 return;
1256 }
1257 ld = ld->next;
1258 }
1259 ld = memory_allocate(sizeof(tLoginData), mapOther);
1260 ld->username = my_strdup(username); ld->password = my_strdup(password);
1261 ld->next = hppi->login_data; hppi->login_data = ld;
1262 }
1263
1264 #if 0
1265 static void hppi_login_get(const tHostPortProtInfo* hppi,
1266 /*@out@*/ const char** username, /*@out@*/ const char** password)
1267 {
1268 }
1269 #endif
1270
1271 #define HASHTABLESIZE_CHI (HASHTABLESIZE)
1272 static tCachedHostInformation* chi_head[HASHTABLESIZE_CHI];
1273
hostinfo_hostname2hashindex(const char * hostname)1274 static tHashIndex hostinfo_hostname2hashindex(const char* hostname)
1275 /* calculates a hash table index from the given hostname; we take at most the
1276 first ten characters of the name into account - this "should be enough" for
1277 good hashing and we need not do "%" after each partial operation (because
1278 the maximum possible value, 10 * 255, fits into a tHashIndex variable). */
1279 { tHashIndex retval = 0;
1280 unsigned char count = 10;
1281 while (count-- > 0)
1282 { unsigned char ch = *((const unsigned char*) (hostname));
1283 if (ch == '\0') break; /* reached end of string */
1284 retval += ((tHashIndex) ch);
1285 hostname++;
1286 }
1287 return(retval % HASHTABLESIZE_CHI);
1288 }
1289
1290 static /* __sallocator -- not an "only" reference... */
hostinfo_create(const char * hostname)1291 tCachedHostInformation* __callocator hostinfo_create(const char* hostname)
1292 /* creates a new record for the host information cache */
1293 { tHashIndex i = hostinfo_hostname2hashindex(hostname);
1294 tCachedHostInformation* retval = (tCachedHostInformation*)
1295 memory_allocate(sizeof(tCachedHostInformation), mapPermanent);
1296 retval->hostname = my_strdup(hostname);
1297 retval->next = chi_head[i];
1298 chi_head[i] = retval;
1299 return(retval);
1300 }
1301
hostinfo_lookup(const char * hostname)1302 static /*@observer@*/ tCachedHostInformation* hostinfo_lookup(const char*
1303 hostname)
1304 /* tries to find known information about the host with the given <hostname> in
1305 the host information cache */
1306 { tHashIndex i = hostinfo_hostname2hashindex(hostname);
1307 tCachedHostInformation* retval = chi_head[i];
1308 while (retval != NULL)
1309 { if (streqcase(hostname, retval->hostname)) break; /* found */
1310 /* IMPROVEME: "!strcmp()" should be enough here. */
1311 retval = retval->next;
1312 }
1313 return(retval);
1314 }
1315
1316 enum
1317 { halrFine = 0, halrLookupFailed = 1, halrNotInCache = 2
1318 #if CONFIG_ASYNC_DNS
1319 , halrLookupRunning = 3
1320 #endif
1321 };
1322 typedef unsigned char tHostAddressLookupResult;
1323
1324 enum { halfNone = 0, halfInCacheOnly = 0x01, halfEnforcedReload = 0x02 };
1325 typedef unsigned char tHostAddressLookupFlags;
1326
postprocess_dns_lookup(tDnsLookup * dns_lookup)1327 static one_caller void postprocess_dns_lookup(tDnsLookup* dns_lookup)
1328 /* always executed in the main thread - that's the crucial point of this
1329 separate function */
1330 { tCachedHostInformation* hostinfo = dns_lookup->hostinfo;
1331 tSockaddrIndex num = dns_lookup->num, idx;
1332 if (num <= 0) { /* hostinfo->lookupfailuretime = my_time(); */ }
1333 else
1334 { const size_t size = num * sizeof(tSockaddrEntry*);
1335 tSockaddrEntry** s = hostinfo->sockaddrs =__memory_allocate(size,mapOther);
1336 hostinfo->num_sockaddrs = num;
1337 for (idx = 0; idx < num; idx++)
1338 { const tSockaddrEntry* entry = &(dns_lookup->sockaddrs[idx]);
1339 const size_t addrlen = entry->addrlen;
1340 const int address_family = entry->address_family;
1341 const tSockaddr* addr = &(entry->addr);
1342 tSockaddrEntry **head = sockaddr2listhead(addr, addrlen), *e = *head;
1343 while (e != NULL)
1344 { if ( (e->addrlen == addrlen) && (e->address_family == address_family)
1345 && (!my_memdiff(&(e->addr), addr, sizeof(tSockaddr))) )
1346 break; /* found */
1347 e = e->next;
1348 }
1349 if (e == NULL) /* create a new entry */
1350 { e = (tSockaddrEntry*) __memory_allocate(sizeof(tSockaddrEntry),
1351 mapPermanent);
1352 my_memcpy(e, entry, sizeof(tSockaddrEntry));
1353 e->next = *head; *head = e;
1354 }
1355 s[idx] = e;
1356 }
1357 }
1358 memory_deallocate(dns_lookup->hostname); memory_deallocate(dns_lookup);
1359 }
1360
check_proxies(tResourceRequest * request,const tConfigProxy * proxy,const char ** _hostname)1361 static void check_proxies(tResourceRequest* request, const tConfigProxy* proxy,
1362 const char** _hostname)
1363 { tPortnumber reqport = request->uri_data->portnumber;
1364 const char* hostname = *_hostname;
1365 while (proxy != NULL)
1366 { tPortnumber hp = proxy->hosts_portnumber;
1367 if ( ( (hp == 0) || (hp == reqport) ) &&
1368 (my_pattern_matcher(proxy->hosts_pattern, hostname)) )
1369 { const char* h = proxy->proxy_hostname;
1370 if (h != NULL) { *_hostname = h; request->proxy = proxy; }
1371 /* "else": configuration explicitly said: "Don't use a proxy here!" */
1372 break;
1373 }
1374 proxy = proxy->next;
1375 }
1376 }
1377
lookup_hostaddress(tResourceRequest * request,tCachedHostInformation ** hostinforet,tHostAddressLookupFlags half)1378 static tHostAddressLookupResult lookup_hostaddress(tResourceRequest* request,
1379 /*@out@*/ tCachedHostInformation** hostinforet, tHostAddressLookupFlags half)
1380 /* tries to lookup the address information for the given hostname in the host
1381 information cache etc.; if that fails, it tries to start a new DNS lookup */
1382 { const char* hostname = request->uri_data->hostname;
1383 tResourceProtocol protocol = request->uri_data->rp;
1384 const tBoolean in_cache_only = cond2boolean(half & halfInCacheOnly),
1385 enforced_reload = cond2boolean(half & halfEnforcedReload);
1386 tCachedHostInformation* hostinfo;
1387 tDnsLookup* dns_lookup;
1388
1389 /* Check whether we shall use a proxy */
1390
1391 if (protocol == rpHttp)
1392 check_proxies(request, config.http_proxies, &hostname);
1393 #if OPTION_TLS
1394 else if (protocol == rpHttps)
1395 check_proxies(request, config.https_proxies, &hostname);
1396 #endif
1397
1398 #if CONFIG_DEBUG
1399 { char* spfbuf;
1400 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf,
1401 "lookup_hostaddress(): %p,%d,%s,%s\n", request, half,
1402 request->uri_data->hostname, hostname);
1403 debugmsg(spfbuf);
1404 my_spf_cleanup(debugstrbuf, spfbuf);
1405 }
1406 #endif
1407
1408 /* Try to find the address for the <hostname> in the cache */
1409
1410 hostinfo = hostinfo_lookup(hostname);
1411 *hostinforet = hostinfo; /* (may be NULL) */
1412 if (hostinfo != NULL)
1413 {
1414 #if CONFIG_ASYNC_DNS
1415 if (hostinfo->flags & chifAddressLookupRunning) /* not yet finished */
1416 return(halrLookupRunning);
1417 else
1418 #endif
1419 if (hostinfo->sockaddrs != NULL) return(halrFine); /* already calculated */
1420 else if (enforced_reload) goto start_dns_lookup; /* enforce (re-)lookup */
1421 /* IMPLEMENTME: if ( (!in_cache_only) && (->lookupfailuretime is
1422 more than e.g. one minute in the past) ) goto start_dns_lookup; */
1423 return(halrLookupFailed); /* lookup failed or bug occurred */
1424 }
1425 if (in_cache_only) return(halrNotInCache);
1426
1427 /* Seems we have to start a DNS lookup request; to do this, we set up a
1428 tDnsLookup structure and e.g. write its address to the DNS pipe. */
1429
1430 start_dns_lookup:
1431 if (hostinfo == NULL) *hostinforet = hostinfo = hostinfo_create(hostname);
1432 dns_lookup = (tDnsLookup*) memory_allocate(sizeof(tDnsLookup), mapOther);
1433 dns_lookup->hostinfo = hostinfo;
1434 dns_lookup->hostname = my_strdup(hostname);
1435 if (request->uri_data->udf & udfTryIpv6) dns_lookup->flags |= dlfTryIpv6;
1436 #if CONFIG_DEBUG
1437 { char* spfbuf;
1438 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf,
1439 "starting DNS lookup at %p for hostinfo %p, hostname *%s*\n",
1440 dns_lookup, hostinfo, hostname);
1441 debugmsg(spfbuf);
1442 my_spf_cleanup(debugstrbuf, spfbuf);
1443 }
1444 #endif
1445 #if OPTION_THREADING
1446 hostinfo->flags |= chifAddressLookupRunning;
1447 dhm_init(hostinfo, NULL, "hostinfo");
1448 my_write_crucial/*_pipe*/(fd_resource2dns_write, &dns_lookup,
1449 sizeof(dns_lookup));
1450 return(halrLookupRunning);
1451 #else
1452 my_getaddrinfo(dns_lookup); /* this call might block the program */
1453 is_time_valid = falsE; /* might have taken a long time */
1454 postprocess_dns_lookup(dns_lookup);
1455 return( (hostinfo->num_sockaddrs > 0) ? halrFine : halrLookupFailed );
1456 #endif
1457 }
1458
1459
1460 /** Sockets and connections I */
1461
1462 #define NEED_QUITCMD_DISSOLVING (CONFIG_FTP || OPTION_NEWS || OPTION_POP)
1463 #define NEED_DISSOLVING (OPTION_TLS || NEED_QUITCMD_DISSOLVING)
1464
1465 my_enum1 enum
1466 { cnfNone = 0, cnfConnected = 0x01, cnfReading = 0x02, cnfWriting = 0x04,
1467 cnfSuspended = 0x08, cnfDataIsResource = 0x10, cnfWantToWrite = 0x20,
1468 cnfDontReuse = 0x40
1469 #if OPTION_TLS
1470 , cnfTlsDedication = 0x80, cnfTlsHandshaking = 0x100
1471 #endif
1472 #if NEED_DISSOLVING
1473 , cnfDissolving = 0x200
1474 #endif
1475 #if CONFIG_DEBUG
1476 , cnfDebugNodata = 0x400
1477 #endif
1478 } my_enum2(unsigned short) tConnectionFlags;
1479
1480 my_enum1 enum
1481 { ccekConnectSetup = 0, ccekConnectWorked = 1, ccekConnectFailed = 2,
1482 ccekRead = 3, ccekWrite = 4
1483 } my_enum2(unsigned char) tConnCbEventKind;
1484
1485 typedef void (*tConnCbFunc)(struct tConnection*, tConnCbEventKind);
1486
1487 #if NEED_DISSOLVING
1488 typedef tBoolean (*tConnDissolver)(struct tConnection*);
1489 #endif
1490
1491 typedef struct
1492 { tSockaddrEntry** sockaddrs;
1493 tPortnumber portnumber; /* (in network byte order) */
1494 tSockaddrsBitfield tried; /* which IP addresses were already tried */
1495 tSockaddrIndex num, current; /* which IP address is currently tried/used */
1496 } tConnectAttemptData;
1497
1498 #if OPTION_TLS == TLS_MATRIX
1499 /* to handle insufficiencies of the "slightly" too simplistic API... */
1500 my_enum1 enum
1501 { tmcfNone = 0, tmcfDidSetup = 0x01, tmcfWriting = 0x02
1502 } my_enum2(unsigned char) tTmcFlags;
1503 typedef struct
1504 { sslBuf_t incoming, outgoing, temp_in;
1505 tTmcFlags flags;
1506 } tTlsMatrixCrutch;
1507 #define TLS_MCBUFSIZE (((SSL_MAX_RECORD_LEN) & ~31) + 32) /* (nicer alloc?) */
1508 /* The library used SSL_MAX_RECORD_SIZE, but that was strangely changed to
1509 SSL_MAX_RECORD_LEN in MatrixSSL 1.2.1. Brain-damaged... */
1510 #endif
1511
1512 #define NEED_CONNLIST (CONFIG_ABOUT & 1) /* "about:activity" needs it */
1513
1514 typedef struct tConnection
1515 { tConnectAttemptData cad;
1516 #if NEED_CONNLIST
1517 struct tConnection* next;
1518 #endif
1519 tConnCbFunc callback;
1520 #if NEED_DISSOLVING
1521 tConnDissolver dissolver;
1522 #endif
1523 void* data; /* (usually tResource*) */
1524 #if OPTION_TLS
1525 tTlsSession tls_session;
1526 #if OPTION_TLS == TLS_MATRIX
1527 tTlsMatrixCrutch* tmc;
1528 #endif
1529 #endif
1530 #if OPTION_POP || OPTION_TRAP
1531 const char* authstamp;
1532 #endif
1533 const tHostPortProtInfo* hppi;
1534 const void* writedata;
1535 size_t writedata_todo, writedata_done;
1536 int fd;
1537 tConnectionFlags flags;
1538 tResourceProtocol protocol;
1539 tResourceError prelire; /* "preliminary" re */
1540 unsigned char x; /* (meaning depends on protocol) */
1541 } tConnection;
1542
1543 #if (!defined(O_NONBLOCK)) && defined(O_NDELAY)
1544 #define O_NONBLOCK (O_NDELAY)
1545 #endif
1546
make_fd_nonblocking(int fd)1547 static tBoolean make_fd_nonblocking(int fd)
1548 /* tries to make <fd> non-blocking; returns whether it worked */
1549 { tBoolean retval;
1550 #if NEED_FD_REGISTER
1551 tFdKind kind = fd_register_lookup(&fd);
1552 #endif
1553 #if USE_LWIP
1554 if (kind & fdkSocket)
1555 { static const tUint32 constant_one = 1;
1556 /* (lwIP 0.7.2 says "u32_t" - never heard of "int"...) */
1557 errno = 0; /* silly old lwIP versions didn't set errno on error */
1558 retval = cond2boolean(lwip_ioctl(fd, FIONBIO, &constant_one) == 0);
1559 }
1560 else
1561 #endif
1562 { const int flags = fcntl(fd, F_GETFL, 0);
1563 if ( (flags == -1) || (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) )
1564 retval = falsE;
1565 else retval = truE;
1566 }
1567 return(retval);
1568 }
1569
create_socket(int address_family)1570 static int create_socket(int address_family)
1571 /* creates a new TCP socket and sets it to non-blocking mode; returns -2 if the
1572 fd wouldn't be observable, -1 for OS errors or a valid fd if it worked */
1573 { int retval;
1574 #if USE_LWIP
1575 errno = 0; /* silly old lwIP versions didn't set errno on error */
1576 retval = lwip_socket(address_family, SOCK_STREAM, ipprotocolnumber_tcp);
1577 #else
1578 retval = socket(address_family, SOCK_STREAM, ipprotocolnumber_tcp);
1579 #endif
1580 if (retval >= 0)
1581 { fd_register(&retval, fdkSocket);
1582 if (!fd_is_observable(retval)) { my_close_sock(retval); retval = -2; }
1583 else if (!make_fd_nonblocking(retval))
1584 { my_close_sock(retval); retval = -1; }
1585 else make_fd_cloexec(retval);
1586 }
1587 #if CONFIG_DEBUG
1588 sprint_safe(debugstrbuf, "create_socket(%d): %d\n", address_family, retval);
1589 debugmsg(debugstrbuf);
1590 #endif
1591 return(retval);
1592 }
1593
set_portnumber(tSockaddr * addr,int address_family,tPortnumber portnumber)1594 static void set_portnumber(tSockaddr* addr, int address_family,
1595 tPortnumber portnumber)
1596 { if (address_family == AF_INET)
1597 ((struct sockaddr_in*)(addr))->sin_port = portnumber;
1598 #if USE_IPv6
1599 else if (address_family == AF_INET6)
1600 ((struct sockaddr_in6*)(addr))->sin6_port = portnumber;
1601 #endif
1602 /* "else": we don't know the AF, so we don't override default ports. */
1603 }
1604
my_do_connect(int fd,const tSockaddr * addr,size_t addrlen)1605 static int my_do_connect(int fd, const tSockaddr* addr, size_t addrlen)
1606 { int err;
1607 #if NEED_FD_REGISTER
1608 tFdKind kind = fd_register_lookup(&fd);
1609 #endif
1610 #if USE_LWIP
1611 if (kind & fdkSocket)
1612 { errno = 0; /* silly old lwIP versions didn't set errno on error */
1613 err = lwip_connect(fd, (const struct sockaddr*) addr,
1614 (socklen_t) addrlen);
1615 }
1616 else
1617 #endif
1618 { unsigned char loopcount = 0;
1619 loop:
1620 err = connect(fd, (const struct sockaddr*) addr, (socklen_t) addrlen);
1621 if (err != 0)
1622 { if ( (errno == EINTR) && (++loopcount < 100) ) goto loop;
1623 else if ( (errno == EALREADY) && (loopcount > 0) ) errno = EINPROGRESS;
1624 else if (errno != EINPROGRESS)
1625 { int e = errno; my_close_sock(fd); errno = e; }
1626 }
1627 }
1628 return(err);
1629 }
1630
my_connect(int fd,const tSockaddrEntry * entry,tPortnumber portnumber)1631 static int my_connect(int fd, const tSockaddrEntry* entry,
1632 tPortnumber portnumber)
1633 { tSockaddr addr = entry->addr; /* (yes, we copy a structure here) */
1634 set_portnumber(&addr, entry->address_family, portnumber);
1635 return(my_do_connect(fd, &addr, entry->addrlen));
1636 }
1637
1638 #define connect_err2error conn_err2error /* (currently the same) */
1639 #define reConnectionFailureDefault (reConnect)
1640 static int conn_err; /* temporary storage for connection-related errors */
1641
conn_err2error(int err)1642 static tResourceError conn_err2error(int err)
1643 /* translates error codes; use this when a general connection-related (read,
1644 write, connect) error occurs */
1645 { tResourceError re;
1646 switch (err)
1647 { case ECONNREFUSED: re = reRefused; break;
1648 case EADDRNOTAVAIL: re = reNetwork; break; /* CHECKME! */
1649 case ENETUNREACH: re = reNetwork; break;
1650 case ETIMEDOUT: re = reTimeout; break;
1651 case EPIPE: case EAGAIN: re = reServerClosed; break;
1652 #ifdef ECONNRESET
1653 case ECONNRESET: re = reConnReset; break;
1654 #endif
1655 default: re = reConnectionFailureDefault; break;
1656 }
1657 return(re);
1658 }
1659
conn_get_failre(tConnection * conn)1660 static tResourceError conn_get_failre(tConnection* conn)
1661 /* for use in ccekConnectFailed handling */
1662 { tResourceError re = conn->prelire;
1663 if (re == reFine) re = connect_err2error(conn_err);
1664 return(re);
1665 }
1666
conn_set_prelire(tConnection * conn,tResourceError re)1667 static __my_inline void conn_set_prelire(tConnection* conn, tResourceError re)
1668 { conn->prelire = re;
1669 #if CONFIG_DEBUG
1670 sprint_safe(debugstrbuf, "prelire: %p, %d\n", conn, re);
1671 debugmsg(debugstrbuf);
1672 #endif
1673 }
1674
conn_connect(tConnection * conn)1675 static tBoolean conn_connect(tConnection* conn)
1676 /* tries to connect to one of the IP addresses which are "allowed" for <conn>;
1677 returns whether it worked */
1678 { tConnectAttemptData* cad = &(conn->cad);
1679 tSockaddrEntry **list = cad->sockaddrs, *entry;
1680 tSockaddrIndex idx, best;
1681 tSockaddrRating rating, best_rating;
1682 int fd;
1683
1684 conn->flags &= ~cnfConnected; conn->fd = -1;
1685 cad->current = SOCKADDR_INDEX_INVALID;
1686 if (list == NULL) /* no choice (e.g. FTP data connections) */
1687 { failed: return(falsE); }
1688
1689 /* try to find the "best" sockaddr */
1690 bestloop:
1691 best = SOCKADDR_INDEX_INVALID; best_rating = SOCKADDR_RATING_NONE;
1692 for (idx = 0; idx < cad->num; idx++)
1693 { const tSockaddrPortProtInfo* sppi;
1694 if (my_bit_test(&(cad->tried), idx)) continue; /* already tried this one */
1695 entry = list[idx];
1696 sppi = sockaddr2sppi(entry, cad->portnumber, conn->protocol, falsE);
1697 rating = ( (sppi != NULL) ? sppi->rating : SOCKADDR_RATING_NEW );
1698 if (best_rating < rating) { best_rating = rating; best = idx; }
1699 }
1700 if (best == SOCKADDR_INDEX_INVALID) goto failed;
1701
1702 /* try to connect to that sockaddr */
1703 idx = best;
1704 my_bit_set(&(cad->tried), idx); cad->current = idx; entry = list[idx];
1705 #if CONFIG_DEBUG
1706 sockaddr2uistr(entry, strbuf, STRBUF_SIZE / 2);
1707 sprint_safe(debugstrbuf, "conn_connect(%p): af=%d, *%s*\n", conn,
1708 entry->address_family, strbuf);
1709 debugmsg(debugstrbuf);
1710 #endif
1711 fd = create_socket(entry->address_family);
1712 if (fd == -2) /* no amount of further looping could help */
1713 { conn_set_prelire(conn, reTmofd); goto failed; }
1714 else if (fd == -1) conn_set_prelire(conn, reSocket);
1715 else if (fd >= 0)
1716 { int err = my_connect(fd, entry, cad->portnumber);
1717 if ( (err == 0) || ( (err == -1) && (errno == EINPROGRESS) ) )
1718 { if (err == 0) conn->flags |= cnfConnected; /*unlikely when non-blocking*/
1719 conn->fd = fd; return(truE);
1720 }
1721 else conn_set_prelire(conn, connect_err2error(errno));
1722 }
1723 goto bestloop;
1724 }
1725
1726 #if CONFIG_DEBUG
debug_conn_callback(const tConnection * conn,tConnCbEventKind ccek)1727 static void debug_conn_callback(const tConnection* conn, tConnCbEventKind ccek)
1728 { sprint_safe(debugstrbuf, "conn_callback(): fd=%d, ccek=%d, data=%p, x=%d\n",
1729 conn->fd, ccek, conn->data, conn->x);
1730 debugmsg(debugstrbuf);
1731 }
conn_bug(const tConnection * conn,tConnCbEventKind ccek)1732 static void conn_bug(const tConnection* conn, tConnCbEventKind ccek)
1733 { sprint_safe(debugstrbuf, "conn_bug(): fd #%d, ccek=%d\n", conn->fd, ccek);
1734 debugmsg(debugstrbuf);
1735 }
1736 #else
1737 #define debug_conn_callback(conn, ccek) do { } while (0)
1738 #define conn_bug(conn, ccek) do { } while (0)
1739 #endif
1740
1741 #define conn_callback(conn, ccek) \
1742 do \
1743 { debug_conn_callback((conn), (ccek)); \
1744 ((conn)->callback)((conn), (ccek)); \
1745 } while (0)
1746
1747 #define conn2resource(conn) \
1748 ( (conn->flags & cnfDataIsResource) ? ((tResource*) (conn->data)) : NULL )
1749
conn2sockaddr(const tConnection * conn)1750 static tSockaddrEntry* conn2sockaddr(const tConnection* conn)
1751 { tSockaddrEntry* retval;
1752 const tConnectAttemptData* cad = &(conn->cad);
1753 const tSockaddrIndex idx = cad->current;
1754 if (idx != SOCKADDR_INDEX_INVALID) retval = cad->sockaddrs[idx];
1755 else retval = NULL;
1756 return(retval);
1757 }
1758
conn2sppi(const tConnection * conn,tBoolean create_if_null)1759 static tSockaddrPortProtInfo* conn2sppi(const tConnection* conn,
1760 tBoolean create_if_null)
1761 { tSockaddrPortProtInfo* retval;
1762 tSockaddrEntry* entry = conn2sockaddr(conn);
1763 retval = ( (entry != NULL) ? sockaddr2sppi(entry, conn->cad.portnumber,
1764 conn->protocol, create_if_null) : NULL );
1765 return(retval);
1766 }
1767
1768 #if NEED_CONNLIST
1769 static tConnection* connlist_head = NULL;
1770 #endif
1771
conn_create(int fd,tConnCbFunc callback,void * data,tBoolean data_is_resource,const tHostPortProtInfo * hppi,tResourceProtocol protocol)1772 static tConnection* conn_create(int fd, tConnCbFunc callback, void* data,
1773 tBoolean data_is_resource, const tHostPortProtInfo* hppi,
1774 tResourceProtocol protocol)
1775 { tConnection* retval = (tConnection*) memory_allocate(sizeof(tConnection),
1776 mapConnection);
1777 tConnectAttemptData* cad = &(retval->cad);
1778 #if NEED_CONNLIST
1779 retval->next = connlist_head; connlist_head = retval;
1780 #endif
1781 retval->fd = fd; retval->callback = callback; retval->data = data;
1782 if (data_is_resource) retval->flags |= cnfDataIsResource;
1783 retval->hppi = hppi; retval->protocol = protocol;
1784 cad->current = SOCKADDR_INDEX_INVALID;
1785 if (hppi != NULL)
1786 { const tCachedHostInformation* hostinfo = hppi->host;
1787 tSockaddrIndex num = hostinfo->num_sockaddrs;
1788 if (num > 0)
1789 { const size_t size = num * sizeof(tSockaddrEntry*);
1790 tSockaddrEntry** e = cad->sockaddrs = __memory_allocate(size, mapOther);
1791 my_memcpy(e, hostinfo->sockaddrs, size); cad->num = num;
1792 }
1793 cad->portnumber = hppi->portnumber;
1794 }
1795 #if CONFIG_DEBUG
1796 sprint_safe(debugstrbuf, "conn_create(): fd #%d\n", fd);
1797 debugmsg(debugstrbuf);
1798 #endif
1799 return(retval);
1800 }
1801
conn_change_data(tConnection * conn,void * data,tBoolean data_is_resource)1802 static my_inline void conn_change_data(tConnection* conn, void* data,
1803 tBoolean data_is_resource)
1804 { conn->data = data;
1805 if (data_is_resource) conn->flags |= cnfDataIsResource;
1806 else conn->flags &= ~cnfDataIsResource;
1807 }
1808
conn_change_callback(tConnection * conn,tConnCbFunc callback,void * data,tBoolean data_is_resource)1809 static my_inline void conn_change_callback(tConnection* conn,
1810 tConnCbFunc callback, void* data, tBoolean data_is_resource)
1811 { conn->callback = callback;
1812 conn_change_data(conn, data, data_is_resource);
1813 }
1814
conn_cleanup_fd(tConnection * conn)1815 static void conn_cleanup_fd(tConnection* conn)
1816 { int fd = conn->fd;
1817 if (fd >= 0) { my_close_sopi(fd); conn->fd = -1; }
1818 conn->flags &= ~(cnfConnected | cnfReading | cnfWriting | cnfSuspended);
1819 }
1820
conn_remove(tConnection ** _conn)1821 static void conn_remove(tConnection** _conn)
1822 { tConnection* conn = *_conn;
1823
1824 #if NEED_DISSOLVING
1825 { tConnDissolver dissolver = conn->dissolver;
1826 if (dissolver != NULL)
1827 { tBoolean did_dissolve_now = ((dissolver)(conn));
1828 #if CONFIG_DEBUG
1829 sprint_safe(debugstrbuf, "dissolve(%d,%d,%p,%p)\n", conn->fd,
1830 did_dissolve_now, conn, dissolver);
1831 debugmsg(debugstrbuf);
1832 #endif
1833 if (!did_dissolve_now) { *_conn = NULL; /* detach */ return; }
1834 }
1835 }
1836 #endif
1837
1838 #if NEED_CONNLIST
1839 list_extract(&connlist_head, conn, tConnection);
1840 #endif
1841
1842 #if OPTION_TLS
1843 { tTlsSession session = conn->tls_session;
1844 if (session != NULL)
1845 {
1846 #if OPTION_TLS == TLS_GNUTLS
1847 (void) gnutls_deinit(session);
1848 #elif OPTION_TLS == TLS_OPENSSL
1849 SSL_free(session);
1850 #elif OPTION_TLS == TLS_MATRIX
1851 tTlsMatrixCrutch* tmc = conn->tmc;
1852 matrixSslDeleteSession(session);
1853 if (tmc != NULL)
1854 { memory_deallocate(tmc->incoming.buf);
1855 memory_deallocate(tmc->outgoing.buf);
1856 memory_deallocate(tmc->temp_in.buf);
1857 memory_deallocate(tmc);
1858 }
1859 #endif
1860 }
1861 }
1862 #endif
1863
1864 { const tConnectAttemptData* cad = &(conn->cad);
1865 tSockaddrEntry** e = cad->sockaddrs;
1866 __dealloc(e);
1867 }
1868
1869 #if CONFIG_DEBUG
1870 sprint_safe(debugstrbuf, "conn_remove(): fd #%d\n", conn->fd);
1871 debugmsg(debugstrbuf);
1872 #endif
1873 conn_cleanup_fd(conn);
1874 __dealloc(conn->writedata); memory_deallocate(conn);
1875 *_conn = NULL;
1876 }
1877
1878 #if NEED_DISSOLVING
conn_set_dissolver(tConnection * conn,tConnDissolver dissolver)1879 static __my_inline void conn_set_dissolver(tConnection* conn,
1880 tConnDissolver dissolver)
1881 { conn->dissolver = dissolver;
1882 }
conn_do_dissolve(tConnection * conn)1883 static my_inline void conn_do_dissolve(tConnection* conn)
1884 { conn_set_dissolver(conn, NULL); conn_remove(&conn);
1885 }
1886 #endif
1887
check_connect(tConnection * conn)1888 static one_caller void check_connect(tConnection* conn)
1889 /* checks whether a connect() attempt for a socket worked */
1890 { int fd = conn->fd, sockerr = 0;
1891 socklen_t dummy = (socklen_t) sizeof(sockerr);
1892 tSockaddrPortProtInfo* sppi = conn2sppi(conn, truE);
1893 #if NEED_FD_REGISTER
1894 (void) fd_register_lookup(&fd);
1895 #endif
1896 #if USE_LWIP
1897 errno = 0; /* silly old lwIP versions didn't set errno on error */
1898 if (lwip_getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &dummy) != 0)
1899 #else
1900 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &dummy) != 0)
1901 #endif
1902 { conn_err = errno;
1903 failed:
1904 #if CONFIG_DEBUG
1905 sprint_safe(debugstrbuf, "check_connect(): failed for fd #%d; %d,%d\n",
1906 fd, conn_err, sockerr);
1907 debugmsg(debugstrbuf);
1908 #endif
1909 #if CONFIG_CUSTOM_CONN
1910 { tResource* resource = conn2resource(conn);
1911 if ( (resource != NULL) && (doing_custom_conn(resource)) )
1912 custom_conn_tell_error(resource, connect_err2error(conn_err));
1913 }
1914 #endif
1915 if (sppi != NULL) sppi->rating >>= 1;
1916 conn_cleanup_fd(conn);
1917 if (conn_connect(conn)) conn_callback(conn, ccekConnectSetup);
1918 else conn_callback(conn, ccekConnectFailed);
1919 return;
1920 }
1921 if (sockerr != 0) { conn_err = sockerr; goto failed; }
1922 if (sppi != NULL) sppi->rating = MIN(sppi->rating + 2, SOCKADDR_RATING_MAX);
1923 conn_set_prelire(conn, reFine); conn->flags |= cnfConnected;
1924 conn_callback(conn, ccekConnectWorked);
1925 }
1926
conn_io_handler(void * data,tFdObservationFlags flags)1927 static void conn_io_handler(void* data, tFdObservationFlags flags)
1928 { tConnection* conn = (tConnection*) data;
1929 if (flags & fdofRead) conn_callback(conn, ccekRead);
1930 else if (flags & fdofWrite)
1931 { if (conn->flags & cnfConnected) conn_callback(conn, ccekWrite);
1932 else check_connect(conn);
1933 }
1934 }
1935
1936 #define __conn_set_readwrite(c, f) \
1937 fd_observe((c)->fd, conn_io_handler, (c), (f))
1938
conn_set_readwrite(tConnection * conn,tFdObservationFlags flags)1939 static void conn_set_readwrite(tConnection* conn, tFdObservationFlags flags)
1940 { __conn_set_readwrite(conn, flags);
1941 if (flags & fdofRead) conn->flags |= cnfReading;
1942 else conn->flags &= ~cnfReading;
1943 if (flags & fdofWrite) conn->flags |= cnfWriting;
1944 else conn->flags &= ~cnfWriting;
1945 }
1946
1947 #define conn_set_read(conn) conn_set_readwrite(conn, fdofRead)
1948 #define conn_set_write(conn) conn_set_readwrite(conn, fdofWrite)
1949
1950 #if CONFIG_USER_QUERY
conn_set_suspend(tConnection * conn,tBoolean do_suspend)1951 static void conn_set_suspend(tConnection* conn, tBoolean do_suspend)
1952 { tConnectionFlags flags = conn->flags;
1953 tBoolean is_suspended = cond2boolean(flags & cnfSuspended);
1954 if (do_suspend != is_suspended) /* must do something */
1955 { if (do_suspend)
1956 { __conn_set_readwrite(conn, fdofNone); conn->flags |= cnfSuspended; }
1957 else /* unsuspend */
1958 { __conn_set_readwrite(conn, (flags & cnfWriting) ? fdofWrite : fdofRead);
1959 conn->flags &= ~cnfSuspended;
1960 }
1961 }
1962 #if CONFIG_DEBUG
1963 sprint_safe(debugstrbuf, "conn_set_suspend(): fd #%d; %d,%d\n", conn->fd,
1964 do_suspend, is_suspended);
1965 debugmsg(debugstrbuf);
1966 #endif
1967 }
1968 #endif
1969
conn_set_software_id(tConnection * conn,const char * text)1970 static void conn_set_software_id(tConnection* conn, const char* text)
1971 { tSockaddrPortProtInfo* sppi = conn2sppi(conn, truE);
1972 if (sppi != NULL)
1973 { const char *r = my_strchr(text, '\r'), *result = ( (r != NULL) ?
1974 my_strndup(text, r - text) : my_strdup(text) );
1975 __dealloc(sppi->software_id); sppi->software_id = result;
1976 }
1977 }
1978
1979
1980 /** TLS */
1981
1982 #if OPTION_TLS
1983
1984 static tBoolean tls_is_usable = falsE;
1985
1986 #define conn_using_tls(conn) ((conn)->flags & cnfTlsDedication)
1987
resource_in_tls(const tResource * resource,tBoolean whether_handshaking)1988 tBoolean resource_in_tls(const tResource* resource,
1989 tBoolean whether_handshaking)
1990 { tBoolean retval = falsE;
1991 const tConnection* conn = resource->cconn;
1992 if (conn != NULL)
1993 { if (!whether_handshaking) { if (conn_using_tls(conn)) retval = truE; }
1994 else { if (conn->flags & cnfTlsHandshaking) retval = truE; }
1995 }
1996 return(retval);
1997 }
1998
tls_err2te(int err)1999 static tTlsError tls_err2te(int err)
2000 /* converts TLS library error codes to tTlsError codes */
2001 { tTlsError retval;
2002 switch (err)
2003 {
2004 #if OPTION_TLS == TLS_GNUTLS
2005 case GNUTLS_E_EXPIRED: retval = teSessionExpired; break;
2006 case GNUTLS_E_FATAL_ALERT_RECEIVED: retval = teFatalAlert; break;
2007 case GNUTLS_E_FILE_ERROR: retval = teFile; break;
2008 case GNUTLS_E_GOT_APPLICATION_DATA: retval = teData; break;
2009 case GNUTLS_E_MEMORY_ERROR: retval = teOom; break;
2010 case GNUTLS_E_NO_CIPHER_SUITES: case GNUTLS_E_UNKNOWN_CIPHER_SUITE:
2011 retval = teCipher; break;
2012 case GNUTLS_E_COMPRESSION_FAILED: case GNUTLS_E_DECOMPRESSION_FAILED:
2013 case GNUTLS_E_NO_COMPRESSION_ALGORITHMS:
2014 case GNUTLS_E_UNKNOWN_COMPRESSION_ALGORITHM:
2015 retval = teCompression; break;
2016 case GNUTLS_E_DECRYPTION_FAILED: case GNUTLS_E_ENCRYPTION_FAILED:
2017 retval = teCrypt; break;
2018 case GNUTLS_E_ILLEGAL_SRP_USERNAME: case GNUTLS_E_SRP_PWD_ERROR:
2019 case GNUTLS_E_SRP_PWD_PARSING_ERROR:
2020 retval = teSrp; break;
2021 case GNUTLS_E_PKCS1_WRONG_PAD: case GNUTLS_E_PK_DECRYPTION_FAILED:
2022 case GNUTLS_E_PK_ENCRYPTION_FAILED: case GNUTLS_E_PK_SIGN_FAILED:
2023 case GNUTLS_E_PK_SIG_VERIFY_FAILED: case GNUTLS_E_UNKNOWN_PK_ALGORITHM:
2024 retval = tePk; break;
2025 case GNUTLS_E_LARGE_PACKET: case GNUTLS_E_RECORD_LIMIT_REACHED:
2026 case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: case GNUTLS_E_UNEXPECTED_PACKET:
2027 case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
2028 case GNUTLS_E_UNSUPPORTED_VERSION_PACKET:
2029 retval = tePacket; break;
2030 case GNUTLS_E_UNIMPLEMENTED_FEATURE: retval = teUnimplemented; break;
2031 case GNUTLS_E_ERROR_IN_FINISHED_PACKET:
2032 case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET:
2033 retval = teHandshake; break;
2034 #elif OPTION_TLS == TLS_OPENSSL /* || .... */
2035 /* IMPLEMENTME for the other libraries? */
2036 #endif
2037 default: retval = teUnknown; break;
2038 }
2039 return(retval);
2040 }
2041
tls_set_error(tConnection * conn,tTlsError te)2042 static void tls_set_error(tConnection* conn, tTlsError te)
2043 { tResource* resource = conn2resource(conn);
2044 if (resource != NULL)
2045 { resource_set_error(resource, reTls);
2046 if (resource->tls_error == teFine) resource->tls_error = te;
2047 }
2048 }
2049
2050 #if OPTION_TLS == TLS_GNUTLS
2051
2052 #define __tls_gnutls_intr(err) \
2053 ( ((err) == GNUTLS_E_INTERRUPTED) || ((err) == GNUTLS_E_AGAIN) )
2054 #define __tls_gnutls_warn(err) ((err) == GNUTLS_E_WARNING_ALERT_RECEIVED)
2055
2056 static
2057 #if DO_GNUTLS_SUFFIX
2058 gnutls_certificate_client_credentials_t
2059 #else
2060 gnutls_certificate_client_credentials
2061 #endif
2062 tls_xcred;
2063
2064 #if CONFIG_DEBUG
2065
tls_gnutls_debug(const char * operation,int err)2066 static void tls_gnutls_debug(const char* operation, int err)
2067 { tBoolean is_error = cond2boolean(err < 0);
2068 const char* errstr;
2069 char* spfbuf;
2070 if (!is_error) errstr = strEmpty;
2071 else { errstr = gnutls_strerror(err); if (errstr == NULL) errstr=strEmpty; }
2072
2073 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf, "TLS%d: %s (%d%s%s)\n", OPTION_TLS,
2074 operation, err, ( (*errstr != '\0') ? ", " : strEmpty ), errstr);
2075 debugmsg(spfbuf);
2076 my_spf_cleanup(debugstrbuf, spfbuf);
2077 /* Don't take the identifier "err" too seriously. The great GnuTLS
2078 documentation often does not say anything about the meaning of the return
2079 value of a specific function, so it might not actually be related to error
2080 handling... :-( */
2081 }
2082
2083 #define tls_gnutls_debug_errvalue tls_gnutls_debug
2084
2085 #else /* #if CONFIG_DEBUG */
2086
2087 #define tls_gnutls_debug(operation, err) do { } while (0)
2088
tls_gnutls_debug_errvalue(const char * operation,int err)2089 static void tls_gnutls_debug_errvalue(const char* operation, int err)
2090 /* Call this function only if it is _known_ (e.g. from GnuTLS documentation)
2091 that <err> really means an error indicator. */
2092 { if ( (err < 0) && (!__tls_gnutls_intr(err)) && (!__tls_gnutls_warn(err)) )
2093 { tTlsError te = tls_err2te(err);
2094 tBoolean show_te = cond2boolean(is_tls_error_expressive(te));
2095 const char* errstr = gnutls_strerror(err);
2096 char* spfbuf;
2097 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
2098 _("retawq: TLS library error during operation \"%s\": #%d%s%s - %s\n"),
2099 operation, err, (show_te ? strSpacedDash : strEmpty), (show_te ?
2100 _(strTlsError[te]) : strEmpty), null2empty(errstr));
2101 if (may_use_fd2()) my_write_str(fd_stderr, spfbuf);
2102 debugmsg(spfbuf);
2103 my_spf_cleanup(strbuf, spfbuf);
2104 }
2105 }
2106
2107 #endif /* #if CONFIG_DEBUG */
2108
tls_gnutls_check_direction(tConnection * conn)2109 static void tls_gnutls_check_direction(tConnection* conn)
2110 { if (gnutls_record_get_direction(conn->tls_session) == 1)
2111 conn_set_write(conn);
2112 else conn_set_read(conn);
2113 }
2114
2115 #elif OPTION_TLS == TLS_OPENSSL
2116
2117 static SSL_CTX* tls_context = NULL;
2118
2119 #if CONFIG_DEBUG
tls_openssl_print_errtext(const char * str,size_t len,__sunused void * data __cunused)2120 static int tls_openssl_print_errtext(const char* str, size_t len,
2121 __sunused void* data __cunused)
2122 { return(my_write(debugfd, str, len));
2123 }
tls_openssl_debug_errstuff(const char * funcname,const tConnection * conn,int err,int sslge)2124 static void tls_openssl_debug_errstuff(const char* funcname,
2125 const tConnection* conn, int err, int sslge)
2126 { char* spfbuf;
2127 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf,
2128 "TLS%d: SSL_%s(): fd=%d, err=%d, sslge=%d, errno=%d%s%s\n", OPTION_TLS,
2129 funcname, conn->fd, err, sslge, errno, ( (errno != 0) ? strSpacedDash :
2130 strEmpty ), ( (errno != 0) ? my_strerror(errno) : strEmpty ));
2131 debugmsg(spfbuf);
2132 my_spf_cleanup(debugstrbuf, spfbuf);
2133 ERR_print_errors_cb(tls_openssl_print_errtext, NULL);
2134 /* (undocumented, but "hopefully not vanishing" - it is OpenSSL's sole
2135 non-ridiculous error printing interface for file descriptors (like
2136 debugfd)) */
2137 }
2138 #else
2139 #define tls_openssl_debug_errstuff(funcname, conn, err, sslge) do { } while (0)
2140 #endif
2141
tls_openssl_handle_error(tConnection * conn,int sslge)2142 static void tls_openssl_handle_error(tConnection* conn, int sslge)
2143 /* Just one more OpenSSL silliness... */
2144 { if (sslge == SSL_ERROR_SYSCALL)
2145 { tResource* resource = conn2resource(conn);
2146 if (resource != NULL)
2147 { tResourceError re = conn_err2error(errno);
2148 if (re != reConnectionFailureDefault)
2149 { resource_set_error(resource, re); return; }
2150 }
2151 }
2152 tls_set_error(conn, teUnknown);
2153 }
2154
2155 #define tls_openssl_operate(operation, funcname) \
2156 do \
2157 { ERR_clear_error(); err = operation; sslge = SSL_get_error(session, err); \
2158 tls_openssl_debug_errstuff(funcname, conn, err, sslge); \
2159 } while (0)
2160
tls_openssl_shutdown(tConnection * conn)2161 static tBoolean tls_openssl_shutdown(tConnection* conn)
2162 /* returns whether it "finished somehow" */
2163 { tBoolean retval;
2164 tTlsSession session = conn->tls_session;
2165 int err, sslge;
2166 tls_openssl_operate(SSL_shutdown(session), "shutdown");
2167 if ( (sslge == SSL_ERROR_SYSCALL) && (err == 0) )
2168 { /* Just one more OpenSSL silliness. SSL_shutdown() documentation says that
2169 sslge can be wrong if SSL_shutdown() returned 0: "The output of
2170 SSL_get_error(3) may be misleading, as an erroneous SSL_ERROR_SYSCALL
2171 may be flagged even though no error occurred." */
2172 /* An additional silliness: SSL_want() documentation says: "Error
2173 conditions are not handled and must be treated using SSL_get_error(3)."
2174 But since SSL_get_error() "may be misleading" - what to do instead?! */
2175 switch (SSL_want(session))
2176 { case SSL_READING: sslge = SSL_ERROR_WANT_READ; break;
2177 case SSL_WRITING: sslge = SSL_ERROR_WANT_WRITE; break;
2178 }
2179 }
2180 switch (sslge)
2181 { case SSL_ERROR_NONE: retval = truE; break;
2182 case SSL_ERROR_WANT_READ: conn_set_read(conn); retval = falsE; break;
2183 case SSL_ERROR_WANT_WRITE: conn_set_write(conn); retval = falsE; break;
2184 default: tls_openssl_handle_error(conn, sslge); retval = truE; break;
2185 }
2186 return(retval);
2187 }
2188
2189 #elif OPTION_TLS == TLS_MATRIX
2190
2191 #if CONFIG_DEBUG
tls_matrix_debug_errvalue(const char * what,int err)2192 static void tls_matrix_debug_errvalue(const char* what, int err)
2193 { sprint_safe(debugstrbuf, "TLS%d: %s, %d\n", OPTION_TLS, what, err);
2194 debugmsg(debugstrbuf);
2195 }
tls_matrix_debug_oserr(const char * what,int err,int err2)2196 static void tls_matrix_debug_oserr(const char* what, int err, int err2)
2197 { sprint_safe(debugstrbuf, "TLS%d: %s, %d, %d\n", OPTION_TLS, what, err,
2198 ( (err == -1) ? err2 : 0 ));
2199 debugmsg(debugstrbuf);
2200 }
tls_matrix_debug_msd(int msd,unsigned char msde,unsigned char msdal,unsigned char msdad)2201 static void tls_matrix_debug_msd(int msd, unsigned char msde,
2202 unsigned char msdal, unsigned char msdad)
2203 { sprint_safe(debugstrbuf, "TLS%d: msd, %d, %d, %d, %d\n", OPTION_TLS, msd,
2204 msde, msdal, msdad);
2205 debugmsg(debugstrbuf);
2206 }
2207 #else
2208 #define tls_matrix_debug_errvalue(a, b) do { } while (0)
2209 #define tls_matrix_debug_oserr(a, b, c) do { } while (0)
2210 #define tls_matrix_debug_msd(a, b, c, d) do { } while (0)
2211 #endif
2212
tmci_buf(sslBuf_t * buf)2213 static void tmci_buf(/*@out@*/ sslBuf_t* buf)
2214 { const int size = buf->size = TLS_MCBUFSIZE; /* (yes, "int"...) */
2215 buf->buf = buf->start = buf->end = __memory_allocate(size, mapOther);
2216 }
2217
tls_matrix_crutch_init(tConnection * conn)2218 static one_caller void tls_matrix_crutch_init(tConnection* conn)
2219 { tTlsMatrixCrutch* tmc = conn->tmc = (tTlsMatrixCrutch*)
2220 memory_allocate(sizeof(tTlsMatrixCrutch), mapOther);
2221 tmci_buf(&(tmc->incoming)); tmci_buf(&(tmc->outgoing));
2222 tmci_buf(&(tmc->temp_in));
2223 }
2224
2225 #endif /* TLS variants */
2226
tls_initialize(void)2227 static one_caller tBoolean tls_initialize(void)
2228 { static tBoolean tls_did_init = falsE;
2229 if (tls_did_init) goto out;
2230 tls_did_init = truE;
2231
2232 #if OPTION_TLS == TLS_GNUTLS
2233
2234 { char* spfbuf;
2235 int err = gnutls_global_init();
2236 tls_gnutls_debug_errvalue(_(strInitialization), err);
2237 if (err != 0) goto out;
2238 err = gnutls_certificate_allocate_credentials(&tls_xcred);
2239 tls_gnutls_debug_errvalue("allocate credentials", err);
2240 my_spf(strbuf, STRBUF_SIZE, &spfbuf, strPercsPercs, config.path,
2241 "gnutls-ca.pem");
2242 err = gnutls_certificate_set_x509_trust_file(tls_xcred, spfbuf,
2243 GNUTLS_X509_FMT_PEM);
2244 tls_gnutls_debug("trustfile", err);
2245 my_spf_cleanup(strbuf, spfbuf);
2246 tls_is_usable = truE;
2247 }
2248
2249 #elif OPTION_TLS == TLS_OPENSSL
2250
2251 { SSL_METHOD* m;
2252 char seedfilenamebuf[512];
2253 const char* seedfilename;
2254 time_t seedval;
2255 unsigned short randcount;
2256 SSL_load_error_strings(); /* Do something before... */
2257 (void) SSL_library_init(); /* ...initializing; what a sane concept! :-( */
2258
2259 /* Handle this random rubbish which a sane library would do itself... */
2260 debugmsg("TLS: random A\n");
2261 if (RAND_status()) goto rand_done; /* nothing to do, nice */
2262 debugmsg("TLS: random B\n");
2263 seedfilename = RAND_file_name(seedfilenamebuf, sizeof(seedfilenamebuf));
2264 if (seedfilename != NULL)
2265 { debugmsg("TLS: random C\n");
2266 #if CONFIG_DEBUG
2267 { char* spfbuf;
2268 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf, "TLS%d: seedfilename=%s\n",
2269 OPTION_TLS, seedfilename);
2270 debugmsg(spfbuf);
2271 my_spf_cleanup(debugstrbuf, spfbuf);
2272 }
2273 #endif
2274 if (RAND_egd(seedfilename) >= 0) goto rand_finish;
2275 debugmsg("TLS: random D\n");
2276 (void) RAND_load_file(seedfilename, -1);
2277 /* ("-1" is allowed for OpenSSL >= 0.9.5) */
2278 if (RAND_status()) goto rand_finish;
2279 }
2280 debugmsg("TLS: random E\n");
2281 seedval = my_time() ^ ((~getpid()) * (~getppid())); /* good enough? :-) */
2282 RAND_seed((const void*) (&seedval), sizeof(seedval));
2283 if (RAND_status()) goto rand_finish;
2284 debugmsg("TLS: random F\n");
2285 srand48(seedval);
2286 randcount = 10000; /* wild guess, thanks to lack of useful documentation,
2287 just to avoid infinite loops */
2288 while (randcount-- > 0)
2289 { long l = lrand48();
2290 RAND_seed((const void*) (&l), sizeof(l));
2291 if (RAND_status()) goto rand_counted;
2292 }
2293 debugmsg("TLS: random G\n");
2294 /* CHECKME: what to do here? Set reRandom? Just hope the best?? */
2295 rand_counted: {}
2296 #if CONFIG_DEBUG
2297 sprint_safe(debugstrbuf, "TLS%d: remaining randcount: %d\n", OPTION_TLS,
2298 randcount);
2299 debugmsg(debugstrbuf);
2300 #endif
2301 rand_finish:
2302 debugmsg("TLS: random H\n");
2303 if (seedfilename != NULL) RAND_write_file(seedfilename);
2304 rand_done:
2305 debugmsg("TLS: random Z\n");
2306
2307 m = SSLv23_client_method();
2308 if ( (m == NULL) || ( (tls_context = SSL_CTX_new(m)) == NULL ) )
2309 { if (may_use_fd2())
2310 my_write_str(fd_stderr, _("retawq: OpenSSL initialization failed\n"));
2311 goto out;
2312 }
2313 (void) SSL_CTX_set_options(tls_context, SSL_OP_ALL);
2314 /* (OpenSSL documentation says this is safe; stupid-me doubts it somehow,
2315 esp. for SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, but...) */
2316 (void) SSL_CTX_set_mode(tls_context, SSL_MODE_ENABLE_PARTIAL_WRITE);
2317 tls_is_usable = truE;
2318 }
2319
2320 #elif OPTION_TLS == TLS_MATRIX
2321
2322 { int err = matrixSslOpen();
2323 tls_matrix_debug_errvalue(_(strInitialization), err);
2324 if (err == 0) tls_is_usable = truE;
2325 }
2326
2327 #endif
2328
2329 out:
2330 return(tls_is_usable);
2331 }
2332
tls_deinitialize(void)2333 static one_caller void tls_deinitialize(void)
2334 {
2335 #if OPTION_TLS == TLS_GNUTLS
2336 if (tls_is_usable) gnutls_global_deinit();
2337 #elif OPTION_TLS == TLS_OPENSSL
2338 if (tls_context != NULL) SSL_CTX_free(tls_context);
2339 #elif OPTION_TLS == TLS_MATRIX
2340 if (tls_is_usable) matrixSslClose();
2341 #endif
2342 }
2343
tls_do_handshaking(tConnection * conn)2344 static unsigned char tls_do_handshaking(tConnection* conn)
2345 { unsigned char retval; /* 0=error, 1=proceeding, 2=done */
2346 tTlsSession session = conn->tls_session;
2347
2348 #if OPTION_TLS == TLS_GNUTLS
2349
2350 int err = gnutls_handshake(session);
2351 tls_gnutls_debug_errvalue("handshake", err);
2352 if ( (__tls_gnutls_intr(err)) || (__tls_gnutls_warn(err)) )
2353 { tls_gnutls_check_direction(conn); retval = 1; }
2354 else if (err < 0) { tls_set_error(conn, tls_err2te(err)); retval = 0; }
2355 else retval = 2;
2356
2357 #elif OPTION_TLS == TLS_OPENSSL
2358
2359 int err, sslge;
2360 tls_openssl_operate(SSL_connect(session), "connect");
2361 switch (sslge)
2362 { case SSL_ERROR_NONE: retval = 2; break;
2363 case SSL_ERROR_WANT_READ: conn_set_read(conn); retval = 1; break;
2364 case SSL_ERROR_WANT_WRITE: conn_set_write(conn); retval = 1; break;
2365 default: tls_openssl_handle_error(conn, sslge); retval = 0; break;
2366 }
2367
2368 #elif OPTION_TLS == TLS_MATRIX
2369
2370
2371 #endif
2372
2373 if (retval == 1)
2374 {
2375 #if CONFIG_DEBUG
2376 if (!(conn->flags & cnfTlsHandshaking))
2377 { sprint_safe(debugstrbuf, "TLS%d: starting handshake, fd #%d\n",
2378 OPTION_TLS, conn->fd);
2379 debugmsg(debugstrbuf);
2380 }
2381 #endif
2382 conn->flags |= cnfTlsHandshaking;
2383 }
2384 else
2385 { conn->flags &= ~cnfTlsHandshaking;
2386 if (retval == 2)
2387 { if (conn->flags & cnfWantToWrite) conn_set_write(conn);
2388 else conn_set_read(conn);
2389 }
2390 }
2391 return(retval);
2392 }
2393
tls_session_dissolver_callback(tConnection * conn,tConnCbEventKind ccek)2394 static void tls_session_dissolver_callback(tConnection* conn,
2395 tConnCbEventKind ccek)
2396 {
2397 #if OPTION_TLS == TLS_GNUTLS
2398 tTlsSession session = conn->tls_session;
2399 int err;
2400 #endif
2401
2402 switch (ccek)
2403 {
2404 #if OPTION_TLS == TLS_GNUTLS
2405 case ccekRead:
2406 err = gnutls_record_recv(session, NULL, 0);
2407 handle_err:
2408 #if CONFIG_DEBUG
2409 sprint_safe(debugstrbuf, "TLS%d: dis - %d,%d,%d\n", OPTION_TLS, conn->fd,
2410 ccek, err);
2411 debugmsg(debugstrbuf);
2412 #endif
2413 if ( (__tls_gnutls_intr(err)) || (__tls_gnutls_warn(err)) )
2414 tls_gnutls_check_direction(conn);
2415 else goto do_dissolve; /* error or done */
2416 break;
2417 case ccekWrite:
2418 err = gnutls_record_send(session, NULL, 0); goto handle_err;
2419 /*@notreached@*/ break;
2420 #elif OPTION_TLS == TLS_OPENSSL
2421 case ccekRead: case ccekWrite:
2422 if (tls_openssl_shutdown(conn)) goto do_dissolve;
2423 break;
2424 #elif OPTION_TLS == TLS_MATRIX
2425 #endif
2426 default: conn_bug(conn, ccek); do_dissolve: conn_do_dissolve(conn); break;
2427 }
2428 }
2429
tls_session_dissolver(tConnection * conn)2430 static tBoolean tls_session_dissolver(tConnection* conn)
2431 /* returns whether it actually dissolved the session right now */
2432 { tBoolean retval;
2433 #if OPTION_TLS == TLS_GNUTLS
2434 int err;
2435 conn->flags &= ~cnfDataIsResource;
2436 err = gnutls_alert_send(conn->tls_session, GNUTLS_AL_WARNING,
2437 GNUTLS_A_CLOSE_NOTIFY);
2438 tls_gnutls_debug("alert_send", err);
2439 if ( (__tls_gnutls_intr(err)) || (__tls_gnutls_warn(err)) )
2440 { tls_gnutls_check_direction(conn); retval = falsE; }
2441 else retval = truE; /* done (unlikely since non-blocking) or error */
2442 #elif OPTION_TLS == TLS_OPENSSL
2443 conn->flags &= ~cnfDataIsResource;
2444 retval = tls_openssl_shutdown(conn);
2445 #elif OPTION_TLS == TLS_MATRIX
2446 #endif
2447 if (!retval)
2448 { conn_set_dissolver(conn, NULL); conn->flags |= cnfDissolving;
2449 conn_change_callback(conn, tls_session_dissolver_callback, NULL, falsE);
2450 }
2451 return(retval);
2452 }
2453
tls_session_init(tConnection * conn,const char * hostname)2454 static tBoolean tls_session_init(tConnection* conn, const char* hostname)
2455 { tBoolean retval = falsE;
2456 tTlsSession session;
2457 tTlsError te = teFine;
2458 #if OPTION_TLS == TLS_GNUTLS
2459 static const int cert_type_priority[] =
2460 { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };
2461 int err;
2462 #elif OPTION_TLS == TLS_MATRIX
2463 int err;
2464 #endif
2465
2466 if (!tls_initialize())
2467 {
2468 #if (OPTION_TLS == TLS_OPENSSL) || (OPTION_TLS == TLS_MATRIX)
2469 init_failed:
2470 #endif
2471 te = teInit; goto out;
2472 }
2473
2474 #if OPTION_TLS == TLS_GNUTLS
2475
2476 err = gnutls_init(&session, GNUTLS_CLIENT);
2477 tls_gnutls_debug_errvalue("initialize session", err);
2478 if (err != 0) { te = tls_err2te(err); goto out; }
2479 conn->tls_session = session;
2480 err = gnutls_set_default_priority(session);
2481 tls_gnutls_debug("set default priority", err);
2482 err = gnutls_certificate_type_set_priority(session, cert_type_priority);
2483 tls_gnutls_debug("set certificate type priority", err);
2484 err = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, tls_xcred);
2485 tls_gnutls_debug("set credentials", err);
2486 gnutls_transport_set_ptr(session, (my_gnutls_transport_ptr) conn->fd);
2487 if (hostname != NULL)
2488 { err = gnutls_server_name_set(session, GNUTLS_NAME_DNS, hostname,
2489 strlen(hostname));
2490 tls_gnutls_debug("nameset", err);
2491 }
2492 /* FIXME: RFC2818, 3.1! */
2493
2494 #elif OPTION_TLS == TLS_OPENSSL
2495
2496 session = conn->tls_session = SSL_new(tls_context);
2497 if (session == NULL) goto init_failed;
2498 (void) SSL_set_fd(session, conn->fd);
2499
2500 #elif OPTION_TLS == TLS_MATRIX
2501
2502 err = matrixSslNewSession(&session, NULL, NULL, 0);
2503 tls_matrix_debug_errvalue("session init", err);
2504 if (err != 0) goto init_failed;
2505 conn->tls_session = session;
2506 tls_matrix_crutch_init(conn);
2507 err = matrixSslEncodeClientHello(session, &(conn->tmc->outgoing), 0);
2508 tls_matrix_debug_errvalue("client hello", err);
2509 if (err != 0) goto init_failed;
2510
2511 #endif
2512
2513 /* try to start the handshaking */
2514 if (tls_do_handshaking(conn) == 0) goto out;
2515
2516 /* 't worked */
2517 conn->flags |= cnfTlsDedication;
2518 conn_set_dissolver(conn, tls_session_dissolver);
2519 retval = truE;
2520
2521 out:
2522 if (te != teFine) tls_set_error(conn, te);
2523 return(retval);
2524 }
2525
tls_record_read(tConnection * conn,void * buf,size_t size,tBoolean * _was_interrupted)2526 static ssize_t tls_record_read(tConnection* conn, /*@out@*/ void* buf,
2527 size_t size, /*@out@*/ tBoolean* _was_interrupted)
2528 { ssize_t retval;
2529 tTlsSession session = conn->tls_session;
2530
2531 #if OPTION_TLS == TLS_GNUTLS
2532
2533 *_was_interrupted = falsE;
2534 retval = gnutls_record_recv(session, buf, size);
2535 tls_gnutls_debug_errvalue(_("read"), retval);
2536 if ( (__tls_gnutls_intr(retval)) || (__tls_gnutls_warn(retval)) )
2537 { tls_gnutls_check_direction(conn);
2538 zeroy: retval = 0; *_was_interrupted = truE;
2539 }
2540 else if (retval == GNUTLS_E_REHANDSHAKE)
2541 { if (tls_do_handshaking(conn) != 0) goto zeroy;
2542 }
2543 else if (retval < 0) tls_set_error(conn, tls_err2te(retval));
2544
2545 #elif OPTION_TLS == TLS_OPENSSL
2546
2547 int err, sslge;
2548 *_was_interrupted = falsE;
2549 tls_openssl_operate(SSL_read(session, buf, size), "read");
2550 switch (sslge)
2551 { case SSL_ERROR_NONE: retval = err; break;
2552 case SSL_ERROR_WANT_READ:
2553 conn_set_read(conn);
2554 zeroy: retval = 0; *_was_interrupted = truE;
2555 break;
2556 case SSL_ERROR_WANT_WRITE: conn_set_write(conn); goto zeroy; break;
2557 default: tls_openssl_handle_error(conn, sslge); retval = 0; break;
2558 }
2559
2560 #elif OPTION_TLS == TLS_MATRIX
2561
2562
2563 #endif
2564
2565 return(retval);
2566 }
2567
tls_record_write(tConnection * conn,const void * buf,size_t size,tBoolean * _was_interrupted)2568 static ssize_t tls_record_write(tConnection* conn, const void* buf,
2569 size_t size, /*@out@*/ tBoolean* _was_interrupted)
2570 { ssize_t retval;
2571 tTlsSession session = conn->tls_session;
2572
2573 #if OPTION_TLS == TLS_GNUTLS
2574
2575 retval = gnutls_record_send(session, buf, size);
2576 tls_gnutls_debug_errvalue(_("write"), retval);
2577 if ( (__tls_gnutls_intr(retval)) || (__tls_gnutls_warn(retval)) )
2578 { tls_gnutls_check_direction(conn); *_was_interrupted = truE; retval = 0; }
2579 else
2580 { *_was_interrupted = falsE;
2581 if (retval < 0) tls_set_error(conn, tls_err2te(retval));
2582 }
2583
2584 #elif OPTION_TLS == TLS_OPENSSL
2585
2586 int err, sslge;
2587 *_was_interrupted = falsE;
2588 tls_openssl_operate(SSL_write(session, buf, size), "write");
2589 switch (sslge)
2590 { case SSL_ERROR_NONE: retval = err; break;
2591 case SSL_ERROR_WANT_READ:
2592 conn_set_read(conn);
2593 zeroy: retval = 0; *_was_interrupted = truE;
2594 break;
2595 case SSL_ERROR_WANT_WRITE: conn_set_write(conn); goto zeroy; break;
2596 default: tls_openssl_handle_error(conn, sslge); retval = 0; break;
2597 }
2598
2599 #elif OPTION_TLS == TLS_MATRIX
2600
2601
2602 #endif
2603
2604 return(retval);
2605 }
2606
2607 #define tls_record_write_str(conn, str, wi) \
2608 tls_record_write(conn, str, strlen(str), wi)
2609
tls_version_is_old(const char * version)2610 static one_caller tBoolean tls_version_is_old(const char* version)
2611 /* returns whether the TLS/libgcrypt library version is known to be "old" */
2612 { static const int minpart[3] = /* "most recent" version number */
2613 #if OPTION_TLS == TLS_GNUTLS
2614 { 1, 2, 10 }; /* GnuTLS */
2615 #elif OPTION_TLS == TLS_OPENSSL
2616 { 0, 9, 7 }; /* OpenSSL */
2617 #define minpart_ch ('i')
2618 #elif OPTION_TLS == TLS_MATRIX
2619 { 1, 7, 3 }; /* MatrixSSL */
2620 #elif OPTION_TLS == TLS_BUILTIN
2621 { 1, 2, 1 }; /* libgcrypt */
2622 #endif
2623 tBoolean retval = falsE;
2624 const char* temp = version;
2625 unsigned char count;
2626 int part[3];
2627
2628 /* try to parse the version string; since the format of this string might
2629 change in the future etc., let's be careful */
2630 for (count = 0; count <= 2; count++)
2631 { if (!my_isdigit(*temp)) goto out;
2632 my_atoi(temp, &(part[count]), &temp, 999);
2633 if (count < 2)
2634 { if (*temp != '.') goto out;
2635 else temp++;
2636 }
2637 }
2638
2639 /* understood the version string, now check the version */
2640 for (count = 0; count <= 2; count++)
2641 { int p = part[count], m = minpart[count];
2642 if (p != m)
2643 { if (p < m) retval = truE;
2644 goto out;
2645 }
2646 }
2647 #if OPTION_TLS == TLS_OPENSSL
2648 { char ch = *temp;
2649 if ( (my_islower(ch)) && (*(temp + 1) == ' ') && (ch < minpart_ch) )
2650 retval = truE;
2651 }
2652 #undef minpart_ch
2653 #endif
2654
2655 out:
2656 return(retval);
2657 }
2658
tls_version_warning(const char * version)2659 static one_caller const char* tls_version_warning(const char* version)
2660 /* returns an HTML warning text if the TLS/libgcrypt library version is known
2661 to be "old" */
2662 { const char* retval = (tls_version_is_old(version) ?
2663 _(" <b>Warning</b>: this version is out of date!") : strEmpty);
2664 return(retval);
2665 }
2666
2667 #endif /* #if OPTION_TLS */
2668
2669
2670 /** Communication with main.c */
2671
resource_finalize(tResource * resource)2672 static void resource_finalize(tResource* resource)
2673 { resource->flags |= rfFinal; stop_save_as(resource);
2674 }
2675
push_to_main_res(tResource * resource,unsigned char flags)2676 static void push_to_main_res(tResource* resource, unsigned char flags)
2677 { if (!(flags & 1)) resource_finalize(resource);
2678 dhm_notify(resource, dhmnfDataChange);
2679 /* IMPLEMENTME: sometimes just "dhmnfMetadataChange"! */
2680 }
2681
push_to_main_req(tResourceRequest * request,unsigned char flags)2682 static void push_to_main_req(tResourceRequest* request, unsigned char flags)
2683 { if (!(flags & 1))
2684 { tResource* resource = request->resource;
2685 if (resource != NULL) resource_finalize(resource);
2686 }
2687 dhm_notify(request, dhmnfDataChange);
2688 /* IMPLEMENTME: sometimes just "dhmnfMetadataChange"! */
2689 }
2690
2691 #if CONFIG_USER_QUERY
2692
resource_suspend(const tResource * resource)2693 static void resource_suspend(const tResource* resource)
2694 { tConnection* conn = resource->cconn;
2695 if (conn != NULL) conn_set_suspend(conn, truE);
2696 }
2697
resource_unsuspend(const tResource * resource)2698 static void resource_unsuspend(const tResource* resource)
2699 { tConnection* conn = resource->cconn;
2700 if (conn != NULL) conn_set_suspend(conn, falsE);
2701 }
2702
user_query_deallocate(const tUserQuery * query)2703 static __my_inline void user_query_deallocate(const tUserQuery* query)
2704 { memory_deallocate(query); /* (_currently_ nothing further to do) */
2705 }
2706
resource_ask_anything(tResource * resource,tUserQueryCallback callback,tMissingInformationFlags mif,unsigned char flags)2707 static tBoolean resource_ask_anything(tResource* resource,
2708 tUserQueryCallback callback, tMissingInformationFlags mif,
2709 unsigned char flags)
2710 /* flags: "&1": prior login attempt failed */
2711 { tBoolean retval = falsE;
2712 tHostPortProtInfo* hppi;
2713 if ( (!is_promptable)
2714 #if CONFIG_CONSOLE
2715 || (program_mode == pmConsole)
2716 #endif
2717 )
2718 goto out; /* no way of prompting a user; but IMPLEMENTME for console! */
2719 if (flags & 1) mif |= mifPriorLoginAttemptFailed;
2720 hppi = ( (mif & mifProxyrelated) ? resource->actual_hppi :
2721 resource->textual_hppi );
2722 if (hppi->flags & hppifUserQueryRunning) /* already a query running */
2723 { goto out; /* IMPLEMENTME: "wait" instead! */
2724 }
2725 else /* start a query */
2726 { tUserQuery* query = (tUserQuery*) memory_allocate(sizeof(tUserQuery),
2727 mapUserQuery);
2728 query->callback = callback; query->resource = resource; query->hppi = hppi;
2729 query->mif = mif; query->hostname = hppi->host->hostname;
2730 query->portnumber = hppi->portnumber;
2731 user_query_queue(query);
2732 if (query->mif & mifQueryFailed) /* main.c knows in advance it won't go */
2733 { user_query_deallocate(query); goto out; }
2734 resource_suspend(resource);
2735 hppi->flags |= hppifUserQueryRunning;
2736 retval = truE;
2737 }
2738 out:
2739 return(retval);
2740 }
2741
resource_ask_finish(tUserQuery * query)2742 static void resource_ask_finish(tUserQuery* query)
2743 { tHostPortProtInfo* hppi = query->hppi;
2744 /* IMPLEMENTME: store login information in hppi! */
2745 hppi->flags &= ~hppifUserQueryRunning;
2746 /* IMPLEMENTME: kick any resources which were "waiting" on this hppi! */
2747 user_query_deallocate(query);
2748 }
2749
2750 #endif /* #if CONFIG_USER_QUERY */
2751
2752
2753 /** Helper functions II */
2754
rp2portnumber(tResourceProtocol rp)2755 tPortnumber rp2portnumber(tResourceProtocol rp)
2756 /* returns the default portnumber for the given protocol */
2757 { tPortnumber retval;
2758 switch (rp)
2759 { case rpHttp: retval = portnumber_http; break;
2760 case __rpFtp: case __rpFtps: retval = portnumber_ftp; break;
2761 case __rpFinger: retval = portnumber_finger; break;
2762 case rpCvs: retval = portnumber_cvs; break;
2763 case __rpGopher: retval = portnumber_gopher; break;
2764 case __rpNntp: retval = portnumber_nntp; break;
2765 case __rpPop: case __rpPops: retval = portnumber_pop3; break;
2766 case __rpHttps: retval = portnumber_https; break;
2767 default: retval = 0; break; /* might happen (for unknown schemes) */
2768 }
2769 return(retval);
2770 }
2771
rp2scheme(tResourceProtocol rp,char * dest)2772 void rp2scheme(tResourceProtocol rp, char* dest)
2773 /* intended for user interface texts */
2774 { const char* str = NULL;
2775 tBoolean use_uc = cond2boolean(__has_rpflag(rp, rpfUiToupper)),
2776 use_alt = cond2boolean(__has_rpflag(rp, rpfUiAlternate));
2777 if (use_alt)
2778 { if (rp == __rpNntp) str = strNntp;
2779 else if (rp == __rpPop) str = strPop3;
2780 }
2781 if (str == NULL) str = rp_data[rp].scheme;
2782 if (use_uc) my_strcpy_toupper(dest, str);
2783 else strcpy(dest, str);
2784 }
2785
2786 #if CONFIG_BLOAT & BLOAT_SSC
ssc2info(tResourceProtocol rp,tServerStatusCode ssc)2787 const char* ssc2info(tResourceProtocol rp, tServerStatusCode ssc)
2788 /* tries to convert a server status code to a nice, human-readable string; this
2789 produces mostly very general texts right now - IMPLEMENTME further! */
2790 { const char* retval = strEmpty; /* default */
2791 static const char strIntermediary[] = N_("intermediary"),
2792 strServerError[] = N_("server error"), strOkay[] = N_("okay"),
2793 strAuthentReq[] = N_("authentication required");
2794 #if OPTION_NEWS || CONFIG_FTP
2795 static const char strServerReady[] = N_("server ready");
2796 #endif
2797 #if CONFIG_FTP
2798 static const char strAccountRequired[] = N_("account required");
2799 #endif
2800
2801 if ( (is_httplike(rp))
2802 #if OPTION_LOCAL_CGI
2803 || (rp == rpLocalCgi)
2804 #endif
2805 )
2806 { switch (ssc / 100)
2807 { case 1: retval = _(strIntermediary); break;
2808 case 2: retval = _(strOkay); break;
2809 case 3: retval = _(strRedirection); break;
2810 case 4:
2811 if (ssc == 401) retval = _(strAuthentReq);
2812 else if (ssc == 407) retval = _(strProxyAuthentReq);
2813 else retval = _("request error");
2814 break;
2815 case 5: retval = _(strServerError); break;
2816 }
2817 }
2818 #if CONFIG_FTP
2819 else if (is_ftplike(rp))
2820 { switch (ssc / 100)
2821 { case 1: retval = _(strIntermediary); break;
2822 case 2:
2823 if (ssc == 200) retval = _(strOkay);
2824 else if (ssc == 220) retval = _(strServerReady);
2825 else if (ssc == 221) retval = _("server closed");
2826 else if (ssc == 226) retval = _("transfer successful");
2827 else if ( (ssc == 227) || (ssc == 229) )
2828 retval = _("entering passive mode");
2829 else if ( (ssc == 230)
2830 #if OPTION_TLS
2831 || ( (rp == rpFtps) && (ssc == 232) )
2832 #endif
2833 )
2834 { retval = _("logged in"); }
2835 break;
2836 case 3:
2837 if (ssc == 331) retval = _("password required");
2838 else if (ssc == 332) retval = _(strAccountRequired);
2839 else if (ssc == 350) retval = _(strIntermediary);
2840 break;
2841 case 4:
2842 if (ssc == 421) retval = _("service not available");
2843 else retval = _("transient server problem");
2844 break;
2845 case 5:
2846 if (ssc == 530) retval = _(strResourceError[reLogin]);
2847 else if (ssc == 532) retval = _(strAccountRequired);
2848 else if (ssc == 550) retval = _("file/directory not found");
2849 else retval = _(strServerError);
2850 break;
2851 case 6: retval = _("protected reply"); break;
2852 }
2853 }
2854 #endif
2855 #if OPTION_NEWS
2856 else if (rp == rpNntp)
2857 { switch (ssc / 100)
2858 { case 2:
2859 if ( (ssc == 200) || (ssc == 201) ) retval = _(strServerReady);
2860 else if (ssc == 211) retval = _("group selected");
2861 else if ( (ssc == 215) || (ssc == 231) )
2862 retval = _("news groups follow");
2863 else if ( (ssc >= 220) && (ssc <= 222) ) retval = _("content follows");
2864 break;
2865 case 4:
2866 if (ssc == 411) retval = _("no such news group");
2867 else if ( ( (ssc >= 421) && (ssc <= 423) ) || (ssc == 430) )
2868 retval = _("no such article");
2869 else if (ssc == 480) retval = _(strAuthentReq);
2870 break;
2871 }
2872 }
2873 #endif
2874 return(retval);
2875 }
2876 #endif /* BLOAT_SSC */
2877
is_filesuffix(const char * filename,const char * suff)2878 static tBoolean is_filesuffix(const char* filename, const char* suff)
2879 /* returns whether the <filename> has the suffix <suff>; the latter must be
2880 given in lowercase! */
2881 { size_t filenamelen = strlen(filename), sufflen = strlen(suff);
2882 const char* temp;
2883 if (filenamelen < sufflen + 1 /* dot */ + 1 /* at least one other char */)
2884 return(falsE);
2885 temp = filename + filenamelen - sufflen;
2886 if ( (*(temp - 1)) != '.' ) return(falsE);
2887 if (!streqcase(temp, suff)) return(falsE);
2888 return(truE);
2889 }
2890
filesuffix2rck(const char * name)2891 static tResourceContentKind filesuffix2rck(const char* name)
2892 /* calculates a resource content kind based on the suffix of <name> */
2893 { if ( (is_filesuffix(name, strHtml)) || (is_filesuffix(name, "htm")) ||
2894 (is_filesuffix(name, strShtml)) || (is_filesuffix(name, "phtml")) )
2895 return(rckHtml);
2896 return(rckUnknown);
2897 }
2898
calc_parentpath(const char * path,char * staticbuf,size_t size)2899 static tBoolean calc_parentpath(const char* path, char* staticbuf, size_t size)
2900 { tBoolean retval = falsE;
2901 size_t pathlen = strlen(path);
2902 if (pathlen > 1)
2903 { const char* p = path + pathlen - 1;
2904 if (*p == chDirsep) p--;
2905 while (p >= path)
2906 { if (*p == chDirsep)
2907 { size_t len = p - path + 1;
2908 if (len + 3 < size)
2909 { my_memcpy(staticbuf, path, len); staticbuf[len] = '\0';
2910 retval = truE;
2911 }
2912 break;
2913 }
2914 p--;
2915 }
2916 }
2917 return(retval);
2918 }
2919
htmlify(const char * src)2920 const char* htmlify(const char* src)
2921 /* converts special HTML characters in <src> */
2922 { enum { convnum = 3 };
2923 static const struct
2924 { const char* replacement;
2925 unsigned short len; /* == strlen(replacement) */
2926 char ch;
2927 } conv[convnum] =
2928 { /* { """, 6, '"' }, */ /* #34 */
2929 { "&", 5, '&' }, /* #38 */
2930 /* { "'", 5, '\'' }, */ /* #39 */
2931 { "<", 4, '<' }, /* #60 */
2932 { ">", 4, '>' } /* #62 */
2933 };
2934 size_t destlen;
2935 const char* srctmp;
2936 char ch, *retval, *desttmp;
2937 unsigned short count;
2938 tBoolean need_conversion = falsE;
2939
2940 /* Calculate the length of the resulting string: */
2941 destlen = 1;
2942 srctmp = src;
2943 while ( (ch = *srctmp++) != '\0' )
2944 { for (count = 0; count < convnum; count++)
2945 { if (ch == conv[count].ch)
2946 { destlen += conv[count].len;
2947 need_conversion = truE;
2948 goto next_char_1;
2949 }
2950 }
2951 destlen++;
2952 next_char_1: {}
2953 }
2954 if (!need_conversion) return(src); /* the most likely case */
2955
2956 /* Build the resulting string: */
2957 retval = desttmp = __memory_allocate(destlen, mapString);
2958 srctmp = src;
2959 while ( (ch = *srctmp++) != '\0' )
2960 { for (count = 0; count < convnum; count++)
2961 { if (ch == conv[count].ch)
2962 { const char* tmp = conv[count].replacement;
2963 while ( (ch = *tmp++) != '\0' ) *desttmp++ = ch;
2964 goto next_char_2;
2965 }
2966 }
2967 *desttmp++ = ch; /* no replacement */
2968 next_char_2: {}
2969 }
2970 *desttmp = '\0';
2971 return(retval);
2972 }
2973
2974
2975 /** Connections */
2976
2977 #if OPTION_TLS
2978
__conn_read(tConnection * conn,void * buf,size_t bufsize,tBoolean * _was_interrupted)2979 static ssize_t __conn_read(tConnection* conn, /*@out@*/ void* buf,
2980 size_t bufsize, /*@out@*/ tBoolean* _was_interrupted)
2981 { ssize_t retval;
2982 if (conn_using_tls(conn))
2983 { retval = tls_record_read(conn, buf, bufsize, _was_interrupted);
2984 if (retval < 0) tls_set_error(conn, tls_err2te(retval));
2985 }
2986 else
2987 { retval = my_read_sopi(conn->fd, buf, bufsize); *_was_interrupted = falsE; }
2988 return(retval);
2989 }
2990
2991 #define conn_read(resultvar, conn, buf, bufsize, action_if_interrupted) \
2992 do \
2993 { tBoolean was_interrupted; \
2994 resultvar = __conn_read(conn, buf, bufsize, &was_interrupted); \
2995 if (was_interrupted) { action_if_interrupted; } \
2996 } while (0)
2997
2998 #else
2999
3000 #define conn_read(resultvar, conn, buf, bufsize, action_if_interrupted) \
3001 resultvar = my_read_sopi(conn->fd, buf, bufsize)
3002
3003 #endif
3004
conn_set_writedata(tConnection * conn,const void * data,size_t size)3005 static void conn_set_writedata(tConnection* conn, const void* data,
3006 size_t size)
3007 /* sets a new message to be sent over the network for this connection */
3008 { __dealloc(conn->writedata); conn->writedata = data;
3009 conn->writedata_todo = size; conn->writedata_done = 0;
3010 conn->flags |= cnfWantToWrite;
3011 #if CONFIG_DEBUG
3012 conn->flags &= ~cnfDebugNodata;
3013 #endif
3014 }
3015
conn_set_writestr(tConnection * conn,const char * str)3016 static __my_inline void conn_set_writestr(tConnection* conn, const char* str)
3017 { conn_set_writedata(conn, str, strlen(str));
3018 }
3019
conn_write_writedata(tConnection * conn)3020 static unsigned char conn_write_writedata(tConnection* conn)
3021 /* tries to write as much of the current message as possible; return value:
3022 0=error, 1=proceeding, 2=done */
3023 { const char *data = conn->writedata, *src;
3024 size_t todo, done, writelen;
3025 ssize_t write_err;
3026 int fd;
3027
3028 if (data == NULL) /* "should not happen" */
3029 { failed: conn->flags &= ~cnfWantToWrite; return(0); }
3030 todo = conn->writedata_todo; done = conn->writedata_done;
3031
3032 src = data + done; writelen = todo - done; fd = conn->fd;
3033 #if OPTION_TLS
3034 if (conn_using_tls(conn))
3035 { tBoolean was_interrupted;
3036 write_err = tls_record_write(conn, src, writelen, &was_interrupted);
3037 if (was_interrupted) return(1);
3038 if (write_err < 0) tls_set_error(conn, tls_err2te(write_err));
3039 }
3040 else
3041 #endif
3042 { write_err = __my_write/*_sopi*/(fd, src, writelen); }
3043
3044 if (write_err <= 0) goto failed;
3045 done += write_err; conn->writedata_done = done;
3046 #if CONFIG_DEBUG
3047 { sprint_safe(debugstrbuf, "cww: %d wrote %d/%d:\n", fd, done, todo);
3048 debugmsg(debugstrbuf);
3049 if (!(conn->flags & cnfDebugNodata))
3050 { __debugmsg(src, write_err);
3051 if (src[write_err - 1] != '\n') debugmsg(strNewline);
3052 }
3053 }
3054 #endif
3055 if (done >= todo)
3056 { conn->flags &= ~cnfWantToWrite; dealloc(conn->writedata); return(2); }
3057 else return(1);
3058 }
3059
3060 #if NEED_QUITCMD_DISSOLVING
3061
quitcmd_dissolver_callback(tConnection * conn,tConnCbEventKind ccek)3062 static void quitcmd_dissolver_callback(tConnection* conn,
3063 tConnCbEventKind ccek)
3064 /* I like spaghetti. Hey, this function is almost readable... :-) */
3065 { switch (ccek)
3066 {
3067 #if OPTION_TLS
3068 case ccekRead:
3069 if (!conn_using_tls(conn)) goto handle_bug;
3070 /* "else": */ /*@fallthrough@*/
3071 #endif
3072 case ccekWrite:
3073 switch (conn_write_writedata(conn))
3074 { case 0: do_dissolve: conn_do_dissolve(conn); break;
3075 case 1: break;
3076 case 2:
3077 #if OPTION_TLS
3078 if (conn_using_tls(conn))
3079 { /* We handled the "high-level" quitcmd, now we have to handle the
3080 "low-level" TLS close_notify alert. */
3081 conn_set_dissolver(conn, tls_session_dissolver);
3082 conn_remove(&conn);
3083 }
3084 else
3085 #endif
3086 { goto do_dissolve; /*@notreached@*/ }
3087 break;
3088 }
3089 break;
3090 default:
3091 #if OPTION_TLS
3092 handle_bug:
3093 #endif
3094 conn_bug(conn, ccek); goto do_dissolve; /*@notreached@*/ break;
3095 }
3096 }
3097
quitcmd_dissolver(tConnection * conn)3098 static tBoolean quitcmd_dissolver(tConnection* conn)
3099 /* returns whether it actually dissolved the session right now */
3100 { tBoolean retval;
3101 if ( (conn->writedata_done <= 0) || (!(conn->flags & cnfWantToWrite)) )
3102 { /* The dissolving attempt doesn't happen in the middle of a write (which
3103 could happen e.g. if we dissolve due to a write() call failure); so we
3104 can safely try to send a nice "quit" command to the server. */
3105 static const char strNetcmdQuit[] = "QUIT\r\n";
3106 conn_set_dissolver(conn, NULL); conn->flags |= cnfDissolving;
3107 conn_change_callback(conn, quitcmd_dissolver_callback, NULL, falsE);
3108 conn_set_writestr(conn, my_strdup(strNetcmdQuit));
3109 conn_set_write(conn);
3110 retval = falsE;
3111 }
3112 #if OPTION_TLS
3113 else if (conn_using_tls(conn))
3114 { /* We can't send the "high-level" quitcmd, but maybe we can at least send
3115 the "low-level" TLS close_notify alert. */
3116 retval = tls_session_dissolver(conn);
3117 }
3118 #endif
3119 else retval = truE; /* can't do anything */
3120 #if CONFIG_DEBUG
3121 sprint_safe(debugstrbuf, "quitcmd_dissolver(): %d,%d\n", conn->fd, retval);
3122 debugmsg(debugstrbuf);
3123 #endif
3124 return(retval);
3125 }
3126
3127 #endif /* #if NEED_QUITCMD_DISSOLVING */
3128
3129
3130 /** Low-level resource handling */
3131
3132 #if 0
3133 static size_t ramcachesize_used = 0, ramcachesize_freeable = 0;
3134 #if CONFIG_DISK_CACHE
3135 static size_t diskcachesize_used = 0;
3136 #endif
3137 #endif
3138
3139 #define HASHTABLESIZE_RC (HASHTABLESIZE)
3140 static tResource* rc_head[HASHTABLESIZE_RC];
3141
resource_uri2hashindex(const char * _uri)3142 static tHashIndex resource_uri2hashindex(const char* _uri)
3143 /* calculates a hash table index from the given URI; we take at most the
3144 first 50 characters of the URI into account - this "should be enough" for
3145 good hashing and we need not do "%" after each partial operation (because
3146 the maximum possible value, 50 * 255, fits into a tHashIndex variable). */
3147 { tHashIndex retval = 0;
3148 const unsigned char* uri = (const unsigned char*) _uri;
3149 unsigned char count = 50;
3150 while (count-- > 0)
3151 { const unsigned char ch = *uri++;
3152 if (ch == '\0') break;
3153 retval += ((tHashIndex) ch);
3154 }
3155 return(retval % HASHTABLESIZE_RC);
3156 }
3157
attach_resource_to_request(tResourceRequest * request,tResource * resource)3158 static void attach_resource_to_request(tResourceRequest* request,
3159 tResource* resource)
3160 { dhm_attach(request->resource, resource);
3161 request->state = rrsAttachedResource; request->flags |= rrfResourceChanged;
3162 dhm_notify(request, dhmnfAttachery);
3163 }
3164
resource_stop(tResource * resource)3165 static void resource_stop(tResource* resource)
3166 { tBoolean did = falsE;
3167 if (resource->cconn != NULL) { conn_remove(&(resource->cconn)); did = truE; }
3168 if (resource->dconn != NULL) { conn_remove(&(resource->dconn)); did = truE; }
3169 if (resource->save_as != NULL) { stop_save_as(resource); did = truE; }
3170 if ( (did) && (resource->state != rsError) ) resource->state = rsStopped;
3171 }
3172
3173 #if USE_RPSD
3174
3175 typedef void (*tRpsdRemover)(struct tRpsdGeneric*);
3176 typedef struct tRpsdGeneric
3177 { tRpsdRemover remover; /* must be first in any specific rpsd struct */
3178 } tRpsdGeneric;
3179
rpsd_remove(tResource * resource)3180 static void rpsd_remove(tResource* resource)
3181 { tRpsdGeneric* rpsd = resource->rpsd;
3182 if (rpsd != NULL)
3183 { tRpsdRemover remover = rpsd->remover;
3184 if (remover != NULL) (remover)(rpsd);
3185 memory_deallocate(rpsd); resource->rpsd = NULL;
3186 }
3187 }
3188
3189 #endif
3190
resource_deallocate(tResource * resource)3191 static one_caller void resource_deallocate(tResource* resource)
3192 { const char* uri = resource->uri_data->uri;
3193 const tHashIndex i = resource_uri2hashindex(uri);
3194 #if CONFIG_DEBUG
3195 { char* spfbuf;
3196 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf, "resource_deallocate(%p,%s)\n",
3197 resource, uri);
3198 debugmsg(spfbuf);
3199 my_spf_cleanup(debugstrbuf, spfbuf);
3200 }
3201 #endif
3202 dhm_notify(resource, dhmnfRemoval);
3203
3204 /* Remove from the RAM cache */
3205
3206 if (rc_head[i] == resource) rc_head[i] = resource->next;
3207 if (resource->prev != NULL) resource->prev->next = resource->next;
3208 if (resource->next != NULL) resource->next->prev = resource->prev;
3209
3210 /* Stop any ongoing I/O for this resource */
3211
3212 resource_stop(resource);
3213
3214 /* Deallocate the resource */
3215
3216 debugmsg("deallocating resource\n");
3217 sinking_data_deallocate(&(resource->sinking_data));
3218 uri_detach(resource->uri_data); cantent_put(resource->cantent);
3219 #if USE_RPSD
3220 rpsd_remove(resource);
3221 #endif
3222 memory_deallocate(resource);
3223 }
3224
resource_dhm_control(void * _resource,__sunused void * data __cunused,tDhmControlCode dcc)3225 static void resource_dhm_control(void* _resource, __sunused void* data
3226 __cunused, tDhmControlCode dcc)
3227 { tResource* resource = (tResource*) _resource;
3228 switch (dcc)
3229 { case dhmccRefcount0:
3230 resource_deallocate(resource);
3231 /* IMPLEMENTME: only deallocate it if the "RAM quota" is exceeded! */
3232 break;
3233 }
3234 }
3235
3236 static /* __sallocator -- not an "only" reference */ tResource* __callocator
resource_create(tResourceRequest * request,tContentblock * content,size_t nominal_contentlength,tResourceContentKind kind,tResourceState state)3237 resource_create(tResourceRequest* request, /*const*/ tContentblock* content,
3238 size_t nominal_contentlength, tResourceContentKind kind,tResourceState state)
3239 { const tUriData* uri_data = request->uri_data;
3240 const char* uri = uri_data->uri;
3241 const tResourceProtocol protocol = uri_data->rp;
3242 const tHashIndex i = resource_uri2hashindex(uri);
3243 tResource* retval = memory_allocate(sizeof(tResource), mapResource);
3244 tCantent* cantent = cantent_create();
3245 #if CONFIG_DEBUG
3246 sprint_safe(debugstrbuf, "created resource at %p\n", retval);
3247 debugmsg(debugstrbuf);
3248 #endif
3249 dhm_init(retval, resource_dhm_control, "resource");
3250 uri_attach(retval->uri_data, request->uri_data);
3251 cantent_attach(retval->cantent, cantent); cantent->kind = kind;
3252 attach_resource_to_request(request, retval);
3253 if (rc_head[i] != NULL)
3254 { retval->next = rc_head[i]; rc_head[i]->prev = retval; }
3255 rc_head[i] = retval;
3256 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3257 if (request->flags & rrfDownload)
3258 retval->flags |= rfDownload | rfActivityWatched;
3259 #endif
3260 retval->protocol = protocol; retval->state = state;
3261 retval->sinking_data = request->sinking_data; request->sinking_data = NULL;
3262 cantent_set_firstcontent(cantent, content);
3263 retval->nominal_contentlength = nominal_contentlength;
3264 return(retval);
3265 }
3266
resource_lookup(tResourceRequest * request)3267 static tResource* resource_lookup(tResourceRequest* request)
3268 /* tries to find a resource with the given URI (from <request>) in the internal
3269 resource cache and attaches it to <request> if found; make sure to call this
3270 only after calculating the _final_ URI (e.g. after appending a "/" to the
3271 name of a local directory if necessary)! */
3272 { const char *uri, *post1;
3273 tHashIndex i;
3274 tResource* resource;
3275 tResourceProtocol protocol;
3276 tResourceRequestFlags rrf;
3277
3278 if ( (request->action != rraLoad)
3279 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3280 || (request->flags & rrfDownload)
3281 #endif
3282 )
3283 { /* We're not "allowed" to look into the cache for this request, e.g.
3284 because the user wants to _re_-load the resource. */
3285 goto out;
3286 }
3287 if ( (request->flags & rrfIsRedirection) &&
3288 (is_httplike(request->uri_data->rp)) )
3289 goto out; /* cf. comment about Apache httpd in http_setup_reqstr() */
3290
3291 /* Look into the RAM cache */
3292
3293 uri = request->uri_data->uri; i = resource_uri2hashindex(uri);
3294 resource = rc_head[i];
3295 if (resource == NULL) goto try_disk; /* (optimizing for most likely case) */
3296 protocol = request->uri_data->rp; rrf = request->flags;
3297 post1 = null2empty(request->uri_data->post);
3298
3299 while (resource != NULL)
3300 { if (resource->protocol != protocol) goto do_next; /* different protocols */
3301 if ( ((rrf & rrfPost) != 0) != ((resource->flags & rfPost) != 0) )
3302 { goto do_next; } /* different method */
3303 if (strcmp(resource->uri_data->uri, uri)) goto do_next; /* different URI */
3304 if (strcmp(post1, null2empty(resource->uri_data->post))) goto do_next;
3305 /* CHECKME: also require identical authorization? */
3306 attach_resource_to_request(request, resource);
3307 #if CONFIG_DEBUG
3308 { char* spfbuf;
3309 my_spf(debugstrbuf, STRBUF_SIZE, &spfbuf,
3310 "found resource in RAM cache (%p,%s)\n", resource, uri);
3311 debugmsg(spfbuf);
3312 my_spf_cleanup(debugstrbuf, spfbuf);
3313 }
3314 #endif
3315 return(resource);
3316 do_next: resource = resource->next;
3317 }
3318
3319 /* Look into the disk cache */
3320
3321 try_disk: {}
3322
3323 /* Nothing found... */
3324
3325 out:
3326 return(NULL);
3327 }
3328
3329 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3330 static char* download_buf = NULL;
3331 static size_t download_buf_size;
3332 #endif
3333
resource_provide_room(tResource * resource,char ** dest,size_t * size,size_t desired_size,unsigned char flags)3334 static void resource_provide_room(tResource* resource, /*@out@*/ char** dest,
3335 /*@out@*/ size_t* size, size_t desired_size, unsigned char flags)
3336 /* <desired_size> and <flags> are only relevant if resource->lastcontent isn't
3337 used: "&1": desired_size is exact; "&2": it's parameter for ocbs();
3338 otherwise it's minimum */
3339 { tCantent* cantent = resource->cantent;
3340 tContentblock* lastcontent;
3341 size_t s;
3342 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3343 const tBoolean is_download = cond2boolean(resource->flags & rfDownload);
3344 #endif
3345
3346 if (
3347 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3348 (!is_download) &&
3349 #endif
3350 ( (lastcontent = cantent->lastcontent) != NULL ) )
3351 { const size_t usable = lastcontent->usable, used = lastcontent->used;
3352 if (used < usable) /* there's some free room left */
3353 { *dest = lastcontent->data + used; *size = usable - used; return; }
3354 }
3355
3356 if ( (flags & 1) && (desired_size > 0) )
3357 { /* ("desired_size > 0" "should" always be true if "flags & 1"...) */
3358 s = desired_size;
3359 }
3360 else if (flags & 2) s = optimal_contentblocksize(desired_size);
3361 else
3362 { size_t ocbs = optimal_contentblocksize(0);
3363 s = MAX(desired_size, ocbs);
3364 }
3365
3366 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3367 if (is_download)
3368 { if (download_buf == NULL) download_buf_size = 4096;
3369 if ( (download_buf_size < s) && (download_buf_size < HIGHEST_OCBS) )
3370 { while ( (download_buf_size < s) && (download_buf_size < HIGHEST_OCBS) )
3371 download_buf_size <<= 1;
3372 dealloc(download_buf);
3373 }
3374 if (download_buf == NULL)
3375 download_buf = __memory_allocate(download_buf_size, mapPermanent);
3376 *dest = download_buf; *size = download_buf_size;
3377 }
3378 else
3379 #endif
3380 { const tContentblock* block = cantent_append_new_contentblock(cantent, s);
3381 *dest = block->data; *size = block->usable;
3382 }
3383 }
3384
resource_record(tResource * resource,const char * data,size_t size)3385 static void resource_record(tResource* resource, const char* data, size_t size)
3386 {
3387 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3388 if (resource->flags & rfDownload)
3389 { const int fd = resource->sinking_data->download_fd;
3390 const ssize_t err = my_write(fd, data, size);
3391 if (err < 0) resource_set_error(resource, conn_err2error(errno));
3392 /* FIXME: handle <err> further, stop the downloading, notify user for
3393 custom connections! */
3394 if ( (data == download_buf) && (size >= download_buf_size) )
3395 { /* looks like the server was fast enough to fill our buffer completely,
3396 so a larger buffer might increase throughput */
3397 if (download_buf_size < HIGHEST_OCBS)
3398 { download_buf_size <<= 1; memory_deallocate(download_buf);
3399 download_buf = __memory_allocate(download_buf_size, mapPermanent);
3400 }
3401 }
3402 }
3403 else
3404 #endif
3405 { resource->cantent->lastcontent->used += size;
3406 do_save_as(resource, data, size);
3407 }
3408 resource->bytecount += size;
3409 if (resource->flags & rfActivityWatched) got_activity = truE;
3410 }
3411
resource_collect(tResource * resource,const char * src,size_t size)3412 static void resource_collect(tResource* resource, const char* src, size_t size)
3413 {
3414 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
3415 if (resource->flags & rfDownload) resource_record(resource, src, size);
3416 else /* might have to record it in smaller pieces */
3417 #endif
3418 { size_t available_size, copy_size;
3419 tBoolean did_loop = falsE;
3420 char* dest;
3421 loop:
3422 resource_provide_room(resource, &dest, &available_size, size, 0);
3423 copy_size = MIN(size, available_size);
3424 my_memcpy(dest, src, copy_size); src += copy_size; size -= copy_size;
3425 resource_record(resource, dest, copy_size);
3426 if ( (size > 0) && (!did_loop) ) { did_loop = truE; goto loop; }
3427 #if CONFIG_DEBUG
3428 if (size > 0) debugmsg("BUG: resource_collect()\n"); /* "can't happen" */
3429 #endif
3430 }
3431 }
3432
resource_collect_str(tResource * resource,const char * str)3433 static __my_inline void resource_collect_str(tResource* resource,
3434 const char* str)
3435 { resource_collect(resource, str, strlen(str));
3436 }
3437
resource_collect_title2(tResource * resource,const char * h,const char * b)3438 static void resource_collect_title2(tResource* resource, const char* h,
3439 const char* b)
3440 /* builds an HTML document intro with title strings for head and body parts */
3441 { char buf[STRBUF_SIZE];
3442 char* spfbuf;
3443 my_spf(buf, STRBUF_SIZE, &spfbuf, strHtmlPageTitle, h, b);
3444 resource_collect_str(resource, spfbuf);
3445 my_spf_cleanup(buf, spfbuf);
3446 }
3447
resource_collect_title(tResource * resource,const char * title)3448 static __my_inline void resource_collect_title(tResource* resource,
3449 const char* title)
3450 { resource_collect_title2(resource, title, title);
3451 }
3452
3453
3454 /* Some stuff for HTTP/about resources */
3455
3456 #if OPTION_COOKIES
3457 #include "cookie.c"
3458 #else
3459 #define cookie_collect(resource) (strEmpty)
3460 #define cookie_collect_cleanup(str) do { } while (0)
3461 #endif
3462
3463 /* begin-autogenerated */
3464 my_enum1 enum
3465 { hhpsDontCare = 0, hhpsConnection = 1, hhpsContentLength = 2,
3466 hhpsContentType = 3, hhpsLocation = 4, hhpsServer = 5, hhpsSetCookie2 = 6,
3467 hhpsSetCookie = 7, hhpsStatus = 8, hhpsTransferEncoding = 9,
3468 hhpsWwwAuthenticate = 10
3469 } my_enum2(unsigned char) tHttpHeaderParsingState;
3470 #define MAX_HHPS (10)
3471
3472 static const char* const strHhps[MAX_HHPS + 1] =
3473 { strA /*don't care*/, "connection:", "content-length:", "content-type:",
3474 "location:", "server:", "set-cookie2:", "set-cookie:", "status:",
3475 "transfer-encoding:", "www-authenticate:"
3476 };
3477
3478 static const unsigned char hhpslen[MAX_HHPS + 1] =
3479 { 1, 11, 15, 13, 9, 7, 12, 11, 7, 18, 17 };
3480
3481 #if CONFIG_ABOUT & 4
prepare_about_ctconfig(void)3482 static one_caller void prepare_about_ctconfig(void)
3483 { sprint_safe(strbuf, "OPTION_TEXTMODEMOUSE = %d\n<br>OPTION_I18N = %d\n<br>OPTION_CED = %d\n<br>OPTION_COOKIES = %d\n<br>OPTION_NEWS = %d\n<br>OPTION_LOCAL_CGI = %d\n<br>OPTION_EXECEXT = %d\n<br>OPTION_TLS = %d\n<br>OPTION_IPV6 = %d\n<br>OPTION_THREADING = %d\n<br>OPTION_BIRTCFG = %d", OPTION_TEXTMODEMOUSE, OPTION_I18N, OPTION_CED, OPTION_COOKIES, OPTION_NEWS, OPTION_LOCAL_CGI, OPTION_EXECEXT, OPTION_TLS, OPTION_IPV6, OPTION_THREADING, OPTION_BIRTCFG);
3484 }
3485 #endif
3486 /* end-autogenerated */
3487
3488
3489 /** "about:" resources */
3490
3491 #if USE_S2U
3492
resource_ui_conn_ip(const tResource * resource,char * buf,size_t size)3493 tBoolean resource_ui_conn_ip(const tResource* resource, char* buf, size_t size)
3494 /* produces a user interface string containing an IP address related to the
3495 <resource> if appropriate */
3496 { tBoolean retval = falsE;
3497 const tConnection* conn;
3498 const tSockaddrEntry* entry;
3499 if ( (resource->state == rsConnecting) &&
3500 ( (conn = resource->cconn) != NULL ) &&
3501 ( (entry = conn2sockaddr(conn)) != NULL ) )
3502 { sockaddr2uistr(entry, buf, size);
3503 if (*buf != '\0') retval = truE;
3504 }
3505 /* IMPLEMENTME: append portnumber; check ->dconn for FTP-like protocols? */
3506 return(retval);
3507 }
3508
3509 #endif
3510
fetch_about(tResourceRequest * request)3511 static one_caller void fetch_about(tResourceRequest* request)
3512 { const char* path;
3513 if (resource_lookup(request) != NULL) return; /* found in cache, done */
3514 path = request->uri_data->path;
3515
3516 if (!strcmp(path, strRetawq))
3517 { tResource* resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
3518 rckHtml, rsComplete);
3519 char* spfbuf;
3520
3521 /* introductory text */
3522
3523 #if CONFIG_TG == TG_CONSOLE
3524 #define WHAT ", console"
3525 #elif CONFIG_TG == TG_NCURSES
3526 #define WHAT ", ncurses"
3527 #elif CONFIG_TG == TG_XCURSES
3528 #define WHAT ", xcurses"
3529 #elif CONFIG_TG == TG_BICURSES
3530 #define WHAT ", bicurses"
3531 #elif TGC_IS_CURSES
3532 #define WHAT ", curses"
3533 #elif CONFIG_TG == TG_X
3534 #define WHAT ", X Window System"
3535 #elif CONFIG_TG == TG_GTK
3536 #define WHAT ", GTK"
3537 #else
3538 #define WHAT (strEmpty)
3539 #endif
3540
3541 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<html><head><title>About retawq</title></head>\n<body>\n<h3 align=\"center\"><b>retawq %s (%s mode%s)</b></h3>\n<p>The web browser <b>retawq</b> is released <b>without any warranty</b>. The project home page is <a href=\"http://retawq.sourceforge.net/\">retawq.sourceforge.net</a>.</p>\n"), RETAWQ_VERSION, _(strTG), WHAT); /* what the h[ae]ck... :-) */
3542 resource_collect_str(resource, spfbuf);
3543 my_spf_cleanup(strbuf, spfbuf);
3544
3545 #undef WHAT
3546
3547 /* support text */
3548
3549 #if OPTION_I18N || CONFIG_JAVASCRIPT || CONFIG_CSS || OPTION_TLS
3550 { static const char strCommaSpace[] = ", ";
3551 tBoolean is_first = truE;
3552 resource_collect_str(resource,
3553 _("<p>This program contains support for: "));
3554 #define ADDSUPP(str) \
3555 do \
3556 { if (is_first) is_first = falsE; \
3557 else resource_collect_str(resource, strCommaSpace); \
3558 resource_collect_str(resource, str); \
3559 } while (0)
3560 #if OPTION_I18N
3561 ADDSUPP("i18n");
3562 #endif
3563 #if CONFIG_JAVASCRIPT
3564 ADDSUPP("Javascript");
3565 #endif
3566 #if CONFIG_CSS
3567 ADDSUPP("CSS");
3568 #endif
3569 #if OPTION_TLS
3570 ADDSUPP(strTLS);
3571 #endif
3572 #undef ADDSUPP
3573 resource_collect_str(resource, ".</p>\n");
3574 }
3575 #endif
3576
3577 /* UI library text */
3578
3579 spfbuf = NULL;
3580
3581 #if CONFIG_TG == TG_X
3582
3583 if (xws_display != NULL)
3584 { my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>Currently using <a href=\"http://www.x.org/\">X Window System</a> library version %d.%d (vendor: %s, release %d) on display \"%s\".</p>\n"), ProtocolVersion(xws_display), ProtocolRevision(xws_display), ServerVendor(xws_display), VendorRelease(xws_display), DisplayString(xws_display));
3585 }
3586
3587 #elif CONFIG_TG == TG_GTK
3588
3589 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>Currently using <a href=\"http://www.gtk.org/\">GTK</a> library version %d.%d.%d on display \"%s\".</p>\n"), gtk_major_version, gtk_minor_version, gtk_micro_version, gdk_get_display()); /* gdk_display_name */
3590
3591 #elif (CONFIG_TG == TG_NCURSES) && defined(NCURSES_VERSION)
3592
3593 /* work-around for ncurses silliness... */
3594 #if (defined(NCURSES_VERSION_MAJOR)) && (NCURSES_VERSION_MAJOR >= 5)
3595 #define my_curses_version curses_version()
3596 #else
3597 #define my_curses_version NCURSES_VERSION
3598 #endif
3599
3600 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>Currently using <a href=\"http://directory.fsf.org/ncurses.html\">ncurses</a> library version \"%s\".</p>\n"), my_curses_version);
3601
3602 #endif
3603
3604 if (spfbuf != NULL)
3605 { resource_collect_str(resource, spfbuf);
3606 my_spf_cleanup(strbuf, spfbuf);
3607 }
3608
3609 /* TLS/libgcrypt library text */
3610
3611 #if OPTION_TLS == TLS_GNUTLS
3612 { const char* v = gnutls_check_version(NULL);
3613 if (v == NULL) v = _(strUnknown);
3614 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>Currently using <a href=\"http://www.gnutls.org/\">GnuTLS</a> library version %s.%s</p>\n"), v, tls_version_warning(v));
3615 resource_collect_str(resource, spfbuf);
3616 my_spf_cleanup(strbuf, spfbuf);
3617 }
3618 #elif OPTION_TLS == TLS_OPENSSL
3619 { const char *v = SSLeay_version(SSLEAY_VERSION), *v2 = null2empty(v),
3620 *v3 = ((strneqcase(v2, "openssl ", 8)) ? v2 + 8 : v2);
3621 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>Currently using <a href=\"http://www.openssl.org/\">OpenSSL</a> library version \"%s\".%s</p>\n"), v2, tls_version_warning(v3));
3622 resource_collect_str(resource, spfbuf);
3623 my_spf_cleanup(strbuf, spfbuf);
3624 }
3625 #elif OPTION_TLS == TLS_MATRIX
3626 { /* IMPLEMENTME: version number?! */
3627 resource_collect_str(resource, _("<p>Using <a href=\"http://www.matrixssl.org/\">MatrixSSL</a>.</p>\n"));
3628 }
3629 #elif OPTION_TLS == TLS_BUILTIN
3630 { const char *v = gcry_check_version(NULL), *v2 = null2empty(v);
3631 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>Currently using <a href=\"http://www.gnupg.org/\">libgcrypt</a> library version %s.%s</p>\n"), v2, tls_version_warning(v2));
3632 resource_collect_str(resource, spfbuf);
3633 my_spf_cleanup(strbuf, spfbuf);
3634 }
3635 #endif
3636
3637 /* other texts */
3638
3639 #if OFWAX
3640 resource_collect_str(resource, "<p>This is a modified version for use with <a href=\"http://www.modest-proposals.com/Hacklin.htm\">Xwoaf</a>.</p>\n");
3641 #endif
3642 if (initial_messages != NULL)
3643 { resource_collect_str(resource, _("<p>Start messages:"));
3644 resource_collect_str(resource, initial_messages);
3645 resource_collect_str(resource, "</p>\n");
3646 }
3647 resource_collect_str(resource, strEndBodyHtml);
3648 }
3649 #if CONFIG_ABOUT & 1
3650 else if (!strcmp(path, "activity")) /* connection activity list */
3651 { tResource* resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
3652 rckHtml, rsComplete);
3653 const tConnection* conn = connlist_head;
3654 tBoolean is_first = truE;
3655 char schemebuf[MAXSCHEMESTRSIZE];
3656 resource->flags |= rfActivityUnwatchable;
3657 resource_collect_title(resource, _("Activity"));
3658 while (conn != NULL)
3659 { const tHostPortProtInfo* hppi = conn->hppi;
3660 const tResource* res;
3661 const char* info = NULL;
3662 char* spfbuf;
3663 tBoolean must_cleanup_info;
3664 /* IMPLEMENTME: reasons for not actually listing a connection? */
3665 /* list this connection */
3666 is_first = must_cleanup_info = falsE;
3667 #if NEED_DISSOLVING
3668 if (conn->flags & cnfDissolving)
3669 { info = unconstify_or_("disconnecting");
3670 /* technically wrong notion, but users will understand it better than
3671 "dissolving" or "shutting down" :-) */
3672 goto finish;
3673 }
3674 #endif
3675 res = conn2resource(conn);
3676 if (res != NULL)
3677 { size_t bytecount = res->bytecount;
3678 char bytecountbuf[200];
3679 if (bytecount < 2) *bytecountbuf = '\0';
3680 else
3681 { sprint_safe(bytecountbuf, "%s%d %s", strSpacedDash, bytecount,
3682 _(strBytes));
3683 }
3684 /* IMPLEMENTME: show "saving to FILENAME" for res->save_as chain! */
3685 my_spf(strbuf2, STRBUF_SIZE, &spfbuf, strPercsPercs,
3686 res->uri_data->uri, bytecountbuf);
3687 info = my_spf_use(spfbuf); must_cleanup_info = truE;
3688 }
3689
3690 if (hppi != NULL)
3691 { /* IMPLEMENTME: show hostinfo! */
3692 }
3693
3694 #if OPTION_TLS
3695 { tTlsSession session = conn->tls_session;
3696 if (session != NULL) { /* IMPLEMENTME: show TLS info (cipher etc.) */ }
3697 }
3698 #endif
3699
3700 finish:
3701 rp2scheme(conn->protocol, schemebuf);
3702 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("<p>fd #%d, scheme %s%s%s</p>"),
3703 conn->fd, schemebuf, ( (info != NULL) ? strSpacedDash : strEmpty ),
3704 null2empty(info));
3705 resource_collect_str(resource, spfbuf);
3706 if (must_cleanup_info) my_spf_cleanup(strbuf2, info);
3707 my_spf_cleanup(strbuf, spfbuf);
3708 conn = conn->next;
3709 }
3710 if (is_first) resource_collect_str(resource, _("<p>none</p>"));
3711 resource_collect_str(resource, strNewlineEndBodyHtml);
3712 }
3713 #endif /* #if CONFIG_ABOUT & 1 */
3714 #if CONFIG_ABOUT & 2
3715 else if (!strcmp(path, "hostinfo")) /* host information cache excerpt */
3716 { /* This existed mostly for debugging purposes, but... */
3717 static const char strNewlineEndp[] = "\n</p>";
3718 tResource* resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
3719 rckHtml, rsComplete);
3720 tBoolean did_something1 = falsE, did_something2 = falsE;
3721 char* spfbuf;
3722 tHashIndex i;
3723 tSockaddrIndex si;
3724 char schemebuf[MAXSCHEMESTRSIZE];
3725
3726 resource_collect_title(resource,
3727 _("Contents of the host information cache"));
3728
3729 for (i = 0; i < HASHTABLESIZE_CHI; i++)
3730 { const tCachedHostInformation* hostinfo = chi_head[i];
3731 if (hostinfo == NULL) continue;
3732 did_something1 = truE;
3733 while (hostinfo != NULL)
3734 { const tHostPortProtInfo* hppi = hostinfo->hppi;
3735 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("\n<p>name: \"%s\"; flags: %d"),
3736 hostinfo->hostname, hostinfo->flags);
3737 resource_collect_str(resource, spfbuf);
3738 my_spf_cleanup(strbuf, spfbuf);
3739 #if USE_S2U
3740 for (si = 0; si < hostinfo->num_sockaddrs; si++)
3741 { sockaddr2uistr(hostinfo->sockaddrs[si], strbuf, STRBUF_SIZE);
3742 if (*strbuf != '\0')
3743 { resource_collect_str(resource, _("; IP address: "));
3744 resource_collect_str(resource, strbuf);
3745 }
3746 }
3747 #endif
3748 while (hppi != NULL)
3749 { tResourceProtocol protocol = hppi->protocol;
3750 rp2scheme(protocol, schemebuf);
3751 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
3752 _("\n<br>port %d: scheme %s; flags: %d"),
3753 ntohs(hppi->portnumber), schemebuf, hppi->flags);
3754 resource_collect_str(resource, spfbuf);
3755 my_spf_cleanup(strbuf, spfbuf);
3756 hppi = hppi->next;
3757 }
3758 #if OPTION_COOKIES
3759 if (hostinfo->cookies != NULL)
3760 { const char* buf = cookie_reviewlist(hostinfo);
3761 if (buf != NULL)
3762 { resource_collect_str(resource, buf); memory_deallocate(buf); }
3763 }
3764 #endif
3765 resource_collect_str(resource, strNewlineEndp);
3766 hostinfo = hostinfo->next;
3767 }
3768 }
3769
3770 #if USE_S2U
3771 for (i = 0; i < HASHTABLESIZE_SOCKADDRS; i++)
3772 { tSockaddrEntry* entry = sockaddr_entry_head[i];
3773 if (entry == NULL) continue;
3774 if ( (did_something1) && (!did_something2) )
3775 { resource_collect_str(resource,
3776 "\n<p><hr width=\"60%\" align=\"center\"><p>");
3777 }
3778 did_something2 = truE;
3779 while (entry != NULL)
3780 { sockaddr2uistr(entry, strbuf, STRBUF_SIZE);
3781 if (*strbuf != '\0')
3782 { tSockaddrPortProtInfo* sppi;
3783 resource_collect_str(resource, _("\n<p>IP address: "));
3784 resource_collect_str(resource, strbuf);
3785 sppi = entry->sppi;
3786 while (sppi != NULL)
3787 { const char *swid = sppi->software_id, *swid2 = ( (swid != NULL) ?
3788 htmlify(swid) : NULL );
3789 const tBoolean do_swid = cond2boolean(swid2 != NULL);
3790 tSockaddrPortProtInfoFlags sppif = sppi->sppif;
3791 rp2scheme(sppi->protocol, schemebuf);
3792 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
3793 _("\n<br>port %d: scheme %s; flags: %d; rating: %d%s%s%s%s"),
3794 ntohs(sppi->portnumber), schemebuf, sppif, sppi->rating,
3795 ( (sppif & sppifCannotHttp11) ? _("; not HTTP/1.1") : strEmpty ),
3796 (do_swid ? _("; server: \"") : strEmpty), (do_swid ? swid2 :
3797 strEmpty), (do_swid ? strDoubleQuote : strEmpty));
3798 if (do_swid) htmlify_cleanup(swid, swid2);
3799 resource_collect_str(resource, spfbuf);
3800 my_spf_cleanup(strbuf, spfbuf);
3801 sppi = sppi->next;
3802 }
3803 resource_collect_str(resource, strNewlineEndp);
3804 }
3805 entry = entry->next;
3806 }
3807 }
3808 #endif
3809 if ( (!did_something1) && (!did_something2) )
3810 resource_collect_str(resource, _("<p>No information known yet.</p>\n"));
3811 resource_collect_str(resource, strNewlineEndBodyHtml);
3812 }
3813 #endif /* #if CONFIG_ABOUT & 2 */
3814 #if CONFIG_ABOUT & 4
3815 else if (!strcmp(path, "ctconfig")) /* compile-time configuration */
3816 { tResource* resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
3817 rckHtml, rsComplete);
3818 resource_collect_title(resource,
3819 _("retawq Compile-Time Configuration Options"));
3820 prepare_about_ctconfig(); resource_collect_str(resource, strbuf);
3821 resource_collect_str(resource, strNewlineEndBodyHtml);
3822 }
3823 #endif /* #if CONFIG_ABOUT & 4 */
3824 #if CONFIG_ABOUT & 8
3825 else if (!strcmp(path, strHelp)) /* go to documentation */
3826 { tResource* resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
3827 rckHtml, rsComplete);
3828 resource_collect_title(resource, _("Help"));
3829 #define HELP1 PATH_INSTALL_DOC "/index.html"
3830 #define HELP2 "http://retawq.sourceforge.net/docu/"
3831 resource_collect_str(resource, "<p><a href=\"local:" HELP1 "\">" HELP1
3832 "</a>\n<br><a href=\"" HELP2 "\">" HELP2 "</a></p>");
3833 resource_collect_str(resource, strNewlineEndBodyHtml);
3834 #undef HELP1
3835 #undef HELP2
3836 }
3837 #endif /* #if CONFIG_ABOUT & 8 */
3838 #if (CONFIG_RTCONFIG) && (OPTION_BIRTCFG)
3839 else if (!strcmp(path, "birtcfg")) /* show built-in run-time configuration */
3840 { /* This one doesn't have a CONFIG_ABOUT bit. If a birtcfg exists, users
3841 shall be able to see what it looks like. A (bi)rtcfg can contain
3842 settings which cause security/privacy problems, and users surely want to
3843 know. */
3844 tResource* resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
3845 rckText, rsComplete);
3846 resource_collect_str(resource, strBirtcfg);
3847 }
3848 #endif /* #if (CONFIG_RTCONFIG) && (OPTION_BIRTCFG) */
3849 else
3850 { resource_request_set_error(request, reUri);
3851 }
3852 }
3853
3854
3855 /** "local:" resources */
3856
3857 typedef struct
3858 {
3859 #if CONFIG_LOCALDIR > 1
3860 struct stat statbuf;
3861 #endif
3862 const char *key, *value;
3863 #if CONFIG_LOCALDIR > 1
3864 tBoolean statbuf_is_good;
3865 #endif
3866 } tDirSorterElement;
3867
3868 #if CONFIG_LOCALDIR > 1
3869
3870 static const char strQuerySort[] = "?sort=";
3871 static const char* current_localdirsort;
3872
local_query2sorting(const char * query)3873 static one_caller char* local_query2sorting(const char* query)
3874 { char* retval = NULL;
3875 enum { buflen = 20 };
3876 static char buf[buflen];
3877 if (query != NULL)
3878 { size_t len = strlen(strQuerySort) - 1, slen;
3879 if (my_strstr(query, strQuerySort + 1))
3880 { const char *sorting = query + len, *end, *end_max;
3881 if (*sorting == '\0') goto out;
3882 end = my_strchr(sorting, '&');
3883 if (end == NULL) end = sorting + strlen(sorting);
3884 end_max = sorting + buflen - 5;
3885 if (end > end_max) end = end_max;
3886 slen = end - sorting;
3887 my_memcpy(buf, sorting, slen);
3888 buf[slen] = '\0';
3889 check_localdirsort(buf);
3890 if (*buf != '\0') retval = buf;
3891 }
3892 }
3893 out:
3894 return(retval);
3895 }
3896
mode2num(mode_t mode)3897 static int mode2num(mode_t mode)
3898 /* converts a file type (file/directory/...) to a number for sorting */
3899 { int retval;
3900 if (S_ISDIR(mode)) retval = 1;
3901 else if (S_ISREG(mode)) retval = 2;
3902 else retval = 3;
3903 return(retval);
3904 }
3905
3906 static one_caller /*@observer@*/ /*@null@*/ const char*
path2format(const char * path)3907 path2format(const char* path)
3908 { const char* retval = NULL;
3909 const tConfigLocaldirformat* ldf = config.ldf;
3910 while (ldf != NULL)
3911 { const char* pattern = ldf->path_pattern;
3912 size_t len;
3913 if ( (pattern == NULL) || (*pattern == '\0') ) goto nxt;
3914 len = strlen(pattern);
3915 if ( ( (pattern[len - 1] == '*') && (!strncmp(pattern, path, len - 1)) ) ||
3916 (!strcmp(pattern, path)) )
3917 { retval = ldf->format;
3918 if ( (retval != NULL) && (*retval == '\0') ) retval = NULL;
3919 break;
3920 }
3921 nxt:
3922 ldf = ldf->next;
3923 }
3924 return(retval);
3925 }
3926
3927 #endif /* #if CONFIG_LOCALDIR > 1 */
3928
directory_sorter(const void * _a,const void * _b)3929 static int directory_sorter(const void* _a, const void* _b)
3930 { const tDirSorterElement *a = (const tDirSorterElement*) _a,
3931 *b = (const tDirSorterElement*) _b;
3932 #if CONFIG_LOCALDIR > 1
3933 const struct stat *as, *bs;
3934 const char* sorting;
3935 char sortcrit;
3936
3937 if (*current_localdirsort == '\0') goto out; /* the most likely case */
3938 if ( (!(a->statbuf_is_good)) || (!(b->statbuf_is_good)) ) goto out;
3939 as = &(a->statbuf); bs = &(b->statbuf);
3940 sorting = current_localdirsort;
3941 while ( (sortcrit = *sorting++) != '\0' )
3942 { int val;
3943 tBoolean reverse;
3944 if (my_islower(sortcrit)) reverse = falsE;
3945 else { sortcrit = my_tolower(sortcrit); reverse = truE; }
3946 switch (sortcrit)
3947 { case 'g': val = my_numcmp(as->st_gid, bs->st_gid); break;
3948 case 'i': val = my_strcasecmp(a->key, b->key); break;
3949 case 'm': val = my_numcmp(as->st_mtime, bs->st_mtime); break;
3950 case 'n': val = strcmp(a->key, b->key); break;
3951 case 's': val = my_numcmp(as->st_size, bs->st_size); break;
3952 case 't': val = mode2num(as->st_mode) - mode2num(bs->st_mode); break;
3953 case 'u': val = my_numcmp(as->st_uid, bs->st_uid); break;
3954 default: val = 0; break; /* "should not happen" */
3955 }
3956 if (val != 0) /* found an "applicable" sorting criterium */
3957 { if (reverse) val = -val;
3958 return(val);
3959 }
3960 }
3961 return(0); /* no criterium applied */
3962 out: {}
3963 #endif
3964 return(strcmp(a->key, b->key)); /* just sort by name */
3965 }
3966
3967 #if CONFIG_LOCALDIR > 0
inodemode2str(mode_t mode)3968 static my_inline char inodemode2str(mode_t mode)
3969 { if (S_ISREG(mode)) return(config.char_file);
3970 else if (S_ISDIR(mode)) return(config.char_dir);
3971 else return('?');
3972 }
3973 static char dirperms_buf[10];
3974 #define __dirperms(pr, pw, px) \
3975 *p++ = ( (mode & pr) ? ('r') : ('-') ); \
3976 *p++ = ( (mode & pw) ? ('w') : ('-') ); \
3977 *p++ = ( (mode & px) ? ('x') : ('-') );
calc_dirperms(mode_t mode)3978 static one_caller void calc_dirperms(mode_t mode)
3979 { char* p = dirperms_buf;
3980 __dirperms(S_IRUSR, S_IWUSR, S_IXUSR)
3981 __dirperms(S_IRGRP, S_IWGRP, S_IXGRP)
3982 __dirperms(S_IROTH, S_IWOTH, S_IXOTH)
3983 *p = '\0';
3984 }
3985 #endif /* #if CONFIG_LOCALDIR > 0 */
3986
3987 static /*@observer@*/ const char strParentDirectory[] = N_("Parent directory");
3988
fetch_local_directory(tResourceRequest * request)3989 static one_caller tBoolean fetch_local_directory(tResourceRequest* request)
3990 /* returns whether it worked */
3991 { const char *path = request->uri_data->path, *htmlpath, *spfbuf2;
3992 #if CONFIG_LOCALDIR > 1
3993 const char* format;
3994 #endif
3995 char *spfbuf, *temp;
3996 const struct dirent* entry;
3997 unsigned int element_count, element_maxcount, i;
3998 /*@relnull@*/ tDirSorterElement* sorter_base;
3999 tResource* resource;
4000 DIR* dir = opendir(path);
4001 if (dir == NULL) return(falsE);
4002
4003 /* Read the directory contents */
4004
4005 #if CONFIG_LOCALDIR > 1
4006 format = path2format(path);
4007 #endif
4008 sorter_base = NULL;
4009 element_count = element_maxcount = 0;
4010 while ( (entry = readdir(dir)) != NULL )
4011 { const char *name = entry->d_name, *key, *htmlkey;
4012 if (name == NULL) continue; /* "should not happen", library bug */
4013 if (*name == '.')
4014 { char c = name[1];
4015 if ( (c == '\0') || ( (c == '.') && (name[2] == '\0') ) )
4016 { continue; } /* we aren't interested in "." and ".." */
4017 }
4018
4019 if (element_maxcount <= element_count)
4020 { element_maxcount += 20;
4021 sorter_base = memory_reallocate(sorter_base, element_maxcount *
4022 sizeof(tDirSorterElement), mapOther);
4023 }
4024 key = my_strdup(name); htmlkey = htmlify(key);
4025 sorter_base[element_count].key = key;
4026
4027 #if CONFIG_LOCALDIR > 0
4028 /* The user wants more information about the contents of the directory. */
4029 { int err;
4030 struct stat statbuf;
4031 my_spf(strbuf, STRBUF_SIZE, &spfbuf, strPercsPercs, path, key);
4032 err = my_stat(spfbuf, &statbuf);
4033 my_spf_cleanup(strbuf, spfbuf);
4034 if (err != 0)
4035 { /* very unlikely... */
4036 #if CONFIG_LOCALDIR > 1
4037 sorter_base[element_count].statbuf_is_good = falsE;
4038 #endif
4039 goto just_the_name;
4040 }
4041 else
4042 { mode_t mode = statbuf.st_mode;
4043 const char* slash = (S_ISDIR(mode) ? strSlash : strEmpty);
4044 if (!S_ISREG(mode)) spfbuf2 = strEmpty;
4045 else
4046 { const size_t bytes = (size_t) statbuf.st_size;
4047 my_spf(strbuf2, STRBUF_SIZE, &temp, strBracedNumstr,
4048 localized_size(bytes), bytebytes(bytes));
4049 spfbuf2 = my_spf_use(temp);
4050 }
4051 calc_dirperms(mode);
4052 my_spf(NULL, 0, &spfbuf,
4053 "<br>%c %s <a href=\"local:%s%s%s\">%s%s</a>%s\n",
4054 inodemode2str(mode), dirperms_buf, path, key, slash, htmlkey,
4055 slash, spfbuf2);
4056 if (spfbuf2 != strEmpty) my_spf_cleanup(strbuf2, spfbuf2);
4057 #if CONFIG_LOCALDIR > 1
4058 sorter_base[element_count].statbuf = statbuf;
4059 sorter_base[element_count].statbuf_is_good = truE;
4060 #endif
4061 }
4062 }
4063 if (falsE) /* ugly (-: */
4064 #endif /* #if CONFIG_LOCALDIR > 0 */
4065 { just_the_name:
4066 my_spf(NULL, 0, &spfbuf, "<br><a href=\"local:%s%s\">%s</a>\n",
4067 path, key, htmlkey);
4068 }
4069 sorter_base[element_count].value = my_spf_use(spfbuf);
4070
4071 element_count++;
4072 htmlify_cleanup(key, htmlkey);
4073 }
4074 (void) closedir(dir);
4075
4076 /* Put the directory contents into an HTML page (sorted) */
4077
4078 resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH, rckHtml,
4079 rsComplete);
4080
4081 htmlpath = htmlify(path);
4082 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("Contents of local directory \"%s\""),
4083 htmlpath);
4084 htmlify_cleanup(path, htmlpath);
4085 resource_collect_title(resource, spfbuf);
4086 my_spf_cleanup(strbuf, spfbuf);
4087
4088 if (calc_parentpath(path, strbuf2, STRBUF_SIZE))
4089 { my_spf(strbuf, STRBUF_SIZE, &spfbuf,
4090 "<p><a href=\"local:%s\">%s</a></p>\n", strbuf2, _(strParentDirectory));
4091 resource_collect_str(resource, spfbuf);
4092 my_spf_cleanup(strbuf, spfbuf);
4093 }
4094
4095 if (element_count <= 0)
4096 { resource_collect_str(resource, _("This directory is empty."));
4097 }
4098 else
4099 {
4100 #if CONFIG_LOCALDIR > 1
4101 const char* clds = local_query2sorting(request->uri_data->query);
4102 if (clds == NULL) /* no special sorting for this URI given */
4103 { tConfigLocaldirsort* lds = config.lds;
4104 while (lds != NULL)
4105 { const char* pattern = lds->path_pattern;
4106 size_t len;
4107 if ( (pattern == NULL) || (*pattern == '\0') ) goto nxt;
4108 len = strlen(pattern);
4109 if ( ( (pattern[len - 1] == '*') && (!strncmp(pattern,path,len-1)) ) ||
4110 (!strcmp(pattern, path)) )
4111 { clds = lds->sorting; break; }
4112 nxt:
4113 lds = lds->next;
4114 }
4115 }
4116 if (clds == NULL) clds = strEmpty; /* sort by name (default) */
4117 else if (*clds == '_') clds = NULL; /* disable all sorting */
4118 if (clds != NULL)
4119 { current_localdirsort = clds;
4120 #else
4121 {
4122 #endif
4123 qsort(sorter_base, element_count, sizeof(tDirSorterElement),
4124 directory_sorter);
4125 }
4126 for (i = 0; i < element_count; i++)
4127 { resource_collect_str(resource, sorter_base[i].value);
4128 memory_deallocate(sorter_base[i].key);
4129 memory_deallocate(sorter_base[i].value);
4130 }
4131 }
4132
4133 resource_collect_str(resource, strEndBodyHtml);
4134 __dealloc(sorter_base);
4135 return(truE);
4136 }
4137
4138 static one_caller void build_local_uri(tResourceRequest* request)
4139 { tUriData* uri_data = request->uri_data;
4140 const char* path = uri_data->path;
4141 char* spfbuf;
4142 #if CONFIG_LOCALDIR > 1
4143 const char* query = uri_data->query;
4144 if (query != NULL)
4145 { my_spf(NULL, 0, &spfbuf, "local:%s%s%s", path, strQuerySort, query);
4146 }
4147 else
4148 #endif
4149 { my_spf(NULL, 0, &spfbuf, "local:%s", path);
4150 }
4151 memory_deallocate(uri_data->uri); uri_data->uri = my_spf_use(spfbuf);
4152 }
4153
4154 static one_caller void fetch_local(tResourceRequest* request)
4155 { tUriData* uri_data = request->uri_data;
4156 char* path = uri_data->path;
4157 struct stat statbuf;
4158 mode_t mode;
4159
4160 /* Check whether the thing really exists and whether it's a regular file or
4161 a directory */
4162
4163 if (my_stat(path, &statbuf) != 0)
4164 { /* The thing doesn't exist (any longer) or is otherwise inaccessible, but
4165 maybe we have its old contents in cache. */
4166 try_cache:
4167 if (resource_lookup(request) != NULL) return; /* found in cache, done */
4168 bad: resource_request_set_error(request, reFile); return;
4169 }
4170
4171 mode = statbuf.st_mode;
4172 if (S_ISDIR(mode)) /* it's a directory */
4173 { const size_t len = strlen(path);
4174 if ( ( (len > 0) && (path[len - 1] != chDirsep) ) || (len == 0) )
4175 { uri_data->path = path = memory_reallocate(path, len + 1 + 1, mapString);
4176 strcat(path, strSlash); build_local_uri(request);
4177 }
4178 if (resource_lookup(request) != NULL) return; /* found in cache, done */
4179 if (!fetch_local_directory(request)) goto bad;
4180 }
4181 else if (S_ISREG(mode)) /* it's a regular file */
4182 { tResource* resource;
4183 tContentblock* content;
4184 void* buf;
4185 size_t size;
4186 tBoolean need_unmap SHUT_UP_COMPILER(falsE);
4187 #if CONFIG_EXTRA & EXTRA_DOWNLOAD
4188 if (request->flags & rrfDownload) /* can't "download" local files */
4189 { resource_request_set_error(request, reUri); return; }
4190 #endif
4191 if (resource_lookup(request) != NULL) return; /* found in cache, done */
4192 switch (my_mmap_file_readonly(path, &buf, &size))
4193 { case 0: goto bad; /*@notreached@*/ break;
4194 case 1:
4195 buf = memory_allocate(1,mapOther); size = 0; need_unmap = falsE; break;
4196 case 2: need_unmap = truE; break;
4197 }
4198 content = contentblock_create(size, 0);
4199 content->used = size; content->data = buf;
4200 resource = resource_create(request, content, size, filesuffix2rck(path),
4201 rsComplete);
4202 resource->bytecount = size;
4203 if (need_unmap) resource->cantent->caf |= cafNeedUnmap;
4204 }
4205 else
4206 { /* The thing isn't a regular file or directory now, but maybe there was
4207 one at this position earlier, which we might have in cache. */
4208 goto try_cache;
4209 }
4210 }
4211
4212
4213 /** Local CGI resources */
4214
4215 #if OPTION_LOCAL_CGI
4216
4217 #if HAVE_SETENV
4218 #define __my_setenv(var, val) (void) setenv(var, val, 1)
4219 #define my_setenv(var, val) __my_setenv(var, val)
4220 #elif HAVE_PUTENV
4221 #define __my_setenv(var, val) (void) putenv(var "=" val) /* fast and */
4222 static void my_setenv(const char* var, const char* val) /* slow variant */
4223 { char* spfbuf;
4224 my_spf(strbuf, STRBUF_SIZE, &spfbuf, "%s=%s", var, val);
4225 putenv(spfbuf);
4226 my_spf_cleanup(strbuf, spfbuf);
4227 }
4228 #else
4229 #error "Cannot use local CGI - your libc has neither setenv() nor putenv(). Disable the compile-time configuration option OPTION_LOCAL_CGI!"
4230 /* IMPLEMENTME? This is only executed in child processes after fork(), so there
4231 are no race conditions possible if we change environ manually... Or simply
4232 setup a minimal, CGI-specific environment and use execle(). */
4233 #endif /* #if HAVE_SETENV/HAVE_PUTENV */
4234
4235 static one_caller uid_t my_geteuid(void)
4236 { static tBoolean done = falsE;
4237 static uid_t euid;
4238 if (!done) { euid = geteuid(); done = truE; } /* (calculate it only once) */
4239 return(euid);
4240 }
4241
4242 static one_caller gid_t my_getegid(void)
4243 { static tBoolean done = falsE;
4244 static gid_t egid;
4245 if (!done) { egid = getegid(); done = truE; } /* (calculate it only once) */
4246 return(egid);
4247 }
4248
4249 /* prototypes */
4250 static void http_read(tResource*);
4251 static void http_rpsd_prepare(tResource*);
4252
4253 static void local_cgi_callback(tConnection* conn, tConnCbEventKind ccek)
4254 { tResource* resource = (tResource*) (conn->data);
4255 if (conn->x == 0) /* reading the script's reply */
4256 { switch (ccek)
4257 { case ccekRead: http_read(resource); break;
4258 default: is_buggy: conn_bug(conn, ccek); break;
4259 }
4260 }
4261 else /* writing the post data */
4262 { switch (ccek)
4263 { case ccekWrite:
4264 if (conn_write_writedata(conn) != 1)
4265 { /* error or done - just stop writing, don't stop reading */
4266 conn_remove(&(resource->dconn));
4267 }
4268 break;
4269 default: goto is_buggy; /*@notreached@*/ break;
4270 }
4271 }
4272 }
4273
4274 static one_caller tBoolean start_local_cgi(tResourceRequest* request)
4275 /* starts a local CGI script if allowed/possible; returns want-it-back info */
4276 { const tConfigLocalCgi* clc = config.local_cgi;
4277 tResourceError error;
4278 tBoolean is_post;
4279 const char *path, *post SHUT_UP_COMPILER(NULL);
4280 size_t pathlen, postlen SHUT_UP_COMPILER(0);
4281 struct stat statbuf;
4282 mode_t mode;
4283 int fd_pair[2], fd_post[2];
4284 pid_t pid;
4285
4286 /* First of all, check whether the configuration allows it */
4287
4288 if (clc == NULL) goto forbidden; /* (optimizing for the most likely case) */
4289 path = request->uri_data->path; pathlen = strlen(path);
4290 while (clc != NULL)
4291 { const char* pattern = clc->path_pattern;
4292 size_t len;
4293 if ( (!strcmp(path, pattern)) ||
4294 ( ( (len = strlen(pattern)) > 0 ) && (pattern[len - 1] == '*') &&
4295 (pathlen >= len) && (!strncmp(path, pattern, len - 1)) ) )
4296 { if (clc->flags & clcfAllowed) goto allowed;
4297 break;
4298 }
4299 clc = clc->next;
4300 }
4301 forbidden:
4302 error = reConfigForbids;
4303 failed:
4304 resource_request_set_error(request, error);
4305 return(falsE);
4306
4307 allowed:
4308
4309 /* Check cache */
4310
4311 if (resource_lookup(request) != NULL) return(falsE); /*found in cache, done*/
4312
4313 /* Check whether the thing actually exists, is a regular file, executable */
4314
4315 if (my_stat(path, &statbuf) != 0) { bad_file: error = reFile; goto failed; }
4316 mode = statbuf.st_mode;
4317 if (!S_ISREG(mode)) goto bad_file;
4318 /* simple pre-check without all the get...id() calls: */
4319 if (!(mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
4320 { cant_exec: error = reExec; goto failed; }
4321 /* more thorough check: */
4322 if (! (( (mode & S_IXOTH) ||
4323 ( (mode & S_IXUSR) && (statbuf.st_uid == my_geteuid()) ) ||
4324 ( (mode & S_IXGRP) && (statbuf.st_gid == my_getegid()) ) )) )
4325 goto cant_exec;
4326
4327 /* Execute the script */
4328
4329 if (my_pipe(fd_pair) != 0) { pipe_failed: error = rePipe; goto failed; }
4330 if (!fd_is_observable(fd_pair[0]))
4331 { tmofd: my_close_pipe(fd_pair[0]); my_close_pipe(fd_pair[1]);
4332 error = reTmofd; goto failed;
4333 }
4334 is_post = cond2boolean(request->flags & rrfPost);
4335 if (is_post)
4336 { if (my_pipe(fd_post) != 0)
4337 { my_close_pipe(fd_pair[0]); my_close_pipe(fd_pair[1]); goto pipe_failed; }
4338 if (!fd_is_observable(fd_post[1]))
4339 { my_close_pipe(fd_post[0]); my_close_pipe(fd_post[1]); goto tmofd; }
4340 post = null2empty(request->uri_data->post);
4341 postlen = strlen(post);
4342 }
4343
4344 pid = fork();
4345 if (pid < 0) /* failed */
4346 { error = reFork;
4347 my_close_pipe(fd_pair[0]); my_close_pipe(fd_pair[1]);
4348 if (is_post) { my_close_pipe(fd_post[0]); my_close_pipe(fd_post[1]); }
4349 goto failed;
4350 }
4351 else if (pid == 0) /* child process */
4352 { char *spfbuf, *spfbuf2;
4353 const char *query, *temp;
4354 reset_signals();
4355 query = request->uri_data->query;
4356 my_close_unregistried(0); my_close_pipe(fd_pair[0]);
4357 if (!may_use_fd2()) my_close_unregistried(2);
4358 if (fd_pair[1] != 1)
4359 { if (!my_dup2(fd_pair[1], 1))
4360 { dup2_failed:
4361 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("retawq: local CGI error: can't setup file descriptors (error #%d, %s)\n"), errno, my_strerror(errno));
4362 if (may_use_fd2()) my_write_str(fd_stderr, spfbuf);
4363 debugmsg(spfbuf);
4364 goto exit_child;
4365 }
4366 my_close_pipe(fd_pair[1]);
4367 }
4368 if (is_post)
4369 { my_close_pipe(fd_post[1]);
4370 if (fd_post[0] != 0)
4371 { if (!my_dup2(fd_post[0], 0)) goto dup2_failed;
4372 my_close_pipe(fd_post[0]);
4373 }
4374 sprint_safe(strbuf, strPercd, postlen);
4375 my_setenv("CONTENT_LENGTH", strbuf);
4376 my_setenv("CONTENT_TYPE", strAxwfu);
4377 }
4378 __my_setenv("GATEWAY_INTERFACE", "CGI/1.1");
4379 my_setenv("HTTP_USER_AGENT", config.user_agent);
4380 if ( (query != NULL) && (*query != '\0') )
4381 my_setenv("QUERY_STRING", query);
4382 my_setenv("REMOTE_HOST", strLocalhost);
4383 __my_setenv("REMOTE_ADDR", "127.0.0.1");
4384 my_setenv("REQUEST_METHOD", is_post ? strPost : strGet);
4385 my_setenv("SCRIPT_NAME", path);
4386 my_setenv("SERVER_NAME", strLocalhost);
4387 __my_setenv("SERVER_PORT", "80"); /* (just a dummy value) */
4388 __my_setenv("SERVER_PROTOCOL", "HTTP/1.0"); /* CHECKME! */
4389 my_setenv("SERVER_SOFTWARE", strSoftwareId);
4390
4391 temp = my_strrchr(path, chDirsep);
4392 if (temp != NULL) /* "should" be true */
4393 { size_t len = temp - path + 1, size = len + 1;
4394 char* ptr = ( (size <= STRBUF_SIZE) ? strbuf :
4395 __memory_allocate(size, mapString) );
4396 my_memcpy(ptr, path, len); ptr[len] = '\0';
4397 (void) chdir(ptr);
4398 if (ptr != strbuf) memory_deallocate(ptr);
4399 }
4400 (void) execl(path, path, NULL);
4401
4402 /* If we get here, execl() failed, so we send a little error text to the
4403 resource handler and exit the child process: */
4404 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
4405 _("Local CGI error: can't execute \"%s\" (error #%d, %s)%s"), path,errno,
4406 my_strerror(errno), ( (errno == ENOENT) ? _("\n\n(Note: an error like \"file not found\" might mean \"script interpreter not found\" here.)") : strEmpty ));
4407 my_spf(strbuf2, STRBUF_SIZE, &spfbuf2,
4408 "Status: 500\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s",
4409 strlen(spfbuf), spfbuf);
4410 my_write_str_unregistried(1, spfbuf2);
4411 exit_child:
4412 _exit(1);
4413 }
4414 else /* parent process */
4415 { tResource* resource;
4416 tConnection* conn;
4417 make_fd_cloexec(fd_pair[0]); my_close_pipe(fd_pair[1]);
4418 resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
4419 rckUnknown, rsReading);
4420 http_rpsd_prepare(resource);
4421 conn = resource->cconn = conn_create(fd_pair[0], local_cgi_callback,
4422 resource, truE, NULL, resource->protocol);
4423 conn->flags |= cnfConnected; conn_set_read(conn);
4424 if (is_post)
4425 { my_close_pipe(fd_post[0]);
4426 if (postlen <= 0) { my_close_pipe(fd_post[1]); } /* nothing to post */
4427 else
4428 { make_fd_cloexec(fd_post[1]);
4429 (void) make_fd_nonblocking(fd_post[1]);
4430 /* (just trying; we won't let it all fail if only this fails...) */
4431 conn = resource->dconn = conn_create(fd_post[1], local_cgi_callback,
4432 resource, truE, NULL, resource->protocol);
4433 conn->flags |= cnfConnected; conn->x = 1; conn_set_write(conn);
4434 conn_set_writestr(conn, my_strdup(post));
4435 }
4436 resource->flags |= rfPost;
4437 }
4438 }
4439 return(truE);
4440 }
4441
4442 #endif /* #if OPTION_LOCAL_CGI */
4443
4444
4445 /** Execution of external programs: shell */
4446
4447 #if OPTION_EXECEXT & EXECEXT_SHELL
4448
4449 static one_caller void execext_shell_read(tConnection* conn)
4450 { tResource* resource = (tResource*) (conn->data); /* always != NULL here */
4451 char* dest;
4452 size_t size;
4453 int err;
4454 resource_provide_room(resource, &dest, &size, resource->bytecount, 2);
4455 err = my_read_pipe(conn->fd, dest, size);
4456 if (err <= 0) /* error or EOF */
4457 { conn_remove(&(resource->cconn));
4458 if (resource->dconn != NULL) conn_remove(&(resource->dconn));
4459 if (err == 0) resource->state = rsComplete;
4460 else resource_set_error(resource, reConnect); /* CHECKME: re...? */
4461 push_to_main_res(resource, 0);
4462 }
4463 else
4464 { resource_record(resource, dest, err);
4465 #if CONFIG_CUSTOM_CONN
4466 if (program_mode == pmConsole)
4467 { /* CHECKME: do this without resource_provide_room()/resource_record()?
4468 Seems superfluous... */
4469 __custom_conn_print(resource, dest, err, ccpkNetresp);
4470 }
4471 #endif
4472 push_to_main_res(resource, 1);
4473 }
4474 }
4475
4476 static void execext_shell_callback(tConnection* conn, tConnCbEventKind ccek)
4477 { if (conn->x == 0) /* reading the command's output */
4478 { switch (ccek)
4479 { case ccekRead: execext_shell_read(conn); break;
4480 default: is_buggy: conn_bug(conn, ccek); break;
4481 }
4482 }
4483 else /* writing the command's input */
4484 { switch (ccek)
4485 { case ccekWrite:
4486 if (conn_write_writedata(conn) != 1)
4487 { /* error or done - just stop writing, don't stop reading */
4488 tResource* resource = conn2resource(conn);
4489 if (resource != NULL) conn_remove(&(resource->dconn));
4490 else conn_remove(&conn);
4491 }
4492 break;
4493 default: goto is_buggy; /*@notreached@*/ break;
4494 }
4495 }
4496 }
4497
4498 void resource_start_execext_shell(tExecextShellData* data)
4499 { tResourceRequest* request = data->request;
4500 const char *command = data->command, *writedata = data->writedata;
4501 tExecextShellFlags esf = data->esf;
4502 tResourceError error;
4503 const tBoolean do_read = cond2boolean(esf & (esfReadStdout | esfReadStderr)),
4504 do_write = cond2boolean(writedata != NULL);
4505 int fd_read[2], fd_write[2]; /* "read"/"write" from parent's point of view */
4506 unsigned char _cleanup = 0; /* what to cleanup on failure */
4507 pid_t pid;
4508
4509 if (do_write)
4510 { if (my_pipe(fd_write) != 0)
4511 { pipe_failed: error = rePipe;
4512 failed:
4513 if (request != NULL)
4514 { resource_request_set_error(request, error);
4515 push_to_main_req(request, 0);
4516 }
4517 if (_cleanup & 1)
4518 { my_close_pipe(fd_write[0]); my_close_pipe(fd_write[1]); }
4519 if (_cleanup & 2)
4520 { my_close_pipe(fd_read[0]); my_close_pipe(fd_read[1]); }
4521 return;
4522 }
4523 _cleanup |= 1;
4524 if (!fd_is_observable(fd_write[1])) { tmofd: error = reTmofd; goto failed;}
4525 }
4526 if (do_read)
4527 { if (my_pipe(fd_read) != 0) goto pipe_failed;
4528 _cleanup |= 2;
4529 if (!fd_is_observable(fd_read[0])) goto tmofd;
4530 }
4531
4532 pid = fork();
4533 if (pid < 0) { error = reFork; goto failed; }
4534 else if (pid == 0) /* child process */
4535 { const char *path, *errstr;
4536 char *spfbuf, *spfbuf2, *param[20];
4537 unsigned char count;
4538 int err;
4539 reset_signals(); count = 0;
4540 if (config.flags & cfExecextShellCustom)
4541 { const tExecextParam *list = config.execext_shell;
4542 while (list != NULL) { param[count++] = list->param; list = list->next; }
4543 }
4544 else
4545 { param[count++] = unconstify("/bin/sh");
4546 param[count++] = unconstify("-c");
4547 }
4548 param[count++] = unconstify(command); param[count] = NULL;
4549 if (do_read) my_close_pipe(fd_read[0]);
4550 if (esf & esfReadStdout)
4551 { if (fd_read[1] != 1)
4552 { if (!my_dup2(fd_read[1], 1))
4553 { dup2_failed: err = errno; errstr = _("can't setup file descriptors");
4554 goto child_error;
4555 }
4556 }
4557 }
4558 else my_close_unregistried(1);
4559 if (esf & esfReadStderr)
4560 { if ( (fd_read[1] != 2) && (!my_dup2(fd_read[1], 2)) ) goto dup2_failed; }
4561 else my_close_unregistried(2);
4562 if (do_write)
4563 { my_close_pipe(fd_write[1]);
4564 if ( (fd_write[0] != 0) && (!my_dup2(fd_write[0], 0)) ) goto dup2_failed;
4565 }
4566 else my_close_unregistried(0);
4567 path = param[0];
4568 (void) execv(path, param);
4569
4570 /* If we get here, execv() failed, so we send a little error text to the
4571 resource handler and exit the child process: */
4572 err = errno;
4573 my_spf(strbuf2, STRBUF_SIZE, &spfbuf2, _("can't execute \"%s\""), path);
4574 errstr = spfbuf2;
4575 child_error:
4576 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
4577 _("retawq: execext-shell error: %s (error #%d, %s)"), errstr, err,
4578 my_strerror(err));
4579 if (esf & esfReadStderr) my_write_str(fd_read[1], spfbuf);
4580 else if (may_use_fd2()) my_write_str_unregistried(2, spfbuf); /* CHECKME!*/
4581 debugmsg(spfbuf);
4582 _exit(1);
4583 }
4584 else /* parent process */
4585 { tResource* resource = NULL;
4586 tConnection *rconn = NULL, *wconn = NULL;
4587 if (do_read)
4588 { my_close_pipe(fd_read[1]); make_fd_cloexec(fd_read[0]);
4589 (void) make_fd_nonblocking(fd_read[0]); /* (just trying) */
4590 resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH,
4591 ( (esf & esfEnforceHtml) ? rckHtml : rckUnknown ), rsReading);
4592 rconn = conn_create(fd_read[0], execext_shell_callback, resource, truE,
4593 NULL, rpExecextShell);
4594 rconn->flags |= cnfConnected; conn_set_read(rconn);
4595 }
4596 if (do_write)
4597 { my_close_pipe(fd_write[0]); make_fd_cloexec(fd_write[1]);
4598 (void) make_fd_nonblocking(fd_write[1]); /* (just trying) */
4599 wconn = conn_create(fd_write[1], execext_shell_callback, resource,
4600 cond2boolean(resource != NULL), NULL, rpExecextShell);
4601 wconn->flags |= cnfConnected; wconn->x = 1; conn_set_write(wconn);
4602 conn_set_writedata(wconn, writedata, data->writedata_size);
4603 data->writedata = NULL; /* detach */
4604 #if CONFIG_DEBUG
4605 wconn->flags |= cnfDebugNodata;
4606 #endif
4607 }
4608 if (resource != NULL) { resource->cconn = rconn; resource->dconn = wconn; }
4609 }
4610 }
4611
4612 #endif /* #if OPTION_EXECEXT & EXECEXT_SHELL */
4613
4614
4615 /** Non-local resources: HTTP */
4616
4617 #if CONFIG_HTTP & HTTP_11
4618 my_enum1 enum
4619 { cpsUnknown = 0, cpsData = 1, cpsSize = 2, cpsCrlf1 = 3, cpsSkipSizeline = 4,
4620 cpsCr2 = 5, cpsCrlf2 = 6, cpsSkipTrailerA = 7, cpsSkipTrailerB = 8,
4621 cpsSkipTrailerC = 9, cpsSkipTrailerD = 10
4622 } my_enum2(unsigned char) tChunkParsingState; /* for HTTP/1.1 chunk encoding */
4623 #endif
4624
4625 my_enum1 enum
4626 { hrfNone = 0
4627 #if CONFIG_HTTP & HTTP_11
4628 , hrfChunked = 0x01
4629 #endif
4630 #if CONFIG_HTTP & HTTP_AUTH_ANY
4631 , hrfUseNewAuth = 0x02
4632 #endif
4633 } my_enum2(unsigned char) tHttpRpsdFlags;
4634
4635 typedef struct
4636 { tRpsdRemover remover;
4637 char* header;
4638 #if CONFIG_HTTP & HTTP_AUTH_ANY
4639 const char* challenge; /* from an HTTP response header */
4640 const char* new_auth; /* for the subsequent HTTP request header */
4641 #endif
4642 size_t usable, used; /* for the header */
4643 #if CONFIG_HTTP & HTTP_11
4644 size_t chunksize_left;
4645 tChunkParsingState cps;
4646 #endif
4647 tHttpRpsdFlags flags;
4648 } tHttpRpsd;
4649
4650 static void http_rpsd_remove(tRpsdGeneric* _rpsd)
4651 { tHttpRpsd* rpsd = (tHttpRpsd*) _rpsd;
4652 __dealloc(rpsd->header);
4653 #if CONFIG_HTTP & HTTP_AUTH_ANY
4654 __dealloc(rpsd->challenge); __dealloc(rpsd->new_auth);
4655 #endif
4656 }
4657
4658 static void http_rpsd_prepare(tResource* resource)
4659 { resource->rpsd = memory_allocate(sizeof(tHttpRpsd), mapRpsd);
4660 resource->rpsd->remover = http_rpsd_remove;
4661 }
4662
4663 #define __IS_HTTP_WHITESPACE(ch) ((ch) == ' ') /* (enough after sanitizing) */
4664 #define IS_HTTP_WHITESPACE(ch) ( ((ch) == ' ') || ((ch) == '\t') )
4665
4666 static void skip_http_whitespace(const char** _ptr)
4667 /* skips an HTTP header whitespace sequence at the current position */
4668 { const char* ptr = *_ptr;
4669 while (__IS_HTTP_WHITESPACE(*ptr)) ptr++;
4670 *_ptr = ptr;
4671 }
4672
4673 #define http_close(res) conn_remove(&((res)->cconn))
4674
4675 #if CONFIG_HTTP & HTTP_11
4676
4677 #define http_cl(res) ((tHttpRpsd*)((res)->rpsd))->chunksize_left
4678 #define http_cps(res) ((tHttpRpsd*)((res)->rpsd))->cps
4679
4680 static void parse_http_chunks(tResource* resource, const char* src,
4681 size_t count)
4682 /* Welcome to the World's Worst Chunk-O-Mania! (Now you know the _real_
4683 meaning of "WWW" and ".com":-) A pseudo-code algorithm for this is available
4684 in RFC2616, 19.4.6, but of course we do it differently... */
4685 { size_t chunksize = http_cl(resource), usable_left, usedsize, add;
4686 char *deststart, *dest SHUT_UP_COMPILER(NULL);
4687 tChunkParsingState cps = http_cps(resource);
4688 tBoolean do_push, want_it_back;
4689
4690 usable_left = usedsize = 0; do_push = falsE; want_it_back = truE;
4691 while (count-- > 0)
4692 { const char ch = *src++;
4693 switch (cps)
4694 { case cpsData: /* the most likely case */
4695 if (chunksize > 0) /* it's a "real data" character, store it */
4696 { if (usable_left <= 0) /* must get more room */
4697 { if (usedsize > 0)
4698 { resource_record(resource, deststart, usedsize); usedsize = 0; }
4699 resource_provide_room(resource, &deststart, &usable_left,
4700 resource->bytecount, 2);
4701 dest = deststart;
4702 }
4703 *dest++ = ch; usedsize++; usable_left--; chunksize--; do_push = truE;
4704 }
4705 else if (ch == '\r') cps = cpsCrlf2;
4706 else
4707 { bad_response:
4708 resource_set_error(resource, reResponse);
4709 goto stopnpush;
4710 }
4711 break;
4712 case cpsSize: /* parse chunk-size value */
4713 if (my_isdigit(ch))
4714 { add = (size_t) (ch - '0');
4715 calc:
4716 chunksize = 16 * chunksize + add;
4717 if (chunksize > 100 * 1024 * 1024) goto bad_response;
4718 }
4719 else if ( (ch >= 'a') && (ch <= 'f') )
4720 { add = (size_t) (ch - 'a' + 10); goto calc; }
4721 else if ( (ch >= 'A') && (ch <= 'F') )
4722 { add = (size_t) (ch - 'A' + 10); goto calc; }
4723 else if (ch == '\r') cps = cpsCrlf1;
4724 else
4725 { /* The server sent optional additional information (called
4726 "chunk-extension" in RFC2616, 3.6.1) we don't care about. */
4727 cps = cpsSkipSizeline;
4728 }
4729 break;
4730 case cpsCrlf1: /* reached end of chunk-size line */
4731 if (ch == '\n')
4732 { if (chunksize == 0) cps = cpsSkipTrailerC;
4733 else cps = cpsData;
4734 #if CONFIG_DEBUG
4735 sprint_safe(debugstrbuf, "expecting HTTP chunk of size %d\n",
4736 chunksize);
4737 debugmsg(debugstrbuf);
4738 #endif
4739 }
4740 else goto bad_response;
4741 break;
4742 case cpsSkipSizeline:
4743 if (ch == '\r') cps = cpsCrlf1;
4744 /* "else": skip further characters */
4745 break;
4746 case cpsCr2: /* reached end of chunk */
4747 if (ch == '\r') cps = cpsCrlf2;
4748 else goto bad_response;
4749 break;
4750 case cpsCrlf2: /* reached end of chunk */
4751 if (ch == '\n') cps = cpsSize;
4752 else goto bad_response;
4753 break;
4754 case cpsSkipTrailerA:
4755 if (ch == '\r') cps = cpsSkipTrailerB;
4756 break;
4757 case cpsSkipTrailerB:
4758 if (ch == '\n') cps = cpsSkipTrailerC;
4759 else cps = cpsSkipTrailerA;
4760 break;
4761 case cpsSkipTrailerC:
4762 if (ch == '\r') cps = cpsSkipTrailerD;
4763 else cps = cpsSkipTrailerA;
4764 break;
4765 case cpsSkipTrailerD:
4766 if (ch == '\n') /* finished receiving this resource */
4767 { debugmsg("finished chunked document\n");
4768 resource->state = rsComplete;
4769 stopnpush: http_close(resource);
4770 #if OPTION_LOCAL_CGI
4771 if (resource->dconn != NULL) conn_remove(&(resource->dconn));
4772 #endif
4773 do_push = truE; want_it_back = falsE; goto out;
4774 }
4775 else cps = cpsSkipTrailerA;
4776 break;
4777 }
4778 }
4779 out:
4780 if (usedsize > 0) resource_record(resource, deststart, usedsize);
4781 http_cps(resource) = cps; http_cl(resource) = chunksize;
4782 if (do_push) push_to_main_res(resource, boolean2bool(want_it_back));
4783 }
4784
4785 #endif /* #if CONFIG_HTTP & HTTP_11 */
4786
4787 static one_caller tMbsIndex do_lookup_hhps(const char* str)
4788 { my_binary_search(0, MAX_HHPS, strneqcase3(str, strHhps[idx], hhpslen[idx]),
4789 return(idx))
4790 /* (case-insensitivity of header field names: RFC2616, 4.2) */
4791 }
4792
4793 static one_caller tHttpHeaderParsingState lookup_hhps(const char** _ptr)
4794 { const char* ptr = *_ptr;
4795 tMbsIndex idx = do_lookup_hhps(ptr);
4796 if (idx < 0) idx = 0; /* hhpsDontCare */
4797 else if (idx > 0) { *_ptr += hhpslen[idx]; skip_http_whitespace(_ptr); }
4798 return((tHttpHeaderParsingState) idx);
4799 }
4800
4801 typedef struct
4802 { char *current, *n, *r;
4803 } tHttpHeaderContext;
4804
4805 static void http_header_cleanup(tHttpHeaderContext* context)
4806 { if (context->n != NULL) { *(context->n) = '\n'; context->n = NULL; }
4807 if (context->r != NULL) { *(context->r) = '\r'; context->r = NULL; }
4808 }
4809
4810 static const char* http_header_next(tHttpHeaderContext* context)
4811 /* returns a pointer to the next header line, NULL for ending; RFC2616, 19.3 */
4812 { char *retval = NULL, *current = context->current, *n;
4813 http_header_cleanup(context); /* cleanup for the previous step */
4814 if ( (current == NULL) || (*current == '\0') ) goto out;
4815 loop:
4816 n = my_strchr(current, '\n');
4817 if (n != NULL)
4818 { char *r, *minpos;
4819 if ( (n > current) && (*(n - 1) == '\r') ) r = minpos = n - 1;
4820 else { r = NULL; minpos = n; }
4821 if (minpos <= current) goto out; /* empty header line, end of header */
4822 if (IS_HTTP_WHITESPACE(*(n + 1))) /* multi-line field (RFC2616, 4.2) */
4823 { *n = ' '; if (r != NULL) *r = ' ';
4824 goto loop;
4825 }
4826 context->n = n; *n = '\0';
4827 if (r != NULL) { context->r = r; *r = '\0'; }
4828 }
4829 retval = current; context->current = ( (n != NULL) ? (n + 1) : NULL );
4830 { unsigned char u, *x = (unsigned char*) retval;
4831 while ( (u = *x) != '\0' )
4832 { if (u == 9) *x = ' '; /* replace tab with space, to simplify */
4833 else if (is_control_char(u)) *x = 'X'; /* found forbidden character */
4834 x++;
4835 }
4836 }
4837 out:
4838 return(retval);
4839 }
4840
4841 static one_caller tBoolean finish_http_header(tResource* resource,char* header)
4842 /* returns whether it worked */
4843 { tCantent* cantent = resource->cantent;
4844 const char* temp;
4845 int http_major, http_minor, http_status, value;
4846 tHttpHeaderContext header_context;
4847 tHttpHeaderParsingState hhps;
4848 tBoolean cannot_http11 = truE, ignore_contentlength = falsE,
4849 can_keep_alive = falsE;
4850 #if CONFIG_HTTP & HTTP_11
4851 tBoolean is_chunked = falsE;
4852 #endif
4853 #if OPTION_COOKIES
4854 const tBoolean accept_cookies = cookie_accept_any(resource);
4855 #endif
4856 #if OPTION_LOCAL_CGI
4857 const tBoolean is_local_cgi = cond2boolean(resource->protocol == rpLocalCgi);
4858 #endif
4859
4860 /* Parse the HTTP header (RFC2616); at first we check the HTTP version
4861 (RFC2145) and status code. */
4862
4863 my_memclr_var(header_context); header_context.current = header;
4864 #if OPTION_LOCAL_CGI
4865 if (is_local_cgi)
4866 { resource->server_status_code = 200; /* assume the script worked nicely */
4867 goto header_loop; /* local CGI scripts don't send an "HTTP/.." line */
4868 }
4869 #endif
4870 temp = http_header_next(&header_context);
4871 if ( (temp == NULL) || (!strneqcase(temp, "http/", 5)) )
4872 { bad: resource_set_error(resource, reResponse);
4873 http_header_cleanup(&header_context); return(falsE);
4874 }
4875 my_atoi(temp + 5, &http_major, &temp, 99);
4876 if (http_major > 1) goto bad; /* beyond our capabilities; blame server :-) */
4877 if (*temp != '.') goto bad;
4878 my_atoi(temp + 1, &http_minor, &temp, 99); skip_http_whitespace(&temp);
4879 my_atoi(temp, &http_status, &temp, 999);
4880 if ( (http_status < 100) || (http_status > 599) ) goto bad;
4881 resource->server_status_code = (tServerStatusCode) http_status;
4882 #if CONFIG_DEBUG
4883 sprint_safe(debugstrbuf, "HTTP header: %d,%d,%d\n", http_major, http_minor,
4884 http_status);
4885 debugmsg(debugstrbuf);
4886 #endif
4887
4888 if ( (http_major < 1) || ( (http_major == 1) && (http_minor < 1) ) )
4889 { tSockaddrPortProtInfo* sppi = conn2sppi(resource->cconn, truE);
4890 if (sppi != NULL) sppi->sppif |= sppifCannotHttp11;
4891 cannot_http11 = truE;
4892 }
4893 else
4894 { cannot_http11 = falsE;
4895 can_keep_alive = truE; /* (RFC2616, 8.1.2.1 par. 4) */
4896 }
4897
4898 header_loop:
4899 temp = http_header_next(&header_context);
4900 if (temp == NULL) goto finish_header; /* reached end of header */
4901 hhps = lookup_hhps(&temp);
4902
4903 switch (hhps)
4904 { case hhpsConnection:
4905 if (strneqcase(temp, "keep-alive", 10)) can_keep_alive = truE;
4906 else if (strneqcase(temp, strClose, 5)) can_keep_alive = falsE;
4907 break;
4908 case hhpsContentLength:
4909 if (!ignore_contentlength)
4910 { my_atoi(temp, &value, NULL, 1000 * 1024 * 1024);
4911 resource->nominal_contentlength = value;
4912 }
4913 break;
4914 case hhpsContentType:
4915 { /* (case-insensitivity of media types: RFC2616, 3.7) */
4916 tResourceContentKind kind;
4917 if (strneqcase(temp, "text/html", 9)) kind = rckHtml;
4918 else if (strneqcase(temp, "text/plain", 10)) kind = rckPlainText;
4919 else kind = rckUnknown;
4920 cantent->kind = kind;
4921 }
4922 break;
4923 case hhpsLocation:
4924 __dealloc(cantent->redirection);
4925 cantent->redirection = ( (*temp != '\0') ? my_strdup(temp) : NULL );
4926 break;
4927 case hhpsServer: conn_set_software_id(resource->cconn, temp); break;
4928 #if OPTION_COOKIES
4929 case hhpsSetCookie: case hhpsSetCookie2:
4930 if (accept_cookies)
4931 cookie_handle_text(resource, temp, cond2boolean(hhps==hhpsSetCookie2));
4932 break;
4933 #endif
4934 #if OPTION_LOCAL_CGI
4935 case hhpsStatus:
4936 if (is_local_cgi)
4937 { int i;
4938 my_atoi(temp, &i, &temp, 999);
4939 if ( (i < 100) || (i > 599) ) goto bad;
4940 resource->server_status_code = (tServerStatusCode) i;
4941 }
4942 break;
4943 #endif
4944 case hhpsTransferEncoding:
4945 ignore_contentlength = truE; /* RFC2616, 4.4(.3|end) plus HTTP errata */
4946 if ( (!cannot_http11) && (strneqcase(temp, "chunked", 7)) )
4947 {
4948 #if CONFIG_HTTP & HTTP_11
4949 is_chunked = truE;
4950 #else
4951 goto bad; /* server bug (we sent an HTTP/1.0 request) */
4952 #endif
4953 }
4954 break;
4955 #if CONFIG_HTTP & HTTP_AUTH_ANY
4956 case hhpsWwwAuthenticate:
4957 { tHttpRpsd* rpsd = (tHttpRpsd*) resource->rpsd;
4958 __dealloc(rpsd->challenge);
4959 rpsd->challenge = ( (*temp != '\0') ? my_strdup(temp) : NULL );
4960 debugmsg("HTTP challenge: *"); debugmsg(null2empty(rpsd->challenge));
4961 debugmsg("*\n");
4962 }
4963 break;
4964 #endif
4965 }
4966 goto header_loop;
4967
4968 finish_header:
4969 if (ignore_contentlength)
4970 resource->nominal_contentlength = UNKNOWN_CONTENTLENGTH;
4971 if (resource->cantent->kind == rckUnknown)
4972 resource->cantent->kind = filesuffix2rck(resource->uri_data->path);
4973 #if CONFIG_HTTP & HTTP_11
4974 if (is_chunked)
4975 { ((tHttpRpsd*)(resource->rpsd))->flags |= hrfChunked;
4976 http_cl(resource) = 0; http_cps(resource) = cpsSize;
4977 }
4978 #endif
4979 if ( (resource->nominal_contentlength == UNKNOWN_CONTENTLENGTH) ||
4980 (!can_keep_alive) )
4981 resource->cconn->flags |= cnfDontReuse;
4982 debugmsg("finished HTTP header:\n"); debugmsg(header); debugmsg(strNewline);
4983 return(truE);
4984 }
4985
4986 #if CONFIG_HTTP & (HTTP_AUTH_BASIC | HTTP_PROXYAUTH)
4987 static const char* http_build_basic_authorization(const char* prefix,
4988 const char* username, const char* password)
4989 { char *spfbuf, *spfbuf_enc;
4990 const char* enc;
4991 my_spf(strbuf, STRBUF_SIZE, &spfbuf_enc, strPercsColonPercs, username,
4992 password);
4993 enc = base64_encode(spfbuf_enc); my_spf_cleanup(strbuf, spfbuf_enc);
4994 my_spf(NULL, 0, &spfbuf, "%sAuthorization: Basic %s\r\n", prefix, enc);
4995 memory_deallocate(enc);
4996 return(my_spf_use(spfbuf));
4997 }
4998 #endif
4999
5000 static one_caller const char* http_calc_version(const tResource* resource)
5001 { const char* version = NULL;
5002 const tConfigProtocolVersion* v = config.http_version;
5003
5004 if (v != NULL)
5005 { const char* hostname = resource2textual_host(resource)->hostname;
5006 tPortnumber resport = resource->uri_data->portnumber;
5007 while (v != NULL)
5008 { tPortnumber hp = v->hosts_portnumber;
5009 if ( (hp == 0) || (hp == resport) )
5010 { const char* pattern = v->hosts_pattern;
5011 if ( (pattern != NULL) && (my_pattern_matcher(pattern, hostname)) )
5012 { version = v->protstr; break; } /* (might be NULL, no problem) */
5013 }
5014 v = v->next;
5015 }
5016 }
5017
5018 if (version == NULL)
5019 {
5020 #if (0) && (CONFIG_HTTP & HTTP_11)
5021 /* This code might be re-enabled at some point in the future - when an
5022 acceptable number of HTTP server implementations can handle HTTP/1.1
5023 correctly... */
5024 const tSockaddrPortProtInfo* sppi = conn2sppi(resource->cconn, falsE);
5025 const tBoolean cannot_http11 =
5026 cond2boolean( (sppi != NULL) && (sppi->sppif & sppifCannotHttp11) );
5027 version = (cannot_http11 ? str1dot0 : str1dot1);
5028 #else
5029 version = str1dot0;
5030 #endif
5031 }
5032 return(version);
5033 }
5034
5035 static one_caller void http_setup_reqstr(tResource* resource)
5036 { const tUriData* uri_data = resource->uri_data;
5037 const tConfigProxy* proxy = resource->proxy;
5038 tResourceFlags rf = resource->flags;
5039 const tPortnumber portnumber = ntohs(resource->uri_data->portnumber);
5040 /* (in host byte order!) */
5041 const tBoolean use_post = cond2boolean(rf & rfPost);
5042 const char *uri = uri_data->uri, *path = uri_data->path, *spfbuf_uri,
5043 *query = uri_data->query, *post = (use_post ? null2empty(uri_data->post) :
5044 strEmpty), *hostname = resource2textual_host(resource)->hostname,
5045 *cookies = cookie_collect(resource), *spfbuf_proxyauth = strEmpty,
5046 *http_version = http_calc_version(resource);
5047 const tBoolean use_query = cond2boolean((query!=NULL) && (*query != '\0')),
5048 use_proxy = cond2boolean((proxy != NULL) && (proxy->proxy_hostname!=NULL));
5049 char portbuf[100], *spfbuf;
5050
5051 if (use_post)
5052 { sprint_safe(strbuf2, "Content-Type: %s\r\nContent-Length: %d\r\n",
5053 strAxwfu, strlen(post));
5054 resource->flags |= rfPost; rf = resource->flags;
5055 }
5056
5057 #if CONFIG_HTTP & HTTP_PROXYAUTH
5058 if (use_proxy)
5059 { const char *auth_user = proxy->auth_user, *auth_pass = proxy->auth_pass;
5060 if ( (auth_user != NULL ) && (auth_pass != NULL ) )
5061 { spfbuf_proxyauth = http_build_basic_authorization("Proxy-", auth_user,
5062 auth_pass);
5063 }
5064 }
5065 #endif
5066
5067 if (use_proxy) spfbuf_uri = uri; /* RFC2616, 5.1.2 */
5068 else if (!use_query) spfbuf_uri = path; /* most likely case */
5069 else /* RFC2616 (3.2.2) plus HTTP/1.1 erratum "URI includes query" */
5070 { my_spf(NULL, 0, &spfbuf, "%s?%s", path, query);
5071 spfbuf_uri = my_spf_use(spfbuf);
5072 }
5073
5074 /* The following might seem to contradict RFC2616 (3.2.2, 3.2.3, 14.23,
5075 14.30), but some servers using Apache httpd would try to cause infinite
5076 redirection to the original URI if we used the portnumber without such a
5077 condition. (For example, the word "equivalent" in 3.2.3 means "basically
5078 the same" to me, and 14.30 says that a server may only redirect to
5079 something "other" than the Request-URI, but here "equivalent" seems to
5080 imply "other" instead of "basically the same".) */
5081 if (!(uri_data->udf & udfGotExplicitPortnumber)) *portbuf = '\0';
5082 else sprint_safe(portbuf, ":%d", portnumber);
5083
5084 my_spf(NULL, 0, &spfbuf,
5085 /*A*/ "%s %s HTTP/%s\r\n"
5086 /*B*/ "Host: %s%s\r\n"
5087 /*C*/ "User-Agent: %s\r\n"
5088 /*D*/ "%s" /* proxy authentication if configured */
5089 /*E*/ "%s" /* possibly authentication (non-proxy) */
5090 /*F*/ "%s" /* cache-control stuff if desired */
5091 /*G*/ "%s" /* cookies if any */
5092 /*H*/ "%s" /* content-type/-length for POST method */
5093 "Accept: */*\r\n"
5094 /*I*/ "Accept-Language: %s\r\n"
5095 "Accept-Charset: iso-8859-1\r\n"
5096 /*J*/ "\r\n%s", /* end of header; POST data if any */
5097
5098 /*A*/ (use_post ? strPost : strGet), spfbuf_uri, http_version,
5099 /*B*/ hostname, portbuf,
5100 /*C*/ config.user_agent,
5101 /*D*/ spfbuf_proxyauth,
5102 /*E*/ strEmpty,
5103 /*F*/ ( (rf & rfIsEnforced) ? /* (RFC2616, 14.32 and 14.9) */
5104 "Pragma: no-cache\r\nCache-Control: no-cache\r\n" : strEmpty ),
5105 /*G*/ cookies,
5106 /*H*/ (use_post ? strbuf2 : strEmpty),
5107 /*I*/ config.languages,
5108 /*J*/ post);
5109
5110 #if OPTION_TLS
5111 if ( (resource->protocol == rpHttps) && (proxy != NULL) )
5112 { char* spfbuf2;
5113 my_spf(NULL, 0, &spfbuf2,
5114 "CONNECT %s:%d HTTP/%s\r\nHost: %s:%d\r\n%s\r\n%s", /* RFC2817, 5.2 */
5115 hostname, portnumber, http_version, hostname, portnumber,
5116 spfbuf_proxyauth, spfbuf);
5117 memory_deallocate(spfbuf);
5118 spfbuf = my_spf_use(spfbuf2);
5119 }
5120 #endif
5121
5122 conn_set_writestr(resource->cconn, my_spf_use(spfbuf));
5123 my_spf_cleanup(strEmpty, spfbuf_proxyauth);
5124 if ( (spfbuf_uri != uri) && (spfbuf_uri != path) )
5125 memory_deallocate(spfbuf_uri);
5126 cookie_collect_cleanup(cookies);
5127 }
5128
5129 #if CONFIG_HTTP & HTTP_AUTH_ANY
5130
5131 static one_caller tBoolean http_might_retry_with_auth(const tResource*
5132 resource)
5133 { tBoolean retval = falsE;
5134 tHttpRpsd* rpsd = (tHttpRpsd*)(resource->rpsd);
5135 const tUriData* uri_data;
5136 const char *c = rpsd->challenge, *u, *p;
5137 if (c == NULL) goto out;
5138 uri_data = resource->uri_data;
5139 u = uri_data->username; p = uri_data->password;
5140 #if CONFIG_HTTP & HTTP_AUTH_BASIC
5141 if ( (strneqcase(c, "basic", 5)) && (__IS_HTTP_WHITESPACE(c[5])) )
5142 { if ( (u != NULL) && (p != NULL) )
5143 { rpsd->new_auth = http_build_basic_authorization(strEmpty, u, p);
5144 retval = truE;
5145 }
5146 goto out;
5147 }
5148 #endif
5149 #if CONFIG_HTTP & HTTP_AUTH_DIGEST
5150 if ( (strneqcase(c, "digest", 6)) && (__IS_HTTP_WHITESPACE(c[6])) )
5151 { /* IMPLEMENTME! */
5152 goto out;
5153 }
5154 #endif
5155 out:
5156 return(retval);
5157 }
5158
5159 #endif /* #if CONFIG_HTTP & HTTP_AUTH_ANY */
5160
5161 #if CONFIG_DEBUG
5162 static void http_read_debug(const tResource* resource, const char* what,
5163 int err, const char* buf)
5164 { tBoolean is_chunked;
5165 #if CONFIG_HTTP & HTTP_11
5166 is_chunked = cond2boolean(((tHttpRpsd*)(resource->rpsd))->flags &hrfChunked);
5167 #else
5168 is_chunked = falsE;
5169 #endif
5170 sprint_safe(debugstrbuf, "http_read(%p, %s): %d,%d,%d\n", resource, what,
5171 err, errno, is_chunked);
5172 debugmsg(debugstrbuf);
5173 if (err > 0)
5174 { __debugmsg(buf, err);
5175 if (buf[err - 1] != '\n') debugmsg(strNewline);
5176 }
5177 }
5178 #else
5179 #define http_read_debug(a, b, c, d) do { } while (0)
5180 #endif
5181
5182 static void http_read(tResource* resource)
5183 /* This function is used for rpHttp, rpHttps and rpLocalCgi. */
5184 { tConnection* conn = resource->cconn;
5185 int err;
5186 tBoolean want_it_back = truE;
5187 #if CONFIG_HTTP & HTTP_11
5188 tBoolean is_chunked;
5189 #endif
5190 tServerStatusCode ssc;
5191 tTlHeaderState ths;
5192 char* dest;
5193 size_t size, ncl;
5194
5195 ths = resource->tlheaderstate;
5196 #if CONFIG_DEBUG
5197 sprint_safe(debugstrbuf, "http_read(%p): %d\n", resource, ths);
5198 debugmsg(debugstrbuf);
5199 #endif
5200 if (ths >= 0) /* reading the header */
5201 { tHttpRpsd* rpsd = (tHttpRpsd*) resource->rpsd;
5202 char *header = rpsd->header, *temp;
5203 size_t usable = rpsd->usable, used = rpsd->used, count;
5204 if (usable <= used)
5205 { if (usable >= HIGHEST_OCBS / 2) /* big header - attack? */
5206 { resource_set_error(resource, reResponse); goto do_stop_reading; }
5207 else if (usable <= 0) usable = 1024;
5208 else usable <<= 1;
5209 rpsd->usable = usable;
5210 rpsd->header = header = memory_reallocate(header, usable, mapOther);
5211 }
5212 temp = header + used;
5213 conn_read(err, conn, temp, usable - used, return);
5214 http_read_debug(resource, "header", err, temp);
5215 if (err <= 0)
5216 { stop_reading:
5217 if (err == 0) resource->state = rsComplete;
5218 else resource_set_error(resource, reServerClosed);
5219 do_stop_reading: want_it_back = falsE; http_close(resource);
5220 #if OPTION_LOCAL_CGI
5221 if (resource->dconn != NULL) conn_remove(&(resource->dconn));
5222 #endif
5223 goto finish;
5224 }
5225 rpsd->used += err; count = err;
5226 while (count-- > 0)
5227 { const char ch = *temp++;
5228 switch (ths)
5229 { case 0: if (ch == '\r') { ths = 1; } break;
5230 case 1:
5231 if (ch == '\n') ths = 2;
5232 else if (ch == '\r') { /* ths = 1; */ }
5233 else ths = 0;
5234 break;
5235 case 2: ths = ( (ch == '\r') ? 3 : 0 ); break;
5236 case 3:
5237 if (ch == '\n') /* found the end of the HTTP header */
5238 { *(temp - 2) = '\0'; /* terminate the tough text trickily :-) */
5239 if (!finish_http_header(resource, header)) goto do_stop_reading;
5240 ssc = resource->server_status_code;
5241 if ( (ssc >= 100) && (ssc <= 199) ) /* informational header */
5242 { rpsd->used -= (temp - header);
5243 if (count > 0) { my_memcpy(header, temp, count); temp = header; }
5244 ths = 0; goto next_char;
5245 }
5246 resource->state = rsReading; ths = -1; /* done with headerisms */
5247 if (count > 0)
5248 {
5249 #if CONFIG_HTTP & HTTP_11
5250 if (((tHttpRpsd*)(resource->rpsd))->flags & hrfChunked)
5251 { parse_http_chunks(resource, temp, count);
5252 resource->tlheaderstate = ths;
5253 return;
5254 }
5255 else
5256 #endif
5257 { resource_collect(resource, temp, count); }
5258 }
5259 goto header_done;
5260 }
5261 else if (ch == '\r') ths = 1;
5262 else ths = 0;
5263 break;
5264 }
5265 next_char: {}
5266 }
5267 header_done: resource->tlheaderstate = ths; goto prefinish;
5268 }
5269
5270 #if CONFIG_HTTP & HTTP_11
5271 is_chunked = cond2boolean(((tHttpRpsd*)(resource->rpsd))->flags &
5272 hrfChunked);
5273 if ( (is_chunked) && (http_cps(resource) != cpsData) )
5274 { /* only read a few bytes, they "should" mean the next chunk-size */
5275 char temp[15];
5276 conn_read(err, conn, temp, sizeof(temp), return);
5277 http_read_debug(resource, "chunky", err, temp);
5278 if (err > 0) { parse_http_chunks(resource, temp, err); return; }
5279 else goto stop_reading; /* error or EOF */
5280 }
5281 #endif
5282
5283 { size_t desired_size, bytecount = resource->bytecount;
5284 unsigned char rprflags;
5285 ncl = resource->nominal_contentlength;
5286 if ( (ncl != UNKNOWN_CONTENTLENGTH) && (bytecount < ncl) )
5287 { /* We know how much to expect, so we try (more or less) to slurp it all
5288 at once in order to avoid unnecessary read() calls. */
5289 ncl -= bytecount; /* subtract what was already read */
5290 if (ncl > HIGHEST_OCBS) ncl = HIGHEST_OCBS; /* (just a "sane" limit) */
5291 desired_size = ncl; rprflags = 0;
5292 }
5293 else { desired_size = bytecount; rprflags = 2; }
5294 resource_provide_room(resource, &dest, &size, desired_size, rprflags);
5295 }
5296
5297 #if CONFIG_HTTP & HTTP_11
5298 if (is_chunked)
5299 { size_t max = http_cl(resource);
5300 if (size > max) size = max; /* read at most until end of chunk */
5301 }
5302 #endif
5303 conn_read(err, conn, dest, size, return);
5304 http_read_debug(resource, "data", err, dest);
5305 if (err <= 0) goto stop_reading;
5306 resource_record(resource, dest, err);
5307 #if CONFIG_HTTP & HTTP_11
5308 if (is_chunked)
5309 { http_cl(resource) -= err;
5310 if (http_cl(resource) == 0) http_cps(resource) = cpsCr2;
5311 }
5312 #endif
5313
5314 prefinish:
5315 ncl = resource->nominal_contentlength;
5316 if ( (ncl != UNKNOWN_CONTENTLENGTH) && (resource->bytecount >= ncl) )
5317 { /* The server sent us all the announced data, so we're done: */
5318 err = 0; goto stop_reading;
5319 }
5320 finish:
5321 #if CONFIG_HTTP & HTTP_AUTH_ANY
5322 if ( (!want_it_back) && (resource->server_status_code == 401) && (0) &&
5323 (!(((tHttpRpsd*)(resource->rpsd))->flags & hrfUseNewAuth)) &&
5324 #if OPTION_LOCAL_CGI
5325 (resource->protocol != rpLocalCgi) &&
5326 #endif
5327 (http_might_retry_with_auth(resource))
5328 )
5329 { ((tHttpRpsd*)(resource->rpsd))->flags |= hrfUseNewAuth;
5330 if (resource->cconn != NULL) conn_remove(&(resource->cconn));
5331 want_it_back = truE; resource->state = rsConnecting;
5332 }
5333 #endif
5334 if ( (!want_it_back) || (resource->state == rsReading) )
5335 push_to_main_res(resource, boolean2bool(want_it_back));
5336 }
5337
5338 static void http_callback(tConnection* conn, tConnCbEventKind ccek)
5339 { tResource* resource = (tResource*) (conn->data);
5340 switch (ccek)
5341 { case ccekConnectSetup:
5342 conn_set_write(conn);
5343 if (conn->flags & cnfConnected) goto do_setup;
5344 else push_to_main_res(resource, 1); /* say "Connecting to..." */
5345 break;
5346 case ccekConnectWorked:
5347 do_setup: http_rpsd_prepare(resource); http_setup_reqstr(resource);
5348 resource->state = rsMsgExchange;
5349 #if OPTION_TLS
5350 if ( (resource->protocol == rpHttps) && (resource->proxy == NULL) )
5351 { tBoolean got_session = tls_session_init(conn,
5352 resource2textual_host(resource)->hostname);
5353 if (!got_session)
5354 { resource_set_error(resource, reTls); goto stopnpush; }
5355 }
5356 #endif
5357 push_to_main_res(resource, 1);
5358 break;
5359 case ccekConnectFailed:
5360 resource_set_error(resource, conn_get_failre(conn));
5361 stopnpush: http_close(resource); push_to_main_res(resource, 0); break;
5362 case ccekRead:
5363 #if OPTION_TLS
5364 if (conn->flags & cnfTlsHandshaking)
5365 { tdh: if (tls_do_handshaking(conn) == 0) goto stopnpush;
5366 break;
5367 }
5368 #endif
5369 http_read(resource); break;
5370 case ccekWrite:
5371 #if OPTION_TLS
5372 if (conn->flags & cnfTlsHandshaking) goto tdh;
5373 #endif
5374 switch (conn_write_writedata(conn))
5375 { case 0:
5376 resource_set_error(resource, reServerClosed); goto stopnpush;
5377 /*@notreached@*/ break;
5378 case 1: break; /* keep writing */
5379 case 2: conn_set_read(conn); break; /* all written, start reading */
5380 }
5381 break;
5382 default: conn_bug(conn, ccek); break;
5383 }
5384 }
5385
5386
5387 /** Non-local resources: FTP */
5388
5389 #if CONFIG_FTP
5390
5391 my_enum1 enum
5392 { frfNone = 0, frfLastLine = 0x01, frfSuffsnip = 0x02, frfDir = 0x04,
5393 frfTryingEpsv = 0x08
5394 #if OPTION_TLS
5395 , frfFtpsDataClear = 0x10
5396 #endif
5397 } my_enum2(unsigned char) tFtpRpsdFlags;
5398
5399 typedef struct
5400 { tRpsdRemover remover;
5401 const char* reply_snippet; /* e.g. part of EPSV reply */
5402 #if CONFIG_FTPDIR > 0
5403 char* text; /* buffer for FTP directory listing beautification */
5404 size_t usable, used;
5405 #endif
5406 #if OPTION_TLS
5407 tFtpTlsMethod ftm;
5408 #define ftps_ftm(resource) (((tFtpRpsd*)((resource)->rpsd))->ftm)
5409 #endif
5410 tFtpRpsdFlags flags;
5411 } tFtpRpsd;
5412
5413 #define ftp_frf(resource) (((tFtpRpsd*)((resource)->rpsd))->flags)
5414
5415 static void ftp_rpsd_remove(tRpsdGeneric* _rpsd)
5416 { tFtpRpsd* rpsd = (tFtpRpsd*) _rpsd;
5417 __dealloc(rpsd->reply_snippet);
5418 #if CONFIG_FTPDIR > 0
5419 __dealloc(rpsd->text);
5420 #endif
5421 }
5422
5423 #if CONFIG_FTPDIR > 0
5424
5425 static one_caller void ftp_data_handle_dirline(tResource* resource, char* text)
5426 /* adds one line to an FTP directory listing */
5427 { const char *filename, *htmlfilename, *info, *htmlinfo, *path;
5428 char *spfbuf, *ws;
5429 tBoolean found_ws, need_pathslash;
5430 size_t pathlen;
5431
5432 if (strneqcase(text, "total", 5)) /* game... */
5433 { /* Some very funny servers send a "total <number>" line (probably from a
5434 dumb "ls -l" shell command) at the beginning of the directory listing,
5435 and the number shouldn't become a link. */
5436 const char* temp = text + 5;
5437 if ( (*temp == ' ') || (*temp == '\t') ) /* ...set... */
5438 { while ( (*temp == ' ') || (*temp == '\t') ) temp++;
5439 if (my_isdigit(*temp))
5440 { while (my_isdigit(*temp)) temp++;
5441 if (*temp == '\0') /* ...and match */
5442 { my_spf(strbuf, STRBUF_SIZE, &spfbuf, "<br>%s\n", text);
5443 goto do_collect;
5444 }
5445 }
5446 }
5447 }
5448
5449 found_ws = falsE; ws = text + strlen(text) - 1;
5450 while (ws >= text)
5451 { char ch = *ws;
5452 if ( (ch == ' ') || (ch == '\t') ) { found_ws = truE; break; }
5453 ws--;
5454 }
5455
5456 if (found_ws)
5457 { /* The server sent us some text before the actual file/dir name; we don't
5458 care what it is (probably some access rights like "rw-r--r--"), we just
5459 make sure it goes _before_ the link: */
5460 *ws++ = '\0'; filename = ws; info = text;
5461 }
5462 else { filename = text; info = strEmpty; }
5463
5464 htmlfilename = htmlify(filename); htmlinfo = htmlify(info);
5465 path = resource->uri_data->path; pathlen = strlen(path);
5466 need_pathslash = cond2boolean((pathlen <= 0) || (path[pathlen - 1] !=
5467 chDirsep));
5468 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
5469 "<br>%s%s<a href=\"%s://%s:%d%s%s%s\">%s</a>\n", htmlinfo,
5470 ( (*htmlinfo != '\0') ? strSpace : strEmpty ),
5471 rp_data[resource->protocol].scheme,
5472 resource2textual_host(resource)->hostname,
5473 ntohs(resource->uri_data->portnumber), path, (need_pathslash ? strSlash :
5474 strEmpty), filename, htmlfilename);
5475 htmlify_cleanup(filename, htmlfilename); htmlify_cleanup(info, htmlinfo);
5476 do_collect:
5477 resource_collect_str(resource, spfbuf);
5478 my_spf_cleanup(strbuf, spfbuf);
5479 }
5480
5481 #endif /* #if CONFIG_FTPDIR > 0 */
5482
5483 #if CONFIG_DEBUG
5484 static void debug_ftp_data_llread(tConnection* conn, int result,
5485 size_t bufsize, const char* debugstr)
5486 { const int e = errno;
5487 sprint_safe(debugstrbuf, "ftp_data_llread(%s): fd=%d,result=%d,bufsize=%d,errno=%d\n", debugstr, conn->fd, result, bufsize, e);
5488 debugmsg(debugstrbuf); errno = e;
5489 }
5490 #else
5491 #define debug_ftp_data_llread(conn, result, bufsize, debugstr) do { } while (0)
5492 #endif
5493
5494 #define ftp_data_llread(result, conn, buf, bufsize, debugstr) /* low-level */ \
5495 do \
5496 { conn_read(result, conn, buf, bufsize, return(3)); \
5497 debug_ftp_data_llread(conn, result, bufsize, debugstr); \
5498 } while (0)
5499
5500 static one_caller void ftp_data_dirstart(tResource* resource)
5501 { const tUriData* uri_data = resource->uri_data;
5502 const char* path = uri_data->path;
5503 char* spfbuf;
5504 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("Contents of FTP directory \"%s\""),
5505 path);
5506 #if CONFIG_FTPDIR > 0
5507 resource->cantent->kind = rckHtml;
5508 resource_collect_title(resource, spfbuf);
5509 #else
5510 resource_collect_str(resource, spfbuf);
5511 resource_collect_str(resource, "\n\n");
5512 #endif
5513 my_spf_cleanup(strbuf, spfbuf);
5514 #if CONFIG_FTPDIR > 0
5515 if (calc_parentpath(path, strbuf2, STRBUF_SIZE))
5516 { my_spf(strbuf, STRBUF_SIZE, &spfbuf,
5517 "<p><a href=\"%s://%s:%d%s\">%s</a></p>\n",
5518 rp_data[resource->protocol].scheme,
5519 resource2textual_host(resource)->hostname,
5520 ntohs(uri_data->portnumber), strbuf2, _(strParentDirectory));
5521 resource_collect_str(resource, spfbuf);
5522 my_spf_cleanup(strbuf, spfbuf);
5523 }
5524 #endif
5525 }
5526
5527 static one_caller unsigned char ftp_data_read(tResource* resource)
5528 /* return value: 0=error, 1=EOF, 2=proceed, 3=interrupted (TLS only),
5529 4=do nothing (custom connections only) */
5530 { tFtpRpsd* rpsd = (tFtpRpsd*) resource->rpsd;
5531 tConnection* conn = resource->dconn;
5532 int err;
5533 #if CONFIG_CUSTOM_CONN
5534 const tBoolean is_cc = cond2boolean(doing_custom_conn(resource));
5535 const tResourceFlags resflags = resource->flags;
5536 if ( (is_cc) && (resflags & rfCustomConnBd1) ) /* e.g. "ls" */
5537 { char ccdirbuf[4096];
5538 ftp_data_llread(err, conn, ccdirbuf, sizeof(ccdirbuf), "ccdir");
5539 if (err > 0) __custom_conn_print(resource, ccdirbuf, err, ccpkNetresp);
5540 else
5541 { finish_cc_data:
5542 resource->flags |= rfCustomConnBd2;
5543 if (err == -1) custom_conn_tell_error(resource, conn_err2error(errno));
5544 conn_remove(&(resource->dconn));
5545 if ( (resource->flags & (rfCustomConnBd2 | rfCustomConnBd3)) ==
5546 (rfCustomConnBd2 | rfCustomConnBd3) )
5547 custom_conn_unbusify(resource);
5548 return(4);
5549 }
5550 }
5551 else if ( (is_cc) && (resflags & rfDownload) ) /* e.g. "get" */
5552 { char* dest;
5553 size_t size;
5554 resource_provide_room(resource, &dest, &size, resource->bytecount, 2);
5555 ftp_data_llread(err, conn, dest, size, "ccget");
5556 if (err <= 0) goto finish_cc_data;
5557 resource_record(resource, dest, err); custom_conn_tell_hash(resource);
5558 }
5559 else
5560 #endif
5561 #if CONFIG_FTPDIR > 0
5562 if (rpsd->flags & frfDir)
5563 { char *text, *temp, *remainder, *end;
5564 size_t usable, used;
5565 tBoolean near_r;
5566
5567 /* read */
5568 text = rpsd->text; usable = rpsd->usable; used = rpsd->used;
5569 if (usable <= used)
5570 { usable += 4096; rpsd->usable = usable;
5571 text = rpsd->text = memory_reallocate(text, usable, mapOther);
5572 }
5573 ftp_data_llread(err, conn, text + used, usable - used, "dir");
5574 if (err <= 0) /* error or EOF */
5575 { resource_collect_str(resource, strEndBodyHtml); goto stop_reading; }
5576 used += err; rpsd->used = used;
5577
5578 /* look for CRLF */
5579 temp = remainder = text; end = text + used - 1; near_r = falsE;
5580 while (temp <= end)
5581 { const char ch = *temp++;
5582 if (ch == '\r') near_r = truE;
5583 else
5584 { if ( (ch == '\n') && (near_r) ) /* found a CRLF */
5585 { *(temp - 2) = '\0'; /* overwrites the "\r" */
5586 ftp_data_handle_dirline(resource, remainder);
5587 remainder = temp;
5588 }
5589 near_r = falsE;
5590 }
5591 }
5592
5593 /* finish */
5594 if (remainder > text) /* did something */
5595 { if (remainder > end) rpsd->used = 0; /* did everything */
5596 else
5597 { const char* src = remainder;
5598 char* dest = text;
5599 while (src <= end) *dest++ = *src++;
5600 rpsd->used = rpsd->used - (remainder - text);
5601 }
5602 }
5603 }
5604 else /* isn't a directory */
5605 #endif /* #if CONFIG_FTPDIR > 0 */
5606 { char* dest;
5607 size_t size;
5608 resource_provide_room(resource, &dest, &size, resource->bytecount, 2);
5609 ftp_data_llread(err, conn, dest, size, "file");
5610 if (err <= 0)
5611 { stop_reading:
5612 if (err == 0) { resource->state = rsComplete; return(1); }
5613 else { resource_set_error(resource, conn_err2error(errno)); return(0); }
5614 }
5615 else resource_record(resource, dest, err);
5616 }
5617 return(2); /* all fine */
5618 }
5619
5620 static void ftp_data_callback(tConnection* conn, tConnCbEventKind ccek)
5621 { tResource* resource = (tResource*) (conn->data);
5622 switch (ccek)
5623 { case ccekConnectSetup:
5624 /* IMPLEMENTME: tell user "connecting to...", esp. for custom conns! */
5625 if (conn->flags & cnfConnected) goto do_setup;
5626 else { conn_set_write(conn); push_to_main_res(resource, 1); }
5627 break;
5628 case ccekConnectWorked:
5629 do_setup:
5630 if (ftp_frf(resource) & frfDir) ftp_data_dirstart(resource);
5631 #if OPTION_TLS
5632 if ( (conn_using_tls(resource->cconn)) &&
5633 (!(ftp_frf(resource) & frfFtpsDataClear)) )
5634 { /* We want to protect the data connection whenever the control
5635 connection is protected and the user didn't explicitly specify the
5636 "dataclear" method. We can get here under two circumstances:
5637 1. rpFtps; 2. rpFtp on a custom connection with a manual "auth". */
5638 tBoolean got_session = tls_session_init(conn,
5639 resource2textual_host(resource)->hostname);
5640 if (!got_session)
5641 { resource_set_error(resource, reTls); goto stopnpush; }
5642 }
5643 else
5644 #endif
5645 { conn_set_read(conn); }
5646 resource->state = rsReading; push_to_main_res(resource, 1);
5647 break;
5648 case ccekConnectFailed:
5649 resource_set_error(resource, conn_get_failre(conn));
5650 stopnpush:
5651 conn_remove(&(resource->dconn));
5652 conn_remove(&(resource->cconn)); /* CHECKME! */
5653 push_to_main_res(resource, 0);
5654 break;
5655 #if OPTION_TLS
5656 case ccekWrite:
5657 if (!conn_using_tls(conn)) goto is_buggy;
5658 /* "else": */ /*@fallthrough@*/
5659 #endif
5660 case ccekRead:
5661 #if OPTION_TLS
5662 if (conn->flags & cnfTlsHandshaking)
5663 { if (tls_do_handshaking(conn) == 0) goto stopnpush;
5664 break;
5665 }
5666 #endif
5667 switch (ftp_data_read(resource))
5668 { case 1:
5669 resource->server_status_code = 226; /* CHECKME: that's dirty! */
5670 /*@fallthrough@*/
5671 case 0: goto stopnpush; /*@notreached@*/ break;
5672 case 2: push_to_main_res(resource, 1); break;
5673 /* case 3: TLS operation was interrupted, just proceed */
5674 /* case 4: custom connection operation, nothing to do here */
5675 }
5676 break;
5677 default:
5678 #if OPTION_TLS
5679 is_buggy:
5680 #endif
5681 conn_bug(conn, ccek); break;
5682 }
5683 }
5684
5685 static const char* ftp_prepare_snippet(const tResource* resource)
5686 { const char *retval = NULL, /* assume failure */
5687 *ptr = ((tFtpRpsd*)(resource->rpsd))->reply_snippet;
5688 unsigned char count;
5689 if (ptr == NULL) goto out;
5690 for (count = 0; count <= 2; count++)
5691 { const char ch = *ptr++;
5692 if (!my_isdigit(ch)) goto out; /* "can't happen" */
5693 }
5694 if ( (*ptr != ' ') && (*ptr != '-') ) goto out; /* "can't happen" */
5695 ptr++; retval = ptr;
5696 out:
5697 return(retval);
5698 }
5699
5700 static one_caller tBoolean ftp_setup_dconn(tResource* resource,
5701 /*@out@*/ tResourceError* error)
5702 /* returns whether it worked */
5703 { const char *ptr = ftp_prepare_snippet(resource), *ptr2, *path;
5704 char ch;
5705 tConnection* conn;
5706 tBoolean is_epsv = cond2boolean(ftp_frf(resource) & frfTryingEpsv),
5707 is_connected;
5708 tSockaddr addr;
5709 size_t addrlen;
5710 int data_fd, err, address_family;
5711
5712 if (ptr == NULL) { bad_ftp: *error = reResponse; failed: return(falsE); }
5713 ptr2 = my_strchr(ptr, '(');
5714 if (ptr2 != NULL)
5715 { ptr2++;
5716 if ( (*ptr2 != '\0') && ( (is_epsv || my_isdigit(*ptr2)) ) )
5717 { ptr = ptr2; goto snipfin; }
5718 }
5719 if (is_epsv) goto bad_ftp; /* invalid server reply format */
5720 while ( (ch = *ptr++) != '\0' )
5721 { if (my_isdigit(ch)) { ptr--; goto snipfin; } }
5722 goto bad_ftp;
5723
5724 snipfin:
5725 if (is_epsv)
5726 { const tSockaddrEntry* sockaddr_entry;
5727 const char dc = *ptr++; /* "delimiting character" */
5728 int _portnumber;
5729 if ( (*ptr != dc) || (*(ptr + 1) != dc) ) goto bad_ftp;
5730 ptr += 2; if (!my_isdigit(*ptr)) goto bad_ftp;
5731 my_atoi(ptr, &_portnumber, &ptr, 99999);
5732 #if CONFIG_DEBUG
5733 sprint_safe(debugstrbuf, "ftp_setup_dconn(): port %d\n", _portnumber);
5734 debugmsg(debugstrbuf);
5735 #endif
5736 if ( (_portnumber < 0) || (_portnumber > 65535) || (*ptr != dc) )
5737 goto bad_ftp;
5738 sockaddr_entry = conn2sockaddr(resource->cconn);
5739 if (sockaddr_entry == NULL)
5740 { *error = reHandshake; /* CHECKME: it's not exactly a _handshake_ bug! */
5741 goto failed;
5742 }
5743 addr = sockaddr_entry->addr;
5744 address_family = sockaddr_entry->address_family;
5745 set_portnumber(&addr, address_family, htons((tPortnumber) _portnumber));
5746 addrlen = sockaddr_entry->addrlen;
5747 }
5748 else /* used PASV */
5749 { int a, b, c, d, p, q;
5750 unsigned int hostlong;
5751 unsigned short int hostshort;
5752 PREPARE_SSCANF_FORMAT(format, 30, "%d,%d,%d,%d,%d,%d")
5753 if (sscanf(ptr, format, &a, &b, &c, &d, &p, &q) != 6) goto bad_ftp;
5754 if ( (a < 0) || (a > 255) || (b < 0) || (b > 255) || (c < 0) || (c > 255)
5755 || (d < 0) || (d > 255) || (p < 0) || (p > 255) || (q < 0) || (q > 255) )
5756 { goto bad_ftp; }
5757 hostlong = (a << 24) | (b << 16) | (c << 8) | (d);
5758 hostshort = (p << 8) | (q);
5759 my_memclr_var(addr);
5760 ((struct sockaddr_in*)&addr)->sin_family = address_family = AF_INET;
5761 ((struct sockaddr_in*)&addr)->sin_addr.s_addr = htonl(hostlong);
5762 set_portnumber(&addr, AF_INET, htons(hostshort));
5763 addrlen = sizeof(struct sockaddr_in);
5764 }
5765
5766 data_fd = create_socket(address_family);
5767 if (data_fd < 0)
5768 { *error = ( (data_fd == -2) ? reTmofd : reSocket ); goto failed; }
5769
5770 err = my_do_connect(data_fd, &addr, addrlen);
5771 #if CONFIG_DEBUG
5772 sprint_safe(debugstrbuf, "FTP data connect: %d,%d,%d,%d\n", data_fd, err,
5773 errno, address_family);
5774 debugmsg(debugstrbuf);
5775 #endif
5776 if (err == 0) is_connected = truE; /* unlikely when non-blocking */
5777 else if (errno == EINPROGRESS) is_connected = falsE;
5778 else
5779 { *error = connect_err2error(errno); my_close_sock(data_fd); goto failed; }
5780
5781 if ((path = resource->uri_data->path) != NULL)
5782 resource->cantent->kind = filesuffix2rck(path);
5783 if (resource->dconn != NULL) /* "should not happen" */
5784 conn_remove(&(resource->dconn));
5785 conn = resource->dconn = conn_create(data_fd, ftp_data_callback, resource,
5786 truE, NULL, resource->protocol);
5787 if (is_connected) conn->flags |= cnfConnected;
5788 conn_callback(conn, ccekConnectSetup);
5789 return(truE);
5790 }
5791
5792 /* File Transfer Protocol server status code equality check; RFC1123 says: "A
5793 User-FTP SHOULD generally use only the highest-order digit of a 3-digit
5794 reply code for making a procedural decision, to prevent difficulties when a
5795 Server-FTP uses non-standard reply codes." CHECKME: should we really? */
5796 #if 1
5797 #define FTPSSCEQ(value1, value2) ((value1) == (value2))
5798 #else
5799 #define FTPSSCEQ(value1, value2) (((value1) / 100) == ((value2) / 100))
5800 #endif
5801
5802 #if CONFIG_USER_QUERY
5803
5804 static __my_inline tBoolean resource_ask_username(tResource* resource,
5805 tUserQueryCallback callback, unsigned char flags)
5806 { return(resource_ask_anything(resource, callback, mifUsername, flags));
5807 }
5808
5809 static __my_inline tBoolean resource_ask_password(tResource* resource,
5810 tUserQueryCallback callback, unsigned char flags)
5811 { return(resource_ask_anything(resource, callback, mifPassword, flags));
5812 }
5813
5814 static void ftp_user_callback(tUserQuery*); /* prototype */
5815
5816 #endif
5817
5818 #if OPTION_TLS
5819
5820 static tFtpTlsMethod calc_ftp_tls_method(const tResource* resource)
5821 { tFtpTlsMethod retval;
5822 const tConfigFtpTlsMethod* cftm;
5823 const tBoolean is_ftps = cond2boolean(resource->protocol == rpFtps);
5824 #if CONFIG_CUSTOM_CONN
5825 if ( (doing_custom_conn(resource)) && (is_ftps) )
5826 { retval = ftmTls; goto out; }
5827 #endif
5828 retval = ftmAutodetect; cftm = config.ftp_tls_method;
5829 if (cftm != NULL)
5830 { const char* hostname = resource2textual_host(resource)->hostname;
5831 const tPortnumber resport = resource->uri_data->portnumber;
5832 while (cftm != NULL)
5833 { tPortnumber hp = cftm->hosts_portnumber;
5834 if ( ( (hp == 0) || (hp == resport) ) &&
5835 (my_pattern_matcher(cftm->hosts_pattern, hostname)) )
5836 { retval = cftm->ftm; break; }
5837 cftm = cftm->next;
5838 }
5839 }
5840 if ( (retval == ftmAutodetect) && (is_ftps) &&
5841 (resource->textual_hppi->portnumber == htons(990)) )
5842 retval = ftmTls; /* (earliest autodetection:-) */
5843 out:
5844 return(retval);
5845 }
5846
5847 static tBoolean ftp_control_tls_session_init(tConnection* conn,
5848 tResource* resource)
5849 { const tBoolean retval = tls_session_init(conn,
5850 resource2textual_host(resource)->hostname);
5851 conn_set_dissolver(conn, quitcmd_dissolver); /* first quit, then close :-) */
5852 return(retval);
5853 }
5854
5855 #if CONFIG_USER_QUERY
5856 static void ftps_dataclear_callback(tUserQuery*); /* prototype */
5857 #endif
5858
5859 #endif /* #if OPTION_TLS */
5860
5861 static void ftp_control_do_start_command(tResource* resource, const char* str)
5862 { tConnection* conn = resource->cconn;
5863 #if CONFIG_DEBUG
5864 debugmsg("starting new FTP command: "); debugmsg(str);
5865 #endif
5866 #if CONFIG_CUSTOM_CONN
5867 if (doing_custom_conn(resource)) custom_conn_print(resource, str,ccpkNetcmd);
5868 #endif
5869 conn_set_writestr(conn, str); conn_set_write(conn);
5870 }
5871
5872 #define FCSCR(x) do { retval = (x); goto out; } while (0)
5873
5874 static unsigned char ftp_control_start_command(tResource* resource,
5875 /*@out@*/ tResourceError* _ftp_error)
5876 { unsigned char retval; /* 0=error, 1=suspended (for user query), 2=started */
5877 const tSockaddrPortProtInfo* sppi;
5878 const char *path, *temp;
5879 char* spfbuf;
5880 size_t pathlen;
5881 switch (resource->handshake)
5882 { case rchFtpUser:
5883 if ( (temp = resource->uri_data->username) != NULL )
5884 my_spf(NULL, 0, &spfbuf, strNetcmdUser, temp);
5885 #if CONFIG_USER_QUERY
5886 else if (resource_ask_username(resource, ftp_user_callback, 0))
5887 { susp: FCSCR(1); }
5888 #endif
5889 else { login_failure: *_ftp_error = reLogin; failed: FCSCR(0); }
5890 break;
5891 case rchFtpPassword:
5892 if ( (temp = resource->uri_data->password) != NULL )
5893 my_spf(NULL, 0, &spfbuf, strNetcmdPass, temp);
5894 #if CONFIG_USER_QUERY
5895 else if (resource_ask_password(resource, ftp_user_callback, 0))
5896 goto susp;
5897 #endif
5898 else goto login_failure;
5899 break;
5900 case rchFtpPasv:
5901 /* We use PASV/EPSV instead of PORT/EPRT due to RFC1579; but IMPLEMENTME:
5902 fall back to PORT/EPRT when PASV/EPSV fails? - We always try the more
5903 modern EPSV command (RFC2428) first unless we definitely know that the
5904 server doesn't support it. */
5905 sppi = conn2sppi(resource->cconn, falsE);
5906 if ( (sppi == NULL) || (!(sppi->sppif & sppifFtpCannotEpsv)) )
5907 { ftp_frf(resource) |= frfTryingEpsv;
5908 spfbuf = my_strdup("EPSV\r\n"); /* CHECKME: "EPSV ALL"? */
5909 }
5910 else spfbuf = my_strdup("PASV\r\n");
5911 break;
5912 case rchFtpRequest:
5913 path = resource->uri_data->path; pathlen = strlen(path);
5914 if ( (pathlen > 0) && (path[pathlen - 1] == '/') )
5915 ftp_frf(resource) |= frfDir;
5916 if (!strncmp(path, "/~/", 3)) path += 3;
5917 my_spf(NULL, 0, &spfbuf, "%s%s%s\r\n", ( (ftp_frf(resource) & frfDir)
5918 ? strList : strRetr ), ( (*path != '\0') ? strSpace : strEmpty ),
5919 path); /* IMPLEMENTME: try "MLSD" instead of "LIST" first? */
5920 break;
5921 #if OPTION_TLS
5922 case rchFtpTlsAuthTls: spfbuf = my_strdup("AUTH TLS\r\n"); break;
5923 case rchFtpTlsAuthSsl: spfbuf = my_strdup("AUTH SSL\r\n"); break;
5924 case rchFtpTlsPbsz: spfbuf = my_strdup("PBSZ 0\r\n"); break;
5925 case rchFtpTlsProt:
5926 { const tBoolean data_clear =
5927 cond2boolean(ftps_ftm(resource) == ftmAuthTlsDataclear);
5928 my_spf(NULL, 0, &spfbuf, "PROT %c\r\n", (data_clear ? 'C' : 'P'));
5929 if (data_clear) ftp_frf(resource) |= frfFtpsDataClear;
5930 }
5931 break;
5932 #endif
5933 default: *_ftp_error = reHandshake; goto failed; /*@notreached@*/ break;
5934 }
5935 ftp_control_do_start_command(resource, my_spf_use(spfbuf));
5936 FCSCR(2);
5937 out:
5938 #if CONFIG_DEBUG
5939 sprint_safe(debugstrbuf, "FTP-csc: retval=%d\n", retval);
5940 debugmsg(debugstrbuf);
5941 #endif
5942 return(retval);
5943 }
5944
5945 #undef FCSCR
5946
5947 my_enum1 enum
5948 { fhdfNone = 0
5949 #if OPTION_TLS
5950 , fhdfTlsFirst = 0x01
5951 #endif
5952 } my_enum2(unsigned char) tFtpHandshakeDataFlags;
5953
5954 static struct
5955 { tResourceCommandHandshake next_rch;
5956 tResourceError re;
5957 tFtpHandshakeDataFlags flags;
5958 } ftp_handshake_data;
5959
5960 #define FCHR(x) do { retval = (x); goto out; } while (0)
5961
5962 static one_caller unsigned char ftp_control_handshake(tResource* resource)
5963 /* return value: 0=error, 1=command sequence finished, 2=suspended (for user
5964 query), 3=possibly prepared next */
5965 { unsigned char retval;
5966 const tServerStatusCode status = resource->server_status_code;
5967 tResourceCommandHandshake next = rchUnknown;
5968 tConnection* conn = resource->cconn;
5969 switch (resource->handshake)
5970 { case rchConnected:
5971 if (FTPSSCEQ(status, 220)) /* "server ready", "awaiting input" */
5972 { const char* ptr = ftp_prepare_snippet(resource);
5973 if (ptr != NULL) conn_set_software_id(conn, ptr);
5974 conn_set_dissolver(conn, quitcmd_dissolver); next = rchFtpUser;
5975 #if OPTION_TLS
5976 if (resource->protocol == rpFtps)
5977 { switch (ftps_ftm(resource))
5978 { case ftmAutodetect: case ftmAuthTls: case ftmAuthTlsDataclear:
5979 next = rchFtpTlsAuthTls; break;
5980 case ftmAuthSsl: prepare_auth_ssl: next = rchFtpTlsAuthSsl; break;
5981 }
5982 }
5983 #endif
5984 }
5985 break;
5986 case rchFtpUser:
5987 if ( (FTPSSCEQ(status, 230))
5988 #if OPTION_TLS
5989 || ( (resource->protocol == rpFtps) && (FTPSSCEQ(status, 232)) )
5990 #endif
5991 ) /* "no password/account required"; skip "PASS" command */
5992 { prepare_epsv_pasv: next = rchFtpPasv; }
5993 else if (status == 331) next = rchFtpPassword; /* "password required" */
5994 else if ( (status == 332) || (status == 532) ) /* "account required" */
5995 next = rchFtpPassword; /* (see 332/532 comment below) */
5996 else
5997 {
5998 #if CONFIG_USER_QUERY
5999 if ( (status == 530) &&
6000 (resource_ask_username(resource, ftp_user_callback, 1)) )
6001 { drp_susp: dealloc(resource->uri_data->password); susp: FCHR(2); }
6002 #endif
6003 login_failed: ftp_handshake_data.re = reLogin; failed: FCHR(0);
6004 }
6005 break;
6006 case rchFtpPassword:
6007 if (status == 230) /* "user logged in" */
6008 { const tUriData* u = resource->uri_data;
6009 hppi_login_set(resource->textual_hppi, u->username, u->password);
6010 goto prepare_epsv_pasv;
6011 }
6012 else if (status == 202) /* "command was superfluous" */
6013 goto prepare_epsv_pasv;
6014 else if ( (status == 332) || (status == 532) ) /* "account required" */
6015 { /* We don't actually send account information (simply because neither
6016 account configuration nor FTP upload has been implemented yet),
6017 but we also don't cause a "login failed" error message. 332 and
6018 532 often mean "account might be required later, e.g. for upload"
6019 and not "account is required _now_, or all further operations will
6020 be refused". So we simply proceed and hope the best. :-) */
6021 goto prepare_epsv_pasv;
6022 }
6023 else
6024 {
6025 #if CONFIG_USER_QUERY
6026 if ( ( (status == 530) /* || (status == 421) */ ) &&
6027 (resource_ask_password(resource, ftp_user_callback, 1)) )
6028 { /* (Yes, some buggy servers send 421 instead of the correct 530 to
6029 indicate "login failed"; IMPLEMENTME: must sometimes create a new
6030 connection and resend the login commands in this case!) */
6031 goto drp_susp;
6032 }
6033 #endif
6034 goto login_failed;
6035 }
6036 break;
6037 case rchFtpPasv:
6038 if (ftp_frf(resource) & frfTryingEpsv)
6039 { if (FTPSSCEQ(status, 229)) /* "entering passive mode" (EPSV) */
6040 { epsv_pasv_worked:
6041 if (!ftp_setup_dconn(resource, &(ftp_handshake_data.re)))goto failed;
6042 next = rchFtpRequest;
6043 }
6044 else /* "EPSV" failed */
6045 { tSockaddrPortProtInfo* sppi = conn2sppi(conn, truE);
6046 if (sppi != NULL) sppi->sppif |= sppifFtpCannotEpsv;
6047 ftp_frf(resource) &= ~frfTryingEpsv;
6048 goto prepare_epsv_pasv; /* try "PASV" */
6049 }
6050 }
6051 else if (FTPSSCEQ(status, 227)) /* "entering passive mode" (PASV) */
6052 { goto epsv_pasv_worked; }
6053 break;
6054 case rchFtpRequest:
6055 if (status == 226)
6056 { /* We leave the control connection open until the data transfer
6057 "actually" finished, not until the server thinks it's finished. */
6058 FCHR(1);
6059 }
6060 else if ( (status / 10 == 55) && (!doing_custom_conn(resource)) )
6061 { if (!(ftp_frf(resource) & frfDir))
6062 { /* The FTP server says something like "file not found", so we hope
6063 the thing is available as a _directory_ and try to get that: */
6064 if (resource->dconn != NULL) conn_remove(&(resource->dconn));
6065 ftp_frf(resource) |= frfDir; debugmsg("retrying as FTP dir\n");
6066 goto prepare_epsv_pasv;
6067 }
6068 /* IMPLEMENTME: else { ....error = re....; goto failed; } */
6069 }
6070 break;
6071 #if OPTION_TLS
6072 case rchFtpTlsAuthTls:
6073 if (FTPSSCEQ(status, 234))
6074 { next = rchFtpTlsPbsz; tls1: ftp_handshake_data.flags |= fhdfTlsFirst; }
6075 else if ( (status / 100 == 5) && (ftps_ftm(resource) == ftmAutodetect) )
6076 { /* The server didn't like "AUTH TLS", but maybe it likes "AUTH SSL". */
6077 goto prepare_auth_ssl;
6078 }
6079 break;
6080 case rchFtpTlsAuthSsl:
6081 if ( (FTPSSCEQ(status, 234)) || (FTPSSCEQ(status, 334)) )
6082 { /* (The 334 is technically wrong, but some buggy servers use(d) it.) */
6083 next = rchFtpUser; goto tls1;
6084 }
6085 break;
6086 case rchFtpTlsPbsz:
6087 if (FTPSSCEQ(status, 200)) next = rchFtpTlsProt;
6088 break;
6089 case rchFtpTlsProt:
6090 if (FTPSSCEQ(status, 200)) next = rchFtpUser;
6091 #if CONFIG_USER_QUERY
6092 else if ( ( (status == 534) || (status == 504) || (status == 431) ) &&
6093 (ftps_ftm(resource) == ftmAutodetect) &&
6094 (resource_ask_anything(resource, ftps_dataclear_callback,
6095 mifFtpsDataclear, 0)) )
6096 { goto susp; }
6097 #endif
6098 break;
6099 #endif /* TLS */
6100 default: /* "should not happen" */
6101 ftp_handshake_data.re = reHandshake; goto failed; /*@notreached@*/ break;
6102 }
6103 ftp_handshake_data.next_rch = next;
6104 FCHR(3);
6105 out:
6106 #if CONFIG_DEBUG
6107 sprint_safe(debugstrbuf, "FTP-hs: retval=%d, next=%d\n", retval, next);
6108 debugmsg(debugstrbuf);
6109 #endif
6110 return(retval);
6111 }
6112
6113 #undef FCHR
6114
6115 static one_caller unsigned char
6116 ftp_control_handshake_proceed(tResource* resource)
6117 /* returns whether the control connection may persist (1) or not (0) or, as a
6118 special case, that a TLS handshake shall be started (2) */
6119 { unsigned char retval = 1; /* most likely result */
6120 unsigned char handshake_result;
6121 tResourceCommandHandshake next;
6122 tResourceError re;
6123 #if CONFIG_CUSTOM_CONN
6124 const tBoolean is_cc = cond2boolean(doing_custom_conn(resource));
6125 if ( (is_cc) && (resource->handshake == rchFtpCustom) )
6126 { if (resource->flags & (rfCustomConnBd1 | rfCustomConnBd4))
6127 { resource->flags |= rfCustomConnBd3;
6128 if ( (resource->server_status_code != 226) && (resource->dconn != NULL) )
6129 { conn_remove(&(resource->dconn)); resource->flags |= rfCustomConnBd2; }
6130 else if (resource->dconn == NULL) resource->flags |= rfCustomConnBd2;
6131 if ( (resource->flags & (rfCustomConnBd2 | rfCustomConnBd3)) ==
6132 (rfCustomConnBd2 | rfCustomConnBd3) )
6133 { /* unbusify */ }
6134 else goto out; /* e.g. still receiving a directory listing */
6135 }
6136 /* The command was absolutely custom, so we don't even have to _try_ to
6137 calculate an automatic next handshake. Just tell the user we're done: */
6138 do_unbusify: custom_conn_unbusify(resource); goto out;
6139 }
6140 #endif
6141 my_memclr_var(ftp_handshake_data);
6142 handshake_result = ftp_control_handshake(resource);
6143 #if CONFIG_CUSTOM_CONN
6144 if (is_cc)
6145 { if (handshake_result == 0)
6146 { resource->flags |= rfCustomConnStopSequencer;
6147 custom_conn_tell_error(resource, ftp_handshake_data.re);
6148 }
6149 else if (handshake_result == 3)
6150 { if (resource->handshake == ftp_handshake_data.next_rch)
6151 { /* The next handshake would be the same as the current one. This means
6152 that the server didn't understand a specific command and we want to
6153 try a different command which will have the same or a similar
6154 effect. The most important case for this is where EPSV failed and we
6155 want to try PASV now. Whatever, this means that we are still "busy"
6156 and want to proceed automatically, so we don't want to unbusify: */
6157 switch (ftp_control_start_command(resource, &re))
6158 { case 0: goto failed; /*@notreached@*/ break;
6159 case 1: /* would be a bug here... */
6160 re = reHandshake; goto failed; /*@notreached@*/ break;
6161 case 2: break; /* all fine */
6162 }
6163 goto out;
6164 }
6165 #if OPTION_TLS
6166 if (ftp_handshake_data.flags & fhdfTlsFirst)
6167 { if ( (resource->handshake == rchFtpTlsAuthSsl) &&
6168 (ftp_handshake_data.next_rch == rchFtpUser) )
6169 { /* we did rchFtpTlsAuthSsl and want to perform just the TLS
6170 handshake, not send an automated rchFtpUser for this _custom_
6171 connection... */
6172 resource->cconn->flags &= ~cnfWantToWrite; retval = 2; goto out;
6173 }
6174 else goto want_tls;
6175 }
6176 else if (ftp_handshake_data.next_rch == rchFtpTlsProt) goto start_prot;
6177 #endif
6178 }
6179 else if (handshake_result == 2) goto out;
6180 goto do_unbusify;
6181 }
6182 #endif
6183 switch (handshake_result)
6184 { case 0:
6185 re = ftp_handshake_data.re;
6186 failed: resource_set_error(resource, re); retval = 0; break;
6187 case 1: case 2: break;
6188 case 3:
6189 #if OPTION_TLS
6190 if (ftp_handshake_data.flags & fhdfTlsFirst) { want_tls: retval = 2; }
6191 start_prot:
6192 #endif
6193 next = ftp_handshake_data.next_rch;
6194 if (next == rchUnknown) { re = reResponse; goto failed; }
6195 resource->handshake = next;
6196 switch (ftp_control_start_command(resource, &re))
6197 { case 0: goto failed; /*@notreached@*/ break;
6198 case 1: break; /* suspended */
6199 case 2: push_to_main_res(resource, 1); break; /* started */
6200 }
6201 break;
6202 }
6203 out:
6204 return(retval);
6205 }
6206
6207 static unsigned char ftp_control_read(tResource* resource)
6208 /* We treat FTP control connections in a special way because we can throw
6209 almost every byte of input from them directly into the bit bucket. We need
6210 mostly two things: 1. we wanna know the server status code ("reply code" in
6211 FTP terminology); 2. we must recognize the end of a (single- or multi-line)
6212 control message; return value: 0=error, 1=proceeding, 2=reply complete. */
6213 { enum { FTP_BUFSIZE = (4096 + 1) };
6214 char buf[FTP_BUFSIZE], *ptr; /* the bit bucket */
6215 tTlHeaderState ths = resource->tlheaderstate;
6216 tResourceCommandHandshake handshake;
6217 tResourceError ftp_error = reServerClosed;
6218 tConnection* conn = resource->cconn;
6219 tServerStatusCode status;
6220 int err, count;
6221
6222 conn_read(err, conn, buf, FTP_BUFSIZE - 1, return(1));
6223 if (err <= 0) /* error (EOF is also an error here because it's unexpected) */
6224 { failed: resource_set_error(resource, ftp_error);
6225 resource->tlheaderstate = ths; /* CHECKME: set "= 0" instead? */
6226 return(0);
6227 }
6228
6229 buf[err] = '\0'; /* for simplicity (e.g. my_strchr()) */
6230 #if CONFIG_DEBUG
6231 debugmsg("ftp_control_read(): "); debugmsg(buf);
6232 if (buf[err - 1] != '\n') debugmsg(strNewline);
6233 #endif
6234 #if CONFIG_CUSTOM_CONN
6235 if (doing_custom_conn(resource)) custom_conn_print(resource,buf,ccpkNetresp);
6236 #endif
6237 ptr = buf; count = err; handshake = resource->handshake;
6238
6239 if ( (handshake == rchConnected) || (handshake == rchFtpPasv) )
6240 { /* might need a part (first line, "snippet") of the reply text later... */
6241 tFtpRpsd* rpsd = (tFtpRpsd*) (resource->rpsd);
6242 const char *ptrr, *ptrn;
6243 size_t len;
6244 if (ths == 0) { dealloc(rpsd->reply_snippet); rpsd->flags &= ~frfSuffsnip;}
6245 else if (rpsd->flags & frfSuffsnip) goto skipsnip;
6246 ptrr = my_strchr(buf, '\r'); ptrn = my_strchr(buf, '\n');
6247 if ( (ptrr == NULL) || ( (ptrn != NULL) && (ptrr > ptrn) ) ) ptrr = ptrn;
6248 if (ptrr == NULL) len = strlen(buf); /* must store all */
6249 else { len = ptrr - buf; rpsd->flags |= frfSuffsnip; }
6250 if (len <= 0) goto skipsnip;
6251 if (rpsd->reply_snippet == NULL) rpsd->reply_snippet = my_strndup(buf,len);
6252 else
6253 { const size_t rrslen = strlen(rpsd->reply_snippet);
6254 char* s = __memory_allocate(rrslen + len + 1, mapString);
6255 my_memcpy(s, rpsd->reply_snippet, rrslen);
6256 my_memcpy(s + rrslen, buf, len); s[rrslen + len] = '\0';
6257 memory_deallocate(rpsd->reply_snippet); rpsd->reply_snippet = s;
6258 }
6259 skipsnip: {}
6260 }
6261
6262 while (count-- > 0)
6263 { const char ch = *ptr++;
6264 switch (ths)
6265 { case 0: resource->server_status_code = 0; /*@fallthrough@*/
6266 case 1: case 2: /* handle the server status code at message start */
6267 if (!my_isdigit(ch)) { bad_ftp: ftp_error = reResponse; goto failed; }
6268 status = resource->server_status_code;
6269 status = 10 * status + ((tServerStatusCode) (ch - '0'));
6270 resource->server_status_code = status; ths++;
6271 if ( (ths == 3) && ( (status < 100) || (status > 699) ) )
6272 goto bad_ftp; /* (The 6yz reply codes were introduced in RFC2228.) */
6273 break;
6274 case 3: /* check whether single- or multi-line message */
6275 if (ch == ' ') ftp_frf(resource) |= frfLastLine;
6276 else if (ch == '-') ftp_frf(resource) &= ~frfLastLine;
6277 else goto bad_ftp;
6278 ths++; break;
6279 case 4: /* somewhere in a message line */
6280 if (ch == '\r') ths++;
6281 break;
6282 case 5:
6283 if (ch == '\n') /* reached the end of a message line */
6284 { ths++;
6285 if (ftp_frf(resource) & frfLastLine)
6286 { /* finished the last line; now look what to do: */
6287 resource->tlheaderstate = ths = 0;
6288 status = resource->server_status_code;
6289 #if CONFIG_DEBUG
6290 sprint_safe(debugstrbuf,
6291 "found end of FTP control message (fd=%d, rch=%d, ssc=%d)\n",
6292 conn->fd, resource->handshake, status);
6293 debugmsg(debugstrbuf);
6294 #endif
6295 if ( (status / 100) == 1 ) continue; /* intermediary reply */
6296 #if 0
6297 else if (status == 421) { /* .... */ } /* RFC1123, 4.1.2.11 */
6298 /* forget it - some buggy servers send 421 when they mean 530 */
6299 #endif
6300 return(2);
6301 }
6302 }
6303 else if (ch == '\r') { /* ths = 5; */ }
6304 else ths = 4;
6305 break;
6306 case 6: case 7: case 8: /* beginning of line within multi-line message */
6307 if (my_isdigit(ch))
6308 { /* still looks like the status code at the beginning of the last line
6309 of a multi-line message */
6310 ths++;
6311 }
6312 else ths = ( (ch == '\r') ? 5 : 4 );
6313 break;
6314 default: /* ths == 9 */
6315 if (ch == ' ') ftp_frf(resource) |= frfLastLine; /* _is_ last line */
6316 ths = ( (ch == '\r') ? 5 : 4 ); break;
6317 } /* switch */
6318 } /* while count */
6319 resource->tlheaderstate = ths; return(1);
6320 }
6321
6322 #if CONFIG_USER_QUERY
6323
6324 static void ftp_user_callback(tUserQuery* query)
6325 { tMissingInformationFlags mif = query->mif;
6326 tResource* resource;
6327 tResourceError re;
6328 if (mif & mifObjectVanished) goto out; /* nothing further to do */
6329 resource = query->resource; resource_unsuspend(resource);
6330 if (mif & mifQueryFailed)
6331 { do_bug: resource_set_error(resource, reLogin);
6332 do_push: resource_stop(resource); push_to_main_res(resource, 0); goto out;
6333 }
6334 if (mif & mifUserCancelled) goto do_push;
6335 if (mif & mifUsername) resource->handshake = rchFtpUser;
6336 else if (mif & mifPassword) resource->handshake = rchFtpPassword;
6337 else goto do_bug;
6338 (void) ftp_control_start_command(resource, &re); /* CHECKME: handle error? */
6339 out:
6340 resource_ask_finish(query);
6341 }
6342
6343 #if OPTION_TLS
6344 static void ftps_dataclear_callback(tUserQuery* query)
6345 { tMissingInformationFlags mif = query->mif;
6346 tResource* resource;
6347 tResourceError re;
6348 if (mif & mifObjectVanished) goto out; /* nothing further to do */
6349 resource = query->resource; resource_unsuspend(resource);
6350 if (mif & (mifQueryFailed | mifUserCancelled))
6351 { resource_stop(resource); push_to_main_res(resource, 0); goto out; }
6352 ftps_ftm(resource) = ftmAuthTlsDataclear;
6353 (void) ftp_control_start_command(resource, &re); /* CHECKME: handle error? */
6354 out:
6355 resource_ask_finish(query);
6356 }
6357 #endif
6358
6359 #endif /* #if CONFIG_USER_QUERY */
6360
6361 static void ftp_control_callback(tConnection* conn, tConnCbEventKind ccek)
6362 { tResource* resource = (tResource*) (conn->data);
6363 tResourceError ftp_error;
6364 switch (ccek)
6365 { case ccekConnectSetup:
6366 #if CONFIG_CUSTOM_CONN
6367 if (doing_custom_conn(resource)) custom_conn_tell_msg(resource);
6368 #endif
6369 if (conn->flags & cnfConnected) goto do_setup;
6370 else { conn_set_write(conn); push_to_main_res(resource, 1); }
6371 break;
6372 case ccekConnectWorked:
6373 do_setup:
6374 resource->state = rsMsgExchange; resource->handshake = rchConnected;
6375 resource->rpsd = memory_allocate(sizeof(tFtpRpsd), mapRpsd);
6376 resource->rpsd->remover = ftp_rpsd_remove;
6377 #if OPTION_TLS
6378 if (resource->protocol == rpFtps)
6379 { tFtpTlsMethod ftm = ftps_ftm(resource) = calc_ftp_tls_method(resource);
6380 if (ftm == ftmTls) /* immediate handshake */
6381 { start_session:
6382 if (!ftp_control_tls_session_init(conn, resource))
6383 { ftp_error = reTls; goto set_error; }
6384 else push_to_main_res(resource, 1);
6385 break; /* to skip the conn_set_read() call */
6386 }
6387 }
6388 #endif
6389 conn_set_read(conn); /* to get the "server ready" message */
6390 push_to_main_res(resource, 1);
6391 break;
6392 case ccekConnectFailed:
6393 ftp_error = conn_get_failre(conn);
6394 set_error: resource_set_error(resource, ftp_error);
6395 stopnpush: conn_remove(&(resource->cconn));
6396 if (resource->dconn != NULL) conn_remove(&(resource->dconn));
6397 push_to_main_res(resource, 0);
6398 break;
6399 case ccekRead:
6400 #if OPTION_TLS
6401 if (conn->flags & cnfTlsHandshaking)
6402 { unsigned char tdhres;
6403 tdh:
6404 tdhres = tls_do_handshaking(conn);
6405 if (tdhres == 0) goto stopnpush;
6406 #if CONFIG_CUSTOM_CONN
6407 else if ( (tdhres == 2) && (doing_custom_conn(resource)) &&
6408 (resource->handshake == rchFtpTlsAuthSsl) )
6409 { custom_conn_unbusify(resource); return; }
6410 #endif
6411 break;
6412 }
6413 #endif
6414 switch (ftp_control_read(resource))
6415 { case 0: goto stopnpush; /*@notreached@*/ break;
6416 case 1: break;
6417 case 2:
6418 switch (ftp_control_handshake_proceed(resource))
6419 { case 0: goto stopnpush; /*@notreached@*/ break;
6420 case 1: break; /* all fine */
6421 #if OPTION_TLS
6422 case 2: goto start_session; /*@notreached@*/ break;
6423 #endif
6424 }
6425 break;
6426 }
6427 break;
6428 case ccekWrite:
6429 #if OPTION_TLS
6430 if (conn->flags & cnfTlsHandshaking) goto tdh;
6431 #endif
6432 switch (conn_write_writedata(conn))
6433 { case 0:
6434 resource_set_error(resource, reServerClosed);
6435 goto stopnpush; /*@notreached@*/ break;
6436 case 1: break; /* keep writing */
6437 case 2: conn_set_read(conn); break; /* all written, start reading */
6438 }
6439 break;
6440 default: conn_bug(conn, ccek); break;
6441 }
6442 }
6443
6444 #endif /* #if CONFIG_FTP */
6445
6446
6447 /** Non-local resources: NNTP */
6448
6449 #if OPTION_NEWS
6450
6451 typedef signed int tNewsArticleNumber;
6452 /* ("signed" for simplicity only; e.g. if a <firstnum> is 0, a "num >=
6453 firstnum" test would always be true for an unsigned variable) */
6454
6455 my_enum1 enum
6456 { nrfNone = 0, nrfHeaderSeries = 0x01
6457 } my_enum2(unsigned char) tNntpRpsdFlags;
6458
6459 typedef struct
6460 { tRpsdRemover remover;
6461 char* text;
6462 size_t usable, used;
6463 tNewsArticleNumber curr_artnum, end_artnum;
6464 tNntpRpsdFlags flags;
6465 } tNntpRpsd;
6466
6467 static void news_rpsd_remove(tRpsdGeneric* _rpsd)
6468 { tNntpRpsd* rpsd = (tNntpRpsd*) _rpsd;
6469 __dealloc(rpsd->text);
6470 }
6471
6472 static one_caller void news_rpsd_prepare(tResource* resource)
6473 { tNntpRpsd* rpsd = (tNntpRpsd*) resource->rpsd;
6474 if (rpsd == NULL)
6475 { void* x = memory_allocate(sizeof(tNntpRpsd), mapRpsd);
6476 rpsd = x; resource->rpsd = x;
6477 rpsd->remover = news_rpsd_remove;
6478 }
6479 else { rpsd->used = 0; /* rpsd->curr_artnum = rpsd->end_artnum = 0; */ }
6480 }
6481
6482 typedef struct tNewsArticle
6483 { struct tNewsArticle* next;
6484 char *header, *lastheader, *body, *lastbody; /* content blocks */
6485 char* id;
6486 tNewsArticleNumber num;
6487 } tNewsArticle;
6488
6489 enum { ngifNone = 0, ngifCannotPost = 0x01 };
6490 typedef unsigned char tNewsGroupInformationFlags;
6491
6492 typedef struct tNewsGroupInformation
6493 { struct tNewsGroupInformation* next;
6494 char* name;
6495 tNewsArticle* articles;
6496 tNewsArticleNumber estnum, firstnum, lastnum;
6497 /* estimated number of articles; number of the first and last article */
6498 tNewsGroupInformationFlags flags;
6499 } tNewsGroupInformation;
6500
6501 static /* __sallocator -- not an "only" reference */ tNewsGroupInformation*
6502 __callocator news_ngi_create(tCachedHostInformation* hostinfo,
6503 const char* name)
6504 { tNewsGroupInformation* retval = (tNewsGroupInformation*)
6505 memory_allocate(sizeof(tNewsGroupInformation), mapPermanent);
6506 retval->next = hostinfo->ngi;
6507 retval->name = my_strdup(name);
6508 hostinfo->ngi = retval;
6509 return(retval);
6510 }
6511
6512 static tNewsGroupInformation* news_ngi_lookup(tCachedHostInformation*
6513 hostinfo, const char* desired_name, tBoolean do_create_if_null)
6514 { tNewsGroupInformation* retval = hostinfo->ngi;
6515 while (retval != NULL)
6516 { const char* name = retval->name;
6517 if ( (name != NULL) && (!strcmp(name, desired_name)) ) break; /* found */
6518 retval = retval->next;
6519 }
6520 if ( (retval == NULL) && (do_create_if_null) )
6521 retval = news_ngi_create(hostinfo, desired_name);
6522 return(retval);
6523 }
6524
6525 static void news_resource2path(const tResource* resource,
6526 /*@out@*/ const char** _path, /*@out@*/ tBoolean* _has_at)
6527 /* prepares the path value of the <resource> a little bit */
6528 { const char* path = resource->uri_data->path;
6529 *_has_at = falsE;
6530 if (path != NULL)
6531 { if (*path == '/') path++;
6532 if (*path == '\0') path = NULL;
6533 else if (my_strchr(path, '@')) *_has_at = truE;
6534 }
6535 *_path = path;
6536 }
6537
6538 static void news_resource2group(const tResource* resource,
6539 /*@out@*/ const char** _group, /*@out@*/ tBoolean* _must_dealloc)
6540 { const char* path;
6541 tBoolean has_at;
6542 *_group = NULL; *_must_dealloc = falsE;
6543 news_resource2path(resource, &path, &has_at);
6544 if ( (path != NULL) && (!has_at) )
6545 { const char* temp = my_strchr(path, '/');
6546 if (temp == NULL) *_group = path; /* all group, no message number */
6547 else if (temp == path) { /* no group given, bad URI - CHECKME! */ }
6548 else { *_group = my_strndup(path, temp - path); *_must_dealloc = truE; }
6549 }
6550 }
6551
6552 static void news_resource2article(const tResource* resource,
6553 /*@out@*/ const char** _article, /*@out@*/ tBoolean* _is_numerical)
6554 { const char* path;
6555 tBoolean has_at;
6556 *_article = NULL; *_is_numerical = falsE;
6557 news_resource2path(resource, &path, &has_at);
6558 if (path != NULL)
6559 { if (has_at) { *_article = path; /* *_is_numerical = falsE; */ }
6560 else
6561 { const char* temp = my_strchr(path, '/');
6562 if ( (temp != NULL) && (temp[1] == '\0') ) /* article string is empty */
6563 temp = NULL;
6564 if (temp != NULL) { *_article = temp + 1; *_is_numerical = truE; }
6565 }
6566 }
6567 }
6568
6569 static tBoolean news_fetch_next_header(tResource* resource)
6570 /* returns whether there actually is a "next" article header to be fetched */
6571 { tNntpRpsd* rpsd = (tNntpRpsd*) resource->rpsd;
6572 tBoolean retval;
6573 tNewsArticleNumber currnum = --(rpsd->curr_artnum);
6574 if (currnum >= rpsd->end_artnum)
6575 { char* spfbuf;
6576 my_spf(NULL, 0, &spfbuf, "HEAD %d\r\n", currnum);
6577 conn_set_writestr(resource->cconn, my_spf_use(spfbuf));
6578 retval = truE;
6579 }
6580 else retval = falsE;
6581 return(retval);
6582 }
6583
6584 #define is_news_whitespace(ch) ( ((ch) == ' ') || ((ch) == '\t') )
6585 /* (RFC2822, 2.2.2) */
6586
6587 static one_caller unsigned char news_handshake(tResource* resource,
6588 const char** _valueptr)
6589 /* return value: 0=error, 1=continue reading, 2=start writing, 3=done */
6590 { tResourceCommandHandshake handshake = resource->handshake;
6591 tServerStatusCode ssc = resource->server_status_code;
6592 const char *valueptr = *_valueptr, *group, *article;
6593 char* spfbuf;
6594 tConnection* conn = resource->cconn;
6595 tBoolean is_numerical, must_dealloc;
6596 switch (handshake)
6597 { case rchConnected:
6598 if (ssc == 200) /* "server ready, posting allowed" */
6599 { prepare_mode_reader:
6600 if (valueptr != NULL)
6601 { while (is_news_whitespace(*valueptr)) valueptr++;
6602 conn_set_software_id(conn, valueptr);
6603 }
6604 conn_set_dissolver(conn, quitcmd_dissolver);
6605 handshake = rchNntpModeReader;
6606 spfbuf = my_strdup("MODE READER\r\n"); /* be polite (RFC2980, 2.3) */
6607 switch_to_writing:
6608 conn_set_writestr(conn, my_spf_use(spfbuf));
6609 start_writing: resource->handshake = handshake;
6610 do_start_writing: return(2);
6611 }
6612 else if (ssc == 201) /* "server ready, no posting allowed" */
6613 { tSockaddrPortProtInfo* sppi = conn2sppi(conn, truE);
6614 if (sppi != NULL) sppi->sppif |= sppifNntpCannotPostArticles;
6615 goto prepare_mode_reader;
6616 }
6617 break;
6618 case rchNntpModeReader:
6619 /* Don't care about the status code here. If the server didn't
6620 understand "MODE READER", that's solely the server's problem. */
6621 news_resource2article(resource, &article, &is_numerical);
6622 if ( (article != NULL) && (!is_numerical) )
6623 { /* fetch the given article (header and body) */
6624 prepare_fetch_article:
6625 handshake = rchNntpFetchArticle;
6626 my_spf(NULL, 0, &spfbuf, "ARTICLE %s%s%s\r\n", (is_numerical ? strEmpty
6627 : strLt), article, (is_numerical ? strEmpty : strGt));
6628 }
6629 else
6630 { news_resource2group(resource, &group, &must_dealloc);
6631 if (group == NULL)
6632 { handshake = rchNntpGetGroups; spfbuf = my_strdup("LIST\r\n"); }
6633 else
6634 { /* must select a group first, in order to prepare further actions */
6635 handshake = rchNntpSelectGroup;
6636 my_spf(NULL, 0, &spfbuf, "GROUP %s\r\n", group);
6637 }
6638 if (must_dealloc) memory_deallocate(group);
6639 }
6640 goto switch_to_writing; /*@notreached@*/ break;
6641 case rchNntpGetGroups:
6642 if (ssc == 215) /* it worked, list of groups follows */
6643 { const char* hostname = resource2textual_host(resource)->hostname;
6644 char* spfbuf2;
6645 resource->cantent->kind = rckHtml;
6646 my_spf(strbuf, STRBUF_SIZE, &spfbuf, _("News groups on %s"), hostname);
6647 my_spf(strbuf2, STRBUF_SIZE, &spfbuf2,
6648 _("List of available news groups on the server %s"), hostname);
6649 resource_collect_title2(resource, spfbuf, spfbuf2);
6650 my_spf_cleanup(strbuf, spfbuf);
6651 my_spf_cleanup(strbuf2, spfbuf2);
6652 continue_reading: ((tNntpRpsd*)(resource->rpsd))->used = 0; return(1);
6653 }
6654 break;
6655 case rchNntpFetchArticle:
6656 if (ssc == 220) goto continue_reading; /* it worked, article follows */
6657 break;
6658 case rchNntpFetchHeader:
6659 if (ssc == 221) goto continue_reading; /* it worked, header follows */
6660 else if ( (((tNntpRpsd*)(resource->rpsd))->flags & nrfHeaderSeries) &&
6661 ( (ssc == 423) || (ssc == 430) ) )
6662 { /* This specific article in a series doesn't exist, but further
6663 articles might exist. */
6664 if (news_fetch_next_header(resource)) goto do_start_writing;
6665 else goto completed;
6666 }
6667 break;
6668 case rchNntpSelectGroup:
6669 if (ssc == 211) /* selecting the group worked */
6670 { tNewsArticleNumber estnum, firstnum, lastnum;
6671 tBoolean need_valuedata;
6672 news_resource2group(resource, &group, &must_dealloc);
6673 news_resource2article(resource, &article, &is_numerical);
6674 need_valuedata = cond2boolean( (article == NULL) || (!is_numerical) );
6675 /* (The "!is_numerical" is a "should not happen" test here.) */
6676 if (need_valuedata)
6677 { /* interpret/store server data */
6678 if (valueptr == NULL)
6679 { bad_valueptr: if (must_dealloc) memory_deallocate(group);
6680 bad_news: resource_set_error(resource, reResponse);
6681 found_error: return(0);
6682 }
6683 while (*valueptr == ' ') { valueptr++; }
6684 { PREPARE_SSCANF_FORMAT(format, 20, "%d %d %d ")
6685 if (sscanf(valueptr, format, &estnum, &firstnum, &lastnum) != 3)
6686 goto bad_valueptr;
6687 }
6688 *_valueptr = NULL;
6689 if (group != NULL) /* "should" be true */
6690 { tNewsGroupInformation* ngi =
6691 news_ngi_lookup(resource2textual_host(resource), group, truE);
6692 ngi->estnum = estnum; ngi->firstnum = firstnum;
6693 ngi->lastnum = lastnum;
6694 }
6695 }
6696 /* prepare next command */
6697 if (need_valuedata)
6698 { const char *groupstr, *hostname;
6699 char* spfbuf2;
6700 tNntpRpsd* rpsd;
6701 prepare_article_index:
6702 groupstr = ((group != NULL) ? group : _(strUnknown));
6703 hostname = resource2textual_host(resource)->hostname;
6704 resource->cantent->kind = rckHtml;
6705 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
6706 _("News group %s, articles %d-%d, server %s"), groupstr, firstnum,
6707 lastnum, hostname);
6708 my_spf(strbuf2, STRBUF_SIZE, &spfbuf2, _("Index of news articles %d-%d in the group %s on the server %s, latest first"),
6709 firstnum, lastnum, groupstr, hostname);
6710 resource_collect_title2(resource, spfbuf, spfbuf2);
6711 my_spf_cleanup(strbuf, spfbuf);
6712 my_spf_cleanup(strbuf2, spfbuf2);
6713 rpsd = (tNntpRpsd*) resource->rpsd; rpsd->curr_artnum = lastnum + 1;
6714 rpsd->end_artnum = firstnum; rpsd->flags |= nrfHeaderSeries;
6715 handshake = rchNntpFetchHeader;
6716 if (must_dealloc) memory_deallocate(group);
6717 if (news_fetch_next_header(resource)) goto start_writing;
6718 else { completed: return(3); }
6719 }
6720 else
6721 { const char* temp;
6722 if ( (article != NULL) && (is_numerical) && (my_isdigit(*article)) &&
6723 ( (temp = my_strchr(article, '-')) != NULL ) &&
6724 (my_isdigit(temp[1])) )
6725 { /* roughly looks like a numerical range of the form "37-42" */
6726 my_atoi(article, &firstnum, &temp, MY_ATOI_INT_MAX);
6727 if (*temp == '-')
6728 { my_atoi(temp + 1, &lastnum, &temp, MY_ATOI_INT_MAX);
6729 if (*temp == '\0') goto prepare_article_index; /* it is */
6730 }
6731 }
6732 if (must_dealloc) memory_deallocate(group);
6733 goto prepare_fetch_article;
6734 }
6735 /* currently: */ /*@notreached@*/
6736 goto switch_to_writing;
6737 }
6738 break;
6739 default:
6740 resource_set_error(resource, reHandshake); goto found_error;
6741 /*@notreached@*/ break;
6742 }
6743 goto bad_news;
6744 }
6745
6746 static const char* __news_lookup_header_line(const char** lines,
6747 size_t linenum, const char* which)
6748 { const char* retval = NULL;
6749 size_t len = strlen(which), count;
6750 for (count = 0; count < linenum; count++)
6751 { const char* line = lines[count];
6752 if (strneqcase(line, which, len))
6753 { const char* temp = line + len;
6754 while (is_news_whitespace(*temp)) temp++;
6755 if (*temp == ':')
6756 { temp++;
6757 while (is_news_whitespace(*temp)) temp++;
6758 if (*temp != '\0') retval = temp;
6759 break;
6760 }
6761 }
6762 }
6763 return(retval);
6764 }
6765
6766 #define news_lookup_header_line(which) \
6767 __news_lookup_header_line(lines, linenum, which)
6768
6769 #define store_header_line \
6770 if (linenum >= maxlinenum) \
6771 { maxlinenum += 20; \
6772 lines = memory_reallocate(lines, maxlinenum * sizeof(char*), mapOther); \
6773 } \
6774 lines[linenum++] = linestart;
6775
6776 static one_caller void news_handle_article_index(tResource* resource)
6777 /* adds one line to an article index document */
6778 { tNntpRpsd* rpsd = (tNntpRpsd*) resource->rpsd;
6779 char ch, *text, *temp, *linestart; /* article header text */
6780 const char *subject, *htmlsubject, *sender, *htmlsender, *date, *htmldate,
6781 *bodylinenum, *htmlbodylinenum, *bodylinenote, *group;
6782 size_t used, linenum, maxlinenum;
6783 const char** lines;
6784 char* spfbuf;
6785 tBoolean near_r, must_dealloc_group;
6786
6787 if (rpsd == NULL) return; /* "should not happen" */
6788 text = rpsd->text; used = rpsd->used;
6789 if ( (text == NULL) || (*text == '\0') || (used <= 0) ) /* no text */
6790 goto out;
6791
6792 /* split the header into lines */
6793
6794 linestart = temp = text; near_r = falsE;
6795 linenum = maxlinenum = 0; lines = NULL;
6796 while ( (ch = *temp++) != '\0' )
6797 { if (ch == '\r') near_r = truE;
6798 else
6799 { if ( (ch == '\n') && (near_r) )
6800 { /* IMPLEMENTME: strip comments (RFC2822, 3.2.3) */
6801 if (is_news_whitespace(*temp)) /* folded; unfold (RFC2822, 2.2.3) */
6802 { *(temp - 2) = *(temp - 1) = ' '; /* overwrites the CRLF */
6803 }
6804 else
6805 { *(temp - 2) = '\0'; /* overwrites the "\r" */
6806 store_header_line
6807 linestart = temp;
6808 }
6809 }
6810 near_r = falsE;
6811 }
6812 }
6813 store_header_line
6814
6815 /* interpret the header lines */
6816
6817 subject = news_lookup_header_line("subject");
6818 if (subject == NULL) subject = _("(no subject)");
6819 sender = news_lookup_header_line("from");
6820 if (sender == NULL)
6821 sender = news_lookup_header_line("sender"); /* better than nothing */
6822 date = news_lookup_header_line("date");
6823 bodylinenum = news_lookup_header_line("lines");
6824 if (bodylinenum == NULL) bodylinenote = strEmpty;
6825 else if (!strcmp(bodylinenum, "1")) bodylinenote = _(" line");
6826 else bodylinenote = _(" lines");
6827
6828 /* build the HTML line */
6829
6830 news_resource2group(resource, &group, &must_dealloc_group);
6831 htmlsubject = htmlify(subject);
6832
6833 #define hify(orig, ified) ified = ( (orig != NULL) ? htmlify(orig) : NULL )
6834 hify(sender, htmlsender); hify(date, htmldate);
6835 hify(bodylinenum, htmlbodylinenum);
6836 #undef hify
6837
6838 #define appt(what) \
6839 ( (what != NULL) ? strSpacedDash : strEmpty ), \
6840 ( (what != NULL) ? what : strEmpty )
6841 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
6842 "<br><a href=\"news://%s/%s/%d\">%s</a>%s%s%s%s%s%s%s\n",
6843 resource2textual_host(resource)->hostname,
6844 ( (group != NULL) ? group : "BUG" ),
6845 ((tNntpRpsd*) (resource->rpsd))->curr_artnum, htmlsubject,
6846 appt(htmlsender), appt(htmldate), appt(htmlbodylinenum), bodylinenote);
6847 #undef appt
6848
6849 resource_collect_str(resource, spfbuf);
6850 my_spf_cleanup(strbuf, spfbuf);
6851
6852 #define hc(orig, ified) if (orig != NULL) htmlify_cleanup(orig, ified)
6853 hc(subject, htmlsubject); hc(sender, htmlsender); hc(date, htmldate);
6854 hc(bodylinenum, htmlbodylinenum);
6855 #undef hc
6856
6857 if (must_dealloc_group) memory_deallocate(group);
6858 memory_deallocate(lines);
6859 out:
6860 rpsd->used = 0;
6861 }
6862
6863 #undef store_header_line
6864 #undef news_lookup_header_line
6865
6866 static one_caller void news_handle_groupline(tResource* resource, char* text)
6867 /* adds one line to a groups index document; the <text> should be of the form
6868 "group last first p" (RFC977, 3.6.1) */
6869 { char *pos = my_strchr(text, ' '), *spfbuf;
6870 const char *info, *name, *htmlname;
6871 if (pos == NULL) info = NULL; /* server bug */
6872 else { *pos++ = '\0'; info = pos; }
6873 name = text; htmlname = htmlify(name);
6874 my_spf(strbuf, STRBUF_SIZE, &spfbuf,
6875 "<br><a href=\"news://%s/%s\">%s</a>%s%s\n",
6876 resource2textual_host(resource)->hostname, name, htmlname,
6877 ( (info != NULL) ? strSpace : strEmpty ), null2empty(info));
6878 resource_collect_str(resource, spfbuf);
6879 my_spf_cleanup(strbuf, spfbuf);
6880 htmlify_cleanup(name, htmlname);
6881 }
6882
6883 static void news_collect(tResource* resource, const char* src, size_t size)
6884 { tResourceCommandHandshake handshake = resource->handshake;
6885 tNntpRpsd* rpsd = (tNntpRpsd*) resource->rpsd;
6886 if ( (handshake == rchNntpGetGroups) ||
6887 ((handshake == rchNntpFetchHeader) && (rpsd->flags & nrfHeaderSeries)) )
6888 {
6889 char* text;
6890 size_t used, usable, needed;
6891 if (rpsd == NULL) goto res_coll; /* "should not happen" */
6892 text = rpsd->text; used = rpsd->used; usable = rpsd->usable;
6893 needed = zero2one(used) + size;
6894 if (needed > usable)
6895 { text = rpsd->text = memory_reallocate(text, needed, mapOther);
6896 rpsd->usable = needed;
6897 }
6898 my_memcpy(text + used - cond2bool(used > 0), src, size);
6899 text[needed - 1] = '\0';
6900 rpsd->used = needed;
6901 if (handshake == rchNntpGetGroups)
6902 { /* look for "\r\n" */
6903 char *temp = text, *remainder = text;
6904 tBoolean near_r = falsE;
6905 char ch;
6906 while ( (ch = *temp++) != '\0' )
6907 { if (ch == '\r') near_r = truE;
6908 else
6909 { if ( (ch == '\n') && (near_r) )
6910 { size_t len = (temp - remainder) - 2; /* "-2" for the CRLF */
6911 if (len > 0) /* actually have useful text */
6912 { remainder[len] = '\0'; /* overwrites the "\r" */
6913 news_handle_groupline(resource, remainder);
6914 }
6915 remainder = temp;
6916 }
6917 near_r = falsE;
6918 }
6919 }
6920 if (remainder > text) /* handled some grouplines */
6921 { if (*remainder == '\0') rpsd->used = 0; /* handled whole text */
6922 else
6923 { char* dest = text;
6924 rpsd->used = rpsd->used - (remainder - text);
6925 do { ch = *remainder++; *dest++ = ch; } while (ch != '\0');
6926 }
6927 push_to_main_res(resource, 1);
6928 }
6929 }
6930 }
6931 else { res_coll: resource_collect(resource, src, size); }
6932 }
6933
6934 #define news_read_flush \
6935 { if (read_buf_used > 0) \
6936 { news_collect(resource, read_buf, read_buf_used); read_buf_used = 0; } \
6937 }
6938
6939 #define news_read_append_ch(ch) \
6940 do \
6941 { if (read_buf_used >= NEWS_READ_BUFSIZE) news_read_flush \
6942 read_buf[read_buf_used++] = ch; \
6943 } while (0)
6944
6945 #define news_read_append(_addr, _count) \
6946 do \
6947 { const char* a = _addr; /* (evaluate it only once) */ \
6948 size_t c = _count; \
6949 while (c-- > 0) { char x = *a++; news_read_append_ch(x); } \
6950 } while (0)
6951
6952 static one_caller void news_read(tResource* resource)
6953 /* We treat NNTP connections in a special way because we must filter out the
6954 control data and construct/store the actual content data. */
6955 { static const char strDotMarker[] = "\r\n.\r\n";
6956 enum { NEWS_BUFSIZE = (4096 + 1), NEWS_READ_BUFSIZE = (2048 - 5) };
6957 char buf[NEWS_BUFSIZE], *ptr; /* temporary raw data buffer */
6958 const char* valueptr; /* NULL or inside buf[] */
6959 char read_buf[NEWS_READ_BUFSIZE + 5]; /* temporary content buffer */
6960 tTlHeaderState ths = resource->tlheaderstate;
6961 tResourceError news_error = reServerClosed;
6962 int fd = resource->cconn->fd, err = my_read_sock(fd, buf, NEWS_BUFSIZE - 1);
6963 size_t count, read_buf_used = 0;
6964 tBoolean do_push = falsE;
6965
6966 if (err <= 0) /* error (EOF is also an error here because it's unexpected) */
6967 { stop_reading:
6968 resource_set_error(resource, news_error);
6969 do_stop_reading:
6970 conn_remove(&(resource->cconn));
6971 resource->tlheaderstate = ths; /* CHECKME: set "= 0" instead? */
6972 news_read_flush
6973 rpsd_remove(resource);
6974 push_to_main_res(resource, 0);
6975 return;
6976 }
6977
6978 buf[err] = '\0'; /* for simplicity (e.g. sscanf()) */
6979 #if CONFIG_DEBUG
6980 debugmsg("news_read(): "); debugmsg(buf);
6981 if (buf[err - 1] != '\n') debugmsg(strNewline);
6982 #endif
6983 ptr = buf; valueptr = NULL; count = err;
6984 while (count-- > 0)
6985 { const char ch = *ptr++;
6986 switch (ths)
6987 { case 0:
6988 resource->server_status_code = 0; /*@fallthrough@*/
6989 case 1: case 2: /* parsing server status code at message start */
6990 if (my_isdigit(ch))
6991 { tServerStatusCode status = resource->server_status_code;
6992 status = 10 * status + ((tServerStatusCode) (ch - '0'));
6993 resource->server_status_code = status;
6994 ths++;
6995 if (ths == 3)
6996 { if ( (status < 100) || (status > 599) )
6997 { bad_news: news_error = reResponse; goto stop_reading; }
6998 valueptr = ptr;
6999 }
7000 }
7001 else goto bad_news;
7002 break;
7003 case 3: /* somewhere in a status response line */
7004 if (ch == '\r') ths = 4; /* possibly at end of line */
7005 break;
7006 case 4:
7007 if (ch == '\r') { /* don't change ths */ }
7008 else if (ch != '\n') ths = 3; /* not actually at end of line */
7009 else /* at end of line; look what to do */
7010 { ths = 0;
7011 switch (news_handshake(resource, &valueptr))
7012 { case 0: goto bad_news; /*@notreached@*/ break;
7013 case 1: /* read some "content" */
7014 resource->state = rsReading; ths = 7; break;
7015 case 2:
7016 start_writing: conn_set_write(resource->cconn);
7017 goto out; /*@notreached@*/ break;
7018 case 3:
7019 resource_complete:
7020 resource->state = rsComplete; goto do_stop_reading;
7021 /*@notreached@*/ break;
7022 }
7023 valueptr = NULL;
7024 }
7025 break;
7026 case 5: case 6: case 7: case 8: case 9: /* "content" */
7027 if (ch == strDotMarker[ths - 5]) /* looks like the end marker */
7028 { ths++;
7029 if (ths == 10) /* _is_ a _complete_ end marker */
7030 { ths = 0; news_read_flush
7031 if ( (resource->handshake == rchNntpFetchHeader) &&
7032 (((tNntpRpsd*)(resource->rpsd))->flags & nrfHeaderSeries) )
7033 { news_handle_article_index(resource);
7034 if (news_fetch_next_header(resource))
7035 goto start_writing; /* get the next article header */
7036 }
7037 goto resource_complete;
7038 }
7039 }
7040 else if (ths > 5)
7041 { /* It wasn't actually the end marker, it only looked like the
7042 beginning of the marker. */
7043 if ( (ths == 8) && (ch == '.') )
7044 { /* collapse double-dot to single-dot; how dotty... */
7045 ths = 7; /* FIXME: "=5" if there wasn't any actual text! */
7046 }
7047 news_read_append(strDotMarker, ths - 5);
7048 if (ch == '\r') ths = 6; /* possibly beginning of end marker */
7049 else { ths = 5; goto append_ch; } /* back to "normal content" mode */
7050 }
7051 else /* "normal content" */
7052 { append_ch:
7053 do_push = truE;
7054 news_read_append_ch(ch);
7055 if ( (ch == '\n') && (resource->cantent->kind == rckHtml) )
7056 news_read_append(strBr, 4); /* FIXME: do this in ...append_ch()! */
7057 }
7058 break;
7059 } /* switch (ths) */
7060 } /* while count */
7061 out:
7062 resource->tlheaderstate = ths;
7063 news_read_flush
7064 if (do_push) push_to_main_res(resource, 1);
7065 }
7066
7067 static void news_callback(tConnection* conn, tConnCbEventKind ccek)
7068 { tResource* resource = (tResource*) (conn->data);
7069 switch (ccek)
7070 { case ccekConnectSetup:
7071 if (conn->flags & cnfConnected) goto do_setup;
7072 else { conn_set_write(conn); push_to_main_res(resource, 1); }
7073 break;
7074 case ccekConnectWorked:
7075 do_setup:
7076 resource->state = rsMsgExchange; resource->handshake = rchConnected;
7077 start_reading:
7078 conn_set_read(conn); /* to get the "server ready" message */
7079 news_rpsd_prepare(resource); push_to_main_res(resource, 1);
7080 break;
7081 case ccekConnectFailed:
7082 resource_set_error(resource, conn_get_failre(conn));
7083 stopnpush: conn_remove(&(resource->cconn)); push_to_main_res(resource,0);
7084 break;
7085 case ccekRead: news_read(resource); break;
7086 case ccekWrite:
7087 switch (conn_write_writedata(conn))
7088 { case 0:
7089 resource_set_error(resource, reServerClosed); goto stopnpush;
7090 /*@notreached@*/ break;
7091 case 1: break; /* keep writing */
7092 case 2: goto start_reading; /*@notreached@*/ break;
7093 }
7094 break;
7095 default: conn_bug(conn, ccek); break;
7096 }
7097 }
7098
7099 #endif /* #if OPTION_NEWS */
7100
7101
7102 /** Non-local resources: finger */
7103
7104 #if CONFIG_FINGER
7105
7106 static one_caller void finger_read(tResource* resource)
7107 { int fd = resource->cconn->fd;
7108 char* dest;
7109 size_t size;
7110 int err;
7111 resource_provide_room(resource, &dest, &size, resource->bytecount, 2);
7112 err = my_read_sock(fd, dest, size);
7113 if (err <= 0) /* error or EOF */
7114 { conn_remove(&(resource->cconn));
7115 if (err == 0) resource->state = rsComplete;
7116 else resource_set_error(resource, reServerClosed);
7117 push_to_main_res(resource, 0);
7118 }
7119 else
7120 { resource_record(resource, dest, err);
7121 push_to_main_res(resource, 1);
7122 }
7123 }
7124
7125 static void finger_callback(tConnection* conn, tConnCbEventKind ccek)
7126 { tResource* resource = (tResource*) (conn->data);
7127 const char* username;
7128 switch (ccek)
7129 { case ccekConnectSetup:
7130 conn_set_write(conn);
7131 if (conn->flags & cnfConnected) goto set_req;
7132 else push_to_main_res(resource, 1); /* say "Connecting to..." */
7133 break;
7134 case ccekConnectWorked:
7135 set_req: resource->state = rsMsgExchange;
7136 username = resource->uri_data->username;
7137 if (username == NULL) conn_set_writestr(conn, my_strdup(strCrlf));
7138 else
7139 { char* spfbuf;
7140 my_spf(NULL, 0, &spfbuf, "%s\r\n", username);
7141 conn_set_writestr(conn, my_spf_use(spfbuf));
7142 }
7143 push_to_main_res(resource, 1);
7144 break;
7145 case ccekConnectFailed:
7146 resource_set_error(resource, conn_get_failre(conn));
7147 stopnpush: conn_remove(&(resource->cconn)); push_to_main_res(resource,0);
7148 break;
7149 case ccekRead: finger_read(resource); break;
7150 case ccekWrite:
7151 switch (conn_write_writedata(conn))
7152 { case 0:
7153 resource_set_error(resource, reServerClosed); goto stopnpush;
7154 /*@notreached@*/ break;
7155 case 1: break; /* keep writing */
7156 case 2: /* all written, start reading */
7157 conn_set_read(conn); resource->state = rsReading;
7158 push_to_main_res(resource, 1); break;
7159 }
7160 break;
7161 default: conn_bug(conn, ccek); break;
7162 }
7163 }
7164
7165 #endif /* #if CONFIG_FINGER */
7166
7167
7168 /** Non-local resources: General */
7169
7170 static tBoolean start_request(tResourceRequest* request,
7171 tCachedHostInformation* hostinfo)
7172 /* starts (prepares) a network request; returns want-it-back info */
7173 { const tUriData* uri_data = request->uri_data;
7174 const tResourceProtocol protocol = uri_data->rp;
7175 const tConfigProxy* proxy = request->proxy;
7176 const tResourceRequestFlags rrf = request->flags;
7177 tResource* resource;
7178 tPortnumber connport, textual_portnumber;
7179 tConnection* conn;
7180 tConnCbFunc cb;
7181
7182 switch (protocol)
7183 { case rpHttp:
7184 #if OPTION_TLS
7185 case rpHttps:
7186 #endif
7187 cb = http_callback; break;
7188 #if CONFIG_FTP
7189 case rpFtp:
7190 #if OPTION_TLS
7191 case rpFtps:
7192 #endif
7193 cb = ftp_control_callback; break;
7194 #endif
7195 #if OPTION_NEWS
7196 case rpNntp: cb = news_callback; break;
7197 #endif
7198 #if CONFIG_FINGER
7199 case rpFinger: cb = finger_callback; break;
7200 #endif
7201 #if CONFIG_GOPHER
7202 case rpGopher: cb = gopher_callback; break;
7203 #endif
7204 default: /* "should not happen" */
7205 resource_request_set_error(request, reProtocol);
7206 failed: return(falsE); /*@notreached@*/ break;
7207 }
7208
7209 textual_portnumber = request->uri_data->portnumber;
7210 if ( (proxy != NULL) && (proxy->proxy_hostname != NULL) )
7211 connport = proxy->proxy_portnumber;
7212 else connport = textual_portnumber;
7213
7214 resource = resource_create(request, NULL, UNKNOWN_CONTENTLENGTH, rckUnknown,
7215 rsConnecting);
7216 resource->proxy = proxy;
7217 resource->actual_hppi = hppi_lookup(hostinfo, connport, protocol, truE);
7218
7219 { const char* hostname = request->uri_data->hostname;
7220 tCachedHostInformation* textual_host = hostinfo_lookup(hostname);
7221 if (textual_host == NULL) textual_host = hostinfo_create(hostname);
7222 resource->textual_hppi = hppi_lookup(textual_host, textual_portnumber,
7223 protocol, truE);
7224 }
7225
7226 if (rrf & rrfPost) resource->flags |= rfPost;
7227 if (rrf & rrfIsRedirection) resource->flags |= rfIsRedirection;
7228 if (rrf & rrfIsEmbedded) resource->flags |= rfIsEmbedded;
7229 if (request->action == rraEnforcedReload) resource->flags |= rfIsEnforced;
7230
7231 conn = resource->cconn = conn_create(-1, cb, resource, truE,
7232 resource->actual_hppi, protocol);
7233 if (conn_connect(conn)) conn_callback(conn, ccekConnectSetup);
7234 else
7235 { tResourceError re = conn->prelire;
7236 resource_request_set_error(request, re ? re : reConnectionFailureDefault);
7237 goto failed;
7238 }
7239 return(truE);
7240 }
7241
7242
7243 /** Resource handling basics/interface */
7244
7245 #if OPTION_THREADING
7246
7247 static void request_dns_callback(void*, tDhmNotificationFlags); /* prototype */
7248
7249 static void request_dns_vanisher(void* _request, tDhmNotificationFlags flags)
7250 { if (flags & dhmnfRemoval) /* "should" be true */
7251 { /* The request goes away, so it won't be interested in DNS results. */
7252 tResourceRequest* request = (tResourceRequest*) _request;
7253 tCachedHostInformation* hostinfo = request->lookup;
7254 if (hostinfo != NULL) /* "should" be true */
7255 dhm_notification_off(hostinfo, request_dns_callback, request);
7256 }
7257 }
7258
7259 static void request_dns_callback(void* _request, tDhmNotificationFlags flags)
7260 { if (flags & dhmnfOnce) /* "should" be true */
7261 { tResourceRequest* request = (tResourceRequest*) _request;
7262 tCachedHostInformation* hostinfo = request->lookup;
7263 tBoolean want_it_back;
7264 dhm_notification_off(request, request_dns_vanisher, request);
7265 if (hostinfo == NULL) /* "should not happen" */
7266 { dns_failed: resource_request_set_error(request, reDns);
7267 want_it_back = falsE; goto out_once;
7268 }
7269 request->lookup = NULL;
7270 if (hostinfo->num_sockaddrs <= 0) goto dns_failed;
7271 want_it_back = start_request(request, hostinfo);
7272 out_once:
7273 push_to_main_req(request, boolean2bool(want_it_back));
7274 }
7275 }
7276
7277 static void resource_dns_handler(__sunused void* data __cunused,
7278 __sunused tFdObservationFlags flags __cunused)
7279 /* The DNS thread finished a lookup. */
7280 { static tDnsLookup* dns_lookup;
7281 static unsigned char count = 0;
7282 tCachedHostInformation* hostinfo;
7283 int err = my_read_pipe(fd_dns2resource_read, ((char*)(&dns_lookup)) + count,
7284 sizeof(dns_lookup) - count);
7285 if (err <= 0) return;
7286 count += err;
7287 if (count < sizeof(dns_lookup)) return; /* not yet a whole pointer */
7288 count = 0; /* for the next round */
7289 hostinfo = dns_lookup->hostinfo; hostinfo->flags &=~chifAddressLookupRunning;
7290 postprocess_dns_lookup(dns_lookup); dhm_notify(hostinfo, dhmnfOnce);
7291 }
7292
7293 #endif /* #if OPTION_THREADING */
7294
7295 void resource_request_start(tResourceRequest* request)
7296 { tBoolean want_it_back = falsE;
7297 tResourceProtocol rp = request->uri_data->rp;
7298 switch (rp)
7299 { case rpLocal: fetch_local(request); break;
7300 case rpAbout: fetch_about(request); break;
7301 #if OPTION_LOCAL_CGI
7302 case rpLocalCgi: want_it_back = start_local_cgi(request); break;
7303 #endif
7304 default:
7305 if (resource_lookup(request) != NULL) { /* found in cache, done */ }
7306 #if OPTION_POP
7307 else if (is_poplike(rp)) /* e-mail access is special */
7308 want_it_back = pop_fetch(request);
7309 #endif
7310 else /* some network activity might be necessary :-) */
7311 { tCachedHostInformation* hostinfo;
7312 const tResourceRequestAction action = request->action;
7313 const tHostAddressLookupFlags half = ( (action == rraEnforcedReload)
7314 #if CONFIG_CUSTOM_CONN
7315 || (action == rraCustomConn)
7316 #endif
7317 ) ? halfEnforcedReload : halfNone;
7318 const tHostAddressLookupResult res = lookup_hostaddress(request,
7319 &hostinfo, half);
7320 if (res == halrFine) want_it_back = start_request(request, hostinfo);
7321 #if CONFIG_ASYNC_DNS
7322 else if (res == halrLookupRunning)
7323 { request->state = rrsDnsLookup; request->lookup = hostinfo;
7324 dhm_notification_setup(hostinfo, request_dns_callback, request,
7325 dhmnfOnce, dhmnSet);
7326 dhm_notification_setup(request, request_dns_vanisher, request,
7327 dhmnfRemoval, dhmnSet);
7328 want_it_back = truE;
7329 }
7330 #endif
7331 else resource_request_set_error(request, reDns);
7332 }
7333 break;
7334 }
7335 push_to_main_req(request, boolean2bool(want_it_back));
7336 }
7337
7338 #if CONFIG_ASYNC_DNS
7339 static void stop_request_dns_lookup(tResourceRequest* request)
7340 { if (request->state == rrsDnsLookup)
7341 { tCachedHostInformation* hostinfo = request->lookup;
7342 if (hostinfo != NULL) /* "should" be true */
7343 { dhm_notification_off(hostinfo, request_dns_callback, request);
7344 dhm_notification_off(request, request_dns_vanisher, request);
7345 request->lookup = NULL;
7346 }
7347 request->state = rrsStopped;
7348 }
7349 }
7350 #else
7351 #define stop_request_dns_lookup(request) do { } while (0)
7352 #endif
7353
7354 void resource_request_stop(tResourceRequest* request)
7355 { tResource* resource = request->resource;
7356 stop_request_dns_lookup(request);
7357 if (resource != NULL) resource_stop(resource);
7358 push_to_main_req(request, 0);
7359 }
7360
7361 #if CONFIG_CUSTOM_CONN
7362
7363 void resource_custom_conn_start(tResource* resource, unsigned char what,
7364 const void* whatever)
7365 { const char* cmd;
7366 unsigned char x;
7367 tResourceError ftp_error;
7368 tResourceCommandHandshake handshake;
7369 #if OPTION_TLS
7370 tFtpTlsMethod ftm;
7371 #endif
7372 switch (what)
7373 { case 0:
7374 resource->handshake = rchFtpCustom; cmd = (const char*) whatever;
7375 ftp_control_do_start_command(resource, my_strdup(cmd)); break;
7376 case 1:
7377 resource->handshake = handshake = MY_POINTER_TO_INT(whatever);
7378 try_1:
7379 x = ftp_control_start_command(resource, &ftp_error);
7380 /* CHECKME: handle errors; at least unbusify! */
7381 if (0) { handle_ftp_error: {} }
7382 break;
7383 #if OPTION_TLS
7384 case 2:
7385 ftm = MY_POINTER_TO_INT(whatever);
7386 if (ftm == ftmAutodetect) ftm = calc_ftp_tls_method(resource);
7387 switch (ftm)
7388 { case ftmAutodetect: case ftmAuthTls: case ftmAuthTlsDataclear:
7389 handshake = rchFtpTlsAuthTls; break;
7390 case ftmAuthSsl: handshake = rchFtpTlsAuthSsl; break;
7391 default: ftp_error = reHandshake; goto handle_ftp_error;
7392 /*@notreached@*/ break;
7393 }
7394 resource->handshake = handshake; ftps_ftm(resource) = ftm;
7395 goto try_1; /*@notreached@*/ break;
7396 #endif
7397 }
7398 }
7399
7400 #endif
7401
7402
7403 /** Initialization */
7404
7405 #ifndef IPPORT_HTTP
7406 #define IPPORT_HTTP 80
7407 #endif
7408 #ifndef IPPORT_FTP
7409 #define IPPORT_FTP 21
7410 #endif
7411 #ifndef IPPORT_FINGER
7412 #define IPPORT_FINGER 79
7413 #endif
7414 #ifndef IPPORT_GOPHER
7415 #define IPPORT_GOPHER 70
7416 #endif
7417 #ifndef IPPORT_NNTP
7418 #define IPPORT_NNTP 119
7419 #endif
7420 #ifndef IPPORT_POP3
7421 #define IPPORT_POP3 110
7422 #endif
7423 #ifndef IPPORT_HTTPS
7424 #define IPPORT_HTTPS 443
7425 #endif
7426
7427 static tPortnumber __init __calculate_portnumber(const char* name,
7428 tPortnumber defaultport)
7429 {
7430 #if CONFIG_PLATFORM != 1
7431 const struct servent* ent = getservbyname(name, strTcp);
7432 if (ent != NULL) return((tPortnumber) ent->s_port);
7433 else
7434 #endif
7435 { return((tPortnumber) htons(defaultport)); }
7436 }
7437
7438 #define calculate_portnumber(rp, defaultport) \
7439 __calculate_portnumber(rp_data[rp].scheme, defaultport)
7440
7441 static void __init setup_pipe(/*@out@*/ int* reader, /*@out@*/ int* writer)
7442 { int fd_pair[2];
7443 unsigned char count;
7444 if (my_pipe(fd_pair) != 0) fatal_error(errno, _(strResourceError[rePipe]));
7445 for (count = 0; count <= 1; count++)
7446 { int fd = fd_pair[count];
7447 if (!fd_is_observable(fd)) fatal_tmofd(fd);
7448 make_fd_cloexec(fd);
7449 }
7450 *reader = fd_pair[0]; *writer = fd_pair[1];
7451 }
7452
7453 #if OPTION_THREADING
7454
7455 static one_caller void does_not_return __init fatal_threading(const int err)
7456 /* fun with annotations :-) */
7457 { fatal_error(err, _("can't create thread"));
7458 }
7459
7460 #endif
7461
7462 #if OPTION_THREADING == 1
7463
7464 static void __init create_thread(void* (*fn)(void* dummy), void* data)
7465 { pthread_t dummy_id;
7466 pthread_attr_t thread_attr;
7467 int err;
7468 if ( ( (err = pthread_attr_init(&thread_attr)) != 0 ) ||
7469 ( (err = pthread_attr_setdetachstate(&thread_attr,
7470 PTHREAD_CREATE_DETACHED)) != 0 ) ||
7471 ( (err = pthread_create(&dummy_id, &thread_attr, fn, data)) != 0 ) )
7472 { fatal_threading(err);
7473 /* (SUSv3 says <err> is error number - <errno> not involved here) */
7474 }
7475 }
7476
7477 #elif OPTION_THREADING == 2
7478
7479 static int __init create_thread(int (*fn)(void* dummy), void* data)
7480 { void* childstack = __memory_allocate(102400, mapPermanent);
7481 int err = clone(fn, childstack + 102400 - 128, CLONE_VM | CLONE_FS |
7482 CLONE_FILES | CLONE_SIGHAND, data);
7483 /* CHECKME: we don't want CLONE_SIGHAND!? */
7484 if (err == -1) fatal_threading(errno);
7485 return(err);
7486 }
7487
7488 #endif /* #if OPTION_THREADING... */
7489
7490 #if USE_LWIP
7491 static tBoolean lwip_timeout_handler(/*@out@*/ int* _msec)
7492 { *_msec = 100; return(truE); }
7493 #endif
7494
7495 one_caller void __init resource_initialize(void)
7496 /* initializes the resource handling; note that this is executed in the main
7497 thread - it _creates_ the other thread(s) at the end. */
7498 { const struct protoent* proto;
7499 #if OPTION_THREADING
7500 static tDnsThreadData dns_thread_data;
7501 #endif
7502
7503 /* Get protocol and port numbers */
7504
7505 #if CONFIG_PLATFORM != 1
7506 proto = getprotobyname(strTcp);
7507 if (proto != NULL) ipprotocolnumber_tcp = proto->p_proto;
7508 /* "else": use the standard value IPPROTO_TCP */
7509 #endif
7510
7511 #if CONFIG_PLATFORM != 1
7512 setservent(1);
7513 #endif
7514 portnumber_http = calculate_portnumber(rpHttp, IPPORT_HTTP);
7515 portnumber_ftp = calculate_portnumber(__rpFtp, IPPORT_FTP);
7516 portnumber_finger = calculate_portnumber(__rpFinger, IPPORT_FINGER);
7517 portnumber_cvs = __calculate_portnumber("cvspserver", 2401);
7518 portnumber_gopher = calculate_portnumber(__rpGopher, IPPORT_GOPHER);
7519 portnumber_nntp = __calculate_portnumber(strNntp, IPPORT_NNTP);
7520 portnumber_pop3 = __calculate_portnumber(strPop3, IPPORT_POP3);
7521 portnumber_https = calculate_portnumber(__rpHttps, IPPORT_HTTPS);
7522 #if CONFIG_PLATFORM != 1
7523 endservent();
7524 #endif
7525
7526 /* Initialize some data structures */
7527
7528 my_memclr_arr(rc_head); my_memclr_arr(chi_head);
7529 my_memclr_arr(sockaddr_entry_head);
7530 #if OPTION_BUILTIN_DNS
7531 my_memclr_var(dns_data);
7532 #endif
7533 #if USE_LWIP
7534 timeout_register(lwip_timeout_handler);
7535 #endif
7536
7537 /* Initialize cookies */
7538
7539 #if OPTION_COOKIES
7540 cookie_initialize();
7541 #endif
7542
7543 /* Setup some file descriptors */
7544
7545 #if OPTION_THREADING
7546 setup_pipe(&fd_dns2resource_read, &fd_dns2resource_write);
7547 setup_pipe(&fd_resource2dns_read, &fd_resource2dns_write);
7548 fd_observe(fd_dns2resource_read, resource_dns_handler, NULL, fdofRead);
7549 dns_thread_data.reader = fd_resource2dns_read;
7550 dns_thread_data.writer = fd_dns2resource_write;
7551 #if NEED_FD_REGISTER
7552 /* the register isn't thread-safe, so we do the lookup here... */
7553 (void) fd_register_lookup(&(dns_thread_data.reader));
7554 (void) fd_register_lookup(&(dns_thread_data.writer));
7555 #endif
7556 #endif
7557
7558 #if CAN_HANDLE_SIGNALS
7559 setup_pipe(&fd_any2main_read, &fd_any2main_write);
7560 #endif
7561
7562 #if CONFIG_TG == TG_XCURSES
7563 setup_pipe(&fd_xcurses2main_read, &fd_xcurses2main_write);
7564 if (is_environed) fd_keyboard_input = fd_xcurses2main_read;
7565 #endif
7566
7567 /* Setup the DNS thread */
7568
7569 #if OPTION_THREADING == 1
7570 create_thread(dns_handler_thread, &dns_thread_data);
7571 #elif OPTION_THREADING == 2
7572 { int err;
7573 pid_main = getpid();
7574 err = create_thread(dns_handler_thread, &dns_thread_data);
7575 if (err > 0) pid_dns = err;
7576 }
7577 #endif
7578 }
7579
7580 void resource_quit(void)
7581 { /* IMPLEMENTME: remove all connections, so that the dissolvers can run? Only
7582 if no fatal error happened? Only if this doesn't need "much time", so
7583 users don't have to wait when quitting? Only if the dissolver is
7584 "important", e.g. for POP3 or TLS? ... */
7585 #if OPTION_TLS
7586 tls_deinitialize();
7587 #endif
7588 #if OPTION_THREADING == 2
7589 if (pid_dns > 0) (void) kill(pid_dns, SIGKILL); /* let's be rude :-) */
7590 #endif
7591 }
7592