1 /*****************************************************************************/
2 /*  LibreDWG - free implementation of the DWG file format                    */
3 /*                                                                           */
4 /*  Copyright (C) 2018-2020 Free Software Foundation, Inc.                   */
5 /*                                                                           */
6 /*  This library is free software, licensed under the terms of the GNU       */
7 /*  General Public License as published by the Free Software Foundation,     */
8 /*  either version 3 of the License, or (at your option) any later version.  */
9 /*  You should have received a copy of the GNU General Public License        */
10 /*  along with this program.  If not, see <http://www.gnu.org/licenses/>.    */
11 /*****************************************************************************/
12 
13 /* dwgwrite.c: write a DWG file from various input formats.
14  * written by Reini Urban
15  */
16 
17 #include "../src/config.h"
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 // strings.h or string.h
22 #ifdef AX_STRCASECMP_HEADER
23 #  include AX_STRCASECMP_HEADER
24 #endif
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #ifdef HAVE_VALGRIND_VALGRIND_H
29 #  include <valgrind/valgrind.h>
30 #endif
31 
32 #include "dwg.h"
33 #include "common.h"
34 #include "bits.h"
35 #include "suffix.inc"
36 #include "decode.h"
37 #include "encode.h"
38 #ifndef DISABLE_JSON
39 #  include "in_json.h"
40 #endif
41 #include "in_dxf.h"
42 
43 static int opts = 1;
44 int overwrite = 0;
45 
46 static int help (void);
47 
48 static int
usage(void)49 usage (void)
50 {
51   printf ("\nUsage: dwgwrite [-v[0-9]] [-y] [--as rNNNN] [-I FMT] [-o DWGFILE] "
52           "INFILE\n");
53   return 1;
54 }
55 static int
opt_version(void)56 opt_version (void)
57 {
58   printf ("dwgwrite %s\n", PACKAGE_VERSION);
59   return 0;
60 }
61 static int
help(void)62 help (void)
63 {
64   printf ("\nUsage: dwgwrite [OPTION]... [-o DWGFILE] INFILE\n");
65   printf ("Writes a DWG file from various input formats. Only r2000 for now.\n"
66           "\n");
67 #ifdef HAVE_GETOPT_LONG
68   printf ("  -v[0-9], --verbose [0-9]  verbosity\n");
69   printf ("  --as rNNNN                save as version\n");
70   printf ("           Valid versions:\n");
71   printf ("             r12, r14, r2000 (default)\n");
72   printf ("           Planned versions:\n");
73   printf ("             r9, r10, r11, r2004, r2007, r2010, r2013, r2018\n");
74 #  ifndef DISABLE_JSON
75   printf ("  -I fmt,  --format fmt     DXF, DXFB, JSON\n");
76 #else
77   printf ("  -I fmt,  --format fmt     DXF, DXFB\n");
78 #  endif
79   printf (
80       "           Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
81   printf ("  -o dwgfile, --file        \n");
82   printf ("  -y, --overwrite           overwrite existing files\n");
83   printf ("           --help           display this help and exit\n");
84   printf ("           --version        output version information and exit\n"
85           "\n");
86 #else
87   printf ("  -v[0-9]     verbosity\n");
88   printf ("  -a rNNNN    save as version\n");
89   printf ("              Valid versions:\n");
90   printf ("                r12, r14, r2000 (default)\n");
91   printf ("              Planned versions:\n");
92   printf ("                r9, r10, r11, r2004, r2007, r2010, r2013, r2018\n");
93 #  ifndef DISABLE_JSON
94   printf ("  -I fmt      fmt: DXF, DXFB, JSON\n");
95 #  else
96   printf ("  -I fmt      fmt: DXF, DXFB\n");
97 #endif
98   printf ("              Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
99   printf ("  -o dwgfile\n");
100   printf ("  -y          overwrite existing files\n");
101   printf ("  -h          display this help and exit\n");
102   printf ("  -i          output version information and exit\n"
103           "\n");
104 #endif
105   printf ("GNU LibreDWG online manual: "
106           "<https://www.gnu.org/software/libredwg/>\n");
107   return 0;
108 }
109 
110 #ifdef __AFL_COMPILER
111 __AFL_FUZZ_INIT();
main(int argc,char * argv[])112 int main (int argc, char *argv[])
113 {
114   Dwg_Data dwg;
115   Bit_Chain dat = { NULL, 0, 0, 0, 0 };
116   Bit_Chain out_dat = { NULL, 0, 0, 0, 0 };
117   FILE *fp;
118 
119   __AFL_INIT();
120   dat.chain = NULL;
121   dat.version = R_2000;
122   printf ("Fuzzing in_json + encode from shared memory\n");
123 
124   while (__AFL_LOOP(10000)) { // llvm_mode persistent, non-forking mode
125 #if 1 // fastest mode via shared mem (crashes still)
126     dat.chain = __AFL_FUZZ_TESTCASE_BUF;
127     dat.size = __AFL_FUZZ_TESTCASE_LEN;
128 #elif 1 // still 1000x faster than the old file-forking fuzzer.
129     /* from stdin: */
130     dat.size = 0;
131     dat_read_stream (&dat, stdin);
132 #else
133     /* else from file */
134     stat (argv[1], &attrib);
135     fp = fopen (argv[1], "rb");
136     if (!fp)
137       return 0;
138     dat.size = attrib.st_size;
139     dat_read_file (&dat, fp, argv[1]);
140     fclose (fp);
141 #endif
142     if (dat.size < 100) continue;  // useful minimum input length
143 
144     if (dwg_read_json (&dat, &dwg) <= DWG_ERR_CRITICAL) {
145       memset (&out_dat, 0, sizeof (out_dat));
146       bit_chain_set_version (&out_dat, &dat);
147       out_dat.version = R_2000;
148       if (dwg_encode (&dwg, &out_dat) >= DWG_ERR_CRITICAL)
149         exit (0);
150       free (out_dat.chain);
151     }
152     else
153       exit (0);
154   }
155   dwg_free (&dwg);
156 }
157 #define main orig_main
158 int orig_main (int argc, char *argv[]);
159 #endif
160 
161 int
main(int argc,char * argv[])162 main (int argc, char *argv[])
163 {
164   int i = 1;
165   int error = 0;
166   Dwg_Data dwg;
167   const char *fmt = NULL;
168   const char *infile = NULL;
169   char *outfile = NULL;
170   Bit_Chain dat = { 0 };
171   const char *version = NULL;
172   Dwg_Version_Type dwg_version = R_2000;
173   int c;
174   int force_free = 0;
175   int free_outfile = 0;
176 #ifdef HAVE_GETOPT_LONG
177   int option_index = 0;
178   static struct option long_options[]
179       = { { "verbose", 1, &opts, 1 }, // optional
180           { "format", 1, 0, 'I' },    { "file", 1, 0, 'o' },
181           { "as", 1, 0, 'a' },        { "help", 0, 0, 0 },
182           { "overwrite", 0, 0, 'y' }, { "version", 0, 0, 0 },
183           { "force-free", 0, 0, 0 },
184           { NULL, 0, NULL, 0 } };
185 #endif
186 
187   if (argc < 2)
188     return usage ();
189 
190   while
191 #ifdef HAVE_GETOPT_LONG
192       ((c
193         = getopt_long (argc, argv, "ya:v::I:o:h", long_options, &option_index))
194        != -1)
195 #else
196       ((c = getopt (argc, argv, "ya:v::I:o:hi")) != -1)
197 #endif
198     {
199       if (c == -1)
200         break;
201       switch (c)
202         {
203         case ':': // missing arg
204           if (optarg && !strcmp (optarg, "v"))
205             {
206               opts = 1;
207               break;
208             }
209           fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
210                    optopt);
211           break;
212 #ifdef HAVE_GETOPT_LONG
213         case 0:
214           /* This option sets a flag */
215           if (!strcmp (long_options[option_index].name, "verbose"))
216             {
217               if (opts < 0 || opts > 9)
218                 return usage ();
219 #  if defined(USE_TRACING) && defined(HAVE_SETENV)
220               {
221                 char v[2];
222                 *v = opts + '0';
223                 *(v + 1) = 0;
224                 setenv ("LIBREDWG_TRACE", v, 1);
225               }
226 #  endif
227               break;
228             }
229           if (!strcmp (long_options[option_index].name, "version"))
230             return opt_version ();
231           if (!strcmp (long_options[option_index].name, "help"))
232             return help ();
233           if (!strcmp (long_options[option_index].name, "force-free"))
234             force_free = 1;
235           break;
236 #else
237         case 'i':
238           return opt_version ();
239 #endif
240         case 'I':
241           fmt = optarg;
242           break;
243         case 'y':
244           overwrite = 1;
245           break;
246         case 'o':
247           outfile = optarg;
248           break;
249         case 'a':
250           dwg_version = dwg_version_as (optarg);
251           if (dwg_version == R_INVALID)
252             {
253               fprintf (stderr, "Invalid version '%s'\n", argv[1]);
254               return usage ();
255             }
256           version = optarg;
257           break;
258         case 'v': // support -v3 and -v
259           i = (optind > 0 && optind < argc) ? optind - 1 : 1;
260           if (!memcmp (argv[i], "-v", 2))
261             {
262               opts = argv[i][2] ? argv[i][2] - '0' : 1;
263             }
264           if (opts < 0 || opts > 9)
265             return usage ();
266 #if defined(USE_TRACING) && defined(HAVE_SETENV)
267           {
268             char v[2];
269             *v = opts + '0';
270             *(v + 1) = 0;
271             setenv ("LIBREDWG_TRACE", v, 1);
272           }
273 #endif
274           break;
275         case 'h':
276           return help ();
277         case '?':
278           fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
279                    optopt);
280           break;
281         default:
282           return usage ();
283         }
284     }
285   i = optind;
286 
287 #ifdef __AFL_HAVE_MANUAL_CONTROL
288   // llvm_mode deferred init
289   __AFL_INIT();
290 #endif
291 
292 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
293   while (__AFL_LOOP(1000)) { // llvm_mode persistent mode (currently broken)
294 #endif
295 
296   // get input format from INFILE, not outfile.
297   // With stdin, should -I be mandatory, or try to autodetect the format?
298   // With a file use the extension.
299   if (optind < argc) // have arg
300     {
301       infile = argv[i];
302       if (!fmt)
303         {
304 #ifndef DISABLE_DXF
305 #  ifndef DISABLE_JSON
306           if (strstr (infile, ".json") || strstr (infile, ".JSON"))
307             fmt = (char *)"json";
308           else
309 #  endif
310           if (strstr (infile, ".dxfb") || strstr (infile, ".DXFB"))
311             fmt = (char *)"dxfb";
312           else if (strstr (infile, ".dxf") || strstr (infile, ".DXF"))
313             fmt = (char *)"dxf";
314           else
315 #endif
316             fprintf (stderr, "Unknown input format for '%s'\n", infile);
317         }
318     }
319 
320   // allow stdin, but require -I|--format then
321   memset (&dwg, 0, sizeof (Dwg_Data));
322   dat.opts = dwg.opts = opts;
323   dat.version = R_2000; // initial target for the importer
324 
325   if (infile)
326     {
327       struct stat attrib;
328       if (stat (infile, &attrib)) // not exists
329         {
330           fprintf (stderr, "Missing input file '%s'\n", infile);
331           exit (1);
332         }
333       dat.fh = fopen (infile, "rb");
334       if (!dat.fh)
335         {
336           fprintf (stderr, "Could not read file '%s'\n", infile);
337           exit (1);
338         }
339       dat.size = attrib.st_size;
340     }
341   else
342     {
343       dat.fh = stdin;
344     }
345 
346 #ifndef DISABLE_DXF
347 #  ifndef DISABLE_JSON
348   if ((fmt && !strcasecmp (fmt, "json"))
349       || (infile && !strcasecmp (infile, ".json")))
350     {
351       if (opts > 1)
352         fprintf (stderr, "Reading JSON file %s\n",
353                  infile ? infile : "from stdin");
354       if (infile)
355         dat_read_file (&dat, dat.fh, infile);
356       error = dwg_read_json (&dat, &dwg);
357     }
358   else
359 #  endif
360   if ((fmt && !strcasecmp (fmt, "dxfb"))
361            || (infile && !strcasecmp (infile, ".dxfb")))
362     {
363       if (opts > 1)
364         {
365           fprintf (stderr, "Reading Binary DXF file %s\n",
366                  infile ? infile : "from stdin");
367           fprintf (stderr, "Warning: still highly experimental and untested.\n");
368         }
369       if (infile)
370         error = dxf_read_file (infile, &dwg); // ascii or binary
371       else
372         error = dwg_read_dxfb (&dat, &dwg);
373     }
374   else if ((fmt && !strcasecmp (fmt, "dxf"))
375            || (infile && !strcasecmp (infile, ".dxf")))
376     {
377       if (opts > 1)
378         {
379           fprintf (stderr, "Reading DXF file %s\n",
380                    infile ? infile : "from stdin");
381         }
382       if (infile)
383         error = dxf_read_file (infile, &dwg); // ascii or binary
384       else
385         error = dwg_read_dxf (&dat, &dwg);
386     }
387   else
388 #endif
389     {
390       if (fmt)
391         fprintf (stderr, "Invalid or unsupported input format '%s'\n", fmt);
392       else if (infile)
393         fprintf (stderr, "Missing input format for '%s'\n", infile);
394       else
395         fprintf (stderr, "Missing input format\n");
396       if (infile)
397         fclose (dat.fh);
398       free (dat.chain);
399       exit (1);
400     }
401 
402   free (dat.chain);
403   if (infile && dat.fh)
404     fclose (dat.fh);
405   if (error >= DWG_ERR_CRITICAL)
406     goto free;
407 
408   if (dwg.header.from_version == R_INVALID)
409     fprintf (stderr, "Unknown DWG header.from_version");
410   // FIXME: for now only R_13 - R_2000. later remove this line.
411   if (dwg.header.from_version < R_13 || dwg.header.from_version >= R_2004)
412     dat.version = dwg.header.version = dwg_version;
413   else
414     dat.version = dwg.header.version = dwg.header.from_version;
415 
416   if (!outfile)
417     {
418       outfile = suffix (infile, "dwg");
419       free_outfile = 1;
420     }
421 
422   if (opts > 1)
423     fprintf (stderr, "Writing DWG file %s\n", outfile);
424 
425   {
426     struct stat attrib;
427     if (!stat (outfile, &attrib)) // exists
428       {
429         if (!overwrite)
430           {
431             fprintf (stderr, "File not overwritten: %s, use -y.\n", outfile);
432             error |= DWG_ERR_IOERROR;
433           }
434         else
435           {
436             if (S_ISREG (attrib.st_mode) &&   // refuse to remove a directory
437                 (access (outfile, W_OK) == 0) // writable
438 #ifndef _WIN32
439                 // refuse to remove a symlink. even with overwrite. security
440                 && !S_ISLNK (attrib.st_mode)
441 #endif
442             )
443               {
444                 unlink (outfile);
445                 dwg.opts |= opts;
446                 error = dwg_write_file (outfile, &dwg);
447               }
448             else if ( // for fuzzing mainly
449 #ifdef _WIN32
450                      strEQc (outfile, "NUL")
451 #else
452                      strEQc (outfile, "/dev/null")
453 #endif
454                      )
455               {
456                 dwg.opts |= opts;
457                 error = dwg_write_file (outfile, &dwg);
458               }
459             else
460               {
461                 fprintf (stderr, "Not writable file or symlink: %s\n",
462                          outfile);
463                 error |= DWG_ERR_IOERROR;
464               }
465           }
466       }
467     else
468       {
469         dwg.opts |= opts;
470         error = dwg_write_file (outfile, &dwg);
471       }
472   }
473 
474   free:
475 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
476   {
477     char *asanenv = getenv("ASAN_OPTIONS");
478     if (!asanenv)
479       force_free = 1;
480     // detect_leaks is enabled by default. see if it's turned off
481     else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
482       force_free = 1;
483   }
484 #endif
485   // forget about leaks. really huge DWG's need endlessly here.
486   if ((dwg.header.version && dwg.num_objects < 1000)
487       || force_free
488 #ifdef HAVE_VALGRIND_VALGRIND_H
489       || (RUNNING_ON_VALGRIND)
490 #endif
491       )
492     {
493       dwg_free (&dwg);
494     }
495 
496 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
497   } // __AFL_LOOP(1000) persistent mode
498 #endif
499 
500   if (error >= DWG_ERR_CRITICAL)
501     {
502       fprintf (stderr, "ERROR 0x%x\n", error);
503       if (error && opts > 2)
504         dwg_errstrings (error);
505     }
506   else
507     {
508       if (opts > 1)
509         {
510           fprintf (stderr, "SUCCESS 0x%x\n", error);
511           if (error && opts > 2)
512             dwg_errstrings (error);
513         }
514       else
515         fprintf (stderr, "SUCCESS\n");
516     }
517 
518   if (free_outfile)
519     free (outfile);
520   return error >= DWG_ERR_CRITICAL ? 1 : 0;
521 }
522