Boing.Boom.Tschak. More fixes in line with [0ef07198,ee4de6e4]
[s-mailx.git] / socket.c
blob50f496ceb3177091458b06e8ff660762fd3c99aa
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Socket operations.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
32 #undef n_FILE
33 #define n_FILE socket
35 #ifndef HAVE_AMALGAMATION
36 # include "nail.h"
37 #endif
39 EMPTY_FILE()
40 #ifdef HAVE_SOCKETS
41 #include <sys/socket.h>
43 #include <netdb.h>
45 #include <netinet/in.h>
47 #ifdef HAVE_ARPA_INET_H
48 # include <arpa/inet.h>
49 #endif
51 #ifdef HAVE_XSSL
52 # include <openssl/err.h>
53 # include <openssl/rand.h>
54 # include <openssl/ssl.h>
55 # include <openssl/x509v3.h>
56 # include <openssl/x509.h>
57 #endif
59 /* Write to socket fd, restarting on EINTR, unless anything is written */
60 static long a_sock_xwrite(int fd, char const *data, size_t sz);
62 static long
63 a_sock_xwrite(int fd, char const *data, size_t sz)
65 long rv = -1, wo;
66 size_t wt = 0;
67 NYD_ENTER;
69 do {
70 if ((wo = write(fd, data + wt, sz - wt)) < 0) {
71 if (errno == EINTR)
72 continue;
73 else
74 goto jleave;
76 wt += wo;
77 } while (wt < sz);
78 rv = (long)sz;
79 jleave:
80 NYD_LEAVE;
81 return rv;
84 FL int
85 sclose(struct sock *sp)
87 int i;
88 NYD_ENTER;
90 i = sp->s_fd;
91 sp->s_fd = -1;
92 /* TODO NOTE: we MUST NOT close the descriptor 0 here...
93 * TODO of course this should be handled in a VMAILFS->open() .s_fd=-1,
94 * TODO but unfortunately it isn't yet */
95 if (i <= 0)
96 i = 0;
97 else {
98 if (sp->s_onclose != NULL)
99 (*sp->s_onclose)();
100 if (sp->s_wbuf != NULL)
101 free(sp->s_wbuf);
102 # ifdef HAVE_XSSL
103 if (sp->s_use_ssl) {
104 void *s_ssl = sp->s_ssl;
106 sp->s_ssl = NULL;
107 sp->s_use_ssl = 0;
108 while (!SSL_shutdown(s_ssl)) /* XXX proper error handling;signals! */
110 SSL_free(s_ssl);
112 # endif
113 i = close(i);
115 NYD_LEAVE;
116 return i;
119 FL enum okay
120 swrite(struct sock *sp, char const *data)
122 enum okay rv;
123 NYD2_ENTER;
125 rv = swrite1(sp, data, strlen(data), 0);
126 NYD2_LEAVE;
127 return rv;
130 FL enum okay
131 swrite1(struct sock *sp, char const *data, int sz, int use_buffer)
133 enum okay rv = STOP;
134 int x;
135 NYD2_ENTER;
137 if (use_buffer > 0) {
138 int di;
140 if (sp->s_wbuf == NULL) {
141 sp->s_wbufsize = 4096;
142 sp->s_wbuf = smalloc(sp->s_wbufsize);
143 sp->s_wbufpos = 0;
145 while (sp->s_wbufpos + sz > sp->s_wbufsize) {
146 di = sp->s_wbufsize - sp->s_wbufpos;
147 sz -= di;
148 if (sp->s_wbufpos > 0) {
149 memcpy(sp->s_wbuf + sp->s_wbufpos, data, di);
150 rv = swrite1(sp, sp->s_wbuf, sp->s_wbufsize, -1);
151 } else
152 rv = swrite1(sp, data, sp->s_wbufsize, -1);
153 if (rv != OKAY)
154 goto jleave;
155 data += di;
156 sp->s_wbufpos = 0;
158 if (sz == sp->s_wbufsize) {
159 rv = swrite1(sp, data, sp->s_wbufsize, -1);
160 if (rv != OKAY)
161 goto jleave;
162 } else if (sz) {
163 memcpy(sp->s_wbuf+ sp->s_wbufpos, data, sz);
164 sp->s_wbufpos += sz;
166 rv = OKAY;
167 goto jleave;
168 } else if (use_buffer == 0 && sp->s_wbuf != NULL && sp->s_wbufpos > 0) {
169 x = sp->s_wbufpos;
170 sp->s_wbufpos = 0;
171 if ((rv = swrite1(sp, sp->s_wbuf, x, -1)) != OKAY)
172 goto jleave;
174 if (sz == 0) {
175 rv = OKAY;
176 goto jleave;
179 # ifdef HAVE_XSSL
180 if (sp->s_use_ssl) {
181 jssl_retry:
182 x = SSL_write(sp->s_ssl, data, sz);
183 if (x < 0) {
184 switch (SSL_get_error(sp->s_ssl, x)) {
185 case SSL_ERROR_WANT_READ:
186 case SSL_ERROR_WANT_WRITE:
187 goto jssl_retry;
190 } else
191 # endif
193 x = a_sock_xwrite(sp->s_fd, data, sz);
195 if (x != sz) {
196 char o[512];
197 snprintf(o, sizeof o, "%s write error",
198 (sp->s_desc ? sp->s_desc : "socket"));
199 # ifdef HAVE_XSSL
200 if (sp->s_use_ssl)
201 ssl_gen_err("%s", o);
202 else
203 # endif
204 n_perr(o, 0);
205 if (x < 0)
206 sclose(sp);
207 rv = STOP;
208 goto jleave;
210 rv = OKAY;
211 jleave:
212 NYD2_LEAVE;
213 return rv;
216 static sigjmp_buf __sopen_actjmp; /* TODO someday, we won't need it no more */
217 static int __sopen_sig; /* TODO someday, we won't need it no more */
218 static void
219 __sopen_onsig(int sig) /* TODO someday, we won't need it no more */
221 NYD_X; /* Signal handler */
222 if (__sopen_sig == -1) {
223 fprintf(stderr, _("\nInterrupting this operation may turn "
224 "the DNS resolver unusable\n"));
225 __sopen_sig = 0;
226 } else {
227 __sopen_sig = sig;
228 siglongjmp(__sopen_actjmp, 1);
232 FL bool_t
233 sopen(struct sock *sp, struct url *urlp) /* TODO sighandling; refactor */
235 # ifdef HAVE_SO_SNDTIMEO
236 struct timeval tv;
237 # endif
238 # ifdef HAVE_SO_LINGER
239 struct linger li;
240 # endif
241 # ifdef HAVE_GETADDRINFO
242 char hbuf[NI_MAXHOST];
243 struct addrinfo hints, *res0 = NULL, *res;
244 # else
245 struct sockaddr_in servaddr;
246 struct in_addr **pptr;
247 struct hostent *hp;
248 struct servent *ep;
249 # endif
250 sighandler_type volatile ohup, oint;
251 char const * volatile serv;
252 int volatile sofd = -1, errval;
253 NYD_ENTER;
255 n_UNINIT(errval, 0);
257 /* Connect timeouts after 30 seconds XXX configurable */
258 # ifdef HAVE_SO_SNDTIMEO
259 tv.tv_sec = 30;
260 tv.tv_usec = 0;
261 # endif
262 serv = (urlp->url_port != NULL) ? urlp->url_port : urlp->url_proto;
264 if (options & OPT_VERB)
265 n_err(_("Resolving host %s:%s ... "), urlp->url_host.s, serv);
267 /* Signal handling (in respect to __sopen_sig dealing) is heavy, but no
268 * healing until v15.0 and i want to end up with that functionality */
269 hold_sigs();
270 __sopen_sig = 0;
271 ohup = safe_signal(SIGHUP, &__sopen_onsig);
272 oint = safe_signal(SIGINT, &__sopen_onsig);
273 if (sigsetjmp(__sopen_actjmp, 0)) {
274 jpseudo_jump:
275 n_err("%s\n",
276 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
277 if (sofd >= 0) {
278 close(sofd);
279 sofd = -1;
281 goto jjumped;
283 rele_sigs();
285 # ifdef HAVE_GETADDRINFO
286 for (;;) {
287 memset(&hints, 0, sizeof hints);
288 hints.ai_socktype = SOCK_STREAM;
289 __sopen_sig = -1;
290 errval = getaddrinfo(urlp->url_host.s, serv, &hints, &res0);
291 if (__sopen_sig != -1) {
292 __sopen_sig = SIGINT;
293 goto jpseudo_jump;
295 __sopen_sig = 0;
296 if (errval == 0)
297 break;
299 if (options & OPT_VERB)
300 n_err(_("failed\n"));
301 n_err(_("Lookup of %s:%s failed: %s\n"),
302 urlp->url_host.s, serv, gai_strerror(errval));
304 /* Error seems to depend on how "smart" the /etc/service code is: is it
305 * "able" to state whether the service as such is NONAME or does it only
306 * check for the given ai_socktype.. */
307 if (errval == EAI_NONAME || errval == EAI_SERVICE) {
308 if (serv == urlp->url_proto &&
309 (serv = n_servbyname(urlp->url_proto, NULL)) != NULL &&
310 *serv != '\0') {
311 n_err(_(" Trying standard protocol port %s\n"), serv);
312 n_err(_(" If that succeeds consider including the "
313 "port in the URL!\n"));
314 continue;
316 if (serv != urlp->url_port)
317 n_err(_(" Including a port number in the URL may "
318 "circumvent this problem\n"));
320 assert(sofd == -1);
321 errval = 0;
322 goto jjumped;
324 if (options & OPT_VERB)
325 n_err(_("done\n"));
327 for (res = res0; res != NULL && sofd < 0; res = res->ai_next) {
328 if (options & OPT_VERB) {
329 if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof hbuf,
330 NULL, 0, NI_NUMERICHOST))
331 memcpy(hbuf, "unknown host", sizeof("unknown host"));
332 n_err(_("%sConnecting to %s:%s ... "),
333 (res == res0 ? n_empty : "\n"), hbuf, serv);
336 sofd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
337 if (sofd >= 0) {
338 # ifdef HAVE_SO_SNDTIMEO
339 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
340 # endif
341 if (connect(sofd, res->ai_addr, res->ai_addrlen)) {
342 errval = errno;
343 close(sofd);
344 sofd = -1;
349 jjumped:
350 if (res0 != NULL) {
351 freeaddrinfo(res0);
352 res0 = NULL;
355 # else /* HAVE_GETADDRINFO */
356 if (serv == urlp->url_proto) {
357 if ((ep = getservbyname(n_UNCONST(serv), "tcp")) != NULL)
358 urlp->url_portno = ntohs(ep->s_port);
359 else {
360 if (options & OPT_VERB)
361 n_err(_("failed\n"));
362 if ((serv = n_servbyname(urlp->url_proto, &urlp->url_portno)) != NULL)
363 n_err(_(" Unknown service: %s\n"), urlp->url_proto);
364 n_err(_(" Trying standard protocol port %s\n"), serv);
365 n_err(_(" If that succeeds consider including the "
366 "port in the URL!\n"));
367 else {
368 n_err(_(" Unknown service: %s\n"), urlp->url_proto);
369 n_err(_(" Including a port number in the URL may "
370 "circumvent this problem\n"));
371 assert(sofd == -1 && errval == 0);
372 goto jjumped;
377 __sopen_sig = -1;
378 hp = gethostbyname(urlp->url_host.s);
379 if (__sopen_sig != -1) {
380 __sopen_sig = SIGINT;
381 goto jpseudo_jump;
383 __sopen_sig = 0;
385 if (hp == NULL) {
386 char const *emsg;
388 if (options & OPT_VERB)
389 n_err(_("failed\n"));
390 switch (h_errno) {
391 case HOST_NOT_FOUND: emsg = N_("host not found"); break;
392 default:
393 case TRY_AGAIN: emsg = N_("(maybe) try again later"); break;
394 case NO_RECOVERY: emsg = N_("non-recoverable server error"); break;
395 case NO_DATA: emsg = N_("valid name without IP address"); break;
397 n_err(_("Lookup of %s:%s failed: %s\n"),
398 urlp->url_host.s, serv, V_(emsg));
399 goto jjumped;
400 } else if (options & OPT_VERB)
401 n_err(_("done\n"));
403 pptr = (struct in_addr**)hp->h_addr_list;
404 if ((sofd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
405 n_perr(_("could not create socket"), 0);
406 assert(sofd == -1 && errval == 0);
407 goto jjumped;
410 memset(&servaddr, 0, sizeof servaddr);
411 servaddr.sin_family = AF_INET;
412 servaddr.sin_port = htons(urlp->url_portno);
413 memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
414 if (options & OPT_VERB)
415 n_err(_("%sConnecting to %s:%d ... "),
416 n_empty, inet_ntoa(**pptr), (int)urlp->url_portno);
417 # ifdef HAVE_SO_SNDTIMEO
418 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
419 # endif
420 if (connect(sofd, (struct sockaddr*)&servaddr, sizeof servaddr)) {
421 errval = errno;
422 close(sofd);
423 sofd = -1;
425 jjumped:
426 # endif /* !HAVE_GETADDRINFO */
428 hold_sigs();
429 safe_signal(SIGINT, oint);
430 safe_signal(SIGHUP, ohup);
431 rele_sigs();
433 if (sofd < 0) {
434 if (errval != 0) {
435 errno = errval;
436 n_perr(_("Could not connect"), 0);
438 goto jleave;
441 if (options & OPT_VERB)
442 n_err(_("connected.\n"));
444 /* And the regular timeouts XXX configurable */
445 # ifdef HAVE_SO_SNDTIMEO
446 tv.tv_sec = 42;
447 tv.tv_usec = 0;
448 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
449 (void)setsockopt(sofd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
450 # endif
451 # ifdef HAVE_SO_LINGER
452 li.l_onoff = 1;
453 li.l_linger = 42;
454 (void)setsockopt(sofd, SOL_SOCKET, SO_LINGER, &li, sizeof li);
455 # endif
457 memset(sp, 0, sizeof *sp);
458 sp->s_fd = sofd;
460 /* SSL/TLS upgrade? */
461 # ifdef HAVE_SSL
462 if (urlp->url_needs_tls) {
463 hold_sigs();
464 ohup = safe_signal(SIGHUP, &__sopen_onsig);
465 oint = safe_signal(SIGINT, &__sopen_onsig);
466 if (sigsetjmp(__sopen_actjmp, 0)) {
467 n_err(_("%s during SSL/TLS handshake\n"),
468 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
469 goto jsclose;
471 rele_sigs();
473 if (ssl_open(urlp, sp) != OKAY) {
474 jsclose:
475 sclose(sp);
476 sofd = -1;
479 hold_sigs();
480 safe_signal(SIGINT, oint);
481 safe_signal(SIGHUP, ohup);
482 rele_sigs();
484 # endif /* HAVE_SSL */
486 jleave:
487 /* May need to bounce the signal to the lex.c trampoline (or wherever) */
488 if (__sopen_sig != 0) {
489 sigset_t cset;
490 sigemptyset(&cset);
491 sigaddset(&cset, __sopen_sig);
492 sigprocmask(SIG_UNBLOCK, &cset, NULL);
493 n_raise(__sopen_sig);
495 NYD_LEAVE;
496 return (sofd >= 0);
499 FL int
500 (sgetline)(char **line, size_t *linesize, size_t *linelen, struct sock *sp
501 n_MEMORY_DEBUG_ARGS)
503 int rv;
504 size_t lsize;
505 char *lp_base, *lp;
506 NYD2_ENTER;
508 lsize = *linesize;
509 lp_base = *line;
510 lp = lp_base;
512 if (sp->s_rsz < 0) {
513 sclose(sp);
514 rv = sp->s_rsz;
515 goto jleave;
518 do {
519 if (lp_base == NULL || PTRCMP(lp, >, lp_base + lsize - 128)) {
520 size_t diff = PTR2SIZE(lp - lp_base);
521 *linesize = (lsize += 256); /* XXX magic */
522 *line = lp_base = (n_realloc)(lp_base, lsize n_MEMORY_DEBUG_ARGSCALL);
523 lp = lp_base + diff;
526 if (sp->s_rbufptr == NULL ||
527 PTRCMP(sp->s_rbufptr, >=, sp->s_rbuf + sp->s_rsz)) {
528 # ifdef HAVE_XSSL
529 if (sp->s_use_ssl) {
530 jssl_retry:
531 sp->s_rsz = SSL_read(sp->s_ssl, sp->s_rbuf, sizeof sp->s_rbuf);
532 if (sp->s_rsz <= 0) {
533 if (sp->s_rsz < 0) {
534 char o[512];
535 switch(SSL_get_error(sp->s_ssl, sp->s_rsz)) {
536 case SSL_ERROR_WANT_READ:
537 case SSL_ERROR_WANT_WRITE:
538 goto jssl_retry;
540 snprintf(o, sizeof o, "%s",
541 (sp->s_desc ? sp->s_desc : "socket"));
542 ssl_gen_err("%s", o);
544 break;
546 } else
547 # endif
549 jagain:
550 sp->s_rsz = read(sp->s_fd, sp->s_rbuf, sizeof sp->s_rbuf);
551 if (sp->s_rsz <= 0) {
552 if (sp->s_rsz < 0) {
553 char o[512];
554 if (errno == EINTR)
555 goto jagain;
556 snprintf(o, sizeof o, "%s",
557 (sp->s_desc ? sp->s_desc : "socket"));
558 n_perr(o, 0);
560 break;
563 sp->s_rbufptr = sp->s_rbuf;
565 } while ((*lp++ = *sp->s_rbufptr++) != '\n');
566 *lp = '\0';
567 lsize = PTR2SIZE(lp - lp_base);
569 if (linelen)
570 *linelen = lsize;
571 rv = (int)lsize;
572 jleave:
573 NYD2_LEAVE;
574 return rv;
576 #endif /* HAVE_SOCKETS */
578 /* s-it-mode */