Merge branch 'topic/mta-url'
[s-mailx.git] / socket.c
blobc512b1c367980d95bcc023232ff93694cb18fd69
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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_OPENSSL
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_OPENSSL
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_OPENSSL
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_OPENSSL
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 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\" ... "),
266 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 (options & OPT_VERB)
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 wether 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 (options & OPT_VERB)
326 n_err(_("done\n"));
328 for (res = res0; res != NULL && sofd < 0; res = res->ai_next) {
329 if (options & OPT_VERB) {
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"), 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 = errno;
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(UNCONST(serv), "tcp")) != NULL)
359 urlp->url_portno = ntohs(ep->s_port);
360 else {
361 if (options & OPT_VERB)
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 (options & OPT_VERB)
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 (options & OPT_VERB)
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 (options & OPT_VERB)
416 n_err(_("%sConnecting to \"%s:%d\" ... "),
417 "", 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 = errno;
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 errno = errval;
437 n_perr(_("Could not connect"), 0);
439 goto jleave;
442 if (options & OPT_VERB)
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 if (urlp->url_needs_tls) {
464 hold_sigs();
465 ohup = safe_signal(SIGHUP, &__sopen_onsig);
466 oint = safe_signal(SIGINT, &__sopen_onsig);
467 if (sigsetjmp(__sopen_actjmp, 0)) {
468 n_err(_("%s during SSL/TLS handshake\n"),
469 (__sopen_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
470 goto jsclose;
472 rele_sigs();
474 if (ssl_open(urlp, sp) != OKAY) {
475 jsclose:
476 sclose(sp);
477 sofd = -1;
480 hold_sigs();
481 safe_signal(SIGINT, oint);
482 safe_signal(SIGHUP, ohup);
483 rele_sigs();
485 # endif /* HAVE_SSL */
487 jleave:
488 /* May need to bounce the signal to the lex.c trampoline (or wherever) */
489 if (__sopen_sig != 0) {
490 sigset_t cset;
491 sigemptyset(&cset);
492 sigaddset(&cset, __sopen_sig);
493 sigprocmask(SIG_UNBLOCK, &cset, NULL);
494 n_raise(__sopen_sig);
496 NYD_LEAVE;
497 return (sofd >= 0);
500 FL int
501 (sgetline)(char **line, size_t *linesize, size_t *linelen, struct sock *sp
502 SMALLOC_DEBUG_ARGS)
504 int rv;
505 size_t lsize;
506 char *lp_base, *lp;
507 NYD2_ENTER;
509 lsize = *linesize;
510 lp_base = *line;
511 lp = lp_base;
513 if (sp->s_rsz < 0) {
514 sclose(sp);
515 rv = sp->s_rsz;
516 goto jleave;
519 do {
520 if (lp_base == NULL || PTRCMP(lp, >, lp_base + lsize - 128)) {
521 size_t diff = PTR2SIZE(lp - lp_base);
522 *linesize = (lsize += 256); /* XXX magic */
523 *line = lp_base = (srealloc)(lp_base, lsize SMALLOC_DEBUG_ARGSCALL);
524 lp = lp_base + diff;
527 if (sp->s_rbufptr == NULL ||
528 PTRCMP(sp->s_rbufptr, >=, sp->s_rbuf + sp->s_rsz)) {
529 # ifdef HAVE_OPENSSL
530 if (sp->s_use_ssl) {
531 jssl_retry:
532 sp->s_rsz = SSL_read(sp->s_ssl, sp->s_rbuf, sizeof sp->s_rbuf);
533 if (sp->s_rsz <= 0) {
534 if (sp->s_rsz < 0) {
535 char o[512];
536 switch(SSL_get_error(sp->s_ssl, sp->s_rsz)) {
537 case SSL_ERROR_WANT_READ:
538 case SSL_ERROR_WANT_WRITE:
539 goto jssl_retry;
541 snprintf(o, sizeof o, "%s",
542 (sp->s_desc ? sp->s_desc : "socket"));
543 ssl_gen_err("%s", o);
545 break;
547 } else
548 # endif
550 jagain:
551 sp->s_rsz = read(sp->s_fd, sp->s_rbuf, sizeof sp->s_rbuf);
552 if (sp->s_rsz <= 0) {
553 if (sp->s_rsz < 0) {
554 char o[512];
555 if (errno == EINTR)
556 goto jagain;
557 snprintf(o, sizeof o, "%s",
558 (sp->s_desc ? sp->s_desc : "socket"));
559 n_perr(o, 0);
561 break;
564 sp->s_rbufptr = sp->s_rbuf;
566 } while ((*lp++ = *sp->s_rbufptr++) != '\n');
567 *lp = '\0';
568 lsize = PTR2SIZE(lp - lp_base);
570 if (linelen)
571 *linelen = lsize;
572 rv = (int)lsize;
573 jleave:
574 NYD2_LEAVE;
575 return rv;
577 #endif /* HAVE_SOCKETS */
579 /* s-it-mode */