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   { /* { "&quot;", 6, '"'  }, */ /* #34 */
2929        { "&amp;",  5, '&'  },    /* #38 */
2930     /* { "&#39;",  5, '\'' }, */ /* #39 */
2931        { "&lt;",   4, '<'  },    /* #60 */
2932        { "&gt;",   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