1 /*
2  * cli.h
3  *
4  * Command line interpreter
5  *
6  * Copyright (C) 2006-2008 Post Increment
7  *
8  * The contents of this file are subject to the Mozilla Public License
9  * Version 1.0 (the "License"); you may not use this file except in
10  * compliance with the License. You may obtain a copy of the License at
11  * http://www.mozilla.org/MPL/
12  *
13  * Software distributed under the License is distributed on an "AS IS"
14  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15  * the License for the specific language governing rights and limitations
16  * under the License.
17  *
18  * The Original Code is WOpenMCU
19  *
20  * The Initial Developer of the Original Code is Post Increment
21  *
22  * Contributor(s): Craig Southeren (craigs@postincrement.com)
23  *                 Robert Jongbloed (robertj@voxlucida.com.au)
24  *
25  * Portions of this code were written by Post Increment (http://www.postincrement.com)
26  * with the assistance of funding from US Joint Forces Command Joint Concept Development &
27  * Experimentation (J9) http://www.jfcom.mil/about/abt_j9.htm
28  *
29  * Further assistance for enhancements from Imagicle spa
30  *
31  * $Revision: 24931 $
32  * $Author: csoutheren $
33  * $Date: 2010-12-07 20:01:15 -0600 (Tue, 07 Dec 2010) $
34  */
35 
36 #ifndef PTLIB_CLI_H
37 #define PTLIB_CLI_H
38 
39 #include <ptlib.h>
40 #include <ptlib/sockets.h>
41 
42 #include <list>
43 
44 
45 /** Command Line Interpreter class.
46     This class contains a set of commands, which are executed via a PNotifier,
47     when entered via a PChannel.
48 
49     The system supports multiple simultaneous interpreter which may access the
50     same command set. For example several telnet sessions.
51 
52     Note that the various command interpreters could be operating in different
53     threads, so care should be taken for sybchronisation issues on the object
54     being acted upon via the PNotifiers.
55   */
56 class PCLI : public PObject
57 {
58     PCLASSINFO(PCLI, PObject);
59   public:
60     class Context;
61 
62     /**Context for command line interpreter.
63       */
64     class Context : public PIndirectChannel
65     {
66       public:
67       /**@name Construction */
68       //@{
69         /**Construct new command line interpreter context.
70           */
71         Context(
72           PCLI & cli
73         );
74 
75         /**Destroy command line interpreter context.
76            This will close and wait for threads to exit.
77           */
78         virtual ~Context();
79       //@}
80 
81       /**@name Overrides from PChannel */
82       //@{
83         /**Low level write to the channel. This function will block until the
84            requested number of characters are written or the write timeout is
85            reached. The GetLastWriteCount() function returns the actual number
86            of bytes written.
87 
88            This translate new line character ('\n') to be the text string in
89            PCLI::m_newLine.
90 
91            The GetErrorCode() function should be consulted after Write() returns
92            false to determine what caused the failure.
93 
94            @return
95            true if at least len bytes were written to the channel.
96          */
97         virtual PBoolean Write(
98           const void * buf, ///< Pointer to a block of memory to write.
99           PINDEX len        ///< Number of bytes to write.
100         );
101       //@}
102 
103       /**@name Operations */
104       //@{
105         /**Start a command line interpreter thread.
106           */
107         bool Start();
108 
109         /**Stop command line interpreter context.
110            This will close the channel and wait for threads to exit.
111           */
112         void Stop();
113 
114         /**Call back frunction for when context is started.
115            This is usually called from within a background thread.
116 
117            The default behaviour displays the prompt.
118           */
119         virtual void OnStart();
120 
121         /**Callback for when context is stopping.
122            This is usually called from within a background thread.
123 
124            The default behaviour does nothing.
125           */
126         virtual void OnStop();
127 
128         /**Write prompt (depending on state) to channel.
129           */
130         virtual bool WritePrompt();
131 
132         /**Read a character from the attached channel an process.
133            If the character was successfully read then ProcessInput() is called.
134           */
135         virtual bool ReadAndProcessInput();
136 
137         /**Process a character read from the channel.
138            Returns false if have error and processing is to cease.
139           */
140         virtual bool ProcessInput(int ch);
141         virtual bool ProcessInput(const PString & line);
142 
143         /**Call back for a command line was completed and ENTER pressed.
144            The default behaviour processes the line into a PArgList and deals
145            with the command history and help.
146 
147            Control is then passed on to PCLI::OnReceivedLine().
148           */
149         virtual void OnCompletedLine();
150       //@}
151 
152       /**@name Member access */
153       //@{
154         /**Get the CLI.
155           */
GetCLI()156         PCLI & GetCLI() const { return m_cli; }
157 
158         /**Indicate is currently processing a command.
159           */
IsProcessingCommand()160         bool IsProcessingCommand() const { return m_state == e_ProcessingCommand; }
161       //@}
162 
163       protected:
164         PDECLARE_NOTIFIER(PThread, Context, ThreadMain);
165 
166         PCLI      & m_cli;
167         PString     m_commandLine;
168         bool        m_ignoreNextEOL;
169         PStringList m_commandHistory;
170         PThread   * m_thread;
171 
172         enum State {
173           e_Username,
174           e_Password,
175           e_CommandEntry,
176           e_ProcessingCommand
177         } m_state;
178         PString m_enteredUsername;
179     };
180 
181     /**This class is an enhancement to PArgList to add context.
182       */
183     class Arguments : public PArgList
184     {
185       public:
186       /**@name Construction */
187       //@{
188         Arguments(
189           Context & context,
190           const PString & rawLine
191         );
192       //@}
193 
194       /**@name Operations */
195       //@{
196         /**Write to the CLI output channel the usage for the current command.
197           */
198         Context & WriteUsage();
199 
200         /**Write an error to the CLI output channel.
201           */
202         Context & WriteError(
203           const PString & error = PString::Empty()  /// Error message
204         );
205       //@}
206 
207       /**@name Member access */
208       //@{
209         /**Get the CLI context supplying the command line arguments.
210           */
GetContext()211         Context & GetContext() const { return m_context; }
212       //@}
213 
214       protected:
215         Context & m_context;
216         PString   m_command;
217         PString   m_usage;
218 
219       friend class PCLI;
220     };
221 
222 
223   /**@name Construction */
224   //@{
225     /** Contracut a new command line interpreter.
226       */
227     PCLI(
228       const char * prompt = NULL
229     );
230 
231     /**Destroy the command line interpreter. This will call Stop() to assure
232        everything is cleaned up before exiting.
233       */
234     virtual ~PCLI();
235   //@}
236 
237   /**@name Operations */
238   //@{
239     /**Start a command line interpreter.
240        If runInBackground is true the all the command line interpreter contexts
241        that have been added will have their background threads started.
242 
243        If runInBackground is false, then there must only be one context added
244        and that context is continuously read until it's channel is closed or
245        returns end of file.
246       */
247     virtual bool Start(
248       bool runInBackground = true   ///< Spawn a thread to read and interpret commands
249     );
250 
251     /**Stop and clean up command line interpreters.
252        All the running contexts threads will be stopped, closing the channels
253        and memory cleaned up.
254       */
255     virtual void Stop();
256 
257     /**Open a command line interpreter context.
258       */
259     bool StartContext(
260       PChannel * channel,           ///< Channel to read/write
261       bool autoDelete = true,       ///< Automatically delete channel on exit
262       bool runInBackground = true   ///< Spawn a thread to read and interpret commands
263     );
264     bool StartContext(
265       PChannel * readChannel,      ///< Channel to be used for both read operations.
266       PChannel * writeChannel,     ///< Channel to be used for both write operations.
267       bool autoDeleteRead = true,  ///< Automatically delete the read channel
268       bool autoDeleteWrite = true, ///< Automatically delete the write channel
269       bool runInBackground = true   ///< Spawn a thread to read and interpret commands
270     );
271 
272     /**Create a new context.
273        Users may use this to create derived classes for their own use.
274       */
275     virtual Context * CreateContext();
276 
277     /**Add a command line interpreter context to the system.
278        If context is NULL then CreateContext() is called to create one.
279       */
280     virtual Context * AddContext(
281       Context * context = NULL    ///< New context to add to the system.
282     );
283 
284     /**Remove the command line interpreter context.
285        The context thread is stopped, the channel closed and memory deleted.
286       */
287     virtual void RemoveContext(
288       Context * context   ///< Context to remove
289     );
290 
291     /**Remove any closed command line interpreter contexts.
292       */
293     virtual void GarbageCollection();
294 
295     /**Received a completed command line.
296        The completed command line is parsed into arguments by the PArgList
297        class, and passed to this function.
298 
299        The default behaviour searches the list of registered commands for a
300        PNotifier to execute.
301       */
302     virtual void OnReceivedLine(
303       Arguments & line
304     );
305 
306     /**Received a login name/pasword to be verified.
307        Note that the m_username or m_password field must be non-empty for a
308        log in sequence to occur, even if this function is overridden and the
309        memnbers not actually used for validation.
310 
311        If the m_username is an empty string, but m_password is not, then it
312        the username not prompted for, just the password is required. The
313        reverse is also poassible and only a username entry required.
314 
315        Default returns true if parameters are equal to m_username, m_password
316        respectively.
317       */
318     virtual bool OnLogIn(
319       const PString & username,
320       const PString & password
321     );
322 
323     /**Set a string to all command line interpreter contexts.
324       */
325     void Broadcast(
326       const PString & message   ///< Message to broadcast.
327     ) const;
328 
329     /**Register a new command to be interpreted.
330        Note the command may be a series of synonyms of the same command
331        separated by the '\n' character.
332 
333        The command may also contain spaces which separates sub-commands, e.g.
334        "list users".
335 
336        Returns false if one of the command synonyms was a dupicate of an
337                existing command.
338       */
339     bool SetCommand(
340       const char * command,       ///< Command(s) to register
341       const PNotifier & notifier, ///< Callback to execute when command interpreted
342       const char * help = NULL,   ///< Help text on command (what it does)
343       const char * usage = NULL   ///< Usage text on command (syntax/options)
344     );
345 
346     /**Show help for registered commands to the context.
347       */
348     void ShowHelp(
349       Context & context   ///< Context to output help to.
350     );
351   //@}
352 
353   /**@name Member access */
354   //@{
355     /**Get new line string output at the end of every line.
356        Default is "\n".
357       */
GetNewLine()358     const PString & GetNewLine() const { return m_newLine; }
359 
360     /**Set new line string output at the end of every line.
361        Default is "\n".
362       */
SetNewLine(const PString & newLine)363     void SetNewLine(const PString & newLine) { m_newLine = newLine; }
364 
365     /**Get flag for echo is required for entered characters.
366        Default is false.
367       */
GetRequireEcho()368     bool GetRequireEcho() const { return m_requireEcho; }
369 
370     /**Set flag for echo is required for entered characters.
371        Default is false.
372       */
SetRequireEcho(bool requireEcho)373     void SetRequireEcho(bool requireEcho) { m_requireEcho = requireEcho; }
374 
375     /**Get characters used for editing (backspace/delete) command lines.
376        Default is "\b\x7f".
377       */
GetEditCharacters()378     const PString & GetEditCharacters() const { return m_editCharacters; }
379 
380     /**Set characters used for editing (backspace/delete) command lines.
381        Default is "\b\x7f".
382       */
SetEditCharacters(const PString & editCharacters)383     void SetEditCharacters(const PString & editCharacters) { m_editCharacters = editCharacters; }
384 
385     /**Get prompt used for command line interpreter.
386        Default is "CLI> ".
387       */
GetPrompt()388     const PString & GetPrompt() const { return m_prompt; }
389 
390     /**Set prompt used for command line interpreter.
391        Default is "CLI> ".
392       */
SetPrompt(const PString & prompt)393     void SetPrompt(const PString & prompt) { m_prompt = prompt; }
394 
395     /**Get prompt used for login (if enabled).
396        Default is "Username: ".
397       */
GetUsernamePrompt()398     const PString & GetUsernamePrompt() const { return m_usernamePrompt; }
399 
400     /**Set prompt used for login (if enabled).
401        Default is "Username: ".
402       */
SetUsernamePrompt(const PString & prompt)403     void SetUsernamePrompt(const PString & prompt) { m_usernamePrompt = prompt; }
404 
405     /**Get prompt used for password (if enabled).
406        Default is "Password: ".
407       */
GetPasswordPrompt()408     const PString & GetPasswordPrompt() const { return m_passwordPrompt; }
409 
410     /**Set prompt used for password (if enabled).
411        Default is "Password: ".
412       */
SetPasswordPrompt(const PString & prompt)413     void SetPasswordPrompt(const PString & prompt) { m_passwordPrompt = prompt; }
414 
415     /**Get username for log in validation.
416        Default is empty string, disabling entry of username/password.
417       */
GetUsername()418     const PString & GetUsername() const { return m_username; }
419 
420     /**Set username for log in validation.
421        Default is empty string, disabling entry of username/password.
422       */
SetUsername(const PString & username)423     void SetUsername(const PString & username) { m_username = username; }
424 
425     /**Get password for log in validation.
426        Default is empty string, disabling entry of password.
427       */
GetPassword()428     const PString & GetPassword() const { return m_password; }
429 
430     /**Set password for log in validation.
431        Default is empty string, disabling entry of password.
432       */
SetPassword(const PString & password)433     void SetPassword(const PString & password) { m_password = password; }
434 
435     /**Get command to be used to exit session.
436        Default is "exit\nquit".
437       */
GetExitCommand()438     const PCaselessString & GetExitCommand() const { return m_exitCommand; }
439 
440     /**Set command to be used to exit session.
441        Default is "exit\nquit".
442       */
SetExitCommand(const PCaselessString & exitCommand)443     void SetExitCommand(const PCaselessString & exitCommand) { m_exitCommand = exitCommand; }
444 
445     /**Get command to be used to display help.
446        Default is "?\nhelp".
447       */
GetHelpCommand()448     const PCaselessString & GetHelpCommand() const { return m_helpCommand; }
449 
450     /**Set command to be used to display help.
451        Default is "?\nhelp".
452       */
SetHelpCommand(const PCaselessString & helpCommand)453     void SetHelpCommand(const PCaselessString & helpCommand) { m_helpCommand = helpCommand; }
454 
455     /**Get help on help.
456        This string is output before the individual help is output for each command.
457        Default describes ?, !, !n, !! followed by "Command available are:".
458       */
GetHelpOnHelp()459     const PString & GetHelpOnHelp() const { return m_helpOnHelp; }
460 
461     /**Set help on help.
462        This string is output before the individual help is output for each command.
463        Default describes ?, !, !n, !! followed by "Command available are:".
464       */
SetHelpOnHelp(const PCaselessString & helpOnHelp)465     void SetHelpOnHelp(const PCaselessString & helpOnHelp) { m_helpOnHelp = helpOnHelp; }
466 
467     /**Get the command to be used to repeat the last executed command.
468        Default is "!!".
469       */
GetRepeatCommand()470     const PCaselessString & GetRepeatCommand() const { return m_repeatCommand; }
471 
472     /**Set the command to be used to repeat the last executed command.
473        Default is "!!".
474       */
SetRepeatCommand(const PCaselessString & repeatCommand)475     void SetRepeatCommand(const PCaselessString & repeatCommand) { m_repeatCommand = repeatCommand; }
476 
477     /**Get command that will list/execute command history.
478        Default is "!".
479       */
GetHistoryCommand()480     const PCaselessString & GetHistoryCommand() const { return m_historyCommand; }
481 
482     /**Set command that will list/execute command history.
483        Default is "!".
484       */
SetHistoryCommand(const PCaselessString & historyCommand)485     void SetHistoryCommand(const PCaselessString & historyCommand) { m_historyCommand = historyCommand; }
486 
487     /**Get error message for if there is no history.
488        Default is "No command history".
489       */
GetNoHistoryError()490     const PString & GetNoHistoryError() const { return m_noHistoryError; }
491 
492     /**Set error message for if there is no history.
493        Default is "No command history".
494       */
SetNoHistoryError(const PString & noHistoryError)495     void SetNoHistoryError(const PString & noHistoryError) { m_noHistoryError = noHistoryError; }
496 
497     /**Get usage prefix for if Arguments::WriteUsage() called.
498        Default is "Usage: ".
499       */
GetCommandUsagePrefix()500     const PString & GetCommandUsagePrefix() const { return m_commandUsagePrefix; }
501 
502     /**Set usage prefix for if Arguments::WriteUsage() called.
503        Default is "Usage: ".
504       */
SetCommandUsagePrefix(const PString & commandUsagePrefix)505     void SetCommandUsagePrefix(const PString & commandUsagePrefix) { m_commandUsagePrefix = commandUsagePrefix; }
506 
507     /**Get error prefix for if Arguments::WriteError() called.
508        Default is ": error: ", always prefixed by command name.
509       */
GetCommandErrorPrefix()510     const PString & GetCommandErrorPrefix() const { return m_commandErrorPrefix; }
511 
512     /**Set error prefix for if Arguments::WriteError() called.
513        Default is ": error: ", always prefixed by command name.
514       */
SetCommandErrorPrefix(const PString & commandErrorPrefix)515     void SetCommandErrorPrefix(const PString & commandErrorPrefix) { m_commandErrorPrefix = commandErrorPrefix; }
516 
517     /**Get error message for if unknown command is entered.
518        Default is "Unknown command".
519       */
GetUnknownCommandError()520     const PString & GetUnknownCommandError() const { return m_unknownCommandError; }
521 
522     /**Set error message for if unknown command is entered.
523        Default is "Unknown command".
524       */
SetUnknownCommandError(const PString & unknownCommandError)525     void SetUnknownCommandError(const PString & unknownCommandError) { m_unknownCommandError = unknownCommandError; }
526   //@}
527 
528     /** Initialise a foreground context and return it
529     */
530     virtual Context * StartForeground();
531 
532     /** Run a context
533     */
534     virtual bool RunContext(Context * context);
535 
536 
537   protected:
538     PString         m_newLine;
539     bool            m_requireEcho;
540     PString         m_editCharacters;
541     PString         m_prompt;
542     PString         m_usernamePrompt;
543     PString         m_passwordPrompt;
544     PString         m_username;
545     PString         m_password;
546     PCaselessString m_exitCommand;
547     PCaselessString m_helpCommand;
548     PString         m_helpOnHelp;
549     PCaselessString m_repeatCommand;
550     PCaselessString m_historyCommand;
551     PString         m_noHistoryError;
552     PString         m_commandUsagePrefix;
553     PString         m_commandErrorPrefix;
554     PString         m_unknownCommandError;
555 
556     struct InternalCommand {
557       PNotifier m_notifier;
558       PString   m_help;
559       PString   m_usage;
560     };
561     typedef std::map<PString, InternalCommand> CommandMap_t;
562     CommandMap_t m_commands;
563 
564     typedef std::list<Context *> ContextList_t;
565     ContextList_t m_contextList;
566     PMutex        m_contextMutex;
567 };
568 
569 
570 /**Command Line Interpreter over standard input/output.
571   */
572 class PCLIStandard : public PCLI
573 {
574   public:
575   /**@name Construction */
576   //@{
577     /**Create new command line interpreter for standard I/O.
578       */
579     PCLIStandard(
580       const char * prompt = NULL
581     );
582   //@}
583 
584   /**@name Overrides from PCLI */
585   //@{
586     /**Start a command line interpreter.
587        As for ancestor function, however if no contexts have been added, then
588        on that takes a PConsoleChannel is automatically added.
589       */
590     virtual bool Start(
591       bool runInBackground = true   ///< Spawn a thread to read and interpret commands
592     );
593   //@}
594 
595     PCLI::Context * StartForeground();
596 };
597 
598 
599 /**Command Line Interpreter over TCP sockets.
600    This class allows for access and automatic creation of command line
601    interpreter contexts from incoming TCP connections on a listening port.
602   */
603 class PCLISocket : public PCLI
604 {
605   public:
606   /**@name Construction */
607   //@{
608     PCLISocket(
609       WORD port = 0,
610       const char * prompt = NULL,
611       bool singleThreadForAll = false
612     );
613     ~PCLISocket();
614   //@}
615 
616   /**@name Overrides from PCLI */
617   //@{
618     /**Start a command line interpreter.
619        This will start listening for incoming TCP connections and
620        create/dispatch contexts to handle them.
621       */
622     virtual bool Start(
623       bool runInBackground = true   ///< Spawn a thread to read and interpret commands
624     );
625 
626     /**Stop and clean up command line interpreters.
627        All the running contexts threads will be stopped, closing the channels
628        and memory cleaned up.
629 
630        The listening socket is also closed and the listener dispatch thread
631        shut down.
632       */
633     virtual void Stop();
634 
635     /**Add a command line interpreter context to the system.
636        If context is NULL then CreateContext() is called to create one.
637       */
638     virtual Context * AddContext(
639       Context * context = NULL
640     );
641 
642     /**Remove the command line interpreter context.
643        The context thread is stopped, the channel closed and memory deleted.
644       */
645     virtual void RemoveContext(
646       Context * context
647     );
648   //@}
649 
650   /**@name Operations */
651   //@{
652     /**Start listening socket.
653       */
654     bool Listen(
655       WORD port = 0
656     );
657 
658     /**Get the port we are listing on.
659       */
GetPort()660     WORD GetPort() const { return m_listenSocket.GetPort(); }
661   //@}
662 
663   protected:
664     PDECLARE_NOTIFIER(PThread, PCLISocket, ThreadMain);
665     bool HandleSingleThreadForAll();
666     bool HandleIncoming();
667     virtual PTCPSocket * CreateSocket();
668 
669     bool m_singleThreadForAll;
670 
671     PTCPSocket m_listenSocket;
672     PThread  * m_thread;
673 
674     typedef std::map<PSocket *, Context *> ContextMap_t;
675     ContextMap_t m_contextBySocket;
676 };
677 
678 
679 /**Command Line Interpreter over Telnet sockets.
680    This class allows for access and automatic creation of command line
681    interpreter contexts from incoming Telnet connections on a listening port.
682   */
683 class PCLITelnet : public PCLISocket
684 {
685   public:
686   /**@name Construction */
687   //@{
688     PCLITelnet(
689       WORD port = 0,
690       const char * prompt = NULL,
691       bool singleThreadForAll = false
692     );
693   //@}
694 
695   protected:
696     virtual PTCPSocket * CreateSocket();
697 };
698 
699 
700 #endif // PTLIB_CLI_H
701 
702 
703 // End Of File ///////////////////////////////////////////////////////////////
704