Since the days when John Beck added
command line editing to zonecfg
Mark Phalan did similar thing
to Kerberos utilities and Huie-Ying Lee to sftp. IPsec utilities (ipseckey(1M) and ikeadm(1M))
offered the ability to enter commands in interactive mode for a long time but
only since Nevada build 112, the commands support command line editing and history too. Again, thanks to
libtecla (shipped with Solaris/OpenSolaris).
Lessons learned:
- adding full-blown command line editing support is hard.
Adding the initial support is quite easy. However, more advanced features could require substantial work.
This is especially true for tab completion. For sftp Huie-Ying decided to add tab completion
in the future phase because of the ambiguities when completing names of files (when to complete local
files versus remote files).
I did the same with tab completion for IPsec utilities - the integration only delivers basic command line
editing support, without tab completion. The problem with ipseckey(1M) and ikeadm(1M) is
that their grammar is quite bifurcated and has contexts. For example, you cannot use encr_alg
with AH SAs in ipseckey. Or, it would be erroneous to tab complete a valid command in the middle
of entering a key if the key hex sequence contained sub-string of a valid command. The hardest part is
I think offering the right tables of valid commands in given context. E.g. in our case a command line in
our case must start with top-level command. Each top-level command offers several valid sub-commands and
we do not offer invalid sub-commands for given top-level command so there is a necessity to track
the state of the finite state machine describing the grammar contexts.
Also, after the user entered src we do not want to allow him to enter it again on the same command line.
Also, if the user already entered say add esp spi we are expecting SPI number, not a command name.
Ideally, to solve this problem in nice way there should be a meta library (or additional API in libtecla)
which would offer the ability to link command tables and set the contexts.
- interruptible cycles in command line mode
ipseckey's monitor command reads from a PF_KEY socket in a loop. The loop
is normally interruptible by SIGINT. To do so in libtecla environment (we do not want to exit the command line
upon SIGINT and yet still need to interrupt the cycle), something like
this
is needed:
static void
monitor_catch(int signal)
{
if (!interactive)
errx(signal, gettext("Bailing on signal %d."), signal);
}
void
doreadcycle(void)
{
...
/* Catch ^C. */
newsig.sa_handler = monitor_catch;
newsig.sa_flags = 0;
(void) sigemptyset(&newsig.sa_mask);
(void) sigaddset(&newsig.sa_mask, SIGINT);
(void) sigaction(SIGINT, &newsig, &oldsig);
for (; ; ) {
rc = read(keysock, samsg, sizeof (get_buffer));
/* handle the data */
}
/* restore old behavior */
if (interactive)
(void) sigaction(SIGINT, &oldsig, NULL);
}
- interaction with SMF
While it's fine to bail out in interactive mode with error, due to the nature of IPsec commands
(they can read the config files using the same routines as for interactive mode and they are used as SMF services
to bring up IPsec policy and keys after boot) we need to distinguish the interactive and non-interactive mode.
- maximum command line history value
It seems that the second parameter to new_GetLine() - histlen is commonly misunderstood.
This variable does not express the number of maximum lines in the history but instead maximum size of the history
buffer in bytes. If the buffer becomes full, libtecla does not trim the last line but shifts instead.
Given the first parameter to new_GetLine() expresses maximum command line size (in bytes) one needs to
do some calculations and estimates on what will be needed too avoid too big buffer - ipseckey is used to enter
key material so the line could become quite long. Say we wanted to keep 1024 lines. If the maximum length of the line is 1024
this will give us 1 megabyte buffer which seems too much for a simple application. Thus I did some guessing
and set the buffer size accordingly:
For "common" ipseckey configuration commands (think moderately bifurcated 'add') it's cca 300 characters.
Mostly however, the users enter query commands like 'flush esp', 'dump ah' and the like so this is somewhere around say
30 characters. Say 30% of the commands are configuration and the rest is queries. To hold 100 such commands only cca 10K
memory is required. In the end I chose 64K to be able to hold 15 of the biggies (4K) commands.