1 /* accept - listen for and accept a remote network connection on a given port */
2 
3 /*
4    Copyright (C) 2020 Free Software Foundation, Inc.
5 
6    This file is part of GNU Bash.
7    Bash is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11 
12    Bash is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with Bash.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <config.h>
22 
23 #if defined (HAVE_UNISTD_H)
24 #  include <unistd.h>
25 #endif
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include "bashtypes.h"
31 #include <errno.h>
32 #include <time.h>
33 #include "typemax.h"
34 
35 #include <sys/socket.h>
36 #include <arpa/inet.h>
37 #include <netinet/in.h>
38 
39 #include "loadables.h"
40 
41 static int accept_bind_variable (char *, int);
42 
43 int
accept_builtin(list)44 accept_builtin (list)
45      WORD_LIST *list;
46 {
47   WORD_LIST *l;
48   SHELL_VAR *v;
49   intmax_t iport;
50   int opt;
51   char *tmoutarg, *fdvar, *rhostvar, *rhost;
52   unsigned short uport;
53   int servsock, clisock;
54   struct sockaddr_in server, client;
55   socklen_t clientlen;
56   struct timeval timeval;
57   struct linger linger = { 0, 0 };
58 
59   rhostvar = tmoutarg = fdvar = rhost = (char *)NULL;
60 
61   reset_internal_getopt ();
62   while ((opt = internal_getopt (list, "r:t:v:")) != -1)
63     {
64       switch (opt)
65 	{
66 	case 'r':
67 	  rhostvar = list_optarg;
68 	  break;
69 	case 't':
70 	  tmoutarg = list_optarg;
71 	  break;
72 	case 'v':
73 	  fdvar = list_optarg;
74 	  break;
75 	CASE_HELPOPT;
76 	default:
77 	  builtin_usage ();
78 	  return (EX_USAGE);
79 	}
80     }
81 
82   list = loptend;
83 
84   /* Validate input and variables */
85   if (tmoutarg)
86     {
87       long ival, uval;
88       opt = uconvert (tmoutarg, &ival, &uval, (char **)0);
89       if (opt == 0 || ival < 0 || uval < 0)
90 	{
91 	  builtin_error ("%s: invalid timeout specification", tmoutarg);
92 	  return (EXECUTION_FAILURE);
93 	}
94       timeval.tv_sec = ival;
95       timeval.tv_usec = uval;
96       /* XXX - should we warn if ival == uval == 0 ? */
97     }
98 
99   if (list == 0)
100     {
101       builtin_usage ();
102       return (EX_USAGE);
103     }
104 
105   if (legal_number (list->word->word, &iport) == 0 || iport < 0 || iport > TYPE_MAXIMUM (unsigned short))
106     {
107       builtin_error ("%s: invalid port number", list->word->word);
108       return (EXECUTION_FAILURE);
109     }
110   uport = (unsigned short)iport;
111 
112   if (fdvar == 0)
113     fdvar = "ACCEPT_FD";
114 
115   unbind_variable (fdvar);
116   if (rhostvar)
117     unbind_variable (rhostvar);
118 
119   if ((servsock = socket (AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0)
120     {
121       builtin_error ("cannot create socket: %s", strerror (errno));
122       return (EXECUTION_FAILURE);
123     }
124 
125   memset ((char *)&server, 0, sizeof (server));
126   server.sin_family = AF_INET;
127   server.sin_port = htons(uport);
128   server.sin_addr.s_addr = htonl(INADDR_ANY);
129 
130   if (bind (servsock, (struct sockaddr *)&server, sizeof (server)) < 0)
131     {
132       builtin_error ("socket bind failure: %s", strerror (errno));
133       close (servsock);
134       return (EXECUTION_FAILURE);
135     }
136 
137   opt = 1;
138   setsockopt (servsock, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof (opt));
139   setsockopt (servsock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof (linger));
140 
141   if (listen (servsock, 1) < 0)
142     {
143       builtin_error ("listen failure: %s", strerror (errno));
144       close (servsock);
145       return (EXECUTION_FAILURE);
146     }
147 
148   if (tmoutarg)
149     {
150       fd_set iofds;
151 
152       FD_ZERO(&iofds);
153       FD_SET(servsock, &iofds);
154 
155       opt = select (servsock+1, &iofds, 0, 0, &timeval);
156       if (opt < 0)
157         builtin_error ("select failure: %s", strerror (errno));
158       if (opt <= 0)
159         {
160           close (servsock);
161           return (EXECUTION_FAILURE);
162         }
163     }
164 
165   clientlen = sizeof (client);
166   if ((clisock = accept (servsock, (struct sockaddr *)&client, &clientlen)) < 0)
167     {
168       builtin_error ("client accept failure: %s", strerror (errno));
169       close (servsock);
170       return (EXECUTION_FAILURE);
171     }
172 
173   close (servsock);
174 
175   accept_bind_variable (fdvar, clisock);
176   if (rhostvar)
177     {
178       rhost = inet_ntoa (client.sin_addr);
179       v = builtin_bind_variable (rhostvar, rhost, 0);
180       if (v == 0 || readonly_p (v) || noassign_p (v))
181 	builtin_error ("%s: cannot set variable", rhostvar);
182     }
183 
184   return (EXECUTION_SUCCESS);
185 }
186 
187 static int
accept_bind_variable(varname,intval)188 accept_bind_variable (varname, intval)
189      char *varname;
190      int intval;
191 {
192   SHELL_VAR *v;
193   char ibuf[INT_STRLEN_BOUND (int) + 1], *p;
194 
195   p = fmtulong (intval, 10, ibuf, sizeof (ibuf), 0);
196   v = builtin_bind_variable (varname, p, 0);
197   if (v == 0 || readonly_p (v) || noassign_p (v))
198     builtin_error ("%s: cannot set variable", varname);
199   return (v != 0);
200 }
201 
202 char *accept_doc[] = {
203 	"Accept a network connection on a specified port.",
204 	""
205 	"This builtin allows a bash script to act as a TCP/IP server.",
206 	"",
207 	"Options, if supplied, have the following meanings:",
208 	"    -t timeout    wait TIMEOUT seconds for a connection. TIMEOUT may",
209 	"                  be a decimal number including a fractional portion",
210 	"    -v varname    store the numeric file descriptor of the connected",
211 	"                  socket into VARNAME. The default VARNAME is ACCEPT_FD",
212 	"    -r rhost      store the IP address of the remote host into the shell",
213 	"                  variable RHOST, in dotted-decimal notation",
214 	"",
215 	"If successful, the shell variable ACCEPT_FD, or the variable named by the",
216 	"-v option, will be set to the fd of the connected socket, suitable for",
217 	"use as 'read -u$ACCEPT_FD'. RHOST, if supplied, will hold the IP address",
218 	"of the remote client. The return status is 0.",
219 	"",
220 	"On failure, the return status is 1 and ACCEPT_FD (or VARNAME) and RHOST,",
221 	"if supplied, will be unset.",
222 	"",
223 	"The server socket fd will be closed before accept returns.",
224 	(char *) NULL
225 };
226 
227 struct builtin accept_struct = {
228 	"accept",		/* builtin name */
229 	accept_builtin,		/* function implementing the builtin */
230 	BUILTIN_ENABLED,	/* initial flags for builtin */
231 	accept_doc,		/* array of long documentation strings. */
232 	"accept [-t timeout] [-v varname] [-r addrvar ] port",		/* usage synopsis; becomes short_doc */
233 	0			/* reserved for internal use */
234 };
235