chiark / gitweb /
Import release 0.1.6
[secnet.git] / slip.c
1 /* When dealing with SLIP (to a pty, or ipif) we have separate rx, tx
2    and client buffers.  When receiving we may read() any amount, not
3    just whole packets.  When transmitting we need to bytestuff anyway,
4    and may be part-way through receiving. */
5
6 #include "secnet.h"
7 #include "util.h"
8 #include "netlink.h"
9 #include <stdio.h>
10 #include <string.h>
11 #include <unistd.h>
12
13 #define SLIP_END    192
14 #define SLIP_ESC    219
15 #define SLIP_ESCEND 220
16 #define SLIP_ESCESC 221
17
18 /* Connection to the kernel through userv-ipif */
19
20 struct userv {
21     struct netlink nl;
22     int txfd; /* We transmit to userv */
23     int rxfd; /* We receive from userv */
24     string_t userv_path;
25     string_t service_user;
26     string_t service_name;
27     uint32_t txbuflen;
28     struct buffer_if *buff; /* We unstuff received packets into here
29                                and send them to the site code. */
30     bool_t pending_esc;
31     netlink_deliver_fn *netlink_to_tunnel;
32     uint32_t local_address; /* host interface address */
33 };
34
35 static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
36                             int *timeout_io, const struct timeval *tv_now,
37                             uint64_t *now)
38 {
39     struct userv *st=sst;
40     *nfds_io=2;
41     fds[0].fd=st->txfd;
42     fds[0].events=POLLERR; /* Might want to pick up POLLOUT sometime */
43     fds[1].fd=st->rxfd;
44     fds[1].events=POLLIN|POLLERR|POLLHUP;
45     return 0;
46 }
47
48 static void userv_afterpoll(void *sst, struct pollfd *fds, int nfds,
49                             const struct timeval *tv_now, uint64_t *now)
50 {
51     struct userv *st=sst;
52     uint8_t rxbuf[DEFAULT_BUFSIZE];
53     int l,i;
54
55     if (fds[1].revents&POLLERR) {
56         Message(M_ERROR,"%s: userv_afterpoll: hup!\n",st->nl.name);
57     }
58     if (fds[1].revents&POLLIN) {
59         l=read(st->rxfd,rxbuf,DEFAULT_BUFSIZE);
60         if (l<0) {
61             fatal_perror("%s: userv_afterpoll: read(rxfd)",st->nl.name);
62         }
63         if (l==0) {
64             fatal("%s: userv_afterpoll: read(rxfd)=0; userv gone away?\n",
65                   st->nl.name);
66         }
67         /* XXX really crude unstuff code */
68         /* XXX check for buffer overflow */
69         BUF_ASSERT_USED(st->buff);
70         for (i=0; i<l; i++) {
71             if (st->pending_esc) {
72                 st->pending_esc=False;
73                 switch(rxbuf[i]) {
74                 case SLIP_ESCEND:
75                     *(uint8_t *)buf_append(st->buff,1)=SLIP_END;
76                     break;
77                 case SLIP_ESCESC:
78                     *(uint8_t *)buf_append(st->buff,1)=SLIP_ESC;
79                     break;
80                 default:
81                     fatal("userv_afterpoll: bad SLIP escape character\n");
82                 }
83             } else {
84                 switch (rxbuf[i]) {
85                 case SLIP_END:
86                     if (st->buff->size>0) {
87                         st->netlink_to_tunnel(&st->nl,NULL,
88                                               st->buff);
89                         BUF_ALLOC(st->buff,"userv_afterpoll");
90                     }
91                     buffer_init(st->buff,st->nl.max_start_pad);
92                     break;
93                 case SLIP_ESC:
94                     st->pending_esc=True;
95                     break;
96                 default:
97                     *(uint8_t *)buf_append(st->buff,1)=rxbuf[i];
98                     break;
99                 }
100             }
101         }
102     }
103 }
104
105 /* Send buf to the kernel. Free buf before returning. */
106 static void userv_deliver_to_kernel(void *sst, void *cid,
107                                     struct buffer_if *buf)
108 {
109     struct userv *st=sst;
110     uint8_t txbuf[DEFAULT_BUFSIZE];
111     uint8_t *i;
112     uint32_t j;
113
114     BUF_ASSERT_USED(buf);
115
116     /* Spit the packet at userv-ipif: SLIP start marker, then
117        bytestuff the packet, then SLIP end marker */
118     /* XXX crunchy bytestuff code */
119     j=0;
120     txbuf[j++]=SLIP_END;
121     for (i=buf->start; i<(buf->start+buf->size); i++) {
122         switch (*i) {
123         case SLIP_END:
124             txbuf[j++]=SLIP_ESC;
125             txbuf[j++]=SLIP_ESCEND;
126             break;
127         case SLIP_ESC:
128             txbuf[j++]=SLIP_ESC;
129             txbuf[j++]=SLIP_ESCESC;
130             break;
131         default:
132             txbuf[j++]=*i;
133             break;
134         }
135     }
136     txbuf[j++]=SLIP_END;
137     if (write(st->txfd,txbuf,j)<0) {
138         fatal_perror("userv_deliver_to_kernel: write()");
139     }
140     BUF_FREE(buf);
141 }
142
143 static void userv_phase_hook(void *sst, uint32_t newphase)
144 {
145     struct userv *st=sst;
146     pid_t child;
147     int c_stdin[2];
148     int c_stdout[2];
149     string_t addrs;
150     string_t nets;
151     string_t s;
152     struct netlink_route *r;
153     int i;
154
155     /* This is where we actually invoke userv - all the networks we'll
156        be using should already have been registered. */
157
158     addrs=safe_malloc(512,"userv_phase_hook:addrs");
159     snprintf(addrs,512,"%s,%s,%d,slip",ipaddr_to_string(st->local_address),
160              ipaddr_to_string(st->nl.secnet_address),st->nl.mtu);
161
162     nets=safe_malloc(1024,"userv_phase_hook:nets");
163     *nets=0;
164     r=st->nl.routes;
165     for (i=0; i<st->nl.n_routes; i++) {
166         if (r[i].up) {
167             r[i].kup=True;
168             s=subnet_to_string(&r[i].net);
169             strcat(nets,s);
170             strcat(nets,",");
171             free(s);
172         }
173     }
174     nets[strlen(nets)-1]=0;
175
176     Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->nl.name,
177             st->userv_path,st->service_user,st->service_name,addrs,nets);
178
179     /* Allocate buffer, plus space for padding. Make sure we end up
180        with the start of the packet well-aligned. */
181     /* ALIGN(st->max_start_pad,16); */
182     /* ALIGN(st->max_end_pad,16); */
183
184     st->pending_esc=False;
185
186     /* Invoke userv */
187     if (pipe(c_stdin)!=0) {
188         fatal_perror("userv_phase_hook: pipe(c_stdin)");
189     }
190     if (pipe(c_stdout)!=0) {
191         fatal_perror("userv_phase_hook: pipe(c_stdout)");
192     }
193     st->txfd=c_stdin[1];
194     st->rxfd=c_stdout[0];
195
196     child=fork();
197     if (child==-1) {
198         fatal_perror("userv_phase_hook: fork()");
199     }
200     if (child==0) {
201         char **argv;
202
203         /* We are the child. Modify our stdin and stdout, then exec userv */
204         dup2(c_stdin[0],0);
205         dup2(c_stdout[1],1);
206         close(c_stdin[1]);
207         close(c_stdout[0]);
208
209         /* The arguments are:
210            userv
211            service-user
212            service-name
213            local-addr,secnet-addr,mtu,protocol
214            route1,route2,... */
215         argv=malloc(sizeof(*argv)*6);
216         argv[0]=st->userv_path;
217         argv[1]=st->service_user;
218         argv[2]=st->service_name;
219         argv[3]=addrs;
220         argv[4]=nets;
221         argv[5]=NULL;
222         execvp(st->userv_path,argv);
223         perror("netlink-userv-ipif: execvp");
224
225         exit(1);
226     }
227     /* We are the parent... */
228            
229     /* Register for poll() */
230     register_for_poll(st, userv_beforepoll, userv_afterpoll, 2, st->nl.name);
231 }
232
233 static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
234                            list_t *args)
235 {
236     struct userv *st;
237     item_t *item;
238     dict_t *dict;
239
240     st=safe_malloc(sizeof(*st),"userv_apply");
241
242     /* First parameter must be a dict */
243     item=list_elem(args,0);
244     if (!item || item->type!=t_dict)
245         cfgfatal(loc,"userv-ipif","parameter must be a dictionary\n");
246     
247     dict=item->data.dict;
248
249     st->netlink_to_tunnel=
250         netlink_init(&st->nl,st,loc,dict,
251                      "netlink-userv-ipif",NULL,userv_deliver_to_kernel);
252
253     st->userv_path=dict_read_string(dict,"userv-path",False,"userv-netlink",
254                                     loc);
255     st->service_user=dict_read_string(dict,"service-user",False,
256                                       "userv-netlink",loc);
257     st->service_name=dict_read_string(dict,"service-name",False,
258                                       "userv-netlink",loc);
259     if (!st->userv_path) st->userv_path="userv";
260     if (!st->service_user) st->service_user="root";
261     if (!st->service_name) st->service_name="ipif";
262     st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"userv-netlink",loc);
263     st->local_address=string_to_ipaddr(
264         dict_find_item(dict,"local-address", True, "netlink", loc),"netlink");
265     BUF_ALLOC(st->buff,"netlink:userv_apply");
266
267     st->rxfd=-1; st->txfd=-1;
268     add_hook(PHASE_DROPPRIV,userv_phase_hook,st);
269
270     return new_closure(&st->nl.cl);
271 }
272
273 init_module slip_module;
274 void slip_module(dict_t *dict)
275 {
276     add_closure(dict,"userv-ipif",userv_apply);
277 #if 0
278     /* TODO */
279     add_closure(dict,"pty-slip",ptyslip_apply);
280     add_closure(dict,"slipd",slipd_apply);
281 #endif /* 0 */
282 }