1//  Copyright (c) 2015-2016 Couchbase, Inc.
2//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
3//  except in compliance with the License. You may obtain a copy of the License at
4//    http://www.apache.org/licenses/LICENSE-2.0
5//  Unless required by applicable law or agreed to in writing, software distributed under the
6//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
7//  either express or implied. See the License for the specific language governing permissions
8//  and limitations under the License.
9
10package main
11
12import (
13	"bytes"
14	"encoding/json"
15	"flag"
16	"fmt"
17	"io"
18	"os"
19	"path/filepath"
20	"strconv"
21	"strings"
22	"unicode"
23
24	"github.com/couchbase/godbc/n1ql"
25	"github.com/couchbase/query/errors"
26	"github.com/couchbase/query/shell/cbq/command"
27)
28
29/*
30   Command line options provided.
31*/
32
33/*
34   Option        : -engine or -e
35   Args          :  <url to the query service or to the cluster>
36   Default value : http://localhost:8091/
37   Point to the cluser/query endpoint to connect to.
38*/
39var serverFlag string
40
41func init() {
42	const (
43		defaultServer = "http://localhost:8091/"
44		usage         = command.USERVERFLAG
45	)
46	flag.StringVar(&serverFlag, "engine", defaultServer, usage)
47	flag.StringVar(&serverFlag, "e", defaultServer, command.NewShorthandMsg("-engine"))
48}
49
50/*
51   Option        : -no-engine or -ne
52   Default value : false
53   Enable/Disable startup connection to a query service/cluster endpoint.
54*/
55var noQueryService bool
56
57func init() {
58	const (
59		defaultval = false
60		usage      = command.UNOENGINE
61	)
62	flag.BoolVar(&noQueryService, "no-engine", defaultval, usage)
63	flag.BoolVar(&noQueryService, "ne", defaultval, command.NewShorthandMsg("-no-engine"))
64}
65
66/*
67   Option        : -quiet
68   Default value : false
69   Enable/Disable startup connection message for the shell. Also disable echoing queries
70   when using \SOURCE or -f.
71*/
72var quietFlag bool
73
74func init() {
75	const (
76		defaultval = false
77		usage      = command.UQUIET
78	)
79	flag.BoolVar(&quietFlag, "quiet", defaultval, usage)
80	flag.BoolVar(&quietFlag, "q", defaultval, command.NewShorthandMsg("-quiet"))
81}
82
83/*
84   Option        : -timeout or -t
85   Args          : <timeout value>
86   Default value : "0ms"
87   Query timeout parameter.
88*/
89
90var timeoutFlag string
91
92func init() {
93	const (
94		defaultval = ""
95		usage      = command.UTIMEOUT
96	)
97	flag.StringVar(&timeoutFlag, "timeout", defaultval, usage)
98	flag.StringVar(&timeoutFlag, "t", defaultval, command.NewShorthandMsg("-timeout"))
99}
100
101/*
102   Option        : -user or -u
103   Args          : Login username
104   Login credentials for users. The shell will prompt for the password.
105*/
106
107var userFlag string
108
109func init() {
110	const (
111		defaultval = ""
112		usage      = command.UUSER
113	)
114	flag.StringVar(&userFlag, "user", defaultval, usage)
115	flag.StringVar(&userFlag, "u", defaultval, command.NewShorthandMsg("-user"))
116
117}
118
119/*
120   Option        : -password or -p
121   Args          : password
122   Password for user given by -u. If -u is present and we provide -p, then
123   do not prompt for the password. Error out if username is not provided.
124*/
125
126var pwdFlag string
127
128func init() {
129	const (
130		defaultval = ""
131		usage      = command.UPWD
132	)
133	flag.StringVar(&pwdFlag, "password", defaultval, usage)
134	flag.StringVar(&pwdFlag, "p", defaultval, command.NewShorthandMsg("-password"))
135
136}
137
138/*
139   Option        : -credentials or -c
140   Args          : A list of credentials, in the form of user/password objects.
141   Login credentials for users as well as SASL Buckets.
142*/
143
144var credsFlag string
145
146func init() {
147	const (
148		defaultval = ""
149		usage      = command.UCREDS
150	)
151	flag.StringVar(&credsFlag, "credentials", defaultval, usage)
152	flag.StringVar(&credsFlag, "c", defaultval, command.NewShorthandMsg("-credentials"))
153
154}
155
156/*
157   Option        : -version or -v
158   Shell Version
159*/
160
161var versionFlag bool
162
163func init() {
164	const (
165		usage = command.UVERSION
166	)
167	flag.BoolVar(&versionFlag, "version", false, usage)
168	flag.BoolVar(&versionFlag, "v", false, command.NewShorthandMsg("-version"))
169
170}
171
172/*
173   Option        : -script or -s
174   Args          : <query>
175   Single command mode
176*/
177
178type scripts []string
179
180var scriptFlag scripts
181
182func init() {
183	const (
184		usage = command.USCRIPT
185	)
186	flag.Var(&scriptFlag, "script", usage)
187	flag.Var(&scriptFlag, "s", command.NewShorthandMsg("-script"))
188
189}
190
191func (s *scripts) String() string {
192	return fmt.Sprintf("%s", *s)
193}
194
195func (s *scripts) Set(val string) error {
196	*s = append(*s, val)
197	return nil
198}
199
200/*
201   Option        : -pretty
202   Default value : false
203   Pretty print output
204*/
205
206var prettyFlag = flag.Bool("pretty", true, command.UPRETTY)
207
208/*
209   Option        : -exit-on-error
210   Default value : false
211   Exit shell after first error encountered.
212*/
213
214var errorExitFlag = flag.Bool("exit-on-error", false, command.UEXIT)
215
216/*
217   Option        : -file or -f
218   Args          : <filename>
219   Input file to run queries from. Exit after the queries are run.
220*/
221
222var inputFlag string
223
224func init() {
225	const (
226		defaultval = ""
227		usage      = command.UINPUT
228	)
229	flag.StringVar(&inputFlag, "file", defaultval, usage)
230	flag.StringVar(&inputFlag, "f", defaultval, command.NewShorthandMsg("-file"))
231
232}
233
234/*
235   Option        : -ouput or -o
236   Args          : <filename>
237   Output file to send results of queries to.
238*/
239
240var outputFlag string
241
242func init() {
243	const (
244		defaultval = ""
245		usage      = command.UOUTPUT
246	)
247	flag.StringVar(&outputFlag, "output", defaultval, usage)
248	flag.StringVar(&outputFlag, "o", defaultval, command.NewShorthandMsg("-output"))
249
250}
251
252/*
253   Option        : -log-file or -l
254   Args          : <filename>
255   Log commands for session.
256*/
257
258var logFlag string
259
260func init() {
261	const (
262		defaultval = ""
263		usage      = command.ULOG
264	)
265	flag.StringVar(&logFlag, "logfile", defaultval, usage)
266	flag.StringVar(&logFlag, "l", defaultval, command.NewShorthandMsg("-logfile"))
267
268}
269
270/*
271   Option        : -no-ssl-verify
272   Default Value : false
273   Skip verification of Certificates.
274*/
275
276var noSSLVerify bool
277
278func init() {
279	const (
280		defaultval = false
281		usage      = command.USSLVERIFY
282	)
283	flag.BoolVar(&noSSLVerify, "no-ssl-verify", defaultval, usage)
284	flag.BoolVar(&noSSLVerify, "skip-verify", defaultval, "Synonym for no-ssl-verify.")
285
286}
287
288/*
289   Option        : -cacert
290   Args : <path to root ca certificate>
291   Pass path to root ca certificate to verify identity of server.
292*/
293
294var rootFile string
295
296func init() {
297	const (
298		defaultval = ""
299		usage      = command.UCACERT
300	)
301	flag.StringVar(&rootFile, "cacert", defaultval, usage)
302}
303
304/*
305   Option        : -cert
306   Args : <path to chain certificate>
307   Pass path to chain certificate.
308*/
309
310var certFile string
311
312func init() {
313	const (
314		defaultval = ""
315		usage      = command.UCERTFILE
316	)
317	flag.StringVar(&certFile, "cert", defaultval, usage)
318}
319
320/*
321   Option        : -key
322   Args : <path to client key>
323   Pass path to client key file.
324*/
325
326var keyFile string
327
328func init() {
329	const (
330		defaultval = ""
331		usage      = command.UKEYFILE
332	)
333	flag.StringVar(&keyFile, "key", defaultval, usage)
334}
335
336/* Define credentials as user/pass and convert into
337   JSON object credentials
338*/
339
340/*
341   Option        : -batch or -b
342   Args          : on/off
343   Batch mode for sending queries to Asterix.
344*/
345
346var batchFlag string
347
348func init() {
349	const (
350		defaultval = "off"
351		usage      = command.UBATCH
352	)
353	flag.StringVar(&batchFlag, "batch", defaultval, usage)
354	flag.StringVar(&batchFlag, "b", defaultval, command.NewShorthandMsg("-batch"))
355}
356
357var (
358	SERVICE_URL  string
359	DISCONNECT   bool
360	EXIT         bool
361	stringBuffer bytes.Buffer
362)
363
364func main() {
365
366	flag.Parse()
367
368	// Initialize Global buffer to store queries for batch mode.
369	stringBuffer.Write([]byte(""))
370
371	if *prettyFlag {
372		n1ql.SetQueryParams("pretty", "true")
373	} else {
374		n1ql.SetQueryParams("pretty", "false")
375	}
376
377	if outputFlag != "" {
378		// Redirect all output to the given file.
379		// This is handled in the HandleInteractiveMode() method
380		// in interactive.go.
381		command.FILE_RW_MODE = true
382		command.FILE_OUTPUT = outputFlag
383	}
384
385	// Set command.W = os.Stdout
386	command.SetWriter(os.Stdout)
387
388	/* Handle options and what they should do */
389
390	/* -version : Display the version of the shell and then exit.
391	 */
392	if versionFlag == true {
393		dummy := []string{}
394		cmd := command.Version{}
395		cmd.ExecCommand(dummy)
396		os.Exit(0)
397	}
398
399	/* Check for input url argument
400	 */
401
402	args := flag.Args()
403	if len(args) > 1 {
404		s_err := command.HandleError(errors.CMD_LINE_ARG, "")
405		command.PrintError(s_err)
406		os.Exit(1)
407	} else {
408		if len(args) == 1 {
409			serverFlag = args[0]
410		}
411	}
412
413	// call command.ParseURL()
414	var errCode int
415	var errStr string
416	serverFlag, errCode, errStr = command.ParseURL(serverFlag)
417	if errCode != 0 {
418		s_err := command.HandleError(errCode, errStr)
419		command.PrintError(s_err)
420		os.Exit(1)
421	}
422
423	//-engine
424	if strings.HasSuffix(serverFlag, "/") == false {
425		serverFlag = serverFlag + "/"
426	}
427
428	/* -user : Accept Admin credentials. Prompt for password and set
429	   the n1ql_creds. Append to creds so that user can also define
430	   bucket credentials using -credentials if they need to.
431	*/
432	var creds command.Credentials
433	var err error
434	var password []byte
435
436	if userFlag != "" {
437		//Check if there is a -password option.
438		if pwdFlag != "" {
439			password = []byte(pwdFlag)
440			err = nil
441		} else {
442			// If no -p option then prompt for the password
443			password, err = promptPassword(command.PWDMSG)
444		}
445
446		if err == nil {
447			if string(password) == "" {
448				s_err := command.HandleError(errors.INVALID_PASSWORD, "")
449				command.PrintError(s_err)
450				os.Exit(1)
451			} else {
452				// Error out in cases where password contains escape sequences
453				// or ctrl chars.
454				for _, c := range bytes.Runes(password) {
455					if !unicode.IsPrint(c) {
456						s_err := command.HandleError(errors.INVALID_PASSWORD, "")
457						command.PrintError(s_err)
458						os.Exit(1)
459					}
460				}
461
462				creds = append(creds, command.Credential{"user": userFlag, "pass": string(password)})
463				// The driver needs the username/password to query the cluster endpoint,
464				// which may require authorization.
465				n1ql.SetUsernamePassword(userFlag, string(password))
466			}
467		} else {
468			s_err := command.HandleError(errors.INVALID_PASSWORD, err.Error())
469			command.PrintError(s_err)
470			os.Exit(1)
471		}
472	} else {
473		// If the -u option isnt specified and the -p option is specified
474		// then Invalid Username error.
475		if pwdFlag != "" {
476			s_err := command.HandleError(errors.INVALID_USERNAME, "")
477			command.PrintError(s_err)
478			os.Exit(1)
479		}
480	}
481
482	/* -credentials : Accept credentials to pass to the n1ql endpoint.
483	   Ensure that the user inputs credentials in the form a:b.
484	   It is important to apend these credentials to those given by
485	   -user.
486	*/
487	if userFlag == "" && credsFlag == "" {
488		// No credentials exist. This can still be used to connect to
489		// un-authenticated servers.
490		// Dont output the statement if we are running in single command
491		// mode.
492		if len(scriptFlag) == 0 && rootFile == "" && certFile == "" && keyFile == "" {
493			_, werr := io.WriteString(command.W, command.STARTUPCREDS)
494
495			if werr != nil {
496				s_err := command.HandleError(errors.WRITER_OUTPUT, werr.Error())
497				command.PrintError(s_err)
498			}
499		}
500
501	} else if credsFlag != "" {
502
503		creds_ret, err_code, err_string := command.ToCreds(credsFlag)
504		if err_code != 0 {
505			s_err := command.HandleError(err_code, err_string)
506			command.PrintError(s_err)
507		}
508		for _, v := range creds_ret {
509			creds = append(creds, v)
510		}
511
512	}
513	//Append empty credentials. This is used for cases where one of the buckets
514	//is a SASL bucket, and we need to access the other unprotected buckets.
515	//CBauth works this way.
516
517	creds = append(creds, command.Credential{"user": "", "pass": ""})
518
519	/* Add the credentials set by -user and -credentials to the
520	   n1ql creds parameter.
521	*/
522	if creds != nil {
523		ac, err := json.Marshal(creds)
524		if err != nil {
525			//Error while Marshalling
526			s_err := command.HandleError(errors.JSON_MARSHAL, err.Error())
527			command.PrintError(s_err)
528			os.Exit(1)
529		}
530		n1ql.SetQueryParams("creds", string(ac))
531	}
532
533	n1ql.SetSkipVerify(noSSLVerify)
534	command.SKIPVERIFY = noSSLVerify
535	if certFile != "" {
536		n1ql.SetCertFile(certFile)
537	}
538	if keyFile != "" {
539		n1ql.SetKeyFile(keyFile)
540	}
541
542	if rootFile != "" {
543		n1ql.SetRootFile(rootFile)
544	}
545
546	if strings.HasPrefix(strings.ToLower(serverFlag), "https://") && rootFile == "" && certFile == "" && keyFile == "" {
547		if noSSLVerify == false {
548			command.PrintStr(command.W, command.SSLVERIFY_FALSE)
549		} else {
550			command.PrintStr(command.W, command.SSLVERIFY_TRUE)
551		}
552	}
553
554	if noQueryService == false {
555		// Check if connection is possible to the input serverFlag
556		// else failed to connect to.
557		pingerr := command.Ping(serverFlag)
558		SERVICE_URL = serverFlag
559		command.SERVICE_URL = serverFlag
560		if pingerr != nil {
561			s_err := command.HandleError(errors.CONNECTION_REFUSED, pingerr.Error())
562			command.PrintError(s_err)
563			serverFlag = ""
564			command.SERVICE_URL = ""
565			SERVICE_URL = ""
566			noQueryService = true
567		}
568
569		/* -quiet : Display Message only if flag not specified
570		 */
571		if !quietFlag && pingerr == nil {
572			s := command.NewMessage(command.STARTUP, fmt.Sprintf("%v", serverFlag)) + command.EXITMSG
573			_, werr := io.WriteString(command.W, s)
574			if werr != nil {
575				s_err := command.HandleError(errors.WRITER_OUTPUT, werr.Error())
576				command.PrintError(s_err)
577			}
578		}
579	}
580
581	//Set QUIET to enable/disable histfile path message
582	//If quiet is true
583	command.QUIET = quietFlag
584	if quietFlag {
585		// SET the quiet mode here
586		//SET batch mode here
587		err_code, err_str := command.PushValue_Helper(true, command.PreDefSV, "quiet", strconv.FormatBool(quietFlag))
588		if err_code != 0 {
589			s_err := command.HandleError(err_code, err_str)
590			command.PrintError(s_err)
591		}
592	}
593
594	if timeoutFlag != "0ms" {
595		n1ql.SetQueryParams("timeout", timeoutFlag)
596	}
597
598	// If batch flag is enabled
599	if batchFlag != "off" {
600		if strings.ToLower(batchFlag) == "on" {
601			command.BATCH = batchFlag
602			//SET batch mode here
603			err_code, err_str := command.PushValue_Helper(true, command.PreDefSV, "batch", batchFlag)
604			if err_code != 0 {
605				s_err := command.HandleError(err_code, err_str)
606				command.PrintError(s_err)
607
608			}
609		} else {
610			s_err := command.HandleError(errors.BATCH_MODE, "")
611			command.PrintError(s_err)
612		}
613
614	} else {
615		command.BATCH = batchFlag
616	}
617
618	n1ql.SetCBUserAgentHeader("CBQ/" + command.SHELL_VERSION)
619
620	// Handle the inputFlag and ScriptFlag options in HandleInteractiveMode.
621	// This is so as to add these to the history.
622
623	HandleInteractiveMode(filepath.Base(os.Args[0]))
624}
625