File: | slirp/tcp_subr.c |
Location: | line 433, column 18 |
Description: | Assigned value is garbage or undefined |
1 | /* | |||
2 | * Copyright (c) 1982, 1986, 1988, 1990, 1993 | |||
3 | * The Regents of the University of California. All rights reserved. | |||
4 | * | |||
5 | * Redistribution and use in source and binary forms, with or without | |||
6 | * modification, are permitted provided that the following conditions | |||
7 | * are met: | |||
8 | * 1. Redistributions of source code must retain the above copyright | |||
9 | * notice, this list of conditions and the following disclaimer. | |||
10 | * 2. Redistributions in binary form must reproduce the above copyright | |||
11 | * notice, this list of conditions and the following disclaimer in the | |||
12 | * documentation and/or other materials provided with the distribution. | |||
13 | * 3. Neither the name of the University nor the names of its contributors | |||
14 | * may be used to endorse or promote products derived from this software | |||
15 | * without specific prior written permission. | |||
16 | * | |||
17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |||
18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |||
21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||
23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||
24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||
25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||
26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||
27 | * SUCH DAMAGE. | |||
28 | * | |||
29 | * @(#)tcp_subr.c 8.1 (Berkeley) 6/10/93 | |||
30 | * tcp_subr.c,v 1.5 1994/10/08 22:39:58 phk Exp | |||
31 | */ | |||
32 | ||||
33 | /* | |||
34 | * Changes and additions relating to SLiRP | |||
35 | * Copyright (c) 1995 Danny Gasparovski. | |||
36 | * | |||
37 | * Please read the file COPYRIGHT for the | |||
38 | * terms and conditions of the copyright. | |||
39 | */ | |||
40 | ||||
41 | #include <slirp.h> | |||
42 | ||||
43 | /* patchable/settable parameters for tcp */ | |||
44 | /* Don't do rfc1323 performance enhancements */ | |||
45 | #define TCP_DO_RFC13230 0 | |||
46 | ||||
47 | /* | |||
48 | * Tcp initialization | |||
49 | */ | |||
50 | void | |||
51 | tcp_init(Slirp *slirp) | |||
52 | { | |||
53 | slirp->tcp_iss = 1; /* wrong */ | |||
54 | slirp->tcb.so_next = slirp->tcb.so_prev = &slirp->tcb; | |||
55 | slirp->tcp_last_so = &slirp->tcb; | |||
56 | } | |||
57 | ||||
58 | void tcp_cleanup(Slirp *slirp) | |||
59 | { | |||
60 | while (slirp->tcb.so_next != &slirp->tcb) { | |||
61 | tcp_close(sototcpcb(slirp->tcb.so_next)((slirp->tcb.so_next)->so_tcpcb)); | |||
62 | } | |||
63 | } | |||
64 | ||||
65 | /* | |||
66 | * Create template to be used to send tcp packets on a connection. | |||
67 | * Call after host entry created, fills | |||
68 | * in a skeletal tcp/ip header, minimizing the amount of work | |||
69 | * necessary when the connection is used. | |||
70 | */ | |||
71 | void | |||
72 | tcp_template(struct tcpcb *tp) | |||
73 | { | |||
74 | struct socket *so = tp->t_socket; | |||
75 | register struct tcpiphdr *n = &tp->t_template; | |||
76 | ||||
77 | n->ti_mbufti_i.ih_mbuf.mptr = NULL((void*)0); | |||
78 | n->ti_x1ti_i.ih_x1 = 0; | |||
79 | n->ti_prti_i.ih_pr = IPPROTO_TCPIPPROTO_TCP; | |||
80 | n->ti_lenti_i.ih_len = htons(sizeof (struct tcpiphdr) - sizeof (struct ip)); | |||
81 | n->ti_srcti_i.ih_src = so->so_faddr; | |||
82 | n->ti_dstti_i.ih_dst = so->so_laddr; | |||
83 | n->ti_sportti_t.th_sport = so->so_fport; | |||
84 | n->ti_dportti_t.th_dport = so->so_lport; | |||
85 | ||||
86 | n->ti_seqti_t.th_seq = 0; | |||
87 | n->ti_ackti_t.th_ack = 0; | |||
88 | n->ti_x2ti_t.th_x2 = 0; | |||
89 | n->ti_offti_t.th_off = 5; | |||
90 | n->ti_flagsti_t.th_flags = 0; | |||
91 | n->ti_winti_t.th_win = 0; | |||
92 | n->ti_sumti_t.th_sum = 0; | |||
93 | n->ti_urpti_t.th_urp = 0; | |||
94 | } | |||
95 | ||||
96 | /* | |||
97 | * Send a single message to the TCP at address specified by | |||
98 | * the given TCP/IP header. If m == 0, then we make a copy | |||
99 | * of the tcpiphdr at ti and send directly to the addressed host. | |||
100 | * This is used to force keep alive messages out using the TCP | |||
101 | * template for a connection tp->t_template. If flags are given | |||
102 | * then we send a message back to the TCP which originated the | |||
103 | * segment ti, and discard the mbuf containing it and any other | |||
104 | * attached mbufs. | |||
105 | * | |||
106 | * In any case the ack and sequence number of the transmitted | |||
107 | * segment are as specified by the parameters. | |||
108 | */ | |||
109 | void | |||
110 | tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, | |||
111 | tcp_seq ack, tcp_seq seq, int flags) | |||
112 | { | |||
113 | register int tlen; | |||
114 | int win = 0; | |||
115 | ||||
116 | DEBUG_CALL("tcp_respond"); | |||
117 | DEBUG_ARG("tp = %p", tp); | |||
118 | DEBUG_ARG("ti = %p", ti); | |||
119 | DEBUG_ARG("m = %p", m); | |||
120 | DEBUG_ARG("ack = %u", ack); | |||
121 | DEBUG_ARG("seq = %u", seq); | |||
122 | DEBUG_ARG("flags = %x", flags); | |||
123 | ||||
124 | if (tp) | |||
125 | win = sbspace(&tp->t_socket->so_rcv)((&tp->t_socket->so_rcv)->sb_datalen - (&tp-> t_socket->so_rcv)->sb_cc); | |||
126 | if (m == NULL((void*)0)) { | |||
127 | if (!tp || (m = m_get(tp->t_socket->slirp)) == NULL((void*)0)) | |||
128 | return; | |||
129 | tlen = 0; | |||
130 | m->m_data += IF_MAXLINKHDR(2 + 14 + 40); | |||
131 | *mtod(m, struct tcpiphdr *)((struct tcpiphdr *)(m)->m_data) = *ti; | |||
132 | ti = mtod(m, struct tcpiphdr *)((struct tcpiphdr *)(m)->m_data); | |||
133 | flags = TH_ACK0x10; | |||
134 | } else { | |||
135 | /* | |||
136 | * ti points into m so the next line is just making | |||
137 | * the mbuf point to ti | |||
138 | */ | |||
139 | m->m_data = (caddr_t)ti; | |||
140 | ||||
141 | m->m_len = sizeof (struct tcpiphdr); | |||
142 | tlen = 0; | |||
143 | #define xchg(a,b,type) { type t; t=a; a=b; b=t; } | |||
144 | xchg(ti->ti_dstti_i.ih_dst.s_addr, ti->ti_srcti_i.ih_src.s_addr, uint32_t); | |||
145 | xchg(ti->ti_dportti_t.th_dport, ti->ti_sportti_t.th_sport, uint16_t); | |||
146 | #undef xchg | |||
147 | } | |||
148 | ti->ti_lenti_i.ih_len = htons((u_short)(sizeof (struct tcphdrslirp_tcphdr) + tlen)); | |||
149 | tlen += sizeof (struct tcpiphdr); | |||
150 | m->m_len = tlen; | |||
151 | ||||
152 | ti->ti_mbufti_i.ih_mbuf.mptr = NULL((void*)0); | |||
153 | ti->ti_x1ti_i.ih_x1 = 0; | |||
154 | ti->ti_seqti_t.th_seq = htonl(seq); | |||
155 | ti->ti_ackti_t.th_ack = htonl(ack); | |||
156 | ti->ti_x2ti_t.th_x2 = 0; | |||
157 | ti->ti_offti_t.th_off = sizeof (struct tcphdrslirp_tcphdr) >> 2; | |||
158 | ti->ti_flagsti_t.th_flags = flags; | |||
159 | if (tp) | |||
160 | ti->ti_winti_t.th_win = htons((uint16_t) (win >> tp->rcv_scale)); | |||
161 | else | |||
162 | ti->ti_winti_t.th_win = htons((uint16_t)win); | |||
163 | ti->ti_urpti_t.th_urp = 0; | |||
164 | ti->ti_sumti_t.th_sum = 0; | |||
165 | ti->ti_sumti_t.th_sum = cksum(m, tlen); | |||
166 | ((struct ip *)ti)->ip_len = tlen; | |||
167 | ||||
168 | if(flags & TH_RST0x04) | |||
169 | ((struct ip *)ti)->ip_ttl = MAXTTL255; | |||
170 | else | |||
171 | ((struct ip *)ti)->ip_ttl = IPDEFTTL64; | |||
172 | ||||
173 | (void) ip_output((struct socket *)0, m); | |||
174 | } | |||
175 | ||||
176 | /* | |||
177 | * Create a new TCP control block, making an | |||
178 | * empty reassembly queue and hooking it to the argument | |||
179 | * protocol control block. | |||
180 | */ | |||
181 | struct tcpcb * | |||
182 | tcp_newtcpcb(struct socket *so) | |||
183 | { | |||
184 | register struct tcpcb *tp; | |||
185 | ||||
186 | tp = (struct tcpcb *)malloc(sizeof(*tp)); | |||
187 | if (tp == NULL((void*)0)) | |||
188 | return ((struct tcpcb *)0); | |||
189 | ||||
190 | memset((char *) tp, 0, sizeof(struct tcpcb)); | |||
191 | tp->seg_next = tp->seg_prev = (struct tcpiphdr*)tp; | |||
192 | tp->t_maxseg = TCP_MSS1460; | |||
193 | ||||
194 | tp->t_flags = TCP_DO_RFC13230 ? (TF_REQ_SCALE0x0020|TF_REQ_TSTMP0x0080) : 0; | |||
195 | tp->t_socket = so; | |||
196 | ||||
197 | /* | |||
198 | * Init srtt to TCPTV_SRTTBASE (0), so we can tell that we have no | |||
199 | * rtt estimate. Set rttvar so that srtt + 2 * rttvar gives | |||
200 | * reasonable initial retransmit time. | |||
201 | */ | |||
202 | tp->t_srtt = TCPTV_SRTTBASE0; | |||
203 | tp->t_rttvar = TCPTV_SRTTDFLT( 3*2) << 2; | |||
204 | tp->t_rttmin = TCPTV_MIN( 1*2); | |||
205 | ||||
206 | TCPT_RANGESET(tp->t_rxtcur,{ (tp->t_rxtcur) = (((0 >> 2) + (( 3*2) << 2)) >> 1); if ((tp->t_rxtcur) < (( 1*2))) (tp->t_rxtcur ) = (( 1*2)); else if ((tp->t_rxtcur) > (( 12*2))) (tp-> t_rxtcur) = (( 12*2)); } | |||
207 | ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1,{ (tp->t_rxtcur) = (((0 >> 2) + (( 3*2) << 2)) >> 1); if ((tp->t_rxtcur) < (( 1*2))) (tp->t_rxtcur ) = (( 1*2)); else if ((tp->t_rxtcur) > (( 12*2))) (tp-> t_rxtcur) = (( 12*2)); } | |||
208 | TCPTV_MIN, TCPTV_REXMTMAX){ (tp->t_rxtcur) = (((0 >> 2) + (( 3*2) << 2)) >> 1); if ((tp->t_rxtcur) < (( 1*2))) (tp->t_rxtcur ) = (( 1*2)); else if ((tp->t_rxtcur) > (( 12*2))) (tp-> t_rxtcur) = (( 12*2)); }; | |||
209 | ||||
210 | tp->snd_cwnd = TCP_MAXWIN65535 << TCP_MAX_WINSHIFT14; | |||
211 | tp->snd_ssthresh = TCP_MAXWIN65535 << TCP_MAX_WINSHIFT14; | |||
212 | tp->t_state = TCPS_CLOSED0; | |||
213 | ||||
214 | so->so_tcpcb = tp; | |||
215 | ||||
216 | return (tp); | |||
217 | } | |||
218 | ||||
219 | /* | |||
220 | * Drop a TCP connection, reporting | |||
221 | * the specified error. If connection is synchronized, | |||
222 | * then send a RST to peer. | |||
223 | */ | |||
224 | struct tcpcb *tcp_drop(struct tcpcb *tp, int err) | |||
225 | { | |||
226 | DEBUG_CALL("tcp_drop"); | |||
227 | DEBUG_ARG("tp = %lx", (long)tp); | |||
228 | DEBUG_ARG("errno = %d", errno); | |||
229 | ||||
230 | if (TCPS_HAVERCVDSYN(tp->t_state)((tp->t_state) >= 3)) { | |||
231 | tp->t_state = TCPS_CLOSED0; | |||
232 | (void) tcp_output(tp); | |||
233 | } | |||
234 | return (tcp_close(tp)); | |||
235 | } | |||
236 | ||||
237 | /* | |||
238 | * Close a TCP control block: | |||
239 | * discard all space held by the tcp | |||
240 | * discard internet protocol block | |||
241 | * wake up any sleepers | |||
242 | */ | |||
243 | struct tcpcb * | |||
244 | tcp_close(struct tcpcb *tp) | |||
245 | { | |||
246 | register struct tcpiphdr *t; | |||
247 | struct socket *so = tp->t_socket; | |||
248 | Slirp *slirp = so->slirp; | |||
249 | register struct mbuf *m; | |||
250 | ||||
251 | DEBUG_CALL("tcp_close"); | |||
252 | DEBUG_ARG("tp = %lx", (long )tp); | |||
253 | ||||
254 | /* free the reassembly queue, if any */ | |||
255 | t = tcpfrag_list_first(tp)((struct tcpiphdr*)(((char*)((tp)->seg_next)) + sizeof(struct qlink))); | |||
256 | while (!tcpfrag_list_end(t, tp)(((struct qlink*)(((char*)(t)) - sizeof(struct qlink))) == (struct qlink*)(tp))) { | |||
257 | t = tcpiphdr_next(t)((struct tcpiphdr*)(((char*)(((struct qlink*)(((char*)(t)) - sizeof (struct qlink)))->next)) + sizeof(struct qlink))); | |||
258 | m = tcpiphdr_prev(t)((struct tcpiphdr*)(((char*)(((struct qlink*)(((char*)(t)) - sizeof (struct qlink)))->prev)) + sizeof(struct qlink)))->ti_mbufti_i.ih_mbuf.mptr; | |||
259 | remqueslirp_remque(tcpiphdr2qlink(tcpiphdr_prev(t))((struct qlink*)(((char*)(((struct tcpiphdr*)(((char*)(((struct qlink*)(((char*)(t)) - sizeof(struct qlink)))->prev)) + sizeof (struct qlink))))) - sizeof(struct qlink)))); | |||
260 | m_free(m); | |||
261 | } | |||
262 | free(tp); | |||
263 | so->so_tcpcb = NULL((void*)0); | |||
264 | /* clobber input socket cache if we're closing the cached connection */ | |||
265 | if (so == slirp->tcp_last_so) | |||
266 | slirp->tcp_last_so = &slirp->tcb; | |||
267 | closesocket(so->s)close(so->s); | |||
268 | sbfree(&so->so_rcv); | |||
269 | sbfree(&so->so_snd); | |||
270 | sofree(so); | |||
271 | return ((struct tcpcb *)0); | |||
272 | } | |||
273 | ||||
274 | /* | |||
275 | * TCP protocol interface to socket abstraction. | |||
276 | */ | |||
277 | ||||
278 | /* | |||
279 | * User issued close, and wish to trail through shutdown states: | |||
280 | * if never received SYN, just forget it. If got a SYN from peer, | |||
281 | * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN. | |||
282 | * If already got a FIN from peer, then almost done; go to LAST_ACK | |||
283 | * state. In all other cases, have already sent FIN to peer (e.g. | |||
284 | * after PRU_SHUTDOWN), and just have to play tedious game waiting | |||
285 | * for peer to send FIN or not respond to keep-alives, etc. | |||
286 | * We can let the user exit from the close as soon as the FIN is acked. | |||
287 | */ | |||
288 | void | |||
289 | tcp_sockclosed(struct tcpcb *tp) | |||
290 | { | |||
291 | ||||
292 | DEBUG_CALL("tcp_sockclosed"); | |||
293 | DEBUG_ARG("tp = %lx", (long)tp); | |||
294 | ||||
295 | switch (tp->t_state) { | |||
296 | ||||
297 | case TCPS_CLOSED0: | |||
298 | case TCPS_LISTEN1: | |||
299 | case TCPS_SYN_SENT2: | |||
300 | tp->t_state = TCPS_CLOSED0; | |||
301 | tp = tcp_close(tp); | |||
302 | break; | |||
303 | ||||
304 | case TCPS_SYN_RECEIVED3: | |||
305 | case TCPS_ESTABLISHED4: | |||
306 | tp->t_state = TCPS_FIN_WAIT_16; | |||
307 | break; | |||
308 | ||||
309 | case TCPS_CLOSE_WAIT5: | |||
310 | tp->t_state = TCPS_LAST_ACK8; | |||
311 | break; | |||
312 | } | |||
313 | if (tp) | |||
314 | tcp_output(tp); | |||
315 | } | |||
316 | ||||
317 | /* | |||
318 | * Connect to a host on the Internet | |||
319 | * Called by tcp_input | |||
320 | * Only do a connect, the tcp fields will be set in tcp_input | |||
321 | * return 0 if there's a result of the connect, | |||
322 | * else return -1 means we're still connecting | |||
323 | * The return value is almost always -1 since the socket is | |||
324 | * nonblocking. Connect returns after the SYN is sent, and does | |||
325 | * not wait for ACK+SYN. | |||
326 | */ | |||
327 | int tcp_fconnect(struct socket *so) | |||
328 | { | |||
329 | Slirp *slirp = so->slirp; | |||
330 | int ret=0; | |||
331 | ||||
332 | DEBUG_CALL("tcp_fconnect"); | |||
333 | DEBUG_ARG("so = %lx", (long )so); | |||
334 | ||||
335 | if( (ret = so->s = qemu_socket(AF_INET2,SOCK_STREAMSOCK_STREAM,0)) >= 0) { | |||
336 | int opt, s=so->s; | |||
337 | struct sockaddr_in addr; | |||
338 | ||||
339 | qemu_set_nonblock(s); | |||
340 | socket_set_fast_reuse(s); | |||
341 | opt = 1; | |||
342 | qemu_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt))setsockopt(s, 1, 10, &opt, sizeof(opt)); | |||
343 | ||||
344 | addr.sin_family = AF_INET2; | |||
345 | if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == | |||
346 | slirp->vnetwork_addr.s_addr) { | |||
347 | /* It's an alias */ | |||
348 | if (so->so_faddr.s_addr == slirp->vnameserver_addr.s_addr) { | |||
349 | if (get_dns_addr(&addr.sin_addr) < 0) | |||
350 | addr.sin_addr = loopback_addr; | |||
351 | } else { | |||
352 | addr.sin_addr = loopback_addr; | |||
353 | } | |||
354 | } else | |||
355 | addr.sin_addr = so->so_faddr; | |||
356 | addr.sin_port = so->so_fport; | |||
357 | ||||
358 | DEBUG_MISC((dfd, " connect()ing, addr.sin_port=%d, " | |||
359 | "addr.sin_addr.s_addr=%.16s\n", | |||
360 | ntohs(addr.sin_port), inet_ntoa(addr.sin_addr))); | |||
361 | /* We don't care what port we get */ | |||
362 | ret = connect(s,(struct sockaddr *)&addr,sizeof (addr)); | |||
363 | ||||
364 | /* | |||
365 | * If it's not in progress, it failed, so we just return 0, | |||
366 | * without clearing SS_NOFDREF | |||
367 | */ | |||
368 | soisfconnecting(so); | |||
369 | } | |||
370 | ||||
371 | return(ret); | |||
372 | } | |||
373 | ||||
374 | /* | |||
375 | * Accept the socket and connect to the local-host | |||
376 | * | |||
377 | * We have a problem. The correct thing to do would be | |||
378 | * to first connect to the local-host, and only if the | |||
379 | * connection is accepted, then do an accept() here. | |||
380 | * But, a) we need to know who's trying to connect | |||
381 | * to the socket to be able to SYN the local-host, and | |||
382 | * b) we are already connected to the foreign host by | |||
383 | * the time it gets to accept(), so... We simply accept | |||
384 | * here and SYN the local-host. | |||
385 | */ | |||
386 | void tcp_connect(struct socket *inso) | |||
387 | { | |||
388 | Slirp *slirp = inso->slirp; | |||
389 | struct socket *so; | |||
390 | struct sockaddr_in addr; | |||
391 | socklen_t addrlen = sizeof(struct sockaddr_in); | |||
392 | struct tcpcb *tp; | |||
393 | int s, opt; | |||
394 | ||||
395 | DEBUG_CALL("tcp_connect"); | |||
396 | DEBUG_ARG("inso = %lx", (long)inso); | |||
397 | ||||
398 | /* | |||
399 | * If it's an SS_ACCEPTONCE socket, no need to socreate() | |||
400 | * another socket, just use the accept() socket. | |||
401 | */ | |||
402 | if (inso->so_state & SS_FACCEPTONCE0x200) { | |||
| ||||
403 | /* FACCEPTONCE already have a tcpcb */ | |||
404 | so = inso; | |||
405 | } else { | |||
406 | so = socreate(slirp); | |||
407 | if (so == NULL((void*)0)) { | |||
408 | /* If it failed, get rid of the pending connection */ | |||
409 | closesocket(accept(inso->s, (struct sockaddr *)&addr, &addrlen))close(accept(inso->s, (struct sockaddr *)&addr, &addrlen )); | |||
410 | return; | |||
411 | } | |||
412 | if (tcp_attach(so) < 0) { | |||
413 | free(so); /* NOT sofree */ | |||
414 | return; | |||
415 | } | |||
416 | so->so_laddr = inso->so_laddr; | |||
417 | so->so_lport = inso->so_lport; | |||
418 | } | |||
419 | ||||
420 | tcp_mss(sototcpcb(so)((so)->so_tcpcb), 0); | |||
421 | ||||
422 | s = accept(inso->s, (struct sockaddr *)&addr, &addrlen); | |||
423 | if (s < 0) { | |||
424 | tcp_close(sototcpcb(so)((so)->so_tcpcb)); /* This will sofree() as well */ | |||
425 | return; | |||
426 | } | |||
427 | qemu_set_nonblock(s); | |||
428 | socket_set_fast_reuse(s); | |||
429 | opt = 1; | |||
430 | qemu_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int))setsockopt(s, 1, 10, &opt, sizeof(int)); | |||
431 | socket_set_nodelay(s); | |||
432 | ||||
433 | so->so_fport = addr.sin_port; | |||
| ||||
434 | so->so_faddr = addr.sin_addr; | |||
435 | /* Translate connections from localhost to the real hostname */ | |||
436 | if (so->so_faddr.s_addr == 0 || | |||
437 | (so->so_faddr.s_addr & loopback_mask) == | |||
438 | (loopback_addr.s_addr & loopback_mask)) { | |||
439 | so->so_faddr = slirp->vhost_addr; | |||
440 | } | |||
441 | ||||
442 | /* Close the accept() socket, set right state */ | |||
443 | if (inso->so_state & SS_FACCEPTONCE0x200) { | |||
444 | /* If we only accept once, close the accept() socket */ | |||
445 | closesocket(so->s)close(so->s); | |||
446 | ||||
447 | /* Don't select it yet, even though we have an FD */ | |||
448 | /* if it's not FACCEPTONCE, it's already NOFDREF */ | |||
449 | so->so_state = SS_NOFDREF0x001; | |||
450 | } | |||
451 | so->s = s; | |||
452 | so->so_state |= SS_INCOMING0x2000; | |||
453 | ||||
454 | so->so_iptos = tcp_tos(so); | |||
455 | tp = sototcpcb(so)((so)->so_tcpcb); | |||
456 | ||||
457 | tcp_template(tp); | |||
458 | ||||
459 | tp->t_state = TCPS_SYN_SENT2; | |||
460 | tp->t_timer[TCPT_KEEP2] = TCPTV_KEEP_INIT( 75*2); | |||
461 | tp->iss = slirp->tcp_iss; | |||
462 | slirp->tcp_iss += TCP_ISSINCR(125*1024)/2; | |||
463 | tcp_sendseqinit(tp)(tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp) ->snd_up = (tp)->iss; | |||
464 | tcp_output(tp); | |||
465 | } | |||
466 | ||||
467 | /* | |||
468 | * Attach a TCPCB to a socket. | |||
469 | */ | |||
470 | int | |||
471 | tcp_attach(struct socket *so) | |||
472 | { | |||
473 | if ((so->so_tcpcb = tcp_newtcpcb(so)) == NULL((void*)0)) | |||
474 | return -1; | |||
475 | ||||
476 | insqueslirp_insque(so, &so->slirp->tcb); | |||
477 | ||||
478 | return 0; | |||
479 | } | |||
480 | ||||
481 | /* | |||
482 | * Set the socket's type of service field | |||
483 | */ | |||
484 | static const struct tos_t tcptos[] = { | |||
485 | {0, 20, IPTOS_THROUGHPUT0x08, 0}, /* ftp data */ | |||
486 | {21, 21, IPTOS_LOWDELAY0x10, EMU_FTP0x2}, /* ftp control */ | |||
487 | {0, 23, IPTOS_LOWDELAY0x10, 0}, /* telnet */ | |||
488 | {0, 80, IPTOS_THROUGHPUT0x08, 0}, /* WWW */ | |||
489 | {0, 513, IPTOS_LOWDELAY0x10, EMU_RLOGIN0x6|EMU_NOCONNECT0x10}, /* rlogin */ | |||
490 | {0, 514, IPTOS_LOWDELAY0x10, EMU_RSH0x8|EMU_NOCONNECT0x10}, /* shell */ | |||
491 | {0, 544, IPTOS_LOWDELAY0x10, EMU_KSH0x3}, /* kshell */ | |||
492 | {0, 543, IPTOS_LOWDELAY0x10, 0}, /* klogin */ | |||
493 | {0, 6667, IPTOS_THROUGHPUT0x08, EMU_IRC0x4}, /* IRC */ | |||
494 | {0, 6668, IPTOS_THROUGHPUT0x08, EMU_IRC0x4}, /* IRC undernet */ | |||
495 | {0, 7070, IPTOS_LOWDELAY0x10, EMU_REALAUDIO0x5 }, /* RealAudio control */ | |||
496 | {0, 113, IPTOS_LOWDELAY0x10, EMU_IDENT0x7 }, /* identd protocol */ | |||
497 | {0, 0, 0, 0} | |||
498 | }; | |||
499 | ||||
500 | static struct emu_t *tcpemu = NULL((void*)0); | |||
501 | ||||
502 | /* | |||
503 | * Return TOS according to the above table | |||
504 | */ | |||
505 | uint8_t | |||
506 | tcp_tos(struct socket *so) | |||
507 | { | |||
508 | int i = 0; | |||
509 | struct emu_t *emup; | |||
510 | ||||
511 | while(tcptos[i].tos) { | |||
512 | if ((tcptos[i].fport && (ntohs(so->so_fport) == tcptos[i].fport)) || | |||
513 | (tcptos[i].lport && (ntohs(so->so_lport) == tcptos[i].lport))) { | |||
514 | so->so_emu = tcptos[i].emu; | |||
515 | return tcptos[i].tos; | |||
516 | } | |||
517 | i++; | |||
518 | } | |||
519 | ||||
520 | /* Nope, lets see if there's a user-added one */ | |||
521 | for (emup = tcpemu; emup; emup = emup->next) { | |||
522 | if ((emup->fport && (ntohs(so->so_fport) == emup->fport)) || | |||
523 | (emup->lport && (ntohs(so->so_lport) == emup->lport))) { | |||
524 | so->so_emu = emup->emu; | |||
525 | return emup->tos; | |||
526 | } | |||
527 | } | |||
528 | ||||
529 | return 0; | |||
530 | } | |||
531 | ||||
532 | /* | |||
533 | * Emulate programs that try and connect to us | |||
534 | * This includes ftp (the data connection is | |||
535 | * initiated by the server) and IRC (DCC CHAT and | |||
536 | * DCC SEND) for now | |||
537 | * | |||
538 | * NOTE: It's possible to crash SLiRP by sending it | |||
539 | * unstandard strings to emulate... if this is a problem, | |||
540 | * more checks are needed here | |||
541 | * | |||
542 | * XXX Assumes the whole command came in one packet | |||
543 | * | |||
544 | * XXX Some ftp clients will have their TOS set to | |||
545 | * LOWDELAY and so Nagel will kick in. Because of this, | |||
546 | * we'll get the first letter, followed by the rest, so | |||
547 | * we simply scan for ORT instead of PORT... | |||
548 | * DCC doesn't have this problem because there's other stuff | |||
549 | * in the packet before the DCC command. | |||
550 | * | |||
551 | * Return 1 if the mbuf m is still valid and should be | |||
552 | * sbappend()ed | |||
553 | * | |||
554 | * NOTE: if you return 0 you MUST m_free() the mbuf! | |||
555 | */ | |||
556 | int | |||
557 | tcp_emu(struct socket *so, struct mbuf *m) | |||
558 | { | |||
559 | Slirp *slirp = so->slirp; | |||
560 | u_int n1, n2, n3, n4, n5, n6; | |||
561 | char buff[257]; | |||
562 | uint32_t laddr; | |||
563 | u_int lport; | |||
564 | char *bptr; | |||
565 | ||||
566 | DEBUG_CALL("tcp_emu"); | |||
567 | DEBUG_ARG("so = %lx", (long)so); | |||
568 | DEBUG_ARG("m = %lx", (long)m); | |||
569 | ||||
570 | switch(so->so_emu) { | |||
571 | int x, i; | |||
572 | ||||
573 | case EMU_IDENT0x7: | |||
574 | /* | |||
575 | * Identification protocol as per rfc-1413 | |||
576 | */ | |||
577 | ||||
578 | { | |||
579 | struct socket *tmpso; | |||
580 | struct sockaddr_in addr; | |||
581 | socklen_t addrlen = sizeof(struct sockaddr_in); | |||
582 | struct sbuf *so_rcv = &so->so_rcv; | |||
583 | ||||
584 | memcpy(so_rcv->sb_wptr, m->m_data, m->m_len); | |||
585 | so_rcv->sb_wptr += m->m_len; | |||
586 | so_rcv->sb_rptr += m->m_len; | |||
587 | m->m_data[m->m_len] = 0; /* NULL terminate */ | |||
588 | if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) { | |||
589 | if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) { | |||
590 | HTONS(n1)((n1) = htons((uint16_t)(n1))); | |||
591 | HTONS(n2)((n2) = htons((uint16_t)(n2))); | |||
592 | /* n2 is the one on our host */ | |||
593 | for (tmpso = slirp->tcb.so_next; | |||
594 | tmpso != &slirp->tcb; | |||
595 | tmpso = tmpso->so_next) { | |||
596 | if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr && | |||
597 | tmpso->so_lport == n2 && | |||
598 | tmpso->so_faddr.s_addr == so->so_faddr.s_addr && | |||
599 | tmpso->so_fport == n1) { | |||
600 | if (getsockname(tmpso->s, | |||
601 | (struct sockaddr *)&addr, &addrlen) == 0) | |||
602 | n2 = ntohs(addr.sin_port); | |||
603 | break; | |||
604 | } | |||
605 | } | |||
606 | } | |||
607 | so_rcv->sb_cc = snprintf(so_rcv->sb_data, | |||
608 | so_rcv->sb_datalen, | |||
609 | "%d,%d\r\n", n1, n2); | |||
610 | so_rcv->sb_rptr = so_rcv->sb_data; | |||
611 | so_rcv->sb_wptr = so_rcv->sb_data + so_rcv->sb_cc; | |||
612 | } | |||
613 | m_free(m); | |||
614 | return 0; | |||
615 | } | |||
616 | ||||
617 | case EMU_FTP0x2: /* ftp */ | |||
618 | *(m->m_data+m->m_len) = 0; /* NUL terminate for strstr */ | |||
619 | if ((bptr = (char *)strstr(m->m_data, "ORT")) != NULL((void*)0)) { | |||
620 | /* | |||
621 | * Need to emulate the PORT command | |||
622 | */ | |||
623 | x = sscanf(bptr, "ORT %u,%u,%u,%u,%u,%u\r\n%256[^\177]", | |||
624 | &n1, &n2, &n3, &n4, &n5, &n6, buff); | |||
625 | if (x < 6) | |||
626 | return 1; | |||
627 | ||||
628 | laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); | |||
629 | lport = htons((n5 << 8) | (n6)); | |||
630 | ||||
631 | if ((so = tcp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), 0, laddr, | |||
632 | lport, SS_FACCEPTONCE0x200)) == NULL((void*)0)) { | |||
633 | return 1; | |||
634 | } | |||
635 | n6 = ntohs(so->so_fport); | |||
636 | ||||
637 | n5 = (n6 >> 8) & 0xff; | |||
638 | n6 &= 0xff; | |||
639 | ||||
640 | laddr = ntohl(so->so_faddr.s_addr); | |||
641 | ||||
642 | n1 = ((laddr >> 24) & 0xff); | |||
643 | n2 = ((laddr >> 16) & 0xff); | |||
644 | n3 = ((laddr >> 8) & 0xff); | |||
645 | n4 = (laddr & 0xff); | |||
646 | ||||
647 | m->m_len = bptr - m->m_data; /* Adjust length */ | |||
648 | m->m_len += snprintf(bptr, m->m_size - m->m_len, | |||
649 | "ORT %d,%d,%d,%d,%d,%d\r\n%s", | |||
650 | n1, n2, n3, n4, n5, n6, x==7?buff:""); | |||
651 | return 1; | |||
652 | } else if ((bptr = (char *)strstr(m->m_data, "27 Entering")) != NULL((void*)0)) { | |||
653 | /* | |||
654 | * Need to emulate the PASV response | |||
655 | */ | |||
656 | x = sscanf(bptr, "27 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n%256[^\177]", | |||
657 | &n1, &n2, &n3, &n4, &n5, &n6, buff); | |||
658 | if (x < 6) | |||
659 | return 1; | |||
660 | ||||
661 | laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); | |||
662 | lport = htons((n5 << 8) | (n6)); | |||
663 | ||||
664 | if ((so = tcp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), 0, laddr, | |||
665 | lport, SS_FACCEPTONCE0x200)) == NULL((void*)0)) { | |||
666 | return 1; | |||
667 | } | |||
668 | n6 = ntohs(so->so_fport); | |||
669 | ||||
670 | n5 = (n6 >> 8) & 0xff; | |||
671 | n6 &= 0xff; | |||
672 | ||||
673 | laddr = ntohl(so->so_faddr.s_addr); | |||
674 | ||||
675 | n1 = ((laddr >> 24) & 0xff); | |||
676 | n2 = ((laddr >> 16) & 0xff); | |||
677 | n3 = ((laddr >> 8) & 0xff); | |||
678 | n4 = (laddr & 0xff); | |||
679 | ||||
680 | m->m_len = bptr - m->m_data; /* Adjust length */ | |||
681 | m->m_len += snprintf(bptr, m->m_size - m->m_len, | |||
682 | "27 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n%s", | |||
683 | n1, n2, n3, n4, n5, n6, x==7?buff:""); | |||
684 | ||||
685 | return 1; | |||
686 | } | |||
687 | ||||
688 | return 1; | |||
689 | ||||
690 | case EMU_KSH0x3: | |||
691 | /* | |||
692 | * The kshell (Kerberos rsh) and shell services both pass | |||
693 | * a local port port number to carry signals to the server | |||
694 | * and stderr to the client. It is passed at the beginning | |||
695 | * of the connection as a NUL-terminated decimal ASCII string. | |||
696 | */ | |||
697 | so->so_emu = 0; | |||
698 | for (lport = 0, i = 0; i < m->m_len-1; ++i) { | |||
699 | if (m->m_data[i] < '0' || m->m_data[i] > '9') | |||
700 | return 1; /* invalid number */ | |||
701 | lport *= 10; | |||
702 | lport += m->m_data[i] - '0'; | |||
703 | } | |||
704 | if (m->m_data[m->m_len-1] == '\0' && lport != 0 && | |||
705 | (so = tcp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), 0, so->so_laddr.s_addr, | |||
706 | htons(lport), SS_FACCEPTONCE0x200)) != NULL((void*)0)) | |||
707 | m->m_len = snprintf(m->m_data, m->m_size, "%d", | |||
708 | ntohs(so->so_fport)) + 1; | |||
709 | return 1; | |||
710 | ||||
711 | case EMU_IRC0x4: | |||
712 | /* | |||
713 | * Need to emulate DCC CHAT, DCC SEND and DCC MOVE | |||
714 | */ | |||
715 | *(m->m_data+m->m_len) = 0; /* NULL terminate the string for strstr */ | |||
716 | if ((bptr = (char *)strstr(m->m_data, "DCC")) == NULL((void*)0)) | |||
717 | return 1; | |||
718 | ||||
719 | /* The %256s is for the broken mIRC */ | |||
720 | if (sscanf(bptr, "DCC CHAT %256s %u %u", buff, &laddr, &lport) == 3) { | |||
721 | if ((so = tcp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), 0, | |||
722 | htonl(laddr), htons(lport), | |||
723 | SS_FACCEPTONCE0x200)) == NULL((void*)0)) { | |||
724 | return 1; | |||
725 | } | |||
726 | m->m_len = bptr - m->m_data; /* Adjust length */ | |||
727 | m->m_len += snprintf(bptr, m->m_size, | |||
728 | "DCC CHAT chat %lu %u%c\n", | |||
729 | (unsigned long)ntohl(so->so_faddr.s_addr), | |||
730 | ntohs(so->so_fport), 1); | |||
731 | } else if (sscanf(bptr, "DCC SEND %256s %u %u %u", buff, &laddr, &lport, &n1) == 4) { | |||
732 | if ((so = tcp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), 0, | |||
733 | htonl(laddr), htons(lport), | |||
734 | SS_FACCEPTONCE0x200)) == NULL((void*)0)) { | |||
735 | return 1; | |||
736 | } | |||
737 | m->m_len = bptr - m->m_data; /* Adjust length */ | |||
738 | m->m_len += snprintf(bptr, m->m_size, | |||
739 | "DCC SEND %s %lu %u %u%c\n", buff, | |||
740 | (unsigned long)ntohl(so->so_faddr.s_addr), | |||
741 | ntohs(so->so_fport), n1, 1); | |||
742 | } else if (sscanf(bptr, "DCC MOVE %256s %u %u %u", buff, &laddr, &lport, &n1) == 4) { | |||
743 | if ((so = tcp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), 0, | |||
744 | htonl(laddr), htons(lport), | |||
745 | SS_FACCEPTONCE0x200)) == NULL((void*)0)) { | |||
746 | return 1; | |||
747 | } | |||
748 | m->m_len = bptr - m->m_data; /* Adjust length */ | |||
749 | m->m_len += snprintf(bptr, m->m_size, | |||
750 | "DCC MOVE %s %lu %u %u%c\n", buff, | |||
751 | (unsigned long)ntohl(so->so_faddr.s_addr), | |||
752 | ntohs(so->so_fport), n1, 1); | |||
753 | } | |||
754 | return 1; | |||
755 | ||||
756 | case EMU_REALAUDIO0x5: | |||
757 | /* | |||
758 | * RealAudio emulation - JP. We must try to parse the incoming | |||
759 | * data and try to find the two characters that contain the | |||
760 | * port number. Then we redirect an udp port and replace the | |||
761 | * number with the real port we got. | |||
762 | * | |||
763 | * The 1.0 beta versions of the player are not supported | |||
764 | * any more. | |||
765 | * | |||
766 | * A typical packet for player version 1.0 (release version): | |||
767 | * | |||
768 | * 0000:50 4E 41 00 05 | |||
769 | * 0000:00 01 00 02 1B D7 00 00 67 E6 6C DC 63 00 12 50 ........g.l.c..P | |||
770 | * 0010:4E 43 4C 49 45 4E 54 20 31 30 31 20 41 4C 50 48 NCLIENT 101 ALPH | |||
771 | * 0020:41 6C 00 00 52 00 17 72 61 66 69 6C 65 73 2F 76 Al..R..rafiles/v | |||
772 | * 0030:6F 61 2F 65 6E 67 6C 69 73 68 5F 2E 72 61 79 42 oa/english_.rayB | |||
773 | * | |||
774 | * Now the port number 0x1BD7 is found at offset 0x04 of the | |||
775 | * Now the port number 0x1BD7 is found at offset 0x04 of the | |||
776 | * second packet. This time we received five bytes first and | |||
777 | * then the rest. You never know how many bytes you get. | |||
778 | * | |||
779 | * A typical packet for player version 2.0 (beta): | |||
780 | * | |||
781 | * 0000:50 4E 41 00 06 00 02 00 00 00 01 00 02 1B C1 00 PNA............. | |||
782 | * 0010:00 67 75 78 F5 63 00 0A 57 69 6E 32 2E 30 2E 30 .gux.c..Win2.0.0 | |||
783 | * 0020:2E 35 6C 00 00 52 00 1C 72 61 66 69 6C 65 73 2F .5l..R..rafiles/ | |||
784 | * 0030:77 65 62 73 69 74 65 2F 32 30 72 65 6C 65 61 73 website/20releas | |||
785 | * 0040:65 2E 72 61 79 53 00 00 06 36 42 e.rayS...6B | |||
786 | * | |||
787 | * Port number 0x1BC1 is found at offset 0x0d. | |||
788 | * | |||
789 | * This is just a horrible switch statement. Variable ra tells | |||
790 | * us where we're going. | |||
791 | */ | |||
792 | ||||
793 | bptr = m->m_data; | |||
794 | while (bptr < m->m_data + m->m_len) { | |||
795 | u_short p; | |||
796 | static int ra = 0; | |||
797 | char ra_tbl[4]; | |||
798 | ||||
799 | ra_tbl[0] = 0x50; | |||
800 | ra_tbl[1] = 0x4e; | |||
801 | ra_tbl[2] = 0x41; | |||
802 | ra_tbl[3] = 0; | |||
803 | ||||
804 | switch (ra) { | |||
805 | case 0: | |||
806 | case 2: | |||
807 | case 3: | |||
808 | if (*bptr++ != ra_tbl[ra]) { | |||
809 | ra = 0; | |||
810 | continue; | |||
811 | } | |||
812 | break; | |||
813 | ||||
814 | case 1: | |||
815 | /* | |||
816 | * We may get 0x50 several times, ignore them | |||
817 | */ | |||
818 | if (*bptr == 0x50) { | |||
819 | ra = 1; | |||
820 | bptr++; | |||
821 | continue; | |||
822 | } else if (*bptr++ != ra_tbl[ra]) { | |||
823 | ra = 0; | |||
824 | continue; | |||
825 | } | |||
826 | break; | |||
827 | ||||
828 | case 4: | |||
829 | /* | |||
830 | * skip version number | |||
831 | */ | |||
832 | bptr++; | |||
833 | break; | |||
834 | ||||
835 | case 5: | |||
836 | /* | |||
837 | * The difference between versions 1.0 and | |||
838 | * 2.0 is here. For future versions of | |||
839 | * the player this may need to be modified. | |||
840 | */ | |||
841 | if (*(bptr + 1) == 0x02) | |||
842 | bptr += 8; | |||
843 | else | |||
844 | bptr += 4; | |||
845 | break; | |||
846 | ||||
847 | case 6: | |||
848 | /* This is the field containing the port | |||
849 | * number that RA-player is listening to. | |||
850 | */ | |||
851 | lport = (((u_char*)bptr)[0] << 8) | |||
852 | + ((u_char *)bptr)[1]; | |||
853 | if (lport < 6970) | |||
854 | lport += 256; /* don't know why */ | |||
855 | if (lport < 6970 || lport > 7170) | |||
856 | return 1; /* failed */ | |||
857 | ||||
858 | /* try to get udp port between 6970 - 7170 */ | |||
859 | for (p = 6970; p < 7071; p++) { | |||
860 | if (udp_listen(slirp, INADDR_ANY((in_addr_t) 0x00000000), | |||
861 | htons(p), | |||
862 | so->so_laddr.s_addr, | |||
863 | htons(lport), | |||
864 | SS_FACCEPTONCE0x200)) { | |||
865 | break; | |||
866 | } | |||
867 | } | |||
868 | if (p == 7071) | |||
869 | p = 0; | |||
870 | *(u_char *)bptr++ = (p >> 8) & 0xff; | |||
871 | *(u_char *)bptr = p & 0xff; | |||
872 | ra = 0; | |||
873 | return 1; /* port redirected, we're done */ | |||
874 | break; | |||
875 | ||||
876 | default: | |||
877 | ra = 0; | |||
878 | } | |||
879 | ra++; | |||
880 | } | |||
881 | return 1; | |||
882 | ||||
883 | default: | |||
884 | /* Ooops, not emulated, won't call tcp_emu again */ | |||
885 | so->so_emu = 0; | |||
886 | return 1; | |||
887 | } | |||
888 | } | |||
889 | ||||
890 | /* | |||
891 | * Do misc. config of SLiRP while its running. | |||
892 | * Return 0 if this connections is to be closed, 1 otherwise, | |||
893 | * return 2 if this is a command-line connection | |||
894 | */ | |||
895 | int tcp_ctl(struct socket *so) | |||
896 | { | |||
897 | Slirp *slirp = so->slirp; | |||
898 | struct sbuf *sb = &so->so_snd; | |||
899 | struct ex_list *ex_ptr; | |||
900 | int do_pty; | |||
901 | ||||
902 | DEBUG_CALL("tcp_ctl"); | |||
903 | DEBUG_ARG("so = %lx", (long )so); | |||
904 | ||||
905 | if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { | |||
906 | /* Check if it's pty_exec */ | |||
907 | for (ex_ptr = slirp->exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { | |||
908 | if (ex_ptr->ex_fport == so->so_fport && | |||
909 | so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { | |||
910 | if (ex_ptr->ex_pty == 3) { | |||
911 | so->s = -1; | |||
912 | so->extra = (void *)ex_ptr->ex_exec; | |||
913 | return 1; | |||
914 | } | |||
915 | do_pty = ex_ptr->ex_pty; | |||
916 | DEBUG_MISC((dfd, " executing %s\n", ex_ptr->ex_exec)); | |||
917 | return fork_exec(so, ex_ptr->ex_exec, do_pty); | |||
918 | } | |||
919 | } | |||
920 | } | |||
921 | sb->sb_cc = | |||
922 | snprintf(sb->sb_wptr, sb->sb_datalen - (sb->sb_wptr - sb->sb_data), | |||
923 | "Error: No application configured.\r\n"); | |||
924 | sb->sb_wptr += sb->sb_cc; | |||
925 | return 0; | |||
926 | } |