Check for valid "looking" IPs before saving them.
[yaboot.git] / second / file.c
1 /*
2  *  file.c - Filesystem related interfaces
3  *
4  *  Copyright (C) 2001, 2002 Ethan Benson
5  *
6  *  parse_device_path()
7  *
8  *  Copyright (C) 2001 Colin Walters
9  *
10  *  Copyright (C) 1999 Benjamin Herrenschmidt
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25  */
26
27 #include "ctype.h"
28 #include "types.h"
29 #include "stddef.h"
30 #include "stdlib.h"
31 #include "file.h"
32 #include "prom.h"
33 #include "string.h"
34 #include "partition.h"
35 #include "fs.h"
36 #include "errors.h"
37 #include "debug.h"
38
39 extern char bootdevice[];
40
41 /* Convert __u32 into std, dotted quad string, leaks like a sive :( */
42 static char *
43 ipv4_to_str(__u32 ip)
44 {
45      char *buf = malloc(sizeof("000.000.000.000"));
46
47      sprintf(buf,"%u.%u.%u.%u",
48              (ip & 0xff000000) >> 24, (ip & 0x00ff0000) >> 16,
49              (ip & 0x0000ff00) >>  8, (ip & 0x000000ff));
50
51      return buf;
52 }
53
54 /* Ensure the string arg is a plausible IPv4 address */
55 static char * is_valid_ipv4_str(char *str)
56 {
57      int i;
58      long tmp;
59      __u32 ip = 0;
60      char *ptr=str, *endptr;
61
62      if (str == NULL)
63           return NULL;
64
65      for (i=0; i<4; i++, ptr = ++endptr) {
66           tmp = strtol(ptr, &endptr, 10);
67           if ((tmp & 0xff) != tmp)
68                return NULL;
69
70           /* If we reach the end of the string but we're not in the 4th octet
71            * we have an invalid IP */
72           if (*endptr == '\x0' && i!=3)
73                return NULL;
74
75           /* If we have anything other than a NULL or '.' we have an invlaid
76            * IP */
77           if (*endptr != '\x0' && *endptr != '.')
78                return NULL;
79
80           ip += (tmp << (24-(i*8)));
81      }
82
83      if (ip == 0 || ip == ~0u)
84           return NULL;
85
86      return str;
87 }
88
89
90 /*
91  * Copy the string from source to dest till newline or comma(,) is seen
92  * in the source.
93  * Move source and dest pointers respectively.
94  * Returns pointer to the start of the string that has just been copied.
95  */
96 static char *
97 scopy(char **dest, char **source)
98 {
99      char *ret = *dest;
100
101      if (!**source)
102           return NULL;
103
104      while (**source != ',' && **source != '\0')
105           *(*dest)++ = *(*source)++;
106      if (**source != '\0')
107           *(*source)++;
108      **dest = '\0';
109      *(*dest)++;
110      return ret;
111 }
112
113 /*
114  * Extract all the ipv4 arguments from the bootpath provided and fill result
115  * Returns 1 on success, 0 on failure.
116  */
117 static int
118 extract_ipv4_args(char *imagepath, struct boot_fspec_t *result)
119 {
120      char *tmp, *args, *str, *start;
121
122      args = strrchr(imagepath, ':');
123      if (!args)
124           return 1;
125
126      start = args; /* used to see if we read any optional parameters */
127
128      /* The obp-tftp device arguments should be at the end of
129       * the argument list.  Skip over any extra arguments (promiscuous,
130       * speed, duplex, bootp, rarp).
131       */
132
133      tmp = strstr(args, "promiscuous");
134      if (tmp && tmp > args)
135           args = tmp + strlen("promiscuous");
136
137      tmp = strstr(args, "speed=");
138      if (tmp && tmp > args)
139           args = tmp + strlen("speed=");
140
141      tmp = strstr(args, "duplex=");
142      if (tmp && tmp > args)
143           args = tmp + strlen("duplex=");
144
145      tmp = strstr(args, "bootp");
146      if (tmp && tmp > args)
147           args = tmp + strlen("bootp");
148
149      tmp = strstr(args, "rarp");
150      if (tmp && tmp > args)
151           args = tmp + strlen("rarp");
152
153      if (args != start) /* we read some parameters, so go past the next comma(,) */
154           args = strchr(args, ',');
155      if (!args)
156           return 1;
157
158      str = malloc(strlen(args) + 1); /*long enough to hold all strings */
159      if (!str)
160           return 0;
161
162      if (args[-1] != ':')
163           args++; /* If comma(,) is not immediately followed by ':' then go past the , */
164
165      /*
166       * read the arguments in order: siaddr,filename,ciaddr,giaddr,
167       * bootp-retries,tftp-retries,addl_prameters
168       */
169      result->siaddr = is_valid_ipv4_str(scopy(&str, &args));
170      result->file = scopy(&str, &args);
171      result->ciaddr = is_valid_ipv4_str(scopy(&str, &args));
172      result->giaddr = is_valid_ipv4_str(scopy(&str, &args));
173      result->bootp_retries = scopy(&str, &args);
174      result->tftp_retries = scopy(&str, &args);
175      if (*args) {
176           result->addl_params = strdup(args);
177           if (!result->addl_params)
178                 return 0;
179      }
180      return 1;
181 }
182
183 /* DHCP options */
184 enum dhcp_options {
185      DHCP_PAD = 0,
186      DHCP_NETMASK = 1,
187      DHCP_ROUTERS = 3,
188      DHCP_DNS = 6,
189      DHCP_END = 255,
190 };
191
192 #define DHCP_COOKIE        0x63825363
193 #define DHCP_COOKIE_SIZE   4
194
195 /*
196  * Process the bootp reply packet's vendor extensions.
197  * Vendor extensions are detailed in: http://www.faqs.org/rfcs/rfc1084.html
198  */
199 static void
200 extract_vendor_options(struct bootp_packet *packet, struct boot_fspec_t *result)
201 {
202      int i = 0;
203      __u32 cookie;
204      __u8 *options = &packet->options[0];
205
206      memcpy(&cookie, &options[i], DHCP_COOKIE_SIZE);
207
208      if (cookie != DHCP_COOKIE) {
209           prom_printf("EEEK! cookie is fubar got %08x expected %08x\n",
210                       cookie, DHCP_COOKIE);
211           return;
212      }
213
214      i += DHCP_COOKIE_SIZE;
215
216      /* FIXME: It may be possible to run off the end of a packet here /if/
217       *         it's malformed. :( */
218      while (options[i] != DHCP_END) {
219           __u8 tag = options[i++], len;
220           __u32 value;
221
222           if (tag == DHCP_PAD)
223                continue;
224
225           len = options[i++];
226           memcpy(&value, &options[i], len);
227
228 #if DEBUG
229 {
230      DEBUG_F("tag=%2d, len=%2d, data=", tag, len);
231      int j;
232      for (j=0; j<len; j++)
233           prom_printf("%02x", options[i+j]);
234      prom_printf("\n");
235 }
236 #endif
237
238           switch (tag) {
239                case DHCP_NETMASK:
240                     /* FIXME: do we need to grok the subnet mask? */
241                     break;
242                case DHCP_ROUTERS:
243                     if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
244                         && value != 0) {
245                          result->giaddr = ipv4_to_str(value);
246                          DEBUG_F("Storing %s as gateway from options\n",
247                                  result->giaddr);
248                     }
249                     break;
250                }
251           i += len;
252      }
253 }
254
255 /*
256  * Check netinfo for ipv4 parameters and add them to the fspec iff the
257  * fspec has no existing value.
258  *
259  * Returns 1 on success, 0 on failure.
260  */
261 static int
262 extract_netinfo_args(struct boot_fspec_t *result)
263 {
264      struct bootp_packet *packet;
265
266      /* Check to see if we can get the [scyg]iaddr fields from netinfo */
267      packet = prom_get_netinfo();
268      if (!packet)
269           return 0;
270
271      DEBUG_F("We have a boot packet\n");
272      DEBUG_F(" siaddr = <%x>\n", packet->siaddr);
273      DEBUG_F(" ciaddr = <%x>\n", packet->ciaddr);
274      DEBUG_F(" yiaddr = <%x>\n", packet->yiaddr);
275      DEBUG_F(" giaddr = <%x>\n", packet->giaddr);
276
277      /* Try to fallback to yiaddr if ciaddr is empty. Broken? */
278      if (packet->ciaddr == 0 && packet->yiaddr != 0)
279           packet->ciaddr = packet->yiaddr;
280
281      if ((result->siaddr == NULL || *(result->siaddr) == '\x0')
282          && packet->siaddr != 0)
283           result->siaddr = ipv4_to_str(packet->siaddr);
284      if ((result->ciaddr == NULL || *(result->ciaddr) == '\x0')
285          && packet->ciaddr != 0)
286           result->ciaddr = ipv4_to_str(packet->ciaddr);
287      if ((result->giaddr == NULL || *(result->giaddr) == '\x0')
288          && packet->giaddr != 0)
289           result->giaddr = ipv4_to_str(packet->giaddr);
290
291      extract_vendor_options(packet, result);
292
293      /* FIXME: Yck! if we /still/ do not have a gateway then "cheat" and use
294       *        the server.  This will be okay if the client and server are on
295       *        the same IP network, if not then lets hope the server does ICMP
296       *        redirections */
297      if (result->giaddr == NULL) {
298           result->giaddr = ipv4_to_str(packet->siaddr);
299           DEBUG_F("Forcing giaddr to siaddr <%s>\n", result->giaddr);
300      }
301
302      return 1;
303 }
304
305 /*
306  * Extract all the arguments provided in the imagepath and fill it in result.
307  * Returns 1 on success, 0 on failure.
308  */
309 static int
310 extract_netboot_args(char *imagepath, struct boot_fspec_t *result)
311 {
312      int ret;
313
314      DEBUG_F("imagepath = %s\n", imagepath);
315
316      if (!imagepath)
317           return 1;
318
319      ret = extract_ipv4_args(imagepath, result);
320      ret |= extract_netinfo_args(result);
321
322      DEBUG_F("siaddr = <%s>\n", result->siaddr);
323      DEBUG_F("file = <%s>\n", result->file);
324      DEBUG_F("ciaddr = <%s>\n", result->ciaddr);
325      DEBUG_F("giaddr = <%s>\n", result->giaddr);
326      DEBUG_F("bootp_retries = <%s>\n", result->bootp_retries);
327      DEBUG_F("tftp_retries = <%s>\n", result->tftp_retries);
328      DEBUG_F("addl_params = <%s>\n", result->addl_params);
329    
330      return ret;
331 }
332
333 static char *netdev_path_to_dev(const char *path)
334 {
335      char *dev, *tmp;
336      size_t len;
337
338      DEBUG_F("path = %s\n", path);
339
340      if (!path)
341           return NULL;
342
343      tmp = strchr(path, ':');
344      if (!tmp)
345           return strdup(path);
346      tmp++;
347
348      len = tmp - path + 1;
349
350      dev = malloc(len);
351      if (dev) {
352           strncpy(dev, path, len);
353           dev[len - 1] = '\0';
354      }
355      return dev;
356 }
357
358 /* This function follows the device path in the devtree and separates
359    the device name, partition number, and other datas (mostly file name)
360    the string passed in parameters is changed since 0 are put in place
361    of some separators to terminate the various strings.
362
363    when a default device is supplied imagepath will be assumed to be a
364    plain filename unless it contains a : otherwise if defaultdev is
365    NULL imagepath will be assumed to be a device path.
366
367    returns 1 on success 0 on failure.
368
369    Supported examples:
370     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4
371     - /pci@80000000/pci-bridge@d/ADPT,2930CU@2/@1:4,/boot/vmlinux
372     - hd:3,/boot/vmlinux
373     - enet:10.0.0.1,/tftpboot/vmlinux
374     - enet:,/tftpboot/vmlinux
375     - enet:bootp
376     - enet:0
377     - arguments for obp-tftp open as specified in section 4.1 of
378       http://playground.sun.com/1275/practice/obp-tftp/tftp1_0.pdf
379       [bootp,]siaddr,filename,ciaddr,giaddr,bootp-retries,tftp-retries
380       ex: enet:bootp,10.0.0.11,bootme,10.0.0.12,10.0.0.1,5,5
381    Supported only if defdevice == NULL
382     - disc
383     - any other device path lacking a :
384    Unsupported examples:
385     - hd:2,\\:tbxi <- no filename will be detected due to the extra :
386     - enet:192.168.2.1,bootme,c-iaddr,g-iaddr,subnet-mask,bootp-retries,tftp-retries */
387
388 int
389 parse_device_path(char *imagepath, char *defdevice, int defpart,
390                   char *deffile, struct boot_fspec_t *result)
391 {
392      char *ptr;
393      char *ipath = NULL;
394      char *defdev = NULL;
395      int device_kind = -1;
396
397      DEBUG_F("imagepath = %s; defdevice %s; defpart %d, deffile %s\n",
398                 imagepath, defdevice, defpart, deffile);
399
400      result->dev = NULL;
401      result->part = -1;
402      result->file = NULL;
403
404      if (!imagepath)
405           return 0;
406
407       /*
408        * Do preliminary checking for an iscsi device; it may appear as
409        * pure a network device (device_type == "network") if this is
410        * ISWI.  This is the case on IBM systems doing an iscsi OFW
411        * boot.
412        */
413      if (strstr(imagepath, TOK_ISCSI)) {
414           /*
415            * get the virtual device information from the
416            * "nas-bootdevice" property.
417            */
418           if (prom_get_chosen("nas-bootdevice", bootdevice, BOOTDEVSZ)) {
419                DEBUG_F("reset boot-device to"
420                        " /chosen/nas-bootdevice = %s\n", bootdevice);
421                device_kind = FILE_DEVICE_ISCSI;
422                ipath = strdup(bootdevice);
423                if (!ipath)
424                     return 0;
425           }
426           else
427                return 0;
428      }
429      else if (!(ipath = strdup(imagepath)))
430           return 0;
431
432      if (defdevice) {
433           defdev = strdup(defdevice);
434           device_kind = prom_get_devtype(defdev);
435      } else if (device_kind == -1)
436           device_kind = prom_get_devtype(ipath);
437
438      /*
439       * When an iscsi iqn is present, it may have embedded colons, so
440       * don't parse off anything.
441       */
442      if (device_kind != FILE_DEVICE_NET &&
443          device_kind != FILE_DEVICE_ISCSI &&
444          strchr(defdev, ':') != NULL) {
445            if ((ptr = strrchr(defdev, ':')) != NULL)
446                 *ptr = 0; /* remove trailing : from defdevice if necessary */
447      }
448
449      /* This will not properly handle an obp-tftp argument list
450       * with elements after the filename; that is handled below.
451       */
452      if (device_kind != FILE_DEVICE_NET &&
453          device_kind != FILE_DEVICE_ISCSI &&
454          strchr(ipath, ':') != NULL) {
455           if ((ptr = strrchr(ipath, ',')) != NULL) {
456                char *colon = strrchr(ipath, ':');
457                /* If a ':' occurs *after* a ',', then we assume that there is
458                   no filename */
459                if (!colon || colon < ptr) {
460                     result->file = strdup(ptr+1);
461                     /* Trim the filename off */
462                     *ptr = 0;
463                }
464           }
465      }
466
467      if (device_kind == FILE_DEVICE_NET) {
468           if (strchr(ipath, ':')) {
469                if (extract_netboot_args(ipath, result) == 0)
470                    return 0;
471           } else {
472                /* If we didn't get a ':' then look only in netinfo */
473                extract_netinfo_args(result);
474                result->file = strdup(ipath);
475           }
476
477           if (!defdev)
478                result->dev = netdev_path_to_dev(ipath);
479      } else if (device_kind != FILE_DEVICE_ISCSI &&
480                 (ptr = strrchr(ipath, ':')) != NULL) {
481           *ptr = 0;
482           result->dev = strdup(ipath);
483           if (*(ptr+1))
484                result->part = simple_strtol(ptr+1, NULL, 10);
485      } else if (!defdev) {
486           result->dev = strdup(ipath);
487      } else if (strlen(ipath)) {
488           result->file = strdup(ipath);
489      } else {
490           free(defdev);
491           return 0;
492      }
493
494      if (!result->dev && defdev)
495           result->dev = strdup(defdev);
496
497      if (result->part < 0)
498           result->part = defpart;
499
500      if (!result->file)
501           result->file = strdup(deffile);
502
503      free(ipath);
504      if (defdev)
505           free(defdev);
506      return 1;
507 }
508
509
510 static int
511 file_block_open(        struct boot_file_t*     file,
512                         struct boot_fspec_t*    fspec,
513                         int                     partition)
514 {
515      struct partition_t*        parts;
516      struct partition_t*        p;
517      struct partition_t*        found;
518
519      parts = partitions_lookup(fspec->dev);
520      found = NULL;
521
522 #if DEBUG
523      if (parts)
524           prom_printf("partitions:\n");
525      else
526           prom_printf("no partitions found.\n");
527 #endif
528      for (p = parts; p && !found; p=p->next) {
529           DEBUG_F("number: %02d, start: 0x%08lx, length: 0x%08lx\n",
530                   p->part_number, p->part_start, p->part_size );
531           if (partition == -1) {
532                file->fs = fs_open( file, p, fspec );
533                if (file->fs == NULL || fserrorno != FILE_ERR_OK)
534                     continue;
535                else {
536                     partition = p->part_number;
537                     goto done;
538                }
539           }
540           if ((partition >= 0) && (partition == p->part_number))
541                found = p;
542 #if DEBUG
543           if (found)
544                prom_printf(" (match)\n");
545 #endif
546      }
547
548      /* Note: we don't skip when found is NULL since we can, in some
549       * cases, let OF figure out a default partition.
550       */
551      DEBUG_F( "Using OF defaults.. (found = %p)\n", found );
552      file->fs = fs_open( file, found, fspec );
553
554 done:
555      if (parts)
556           partitions_free(parts);
557
558      return fserrorno;
559 }
560
561 static int
562 file_net_open(struct boot_file_t* file, struct boot_fspec_t *fspec)
563 {
564      file->fs = fs_of_netboot;
565      return fs_of_netboot->open(file, NULL, fspec);
566 }
567
568 static int
569 default_read(   struct boot_file_t*     file,
570                 unsigned int            size,
571                 void*                   buffer)
572 {
573      prom_printf("WARNING ! default_read called !\n");
574      return FILE_ERR_EOF;
575 }
576
577 static int
578 default_seek(   struct boot_file_t*     file,
579                 unsigned int            newpos)
580 {
581      prom_printf("WARNING ! default_seek called !\n");
582      return FILE_ERR_EOF;
583 }
584
585 static int
586 default_close(  struct boot_file_t*     file)
587 {
588      prom_printf("WARNING ! default_close called !\n");
589      return FILE_ERR_OK;
590 }
591
592 static struct fs_t fs_default =
593 {
594      "defaults",
595      NULL,
596      default_read,
597      default_seek,
598      default_close
599 };
600
601
602 int open_file(struct boot_fspec_t* spec, struct boot_file_t* file)
603 {
604      int result;
605
606      memset(file, 0, sizeof(struct boot_file_t*));
607      file->fs        = &fs_default;
608
609      DEBUG_F("dev_path = %s\nfile_name = %s\npartition = %d\n",
610              spec->dev, spec->file, spec->part);
611
612      result = prom_get_devtype(spec->dev);
613      if (result > 0)
614           file->device_kind = result;
615      else
616           return result;
617
618      switch(file->device_kind) {
619      case FILE_DEVICE_BLOCK:
620           DEBUG_F("device is a block device\n");
621           return file_block_open(file, spec, spec->part);
622      case FILE_DEVICE_NET:
623           DEBUG_F("device is a network device\n");
624           return file_net_open(file, spec);
625      }
626      return 0;
627 }
628
629 /*
630  * Local variables:
631  * c-file-style: "k&r"
632  * c-basic-offset: 5
633  * End:
634  */