Alloc main config section before reading config file in case there are any sections...
[freeradius.git] / src / main / radmin.c
1 /*
2  * radmin.c     RADIUS Administration tool.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2012   The FreeRADIUS server project
21  * Copyright 2012   Alan DeKok <aland@deployingradius.com>
22  */
23
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/md5.h>
28
29 #ifdef HAVE_SYS_UN_H
30 #  include <sys/un.h>
31 #  ifndef SUN_LEN
32 #    define SUN_LEN(su)  (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
33 #  endif
34 #endif
35
36 #ifdef HAVE_GETOPT_H
37 #  include <getopt.h>
38 #endif
39
40 #ifdef HAVE_SYS_STAT_H
41 #  include <sys/stat.h>
42 #endif
43
44 #ifdef HAVE_LIBREADLINE
45
46 #if defined(HAVE_READLINE_READLINE_H)
47 #  include <readline/readline.h>
48 #  define USE_READLINE (1)
49 #elif defined(HAVE_READLINE_H)
50 #  include <readline.h>
51 #  define USE_READLINE (1)
52 #endif /* !defined(HAVE_READLINE_H) */
53
54 #ifdef HAVE_READLINE_HISTORY
55 #  if defined(HAVE_READLINE_HISTORY_H)
56 #    include <readline/history.h>
57 #    define USE_READLINE_HISTORY (1)
58 #  elif defined(HAVE_HISTORY_H)
59 #    include <history.h>
60 #    define USE_READLINE_HISTORY (1)
61 #endif /* defined(HAVE_READLINE_HISTORY_H) */
62
63 #endif /* HAVE_READLINE_HISTORY */
64
65 #endif /* HAVE_LIBREADLINE */
66
67 /*
68  *      For configuration file stuff.
69  */
70 char const *progname = "radmin";
71 static char const *radmin_version = "radmin version " RADIUSD_VERSION_STRING
72 #ifdef RADIUSD_VERSION_COMMIT
73 " (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")"
74 #endif
75 ", built on " __DATE__ " at " __TIME__;
76
77
78 /*
79  *      The rest of this is because the conffile.c, etc. assume
80  *      they're running inside of the server.  And we don't (yet)
81  *      have a "libfreeradius-server", or "libfreeradius-util".
82  */
83 log_lvl_t debug_flag = 0;
84 struct main_config_t main_config;
85
86 bool check_config = false;
87
88 static FILE *outputfp = NULL;
89 static bool echo = false;
90 static char const *secret = "testing123";
91
92 #include <sys/wait.h>
93 pid_t rad_fork(void)
94 {
95         return fork();
96 }
97
98 #ifdef HAVE_PTHREAD_H
99 pid_t rad_waitpid(pid_t pid, int *status)
100 {
101         return waitpid(pid, status, 0);
102 }
103 #endif
104
105 static void NEVER_RETURNS usage(int status)
106 {
107         FILE *output = status ? stderr : stdout;
108         fprintf(output, "Usage: %s [ args ]\n", progname);
109         fprintf(output, "  -d raddb_dir    Configuration files are in \"raddbdir/*\".\n");
110         fprintf(stderr, "  -D <dictdir>    Set main dictionary directory (defaults to " DICTDIR ").\n");
111         fprintf(output, "  -e command      Execute 'command' and then exit.\n");
112         fprintf(output, "  -E              Echo commands as they are being executed.\n");
113         fprintf(output, "  -f socket_file  Open socket_file directly, without reading radius.conf\n");
114         fprintf(output, "  -h              Print usage help information.\n");
115         fprintf(output, "  -i input_file   Read commands from 'input_file'.\n");
116         fprintf(output, "  -n name         Read raddb/name.conf instead of raddb/radiusd.conf\n");
117         fprintf(output, "  -o output_file  Write commands to 'output_file'.\n");
118         fprintf(output, "  -q              Quiet mode.\n");
119
120         exit(status);
121 }
122
123 static int fr_domain_socket(char const *path)
124 {
125         int sockfd = -1;
126 #ifdef HAVE_SYS_UN_H
127         size_t len;
128         socklen_t socklen;
129         struct sockaddr_un saremote;
130
131         len = strlen(path);
132         if (len >= sizeof(saremote.sun_path)) {
133                 fprintf(stderr, "%s: Path too long in filename\n", progname);
134                 return -1;
135         }
136
137         if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
138                 fprintf(stderr, "%s: Failed creating socket: %s\n",
139                         progname, fr_syserror(errno));
140                 return -1;
141         }
142
143         saremote.sun_family = AF_UNIX;
144         memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
145
146         socklen = SUN_LEN(&saremote);
147
148         if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
149                 struct stat buf;
150
151                 close(sockfd);
152                 fprintf(stderr, "%s: Failed connecting to %s: %s\n",
153                         progname, path, fr_syserror(errno));
154
155                 /*
156                  *      The file doesn't exist.  Tell the user how to
157                  *      fix it.
158                  */
159                 if ((stat(path, &buf) < 0) &&
160                     (errno == ENOENT)) {
161                         fprintf(stderr, "  Perhaps you need to run the commands:\n\tcd /etc/raddb\n\tln -s sites-available/control-socket sites-enabled/control-socket\n  and then re-start the server?\n");
162                 }
163
164                 return -1;
165         }
166
167 #ifdef O_NONBLOCK
168         {
169                 int flags;
170
171                 if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0)  {
172                         fprintf(stderr, "%s: Failure getting socket flags: %s",
173                                 progname, fr_syserror(errno));
174                         close(sockfd);
175                         return -1;
176                 }
177
178                 flags |= O_NONBLOCK;
179                 if( fcntl(sockfd, F_SETFL, flags) < 0) {
180                         fprintf(stderr, "%s: Failure setting socket flags: %s",
181                                 progname, fr_syserror(errno));
182                         close(sockfd);
183                         return -1;
184                 }
185         }
186 #endif
187 #endif
188         return sockfd;
189 }
190
191 static int client_socket(char const *server)
192 {
193         int sockfd;
194         uint16_t port;
195         fr_ipaddr_t ipaddr;
196         char *p, buffer[1024];
197
198         strlcpy(buffer, server, sizeof(buffer));
199
200         p = strchr(buffer, ':');
201         if (!p) {
202                 port = PW_RADMIN_PORT;
203         } else {
204                 port = atoi(p + 1);
205                 *p = '\0';
206         }
207
208         if (ip_hton(&ipaddr, AF_INET, buffer, false) < 0) {
209                 fprintf(stderr, "%s: Failed looking up host %s: %s\n",
210                         progname, buffer, fr_syserror(errno));
211                 exit(1);
212         }
213
214         sockfd = fr_tcp_client_socket(NULL, &ipaddr, port);
215         if (sockfd < 0) {
216                 fprintf(stderr, "%s: Failed opening socket %s: %s\n",
217                         progname, server, fr_syserror(errno));
218                 exit(1);
219         }
220
221         return sockfd;
222 }
223
224 static void do_challenge(int sockfd)
225 {
226         size_t total;
227         ssize_t r;
228         uint8_t challenge[16];
229
230         for (total = 0; total < sizeof(challenge); ) {
231                 r = read(sockfd, challenge + total, sizeof(challenge) - total);
232                 if (r == 0) exit(1);
233
234                 if (r < 0) {
235 #ifdef ECONNRESET
236                         if (errno == ECONNRESET) {
237                                 fprintf(stderr, "%s: Connection reset",
238                                         progname);
239                                 exit(1);
240                         }
241 #endif
242                         if (errno == EINTR) continue;
243
244                         fprintf(stderr, "%s: Failed reading data: %s\n",
245                                 progname, fr_syserror(errno));
246                         exit(1);
247                 }
248                 total += r;
249                 fflush(stdout);
250         }
251
252         fr_hmac_md5(challenge, (uint8_t const *) secret, strlen(secret),
253                     challenge, sizeof(challenge));
254
255         if (write(sockfd, challenge, sizeof(challenge)) < 0) {
256                 fprintf(stderr, "%s: Failed writing challenge data: %s\n",
257                         progname, fr_syserror(errno));
258         }
259 }
260
261 static ssize_t run_command(int sockfd, char const *command,
262                            char *buffer, size_t bufsize)
263 {
264         char *p;
265         ssize_t size, len;
266
267         if (echo) {
268                 fprintf(outputfp, "%s\n", command);
269         }
270
271         /*
272          *      Write the text to the socket.
273          */
274         if (write(sockfd, command, strlen(command)) < 0) return -1;
275         if (write(sockfd, "\r\n", 2) < 0) return -1;
276
277         /*
278          *      Read the response
279          */
280         size = 0;
281         buffer[0] = '\0';
282
283         memset(buffer, 0, bufsize);
284
285         while (1) {
286                 int rcode;
287                 fd_set readfds;
288
289                 FD_ZERO(&readfds);
290                 FD_SET(sockfd, &readfds);
291
292                 rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
293                 if (rcode < 0) {
294                         if (errno == EINTR) continue;
295
296                         fprintf(stderr, "%s: Failed selecting: %s\n",
297                                 progname, fr_syserror(errno));
298                         return -1;
299                 }
300
301                 if (rcode == 0) {
302                         fprintf(stderr, "%s: Server closed the connection.\n",
303                                 progname);
304                         return -1;
305                 }
306
307 #ifdef MSG_DONTWAIT
308                 len = recv(sockfd, buffer + size,
309                            bufsize - size - 1, MSG_DONTWAIT);
310 #else
311                 /*
312                  *      Read one byte at a time (ugh)
313                  */
314                 len = recv(sockfd, buffer + size, 1, 0);
315 #endif
316                 if (len < 0) {
317                         /*
318                          *      No data: keep looping
319                          */
320                         if ((errno == EAGAIN) || (errno == EINTR)) {
321                                 continue;
322                         }
323
324                         fprintf(stderr, "%s: Error reading socket: %s\n",
325                                 progname, fr_syserror(errno));
326                         return -1;
327                 }
328                 if (len == 0) return 0; /* clean exit */
329
330                 size += len;
331                 buffer[size] = '\0';
332
333                 /*
334                  *      There really is a better way of doing this.
335                  */
336                 p = strstr(buffer, "radmin> ");
337                 if (p &&
338                     ((p == buffer) ||
339                      (p[-1] == '\n') ||
340                      (p[-1] == '\r'))) {
341                         *p = '\0';
342
343                         if (p[-1] == '\n') p[-1] = '\0';
344                         break;
345                 }
346         }
347
348         /*
349          *      Blank prompt.  Go get another command.
350          */
351         if (!buffer[0]) return 1;
352
353         buffer[size] = '\0'; /* this is at least right */
354
355         return 2;
356 }
357
358 static int do_connect(int *out, char const *file, char const *server)
359 {
360         char buffer[65536];
361         ssize_t len, size;
362         int sockfd;
363
364         uint32_t magic, needed;
365
366         /*
367          *      Close stale file descriptors
368          */
369         if (*out != -1) {
370                 close(*out);
371                 *out = -1;
372         }
373
374         if (file) {
375                 /*
376                  *      FIXME: Get destination from command line, if possible?
377                  */
378                 sockfd = fr_domain_socket(file);
379                 if (sockfd < 0) return -1;
380         } else {
381                 sockfd = client_socket(server);
382         }
383
384         /*
385          *      Only works for BSD, but Linux allows us
386          *      to mask SIGPIPE, so that's fine.
387          */
388 #ifdef SO_NOSIGPIPE
389         {
390                 int set = 1;
391
392                 setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
393         }
394 #endif
395
396         /*
397          *      Read initial magic && version information.
398          */
399         for (size = 0; size < 8; size += len) {
400                 len = read(sockfd, buffer + size, 8 - size);
401                 if (len < 0) {
402                         fprintf(stderr, "%s: Error reading initial data from socket: %s\n",
403                                 progname, fr_syserror(errno));
404                         close(sockfd);
405                         return -1;
406                 }
407         }
408
409         memcpy(&magic, buffer, 4);
410         magic = ntohl(magic);
411         if (magic != 0xf7eead15) {
412                 fprintf(stderr, "%s: Socket %s is not FreeRADIUS administration socket\n", progname, file);
413                 close(sockfd);
414                 return -1;
415         }
416
417         memcpy(&magic, buffer + 4, 4);
418         magic = ntohl(magic);
419
420         if (!server) {
421                 needed = 1;
422         } else {
423                 needed = 2;
424         }
425
426         if (magic != needed) {
427                 fprintf(stderr, "%s: Socket version mismatch: Need %d, got %d\n",
428                         progname, needed, magic);
429                 close(sockfd);
430                 return -1;
431         }
432
433         if (server && secret) do_challenge(sockfd);
434
435         *out = sockfd;
436
437         return 0;
438 }
439
440 #define MAX_COMMANDS (4)
441
442 int main(int argc, char **argv)
443 {
444         int             argval;
445         bool            quiet = false;
446         int             sockfd = -1;
447         char            *line = NULL;
448         ssize_t         len;
449         char const      *file = NULL;
450         char const      *name = "radiusd";
451         char            *p, buffer[65536];
452         char const      *input_file = NULL;
453         FILE            *inputfp = stdin;
454         char const      *output_file = NULL;
455         char const      *server = NULL;
456
457         char const      *radius_dir = RADIUS_DIR;
458         char const      *dict_dir = DICTDIR;
459
460         char *commands[MAX_COMMANDS];
461         int num_commands = -1;
462
463 #ifndef NDEBUG
464         if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
465                 fr_perror("radmin");
466                 exit(EXIT_FAILURE);
467         }
468 #endif
469
470         talloc_set_log_stderr();
471
472         outputfp = stdout;      /* stdout is not a constant value... */
473
474         if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) {
475                 progname = argv[0];
476         } else {
477                 progname++;
478         }
479
480         while ((argval = getopt(argc, argv, "d:D:hi:e:Ef:n:o:qs:S")) != EOF) {
481                 switch (argval) {
482                 case 'd':
483                         if (file) {
484                                 fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
485                                 exit(1);
486                         }
487                         if (server) {
488                                 fprintf(stderr, "%s: -d and -s cannot be used together.\n", progname);
489                                 exit(1);
490                         }
491                         radius_dir = optarg;
492                         break;
493
494                 case 'D':
495                         dict_dir = optarg;
496                         break;
497
498                 case 'e':
499                         num_commands++; /* starts at -1 */
500                         if (num_commands >= MAX_COMMANDS) {
501                                 fprintf(stderr, "%s: Too many '-e'\n",
502                                         progname);
503                                 exit(1);
504                         }
505                         commands[num_commands] = optarg;
506                         break;
507
508                 case 'E':
509                         echo = true;
510                         break;
511
512                 case 'f':
513                         radius_dir = NULL;
514                         file = optarg;
515                         break;
516
517                 default:
518                 case 'h':
519                         usage(0);       /* never returns */
520
521                 case 'i':
522                         if (strcmp(optarg, "-") != 0) {
523                                 input_file = optarg;
524                         }
525                         quiet = true;
526                         break;
527
528                 case 'n':
529                         name = optarg;
530                         break;
531
532                 case 'o':
533                         if (strcmp(optarg, "-") != 0) {
534                                 output_file = optarg;
535                         }
536                         quiet = true;
537                         break;
538
539                 case 'q':
540                         quiet = true;
541                         break;
542
543                 case 's':
544                         if (file) {
545                                 fprintf(stderr, "%s: -s and -f cannot be used together.\n", progname);
546                                 usage(1);
547                         }
548                         radius_dir = NULL;
549                         server = optarg;
550                         break;
551
552                 case 'S':
553                         secret = NULL;
554                         break;
555                 }
556         }
557
558         /*
559          *      Mismatch between the binary and the libraries it depends on
560          */
561         if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
562                 fr_perror("radmin");
563                 exit(1);
564         }
565
566         if (radius_dir) {
567                 int rcode;
568                 CONF_SECTION *cs, *subcs;
569
570                 file = NULL;    /* MUST read it from the conffile now */
571
572                 snprintf(buffer, sizeof(buffer), "%s/%s.conf", radius_dir, name);
573
574                 /*
575                  *      Need to read in the dictionaries, else we may get
576                  *      validation errors when we try and parse the config.
577                  */
578                 if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
579                         fr_perror("radmin");
580                         exit(64);
581                 }
582
583                 if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
584                         fr_perror("radmin");
585                         exit(64);
586                 }
587
588                 cs = cf_section_alloc(NULL, "main", NULL);
589                 if (!cs) exit(1);
590
591                 if (cf_file_read(cs, buffer) < 0) {
592                         fprintf(stderr, "%s: Errors reading or parsing %s\n", progname, buffer);
593                         usage(1);
594                 }
595
596                 subcs = NULL;
597                 while ((subcs = cf_subsection_find_next(cs, subcs, "listen")) != NULL) {
598                         char const *value;
599                         CONF_PAIR *cp = cf_pair_find(subcs, "type");
600
601                         if (!cp) continue;
602
603                         value = cf_pair_value(cp);
604                         if (!value) continue;
605
606                         if (strcmp(value, "control") != 0) continue;
607
608                         /*
609                          *      Now find the socket name (sigh)
610                          */
611                         rcode = cf_item_parse(subcs, "socket", FR_ITEM_POINTER(PW_TYPE_STRING, &file), NULL);
612                         if (rcode < 0) {
613                                 fprintf(stderr, "%s: Failed parsing listen section\n", progname);
614                                 exit(1);
615                         }
616
617                         if (!file) {
618                                 fprintf(stderr, "%s: No path given for socket\n", progname);
619                                 usage(1);
620                         }
621                         break;
622                 }
623
624                 if (!file) {
625                         fprintf(stderr, "%s: Could not find control socket in %s\n", progname, buffer);
626                         exit(1);
627                 }
628         }
629
630         if (input_file) {
631                 inputfp = fopen(input_file, "r");
632                 if (!inputfp) {
633                         fprintf(stderr, "%s: Failed opening %s: %s\n", progname, input_file, fr_syserror(errno));
634                         exit(1);
635                 }
636         }
637
638         if (output_file) {
639                 outputfp = fopen(output_file, "w");
640                 if (!outputfp) {
641                         fprintf(stderr, "%s: Failed creating %s: %s\n", progname, output_file, fr_syserror(errno));
642                         exit(1);
643                 }
644         }
645
646         if (!file && !server) {
647                 fprintf(stderr, "%s: Must use one of '-d' or '-f' or '-s'\n",
648                         progname);
649                 exit(1);
650         }
651
652         /*
653          *      Check if stdin is a TTY only if input is from stdin
654          */
655         if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = true;
656
657 #ifdef USE_READLINE
658         if (!quiet) {
659 #ifdef USE_READLINE_HISTORY
660                 using_history();
661 #endif
662                 rl_bind_key('\t', rl_insert);
663         }
664 #endif
665
666         /*
667          *      Prevent SIGPIPEs from terminating the process
668          */
669         signal(SIGPIPE, SIG_IGN);
670
671         if (do_connect(&sockfd, file, server) < 0) exit(1);
672
673         /*
674          *      Run one command.
675          */
676         if (num_commands >= 0) {
677                 int i;
678
679                 for (i = 0; i <= num_commands; i++) {
680                         len = run_command(sockfd, commands[i], buffer, sizeof(buffer));
681                         if (len < 0) exit(1);
682
683                         if (buffer[0]) {
684                                 fputs(buffer, outputfp);
685                                 fprintf(outputfp, "\n");
686                                 fflush(outputfp);
687                         }
688                 }
689                 exit(0);
690         }
691
692         if (!quiet) {
693                 printf("%s - FreeRADIUS Server administration tool.\n", radmin_version);
694                 printf("Copyright (C) 2008-2015 The FreeRADIUS server project and contributors.\n");
695                 printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
696                 printf("PARTICULAR PURPOSE.\n");
697                 printf("You may redistribute copies of FreeRADIUS under the terms of the\n");
698                 printf("GNU General Public License v2.\n");
699         }
700
701         /*
702          *      FIXME: Do login?
703          */
704
705         while (1) {
706 #ifndef USE_READLINE
707                 if (!quiet) {
708                         printf("radmin> ");
709                         fflush(stdout);
710                 }
711 #else
712                 if (!quiet) {
713                         line = readline("radmin> ");
714
715                         if (!line) break;
716
717                         if (!*line) {
718                                 free(line);
719                                 continue;
720                         }
721
722 #ifdef USE_READLINE_HISTORY
723                         add_history(line);
724 #endif
725                 } else          /* quiet, or no readline */
726 #endif
727                 {
728                         line = fgets(buffer, sizeof(buffer), inputfp);
729                         if (!line) break;
730
731                         p = strchr(buffer, '\n');
732                         if (!p) {
733                                 fprintf(stderr, "%s: Input line too long\n",
734                                         progname);
735                                 exit(1);
736                         }
737
738                         *p = '\0';
739
740                         /*
741                          *      Strip off leading spaces.
742                          */
743                         for (p = line; *p != '\0'; p++) {
744                                 if ((p[0] == ' ') ||
745                                     (p[0] == '\t')) {
746                                         line = p + 1;
747                                         continue;
748                                 }
749
750                                 if (p[0] == '#') {
751                                         line = NULL;
752                                         break;
753                                 }
754
755                                 break;
756                         }
757
758                         /*
759                          *      Comments: keep going.
760                          */
761                         if (!line) continue;
762
763                         /*
764                          *      Strip off CR / LF
765                          */
766                         for (p = line; *p != '\0'; p++) {
767                                 if ((p[0] == '\r') ||
768                                     (p[0] == '\n')) {
769                                         p[0] = '\0';
770                                         break;
771                                 }
772                         }
773                 }
774
775                 if (strcmp(line, "reconnect") == 0) {
776                         if (do_connect(&sockfd, file, server) < 0) exit(1);
777                         line = NULL;
778                         continue;
779                 }
780
781                 if (memcmp(line, "secret ", 7) == 0) {
782                         if (!secret) {
783                                 secret = line + 7;
784                                 do_challenge(sockfd);
785                         }
786                         line = NULL;
787                         continue;
788                 }
789
790                 /*
791                  *      Exit, done, etc.
792                  */
793                 if ((strcmp(line, "exit") == 0) ||
794                     (strcmp(line, "quit") == 0)) {
795                         break;
796                 }
797
798                 if (server && !secret) {
799                         fprintf(stderr, "ERROR: You must enter 'secret <SECRET>' before running any commands\n");
800                         line = NULL;
801                         continue;
802                 }
803
804                 len = run_command(sockfd, line, buffer, sizeof(buffer));
805                 if ((len < 0) && (do_connect(&sockfd, file, server) < 0)) {
806                         fprintf(stderr, "Reconnecting...");
807                         exit(1);
808                 } else if (len == 0) break;
809                 else if (len == 1) continue; /* no output. */
810
811                 fputs(buffer, outputfp);
812                 fflush(outputfp);
813                 fprintf(outputfp, "\n");
814         }
815
816         fprintf(outputfp, "\n");
817
818         return 0;
819 }
820