1 /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2  * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Brian Bruns
3  * Copyright (C) 2006-2015  Frediano Ziglio
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 
21 #include <config.h>
22 
23 #include <freetds/time.h>
24 
25 #include <stdio.h>
26 #include <assert.h>
27 #include <ctype.h>
28 #if HAVE_FORK
29 #include <sys/wait.h>
30 #endif
31 #include <signal.h>
32 
33 #ifdef HAVE_READLINE
34 #include <readline/readline.h>
35 #include <readline/history.h>
36 #endif /* HAVE_READLINE */
37 
38 #if HAVE_ERRNO_H
39 #include <errno.h>
40 #endif /* HAVE_ERRNO_H */
41 
42 #if HAVE_STDLIB_H
43 #include <stdlib.h>
44 #endif /* HAVE_STDLIB_H */
45 
46 #if HAVE_STRING_H
47 #include <string.h>
48 #endif /* HAVE_STRING_H */
49 
50 #if HAVE_UNISTD_H
51 # include <unistd.h>
52 #elif defined(_WIN32)
53 # include <io.h>
54 # undef isatty
55 # define isatty(fd) _isatty(fd)
56 #endif /* HAVE_UNISTD_H */
57 
58 /* HP-UX require some constants defined by limits.h */
59 #ifdef HAVE_LIMITS_H
60 #include <limits.h>
61 #endif /* HAVE_LIMITS_H */
62 
63 #if defined(__hpux__) && !defined(_POSIX_PATH_MAX)
64 #define _POSIX_PATH_MAX 255
65 #endif
66 
67 #ifdef HAVE_LOCALE_H
68 #include <locale.h>
69 #endif /* HAVE_LOCALE_H */
70 
71 #ifdef HAVE_LANGINFO_H
72 #include <langinfo.h>
73 #endif /* HAVE_LANGINFO_H */
74 
75 #ifdef HAVE_LOCALCHARSET_H
76 #include <localcharset.h>
77 #endif /* HAVE_LOCALCHARSET_H */
78 
79 #include <freetds/tds.h>
80 #include <freetds/iconv.h>
81 #include <freetds/utils/string.h>
82 #include <freetds/convert.h>
83 #include <freetds/data.h>
84 #include <freetds/utils.h>
85 #include <freetds/replacements.h>
86 
87 #define TDS_ISSPACE(c) isspace((unsigned char) (c))
88 
89 enum
90 {
91 	OPT_VERSION =  0x01,
92 	OPT_TIMER =    0x02,
93 	OPT_NOFOOTER = 0x04,
94 	OPT_NOHEADER = 0x08,
95 	OPT_QUIET =    0x10,
96 	OPT_VERBOSE =  0x20,
97 	OPT_INSTANCES= 0x40
98 };
99 
100 static int istty = 0;
101 static int global_opt_flags = 0;
102 
103 #define QUIET (global_opt_flags & OPT_QUIET)
104 #define VERBOSE (global_opt_flags & OPT_VERBOSE)
105 
106 static const char *opt_col_term = "\t";
107 static const char *opt_row_term = "\n";
108 static const char *opt_default_db = NULL;
109 
110 static int do_query(TDSSOCKET * tds, char *buf, int opt_flags);
111 static int get_opt_flags(char *s, int *opt_flags);
112 static void populate_login(TDSLOGIN * login, int argc, char **argv);
113 static int tsql_handle_message(const TDSCONTEXT * context, TDSSOCKET * tds, TDSMESSAGE * msg);
114 static int tsql_handle_error  (const TDSCONTEXT * context, TDSSOCKET * tds, TDSMESSAGE * msg);
115 static void slurp_input_file(char *fname, char **mybuf, size_t *bufsz, size_t *buflen, int *line);
116 
117 static char *
tsql_readline(char * prompt)118 tsql_readline(char *prompt)
119 {
120 	size_t sz, pos;
121 	char *line, *p;
122 
123 #ifdef HAVE_READLINE
124 	if (istty)
125 		return readline(prompt);
126 #endif
127 
128 	sz = 1024;
129 	pos = 0;
130 	line = tds_new(char, sz);
131 	if (!line)
132 		return NULL;
133 
134 	if (prompt && prompt[0])
135 		printf("%s", prompt);
136 	for (;;) {
137 		/* read a piece */
138 		if (fgets(line + pos, (int)(sz - pos), stdin) == NULL) {
139 			if (pos)
140 				return line;
141 			break;
142 		}
143 
144 		/* got end-of-line ? */
145 		p = strchr(line + pos, '\n');
146 		if (p) {
147 			*p = 0;
148 			return line;
149 		}
150 
151 		/* allocate space if needed */
152 		pos += strlen(line + pos);
153 		if (pos + 1024 >= sz) {
154 			sz += 1024;
155 			if (!TDS_RESIZE(line, sz))
156 				break;
157 		}
158 	}
159 	free(line);
160 	return NULL;
161 }
162 
163 static void
tsql_add_history(const char * s)164 tsql_add_history(const char *s)
165 {
166 #ifdef HAVE_READLINE
167 	if (istty)
168 		add_history(s);
169 #endif
170 }
171 
172 /**
173  * Returns the version of the TDS protocol in effect for the link
174  * as a decimal integer.
175  *	Typical returned values are 42, 50, 70, 80.
176  * Also fills pversion_string unless it is null.
177  * 	Typical pversion_string values are "4.2" and "7.0".
178  */
179 static int
tds_version(TDSCONNECTION * conn,char * pversion_string)180 tds_version(TDSCONNECTION * conn, char *pversion_string)
181 {
182 	int iversion = 0;
183 
184 	iversion = 10 * TDS_MAJOR(conn) + TDS_MINOR(conn);
185 
186 	sprintf(pversion_string, "%d.%d", TDS_MAJOR(conn), TDS_MINOR(conn));
187 
188 	return iversion;
189 }
190 
191 static int
do_query(TDSSOCKET * tds,char * buf,int opt_flags)192 do_query(TDSSOCKET * tds, char *buf, int opt_flags)
193 {
194 	int rows = 0;
195 	TDSRET rc;
196 	int i;
197 	TDSCOLUMN *col;
198 	int ctype;
199 	CONV_RESULT dres;
200 	unsigned char *src;
201 	TDS_INT srclen;
202 	TDS_INT resulttype;
203 	struct timeval start, stop;
204 	int print_rows = 1;
205 	char message[128];
206 
207 	rc = tds_submit_query(tds, buf);
208 	if (TDS_FAILED(rc)) {
209 		fprintf(stderr, "tds_submit_query() failed\n");
210 		return 1;
211 	}
212 
213 	while ((rc = tds_process_tokens(tds, &resulttype, NULL, TDS_TOKEN_RESULTS)) == TDS_SUCCESS) {
214 		const int stop_mask = TDS_STOPAT_ROWFMT|TDS_RETURN_DONE|TDS_RETURN_ROW|TDS_RETURN_COMPUTE;
215 		if (opt_flags & OPT_TIMER) {
216 			gettimeofday(&start, NULL);
217 			print_rows = 0;
218 		}
219 		switch (resulttype) {
220 		case TDS_ROWFMT_RESULT:
221 			if ((!(opt_flags & OPT_NOHEADER)) && tds->current_results) {
222 				for (i = 0; i < tds->current_results->num_cols; i++) {
223 					if (i) fputs(opt_col_term, stdout);
224 					fputs(tds_dstr_cstr(&tds->current_results->columns[i]->column_name), stdout);
225 				}
226 				fputs(opt_row_term, stdout);
227 			}
228 			break;
229 		case TDS_COMPUTE_RESULT:
230 		case TDS_ROW_RESULT:
231 			rows = 0;
232 			while ((rc = tds_process_tokens(tds, &resulttype, NULL, stop_mask)) == TDS_SUCCESS) {
233 				if (resulttype != TDS_ROW_RESULT && resulttype != TDS_COMPUTE_RESULT)
234 					break;
235 
236 				rows++;
237 
238 				if (!tds->current_results)
239 					continue;
240 
241 				for (i = 0; i < tds->current_results->num_cols; i++) {
242 					col = tds->current_results->columns[i];
243 					if (col->column_cur_size < 0) {
244 						if (print_rows)  {
245 							if (i) fputs(opt_col_term, stdout);
246 							fputs("NULL", stdout);
247 						}
248 						continue;
249 					}
250 					ctype = tds_get_conversion_type(col->column_type, col->column_size);
251 
252 					src = col->column_data;
253 					if (is_blob_col(col) && col->column_type != SYBVARIANT)
254 						src = (unsigned char *) ((TDSBLOB *) src)->textvalue;
255 					srclen = col->column_cur_size;
256 
257 
258 					if (tds_convert(tds_get_ctx(tds), ctype, src, srclen, SYBVARCHAR, &dres) < 0)
259 						continue;
260 					if (print_rows)  {
261 						if (i) fputs(opt_col_term, stdout);
262 						fputs(dres.c, stdout);
263 					}
264 					free(dres.c);
265 				}
266 				if (print_rows)
267 					fputs(opt_row_term, stdout);
268 
269 			}
270 			if (!QUIET) printf("(%d row%s affected)\n", rows, rows == 1 ? "" : "s");
271 			break;
272 		case TDS_STATUS_RESULT:
273 			if (!QUIET)
274 				printf("(return status = %d)\n", tds->ret_status);
275 			break;
276 		default:
277 			break;
278 		}
279 
280 		if (opt_flags & OPT_VERSION) {
281 			char version[64];
282 			int line = 0;
283 
284 			line = tds_version(tds->conn, version);
285 			if (line) {
286 				TDSMESSAGE msg;
287 				memset(&msg, 0, sizeof(TDSMESSAGE));
288 				msg.server = "tsql";
289 				sprintf(message, "using TDS version %s", version);
290 				msg.message = message;
291 				tsql_handle_message(tds_get_ctx(tds), tds, &msg);
292 			}
293 		}
294 		if (opt_flags & OPT_TIMER) {
295 			TDSMESSAGE msg;
296 			gettimeofday(&stop, NULL);
297 			sprintf(message, "Total time for processing %d rows: %ld msecs\n",
298 				rows, (long) ((stop.tv_sec - start.tv_sec) * 1000) + ((stop.tv_usec - start.tv_usec) / 1000));
299 
300 			memset(&msg, 0, sizeof(TDSMESSAGE));
301 			msg.server = "tsql";
302 			msg.message = message;
303 			tsql_handle_message(tds_get_ctx(tds), tds, &msg);
304 		}
305 	}
306 	return 0;
307 }
308 
309 static void
tsql_print_usage(const char * progname)310 tsql_print_usage(const char *progname)
311 {
312 	fprintf(stderr,
313 		"Usage: %s [-a <appname>] [-S <server> | -H <hostname> -p <port>] -U <username> [-P <password>] [-I <config file>] [-o <options>] [-t delim] [-r delim] [-D database]\n"
314 		"  or:  %s -C\n"
315 		"  or:  %s -L -H <hostname>\n"
316 		"If -C is specified just print configuration and exit.\n"
317 		"If -L is specified with a host name (-H) instances found are printed.\n"
318 		"  -a  specify application name\n"
319 		"  -S  specify server entry in freetds.conf to connect\n"
320 		"  -H  specify hostname to connect\n"
321 		"  -p  specify port to connect to\n"
322 		"  -U  specify username to use\n"
323 		"  -P  specify password to use\n"
324 		"  -D  specify database name to use\n"
325 		"  -I  specify old configuration file (called interface) to use\n"
326 		"  -J  specify character set to use\n"
327 		"  -v  verbose mode\n"
328 		"-o options:\n"
329 		"\tf\tDo not print footer\n"
330 		"\th\tDo not print header\n"
331 		"\tt\tPrint time informations\n"
332 		"\tv\tPrint TDS version\n"
333 		"\tq\tQuiet\n\n"
334 		"\tDelimiters can be multi-char strings appropriately escaped for your shell.\n"
335 		"\tDefault column delimitor is <tab>; default row delimiter is <newline>\n",
336 		progname, progname, progname);
337 }
338 
339 static void
reset_getopt(void)340 reset_getopt(void)
341 {
342 #ifdef HAVE_GETOPT_OPTRESET
343 	optreset = 1;
344 	optind = 1;
345 #else
346 	optind = 0;
347 #endif
348 }
349 
350 /*
351  * The 'GO' command may be followed by options that apply to the batch.
352  * If they don't appear to be right, assume the letters "go" are part of the
353  * SQL, not a batch separator.
354  */
355 static int
get_opt_flags(char * s,int * opt_flags)356 get_opt_flags(char *s, int *opt_flags)
357 {
358 	char **argv;
359 	int argc;
360 	int opt;
361 
362 	/* make sure we have enough elements */
363 	assert(s && opt_flags);
364 	argv = tds_new0(char*, strlen(s) + 2);
365 	if (!argv)
366 		return 0;
367 
368 	/* parse the command line and assign to argv */
369 	for (argc=0; (argv[argc] = strtok(s, " ")) != NULL; argc++)
370 		s = NULL;
371 
372 	*opt_flags = 0;
373 	reset_getopt();
374 	opterr = 0;		/* suppress error messages */
375 	while ((opt = getopt(argc, argv, "fhLqtv")) != -1) {
376 		switch (opt) {
377 		case 'f':
378 			*opt_flags |= OPT_NOFOOTER;
379 			break;
380 		case 'h':
381 			*opt_flags |= OPT_NOHEADER;
382 			break;
383 		case 't':
384 			*opt_flags |= OPT_TIMER;
385 			break;
386 		case 'v':
387 			*opt_flags |= OPT_VERSION;
388 			break;
389 		case 'q':
390 			*opt_flags |= OPT_QUIET;
391 			break;
392 		default:
393 			fprintf(stderr, "Warning: invalid option '%s' found: \"go\" treated as simple SQL\n", argv[optind-1]);
394 			free(argv);
395 			return 0;
396 		}
397 	}
398 
399 	free(argv);
400 	return 1;
401 }
402 
403 static int
get_default_instance_port(const char hostname[])404 get_default_instance_port(const char hostname[])
405 {
406 	int port;
407 	struct addrinfo *addr;
408 
409 	if ((addr = tds_lookup_host(hostname)) == NULL)
410 		return 0;
411 
412 	port = tds7_get_instance_port(addr, "MSSQLSERVER");
413 
414 	freeaddrinfo(addr);
415 
416 	return port;
417 }
418 
419 #if !defined(LC_ALL)
420 # define LC_ALL 0
421 #endif
422 
423 static const char *
yes_no(bool value)424 yes_no(bool value)
425 {
426 	return value ? "yes" : "no";
427 }
428 
429 static void
populate_login(TDSLOGIN * login,int argc,char ** argv)430 populate_login(TDSLOGIN * login, int argc, char **argv)
431 {
432 	const TDS_COMPILETIME_SETTINGS *settings;
433 	char *hostname = NULL, *servername = NULL;
434 	char *username = NULL, *password = NULL;
435 	char *confile = NULL;
436 	const char *appname = "TSQL";
437 	int opt, port=0, use_domain_login=0;
438 	char *charset = NULL;
439 	char *opt_flags_str = NULL;
440 
441 	while ((opt = getopt(argc, argv, "a:H:S:I:J:P:U:p:Co:t:r:D:Lv")) != -1) {
442 		switch (opt) {
443 		case 'a':
444 			appname = optarg;
445 			break;
446 		case 't':
447 			opt_col_term = optarg;
448 			break;
449 		case 'r':
450 			opt_row_term = optarg;
451 			break;
452 		case 'D':
453 			opt_default_db = optarg;
454 			break;
455 		case 'o':
456 			opt_flags_str = optarg;
457 			break;
458 		case 'H':
459 			free(hostname);
460 			hostname = strdup(optarg);
461 			break;
462 		case 'S':
463 			free(servername);
464 			servername = strdup(optarg);
465 			break;
466 		case 'U':
467 			free(username);
468 			username = strdup(optarg);
469 			break;
470 		case 'P':
471 			free(password);
472 			password = tds_getpassarg(optarg);
473 			break;
474 		case 'I':
475 			free(confile);
476 			confile = strdup(optarg);
477 			break;
478 		case 'J':
479 			free(charset);
480 			charset = strdup(optarg);
481 			break;
482 		case 'p':
483 			port = atoi(optarg);
484 			break;
485 		case 'L':
486 			global_opt_flags |= OPT_INSTANCES;
487 			break;
488 		case 'v':
489 			global_opt_flags |= OPT_VERBOSE;
490 			break;
491 		case 'C':
492 			settings = tds_get_compiletime_settings();
493 			printf("%s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n%35s: %s\n",
494 			       "Compile-time settings (established with the \"configure\" script)",
495 			       "Version", settings->freetds_version,
496 			       "freetds.conf directory", settings->sysconfdir,
497 			       /* settings->last_update */
498 			       "MS db-lib source compatibility", yes_no(settings->msdblib),
499 			       "Sybase binary compatibility", yes_no(settings->sybase_compat),
500 			       "Thread safety", yes_no(settings->threadsafe),
501 			       "iconv library", yes_no(settings->libiconv),
502 			       "TDS version", settings->tdsver,
503 			       "iODBC", yes_no(settings->iodbc),
504 			       "unixodbc", yes_no(settings->unixodbc),
505 			       "SSPI \"trusted\" logins", yes_no(settings->sspi),
506 			       "Kerberos", yes_no(settings->kerberos),
507 			       "OpenSSL", yes_no(settings->openssl),
508 			       "GnuTLS", yes_no(settings->gnutls),
509 			       "MARS", yes_no(settings->mars));
510 			exit(0);
511 			break;
512 		default:
513 			tsql_print_usage(basename(argv[0]));
514 			exit(1);
515 			break;
516 		}
517 	}
518 
519 	if (opt_flags_str != NULL) {
520 		char *minus_flags = tds_new(char, strlen(opt_flags_str) + 5);
521 		if (minus_flags != NULL) {
522 			strcpy(minus_flags, "go -");
523 			strcat(minus_flags, opt_flags_str);
524 			get_opt_flags(minus_flags, &global_opt_flags);
525 			free(minus_flags);
526 		}
527 	}
528 
529 	if ((global_opt_flags & OPT_INSTANCES) && hostname) {
530 		struct addrinfo *addr;
531 		char *filename = getenv("TDSDUMP");
532 
533 		if (filename) {
534 			if (asprintf(&filename, "%s.instances", filename) < 0)
535 				exit(1);
536 			tdsdump_open(filename);
537 			free(filename);
538 		}
539 		if ((addr = tds_lookup_host(hostname)) != NULL) {
540 			tds7_get_instance_ports(stderr, addr);
541 			freeaddrinfo(addr);
542 		}
543 		tdsdump_close();
544 		exit(0);
545 	}
546 
547 	/* validate parameters */
548 	if (!servername && !hostname) {
549 		fprintf(stderr, "%s: error: Missing argument -S or -H\n", argv[0]);
550 		exit(1);
551 	}
552 	if (hostname && !port) {
553 		/*
554 		 * TODO: It would be convenient to have a function that looks up a reasonable port based on:
555 		 * 	- TDSPORT environment variable
556 		 * 	- config files
557 		 * 	- get_default_instance_port
558 		 *	- TDS version
559 		 *	in that order.
560 		 */
561 		if (!QUIET) {
562 			printf("Missing argument -p, looking for default instance ... ");
563 		}
564 		if (0 == (port = get_default_instance_port(hostname))) {
565 			fprintf(stderr, "%s: no default port provided by host %s\n", argv[0], hostname);
566 			exit(1);
567 		}
568 		if (!QUIET)
569 			printf("found default instance, port %d\n", port);
570 
571 	}
572 	/* A NULL username indicates a domain (trusted) login */
573 	if (!username) {
574 		username = tds_new0(char, 1);
575 		if (!username) {
576 			fprintf(stderr, "Could not allocate memory for username\n");
577 			exit(1);
578 		}
579 		use_domain_login = 1;
580 	}
581 	if (!password) {
582 		password = tds_new0(char, 128);
583 		if (!password) {
584 			fprintf(stderr, "Could not allocate memory for password\n");
585 			exit(1);
586 		}
587 		if (!use_domain_login)
588 			readpassphrase("Password: ", password, 128, RPP_ECHO_OFF);
589 	}
590 	if (!opt_col_term) {
591 		fprintf(stderr, "%s: missing delimiter for -t (check escaping)\n", argv[0]);
592 		exit(1);
593 	}
594 	if (!opt_row_term) {
595 		fprintf(stderr, "%s: missing delimiter for -r (check escaping)\n", argv[0]);
596 		exit(1);
597 	}
598 
599 	/* all validated, let's do it */
600 	if (!tds_set_user(login, username)
601 	    || !tds_set_app(login, appname)
602 	    || !tds_set_library(login, "TDS-Library")
603 	    || !tds_set_language(login, "us_english")
604 	    || !tds_set_passwd(login, password))
605 		goto out_of_memory;
606 	if (charset && !tds_set_client_charset(login, charset))
607 		goto out_of_memory;
608 
609 	/* if it's a servername */
610 	if (servername) {
611 		if (!tds_set_server(login, servername))
612 			goto out_of_memory;
613 		if (confile) {
614 			tds_set_interfaces_file_loc(confile);
615 		}
616 		/* else we specified hostname/port */
617 	} else {
618 		if (!tds_set_server(login, hostname))
619 			goto out_of_memory;
620 		tds_set_port(login, port);
621 	}
622 
623 	memset(password, 0, strlen(password));
624 
625 	/* free up all the memory */
626 	free(confile);
627 	free(hostname);
628 	free(username);
629 	free(password);
630 	free(servername);
631 	free(charset);
632 	return;
633 
634 out_of_memory:
635 	fprintf(stderr, "%s: out of memory\n", argv[0]);
636 	exit(1);
637 }
638 
639 static int
tsql_handle_message(const TDSCONTEXT * context,TDSSOCKET * tds,TDSMESSAGE * msg)640 tsql_handle_message(const TDSCONTEXT * context, TDSSOCKET * tds, TDSMESSAGE * msg)
641 {
642 	if (msg->msgno == 0) {
643 		fprintf(stderr, "%s\n", msg->message);
644 		return 0;
645 	}
646 
647 	switch (msg->msgno) {
648 	case 5701: 	/* changed_database */
649 	case 5703: 	/* changed_language */
650 	case 20018:	/* The @optional_command_line is too long */
651 		if (VERBOSE)
652 			fprintf(stderr, "%s\n", msg->message);
653 		break;
654 	default:
655 		fprintf(stderr, "Msg %d (severity %d, state %d) from %s",
656 			msg->msgno, msg->severity, msg->state, msg->server);
657 		if (msg->proc_name && strlen(msg->proc_name))
658 			fprintf(stderr, ", Procedure %s", msg->proc_name);
659 		if (msg->line_number > 0)
660 			fprintf(stderr, " Line %d", msg->line_number);
661 		fprintf(stderr, ":\n\t\"%s\"\n", msg->message);
662 		break;
663 	}
664 
665 	return 0;
666 }
667 
668 static int	/* error from library, not message from server */
tsql_handle_error(const TDSCONTEXT * context,TDSSOCKET * tds,TDSMESSAGE * msg)669 tsql_handle_error(const TDSCONTEXT * context, TDSSOCKET * tds, TDSMESSAGE * msg)
670 {
671 	fprintf(stderr, "Error %d (severity %d):\n\t%s\n", msg->msgno, msg->severity, msg->message);
672 	if (0 != msg->oserr) {
673 		fprintf(stderr, "\tOS error %d, \"%s\"\n", msg->oserr, strerror(msg->oserr));
674 	}
675 	return TDS_INT_CANCEL;
676 }
677 
678 static void
slurp_input_file(char * fname,char ** mybuf,size_t * bufsz,size_t * buflen,int * line)679 slurp_input_file(char *fname, char **mybuf, size_t *bufsz, size_t *buflen, int *line)
680 {
681 	FILE *fp = NULL;
682 	register char *n;
683 	char linebuf[1024];
684 	char *s = NULL;
685 
686 	if ((fp = fopen(fname, "r")) == NULL) {
687 		fprintf(stderr, "Unable to open input file '%s': %s\n", fname, strerror(errno));
688 		return;
689 	}
690 	while ((s = fgets(linebuf, sizeof(linebuf), fp)) != NULL) {
691 		while (*buflen + strlen(s) + 2 > *bufsz) {
692 			*bufsz *= 2;
693 			if (!TDS_RESIZE(*mybuf, *bufsz)) {
694 				perror("tsql: ");
695 				exit(1);
696 			}
697 		}
698 		strcpy(*mybuf + *buflen, s);
699 		*buflen += strlen(*mybuf + *buflen);
700 		n = strrchr(s, '\n');
701 		if (n != NULL)
702 			*n = '\0';
703 		tsql_add_history(s);
704 		(*line)++;
705 	}
706 	fclose(fp);
707 }
708 
709 static void
print_instance_data(TDSLOGIN * login)710 print_instance_data(TDSLOGIN *login)
711 {
712 	if (!login)
713 		return;
714 
715 	if (!tds_dstr_isempty(&login->instance_name))
716 		printf("connecting to instance %s on port %d\n", tds_dstr_cstr(&login->instance_name), login->port);
717 }
718 
719 #if defined(HAVE_ALARM) && !defined(_WIN32)
720 static void
count_alarm(int s)721 count_alarm(int s)
722 {
723 	static int count = 0;
724 	char buf[64];
725 
726 	/* print the counter, do not use stderr as may be locked! */
727 	sprintf(buf, "\r%2d", ++count);
728 	write(STDERR_FILENO, buf, strlen(buf));
729 
730 	alarm(1);
731 }
732 #endif
733 
734 int
main(int argc,char ** argv)735 main(int argc, char **argv)
736 {
737 	char *s = NULL, *s2 = NULL, *cmd = NULL;
738 	char prompt[20];
739 	int line = 0;
740 	char *mybuf;
741 	size_t bufsz = 4096;
742 	size_t buflen = 0;
743 	TDSSOCKET *tds;
744 	TDSLOGIN *login;
745 	TDSCONTEXT *context;
746 	TDSLOGIN *connection;
747 	int opt_flags = 0;
748 	const char *locale = NULL;
749 	const char *charset = NULL;
750 
751 	istty = isatty(0);
752 
753 	if (INITSOCKET()) {
754 		fprintf(stderr, "Unable to initialize sockets\n");
755 		return 1;
756 	}
757 
758 	setlocale(LC_ALL, "");
759 
760 	/* grab a login structure */
761 	login = tds_alloc_login(1);
762 	if (!login) {
763 		fprintf(stderr, "login cannot be null\n");
764 		return 1;
765 	}
766 
767 	context = tds_alloc_context(NULL);
768 	if (!context) {
769 		fprintf(stderr, "context cannot be null\n");
770 		return 1;
771 	}
772 	if (context->locale && !context->locale->date_fmt) {
773 		/* set default in case there's no locale file */
774 		context->locale->date_fmt = strdup(STD_DATETIME_FMT);
775 	}
776 
777 	context->msg_handler = tsql_handle_message;
778 	context->err_handler = tsql_handle_error;
779 
780 	/* process all the command line args into the login structure */
781 	populate_login(login, argc, argv);
782 
783 	/* Try to open a connection */
784 	tds = tds_alloc_socket(context, 512);
785 	assert(tds);
786 	tds_set_parent(tds, NULL);
787 	connection = tds_read_config_info(tds, login, context->locale);
788 	if (!connection)
789 		return 1;
790 
791 	locale = setlocale(LC_ALL, NULL);
792 
793 #if HAVE_LOCALE_CHARSET
794 	charset = locale_charset();
795 #endif
796 #if HAVE_NL_LANGINFO && defined(CODESET)
797 	if (!charset)
798 		charset = nl_langinfo(CODESET);
799 #endif
800 
801 	if (locale)
802 		if (!QUIET) printf("locale is \"%s\"\n", locale);
803 	if (charset) {
804 		if (!QUIET) printf("locale charset is \"%s\"\n", charset);
805 	}
806 
807 	if (tds_dstr_isempty(&connection->client_charset)) {
808 		if (!charset)
809 			charset = "ISO-8859-1";
810 
811 		if (!tds_set_client_charset(login, charset))
812 			return 1;
813 		if (!tds_dstr_dup(&connection->client_charset, &login->client_charset))
814 			return 1;
815 	}
816 	if (!QUIET) printf("using default charset \"%s\"\n", tds_dstr_cstr(&connection->client_charset));
817 
818 	if (opt_default_db) {
819 		if (!tds_dstr_copy(&connection->database, opt_default_db))
820 			return 1;
821 		if (!QUIET) fprintf(stderr, "Setting %s as default database in login packet\n", opt_default_db);
822 	}
823 
824 	/*
825 	 * If we're able to establish an ip address for the server, we'll try to connect to it.
826 	 * If that machine is currently unreachable, show a timer connecting to the server.
827 	 */
828 #if defined(HAVE_ALARM) && !defined(_WIN32)
829 	if (connection && !QUIET) {
830 		signal(SIGALRM, count_alarm);
831 		fflush(stderr);
832 		alarm(1);
833 	}
834 #endif
835 	if (!connection || TDS_FAILED(tds_connect_and_login(tds, connection))) {
836 		if (VERBOSE)
837 			print_instance_data(connection);
838 		tds_free_socket(tds);
839 		tds_free_login(login);
840 		tds_free_context(context);
841 		fprintf(stderr, "There was a problem connecting to the server\n");
842 		exit(1);
843 	}
844 
845 #if defined(HAVE_ALARM) && !defined(_WIN32)
846 	if (!QUIET) {
847 		alarm(0);
848 		signal(SIGALRM, SIG_DFL);
849 		fprintf(stderr, "\r");
850 	}
851 #endif
852 
853 	if (VERBOSE)
854 		print_instance_data(connection);
855 	tds_free_login(connection);
856 	/* give the buffer an initial size */
857 	bufsz = 4096;
858 	mybuf = tds_new(char, bufsz);
859 	if (!mybuf) {
860 		fprintf(stderr, "Could not allocate memory for mybuf\n");
861 		return 1;
862 	}
863 	mybuf[0] = '\0';
864 	buflen = 0;
865 
866 #if defined(HAVE_READLINE) && HAVE_RL_INHIBIT_COMPLETION
867 	rl_inhibit_completion = 1;
868 #endif
869 
870 	for (s=NULL, s2=NULL; ; free(s), free(s2), s2=NULL) {
871 		sprintf(prompt, "%d> ", ++line);
872 		s = tsql_readline(QUIET ? NULL : prompt);
873 		if (s == NULL) {
874 			if (buflen)
875 				do_query(tds, mybuf, global_opt_flags);
876 			break;
877 		}
878 
879 		/*
880 		 * 'GO' is special only at the start of a line
881 		 *  The rest of the line may include options that apply to the batch,
882 		 *  and perhaps whitespace.
883 		 */
884 		if (0 == strncasecmp(s, "go", 2) && (strlen(s) == 2 || TDS_ISSPACE(s[2]))) {
885 			char *go_line = strdup(s);
886 			assert(go_line);
887 			line = 0;
888 			if (get_opt_flags(go_line, &opt_flags)) {
889 				free(go_line);
890 				opt_flags ^= global_opt_flags;
891 				do_query(tds, mybuf, opt_flags);
892 				mybuf[0] = '\0';
893 				buflen = 0;
894 				continue;
895 			}
896 			free(go_line);
897 		}
898 
899 		/* skip leading whitespace */
900 		if ((s2 = strdup(s)) == NULL) {	/* copy to mangle with strtok() */
901 			perror("tsql: ");
902 			exit(1);
903 		}
904 
905 		if ((cmd = strtok(s2, " \t")) == NULL)
906 			cmd = "";
907 
908 		if (!strcasecmp(cmd, "exit") || !strcasecmp(cmd, "quit") || !strcasecmp(cmd, "bye"))
909 			break;
910 		if (!strcasecmp(cmd, "version")) {
911 			tds_version(tds->conn, mybuf);
912 			printf("using TDS version %s\n", mybuf);
913 			line = 0;
914 			mybuf[0] = '\0';
915 			buflen = 0;
916 			continue;
917 		}
918 		if (!strcasecmp(cmd, "reset")) {
919 			line = 0;
920 			mybuf[0] = '\0';
921 			buflen = 0;
922 		} else if (!strcasecmp(cmd, ":r")) {
923 			slurp_input_file(strtok(NULL, " \t"), &mybuf, &bufsz, &buflen, &line);
924 		} else {
925 			while (buflen + strlen(s) + 2 > bufsz) {
926 				bufsz *= 2;
927 				if (!TDS_RESIZE(mybuf, bufsz)) {
928 					perror("tsql: ");
929 					exit(1);
930 				}
931 			}
932 			tsql_add_history(s);
933 			strcpy(mybuf + buflen, s);
934 			/* preserve line numbering for the parser */
935 			strcat(mybuf + buflen, "\n");
936 			buflen += strlen(mybuf + buflen);
937 		}
938 	}
939 
940 	/* close up shop */
941 	free(mybuf);
942 	tds_close_socket(tds);
943 	tds_free_socket(tds);
944 	tds_free_login(login);
945 	tds_free_context(context);
946 	DONESOCKET();
947 
948 	return 0;
949 }
950