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