gen-okeys.h: regenerate
[s-mailx.git] / socket.c
blobaec1f8da47e1d184978a8cc35b71f1cc60b2e5b8
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 (n_err_no == n_ERR_INTR)
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];
198 snprintf(o, sizeof o, "%s write error",
199 (sp->s_desc ? sp->s_desc : "socket"));
200 # ifdef HAVE_XSSL
201 if (sp->s_use_ssl)
202 ssl_gen_err("%s", o);
203 else
204 # endif
205 n_perr(o, 0);
206 if (x < 0)
207 sclose(sp);
208 rv = STOP;
209 goto jleave;
211 rv = OKAY;
212 jleave:
213 NYD2_LEAVE;
214 return rv;
217 static sigjmp_buf __sopen_actjmp; /* TODO someday, we won't need it no more */
218 static int __sopen_sig; /* TODO someday, we won't need it no more */
219 static void
220 __sopen_onsig(int sig) /* TODO someday, we won't need it no more */
222 NYD_X; /* Signal handler */
223 if (__sopen_sig == -1) {
224 fprintf(n_stderr, _("\nInterrupting this operation may turn "
225 "the DNS resolver unusable\n"));
226 __sopen_sig = 0;
227 } else {
228 __sopen_sig = sig;
229 siglongjmp(__sopen_actjmp, 1);
233 FL bool_t
234 sopen(struct sock *sp, struct url *urlp) /* TODO sighandling; refactor */
236 # ifdef HAVE_SO_SNDTIMEO
237 struct timeval tv;
238 # endif
239 # ifdef HAVE_SO_LINGER
240 struct linger li;
241 # endif
242 # ifdef HAVE_GETADDRINFO
243 char hbuf[NI_MAXHOST];
244 struct addrinfo hints, *res0 = NULL, *res;
245 # else
246 struct sockaddr_in servaddr;
247 struct in_addr **pptr;
248 struct hostent *hp;
249 struct servent *ep;
250 # endif
251 sighandler_type volatile ohup, oint;
252 char const * volatile serv;
253 int volatile sofd = -1, errval;
254 NYD_ENTER;
256 n_UNINIT(errval, 0);
258 /* Connect timeouts after 30 seconds XXX configurable */
259 # ifdef HAVE_SO_SNDTIMEO
260 tv.tv_sec = 30;
261 tv.tv_usec = 0;
262 # endif
263 serv = (urlp->url_port != NULL) ? urlp->url_port : urlp->url_proto;
265 if (n_poption & n_PO_D_V)
266 n_err(_("Resolving host %s:%s ... "), urlp->url_host.s, serv);
268 /* Signal handling (in respect to __sopen_sig dealing) is heavy, but no
269 * healing until v15.0 and i want to end up with that functionality */
270 hold_sigs();
271 __sopen_sig = 0;
272 ohup = safe_signal(SIGHUP, &__sopen_onsig);
273 oint = safe_signal(SIGINT, &__sopen_onsig);
274 if (sigsetjmp(__sopen_actjmp, 0)) {
275 jpseudo_jump:
276 n_err("%s\n",
277 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
278 if (sofd >= 0) {
279 close(sofd);
280 sofd = -1;
282 goto jjumped;
284 rele_sigs();
286 # ifdef HAVE_GETADDRINFO
287 for (;;) {
288 memset(&hints, 0, sizeof hints);
289 hints.ai_socktype = SOCK_STREAM;
290 __sopen_sig = -1;
291 errval = getaddrinfo(urlp->url_host.s, serv, &hints, &res0);
292 if (__sopen_sig != -1) {
293 __sopen_sig = SIGINT;
294 goto jpseudo_jump;
296 __sopen_sig = 0;
297 if (errval == 0)
298 break;
300 if (n_poption & n_PO_D_V)
301 n_err(_("failed\n"));
302 n_err(_("Lookup of %s:%s failed: %s\n"),
303 urlp->url_host.s, serv, gai_strerror(errval));
305 /* Error seems to depend on how "smart" the /etc/service code is: is it
306 * "able" to state whether the service as such is NONAME or does it only
307 * check for the given ai_socktype.. */
308 if (errval == EAI_NONAME || errval == EAI_SERVICE) {
309 if (serv == urlp->url_proto &&
310 (serv = n_servbyname(urlp->url_proto, NULL)) != NULL &&
311 *serv != '\0') {
312 n_err(_(" Trying standard protocol port %s\n"), serv);
313 n_err(_(" If that succeeds consider including the "
314 "port in the URL!\n"));
315 continue;
317 if (serv != urlp->url_port)
318 n_err(_(" Including a port number in the URL may "
319 "circumvent this problem\n"));
321 assert(sofd == -1);
322 errval = 0;
323 goto jjumped;
325 if (n_poption & n_PO_D_V)
326 n_err(_("done\n"));
328 for (res = res0; res != NULL && sofd < 0; res = res->ai_next) {
329 if (n_poption & n_PO_D_V) {
330 if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof hbuf,
331 NULL, 0, NI_NUMERICHOST))
332 memcpy(hbuf, "unknown host", sizeof("unknown host"));
333 n_err(_("%sConnecting to %s:%s ... "),
334 (res == res0 ? n_empty : "\n"), hbuf, serv);
337 sofd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
338 if (sofd >= 0) {
339 # ifdef HAVE_SO_SNDTIMEO
340 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
341 # endif
342 if (connect(sofd, res->ai_addr, res->ai_addrlen)) {
343 errval = n_err_no;
344 close(sofd);
345 sofd = -1;
350 jjumped:
351 if (res0 != NULL) {
352 freeaddrinfo(res0);
353 res0 = NULL;
356 # else /* HAVE_GETADDRINFO */
357 if (serv == urlp->url_proto) {
358 if ((ep = getservbyname(n_UNCONST(serv), "tcp")) != NULL)
359 urlp->url_portno = ntohs(ep->s_port);
360 else {
361 if (n_poption & n_PO_D_V)
362 n_err(_("failed\n"));
363 if ((serv = n_servbyname(urlp->url_proto, &urlp->url_portno)) != NULL)
364 n_err(_(" Unknown service: %s\n"), urlp->url_proto);
365 n_err(_(" Trying standard protocol port %s\n"), serv);
366 n_err(_(" If that succeeds consider including the "
367 "port in the URL!\n"));
368 else {
369 n_err(_(" Unknown service: %s\n"), urlp->url_proto);
370 n_err(_(" Including a port number in the URL may "
371 "circumvent this problem\n"));
372 assert(sofd == -1 && errval == 0);
373 goto jjumped;
378 __sopen_sig = -1;
379 hp = gethostbyname(urlp->url_host.s);
380 if (__sopen_sig != -1) {
381 __sopen_sig = SIGINT;
382 goto jpseudo_jump;
384 __sopen_sig = 0;
386 if (hp == NULL) {
387 char const *emsg;
389 if (n_poption & n_PO_D_V)
390 n_err(_("failed\n"));
391 switch (h_errno) {
392 case HOST_NOT_FOUND: emsg = N_("host not found"); break;
393 default:
394 case TRY_AGAIN: emsg = N_("(maybe) try again later"); break;
395 case NO_RECOVERY: emsg = N_("non-recoverable server error"); break;
396 case NO_DATA: emsg = N_("valid name without IP address"); break;
398 n_err(_("Lookup of %s:%s failed: %s\n"),
399 urlp->url_host.s, serv, V_(emsg));
400 goto jjumped;
401 } else if (n_poption & n_PO_D_V)
402 n_err(_("done\n"));
404 pptr = (struct in_addr**)hp->h_addr_list;
405 if ((sofd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
406 n_perr(_("could not create socket"), 0);
407 assert(sofd == -1 && errval == 0);
408 goto jjumped;
411 memset(&servaddr, 0, sizeof servaddr);
412 servaddr.sin_family = AF_INET;
413 servaddr.sin_port = htons(urlp->url_portno);
414 memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
415 if (n_poption & n_PO_D_V)
416 n_err(_("%sConnecting to %s:%d ... "),
417 n_empty, inet_ntoa(**pptr), (int)urlp->url_portno);
418 # ifdef HAVE_SO_SNDTIMEO
419 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
420 # endif
421 if (connect(sofd, (struct sockaddr*)&servaddr, sizeof servaddr)) {
422 errval = n_err_no;
423 close(sofd);
424 sofd = -1;
426 jjumped:
427 # endif /* !HAVE_GETADDRINFO */
429 hold_sigs();
430 safe_signal(SIGINT, oint);
431 safe_signal(SIGHUP, ohup);
432 rele_sigs();
434 if (sofd < 0) {
435 if (errval != 0) {
436 n_err_no = errval;
437 n_perr(_("Could not connect"), 0);
439 goto jleave;
442 if (n_poption & n_PO_D_V)
443 n_err(_("connected.\n"));
445 /* And the regular timeouts XXX configurable */
446 # ifdef HAVE_SO_SNDTIMEO
447 tv.tv_sec = 42;
448 tv.tv_usec = 0;
449 (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
450 (void)setsockopt(sofd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
451 # endif
452 # ifdef HAVE_SO_LINGER
453 li.l_onoff = 1;
454 li.l_linger = 42;
455 (void)setsockopt(sofd, SOL_SOCKET, SO_LINGER, &li, sizeof li);
456 # endif
458 memset(sp, 0, sizeof *sp);
459 sp->s_fd = sofd;
461 /* SSL/TLS upgrade? */
462 # ifdef HAVE_SSL
463 hold_sigs();
465 # if defined HAVE_GETADDRINFO && defined SSL_CTRL_SET_TLSEXT_HOSTNAME /* TODO
466 * TODO the SSL_ def check should NOT be here */
467 if(urlp->url_flags & n_URL_TLS_MASK){
468 memset(&hints, 0, sizeof hints);
469 hints.ai_family = AF_UNSPEC;
470 hints.ai_flags = AI_NUMERICHOST;
471 res0 = NULL;
472 if(getaddrinfo(urlp->url_host.s, NULL, &hints, &res0) == 0)
473 freeaddrinfo(res0);
474 else
475 urlp->url_flags |= n_URL_HOST_IS_NAME;
477 # endif
479 if (urlp->url_flags & n_URL_TLS_REQUIRED) {
480 ohup = safe_signal(SIGHUP, &__sopen_onsig);
481 oint = safe_signal(SIGINT, &__sopen_onsig);
482 if (sigsetjmp(__sopen_actjmp, 0)) {
483 n_err(_("%s during SSL/TLS handshake\n"),
484 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
485 goto jsclose;
487 rele_sigs();
489 if (ssl_open(urlp, sp) != OKAY) {
490 jsclose:
491 sclose(sp);
492 sofd = -1;
495 hold_sigs();
496 safe_signal(SIGINT, oint);
497 safe_signal(SIGHUP, ohup);
500 rele_sigs();
501 # endif /* HAVE_SSL */
503 jleave:
504 /* May need to bounce the signal to the go.c trampoline (or wherever) */
505 if (__sopen_sig != 0) {
506 sigset_t cset;
507 sigemptyset(&cset);
508 sigaddset(&cset, __sopen_sig);
509 sigprocmask(SIG_UNBLOCK, &cset, NULL);
510 n_raise(__sopen_sig);
512 NYD_LEAVE;
513 return (sofd >= 0);
516 FL int
517 (sgetline)(char **line, size_t *linesize, size_t *linelen, struct sock *sp
518 n_MEMORY_DEBUG_ARGS)
520 int rv;
521 size_t lsize;
522 char *lp_base, *lp;
523 NYD2_ENTER;
525 lsize = *linesize;
526 lp_base = *line;
527 lp = lp_base;
529 if (sp->s_rsz < 0) {
530 sclose(sp);
531 rv = sp->s_rsz;
532 goto jleave;
535 do {
536 if (lp_base == NULL || PTRCMP(lp, >, lp_base + lsize - 128)) {
537 size_t diff = PTR2SIZE(lp - lp_base);
538 *linesize = (lsize += 256); /* XXX magic */
539 *line = lp_base = (n_realloc)(lp_base, lsize n_MEMORY_DEBUG_ARGSCALL);
540 lp = lp_base + diff;
543 if (sp->s_rbufptr == NULL ||
544 PTRCMP(sp->s_rbufptr, >=, sp->s_rbuf + sp->s_rsz)) {
545 # ifdef HAVE_XSSL
546 if (sp->s_use_ssl) {
547 jssl_retry:
548 sp->s_rsz = SSL_read(sp->s_ssl, sp->s_rbuf, sizeof sp->s_rbuf);
549 if (sp->s_rsz <= 0) {
550 if (sp->s_rsz < 0) {
551 char o[512];
553 switch(SSL_get_error(sp->s_ssl, sp->s_rsz)) {
554 case SSL_ERROR_WANT_READ:
555 case SSL_ERROR_WANT_WRITE:
556 goto jssl_retry;
558 snprintf(o, sizeof o, "%s",
559 (sp->s_desc ? sp->s_desc : "socket"));
560 ssl_gen_err("%s", o);
562 break;
564 } else
565 # endif
567 jagain:
568 sp->s_rsz = read(sp->s_fd, sp->s_rbuf, sizeof sp->s_rbuf);
569 if (sp->s_rsz <= 0) {
570 if (sp->s_rsz < 0) {
571 char o[512];
573 if (n_err_no == n_ERR_INTR)
574 goto jagain;
575 snprintf(o, sizeof o, "%s",
576 (sp->s_desc ? sp->s_desc : "socket"));
577 n_perr(o, 0);
579 break;
582 sp->s_rbufptr = sp->s_rbuf;
584 } while ((*lp++ = *sp->s_rbufptr++) != '\n');
585 *lp = '\0';
586 lsize = PTR2SIZE(lp - lp_base);
588 if (linelen)
589 *linelen = lsize;
590 rv = (int)lsize;
591 jleave:
592 NYD2_LEAVE;
593 return rv;
595 #endif /* HAVE_SOCKETS */
597 /* s-it-mode */