1 /**************************************************************************************************
2 	$Id: export.c,v 1.29 2006/01/18 22:45:51 bboy Exp $
3 
4 	Outputs zone data in various formats.
5 
6 	Copyright (C) 2002-2005  Don Moore <bboy@bboy.net>
7 
8 	This program is free software; you can redistribute it and/or modify
9 	it under the terms of the GNU General Public License as published by
10 	the Free Software Foundation; either version 2 of the License, or
11 	(at Your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	GNU General Public License for more details.
17 
18 	You should have received a copy of the GNU General Public License
19 	along with this program; if not, write to the Free Software
20 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 **************************************************************************************************/
22 
23 #include "util.h"
24 
25 char zone[DNS_MAXNAMELEN];										/* Zone name to dump */
26 unsigned int zones_out = 0;									/* Number of zones output */
27 enum _output_format {											/* Output format types */
28 	OUTPUT_BIND,
29 	OUTPUT_TINYDNS
30 } output_format = OUTPUT_BIND;
31 
32 char  hostname[256];                                  /* Hostname of local machine */
33 char *command_line = NULL;										/* Full command line */
34 
35 #define DNSBUFLEN	DNS_MAXNAMELEN+1
36 
37 
38 /**************************************************************************************************
39 	USAGE
40 	Display program usage information.
41 **************************************************************************************************/
42 static void
usage(status)43 usage(status)
44 	int status;
45 {
46 	if (status != EXIT_SUCCESS)
47 	{
48 		fprintf(stderr, _("Try `%s --help' for more information."), progname);
49 		fputs("\n", stderr);
50 	}
51 	else
52 	{
53 		printf(_("Usage: %s [ZONE]..."), progname);
54 		puts("");
55 		puts(_("Output MyDNS zone in formats understood by other DNS servers."));
56 		puts("");
57 /*		puts("----------------------------------------------------------------------------78");  */
58 		puts(_("  -b, --bind              output in BIND format (the default)"));
59 		puts(_("  -t, --tinydns-data      output in tinydns-data format"));
60 		puts("");
61 		puts(_("  -D, --database=DB       database name to use"));
62 		puts(_("  -h, --host=HOST         connect to SQL server at HOST"));
63 		puts(_("  -p, --password=PASS     password for SQL server (or prompt from tty)"));
64 		puts(_("  -u, --user=USER         username for SQL server if not current user"));
65 		puts("");
66 #if DEBUG_ENABLED
67 		puts(_("  -d, --debug             enable debug output"));
68 #endif
69 		puts(_("  -v, --verbose           be more verbose while running"));
70 		puts(_("      --help              display this help and exit"));
71 		puts(_("      --version           output version information and exit"));
72 		puts("");
73 		printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
74 	}
75 	exit(status);
76 }
77 /*--- usage() -----------------------------------------------------------------------------------*/
78 
79 
80 /**************************************************************************************************
81 	CMDLINE
82 	Process command line options.
83 **************************************************************************************************/
84 static void
cmdline(int argc,char ** argv)85 cmdline(int argc, char **argv)
86 {
87 	char	*optstr;
88 	int	optc, optindex, n;
89 	struct option const longopts[] =
90 	{
91 		{"bind",				no_argument,			NULL,	'b'},
92 		{"database",		required_argument,	NULL,	'D'},
93 		{"host",				required_argument,	NULL,	'h'},
94 		{"password",		optional_argument,	NULL,	'p'},
95 		{"tinydns-data",	no_argument,			NULL,	't'},
96 		{"user",				required_argument,	NULL,	'u'},
97 
98 		{"debug",			no_argument,			NULL,	'd'},
99 		{"verbose",			no_argument,			NULL,	'v'},
100 		{"help",				no_argument,			NULL,	0},
101 		{"version",			no_argument,			NULL,	0},
102 
103 		{NULL, 0, NULL, 0}
104 	};
105 
106 	/* Copy full command line into 'command_line' */
107 	for (n = 0; n < argc; n++)
108 	{
109 		if (!command_line)
110 		{
111 			if (!(command_line = strdup(argv[n])))
112 				Err("strdup");
113 		}
114 		else
115 		{
116 			if (!(command_line = realloc(command_line, strlen(command_line) + strlen(argv[n]) + 2)))
117 				Err("realloc");
118 			strcat(command_line, " ");
119 			strcat(command_line, argv[n]);
120 		}
121 	}
122 
123 	gethostname(hostname, sizeof(hostname)-1);
124 
125 	err_file = stdout;
126 	error_init(argv[0], LOG_USER);							/* Init output routines */
127 	optstr = getoptstr(longopts);
128 	while ((optc = getopt_long(argc, argv, optstr, longopts, &optindex)) != -1)
129 	{
130 		switch (optc)
131 		{
132 			case 0:
133 				{
134 					const char *opt = longopts[optindex].name;
135 
136 					if (!strcmp(opt, "version"))									/* --version */
137 					{
138 						printf("%s ("PACKAGE_NAME") "PACKAGE_VERSION" ("SQL_VERSION_STR")\n", progname);
139 						puts("\n" PACKAGE_COPYRIGHT);
140 						puts(_("This is free software; see the source for copying conditions.  There is NO"));
141 						puts(_("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."));
142 						exit(EXIT_SUCCESS);
143 					}
144 					else if (!strcmp(opt, "help"))								/* --help */
145 						usage(EXIT_SUCCESS);
146 				}
147 				break;
148 
149 			case 'd':																	/* -d, --debug */
150 #if DEBUG_ENABLED
151 				err_verbose = err_debug = 1;
152 #endif
153 				break;
154 			case 'D':																	/* -D, --database=DB */
155 				conf_set(&Conf, "database", optarg, 0);
156 				break;
157 			case 'h':																	/* -h, --host=HOST */
158 				conf_set(&Conf, "db-host", optarg, 0);
159 				break;
160 			case 'p':																	/* -p, --password=PASS */
161 				if (optarg)
162 				{
163 					conf_set(&Conf, "db-password", optarg, 0);
164 					memset(optarg, 'X', strlen(optarg));
165 				}
166 				else
167 					conf_set(&Conf, "db-password", passinput(_("Enter password")), 0);
168 				break;
169 			case 'u':																	/* -u, --user=USER */
170 				conf_set(&Conf, "db-user", optarg, 0);
171 				break;
172 
173 			case 'b':																	/* -b, --bind */
174 				output_format = OUTPUT_BIND;
175 				break;
176 			case 't':																	/* -t, --tinydns-data */
177 				output_format = OUTPUT_TINYDNS;
178 				break;
179 
180 			case 'v':																	/* -v, --verbose */
181 				err_verbose = 1;
182 				break;
183 			default:
184 				usage(EXIT_FAILURE);
185 		}
186 	}
187 }
188 /*--- cmdline() ---------------------------------------------------------------------------------*/
189 
190 
191 /**************************************************************************************************
192 	DUMP_HEADER
193 	Dumps a header.  Currently only writes a header for tinydns-data format, since BIND format
194 	puts a header at the start of each "file".
195 **************************************************************************************************/
196 static void
dump_header(void)197 dump_header(void)
198 {
199 	time_t now = time(NULL);
200 
201 	switch (output_format)
202 	{
203 		case OUTPUT_BIND:
204 			break;
205 		case OUTPUT_TINYDNS:
206 			printf("#\n");
207 			printf("# Created by \"%s\"\n", command_line);
208 			printf("# %.24s\n", ctime(&now));
209 			printf("#\n");
210 			break;
211 	}
212 }
213 /*--- dump_header() -----------------------------------------------------------------------------*/
214 
215 
216 /**************************************************************************************************
217 	BIND_DUMP_SOA
218 	Output SOA, BIND format.
219 **************************************************************************************************/
220 static void
bind_dump_soa(MYDNS_SOA * soa)221 bind_dump_soa(MYDNS_SOA *soa)
222 {
223 	time_t now = time(NULL);
224 
225 	if (zones_out > 0)
226 		puts("\f");
227 
228 	printf("$TTL %u\n", soa->ttl);
229 	printf("; Zone: %s (#%u)\n", soa->origin, soa->id);
230 	printf("; Created by \"%s\"\n", command_line);
231 	printf("; %.24s\n", ctime(&now));
232 	printf("$ORIGIN %s\n\n", soa->origin);
233 
234 	printf("@\tIN SOA\t%s\t%s (\n", soa->ns, soa->mbox);
235 	printf("\t%-10u\t  ; Serial\n", soa->serial);
236 	printf("\t%-10u\t  ; Refresh\n", soa->refresh);
237 	printf("\t%-10u\t  ; Retry\n", soa->retry);
238 	printf("\t%-10u\t  ; Expire\n", soa->expire);
239 	printf("\t%-10u\t) ; Minimum\n\n", soa->minimum);
240 }
241 /*--- bind_dump_soa() ---------------------------------------------------------------------------*/
242 
243 
244 /**************************************************************************************************
245 	BIND_DUMP_RR
246 	Output resource record, BIND format.
247 **************************************************************************************************/
248 static void
bind_dump_rr(MYDNS_SOA * soa,MYDNS_RR * rr,int maxlen)249 bind_dump_rr(MYDNS_SOA *soa, MYDNS_RR *rr, int maxlen)
250 {
251 	char *type = mydns_qtype_str(
252 #if ALIAS_ENABLED
253 											(rr->alias == 0) ?
254 #endif /* ALIAS_ENABLED */
255 											 rr->type
256 #if ALIAS_ENABLED
257 											: DNS_QTYPE_CNAME
258 #endif /* ALIAS_ENABLED */
259 	);
260 
261 	printf("%-*s", maxlen, rr->name);
262 
263 	printf("\t%u\tIN %-5s\t", (rr->ttl < soa->minimum) ? soa->minimum : rr->ttl, type);
264 
265 	if (rr->type == DNS_QTYPE_MX)
266 		printf("%u %s\n", (uint32_t)rr->aux, rr->data);
267 	else if (rr->type == DNS_QTYPE_SRV)
268 		printf("%u %u %u %s\n", (uint32_t)rr->aux, rr->srv_weight, rr->srv_port, rr->data);
269 	else if (rr->type == DNS_QTYPE_RP)
270 		printf("%s %s\n", rr->data, rr->rp_txt);
271 	else if (rr->type == DNS_QTYPE_TXT)
272 	{
273 		register unsigned char *c;
274 		putc('"', stdout);
275 		for (c = rr->data; *c; c++)
276 		{
277 			if (*c == '"')
278 				putc('\\', stdout);
279 			putc(*c, stdout);
280 		}
281 		putc('"', stdout);
282 		putc('\n', stdout);
283 	}
284 	else
285 		printf("%s\n", rr->data);
286 }
287 /*--- bind_dump_rr() ----------------------------------------------------------------------------*/
288 
289 
290 #define TINYDNS_NAMEFIX(str) \
291 			if (!(str)[0] || LASTCHAR((str)) != '.') \
292 			{ \
293 				if ((str)[0]) strncat((str), ".", sizeof((str))-strlen((str))-1); \
294 				strncat((str), soa->origin, sizeof((str))-strlen((str))-1); \
295 			} \
296 			if (LASTCHAR((str)) == '.') LASTCHAR((str)) = '\0';
297 
298 
299 /**************************************************************************************************
300 	TINYDNS_DUMP_SOA
301 **************************************************************************************************/
302 static void
tinydns_dump_soa(MYDNS_SOA * soa)303 tinydns_dump_soa(MYDNS_SOA *soa)
304 {
305 	char origin[DNSBUFLEN], ns[DNSBUFLEN], mbox[DNSBUFLEN];
306 
307 	strncpy(origin, soa->origin, sizeof(origin)-1);
308 	strncpy(ns, soa->ns, sizeof(ns)-1);
309 	strncpy(mbox, soa->mbox, sizeof(mbox)-1);
310 
311 	if (LASTCHAR(origin) == '.') LASTCHAR(origin) = '\0';
312 	if (LASTCHAR(ns) == '.') LASTCHAR(ns) = '\0';
313 	if (LASTCHAR(mbox) == '.') LASTCHAR(mbox) = '\0';
314 
315 	printf("Z%s:%s:%s:%u:%u:%u:%u:%u:%u\n",
316 			 origin, ns, mbox,
317 			 soa->serial, soa->refresh, soa->retry, soa->expire, soa->minimum, soa->ttl);
318 }
319 /*--- tinydns_dump_soa() ------------------------------------------------------------------------*/
320 
321 
322 /**************************************************************************************************
323 	TINYDNS_DUMP_RR
324 	Output resource record, BIND format.
325 **************************************************************************************************/
326 static void
tinydns_dump_rr(MYDNS_SOA * soa,MYDNS_RR * rr,int maxlen)327 tinydns_dump_rr(MYDNS_SOA *soa, MYDNS_RR *rr, int maxlen)
328 {
329 	char name[DNSBUFLEN], data[DNSBUFLEN];
330 
331 	strncpy(name, rr->name, sizeof(name));
332 	TINYDNS_NAMEFIX(name);
333 
334 	switch (rr->type)
335 	{
336 		case DNS_QTYPE_A:
337 			printf("=%s:%s:%u\n", name, rr->data, rr->ttl);
338 			break;
339 
340 		case DNS_QTYPE_AAAA:
341 			/* Not supported by tinydns (?) */
342 			break;
343 
344 		case DNS_QTYPE_CNAME:
345 			strncpy(data, rr->data, sizeof(data));
346 			TINYDNS_NAMEFIX(data);
347 			printf("C%s:%s:%u\n", name, data, rr->ttl);
348 			break;
349 
350 		case DNS_QTYPE_MX:
351 			strncpy(data, rr->data, sizeof(data));
352 			TINYDNS_NAMEFIX(data);
353 			printf("@%s::%s:%u:%u\n", name, data, rr->aux, rr->ttl);
354 			break;
355 
356 		case DNS_QTYPE_NS:
357 			strncpy(data, rr->data, sizeof(data));
358 			TINYDNS_NAMEFIX(data);
359 			printf(".%s::%s:%u\n", name, data, rr->ttl);
360 			break;
361 
362 		/* tinydns does not natively support SRV; However, there's a patch
363 			(http://tinydns.org/srv-patch) to support it.  This code complies with
364 			its format, which is "Sfqdn:ip:x:port:weight:priority:ttl:timestamp" */
365 		case DNS_QTYPE_SRV:
366 			strncpy(data, rr->data, sizeof(data));
367 			TINYDNS_NAMEFIX(data);
368 			printf("S%s::%s:%u:%u:%u:%u\n", name, data, rr->srv_port, rr->srv_weight, rr->aux, rr->ttl);
369 			break;
370 
371 		case DNS_QTYPE_TXT:
372 			{
373 				char databuf[DNSBUFLEN * 4], *c, *d;
374 
375 				/* Need to output colons as octal - also any other wierd chars */
376 				for (c = rr->data, d = databuf; *c; c++)
377 				{
378 					if (*c == ':' || !isprint((int)(*c)))
379 						d += sprintf(d, "\\%03o", *c);
380 					else
381 						*(d++) = *c;
382 				}
383 				*d = '\0';
384 				printf("'%s:%s:%u\n", name, databuf, rr->ttl);
385 			}
386 			break;
387 
388 		default:
389 			break;
390 	}
391 }
392 /*--- tinydns_dump_rr() -------------------------------------------------------------------------*/
393 
394 
395 /**************************************************************************************************
396 	DUMP_SOA
397 **************************************************************************************************/
398 static MYDNS_SOA *
dump_soa(void)399 dump_soa(void)
400 {
401 	MYDNS_SOA *soa;
402 
403 	if (mydns_soa_load(sql, &soa, zone) != 0)
404 		ErrSQL(sql, "%s: %s", zone, _("error loading SOA record for zone"));
405 	if (!soa)
406 		Errx("%s: %s", zone, _("zone not found"));
407 
408 	switch (output_format)
409 	{
410 		case OUTPUT_BIND:
411 			bind_dump_soa(soa);
412 			break;
413 		case OUTPUT_TINYDNS:
414 			tinydns_dump_soa(soa);
415 			break;
416 	}
417 
418 	return (soa);
419 }
420 /*--- dump_soa() --------------------------------------------------------------------------------*/
421 
422 
423 /**************************************************************************************************
424 	DUMP_RR
425 **************************************************************************************************/
426 static void
dump_rr(MYDNS_SOA * soa,MYDNS_RR * rr,int maxlen)427 dump_rr(MYDNS_SOA *soa, MYDNS_RR *rr, int maxlen)
428 {
429 	switch (output_format)
430 	{
431 		case OUTPUT_BIND:
432 			bind_dump_rr(soa, rr, maxlen);
433 			break;
434 		case OUTPUT_TINYDNS:
435 			tinydns_dump_rr(soa, rr, maxlen);
436 			break;
437 	}
438 }
439 /*--- dump_rr() ---------------------------------------------------------------------------------*/
440 
441 
442 /**************************************************************************************************
443 	DUMP_RR_LONG
444 **************************************************************************************************/
445 static void
dump_rr_long(MYDNS_SOA * soa)446 dump_rr_long(MYDNS_SOA *soa)
447 {
448 	int maxlen = 0;
449 	char query[BUFSIZ];
450 	size_t querylen;
451 	SQL_RES *res;
452 	SQL_ROW row;
453 
454 	/* No records in zone - return immediately */
455 	if (!sql_count(sql, "SELECT COUNT(*) FROM %s WHERE zone=%u", mydns_rr_table_name, soa->id))
456 	{
457 		if (output_format == OUTPUT_BIND)
458 			puts("");
459 		return;
460 	}
461 
462 	if (output_format == OUTPUT_BIND)
463 		maxlen = sql_count(sql, "SELECT MAX(LENGTH(name)) FROM %s WHERE zone=%u", mydns_rr_table_name, soa->id) + 1;
464 
465 	if (!maxlen)
466 		maxlen = DNS_MAXNAMELEN;
467 
468 	querylen = snprintf(query, sizeof(query),
469 		"SELECT "MYDNS_RR_FIELDS" FROM %s WHERE zone=%u ORDER BY name,type,aux",
470 		mydns_rr_table_name, soa->id);
471 
472 	/* Submit query */
473 	if (!(res = sql_query(sql, query, querylen)))
474 		return;
475 
476 	/* Add results to list */
477 	while ((row = sql_getrow(res)))
478 	{
479 		MYDNS_RR *rr;
480 
481 		if (!(rr = mydns_rr_parse(row, soa->origin)))
482 			continue;
483 		dump_rr(soa, rr, maxlen);
484 		mydns_rr_free(rr);
485 	}
486    sql_free(res);
487 
488 	if (output_format == OUTPUT_BIND)
489 		puts("");
490 }
491 /*--- dump_rr_long() ----------------------------------------------------------------------------*/
492 
493 
494 /**************************************************************************************************
495 	DUMP_ZONE
496 **************************************************************************************************/
497 static void
dump_zone(char * zone_name)498 dump_zone(char *zone_name)
499 {
500 	MYDNS_SOA *soa;
501 
502 	strncpy(zone, zone_name, sizeof(zone)-2);
503 	if (LASTCHAR(zone) != '.')
504 		strcat(zone, ".");
505 	if ((soa = dump_soa()))
506 	{
507 		dump_rr_long(soa);
508 		mydns_soa_free(soa);
509 	}
510 	zones_out++;
511 }
512 /*--- dump_zone() -------------------------------------------------------------------------------*/
513 
514 
515 /**************************************************************************************************
516 	MAIN
517 **************************************************************************************************/
518 int
main(int argc,char ** argv)519 main(int argc, char **argv)
520 {
521 	setlocale(LC_ALL, "");										/* Internationalization */
522 	bindtextdomain(PACKAGE, LOCALEDIR);
523 	textdomain(PACKAGE);
524 	cmdline(argc, argv);
525 	load_config();
526 	db_connect();
527 
528 	dump_header();
529 	if (optind >= argc)
530 	{
531 		SQL_RES *res;
532 		SQL_ROW row;
533 		char query[256];
534 		size_t querylen;
535 
536 		querylen = snprintf(query, sizeof(query), "SELECT origin FROM %s", mydns_soa_table_name);
537 		if (!(res = sql_query(sql, query, querylen)))
538 			return (0);
539 
540 		while ((row = sql_getrow(res)))
541 			dump_zone(row[0]);
542 		sql_free(res);
543 	}
544 
545 	while (optind < argc)
546 		dump_zone((char *)argv[optind++]);
547 
548 	return (0);
549 }
550 /*--- main() ------------------------------------------------------------------------------------*/
551 
552 /* vi:set ts=3: */
553 /* NEED_PO */
554