static int xioopen_rawip_recv(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, unsigned groups, int pf, int socktype, int dummy3) { const char *protname = argv[1]; char *garbage; bool needbind = false; union sockaddr_union us; socklen_t uslen = sizeof(us); int ipproto; int result; if (argc != 2) { Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); return STAT_NORETRY; } if ((ipproto = strtoul(protname, &garbage, 0)) >= 256) { Error2("xioopen_rawip_recv(\"%s\",,): protocol number exceeds 255 (%u)", protname, ipproto); return STAT_NORETRY; } else if (*garbage) { Warn1("xioopen_rawip_recv(\"%s\",,): trailing garbage in protocol specification", protname); /*return STAT_NORETRY;*/ } retropt_socket_pf(opts, &pf); if (pf == PF_UNSPEC) { #if WITH_IP4 && WITH_IP6 pf = xioopts.default_ip=='6'?PF_INET6:PF_INET; #elif WITH_IP6 pf = PF_INET6; #else pf = PF_INET; #endif } if (retropt_bind(opts, pf, socktype, ipproto, &/*us.soa*/xfd->stream.para.socket.la.soa, &uslen, 1, xfd->stream.para.socket.ip.res_opts[0], xfd->stream.para.socket.ip.res_opts[1]) == STAT_OK) { needbind = true; } else { /* pf is required during xioread checks */ xfd->stream.para.socket.la.soa.sa_family = pf; } xfd->stream.dtype = XIODATA_RECV_SKIPIP; result = _xioopen_dgram_recv(&xfd->stream, xioflags, needbind?&/*us.soa*/xfd->stream.para.socket.la.soa:NULL, uslen, opts, pf, socktype, ipproto, E_ERROR); _xio_openlate(&xfd->stream, opts); return result; }
static int xioopen_unix_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int abstract, int dummy2, int dummy3) { /* we expect the form: filename */ const char *name; struct single *xfd = &xxfd->stream; int pf = PF_UNIX; int socktype = SOCK_STREAM; int protocol = 0; struct sockaddr_un them, us; socklen_t themlen, uslen; bool tight = true; bool needbind = false; bool opt_unlink_close = false; int result; if (argc != 2) { Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); return STAT_NORETRY; } xfd->howtoend = END_SHUTDOWN; name = argv[1]; retropt_socket_pf(opts, &pf); retropt_bool(opts, OPT_UNIX_TIGHTSOCKLEN, &tight); themlen = xiosetunix(pf, &them, name, abstract, tight); if (!(ABSTRACT && abstract)) { /* only for non abstract because abstract do not work in file system */ retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); } if (retropt_bind(opts, pf, socktype, protocol, (struct sockaddr *)&us, &uslen, 0, 0, 0) != STAT_NOACTION) { needbind = true; } if (opt_unlink_close) { if ((xfd->unlink_close = strdup(name)) == NULL) { Error1("strdup(\"%s\"): out of memory", name); } xfd->opt_unlink_close = true; } if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); applyopts(-1, opts, PH_EARLY); if ((result = xioopen_connect(xfd, needbind?(struct sockaddr *)&us:NULL, uslen, (struct sockaddr *)&them, themlen, opts, pf, socktype, protocol, false)) != 0) { return result; } if ((result = _xio_openlate(xfd, opts)) < 0) { return result; } return STAT_OK; }
static int xioopen_udp_datagram(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int pf, int socktype, int ipproto) { xiosingle_t *xfd = &xxfd->stream; char *rangename; char *hostname; int result; if (argc != 3) { Error2("%s: wrong number of parameters (%d instead of 2)", argv[0], argc-1); return STAT_NORETRY; } if ((hostname = strdup(argv[1])) == NULL) { Error1("strdup(\"%s\"): out of memory", argv[1]); return STAT_RETRYLATER; } retropt_socket_pf(opts, &pf); result = _xioopen_udp_sendto(hostname, argv[2], opts, xioflags, xxfd, groups, pf, socktype, ipproto); free(hostname); if (result != STAT_OK) { return result; } xfd->dtype = XIOREAD_RECV|XIOWRITE_SENDTO; xfd->para.socket.la.soa.sa_family = xfd->peersa.soa.sa_family; /* only accept packets with correct remote ports */ xfd->para.socket.ip.sourceport = ntohs(xfd->peersa.ip4.sin_port); xfd->para.socket.ip.dosourceport = true; /* which reply packets will be accepted - determine by range option */ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &xfd->para.socket.range) < 0) { free(rangename); return STAT_NORETRY; } xfd->para.socket.dorange = true; xfd->dtype |= XIOREAD_RECV_CHECKRANGE; free(rangename); } #if WITH_LIBWRAP xio_retropt_tcpwrap(xfd, opts); #endif /* WITH_LIBWRAP */ _xio_openlate(xfd, opts); return STAT_OK; }
static int xioopen_proxy_connect2(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int dummy1, int dummy2, int dummy3) { struct single *xfd = &xxfd->stream; struct proxyvars struct_proxyvars = { 0 }, *proxyvars = &struct_proxyvars; const char *targetname, *targetport; /* variables to be filled with address option values */ bool dofork = false; int result; if (xfd->rfd < 0) { Error("xioopen_proxy_connect(): proxyname missing"); return STAT_NORETRY; } targetname = argv[1]; targetport = argv[2]; if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); retropt_bool(opts, OPT_FORK, &dofork); if (dofork && !(xioflags & XIO_MAYFORK)) { Error("fork option not allowed by application"); } result = _xioopen_proxy_prepare(proxyvars, opts, targetname, targetport); if (result != STAT_OK) return result; Notice2("opening connection to %s:%u using proxy CONNECT", proxyvars->targetaddr, proxyvars->targetport); xfd->dtype = XIODATA_STREAM; applyopts(xfd->rfd, opts, PH_ALL); /*!*/ if ((result = _xio_openlate(xfd, opts)) < 0) return result; result = _xioopen_proxy_connect(xfd, proxyvars, E_ERROR); switch (result) { case STAT_OK: break; default: return result; } Notice2("successfully connected to %s:%u via proxy", proxyvars->targetaddr, proxyvars->targetport); return STAT_OK; }
/* open for writing: if the filesystem entry already exists, the data is appended if it does not exist, a file is created and the data is appended */ static int xioopen_open(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *fd, unsigned groups, int dummy1, int dummy2, int dummy3) { const char *filename = argv[1]; int rw = (xioflags & XIO_ACCMODE); bool exists; bool opt_unlink_close = false; int result; /* remove old file, or set user/permissions on old file; parse options */ if ((result = _xioopen_named_early(argc, argv, fd, groups, &exists, opts)) < 0) { return result; } retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); if (opt_unlink_close) { if ((fd->stream.unlink_close = strdup(filename)) == NULL) { Error1("strdup(\"%s\"): out of memory", filename); } fd->stream.opt_unlink_close = true; } Notice3("opening %s \"%s\" for %s", filetypenames[(result&S_IFMT)>>12], filename, ddirection[rw]); if ((result = _xioopen_open(filename, rw, opts)) < 0) return result; fd->stream.fd = result; #if WITH_TERMIOS if (Isatty(fd->stream.fd)) { if (Tcgetattr(fd->stream.fd, &fd->stream.savetty) < 0) { Warn2("cannot query current terminal settings on fd %d: %s", fd->stream.fd, strerror(errno)); } else { fd->stream.ttyvalid = true; } } #endif /* WITH_TERMIOS */ applyopts_named(filename, opts, PH_FD); applyopts(fd->stream.fd, opts, PH_FD); applyopts_cloexec(fd->stream.fd, opts); applyopts_fchown(fd->stream.fd, opts); if ((result = _xio_openlate(&fd->stream, opts)) < 0) return result; return 0; }
/* socklen_t salen;*/ static int xioopen_rawip_sendto(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int pf, int dummy2, int dummy3) { int result; if (argc != 3) { Error2("%s: wrong number of parameters (%d instead of 2)", argv[0], argc-1); return STAT_NORETRY; } if ((result = _xioopen_rawip_sendto(argv[1], argv[2], opts, xioflags, xxfd, groups, &pf)) != STAT_OK) { return result; } _xio_openlate(&xxfd->stream, opts); return STAT_OK; }
/* we expect the form: address:protocol */ static int xioopen_rawip_datagram(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int pf, int dummy2, int dummy3) { xiosingle_t *xfd = &xxfd->stream; char *rangename; int result; if (argc != 3) { Error2("%s: wrong number of parameters (%d instead of 2)", argv[0], argc-1); return STAT_NORETRY; } if ((result = _xioopen_rawip_sendto(argv[1], argv[2], opts, xioflags, xxfd, groups, &pf)) != STAT_OK) { return result; } xfd->dtype = XIOREAD_RECV|XIOWRITE_SENDTO; if (pf == PF_INET) { xfd->dtype |= XIOREAD_RECV_SKIPIP; } xfd->para.socket.la.soa.sa_family = xfd->peersa.soa.sa_family; /* which reply packets will be accepted - determine by range option */ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &xfd->para.socket.range) < 0) { free(rangename); return STAT_NORETRY; } xfd->para.socket.dorange = true; xfd->dtype |= XIOREAD_RECV_CHECKRANGE; free(rangename); } #if WITH_LIBWRAP xio_retropt_tcpwrap(xfd, opts); #endif /* WITH_LIBWRAP */ _xio_openlate(xfd, opts); return STAT_OK; }
static int xioopen_udp_sendto(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int pf, int socktype, int ipproto) { int result; if (argc != 3) { Error2("%s: wrong number of parameters (%d instead of 2)", argv[0], argc-1); return STAT_NORETRY; } retropt_socket_pf(opts, &pf); if ((result = _xioopen_udp_sendto(argv[1], argv[2], opts, xioflags, xxfd, groups, pf, socktype, ipproto)) != STAT_OK) { return result; } _xio_openlate(&xxfd->stream, opts); return STAT_OK; }
static int xioopen_creat(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, unsigned groups, int dummy1, int dummy2, int dummy3) { const char *filename = argv[1]; int rw = (xioflags&XIO_ACCMODE); int fd; bool exists; bool opt_unlink_close = false; int result; /* remove old file, or set user/permissions on old file; parse options */ if ((result = _xioopen_named_early(argc, argv, xfd, groups, &exists, opts)) < 0) { return result; } retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); if (opt_unlink_close) { if ((xfd->stream.unlink_close = strdup(filename)) == NULL) { Error1("strdup(\"%s\"): out of memory", filename); } xfd->stream.opt_unlink_close = true; } Notice2("creating regular file \"%s\" for %s", filename, ddirection[rw]); if ((fd = _xioopen_creat(filename, rw, opts)) < 0) return fd; if (XIOWITHRD(rw)) xfd->stream.rfd = fd; if (XIOWITHWR(rw)) xfd->stream.wfd = fd; applyopts_named(filename, opts, PH_PASTOPEN); if ((result = applyopts2(fd, opts, PH_PASTOPEN, PH_LATE2)) < 0) return result; applyopts_cloexec(fd, opts); applyopts_fchown(fd, opts); if ((result = _xio_openlate(&xfd->stream, opts)) < 0) return result; return 0; }
/* retrieve and apply options to a standard file descriptor. Do not set FD_CLOEXEC flag. */ int xioopen_fd(struct opt *opts, int rw, xiosingle_t *xfd, int numfd, int dummy2, int dummy3) { xfd->fd = numfd; xfd->howtoend = END_NONE; #if WITH_TERMIOS if (Isatty(xfd->fd)) { if (Tcgetattr(xfd->fd, &xfd->savetty) < 0) { Warn2("cannot query current terminal settings on fd %d: %s", xfd->fd, strerror(errno)); } else { xfd->ttyvalid = true; } } #endif /* WITH_TERMIOS */ if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); applyopts2(xfd->fd, opts, PH_INIT, PH_FD); return _xio_openlate(xfd, opts); }
/* establishes communication with an existing UNIX type socket. supports stream and datagram socket types: first tries to connect(), but when this fails it falls back to sendto(). applies and consumes the following option: PH_INIT, PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECTED, PH_LATE, ?PH_CONNECT OFUNC_OFFSET, OPT_PROTOCOL_FAMILY, OPT_UNIX_TIGHTSOCKLEN, OPT_UNLINK_CLOSE, OPT_BIND, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_CLOEXEC, OPT_USER, OPT_GROUP, ?OPT_FORK, */ int _xioopen_unix_client(xiosingle_t *xfd, int xioflags, unsigned groups, int abstract, struct opt *opts, const char *name) { int pf = PF_UNIX; int socktype = 0; /* to be determined by server socket type */ int protocol = 0; union sockaddr_union them, us; socklen_t themlen, uslen; bool tight = true; bool needbind = false; bool opt_unlink_close = false; struct opt *opts0; int result; if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); xfd->howtoend = END_SHUTDOWN; retropt_socket_pf(opts, &pf); retropt_bool(opts, OPT_UNIX_TIGHTSOCKLEN, &tight); themlen = xiosetunix(pf, &them.un, name, abstract, tight); if (!(ABSTRACT && abstract)) { /* only for non abstract because abstract do not work in file system */ retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); } if (retropt_bind(opts, pf, socktype, protocol, &us.soa, &uslen, 0, 0, 0) != STAT_NOACTION) { needbind = true; } if (opt_unlink_close) { if ((xfd->unlink_close = strdup(name)) == NULL) { Error1("strdup(\"%s\"): out of memory", name); } xfd->opt_unlink_close = true; } /* save options, because we might have to start again */ opts0 = copyopts(opts, GROUP_ALL); /* xfd->dtype = DATA_STREAM; // is default */ if ((result = xioopen_connect(xfd, needbind?(struct sockaddr *)&us:NULL, uslen, (struct sockaddr *)&them, themlen, opts, pf, socktype?socktype:SOCK_STREAM, protocol, false)) != 0) { if (errno == EPROTOTYPE) { if (needbind) { Unlink(us.un.sun_path); } dropopts2(opts, PH_INIT, PH_SPEC); opts = opts0; xfd->peersa = them; xfd->salen = sizeof(struct sockaddr_un); if ((result = _xioopen_dgram_sendto(needbind?&us:NULL, uslen, opts, xioflags, xfd, groups, pf, socktype?socktype:SOCK_DGRAM, protocol)) != 0) { return result; } xfd->dtype = XIODATA_RECVFROM; } } if ((result = _xio_openlate(xfd, opts)) < 0) { return result; } return 0; }
/* process a bidirectional "stdio" or "-" argument with options. generate a dual address. */ int xioopen_stdio_bi(xiofile_t *sock) { struct opt *optspr; unsigned int groups1 = addr_stdio.groups; int result; if (xioopen_makedual(sock) < 0) { return -1; } sock->dual.stream[0]->tag = XIO_TAG_RDONLY; sock->dual.stream[0]->fd = 0 /*stdin*/; sock->dual.stream[1]->tag = XIO_TAG_WRONLY; sock->dual.stream[1]->fd = 1 /*stdout*/; sock->dual.stream[0]->howtoend = sock->dual.stream[1]->howtoend = END_NONE; #if WITH_TERMIOS if (Isatty(sock->dual.stream[0]->fd)) { if (Tcgetattr(sock->dual.stream[0]->fd, &sock->dual.stream[0]->savetty) < 0) { Warn2("cannot query current terminal settings on fd %d: %s", sock->dual.stream[0]->fd, strerror(errno)); } else { sock->dual.stream[0]->ttyvalid = true; } } if (Isatty(sock->dual.stream[1]->fd)) { if (Tcgetattr(sock->dual.stream[1]->fd, &sock->dual.stream[1]->savetty) < 0) { Warn2("cannot query current terminal settings on fd %d: %s", sock->dual.stream[1]->fd, strerror(errno)); } else { sock->dual.stream[1]->ttyvalid = true; } } #endif /* WITH_TERMIOS */ /* options here are one-time and one-direction, no second use */ retropt_bool(sock->stream.opts, OPT_IGNOREEOF, &sock->dual.stream[0]->ignoreeof); /* extract opts that should be applied only once */ if ((optspr = copyopts(sock->stream.opts, GROUP_PROCESS)) == NULL) { return -1; } /* here we copy opts, because most have to be applied twice! */ if ((sock->dual.stream[1]->opts = copyopts(sock->stream.opts, GROUP_FD|GROUP_APPL|(groups1&~GROUP_PROCESS))) == NULL) { return -1; } sock->dual.stream[0]->opts = sock->stream.opts; sock->stream.opts = NULL; if (applyopts_single(sock->dual.stream[0], sock->dual.stream[0]->opts, PH_INIT) < 0) return -1; if (applyopts_single(sock->dual.stream[1], sock->dual.stream[1]->opts, PH_INIT) < 0) return -1; applyopts(-1, sock->dual.stream[0]->opts, PH_INIT); applyopts(-1, sock->dual.stream[1]->opts, PH_INIT); if ((result = applyopts(-1, optspr, PH_EARLY)) < 0) return result; if ((result = applyopts(-1, optspr, PH_PREOPEN)) < 0) return result; /* apply options to first FD */ if ((result = applyopts(sock->dual.stream[0]->fd, sock->dual.stream[0]->opts, PH_ALL)) < 0) { return result; } if ((result = _xio_openlate(sock->dual.stream[0], sock->dual.stream[0]->opts)) < 0) { return result; } #if 0 /* ignore this opt */ retropt_bool(sock->dual.stream[0]->opts, OPT_COOL_WRITE); #endif /* apply options to second FD */ if ((result = applyopts(sock->dual.stream[1]->fd, sock->dual.stream[1]->opts, PH_ALL)) < 0) { return result; } if ((result = _xio_openlate(sock->dual.stream[1], sock->dual.stream[1]->opts)) < 0) { return result; } #if 0 if ((result = _xio_openlate(sock->dual.stream[1], optspr)) < 0) { return result; } #endif Notice("reading from and writing to stdio"); return 0; }
/* we expect the form "host:port" */ int xioopen_ipapp_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int socktype, int ipproto, int pf) { struct single *xfd = &xxfd->stream; struct opt *opts0 = NULL; const char *hostname = argv[1], *portname = argv[2]; bool dofork = false; union sockaddr_union us_sa, *us = &us_sa; union sockaddr_union them_sa, *them = &them_sa; socklen_t uslen = sizeof(us_sa); socklen_t themlen = sizeof(them_sa); bool needbind = false; bool lowport = false; int level; int result; if (argc != 3) { Error2("%s: wrong number of parameters (%d instead of 2)", argv[0], argc-1); } xfd->howtoend = END_SHUTDOWN; if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); retropt_bool(opts, OPT_FORK, &dofork); if (_xioopen_ipapp_prepare(opts, &opts0, hostname, portname, &pf, ipproto, xfd->para.socket.ip.res_opts[1], xfd->para.socket.ip.res_opts[0], them, &themlen, us, &uslen, &needbind, &lowport, socktype) != STAT_OK) { return STAT_NORETRY; } if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } if (xioopts.logopt == 'm') { Info("starting connect loop, switching to syslog"); diag_set('y', xioopts.syslogfac); xioopts.logopt = 'y'; } else { Info("starting connect loop"); } do { /* loop over retries and forks */ #if WITH_RETRY if (xfd->forever || xfd->retry) { level = E_INFO; } else #endif /* WITH_RETRY */ level = E_ERROR; result = _xioopen_connect(xfd, needbind?(struct sockaddr *)us:NULL, uslen, (struct sockaddr *)them, themlen, opts, pf, socktype, ipproto, lowport, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: case STAT_RETRYNOW: if (xfd->forever || xfd->retry) { --xfd->retry; if (result == STAT_RETRYLATER) { Nanosleep(&xfd->intervall, NULL); } dropopts(opts, PH_ALL); free(opts); opts = copyopts(opts0, GROUP_ALL); continue; } return STAT_NORETRY; #endif /* WITH_RETRY */ default: free(opts0);free(opts); return result; } #if WITH_RETRY if (dofork) { pid_t pid; int level = E_ERROR; if (xfd->forever || xfd->retry) { level = E_WARN; /* most users won't expect a problem here, so Notice is too weak */ } while ((pid = xio_fork(false, level)) < 0) { if (xfd->forever || --xfd->retry) { Nanosleep(&xfd->intervall, NULL); continue; } free(opts0); return STAT_RETRYLATER; } if (pid == 0) { /* child process */ xfd->forever = false; xfd->retry = 0; break; } /* parent process */ Close(xfd->fd); /* with and without retry */ Nanosleep(&xfd->intervall, NULL); dropopts(opts, PH_ALL); free(opts); opts = copyopts(opts0, GROUP_ALL); continue; /* with next socket() bind() connect() */ } else #endif /* WITH_RETRY */ { break; } } while (true); /* only "active" process breaks (master without fork, or child) */ if ((result = _xio_openlate(xfd, opts)) < 0) { free(opts0);free(opts); return result; } free(opts0);free(opts); return 0; }
/* we expect the form: port */ int xioopen_ipdgram_listen(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *fd, unsigned groups, int pf, int ipproto, int protname) { const char *portname = argv[1]; union sockaddr_union us; union sockaddr_union themunion; union sockaddr_union *them = &themunion; int socktype = SOCK_DGRAM; struct pollfd readfd; bool dofork = false; pid_t pid; char *rangename; char infobuff[256]; unsigned char buff1[1]; socklen_t uslen; socklen_t themlen; int result; if (argc != 2) { Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); } if (pf == PF_UNSPEC) { #if WITH_IP4 && WITH_IP6 pf = xioopts.default_ip=='6'?PF_INET6:PF_INET; #elif WITH_IP6 pf = PF_INET6; #else pf = PF_INET; #endif } retropt_socket_pf(opts, &pf); if (applyopts_single(&fd->stream, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); uslen = socket_init(pf, &us); retropt_bind(opts, pf, socktype, IPPROTO_UDP, (struct sockaddr *)&us, &uslen, 1, fd->stream.para.socket.ip.res_opts[1], fd->stream.para.socket.ip.res_opts[0]); if (false) { ; #if WITH_IP4 } else if (pf == PF_INET) { us.ip4.sin_port = parseport(portname, ipproto); #endif #if WITH_IP6 } else if (pf == PF_INET6) { us.ip6.sin6_port = parseport(portname, ipproto); #endif } else { Error1("xioopen_ipdgram_listen(): unknown address family %d", pf); } retropt_bool(opts, OPT_FORK, &dofork); if (dofork) { if (!(xioflags & XIO_MAYFORK)) { Error("option fork not allowed here"); return STAT_NORETRY; } } #if WITH_IP4 /*|| WITH_IP6*/ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &fd->stream.para.socket.range) < 0) { free(rangename); return STAT_NORETRY; } free(rangename); fd->stream.para.socket.dorange = true; } #endif #if WITH_LIBWRAP xio_retropt_tcpwrap(&fd->stream, opts); #endif /* WITH_LIBWRAP */ if (retropt_ushort(opts, OPT_SOURCEPORT, &fd->stream.para.socket.ip.sourceport) >= 0) { fd->stream.para.socket.ip.dosourceport = true; } retropt_bool(opts, OPT_LOWPORT, &fd->stream.para.socket.ip.lowport); if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } while (true) { /* we loop with fork or prohibited packets */ /* now wait for some packet on this datagram socket, get its sender address, connect there, and return */ int one = 1; char infobuff[256]; union sockaddr_union _sockname; union sockaddr_union *la = &_sockname; /* local address */ if ((fd->stream.fd = xiosocket(opts, pf, socktype, ipproto, E_ERROR)) < 0) { return STAT_RETRYLATER; } applyopts(fd->stream.fd, opts, PH_PASTSOCKET); if (Setsockopt(fd->stream.fd, opt_so_reuseaddr.major, opt_so_reuseaddr.minor, &one, sizeof(one)) < 0) { Warn6("setsockopt(%d, %d, %d, {%d}, "F_Zd"): %s", fd->stream.fd, opt_so_reuseaddr.major, opt_so_reuseaddr.minor, one, sizeof(one), strerror(errno)); } applyopts_cloexec(fd->stream.fd, opts); applyopts(fd->stream.fd, opts, PH_PREBIND); applyopts(fd->stream.fd, opts, PH_BIND); if (Bind(fd->stream.fd, &us.soa, uslen) < 0) { Error4("bind(%d, {%s}, "F_Zd"): %s", fd->stream.fd, sockaddr_info(&us.soa, uslen, infobuff, sizeof(infobuff)), uslen, strerror(errno)); return STAT_RETRYLATER; } /* under some circumstances bind() fills sockaddr with interesting info. */ if (Getsockname(fd->stream.fd, &us.soa, &uslen) < 0) { Error4("getsockname(%d, %p, {%d}): %s", fd->stream.fd, &us.soa, uslen, strerror(errno)); } applyopts(fd->stream.fd, opts, PH_PASTBIND); Notice1("listening on UDP %s", sockaddr_info(&us.soa, uslen, infobuff, sizeof(infobuff))); readfd.fd = fd->stream.fd; readfd.events = POLLIN|POLLERR; while (xiopoll(&readfd, 1, NULL) < 0) { if (errno != EINTR) break; } themlen = socket_init(pf, them); do { result = Recvfrom(fd->stream.fd, buff1, 1, MSG_PEEK, &them->soa, &themlen); } while (result < 0 && errno == EINTR); if (result < 0) { Error5("recvfrom(%d, %p, 1, MSG_PEEK, {%s}, {"F_Zu"}): %s", fd->stream.fd, buff1, sockaddr_info(&them->soa, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); return STAT_RETRYLATER; } Notice1("accepting UDP connection from %s", sockaddr_info(&them->soa, themlen, infobuff, sizeof(infobuff))); if (xiocheckpeer(&fd->stream, them, la) < 0) { /* drop packet */ char buff[512]; Recv(fd->stream.fd, buff, sizeof(buff), 0); /* drop packet */ Close(fd->stream.fd); continue; } Info1("permitting UDP connection from %s", sockaddr_info(&them->soa, themlen, infobuff, sizeof(infobuff))); if (dofork) { pid = xio_fork(false, E_ERROR); if (pid < 0) { return STAT_RETRYLATER; } if (pid == 0) { /* child */ break; } /* server: continue loop with socket()+recvfrom() */ /* when we dont close this we get awkward behaviour on Linux 2.4: recvfrom gives 0 bytes with invalid socket address */ if (Close(fd->stream.fd) < 0) { Info2("close(%d): %s", fd->stream.fd, strerror(errno)); } Sleep(1); /*! give child a chance to consume the old packet */ continue; } break; } applyopts(fd->stream.fd, opts, PH_CONNECT); if ((result = Connect(fd->stream.fd, &them->soa, themlen)) < 0) { Error4("connect(%d, {%s}, "F_Zd"): %s", fd->stream.fd, sockaddr_info(&them->soa, themlen, infobuff, sizeof(infobuff)), themlen, strerror(errno)); return STAT_RETRYLATER; } /* set the env vars describing the local and remote sockets */ if (Getsockname(fd->stream.fd, &us.soa, &uslen) < 0) { Warn4("getsockname(%d, %p, {%d}): %s", fd->stream.fd, &us.soa, uslen, strerror(errno)); } xiosetsockaddrenv("SOCK", &us, uslen, IPPROTO_UDP); xiosetsockaddrenv("PEER", them, themlen, IPPROTO_UDP); fd->stream.howtoend = END_SHUTDOWN; applyopts_fchown(fd->stream.fd, opts); applyopts(fd->stream.fd, opts, PH_LATE); if ((result = _xio_openlate(&fd->stream, opts)) < 0) return result; return 0; }
static int xioopen_udp_recv(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, unsigned groups, int pf, int socktype, int ipproto) { union sockaddr_union us; socklen_t uslen = sizeof(us); char *rangename; int result; if (argc != 2) { Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); return STAT_NORETRY; } retropt_socket_pf(opts, &pf); if (pf == PF_UNSPEC) { #if WITH_IP4 && WITH_IP6 pf = xioopts.default_ip=='6'?PF_INET6:PF_INET; #elif WITH_IP6 pf = PF_INET6; #else pf = PF_INET; #endif } if ((result = xiogetaddrinfo(NULL, argv[1], pf, socktype, ipproto, &us, &uslen, xfd->stream.para.socket.ip.res_opts[0], xfd->stream.para.socket.ip.res_opts[1])) != STAT_OK) { return result; } if (pf == PF_UNSPEC) { pf = us.soa.sa_family; } #if 1 { union sockaddr_union la; socklen_t lalen = sizeof(la); if (retropt_bind(opts, pf, socktype, ipproto, &xfd->stream.para.socket.la.soa, &lalen, 1, xfd->stream.para.socket.ip.res_opts[0], xfd->stream.para.socket.ip.res_opts[1]) != STAT_NOACTION) { switch (pf) { #if WITH_IP4 case PF_INET: us.ip4.sin_addr = xfd->stream.para.socket.la.ip4.sin_addr; break; #endif #if WITH_IP6 case PF_INET6: us.ip6.sin6_addr = xfd->stream.para.socket.la.ip6.sin6_addr; break; #endif } } else { xfd->stream.para.socket.la.soa.sa_family = pf; } } #endif #if WITH_IP4 /*|| WITH_IP6*/ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &xfd->stream.para.socket.range) < 0) { return STAT_NORETRY; } xfd->stream.para.socket.dorange = true; } #endif #if WITH_LIBWRAP xio_retropt_tcpwrap(&xfd->stream, opts); #endif /* WITH_LIBWRAP */ if (retropt_ushort(opts, OPT_SOURCEPORT, &xfd->stream.para.socket.ip.sourceport) >= 0) { xfd->stream.para.socket.ip.dosourceport = true; } retropt_bool(opts, OPT_LOWPORT, &xfd->stream.para.socket.ip.lowport); xfd->stream.dtype = XIODATA_RECV; if ((result = _xioopen_dgram_recv(&xfd->stream, xioflags, &us.soa, uslen, opts, pf, socktype, ipproto, E_ERROR)) != STAT_OK) { return result; } _xio_openlate(&xfd->stream, opts); return result; }
static int xioopen_udp_recvfrom(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, unsigned groups, int pf, int socktype, int ipproto) { union sockaddr_union us; socklen_t uslen = sizeof(us); bool needbind = false; int result; if (argc != 2) { Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); return STAT_NORETRY; } xfd->stream.howtoend = END_NONE; retropt_socket_pf(opts, &pf); if (pf == PF_UNSPEC) { #if WITH_IP4 && WITH_IP6 pf = xioopts.default_ip=='6'?PF_INET6:PF_INET; #elif WITH_IP6 pf = PF_INET6; #else pf = PF_INET; #endif } if ((result = xiogetaddrinfo(NULL, argv[1], pf, socktype, ipproto, &us, &uslen, xfd->stream.para.socket.ip.res_opts[0], xfd->stream.para.socket.ip.res_opts[1])) != STAT_OK) { return result; } if (pf == PF_UNSPEC) { pf = us.soa.sa_family; } { union sockaddr_union la; socklen_t lalen = sizeof(la); if (retropt_bind(opts, pf, socktype, ipproto, &la.soa, &lalen, 1, xfd->stream.para.socket.ip.res_opts[0], xfd->stream.para.socket.ip.res_opts[1]) != STAT_NOACTION) { switch (pf) { #if WITH_IP4 case PF_INET: us.ip4.sin_addr = la.ip4.sin_addr; break; #endif #if WITH_IP6 case PF_INET6: us.ip6.sin6_addr = la.ip6.sin6_addr; break; #endif } needbind = true; } } if (retropt_ushort(opts, OPT_SOURCEPORT, &xfd->stream.para.socket.ip.sourceport) >= 0) { xfd->stream.para.socket.ip.dosourceport = true; } retropt_bool(opts, OPT_LOWPORT, &xfd->stream.para.socket.ip.lowport); xfd->stream.dtype = XIODATA_RECVFROM_ONE; if ((result = _xioopen_dgram_recvfrom(&xfd->stream, xioflags, &us.soa, uslen, opts, pf, socktype, ipproto, E_ERROR)) != STAT_OK) { return result; } _xio_openlate(&xfd->stream, opts); return STAT_OK; }
static int xioopen_proxy_connect3(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int dummy1, int dummy2, int dummy3) { /* we expect the form: host:host:port */ struct single *xfd = &xxfd->stream; int rw = (xioflags & XIO_ACCMODE); struct proxyvars struct_proxyvars = { 0 }, *proxyvars = &struct_proxyvars; const char *proxyname; char *proxyport = NULL; const char *targetname, *targetport; /* variables to be filled with address option values */ bool dofork = false; int socktype = SOCK_STREAM; struct opt *opts0 = NULL; /* */ int pf = PF_UNSPEC; union sockaddr_union us_sa, *us = &us_sa; union sockaddr_union them_sa, *them = &them_sa; socklen_t uslen = sizeof(us_sa); socklen_t themlen = sizeof(them_sa); int ipproto = IPPROTO_TCP; bool needbind = false; bool lowport = false; int level; int result; if (xfd->rfd >= 0) { Error("xioopen_proxy_connect(): proxyname not allowed here"); return STAT_NORETRY; } proxyname = argv[1]; targetname = argv[2]; targetport = argv[3]; xfd->howtoshut = XIOSHUT_DOWN; xfd->howtoclose = XIOCLOSE_CLOSE; if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); retropt_int(opts, OPT_SO_TYPE, &socktype); retropt_bool(opts, OPT_FORK, &dofork); if (dofork && !(xioflags & XIO_MAYFORK)) { Error("fork option not allowed by application"); } if (retropt_string(opts, OPT_PROXYPORT, &proxyport) < 0) { if ((proxyport = strdup(PROXYPORT)) == NULL) { errno = ENOMEM; return -1; } } result = _xioopen_proxy_prepare(proxyvars, opts, targetname, targetport); if (result != STAT_OK) return result; result = _xioopen_ipapp_prepare(opts, &opts0, proxyname, proxyport, &pf, ipproto, xfd->para.socket.ip.res_opts[1], xfd->para.socket.ip.res_opts[0], them, &themlen, us, &uslen, &needbind, &lowport, socktype); if (result != STAT_OK) return result; Notice4("opening connection to %s:%u via proxy %s:%s", proxyvars->targetaddr, proxyvars->targetport, proxyname, proxyport); do { /* loop over failed connect and proxy connect attempts */ #if WITH_RETRY if (xfd->forever || xfd->retry) { level = E_INFO; } else #endif /* WITH_RETRY */ level = E_ERROR; result = _xioopen_connect(xfd, needbind?(struct sockaddr *)us:NULL, sizeof(*us), (struct sockaddr *)them, themlen, opts, pf, socktype, IPPROTO_TCP, lowport, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: case STAT_RETRYNOW: if (xfd->forever || xfd->retry--) { if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL); continue; } #endif /* WITH_RETRY */ default: return result; } if (XIOWITHWR(rw)) xfd->wfd = xfd->rfd; if (!XIOWITHRD(rw)) xfd->rfd = -1; applyopts(xfd->rfd, opts, PH_ALL); /*!*/ if ((result = _xio_openlate(xfd, opts)) < 0) return result; result = _xioopen_proxy_connect(xfd, proxyvars, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: case STAT_RETRYNOW: if (xfd->forever || xfd->retry--) { if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL); continue; } #endif /* WITH_RETRY */ default: return result; } if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } #if WITH_RETRY if (dofork) { pid_t pid; int level = E_ERROR; if (xfd->forever || xfd->retry) { level = E_WARN; } while ((pid = xio_fork(false, level)) < 0) { if (xfd->forever || --xfd->retry) { Nanosleep(&xfd->intervall, NULL); continue; } return STAT_RETRYLATER; } if (pid == 0) { /* child process */ xfd->forever = false; xfd->retry = 0; break; } /* parent process */ Notice1("forked off child process "F_pid, pid); Close(xfd->rfd); Close(xfd->wfd); Nanosleep(&xfd->intervall, NULL); dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); continue; } else #endif /* WITH_RETRY */ { break; } } while (true); /* end of complete open loop - drop out on success */ Notice2("successfully connected to %s:%u via proxy", proxyvars->targetaddr, proxyvars->targetport); return 0; }
/* open a named or unnamed pipe/fifo */ static int xioopen_fifo(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *fd, unsigned groups, int dummy1, int dummy2, int dummy3) { const char *pipename = argv[1]; int rw = (xioflags & XIO_ACCMODE); #if HAVE_STAT64 struct stat64 pipstat; #else struct stat pipstat; #endif /* !HAVE_STAT64 */ bool opt_unlink_early = false; bool opt_unlink_close = true; mode_t mode = 0666; int result; if (argc == 1) { return xioopen_fifo_unnamed(fd, fd->stream.opts); } if (argc != 2) { Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); } if (applyopts_single(&fd->stream, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); retropt_bool(opts, OPT_UNLINK_EARLY, &opt_unlink_early); applyopts_named(pipename, opts, PH_EARLY); /* umask! */ applyopts(-1, opts, PH_EARLY); if (opt_unlink_early) { if (Unlink(pipename) < 0) { if (errno == ENOENT) { Warn2("unlink(%s): %s", pipename, strerror(errno)); } else { Error2("unlink(%s): %s", pipename, strerror(errno)); return STAT_RETRYLATER; } } } retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close); retropt_modet(opts, OPT_PERM, &mode); if (applyopts_named(pipename, opts, PH_EARLY) < 0) { return STAT_RETRYLATER; } if (applyopts_named(pipename, opts, PH_PREOPEN) < 0) { return STAT_RETRYLATER; } if ( #if HAVE_STAT64 Stat64(pipename, &pipstat) < 0 #else Stat(pipename, &pipstat) < 0 #endif /* !HAVE_STAT64 */ ) { if (errno != ENOENT) { Error3("stat(\"%s\", %p): %s", pipename, &pipstat, strerror(errno)); } else { Debug1("xioopen_fifo(\"%s\"): does not exist, creating fifo", pipename); #if 0 result = Mknod(pipename, S_IFIFO|mode, 0); if (result < 0) { Error3("mknod(%s, %d, 0): %s", pipename, mode, strerror(errno)); return STAT_RETRYLATER; } #else result = Mkfifo(pipename, mode); if (result < 0) { Error3("mkfifo(%s, %d): %s", pipename, mode, strerror(errno)); return STAT_RETRYLATER; } #endif Notice2("created named pipe \"%s\" for %s", pipename, ddirection[rw]); applyopts_named(pipename, opts, PH_ALL); } if (opt_unlink_close) { if ((fd->stream.unlink_close = strdup(pipename)) == NULL) { Error1("strdup(\"%s\"): out of memory", pipename); } fd->stream.opt_unlink_close = true; } } else { /* exists */ Debug1("xioopen_fifo(\"%s\"): already exist, opening it", pipename); Notice3("opening %s \"%s\" for %s", filetypenames[(pipstat.st_mode&S_IFMT)>>12], pipename, ddirection[rw]); /*applyopts_early(pipename, opts);*/ applyopts_named(pipename, opts, PH_EARLY); } if ((result = _xioopen_open(pipename, rw, opts)) < 0) { return result; } fd->stream.fd = result; applyopts_named(pipename, opts, PH_FD); applyopts(fd->stream.fd, opts, PH_FD); applyopts_cloexec(fd->stream.fd, opts); return _xio_openlate(&fd->stream, opts); }
static int xioopen_tun(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, unsigned groups, int dummy1, int dummy2, int dummy3) { char *tundevice = NULL; char *tunname = NULL, *tuntype = NULL; int pf = /*! PF_UNSPEC*/ PF_INET; struct xiorange network; bool no_pi = false; const char *namedargv[] = { "tun", NULL, NULL }; int rw = (xioflags & XIO_ACCMODE); bool exists; struct ifreq ifr; int sockfd; char *ifaddr; int result; if (argc > 2 || argc < 0) { Error2("%s: wrong number of parameters (%d instead of 0 or 1)", argv[0], argc-1); } if (retropt_string(opts, OPT_TUN_DEVICE, &tundevice) != 0) { tundevice = strdup("/dev/net/tun"); } /*! socket option here? */ retropt_socket_pf(opts, &pf); namedargv[1] = tundevice; /* open the tun cloning device */ if ((result = _xioopen_named_early(2, namedargv, xfd, groups, &exists, opts)) < 0) { return result; } /*========================= the tunnel interface =========================*/ Notice("creating tunnel network interface"); if ((result = _xioopen_open(tundevice, rw, opts)) < 0) return result; xfd->stream.fd = result; /* prepare configuration of the new network interface */ memset(&ifr, 0,sizeof(ifr)); if (retropt_string(opts, OPT_TUN_NAME, &tunname) == 0) { strncpy(ifr.ifr_name, tunname, IFNAMSIZ); /* ok */ free(tunname); } else { ifr.ifr_name[0] = '\0'; } ifr.ifr_flags = IFF_TUN; if (retropt_string(opts, OPT_TUN_TYPE, &tuntype) == 0) { if (!strcmp(tuntype, "tap")) { ifr.ifr_flags = IFF_TAP; } else if (strcmp(tuntype, "tun")) { Error1("unknown tun-type \"%s\"", tuntype); } } if (retropt_bool(opts, OPT_IFF_NO_PI, &no_pi) == 0) { if (no_pi) { ifr.ifr_flags |= IFF_NO_PI; #if 0 /* not neccessary for now */ } else { ifr.ifr_flags &= ~IFF_NO_PI; #endif } } if (Ioctl(xfd->stream.fd, TUNSETIFF, &ifr) < 0) { Error3("ioctl(%d, TUNSETIFF, {\"%s\"}: %s", xfd->stream.fd, ifr.ifr_name, strerror(errno)); Close(xfd->stream.fd); } /*===================== setting interface properties =====================*/ /* we seem to need a socket for manipulating the interface */ if ((sockfd = Socket(PF_INET, SOCK_DGRAM, 0)) < 0) { Error1("socket(PF_INET, SOCK_DGRAM, 0): %s", strerror(errno)); sockfd = xfd->stream.fd; /* desparate fallback attempt */ } /*--------------------- setting interface address and netmask ------------*/ if (argc == 2) { if ((ifaddr = strdup(argv[1])) == NULL) { Error1("strdup(\"%s\"): out of memory", argv[1]); return STAT_RETRYLATER; } if ((result = xioparsenetwork(ifaddr, pf, &network)) != STAT_OK) { /*! recover */ return result; } socket_init(pf, (union sockaddr_union *)&ifr.ifr_addr); ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr = network.netaddr.ip4.sin_addr; if (Ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) { Error4("ioctl(%d, SIOCSIFADDR, {\"%s\", \"%s\"}: %s", sockfd, ifr.ifr_name, ifaddr, strerror(errno)); } ((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr = network.netmask.ip4.sin_addr; if (Ioctl(sockfd, SIOCSIFNETMASK, &ifr) < 0) { Error4("ioctl(%d, SIOCSIFNETMASK, {\"0x%08u\", \"%s\"}, %s", sockfd, ((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr.s_addr, ifaddr, strerror(errno)); } free(ifaddr); } /*--------------------- setting interface flags --------------------------*/ applyopts_single(&xfd->stream, opts, PH_FD); if (Ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) { Error3("ioctl(%d, SIOCGIFFLAGS, {\"%s\"}: %s", sockfd, ifr.ifr_name, strerror(errno)); } Debug2("\"%s\": system set flags: 0x%hx", ifr.ifr_name, ifr.ifr_flags); ifr.ifr_flags |= xfd->stream.para.tun.iff_opts[0]; ifr.ifr_flags &= ~xfd->stream.para.tun.iff_opts[1]; Debug2("\"%s\": xio merged flags: 0x%hx", ifr.ifr_name, ifr.ifr_flags); if (Ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) { Error4("ioctl(%d, SIOCSIFFLAGS, {\"%s\", %hd}: %s", sockfd, ifr.ifr_name, ifr.ifr_flags, strerror(errno)); } ifr.ifr_flags = 0; if (Ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) { Error3("ioctl(%d, SIOCGIFFLAGS, {\"%s\"}: %s", sockfd, ifr.ifr_name, strerror(errno)); } Debug2("\"%s\": resulting flags: 0x%hx", ifr.ifr_name, ifr.ifr_flags); #if LATER applyopts_named(tundevice, opts, PH_FD); #endif applyopts(xfd->stream.fd, opts, PH_FD); applyopts_cloexec(xfd->stream.fd, opts); applyopts_fchown(xfd->stream.fd, opts); if ((result = _xio_openlate(&xfd->stream, opts)) < 0) return result; return 0; }
/* creates the listening socket, bind, applies options; waits for incoming connection, checks its source address and port. Depending on fork option, it may fork a subprocess. pf specifies the syntax expected for range option. In the case of generic socket it is 0 (expecting raw binary data), and the real pf can be obtained from us->af_family; for other socket types pf == us->af_family Returns 0 if a connection was accepted; with fork option, this is always in a subprocess! Other return values indicate a problem; this can happen in the master process or in a subprocess. This function does not retry. If you need retries, handle this in a loop in the calling function (and always provide the options...) After fork, we set the forever/retry of the child process to 0 applies and consumes the following option: PH_INIT, PH_PASTSOCKET, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, PH_PREOPEN, PH_FD, PH_CONNECTED, PH_LATE, PH_LATE2 OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_BACKLOG, OPT_RANGE, tcpwrap, OPT_SOURCEPORT, OPT_LOWPORT, cloexec */ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int socktype, int proto, int level) { struct sockaddr sa; socklen_t salen; int backlog = 5; /* why? 1 seems to cause problems under some load */ char *rangename; bool dofork = false; int maxchildren = 0; char infobuff[256]; char lisname[256]; union sockaddr_union _peername; union sockaddr_union _sockname; union sockaddr_union *pa = &_peername; /* peer address */ union sockaddr_union *la = &_sockname; /* local address */ socklen_t pas = sizeof(_peername); /* peer address size */ socklen_t las = sizeof(_sockname); /* local address size */ int result; retropt_bool(opts, OPT_FORK, &dofork); if (dofork) { if (!(xioflags & XIO_MAYFORK)) { Error("option fork not allowed here"); return STAT_NORETRY; } xfd->flags |= XIO_DOESFORK; } retropt_int(opts, OPT_MAX_CHILDREN, &maxchildren); if (! dofork && maxchildren) { Error("option max-children not allowed without option fork"); return STAT_NORETRY; } if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } if ((xfd->fd = xiosocket(opts, us->sa_family, socktype, proto, level)) < 0) { return STAT_RETRYLATER; } applyopts_cloexec(xfd->fd, opts); applyopts(xfd->fd, opts, PH_PREBIND); applyopts(xfd->fd, opts, PH_BIND); if (Bind(xfd->fd, (struct sockaddr *)us, uslen) < 0) { Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd, sockaddr_info(us, uslen, infobuff, sizeof(infobuff)), uslen, strerror(errno)); Close(xfd->fd); return STAT_RETRYLATER; } #if WITH_UNIX if (us->sa_family == AF_UNIX) { applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD); } #endif /* under some circumstances (e.g., TCP listen on port 0) bind() fills empty fields that we want to know. */ salen = sizeof(sa); if (Getsockname(xfd->fd, us, &uslen) < 0) { Warn4("getsockname(%d, %p, {%d}): %s", xfd->fd, &us, uslen, strerror(errno)); } applyopts(xfd->fd, opts, PH_PASTBIND); #if WITH_UNIX if (us->sa_family == AF_UNIX) { /*applyopts_early(((struct sockaddr_un *)us)->sun_path, opts);*/ applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY); applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN); } #endif /* WITH_UNIX */ #if WITH_IP4 /*|| WITH_IP6*/ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &xfd->para.socket.range) < 0) { free(rangename); return STAT_NORETRY; } free(rangename); xfd->para.socket.dorange = true; } #endif #if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP xio_retropt_tcpwrap(xfd, opts); #endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */ #if WITH_TCP || WITH_UDP if (retropt_ushort(opts, OPT_SOURCEPORT, &xfd->para.socket.ip.sourceport) >= 0) { xfd->para.socket.ip.dosourceport = true; } retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport); #endif /* WITH_TCP || WITH_UDP */ applyopts(xfd->fd, opts, PH_PRELISTEN); retropt_int(opts, OPT_BACKLOG, &backlog); if (Listen(xfd->fd, backlog) < 0) { Error3("listen(%d, %d): %s", xfd->fd, backlog, strerror(errno)); return STAT_RETRYLATER; } if (xioopts.logopt == 'm') { Info("starting accept loop, switching to syslog"); diag_set('y', xioopts.syslogfac); xioopts.logopt = 'y'; } else { Info("starting accept loop"); } while (true) { /* but we only loop if fork option is set */ char peername[256]; char sockname[256]; int ps; /* peer socket */ pa = &_peername; la = &_sockname; salen = sizeof(struct sockaddr); do { /*? int level = E_ERROR;*/ Notice1("listening on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname))); ps = Accept(xfd->fd, (struct sockaddr *)&sa, &salen); if (ps >= 0) { /*0 Info4("accept(%d, %p, {"F_Zu"}) -> %d", xfd->fd, &sa, salen, ps);*/ break; /* success, break out of loop */ } if (errno == EINTR) { continue; } if (errno == ECONNABORTED) { Notice4("accept(%d, %p, {"F_socklen"}): %s", xfd->fd, &sa, salen, strerror(errno)); continue; } Msg4(level, "accept(%d, %p, {"F_socklen"}): %s", xfd->fd, &sa, salen, strerror(errno)); Close(xfd->fd); return STAT_RETRYLATER; } while (true); applyopts_cloexec(ps, opts); if (Getpeername(ps, &pa->soa, &pas) < 0) { Warn4("getpeername(%d, %p, {"F_socklen"}): %s", ps, pa, pas, strerror(errno)); pa = NULL; } if (Getsockname(ps, &la->soa, &las) < 0) { Warn4("getsockname(%d, %p, {"F_socklen"}): %s", ps, la, las, strerror(errno)); la = NULL; } Notice2("accepting connection from %s on %s", pa? sockaddr_info(&pa->soa, pas, peername, sizeof(peername)):"NULL", la? sockaddr_info(&la->soa, las, sockname, sizeof(sockname)):"NULL"); if (pa != NULL && la != NULL && xiocheckpeer(xfd, pa, la) < 0) { if (Shutdown(ps, 2) < 0) { Info2("shutdown(%d, 2): %s", ps, strerror(errno)); } Close(ps); continue; } if (pa != NULL) Info1("permitting connection from %s", sockaddr_info((struct sockaddr *)pa, pas, infobuff, sizeof(infobuff))); if (dofork) { pid_t pid; /* mostly int; only used with fork */ sigset_t mask_sigchld; /* we must prevent that the current packet triggers another fork; therefore we wait for a signal from the recent child: USR1 indicates that is has consumed the last packet; CHLD means it has terminated */ /* block SIGCHLD and SIGUSR1 until parent is ready to react */ sigemptyset(&mask_sigchld); sigaddset(&mask_sigchld, SIGCHLD); Sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); if ((pid = xio_fork(false, level==E_ERROR?level:E_WARN)) < 0) { Close(xfd->fd); Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); return STAT_RETRYLATER; } if (pid == 0) { /* child */ pid_t cpid = Getpid(); Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); Info1("just born: child process "F_pid, cpid); xiosetenvulong("PID", cpid, 1); if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } xfd->fd = ps; #if WITH_RETRY /* !? */ xfd->forever = false; xfd->retry = 0; level = E_ERROR; #endif /* WITH_RETRY */ break; } /* server: continue loop with listen */ /* shutdown() closes the socket even for the child process, but close() does what we want */ if (Close(ps) < 0) { Info2("close(%d): %s", ps, strerror(errno)); } /* now we are ready to handle signals */ Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); while (maxchildren) { if (num_child < maxchildren) break; Notice("maxchildren are active, waiting"); /* UINT_MAX would even be nicer, but Openindiana works only with 31 bits */ while (!Sleep(INT_MAX)) ; /* any signal lets us continue */ } Info("still listening"); } else { if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } xfd->fd = ps; break; } } applyopts(xfd->fd, opts, PH_FD); applyopts(xfd->fd, opts, PH_PASTSOCKET); applyopts(xfd->fd, opts, PH_CONNECTED); if ((result = _xio_openlate(xfd, opts)) < 0) return result; /* set the env vars describing the local and remote sockets */ if (la != NULL) xiosetsockaddrenv("SOCK", la, las, proto); if (pa != NULL) xiosetsockaddrenv("PEER", pa, pas, proto); return 0; }
static int xioopen_socks4_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, unsigned groups, int socks4a, int dummy2, int dummy3) { /* we expect the form: host:host:port */ struct single *xfd = &xxfd->stream; struct opt *opts0 = NULL; const char *sockdname; char *socksport; const char *targetname, *targetport; int pf = PF_UNSPEC; int ipproto = IPPROTO_TCP; bool dofork = false; union sockaddr_union us_sa, *us = &us_sa; union sockaddr_union them_sa, *them = &them_sa; socklen_t uslen = sizeof(us_sa); socklen_t themlen = sizeof(them_sa); bool needbind = false; bool lowport = false; unsigned char buff[BUFF_LEN]; struct socks4 *sockhead = (struct socks4 *)buff; size_t buflen = sizeof(buff); int socktype = SOCK_STREAM; int level; int result; if (argc != 4) { Error1("%s: 3 parameters required", argv[0]); return STAT_NORETRY; } sockdname = argv[1]; targetname = argv[2]; targetport = argv[3]; xfd->howtoend = END_SHUTDOWN; if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; applyopts(-1, opts, PH_INIT); retropt_int(opts, OPT_SO_TYPE, &socktype); retropt_bool(opts, OPT_FORK, &dofork); result = _xioopen_socks4_prepare(targetport, opts, &socksport, sockhead, &buflen); if (result != STAT_OK) return result; result = _xioopen_ipapp_prepare(opts, &opts0, sockdname, socksport, &pf, ipproto, xfd->para.socket.ip.res_opts[1], xfd->para.socket.ip.res_opts[0], them, &themlen, us, &uslen, &needbind, &lowport, socktype); Notice5("opening connection to %s:%u via socks4 server %s:%s as user \"%s\"", targetname, ntohs(sockhead->port), sockdname, socksport, sockhead->userid); do { /* loop over failed connect and socks-request attempts */ #if WITH_RETRY if (xfd->forever || xfd->retry) { level = E_INFO; } else #endif /* WITH_RETRY */ level = E_ERROR; /* we try to resolve the target address _before_ connecting to the socks server: this avoids unnecessary socks connects and timeouts */ result = _xioopen_socks4_connect0(xfd, targetname, socks4a, sockhead, (ssize_t *)&buflen, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: case STAT_RETRYNOW: if (xfd->forever || xfd->retry--) { if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL); continue; } #endif /* WITH_RETRY */ default: return result; } /* this cannot fork because we retrieved fork option above */ result = _xioopen_connect (xfd, needbind?(struct sockaddr *)us:NULL, sizeof(*us), (struct sockaddr *)them, themlen, opts, pf, socktype, IPPROTO_TCP, lowport, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: case STAT_RETRYNOW: if (xfd->forever || xfd->retry--) { if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL); continue; } #endif /* WITH_RETRY */ default: return result; } applyopts(xfd->fd, opts, PH_ALL); if ((result = _xio_openlate(xfd, opts)) < 0) return result; result = _xioopen_socks4_connect(xfd, sockhead, buflen, level); switch (result) { case STAT_OK: break; #if WITH_RETRY case STAT_RETRYLATER: case STAT_RETRYNOW: if (xfd->forever || xfd->retry--) { if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL); continue; } #endif /* WITH_RETRY */ default: return result; } if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } #if WITH_RETRY if (dofork) { pid_t pid; int level = E_ERROR; if (xfd->forever || xfd->retry) { level = E_WARN; /* most users won't expect a problem here, so Notice is too weak */ } while ((pid = xio_fork(false, level)) < 0) { if (xfd->forever || --xfd->retry) { Nanosleep(&xfd->intervall, NULL); continue; } return STAT_RETRYLATER; } if (pid == 0) { /* child process */ xfd->forever = false; xfd->retry = 0; break; } /* parent process */ Close(xfd->fd); Nanosleep(&xfd->intervall, NULL); dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); continue; } else #endif /* WITH_RETRY */ { break; } } while (true); /* end of complete open loop - drop out on success */ return 0; }
/* process a bidirectional "stdio" or "-" argument with options. */ int xioopen_stdio_bi(xiofile_t *sock) { struct opt *opts1, *opts2, *optspr; unsigned int groups1 = xioaddr_stdio0.groups, groups2 = xioaddr_stdio0.groups; int result; sock->stream.rfd = 0 /*stdin*/; sock->stream.wfd = 1 /*stdout*/; #if WITH_TERMIOS if (Isatty(sock->stream.rfd)) { if (Tcgetattr(sock->stream.rfd, &sock->stream.savetty) < 0) { Warn2("cannot query current terminal settings on fd %d: %s", sock->stream.rfd, strerror(errno)); } else { sock->stream.ttyvalid = true; } } if (Isatty(sock->stream.wfd) && (sock->stream.wfd != sock->stream.rfd)) { if (Tcgetattr(sock->stream.wfd, &sock->stream.savetty) < 0) { Warn2("cannot query current terminal settings on fd %d: %s", sock->stream.wfd, strerror(errno)); } else { sock->stream.ttyvalid = true; } } #endif /* WITH_TERMIOS */ if (applyopts_single(&sock->stream, sock->stream.opts, PH_INIT) < 0) return -1; applyopts(-1, sock->stream.opts, PH_INIT); if (sock->stream.howtoshut == XIOSHUT_UNSPEC) sock->stream.howtoshut = XIOSHUT_NONE; /* options here are one-time and one-direction, no second use */ retropt_bool(sock->stream.opts, OPT_IGNOREEOF, &sock->stream.ignoreeof); /* extract opts that should be applied only once */ if ((optspr = copyopts(sock->stream.opts, GROUP_PROCESS)) == NULL) { return -1; } if ((result = applyopts(-1, optspr, PH_EARLY)) < 0) return result; if ((result = applyopts(-1, optspr, PH_PREOPEN)) < 0) return result; /* here we copy opts, because most have to be applied twice! */ if ((opts1 = copyopts(sock->stream.opts, GROUP_FD|GROUP_APPL|(groups1&~GROUP_PROCESS))) == NULL) { return -1; } /* apply options to first FD */ if ((result = applyopts(sock->stream.rfd, opts1, PH_ALL)) < 0) { return result; } if ((result = _xio_openlate(&sock->stream, opts1)) < 0) { return result; } if ((opts2 = copyopts(sock->stream.opts, GROUP_FD|GROUP_APPL|(groups2&~GROUP_PROCESS))) == NULL) { return -1; } /* apply options to second FD */ if ((result = applyopts(sock->stream.wfd, opts2, PH_ALL)) < 0) { return result; } if ((result = _xio_openlate(&sock->stream, opts2)) < 0) { return result; } if ((result = _xio_openlate(&sock->stream, optspr)) < 0) { return result; } Notice("reading from and writing to stdio"); return 0; }