1 /*
2  * sqsh_main.c - Primary entry point into sqsh
3  *
4  * Copyright (C) 1995, 1996 by Scott C. Gray
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, 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  * You may contact the author :
21  *   e-mail:  gray@voicenet.com
22  *            grays@xtend-tech.com
23  *            gray@xenotropic.com
24  */
25 #include <stdio.h>
26 #include <sys/ioctl.h>
27 #include <termios.h>
28 #include <pwd.h>
29 #include "sqsh_config.h"
30 #include "sqsh_job.h"
31 #include "sqsh_error.h"
32 #include "sqsh_global.h"
33 #include "sqsh_getopt.h"
34 #include "sqsh_init.h"
35 #include "sqsh_fd.h"
36 #include "sqsh_readline.h"
37 #include "sqsh_expand.h"
38 #include "sqsh_alias.h"
39 #include "sqsh_sig.h"
40 #include "sqsh_stdin.h"
41 #include "cmd.h"
42 
43 /*-- Current Version --*/
44 #if !defined(lint) && !defined(__LINT__)
45 static char RCS_Id[] = "$Id: sqsh_main.c,v 1.23 2013/04/25 14:09:47 mwesdorp Exp $";
46 USE(RCS_Id)
47 #endif /* !defined(lint) */
48 
49 /*-- Prototypes --*/
50 static void print_usage      _ANSI_ARGS(( void ));
51 
52 #define SQSH_HIDEPWD
53 
54 #if defined(SQSH_HIDEPWD)
55 #define MAXPWD 256
56 static void hide_password _ANSI_ARGS(( int argc, char *argv[]));
57 #endif
58 
59 
60 #if defined(TIOCGWINSZ) && defined(SIGWINCH)
61 static void sigwinch_handler _ANSI_ARGS(( int, void* ));
62 #endif
63 
64 /*
65  * The following structure is only used by print_usage() to describe
66  * the set of valid flags, a the arguments for that flag, and a
67  * description of that it does.
68  */
69 typedef struct sqsh_flag_st {
70     char *flag;
71     char *arg;
72     char *description;
73 } sqsh_flag_t;
74 
75 /*
76  * Array of information about the flags accepted by sqsh. This
77  * is maintained in this manner so that I can have print_usage()
78  * to the format for me...I got tired of re-doing it every time
79  * I added a new flag.
80  * sqsh-2.1.6 - New parameters added and list neatly ordered.
81  */
82 static sqsh_flag_t sg_flags[] = {
83 /* Flag    Argument           Description
84    -----   ---------          ----------------------------------- */
85     { "-a", "count",          "Max. # of errors before abort"      },
86     { "-A", "packet_size",    "Adjust TDS packet size"             },
87     { "-b", "",               "Suppress banner message on startup" },
88     { "-B", "",               "Turn off file buffering on startup" },
89     { "-c", "[cmdend]",       "Alias for the 'go' command"         },
90     { "-C", "sql",            "Send sql statement to server"       },
91     { "-d", "severity",       "Min. severity level to display"     },
92     { "-D", "database",       "Change database context on startup" },
93     { "-e", "",               "Echo batch prior to executing"      },
94     { "-E", "editor",         "Replace default editor (vi)"        },
95     { "-f", "severity",       "Min. severity level for failure"    },
96     { "-G", "TDS version",    "TDS version to use"                 },
97     { "-h", "",               "Disable headers and footers"        },
98     { "-H", "hostname",       "Set the client hostname"            },
99     { "-i", "filename",       "Read input from file"               },
100     { "-I", "interfaces",     "Alternate interfaces file"          },
101     { "-J", "charset",        "Client character set"               },
102     { "-k", "keywords",       "Specify alternate keywords file"    },
103     { "-K", "keytab",         "Network security keytab file (DCE)" },
104     { "-l", "level|flags",    "Set debugging level"                },
105     { "-L", "var=value",      "Set the value of a given variable"  },
106     { "-m", "style",          "Set display mode"                   },
107     { "-n", "{on|off}",       "Set chained transaction mode"       },
108     { "-N", "appname",        "Set Application Name (sqsh)"        },
109     { "-o", "filename",       "Direct all output to file"          },
110     { "-p", "",               "Display performance stats"          },
111     { "-P", "[password]",     "Sybase password (NULL)"             },
112     { "-Q", "query_timeout",  "Query timeout period in seconds"    },
113     { "-r", "[sqshrc]",       "Specify name of .sqshrc"            },
114     { "-R", "principal",      "Network security server principal"  },
115     { "-s", "colsep",         "Alternate column separator (\\t)"   },
116     { "-S", "server",         "Name of Sybase server ($DSQUERY)"   },
117     { "-t", "[filter]",       "Filter batches through program"     },
118     { "-T", "login_timeout",  "Login timeout period in seconds"    },
119     { "-U", "username",       "Name of Sybase user"                },
120     { "-v", "",               "Display current version and exit"   },
121     { "-V", "[bcdimoqru]",    "Request network security services"  },
122     { "-w", "width",          "Adjust result display width"        },
123     { "-X", "",               "Enable client password encryption"  },
124     { "-y", "directory",      "Override value of $SYBASE"          },
125     { "-z", "language",       "Alternate display language"         },
126     { "-Z", "[secmech]",      "Network security mechanism"         },
127 };
128 
129 int
main(argc,argv)130 main( argc, argv )
131     int    argc;
132     char  *argv[];
133 {
134     int           ret;
135     int           exit_status;
136 
137     /*-- Variables required by sqsh_getopt() --*/
138     extern  int    sqsh_optind;
139     extern  char  *sqsh_optarg;
140     char          *username = NULL;
141     char          *rcfile;
142     char          *history;
143     char          *banner;
144     char          *thresh_exit;
145     char          *batch_failcount;
146     char          *exit_failcount;
147     char          *exit_value;
148     char          *term_title;
149     char           str[512];
150     int            ch;
151     uid_t          uid;
152     struct passwd *pwd;
153     int            fd;
154     int            stdout_tty;  /* True if stdout is a tty */
155     int            stdin_tty;   /* True if stdin is a tty */
156     int            show_banner = True;
157     int            set_width   = False;
158     int            read_file   = False;  /* True if -i supplied */
159     char          *sql = NULL;
160     char          *cptr;
161     int            i;
162     varbuf_t      *exp_buf;
163 
164     /*
165      * sqsh-2.2.0 - Variables used by password handling option
166      * moved here.
167      */
168     char           buf[MAXPWD];
169     char          *p;
170     int            fdin, fdout;
171 
172 
173     /*
174      * If termios.h defines TIOCGWINSZ, then we need to declare a
175      * structure in which to retrieve the current window size.
176      */
177 #if defined(TIOCGWINSZ)
178     struct winsize  ws;
179 #endif
180 
181 #if defined(SQSH_HIDEPWD)
182     /*
183      * If the password is passed in with the -P option then we do a little
184      * dance to hide it (i.e. we open a pipe, write the password to the pipe
185      * re-exec the program and read the pipe there.)
186      */
187     hide_password(argc, argv);
188 #endif
189 
190     /*
191      * Start us our reading from regular old stdin.
192      */
193     sqsh_stdin_file( stdin );
194 
195 
196     /*
197      * The first thing we need to do is intialize all commands, variables,
198      * etc prior to parsing the command line.  This is important because
199      * the command line flags simply alter these variables.
200      */
201     if( sqsh_init() == False ) {
202         fprintf( stderr, "%s\n", sqsh_get_errstr() );
203         sqsh_exit(255);
204     }
205 
206     /*
207      * If the first argument on the command line is -r, then we want
208      * to process it before we attempt to read the .sqshrc file (since
209      * -r allows the user to specify another .sqshrc file.
210      */
211     if ( argc > 1 && strcmp( argv[1], "-r" ) == 0)
212     {
213         if (argc > 2 && *argv[2] != '-')
214         {
215             if (access( argv[2], R_OK ) == -1)
216             {
217                 fprintf( stderr, "sqsh: Unable to open %s: %s\n", argv[2],
218                          strerror(errno) );
219                 sqsh_exit(255);
220             }
221             rcfile = argv[2];
222             env_set( g_env, "rcfile", argv[2] );
223         }
224         else
225         {
226             rcfile = NULL;
227             /* sqsh-2.1.9 - Set rcfile environment variable also to NULL */
228             env_set( g_env, "rcfile", rcfile );
229         }
230     }
231     else
232     {
233         /*
234          * We now want to read the users resource file prior to dealing
235          * with any command line options, this way we can override any
236          * value defined in the rc file with values on the command line.
237          */
238         env_get( g_env, "SQSHRC", &rcfile );
239         if (rcfile != NULL && *rcfile != '\0')
240         {
241             env_set( g_env, "rcfile", rcfile );
242         }
243         else
244         {
245             env_get( g_env, "rcfile", &rcfile );
246         }
247     }
248 
249     /*
250      * Here is a nifty trick; in order to read the rc file we run the
251      * \loop command, redirecting $rcfile to its standard in, asking
252      * for it to *not* connect to the database using the -n flag.
253      */
254     if (rcfile != NULL && *rcfile != '\0')
255     {
256         /*
257          * Since we need to chop up the contents of rcfile destructively
258          * we make a backup copy of it.
259          */
260         strcpy(str, rcfile);
261         cptr = strtok( str, ":\n\t\r" );
262 
263         /*
264          * Create a temporary buffer for expansion.
265          */
266         exp_buf = varbuf_create( 512 );
267         while (cptr != NULL)
268         {
269             if (sqsh_expand( cptr, exp_buf, 0 ) == False)
270             {
271                 fprintf( stderr, "sqsh: Error expanding $rcfile: %s\n",
272                     sqsh_get_errstr() );
273                 sqsh_exit(255);
274             }
275 
276             cptr = varbuf_getstr(exp_buf);
277             if (access( cptr, R_OK ) != -1)
278             {
279                 env_set( g_env, "cur_rcfile", cptr );
280                 if((jobset_run( g_jobset, "\\loop -n $cur_rcfile", &exit_status))
281                     == -1 )
282                 {
283                     fprintf( stderr, "\\loop: %s\n", sqsh_get_errstr() );
284                     sqsh_exit(255);
285                 }
286 
287                 if( exit_status == CMD_FAIL )
288                 {
289                     fprintf( stderr, "sqsh: Error in %s\n", cptr );
290                     sqsh_exit(255);
291                 }
292             }
293 
294             cptr = strtok( NULL, ":\n\t\r" );
295         }
296         /* sqsh-2.1.9 - Remove temporary environment variable cur_rcfile */
297         env_remove( g_env, "cur_rcfile", 0);
298         varbuf_destroy(exp_buf);
299     }
300 
301     /*
302      * Parse the command line options.  Note, that setting most of
303      * these variables has side effects.  For example, setting
304      * $script automatically sets $interactive to 0 and redirects
305      * stdin from the script file.
306      * sqsh-2.1.6 - New parameters added to the list and cases neatly ordered
307      */
308     while ((ch = sqsh_getopt_combined( "SQSH", argc, argv,
309         "a:A:bBc;C:d:D:eE:f:G:hH:i:I:J:k:K:l:L:m:n:N:o:pP;Q:r;R:s:S:t;T:U:vV;w:Xy:z:Z;\250:" )) != EOF)
310     {
311         ret = 0;
312         switch (ch)
313         {
314             case 'a' :
315                 ret = env_set( g_env, "thresh_exit", sqsh_optarg );
316                 break;
317             case 'A' :
318                 ret = env_set( g_env, "packet_size", sqsh_optarg );
319                 break;
320             case 'b' :
321                 ret = env_set( g_env, "banner", "0" );
322                 break;
323             case 'B' :
324                 setbuf( stdout, NULL );
325                 setbuf( stderr, NULL );
326                 setbuf( stdin, NULL );
327                 ret = True;
328                 break;
329             case 'c' :
330                 /*-- Cheesy newline-go hack. Suck! --*/
331                 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
332                 {
333                     ret = env_set( g_env, "newline_go", "1" );
334                 }
335                 else if (alias_add( g_alias, sqsh_optarg, "\\go" ) <= 0)
336                 {
337                     fprintf( stderr, "sqsh: -c: %s\n", sqsh_get_errstr() );
338                     sqsh_exit(255);
339                 }
340                 ret = True;
341                 break;
342             case 'C' :
343                 ret = True;
344                 sql = sqsh_optarg;
345                 env_set( g_env, "sql", sqsh_optarg );
346                 break;
347             case 'd' :
348                 ret = env_set( g_env, "thresh_display", sqsh_optarg );
349                 break;
350             case 'D' :
351                 ret = env_set( g_env, "database", sqsh_optarg );
352                 break;
353             case 'e' :
354                 ret = env_set( g_env, "echo", "1" );
355                 break;
356             case 'E' :
357                 ret = env_set( g_env, "EDITOR", sqsh_optarg );
358                 break;
359             case 'f' :
360                 ret = env_set( g_env, "thresh_fail", sqsh_optarg );
361                 break;
362             case 'G' : /* sqsh-2.1.6 */
363                 ret = env_set( g_env, "tds_version", sqsh_optarg );
364                 break;
365             case 'h' :
366                 ret = env_set( g_env, "headers", "0" );
367 
368                 if (ret != -1)
369                 {
370                     ret = env_set( g_env, "footers", "0" );
371                 }
372                 break;
373             case 'H' :
374                 ret = env_set( g_env, "hostname", sqsh_optarg );
375                 break;
376             case 'i' :
377                 read_file   = True;
378                 show_banner = False;
379                 ret = env_set( g_env, "script", sqsh_optarg );
380                 break;
381             case 'I' :
382                 ret = env_set( g_env, "interfaces", sqsh_optarg );
383                 break;
384             case 'J' :
385                 ret = env_set( g_env, "charset", sqsh_optarg );
386                 break;
387             case 'k' :
388                 ret = env_set( g_env, "keyword_file", sqsh_optarg );
389                 break;
390             case 'K' : /* sqsh-2.1.6 */
391                 ret = env_set( g_env, "keytab_file", sqsh_optarg );
392                 break;
393             case 'l' :
394                 ret = env_set( g_env, "debug", sqsh_optarg );
395                 break;
396             case 'L' :
397                 cptr = sqsh_optarg;
398                 i    = 0;
399                 while( i < sizeof(str) && *cptr != '\0' && *cptr != '=' )
400                     str[i++] = *cptr++;
401                 str[i] = '\0';
402                 if( *cptr != '=' ) {
403                     fprintf( stderr, "sqsh: -L: Missing '='\n" );
404                     sqsh_exit(255);
405                 }
406                 ++cptr;
407                 ret = env_set( g_env, str, cptr );
408                 break;
409             case 'm' :
410                 ret = env_set( g_env, "style", sqsh_optarg );
411                 break;
412             case 'n' :
413                 ret = env_set( g_env, "chained", sqsh_optarg );
414                 break;
415             case 'N' :
416                 ret = env_set( g_env, "appname", sqsh_optarg);
417                 break;
418             case 'o' :
419                 fflush(stdout);
420                 fflush(stderr);
421                 if( (fd = sqsh_open( sqsh_optarg, O_WRONLY|O_CREAT|O_TRUNC, 0 )) == -1 ||
422                      sqsh_dup2( fd, fileno(stdout) ) == -1 ||
423                      sqsh_dup2( fd, fileno(stderr) ) == -1 ||
424                      sqsh_close( fd ) == -1 ) {
425                     fprintf( stderr, "sqsh: -o: %s: %s\n", sqsh_optarg,
426                                 sqsh_get_errstr() );
427                     sqsh_exit(255);
428                 }
429                 ret = True;
430                 break;
431             case 'p' :
432                 ret = env_set( g_env, "statistics", "1" );
433                 break;
434             case 'P' :
435                 /*
436                  * Special case: An explicit -P'' should be treated the
437                  * space as a -P without the optional argument.
438                  */
439                 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
440                 {
441                     ret = env_set( g_env, "password", NULL );
442                 }
443                 else
444                 {
445                     ret = env_set( g_env, "password", sqsh_optarg );
446 
447                     while( *sqsh_optarg != '\0' )
448                         *sqsh_optarg++ = ' ';
449                 }
450 
451                 break;
452             case 'Q' : /* sqsh-2.1.6 */
453                 ret = env_set( g_env, "query_timeout", sqsh_optarg );
454                 break;
455             case 'r' :
456                 /*
457                  * The alternative sqshrc file should already have been
458                  * processed above. Note that the -r option should be the
459                  * first option on the command line, otherwise the option
460                  * is ignored, also the option and the filename should be
461                  * separated by at leat a blank space.
462                 */
463                 ret = True;
464                 break;
465             case 'R' : /* sqsh-2.1.6 */
466                 ret = env_set( g_env, "principal", sqsh_optarg);
467                 break;
468             case 's' :
469                 ret = env_set( g_env, "colsep", sqsh_optarg );
470                 break;
471             case 'S' :
472                 ret = env_set( g_env, "DSQUERY", sqsh_optarg );
473                 break;
474             case 't' :
475                 ret = env_set( g_env, "filter", "1" );
476                 if (ret == True && sqsh_optarg != NULL)
477                 {
478                     ret = env_set( g_env, "filter_prog", sqsh_optarg );
479                 }
480                 break;
481             case 'T' : /* sqsh-2.1.6 */
482                 ret = env_set( g_env, "login_timeout", sqsh_optarg );
483                 break;
484             case 'U' :
485                 ret = env_set( g_env, "username", sqsh_optarg );
486                 username = sqsh_optarg;
487                 break;
488             case 'v' :
489                 printf( "%s\n", g_version );
490                 sqsh_exit(0);
491                 break;
492             case 'V' : /* sqsh-2.1.6 */
493                 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
494                     ret = env_set( g_env, "secure_options", "u" );
495                 else
496                     ret = env_set( g_env, "secure_options", sqsh_optarg );
497                 break;
498             case 'w' :
499                 ret = env_set( g_env, "width", sqsh_optarg );
500                 set_width = True;
501                 break;
502             case 'X' :
503                 ret = env_set( g_env, "encryption", "1" );
504                 break;
505             case 'y' :
506                 sprintf( str, "SYBASE=%s", sqsh_optarg );
507                 putenv( str );
508                 ret = env_set( g_env, "SYBASE", sqsh_optarg );
509                 break;
510             case 'z' :
511                 ret = env_set( g_env, "language", sqsh_optarg );
512                 break;
513             case 'Z' : /* sqsh-2.1.6 */
514                 if (sqsh_optarg == NULL || *sqsh_optarg == '\0')
515                     ret = env_set( g_env, "secmech", "default");
516                 else
517                     ret = env_set( g_env, "secmech", sqsh_optarg );
518                 break;
519             case '\250' :
520 #if defined(SQSH_HIDEPWD)
521                 {
522                   /*
523                    * sqsh-2.1.7 - Incorporated patch by David Wood
524                    * to solve a problem with pipes already in use. (Patch-id 2607434)
525                    * The actual pipe file descriptors will now be passed on with the \250 option.
526                   */
527                   memset(buf, 0, MAXPWD);
528                   if (sqsh_optarg != NULL)
529                     strcpy (buf, sqsh_optarg);
530                   if ((p = strchr(buf, '/')) != NULL) {
531                     *p    = '\0';
532                     fdin  = atoi(buf);
533                     fdout = atoi(p+1);
534 
535                     memset(buf, 0, MAXPWD);
536                     if (read(fdin, buf, MAXPWD-1) <= 0) {
537                       fprintf(stderr, "sqsh: Error: Can't read password from pipe (filedes=%d)\n", fdin);
538                       ret = False;
539                     } else {
540                       if (buf[0] == '\n')
541                         ret = env_set( g_env, "password", NULL );
542                       else
543                         ret = env_set( g_env, "password", buf );
544                     }
545                     close(fdin);
546                     close(fdout);
547                   } else {
548                       fprintf(stderr, "sqsh: Error: Missing pipe file descriptors for password option 250\n");
549                       ret = False;
550                   }
551                 }
552 #endif
553                 break;
554 
555             default :
556                 fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
557                 print_usage();
558                 sqsh_exit(255);
559                 break;
560         }
561 
562         /*
563          * Check the results from whichever variable we attempted
564          * to set.
565          */
566         if (ret == False)
567         {
568             fprintf( stderr, "sqsh: -%c: %s\n", ch, sqsh_get_errstr() );
569             sqsh_exit( 255 );
570         }
571     }
572 
573     /*
574      * The only time that we allow extra parameters on the command line
575      * is if the -i flag is supplied.  These are then passed as positional
576      * parameters to the underlying script.
577      */
578     if (read_file == False && argc != sqsh_optind)
579     {
580         print_usage();
581         sqsh_exit( 255 );
582     }
583 
584     /*
585      * If we are reading from a file and there are parameters left then
586      * we want to copy them to the internal environment list.
587      */
588     if (read_file)
589     {
590         if (sqsh_optind > 1)
591         {
592             env_get (g_env, "script", &(argv[sqsh_optind-1])) ;
593         }
594         g_func_args[g_func_nargs].argc = argc - sqsh_optind + 1;
595         g_func_args[g_func_nargs].argv = &(argv[sqsh_optind-1]);
596         ++g_func_nargs;
597     }
598 
599     /*
600      * Figure out if stdin or stdout is connected to a tty.
601      *
602      * sqsh-2.1.7 - Note, if a file is provided with -i, stdin will be directed
603      * to this file in \cmd_loop. So at this point, technically speaking, stdin
604      * is still connected to a tty. That's why we can't use sqsh_stdin_isatty()
605      * here as it will always return True.
606      */
607     stdout_tty = isatty( fileno(stdout) );
608     if (isatty (fileno(stdin)) && !read_file && !sql)
609         stdin_tty = True;
610     else
611         stdin_tty = False;
612     DBG(sqsh_debug(DEBUG_SCREEN,"sqsh_main: : stdin_tty: %d, stdout_tty: %d\n",
613         stdin_tty, stdout_tty));
614     if (stdin_tty && !stdout_tty)
615     {
616         fprintf( stderr, "sqsh: Error: Cannot run in interactive mode with redirected output\n" );
617         sqsh_exit( 255 );
618     }
619     if (stdin_tty && stdout_tty)
620         g_interactive = True;
621 
622 #if defined(TIOCGWINSZ)
623     /*
624      * If the width hasn't been explicitly set by the user and the
625      * stdout is connected to a tty, then we want to request the
626      * current screen width from the tty.
627      */
628     if (set_width == False && stdout_tty)
629     {
630         /* Check to see if the width has been set via the sqshrc file.
631          * To do this we get the current setting - if it is != 80 then
632          * the sqshrc file had a \set width directive, which we don't want
633          * to override here.
634          */
635         char *w;
636         env_get( g_env, "width", &w);
637         if(!w || atoi(w) == 80) {
638             if (ioctl(fileno(stdout), TIOCGWINSZ, &ws ) != -1) {
639                 sprintf( str, "%d", ws.ws_col );
640                 env_set( g_env, "width", str );
641 
642                 DBG(sqsh_debug(DEBUG_SCREEN,"sqsh_main: Screen width = %d\n",
643                     ws.ws_col);)
644             } else {
645             DBG(sqsh_debug(DEBUG_SCREEN,"sqsh_main: ioctl(%d,TIOCGWINSZ): %s\n",
646                            (int)fileno(stdout), strerror(errno));)
647             }
648         }
649 
650 #if defined(SIGWINCH)
651         if (stdout_tty)
652         {
653             sig_install( SIGWINCH, sigwinch_handler, (void*)NULL, 0 );
654         }
655 #endif /* SIGWINCH */
656     }
657 #endif /* TIOGWINSZ */
658 
659 #if defined (SIGQUIT)
660     /*
661      * Disable the CTRL-\ (SIGQUIT) signal when in interactive mode.
662      */
663     if (g_interactive)
664         sig_install( SIGQUIT, SIG_H_IGN, NULL, 0 );
665 #endif /* SIGQUIT */
666 
667     /*
668      * Set a TERM window title when running in interactive mode
669      * and variable term_title is set.
670      */
671     if (g_interactive)
672     {
673         env_get( g_env, "term_title", &term_title );
674         if (term_title != NULL && *term_title != '\0')
675         {
676             exp_buf = varbuf_create( 64 );
677 
678             if (exp_buf == NULL)
679             {
680                 fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
681                 sqsh_exit( 255 );
682             }
683 
684             if (sqsh_expand( term_title, exp_buf, 0 ) == False)
685             {
686                 fprintf( stderr, "sqsh: Error expanding $term_title: %s\n",
687                     sqsh_get_errstr() );
688                 sqsh_exit( 255 );
689             }
690             fprintf (stdout, "%c]0;%s %s%c",
691                      '\033', argv[0], varbuf_getstr( exp_buf ), '\007' );
692             varbuf_destroy( exp_buf );
693         }
694     }
695 
696     /*
697      * If both input and output are connected to a tty *and* $banner
698      * is 1 and the -i flag has not been set, then we display the
699      * banner.
700      */
701     env_get( g_env, "banner", &banner );
702     if (show_banner && (banner == NULL || *banner == '1') &&
703         g_interactive)
704     {
705         printf( "%s ", g_version );
706         DBG(printf( "(DEBUG) " );)
707         printf( "%s", g_copyright );
708         printf( "\n" );
709         printf( "This is free software with ABSOLUTELY NO WARRANTY\n" );
710         printf( "For more information type '\\warranty'\n" );
711     }
712 
713     /*
714      * Now, if username hasn't been explicitly set at the command
715      * line then figure out what it is by looking at the current
716      * uid.
717      */
718     env_get( g_env, "username", &username );
719 
720     if (username == NULL)
721     {
722         uid = getuid();
723         pwd = getpwuid( uid );
724 
725         if (pwd == NULL)
726         {
727             fprintf( stderr, "sqsh: Unable to get username for uid %d: %s\n",
728                      (int)uid, strerror(errno) );
729             sqsh_exit(255);
730         }
731 
732         env_set( g_env, "username", pwd->pw_name );
733     }
734 
735     /*
736      * Before we go any further we need to do a little work if
737      * we are connected to a tty.
738      */
739     if (stdin_tty)
740     {
741         /*
742          * Next, we need to try to load the history file, if one is
743          * available.  We ignore the results of history_load, just
744          * in case the history file doesn't exist.
745          */
746         env_get( g_env, "history", &history );
747 
748         /*
749          * If a history file has been defined, then we want to
750          * expand its contents. This will allow folks to have
751          * a different history file for each server.
752          */
753         if (history != NULL && *history != '\0')
754         {
755             exp_buf = varbuf_create( 512 );
756 
757             if (exp_buf == NULL)
758             {
759                 fprintf( stderr, "sqsh: %s\n", sqsh_get_errstr() );
760                 sqsh_exit( 255 );
761             }
762 
763             if (sqsh_expand( history, exp_buf, 0 ) == False)
764             {
765                 fprintf( stderr, "sqsh: Error expanding $history: %s\n",
766                     sqsh_get_errstr() );
767                 history_load( g_history, history );
768             }
769             else
770             {
771                 history_load( g_history, varbuf_getstr( exp_buf ) );
772             }
773             varbuf_destroy( exp_buf );
774 
775             sprintf( str, "%d", history_get_nbr(g_history) );
776             env_set( g_env, "histnum", str );
777         }
778 
779         /*
780          * Initialize the readline "sub-system".  This basically consists
781          * of installing handlers for readline keyword completion and
782          * sucking in the completion keyword list and the readline history file.
783          */
784         sqsh_readline_init();
785     }
786 
787     /*
788      * If a single SQL statement was supplied on the command line
789      * then we simply want to execute the \loop -e.
790      */
791     if (sql != NULL)
792     {
793         if ((jobset_run( g_jobset, "\\loop -e \"$sql\"", &exit_status )) == -1)
794         {
795             fprintf( stderr, "\\loop: %s\n", sqsh_get_errstr() );
796             sqsh_exit( 255 );
797         }
798     }
799     else
800     {
801         /*
802          * Now that everything is configured we can go ahead and enter
803          * the read-eval-print loop.  Note, it is the responsibility
804          * of the loop to establish the connection to the database.
805          */
806         if ((jobset_run( g_jobset, "\\loop $script", &exit_status )) == -1)
807         {
808             fprintf( stderr, "\\loop: %s\n", sqsh_get_errstr() );
809             sqsh_exit( 255 );
810         }
811     }
812 
813     /*
814      * CMD_FAIL indicates that something went wrong internally with
815      * \loop and that it probably never even executed a command.  If
816      * this is the case, then just exit with 255.
817      */
818     if( exit_status == CMD_FAIL )
819         sqsh_exit( 255 );
820 
821     /*
822      * However, if \loop exited with a CMD_ABORT, then we need to check
823      * to see if the reason was because the maximum number of errors
824      * was reached.
825      */
826     if (exit_status == CMD_ABORT || exit_status == CMD_INTERRUPTED)
827     {
828         /*
829          * Retrieve the values of thresh_exit and batch_failcount.  If
830          * thresh_exit is not zero, and the two variables are equal, then
831          * we need to exit with the total number of batches that failed.
832          */
833         env_get( g_env, "thresh_exit", &thresh_exit );
834         env_get( g_env, "batch_failcount", &batch_failcount );
835         if (thresh_exit != NULL && batch_failcount != NULL )
836         {
837             if( strcmp(thresh_exit,"0") != 0 &&
838                 strcmp( thresh_exit, batch_failcount ) == 0 )
839                 sqsh_exit( atoi(batch_failcount) );
840         }
841 
842         /*
843          * Otherwise, we return the abort error code.
844          */
845         sqsh_exit( 254 );
846     }
847 
848     /*
849      * This point is reached if \loop returned CMD_EXIT. Normally
850      * isql would just exit(0) here. However, we are not isql,
851      * and if exit_value is explicitly set with \exit n, then
852      * we use that return value, otherwise if exit_failcount is 1,
853      * then we exit with the total number of batches that failed.
854      */
855     env_get( g_env, "exit_value", &exit_value );
856     if ( exit_value != NULL && atoi(exit_value) != 0 )
857         sqsh_exit( atoi(exit_value) );
858 
859     env_get( g_env, "exit_failcount", &exit_failcount );
860     env_get( g_env, "batch_failcount", &batch_failcount );
861     if( exit_failcount != NULL && *exit_failcount == '1' &&
862         batch_failcount != NULL ) {
863         sqsh_exit( atoi(batch_failcount) );
864     }
865 
866     /*
867      * If exit_failcount is 0, then just perform a normal exit.
868      */
869     sqsh_exit( 0 );
870     /* NOTREACHED */
871     return (0);
872 }
873 
874 #if defined(TIOCGWINSZ) && defined(SIGWINCH)
875 
876 /*
877  * sigwinch_handler():
878  *
879  * This function is called whenever a SIGWINCH (window size change)
880  * signal is recieved during the life of sqsh.  Unfortunately, this
881  * function calls quite a few functions that are known not to be
882  * signal safe, but I am willing to accept the risk.  Thanks
883  * to David Whitemarsh <djw@accessio.demon.co.uk> for supplying
884  * this code.
885  */
sigwinch_handler(sig,user_data)886 static void sigwinch_handler( sig, user_data )
887     int   sig;
888     void *user_data;
889 {
890     struct winsize ws;
891     char   ctty_path[SQSH_MAXPATH+1];
892     int    ctty_fd;
893     char   num[10];
894 
895     /*
896      * Unfortunately, we can recieve the SIGWINCH if our controlling
897      * terminal changes size, even though our stdout may have been
898      * redirected to a file.  If this is the case, we cannot query
899      * stdout for the size, we must ask the controlling terminal.
900      */
901     if (isatty( fileno(stdout) ))
902         ctty_fd = fileno(stdout);
903     else {
904 
905         /*
906          * Attempt to grab the path to our controlling tty.  If we can't
907          * find it, then we have to give up.
908          */
909         if (ctermid( ctty_path ) == NULL) {
910             DBG(sqsh_debug(DEBUG_SCREEN,
911                 "sigwinch: Unable to get controlling tty\n");)
912             return;
913         }
914 
915         /*
916          * Now, open a file descriptor to the controlling terminal. We'll
917          * use this to query the current size.
918          */
919         if ((ctty_fd = open( ctty_path, O_RDONLY )) == -1) {
920             DBG(sqsh_debug(DEBUG_SCREEN, "sigwinch: Unable to open %s for read\n",
921                 ctty_path);)
922             return;
923         }
924 
925     }
926 
927     if (ioctl( ctty_fd, TIOCGWINSZ, &ws ) != -1) {
928         sprintf( num, "%d", (int)ws.ws_col );
929         env_set( g_env, "width", num );
930 
931         DBG(sqsh_debug(DEBUG_SCREEN, "sigwinch: Screen size changed to %d\n",
932              ws.ws_col);)
933     } else {
934         DBG(sqsh_debug(DEBUG_SCREEN, "sigwinch: ioctl(%d,TIOCGWINSZ): %s\n",
935             fileno(stdout), strerror(errno));)
936     }
937 
938     /*
939      * Now, if we created a file descriptor to test the screen size
940      * then we need to destroy it.
941      */
942     if (ctty_fd != fileno(stdout))
943         close( ctty_fd );
944 }
945 
946 #endif
947 
948 /*
949  * print_usage():
950  *
951  * Displays the contents of the sg_flags array in a pretty manner.
952  */
print_usage()953 static void print_usage()
954 {
955     char  str[40];
956     int   nflags;
957     int   line_len;
958     int   len;
959     int   middle;
960     int   i, j;
961 
962     /*-- Calculate the size of our array of flags --*/
963     nflags = sizeof(sg_flags) / sizeof(sqsh_flag_t);
964 
965     /*
966      * The following nastyness is responsible for formatting the
967      * initial usage line.  Don't bother to try to understand it...
968      * it would be easier to re-write it.
969      */
970     fprintf( stderr, "Use: sqsh" );
971     line_len = 0;
972     for( i = 0; i < nflags; i++ ) {
973         if( sg_flags[i].arg == NULL || *sg_flags[i].arg == '\0' )
974             sprintf( str, " [%s]", sg_flags[i].flag );
975         else
976             sprintf( str, " [%s %s]", sg_flags[i].flag, sg_flags[i].arg );
977 
978         len = strlen( str );
979 
980         if( (line_len + len) > 68 ) {
981             fprintf( stderr, "\n         " );
982             line_len = 0;
983         }
984         fputs( str, stderr );
985         line_len += len;
986     }
987 
988     fputs( "\n\n", stderr );
989 
990     /*
991      * The splits the descriptions for each flag in our array into
992      * two nice columns.
993      */
994     middle = (nflags+1) / 2;
995     j = middle;
996     for( i = 0; i < middle; i++ ) {
997         fprintf( stderr, " %2s  %-35.35s", sg_flags[i].flag,
998                                           sg_flags[i].description );
999         if( j < nflags ) {
1000             fprintf( stderr, " %2s  %s\n", sg_flags[j].flag,
1001                                            sg_flags[j].description );
1002             ++j;
1003         } else {
1004             fputc( '\n', stderr );
1005         }
1006     }
1007     fputc( '\n', stderr );
1008 }
1009 
1010 #if defined(SQSH_HIDEPWD)
1011 /*
1012  * sqsh-2.1.7 - Incorporated patch by David Wood
1013  * to solve a problem with pipes already in use. (Patch-id 2607434)
1014  * The actual pipe file descriptors will now be passed on with the \250 option.
1015  * sqsh-2.2.0 - Function reworked.
1016 */
hide_password(argc,argv)1017 static void hide_password (argc, argv)
1018   int   argc;
1019   char *argv[];
1020 {
1021   int    i, j;
1022   char   buf[32];
1023   int    filedes[2];
1024   char   nullpwd[2];
1025   pid_t  pid;
1026   char  *pwd = NULL;
1027   int    status;
1028 
1029 
1030   nullpwd[0] = '\n';
1031   nullpwd[1] = '\0';
1032 
1033   /*
1034    * Loop through the list of arguments only once and skip all intermediate -P
1035    * entries, but remember the last password specified.
1036   */
1037   for (i = 0, j = 0; argv[i] != NULL; ++i)
1038   {
1039     if (*(argv[i]) == '-' && *(argv[i]+1) == 'P')
1040     {
1041       /*
1042        * New password parameter encounterd.
1043       */
1044       pwd = NULL;
1045       if (*(argv[i]+2) != '\0')
1046       {
1047         /*
1048          * Password passed on as: "sqsh -SSYBASE -Usa -Pxxxxxx" , or as -P-
1049         */
1050         pwd = (argv[i]+2);
1051       }
1052       else if ((i+1 < argc) && (*(argv[i+1]) != '-' || (*(argv[i+1]) == '-' && *(argv[i+1]+1) == '\0')))
1053       {
1054         /*
1055          * Password passed on as: "sqsh -SSYBASE -Usa -P xxxxxx" , or as -P -
1056         */
1057         pwd = argv[++i];
1058       }
1059       if (pwd == NULL || (pwd != NULL && strlen(pwd) == 0))
1060       {
1061         /*
1062          * Empty (NULL) password passed on as:
1063          * "sqsh -SSYBASE -Usa -P " or "sqsh -SSYBASE -Usa -P '' "
1064         */
1065         pwd = nullpwd;
1066       }
1067     }
1068     else
1069       argv[j++] = argv[i];
1070   }
1071 
1072   /*
1073    * If pwd == NULL then no -P was specified as argument and we do not
1074    * have to hide anything from the argument list.
1075   */
1076   if (pwd == NULL)
1077   {
1078     return;
1079   }
1080 
1081   /*
1082    * Create the pipe.
1083   */
1084   if (pipe (filedes) == -1)
1085   {
1086     perror ("sqsh: Error: Can't pipe()");
1087     return;
1088   }
1089   sprintf (buf, "-%c%d/%d", '\250', filedes[0], filedes[1]);
1090   argv[j++] = buf;
1091   argv[j]   = NULL;
1092 
1093   if ((pid = fork()) != 0)
1094   {
1095     /*
1096      * sqsh-2.2.0 - This code is executed by the parent process.
1097      * Wait for the child process to finish execution before we continue.
1098      */
1099     (void) waitpid (pid, &status, 0);
1100     /*
1101      * Re-execute ourselves in the parent process, with the modified argv[] list.
1102      */
1103     if (WIFEXITED(status) != 0 && WEXITSTATUS(status) == 0)
1104       (void) execvp (argv[0], argv);
1105       /* Not reached */
1106     else
1107     {
1108       fprintf (stderr, "sqsh: Error: Processing of function hide_password failed unexpectedly\n");
1109       sqsh_exit (255);
1110     }
1111   }
1112   else
1113   {
1114     /*
1115      * The child process writes the password to the pipe, closes the pipe
1116      * and exits.
1117     */
1118     if ((int) write (filedes[1], pwd, strlen(pwd)) != (int) strlen(pwd))
1119     {
1120       fprintf (stderr, "sqsh: Error: Failed to write password to pipe (filedes=%d)\n", filedes[1]);
1121       sqsh_exit (255);
1122     }
1123     (void) close (filedes[0]);
1124     (void) close (filedes[1]);
1125     sqsh_exit (0);
1126   }
1127 }
1128 
1129 #endif
1130 
1131