Fix output.
[util-vserver.git] / src / naddress.c
1 // $Id$
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 // Copyright (C) 2006 Daniel Hokka Zakrisson <daniel@hozac.com>
5 // based on chbind.cc by Jacques Gelinas
6 //  
7 // This program 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 2, or (at your option)
10 // any later version.
11 //  
12 // This program 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 this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include "vserver.h"
26 #include "util.h"
27
28 #include <lib/internal.h>
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <netdb.h>
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <net/if.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <getopt.h>
40 #include <fcntl.h>
41 #include <netinet/in.h>
42 #include <arpa/inet.h>
43
44 #define ENSC_WRAPPERS_PREFIX    "naddress: "
45 #define ENSC_WRAPPERS_IO        1
46 #define ENSC_WRAPPERS_UNISTD    1
47 #define ENSC_WRAPPERS_VSERVER   1
48 #include "wrappers.h"
49
50 #define CMD_HELP        0x1000
51 #define CMD_VERSION     0x1001
52
53 #define CMD_SILENT      0x2000
54 #define CMD_NID         0x2001
55 #define CMD_ADD         0x2002
56 #define CMD_REMOVE      0x2003
57 #define CMD_SET         0x2004
58 #define CMD_IP          0x2010
59 #define CMD_BCAST       0x2011
60
61 int wrapper_exit_code = 255;
62
63
64 static struct option const
65 CMDLINE_OPTIONS[] = {
66   { "help",     no_argument,  0, CMD_HELP },
67   { "version",  no_argument,  0, CMD_VERSION },
68   { "silent",   no_argument,  0, CMD_SILENT },
69   { "add",      no_argument,  0, CMD_ADD },
70   { "remove",   no_argument,  0, CMD_REMOVE },
71   { "set",      no_argument,  0, CMD_SET },
72   { "nid",      required_argument, 0, CMD_NID },
73   { "ip",       required_argument, 0, CMD_IP },
74   { "bcast",    required_argument, 0, CMD_BCAST },
75   { 0,0,0,0 }
76 };
77
78 struct vc_ips {
79   struct vc_net_nx a;
80   struct vc_ips *next;
81 };
82
83 struct Arguments {
84   nid_t         nid;
85   struct vc_ips head;
86   bool          is_silent;
87   bool          do_add;
88   bool          do_remove;
89   bool          do_set;
90 };
91
92 static void
93 showHelp(int fd, char const *cmd, int res)
94 {
95   WRITE_MSG(fd, "Usage:\n  ");
96   WRITE_STR(fd, cmd);
97   WRITE_MSG(fd,
98             " (--add|--remove|--set) [--silent] [--nid <nid>]\n"
99             "    [--ip <ip_num>[/<mask>]] [--bcast <broadcast>] [--] <commands> <args>*\n\n"
100             "Please report bugs to " PACKAGE_BUGREPORT "\n");
101
102   exit(res);
103 }
104
105 static void
106 showVersion()
107 {
108   WRITE_MSG(1,
109             "naddress " VERSION " -- bind to an ip and execute a program\n"
110             "This program is part of " PACKAGE_STRING "\n\n"
111             "Copyright (C) 2003,2004 Enrico Scholz\n"
112             "Copyright (C) 2006 Daniel Hokka Zakrisson\n"
113             VERSION_COPYRIGHT_DISCLAIMER);
114   exit(0);
115 }
116
117 /*
118         Check if a network device exist in /proc/net/dev.
119         This is used because ifconfig_ioctl triggers modprobe if requesting
120         information about non existant devices.
121
122         Return != 0 if the device exist.
123 */
124 static bool
125 existsDevice(char const *dev_raw)
126 {
127   size_t        buf_size=8192;
128   char          dev[strlen(dev_raw)+2];
129
130   strcpy(dev, dev_raw);
131   strcat(dev, ":");
132   for (;;) {
133     char        buf[buf_size];
134     char *      pos;
135     bool        too_small;
136     int         fd=open("/proc/net/dev", O_RDONLY);
137     
138     if (fd==-1) return false;
139     too_small = EreadAll(fd, buf, buf_size);
140     close(fd);
141
142     if (too_small) {
143       buf_size *= 2;
144       continue;
145     }
146
147     pos = strstr(buf, dev);
148     return (pos && (pos==buf || pos[-1]==' ' || pos[-1]=='\n'));
149   }
150 }
151
152 static int ifconfig_ioctl(
153         int fd,
154         const char *ifname,
155         int cmd,
156         struct ifreq *ifr)
157 {
158         strcpy(ifr->ifr_name, ifname);
159         return ioctl(fd, cmd, ifr);
160 }
161
162 /*
163         Fetch the IP number of an interface from the kernel.
164         Assume the device is already available in the kernel
165         Return -1 if any error.
166 */
167 int ifconfig_getaddr (
168         const char *ifname,
169         uint32_t *addr,
170         uint32_t *mask,
171         uint32_t *bcast)
172 {
173         int ret = -1;
174         if (existsDevice(ifname)){
175                 int skfd = socket(AF_INET, SOCK_DGRAM, 0);
176                 if (skfd != -1){
177                         struct ifreq ifr;
178                         if (addr != NULL && ifconfig_ioctl(skfd,ifname,SIOCGIFADDR, &ifr) >= 0){
179                                 struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
180                                 *addr = sin->sin_addr.s_addr;
181                                 ret = 0;
182                         }
183                         if (mask != NULL && ifconfig_ioctl(skfd,ifname,SIOCGIFNETMASK, &ifr) >= 0){
184                                 struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
185                                 *mask = sin->sin_addr.s_addr;
186                                 ret = 0;
187                         }
188                         if (bcast != NULL && ifconfig_ioctl(skfd,ifname,SIOCGIFBRDADDR, &ifr) >= 0){
189                                 struct sockaddr_in *sin = (struct sockaddr_in*)&ifr.ifr_addr;
190                                 *bcast = sin->sin_addr.s_addr;
191                                 ret = 0;
192                         }
193                         close (skfd);
194                 }
195         }
196         return ret;
197 }
198
199 static int
200 convertAddress(const char *str, vc_net_nx_type *type, void *dst)
201 {
202   int   ret;
203   if (type) *type = vcNET_IPV4;
204   ret = inet_pton(AF_INET, str, dst);
205   if (ret==0) {
206     if (type) *type = vcNET_IPV6;
207     ret = inet_pton(AF_INET6, str, dst);
208   }
209   return ret > 0 ? 0 : -1;
210 }
211
212 static void
213 readIP(char const *str, struct vc_ips **ips)
214 {
215   if (ifconfig_getaddr(str, &(*ips)->a.ip[0], &(*ips)->a.mask[0], NULL)==-1) {
216     char                *pt;
217     char                tmpopt[strlen(str)+1];
218     uint32_t            *mask = (*ips)->a.mask;
219
220     strcpy(tmpopt,str);
221     pt = strchr(tmpopt,'/');
222     if (pt)
223       *pt++ = '\0';
224
225     if (convertAddress(tmpopt, &(*ips)->a.type, (*ips)->a.ip) == -1) {
226       WRITE_MSG(2, "Invalid IP number '");
227       WRITE_STR(2, tmpopt);
228       WRITE_MSG(2, "'\n");
229       exit(wrapper_exit_code);
230     }
231
232     if (pt==0) {
233       switch ((*ips)->a.type) {
234         case vcNET_IPV4:
235           mask[0] = htonl(0xffffff00);
236           break;
237         case vcNET_IPV6:
238           mask[0] = 64;
239           break;
240         default: break;
241       }
242     }
243     else {
244       // Ok, we have a network size, not a netmask
245       if (strchr(pt,'.')==0 && strchr(pt,':')==0) {
246         unsigned long   sz, limit = 0;
247
248         switch ((*ips)->a.type) {
249           case vcNET_IPV4: limit =  32; break;
250           case vcNET_IPV6: limit = 128; break;
251           default:                      break;
252         }
253
254         if (!isNumberUnsigned(pt, &sz, true) || sz > limit) {
255           WRITE_MSG(2, "Invalid prefix '");
256           WRITE_STR(2, pt);
257           WRITE_MSG(2, "'\n");
258           exit(wrapper_exit_code);
259         }
260
261         switch ((*ips)->a.type) {
262           case vcNET_IPV4:
263             mask[0] = htonl(~((1 << (32 - sz)) - 1));
264             break;
265           case vcNET_IPV6:
266             mask[0] = sz;
267             break;
268           default: break;
269         }
270       }
271       else { 
272         if (convertAddress(pt, NULL, &(*ips)->a.mask) == -1) {
273           WRITE_MSG(2, "Invalid netmask '");
274           WRITE_STR(2, pt);
275           WRITE_MSG(2, "'\n");
276           exit(wrapper_exit_code);
277         }
278       }
279     }
280   }
281   else
282     (*ips)->a.type = vcNET_IPV4;
283
284   (*ips)->a.count = 1;
285   (*ips)->next = calloc(1, sizeof(struct vc_ips));
286   *ips = (*ips)->next;
287 }
288
289 static void
290 readBcast(char const *str, struct vc_ips **ips)
291 {
292   uint32_t bcast;
293   if (ifconfig_getaddr(str, NULL, NULL, &bcast)==-1){
294     if (convertAddress(str, NULL, &bcast) == -1) {
295       WRITE_MSG(2, "Invalid broadcast number '");
296       WRITE_STR(2, optarg);
297       WRITE_MSG(2, "'\n");
298       exit(wrapper_exit_code);
299     }
300   }
301   (*ips)->a.ip[0] = bcast;
302   (*ips)->a.count = 1;
303   (*ips)->a.type = vcNET_IPV4B;
304   (*ips)->next = calloc(1, sizeof(struct vc_ips));
305   *ips = (*ips)->next;
306 }
307
308 static void
309 tellAddress(struct vc_net_nx *addr, bool silent)
310 {
311   char buf[41];
312   if (silent)
313     return;
314   if (inet_ntop(addr->type == vcNET_IPV6 ? AF_INET6 : AF_INET,
315                 addr->ip, buf, sizeof(buf)) == NULL) {
316     WRITE_MSG(1, " <conversion failed>");
317     return;
318   }
319   WRITE_MSG(1, " ");
320   WRITE_STR(1, buf);
321 }
322
323 static inline void
324 doit(struct Arguments *args)
325 {
326   struct vc_ips *ips;
327
328   if (args->do_set) {
329     struct vc_net_nx remove = { .type = vcNET_ANY };
330     if (vc_net_remove(args->nid, &remove) == -1) {
331       perror(ENSC_WRAPPERS_PREFIX "vc_net_remove()");
332       exit(wrapper_exit_code);
333     }
334   }
335
336   if (args->do_add || args->do_set) {
337     if (!args->is_silent)
338       WRITE_MSG(1, "Adding");
339     for (ips = &args->head; ips->next; ips = ips->next) {
340       tellAddress(&ips->a, args->is_silent);
341       if (vc_net_add(args->nid, &ips->a) != (int)ips->a.count) {
342         perror(ENSC_WRAPPERS_PREFIX "vc_net_add()");
343         exit(wrapper_exit_code);
344       }
345     }
346     if (!args->is_silent)
347       WRITE_MSG(1, "\n");
348   }
349   else if (args->do_remove) {
350     if (!args->is_silent)
351       WRITE_MSG(1, "Removing");
352     for (ips = &args->head; ips->next; ips = ips->next) {
353       tellAddress(&ips->a, args->is_silent);
354       if (vc_net_remove(args->nid, &ips->a) != (int)ips->a.count) {
355         perror(ENSC_WRAPPERS_PREFIX "vc_net_remove()");
356         exit(wrapper_exit_code);
357       }
358     }
359     if (!args->is_silent)
360       WRITE_MSG(1, "\n");
361   }
362 }
363
364 int main (int argc, char *argv[])
365 {
366   struct Arguments args = {
367     .nid        = VC_NOCTX,
368     .is_silent  = false,
369     .do_add     = false,
370     .do_remove  = false,
371     .do_set     = false,
372     .head       = { .next = NULL },
373   };
374   struct vc_ips *ips = &args.head;
375   
376   while (1) {
377     int         c = getopt_long(argc, argv, "+", CMDLINE_OPTIONS, 0);
378     if (c==-1) break;
379
380     switch (c) {
381       case CMD_HELP     :  showHelp(1, argv[0], 0);
382       case CMD_VERSION  :  showVersion();
383       case CMD_SILENT   :  args.is_silent = true; break;
384       case CMD_NID      :  args.nid       = Evc_nidopt2nid(optarg,true); break;
385       case CMD_ADD      :  args.do_add    = true; break;
386       case CMD_REMOVE   :  args.do_remove = true; break;
387       case CMD_SET      :  args.do_set    = true; break;
388       case CMD_IP       :  readIP(optarg, &ips); break;
389       case CMD_BCAST    :  readBcast(optarg, &ips); break;
390       default           :
391         WRITE_MSG(2, "Try '");
392         WRITE_STR(2, argv[0]);
393         WRITE_MSG(2, " --help' for more information.\n");
394         exit(wrapper_exit_code);
395         break;
396     }
397   }
398
399   if (args.nid == VC_NOCTX) args.nid = Evc_get_task_nid(0);
400
401   if (!args.do_add && !args.do_remove && !args.do_set) {
402     WRITE_MSG(2, "No operation specified; try '--help' for more information\n");
403     exit(wrapper_exit_code);
404   }
405   else if (((args.do_add ? 1 : 0) + (args.do_remove ? 1 : 0) + (args.do_set ? 1 : 0)) > 1) {
406     WRITE_MSG(2, "Multiple operations specified; try '--help' for more information\n");
407     exit(wrapper_exit_code);
408   }
409
410   doit(&args);
411
412   if (optind != argc)
413     Eexecvp (argv[optind],argv+optind);
414   return EXIT_SUCCESS;
415 }
416
417 #ifdef ENSC_TESTSUITE
418 #include <assert.h>
419
420 void test()
421 {
422   struct vc_ip_mask_pair        ip;
423   uint32_t                      bcast;
424
425   bcast = 0;
426   readIP("1.2.3.4", &ip, &bcast);
427   assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xffffff00) && bcast==0);
428
429   readIP("1.2.3.4/8", &ip, &bcast);
430   assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xff000000) && bcast==0);
431
432   readIP("1.2.3.4/255.255.0.0", &ip, &bcast);
433   assert(ip.ip==ntohl(0x01020304) && ip.mask==ntohl(0xffff0000) && bcast==0);
434
435   readIP("localhost", &ip, &bcast);
436   assert(ip.ip==ntohl(0x7f000001) && ip.mask==ntohl(0xffffff00) && bcast==0);
437
438 #if 0
439   if (ifconfig_getaddr("lo", &tmp, &tmp, &tmp)!=-1) {
440     readIP("lo", &ip, &bcast);
441     assert(ip.ip==ntohl(0x7f000001) && ip.mask==ntohl(0xff000000) && bcast==ntohl(0x7fffffff));
442   }
443 #endif
444 }
445 #endif