The Kermit Project |
Columbia
University
612 West 115th Street, New York NY 10025 USA • kermit@columbia.edu
| |||||||||
|
Note: | C-Kermit 9.0.301 contains a correction that applies only to Solaris 10 and 11. |
C-Kermit 9.0.302 contains corrections that apply only to FreeBSD 8 and 9. |
This is the third supplement to Using C-Kermit, Second Edition. I apologize for the scattered nature of the information and I hope I can organize it and gather it all into one place for easy and definitive reference some day. It's a big job so it depends on the demand. For the time being the definitive reference and introduction is the book (which is now available also in a Kindle Edition), plus the C-Kermit 7.0 update, C-Kermit 8.0 update, and now this one. Plus tons of other web pages on this site, sample script programs, and so on.
In version 6.0, C-Kermit was a pretty powerful and flexible communication program with scripting capabilities. By version 9.0, I'd like to think of it more as a scripting language with built-in communications. You can get an idea of the kinds of programs you can write in Kermit language here. You can develop programs quickly because it's an interactive program, not a compiler. The scripting language is the command language. Kind of like the Unix shell but “somewhat” less cryptic, including concepts not only from C but from PL/I, Snobol, LISP, Bliss, and Smalltalk. The language itself is built upon the command language of the much-loved DECSYSTEM-20 from the 1970s and 80s, the Clipper Ship of the Text Era. (Text is not a bad word. Those of us who can touch-type and who are proficient in text-based computing environments like Unix shell or VMS DCL are likely to be orders of magnitude more productive than users of GUIs.)
Thanks to (at least) Jeff Altman, William Bader, Ian Beckwith, Nelson Beebe, Gerry Belanger, Joop Boonen, Rob Brown, Christian Corti, Alexey Dokuchaev, John Dunlap, Peter Eichhorn, Carl Friedberg, Terry Kennedy, Günter Knauf, Jason Lehr, Arthur Marsh, Lewis McCarthy, Gary Mills, Ed Ravin, Jonathan Reams, Mike Rechtman, Mark Sapiro, Steven Schweda (SMS), Kinjal Shah, Michael Sokolov, Andy Tanenbaum, Seth Theriault, Zach A. Thomas, Martin Vorländer, and Eric Weaver for assistance, and to Hewlett-Packard Company for support.
– Frank da Cruz fdc@columbia.edu, 30 June 2011
P.S. | It occurred to me just before the end of the day that maybe I should back
up the Kermit website on DVD, just in case. Using
Kermit 95 on the desktop over an SSH connection
to the Unix file system where the website resides, I made a fresh directory
on the PC, CD'd to it, and on Unix cd'd to the Website directory, and told
C-Kermit 9.0 to:
and it re-created the website directory tree in the PC directory, text files correctly converted to Windows format and binary files correctly left as-is. The /dotfiles switch means to include files such as .htaccess whose names start with a dot (period), and the /nobackup switch means to skip backup files created by EMACs (such as index.html.~243~). And then I did the same with the FTP sites, about 8GB in all. Watching the file-transfer display was kind of like having 30 years of my life flash before my eyes in a few minutes. Then I copied the two directories to DVD (the FTP site had to be split over 2 DVDs). The whole operation took under half an hour. The directory tree on the CD is directly usable in Windows, Unix, or any other operating system (unlike if I had transferred the files all in binary mode or all in text mode, or if I had made, say, a gzipped tar archive or a zip archive). I believe that, to this day, Kermit is the only software that can do this. If someday I have to upload from these DVDs to Unix, VMS, or any other operating system, it can be done exactly the same way, with any necessary conversions on text files done automatically, and binary files left intact, recursively through a whole very large directory tree.C-Kermit> send /recursive /dotfiles /nobackup * |
The biggest change since C-Kermit 8.0.211 is support for large files on platforms that support them. A "large file" is one whose size is greater than 231-1 (2,147,483,647) bytes (2GB-1); that is, one whose size requires more than 31 bits to represent. Before now, Kermit was able to access such files only on 100% 64-bit platforms such as Digital Unix, later known as Tru64 Unix. In the new release, Kermit takes advantage of the X/Open Single UNIX Specification Version 2 (UNIX 98) Large File Support (LFS) specification, which allows 32-bit platforms to create, access, and manage files larger than 2GB.
Accommodating large files required code changes in many modules, affecting not only file transfer, but also file management functions from directory listings to local file manipulation, plus the user interface itself to allow entry and display of large numbers. All this had to be done in a way that would not affect pure 32-bit builds on platforms that do not support large files. Large file support is summarized in the Table of Platforms; entries in Yellow (32-bit builds that support 64-bit integers) and Green (64-bit builds) support large files.
Note that VMS C-Kermit and Kermit 95 for Windows have always been able to transfer large files. However their user interface used 32-bit integers for statistics and the file transfer display. In C-Kermit 9.0 Alpha.03, VMS C-Kermit on 64-bit platforms (Alpha and Itanium) should now give correct statistics and progress displays. (We'll see about Kermit 95 later.)
(At remote kermit...)This sends a simulated file 4.3GB in length, that does not exist on the sender and will not take up any disk space on the receiver. SEND /CALIBRATE: accepts big numbers only in Kermit versions that support them (this does not include Kermit 95 on Windows). This method tests only Kermit's ability to express and understand large file sizes, but does not test Kermit's file-system interface, since no files are involved.
$ kermit -Y
C-Kermit> receive /calibrate
(Return to local kermit...)
Ctrl-\c
C-Kermit> send /calibrate:4300000000
S-Expressions can now be forced to operate with integers only, without floating-point conversion or having to explicitly truncate each result; as an example. see the revised Easter date calculation script.
But the Internet is not everywhere, and not all modems are error-correcting. Perhaps the most difficult trial so far for Kermit or any other protocol is the EM-APEX project, in which floats are dropped into the ocean from an aircraft into the path of a hurricane; these floats dive into the water measuring current, temperature, and salinity at different depths and then surface to phone home, sending the data to land stations using Kermit protocol over non-error-correcting 300bps Iridium satellite modems, with high seas and winds battering the floats and heavy (sometimes electrical) storms between the modem and the satellite.
Because of the transmission speed and long distances involved, the transfers were very slow. The Kermit software in the floats is Embedded Kermit, which did not implement sliding windows, which would have sped up the flow considerably. John Dunlap, engineer at the University of Washington's Applied Physics Laboratory, undertook the task of adding sliding windows to E-Kermit. For testing, he rigged up a simulator in which Kermit transfers take place over a connection with different amounts of noise and delay. He found that occasionally, a transfer would appear to succeed, but the received file would be corrupt.
According to the Kermit protocol definition, the first packet always has block-check type 1, a 6-bit checksum, which is the only block check type that all Kermit implementations are required to support; thus any Kermit partner can process this packet. This packet itself can negotiate a higher level of checking, such that subsequent packets have (say) block-check type 3, a 16-bit cyclic redundancy check (CRC) encoded as three printable 7-bit ASCII characters. The 16-bit CRC can catch all errors of certain kinds (single-bit, double-bit, bursts of 16 bits or less), and more than 99.9984741210937% of all other possible errors.
John's simulations revealed that file corruption could occur undetected when the initial packet was corrupted in such a way that a parameter or capability byte was changed and the checksum also changed to make the packet appear to be correct, thus allowing the transfer to proceed with the two Kermit partners out of sync as to packet encoding and interpretation (the chances of two such errors producing a seemingly valid packet are no better than 1 in 6000 when using the 6-bit checksum, and actually even smaller than that because each parameter is range-checked). For example, the compression technique might be misnegotiated and then the receiver might store incoming data without decompressing it.
The solution is a new option, selected by:
BLOCK-CHECK TYPE 5
to require a type 3 block check (16-bit CRC) on every packet, including the initial ones, thus reducing the probability of a misnegotiation by many orders of magnitude. THIS PARAMETER CAN NOT BE NEGOTIATED. Each Kermit program must be given the "set block 5" command prior to transfer. That's because normally every Kermit program expects the first packet to have a 6-bit checksum, and if the first packet has a 3-byte, 16-bit CRC, the packet receiver will think it is corrupted.
In practice, however, it is possible to code the packet receiver "cheat" by reading the packet data before verifying the block check. Thus when the receiver is C-Kermit 9.0 or later or E-Kermit 1.7 or later, it is only necessary to give the "set block 5" command to the file sender, and the receiver will check for a FORCE-3 first packet. If the receiver does not support this feature, however, the initial packet will be be rejected (after several retries) and the file transfer will not take place. There is no attempt to "back off" to normal behavior.
Table 4. Kermit Protocol Packet Block Check Types Type Command Bytes Status Explanation 1 SET BLOCK 1 1 Required in all Kermit implementations. Negotiated. 6-bit checksum, suitable for good connections. 2 SET BLOCK 2 2 Optional, negotiated. 12-bit checksum. 64 times stronger than type 1. 3 SET BLOCK 3 3 Optional, negotiated. 16-bit CRC. BLANK-FREE-2 SET BLOCK 4 2 Optional, negotiated. 12-bit checksum, two nonblank bytes. FORCE-3 SET BLOCK 5 3 Optional, not negotiated. 16-bit CRC forced all packets.
BLANK-FREE-2 is for environments where Kermit packets are treated as lines of text, and in which trailing blanks can be stripped; for example, when transferring files with an IBM mainframe through a 3270 protocol converter.
Does the strange behavior of Kermit's \%x variables puzzle or annoy you?
Kermit software development has been a collaborative project over the years, with contributions coming in from almost every country and every sector of the economy – academic, corporate, government. Thus not all versions, and not all features of a given version, are a product of systematic design.
One example was the introduction of variables for text substitution, first in a version of MS-DOS Kermit that was sent in by someone somewhere (I could look it up, but no time...) Although the design of the notation for variable names (table below) is mine, the underlying code was contributed. In that code there was only one kind of variable, and if I recall correctly the variable name was a backslash followed by a single letter, for example \a, \b, etc. The contributed code evaluated these variables recursively, meaning if the definition of a variable contained variable references, then these were resolved when dereferencing the variable, and the process would continue as deep down as necessary to resolve the thing fully.
This was sometimes handy, but it had one severe drawback: There was no way to use variables in a straightforward way to represent strings that contained literal backslashes; for example, DOS or Windows pathnames. This gave rise to all kinds of quoting rules and conventions (e.g. doubling backslashes or forcing single-level evaluation with \\fcontents()), and also to the introduction of other kinds of variables that were evaluated one level deep, rather than recursively.
To accommodate coexistence of different kinds of variables as well as "escape sequences" for representing control and 8-bit characters, the syntax for variable names was extended to include three elements: the leading backslash, then a single character indicating the type of variable, and then the name of the variable in a format corresponding to the type designator, as shown in this somewhat simplified table:
Table 1. Variable-name Syntax in Kermit Notation Meaning \000 – \255 8-bit character constant (decimal) \d000 – \d255 Alternative notation for 8-bit character (byte) constant (decimal) \o000 – \o377 8-bit character constant (octal) \x00 – \xff 8-bit character constant (hexadecimal) \%a – \%z Scalar variable, evaluated recursively. \%0 – \%9 Macro argument, scalar, evaluated recursively. \&a – \%& Array name \&a[x] Array reference, evaluated recursively (x is any constant or variable) \v(name) Built-in scalar variable, evaluated one level deep. \m(name) User-defined scalar variable, evaluated one level deep. \$(name) An environment variable, evaluated one level deep. \s(name[n:m]) Compact substring notation, evaluated one level deep. \fname(args...) Built-in function with zero or more arguments. \\ Literal backslash \N OUTPUT command only: NUL, ASCII 0 \B OUTPUT command only: BREAK (250ms, for serial connections) \L OUTPUT command only: Long BREAK (1.5sec, ditto)
Variable names in Kermit are case-independent. The simplifications in the table are that the notation for decimal and octal bytes can have from one to three digits, and can include braces to separate them from text digits, e.g. \7, \{123}, \o{50}. Hex bytes too, except they must always have exactly two hex digits, 0-9a-f. Array indices must be, or must evaluate to, numbers (floating point numbers are truncated). Associative arrays are also available (dynamic arrays with arbitrary text as subscript), but they are really just a variation on \m() variables (read about associative arrays here). Also, there are some alternative notations for compact substring notation.
We didn't want to have lots of "distinguished" characters, as the UNIX shell does; one is enough, clarity over brevity. Although the notation can be a bit cumbersome, we can use the \m(name) form to circumvent the overevaluation in most contexts. But macro arguments are always assigned to the \%0-9 variables, and thus always evaluated recursively, making it difficult and confusing to pass (e.g.) Windows pathnames as arguments to macros. The same is true for array elements, especially in contexts where they are used to return results from built-in functions (for example, \fsplit() used to return the elements of a comma-separated value list if any of the values contained backslashes). An even worse scenario is when macro arguments are passed from one macro to another; for some graphic illustrations see Taming the Wild Backslash – Part Deux from the C-Kermit 7.0 Update Notes.
We can't just change how variables are evaluated because that would break existing scripts. But we can always add Yet Another SET Command:
SET COMMAND VARIABLE-EVALUATION { RECURSIVE, SIMPLE }This applies only to \%a-z and \%0-9 variables and to \&a-z[] arrays (since all other kinds of variables are evaluated only one level deep). The default, of course, for backwards compatibility, is RECURSIVE. SIMPLE forces the evaluation of these variables to return their literal contents, without further evaluation:
def \%a 1 \%b 3 def \%b 2 def xx easy as \%a show mac xx echo \frecurse(\m(xx)) easy as 1 2 3 echo \frecurse(it's as easy as \m(xx)) it's as easy as easy as 1 2 3
Here's a short script for illustration:
define path c:\users\fdc\somefile.txt define test1 { # Normal recursive argument evaluation echo \%0: arg=\%1 } define test2 { # Simple argument evaluation set var simple echo \%0: arg=\%1 } test1 \m(path) test2 \m(path) exit
And here's the result:
?<ERROR:NO_SUCH_FUNCTION:\fdc\somefile.txt()> test2: arg=c:\users\fdc\somefile.txt
The first line might seem surprising, but under the normal rules (see table above) \f indicates a function call, with the letters following the 'f' being the name of the function. But there is no function by that name... and if there were, you probably didn't intend to call it!
SET COMMAND VARIABLE-EVALUATION SIMPLE has no effect on constants, only on variables. Note how \m(path) is defined. The DEFINE command assigns the literal value of its argument to the named variable (see Table 3 below), thus in this case no special syntax is needed. But in other contexts, you must double the backslashes or use the \fliteral() function to use literal backslashes in data:
test2 c:\\users\\fdc\\somefile.txt test2 \fliteral(c:\users\fdc\somefile.txt)
C-Kermit 9.0 adds a new notation for \fliteral() which also has certain advantages over it: \q(string):
test2 \q(c:\users\fdc\somefile.txt)
Since \fliteral() is a function, its argument list (the text within parentheses) has special syntax of its own, in which commas and braces are treated specially and introduce another set of quoting problems. \q(string) doesn't have these problems. The only consideration is that parentheses must be balanced or else quoted (preceded by backslash), or represented as numeric character entities (left paren = \40, (right paren = \41).
Or else hold the value in a simple variable as we did with \\m(path) above.
SET COMMAND VARIABLE-EVALUATION SIMPLE is a big change and might have repercussions that didn't show up in the initial tests; a lot more testing is needed.
On the topic of variables, let's summarize in one place the ways in which values can be explicitly assigned to variables. There is nothing new here except the table itself:
Table 2. Variable Assignment in Kermit Command Shorthand Explanation DEFINE name value .name = value The literal value becomes the contents of the named variable; variables names in the value are copied without evaluation. This command is for defining macros that take parameters, as well as for defining simple variables, especially if the values contain backslashes. _DEFINE name value Like DEFINE but the name is evaluated before use. ASSIGN name value .name := value The value is evaluated and the result becomes the contents of the named variable. _ASSIGN name value Like ASSIGN but the name is evaluated before use. EVALUATE name expression .name ::= value The expression (in regular algebraic notation) is evaluated arithmetically and the result becomes the contents of the named variable. If the expression contains any variables they are evaluated first. _EVALUATE name expression Like EVALUATE but the name is evaluated before use. INCREMENT name expression Evaluates the variables in the expression, then evaluates the expression arithmetically, and then adds the value to the contents of the named variable, which must be a number or an algebraic expression. If the expression is empty, a value of 1 is used. _INCREMENT name expression Like INCREMENT but the name is evaluated before use. DECREMENT name expression Evaluates the variables in the expression, then evaluates the expression arithmetically, and then subtracts the value from the contents of the named variable, which must be a number or an algebraic expression. If the expression is empty, a value of 1 is used. _DECREMENT name expression Like DECREMENT but the name is evaluated before use. DECLARE name = list An array declaration can include an initializer list; items in the list are evaluated before assignment. This can be defeated by doubling any backslashes or enclosing individual arguments in \fliteral(). DO name arguments name arguments When invoking a macro with a DO command (or an implied one), the arguments are evaluated, then assigned to \%1, \%2, etc, and the macro's name to \%0. (SETQ name value) Kermit also includes a mini-LISP interpreter
Variables are evaluated automatically in Kermit commands simply by referencing them, according to rules given in Table 1. The following functions can be used to change how a a particular variable is evaluated:
Table 3. Kermit Functions for Evaluating Variables Function Argument Description \fcontents() \%x or \&x[y] Evaluates the variable or array element (which normally would be evaluated recursively) one level deep. \fdefinition() name If the argument is a \%x variable or an array element, it is evaluated to get the name; otherwise the argument is the name. Its definition is returned with no recursion. \m() name Equivalent to \fdefinition(). \frecurse() \m(name) Forces recursive evaluation of a macro definition (a.k.a. long variable name). NOTE: \frecurse() can operate on any kind of variable as well as on any string containing any mixture of variables.
Traditionally, the optional switches have been:
Reminder: Every switch starts with a slash (/) and must be preceded by a space.
/LOWER: Convert the filename to lowercase /UPPER: Convert the filename to uppercase /CONVERT: Change the filename's character encoding /REPLACE: Do string substitutions on the filename
If the source-file specification includes a path or directory, any changes are applied to the filenames only, not to the directory or path specification.
Since name changes, when applied to many files at once, can have consequences that are not easily undone, there are also some new controls, safeguards, and conveniences:
Reminder: In switches such as /COLLISION that take arguments (operands), the switch name and its argument(s) are separated by a colon (:) with no intervening spaces. Also remember that Kermit keywords can always be abbreviated by leaving off characters from the right, as long as the result is still unique in its context. Thus "ren /col:f" would be equivalent to "rename /collision:fail".
You can change the following preferences for the RENAME command with the new SET RENAME command:
By default, all files that match the given filespec have their names changed (if necessary). This is what the ALL argument means, e.g.:
RENAME /LOWER:ALL *
RENAME /LOWER *
You can use either form: RENAME /LOWER is equivalent to RENAME /LOWER:ALL. The other argument (/LOWER:UPPER or /UPPER:LOWER) means to leave mixed-case filenames alone, and rename only those files whose names contain letters of only the given case. Examples:
Case conversion works reliably for ASCII characters only. Kermit uses the C library for this, which on any given platform might or might not handle non-ASCII letters, and if it does, then how it works would normally depend on your locale definitions (the LC_CTYPE and/or LANG environment variable in Unix). When non-ASCII letters are not handled by the C library, the RENAME command does change their case. For example, Olga_Tañón.txt might become OLGA_TAñóN.TXT.
RENAME /FIX *
RENAME /FIXSPACES:_ *
RENAME /FIXSPACES:"" *
RENAME /FIXSPACES:<040> *
The first two are equivalent, replacing each space with underscore; a file called "My Favorite Photo.jpg" becomes "My_Favorite_Photo.jpg". The third example removes all spaces ("MyFavoritePhoto.jpg"). The fourth replaces each space with the string "<040>" ("My<040>Favorite<040>Photo.jpg").
RENAME /REPLACE:{{.jpeg}{.jpg}} *
changes all *.jpeg files to *.jpg.
For greater control and flexibility, the /REPLACE: switch argument can take several distinct forms:
|
The tilde modifier works only with single-byte character sets such as ASCII, CP437, ISO 8859-1, etc, but not with multibyte character sets such as UCS2, UTF8, or any of the Japanese Kanji sets.
Here are some examples showing how to use the /REPLACE options:
In the Options field, digits (and their modifiers), ^, and $ are mutually exclusive. If you include more than one of these in the option string, only the last one is used. Similarly for 'a' and 'A':
If you give an /UPPER or /LOWER switch and a /REPLACE switch in the same RENAME command, the /REPLACE action occurs first, then the case conversion:
RENAME /CONVERT:latin1:utf8 *
/CONVERT can not be combined with /UPPER, /LOWER, or /REPLACE.
You should NOT use UCS2 for filenames since this encoding is not compatible with C strings used in Unix and elsewhere.
RENAME /CONVERT affects only the filename, not the file's contents. You can use the TRANSLATE command to convert the encoding of the contents of a text file.
foption in P1.
dir /top:10 /sort:size /reverse *or equivalently, "hdir /top:10 *". WDIR lists files in reverse chronological order, shorthand for "dir /sort:date /reverse".
quantity description...in which the first "word" is a number, followed by a description (for example, the name of an item). Here is how to use FSEEK to quickly get the total quantity of any given item, which is passed as a parameter (either a literal string or a pattern) on the command line:
The syntax of the FSEEK command in this example indicates that each search should start relative to the current file line. Since Kermit is an interpretive language, FSEEK is a lot faster than FREAD'ing each line and checking it for the target, especially for big files. An especially handy use for FSEEK is for use with potentially huge sequentially timestamped logs, to seek directly to the date-time where you want to start processing. Some other improvements for the FOPEN/FREAD/FWRITE/FCLOSE family of commands are included also (performance, bug fixes, convenience features), listed in the change log. (Prior to 9.0.299 Alpha.02, the FSEEK /FIND: command always started from the top.)#!/usr/local/bin/kermit + if not def \%1 exit 1 Usage: \fbasename(\%0) string-or-pattern .filename = /usr/local/data/items.log # Substitute the actual filename set case off # Searches are case-independent fopen /read \%c \m(filename) # Open the file if fail exit 1 "\m(filename): \v(errstring)" # Fail: exit with error message .total = 0 # OK: Initialize the total echo Searching "\%1"... while true { fseek /line /relative /find:\%1 \%c 0 # Get next line that has target if fail break # Failure indicates EOF fread /line \%c line # Read it if fail break # (shouldn't happen) increment total \fword(\m(line),1) # Increment the total } fclose \%c # Close the file echo Total for "\%1" : \m(total) # Print the result exit 0
Based on code inspection, C-Kermit appears to have an SSL-related security vulnerability analogous to that identified as CVE-2009-3767 (see e.g. http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3767).I'm attaching a patch for this issue relative to the revision of ck_ssl.c obtained from a copy of http://www.columbia.edu/kermit/ftp/test/tar/x.zip downloaded on 2010/07/30, which I believe is the latest.
When this flaw was first widely publicized at last year's Black Hat conference, it was claimed that some public certificate authorities had indeed issued certificates that could be used to exploit this class of vulnerability. As far as I know they have not revealed specifically which public CA(s) had been found issuing such certificates. Some references:
For the motivation for these features and an application that uses them to analyze web logs, see the Weblog script below.
Of course the old way still works too. But watch out because if the variable name is the same as a symbolic IF condition (for example COUNT), it won't do what you expected. (IF COUNT was used for loop control in early versions of MS-DOS Kermit, before it got real FOR and WHILE loops; it was added to C-Kermit for compatibility, and it can't be removed because that could break existing scripts).
$ @ckvker "" "" "CK_SSL"the OpenSSL version is detected automatically and the appropriate compile-time options are emitted (such as OPENSSL_DISABLE_OLD_DES_SUPPORT).
There's one exception this time. The \fsplit() function is incredibly handy, it can do almost anything, up to and including parsing a LISP program (the underlying code is the basis of the S-Expression interpreter). But did you ever try to use it to parse (say) a Tab-Separated-List (TSV file) or Comma-Separated-List (CSV)? It works as expected as long as the data contains only 7-bit characters. But if your data contains (say) Spanish or German or Russian text written in an 8-bit character set such as ISO 8859-1, every 8-bit character (any value 128-255) is treated as a break character. This is fixed in C-Kermit 9.0 by treating all 8-bit bytes as "include" characters rather than break characters, a total reversal of past behavior. I don't think it will affect anyone though, because if this had happened to anyone, I would have heard about it!
Since most standard 8-bit character sets have control characters in positions 128-160, it might have made sense to keep 128-160 in the break set, but with the proliferation of Microsoft Windows code pages, there is no telling which 8-bit character is likely to be some kind of text, e.g. “smart quotes” or East European or Turkish accented letters.
That's because as coded (through 8.0.211), C-Kermit simply starts the external protocol in a fork with its standard i/o redirected to the connection. This completely bypasses the encryption and decryption that is done by C-Kermit itself, and of course it doesn't work. The same thing occurs if you use the REDIRECT command. The routine that handles this is ttruncmd() in ckutio.c.
In order to allow (say) Zmodem transfers on secure connections, it is necessary for C-Kermit to interpose itself between the external Zmodem program and the connection, decrypting the incoming stream before feeding it to Zmodem and encrypting Zmodem's output before sending out the connection.
In principal, this is simple enough. We open a pseudoterminal pair ("master" and "slave") for Zmodem's i/o and we create a fork and start Zmodem in it; we read from the fork pty's standard output, encrypt, and send to the net; we read from the net, decrypt, and write to the fork pty's standard input.
In practice, it's not so simple. First of all, pseudoterminals (ptys) don't seem to interface correctly with certain crucial APIs, at least not in the OS's I have tried (Mac OS X, Linux, NetBSD, etc), such as select(). And i/o with the pty often – perhaps always – fails to indicate errors when they occur; for example, when the fork has exited.
But, even after coding around the apparent uselessness of select() for multiplexing pty and net, and using various tricks to detect when the external protocol exits and what its exit status is, I'm still left with a show-stopping problem: I just simply can not download (receive) a file with Zmodem, which is the main thing that people would probably want to do. I can send files just fine, but not receive. The incoming stream is delivered to Zmodem (to the pty slave) but upon arrival at the Zmodem process itself, pieces are always missing and/or corrupt. Yet I can receive files just fine if I use Kermit itself (C-Kermit or G-Kermit) as the external protocol, rather than Zmodem.
I can think of two reasons why this might be the case:
But Zmodem puts its controlling terminal into raw mode. And C-Kermit puts the pty into raw mode too, just for good measure. If any 0xFF codes are in the Zmodem data stream, and it's a Telnet session, Kermit does any needed byte stuffing/unstuffing automatically. Anyway, if I tell Zmodem to prefix everything, it makes no difference.
I can vary the size of the i/o buffers used for writing to the pty, and get different effects, but I am not able to get a clean download, no matter what buffer size I use. write()'ing to the pty does not return an error, and I can't see the errors because they happen on the master side. It's as if the path between the pty slave and master lacks flow control; I deliver a valid data stream to the pty slave and the master gets bits and pieces. This impression is bolstered somewhat by the "man 7 pty" page in HP-UX, which talks about some special modes for ptys that turn off all termio processing and guarantee a flow-controlled reliable stream of bytes in both directions – a feature that seems to be specific to HP-UX, and exactly the one we need everywhere.
Well, in Pass One I used C-Kermit's existing pty routines from ckupty.[ch], which are well-proven in terms of portability and of actually working. They are currently used by SET HOST /PTY for making terminal connections to external processes. But these routines are written on the assumption that the pty is to be accessed interactively, and maybe they are setting the fork/pty arrangement up in such a way that that's not suitable for file transfer. The Pass One routine is called xttptycmd() in ckutio.c.
So in Pass Two I made a second copy of the routine, yttptycmd(), that manages the pty and fork itself, so all the code is in one place and it's simple and understandable. But it still doesn't work for Zmodem downloads. In this routine, I use openpty() to get the pty pair, which is not portable, so I can have access to both the master and slave pty file descriptors. This version can be used only a platforms that have openpty(): Linux, Mac OS X, NetBSD, etc.
In Pass Three, zttptycmd(), I tried using pipes instead of ptys, in case ptys are simply not up to this task (but that can't be true because if I make a Telnet or SSH connection into a host, I can send files to it with Zmodem, and the remote Zmodem receiver is, indeed, running on a pty). But pipes didn't work either.
In Pass Four, I extracted the relevant routines into a standalone program based on yttptycmd() (the openpty() version, for simplicity), which I tested on Mac OS X, the idea being to rule out any "environmental" effects of running inside the C-Kermit process. There was no difference -- Kermit transfers (with C-Kermit itself as the external protocol) worked; Zmodem transfers (neither sz or lsz) did not.
Well, it's a much longer story. As the external protocol, I've tried rzsz, crzsz, and lrzsz. We know that some of these have quirks regarding standard i/o, etc, which is one of the reasons for using ptys in the first place, and i/o does work – just not reliably. Anyway, the 1100 lines or so of ckc299.txt, starting just below where it says "--- Dev.27 ---" tell the full story. At this point I have to give up and move on; it might be more productive to let somebody else who has more experience with ptys take a look at it – if indeed anyone still cares about being able to do Zmodem transfers over secure Telnet connections.
C-Kermit 9.0 contains the three new routines (and some auxiliary ones), but they are not compiled or called unless you build it specially:
make targetname KFLAGS=-DXTTPTYCMD (builds with xttptycmd())These are all in ckutio.c. As noted, the second one works only for Linux, FreeBSD, NetBSD, and Mac OS X, because it uses non-POSIX, non-portable openpty(). If you want to try it on some other platform that has openpty(), you can build it like this:
make targetname KFLAGS=-DYTTPTYCMD (builds with yttptycmd())
make targetname KFLAGS=-DZTTPTYCMD (builds with zttptycmd())
make targetname "KFLAGS=-DYTTPTYCMD -DHAVE_OPENPTY"(and let me know, so I can have HAVE_OPENPTY predefined for that platform too). The best strategy to get this working, I think, would be to concentrate on yttptycmd(), which is the simpler of the two pty-based routines. If it can be made to work, then we'll see if we can retrofit it to use the ckupty.c routines so it will be portable to non-BSD platforms.
By the way, if you build with any of [XYZ]TTPTYCMD defined, then the selected routine will always be used in place of ttruncmd(). This is to allow testing on all kinds of connections, not just secure ones, in both local and remote mode. Once the thing works, if it ever does, I'll add the appropriate tests and/or commands.
By default, in the initial test release, C-Kermit 9.0 uses ttruncmd() on serial connections and ttyptycmd() on network connections. Even when a network connection is not encrypted, Kermit still needs to handle the network protocol, e.g. the quoting of 0xff bytes on Telnet connections.
Typically a backup can be done by making a Telnet, SSH, or serial connection to the device with Kermit and giving a command such as "show config" at the command-line prompt of the device with Kermit's session log activated. The result is a list of the commands that were used to establish the current configuration, suitable for feeding back to the device's console (e.g. with C-Kermit's TRANSMIT command) to reestablish the same configuration or to duplicate it on another device.
At an HP installation it was noted, however, that while the HP switches (various ProCurve models) produced the desired list of commands, they were interspersed with escape sequences for special effects, thus rendering the recorded sessions unsuitable for feeding back into the switches.
C-Kermit 9.0 introduces a new feature to strip the offending sequences out of a session log, leaving just the text. The command SET SESSION-LOG TEXT activates this feature. In C-Kermit 9.0 Alpha.02 and earlier, escape sequence stripping occurred only while logging interactive (CONNECT) sessions; beginning with Alpha.03 it is done also for data that is read by INPUT commands and therefore works for scripts too.
A sample HP Switch Configuration Backup script is HERE, and its data file is HERE. This script also illustrates some other new features of Alpha.03:
$ DEBUG=1 scriptname arg1 arg2...and then include the following command in your script:
if defined \$(DEBUG) set debug message on
which, if the SET HOST command fails, prints "FATAL - set host somehost.somecompany.com" and then exits with status 1 (which normally indicates failure).set host somehost.somecompany.com if fail exit 1 "FATAL - \v(lastcommand)"
* | Of course the University is deploying new technology but the but the old system will be used in parallel for some time to come. |
Comma-Separated Value (CSV) format is commonly output by spreadsheets and databases when exporting data into plain-text files for import into other applications. Here are the details:
Here is an example:
The first two are regular fields. The second is a field that has an embedded space but in which any leading or trailing spaces are to be ignored. The fourth is an empty field, but still a field. The fifth is a field that contains embedded commas. The sixth has leading and trailing spaces. The last field has embedded quotation marks.aaa, bbb, has spaces,,"ddd,eee,fff", " has spaces ","Muhammad ""The Greatest"" Ali"
Prior to C-Kermit 9.0 Alpha.06, C-Kermit did not handle CSV files according to the specification above. Most seriously, there was no provision for a separator to be surrounded by whitespace that was to be considered part of the separator. Also there was no provision for quoting doublequotes inside of a quoted string.
gives the following results:def xx { echo [\fcontents(\%1)] .\%9 := \fsplit(\fcontents(\%1), &a, \44, CSV) for \%i 1 \%9 1 { echo "\flpad(\%i,3). [\&a[\%i]]" } echo "-----------" } xx {a,b,c} xx { a , b , c } xx { aaa,,ccc," with spaces ",zzz } xx { "1","2","3","","5" } xx { this is a single field } xx { this is one field, " and this is another " } xx { name,"Mohammad ""The Greatest"" Ali", age, 67 } xx { """field enclosed in doublequotes""" } exit
[a,b,c] 1. [a] 2. [b] 3. [c] ----------- [ a , b , c ] 1. [a] 2. [b] 3. [c] ----------- [ aaa,,ccc," with spaces ",zzz ] 1. [aaa] 2. [] 3. [ccc] 4. [ with spaces ] 5. [zzz] ----------- [ "1","2","3","","5" ] 1. [1] 2. [2] 3. [3] 4. [] 5. [5] ----------- [ this is a single field ] 1. [this is a single field] ----------- [ this is one field, " and this is another " ] 1. [this is one field] 2. [ and this is another ] ----------- [ name,"Mohammad ""The Greatest"" Ali", age, 67 ] 1. [name] 2. [Mohammad "The Greatest" Ali] 3. [age] 4. [67] ----------- [ """field enclosed in doublequotes""" ] 1. ["field enclosed in doublequotes"] -----------
The separator \44 (comma) must still be specified as the break set (3rd \fsplit() parameter). When “CSV” is specified as the include set:
Of course you can specify any separator(s) you want with either the CSV, TSV, or ALL symbolic include sets. For example, if you have a TSV file in which you want the spaces around each Tab to be discarded, you can use:
\9 is Tab.\fsplit(variable, &a, \9, CSV)
The new symbolic include sets can also be used with \fword(), which is just like \fsplit() except that it retrieves the nth word from the argument string, rather than an array of all the words. In C-Kermit you can get information about these or any other functions with the HELP FUNCTION command, e.g.:
C-Kermit> help func word Function \fword(s1,n1,s2,s3,n2,n3) - Extracts a word from a string. s1 = source string. n1 = word number (1-based) counting from left; if negative, from right. s2 = optional break set. s3 = optional include set (or ALL, CSV, or TSV). n2 = optional grouping mask. n3 = optional separator flag: 0 = collapse adjacent separators; 1 = don't collapse adjacent separators. \fword() returns the n1th "word" of the string s1, according to the criteria specified by the other parameters. The BREAK SET is the set of all characters that separate words. The default break set is all characters except ASCII letters and digits. ASCII (C0) control characters are treated as break characters by default, as are spacing and punctuation characters, brackets, and so on, and all 8-bit characters. The INCLUDE SET is the set of characters that are to be treated as parts of words even though they normally would be separators. The default include set is empty. Three special symbolic include sets are also allowed: ALL (meaning include all bytes that are not in the break set) CSV (special treatment for Comma-Separated-Value records) TSV (special treatment for Tab-Separated-Value records) For operating on 8-bit character sets, the include set should be ALL. If the GROUPING MASK is given and is nonzero, words can be grouped by quotes or brackets selected by the sum of the following: 1 = doublequotes: "a b c" 2 = braces: {a b c} 4 = apostrophes: 'a b c' 8 = parentheses: (a b c) 16 = square brackets: [a b c] 32 = angle brackets: <a b c> Nesting is possible with {}()[]<> but not with quotes or apostrophes. Returns string: Word number n1, if there is one, otherwise an empty string. Also see: HELP FUNCTION SPLIT C-Kermit>
Previous calling conventions for \fjoin() are undisturbed, including the ability to specify a portion of an array, rather than the whole array:
declare \&a[] = 1 2 3 4 5 6 7 8 9 echo \fjoin(&a[3:7],CSV) 3,4,5,6,7
Using \fsplit() and \fjoin() it is now possible to convert a comma-separated value list into a tab-separated value list, and vice versa (which is not a simple matter of changing commas to tabs or vice versa).
Here is a simple example in which we purge all records of customers who have two or more unpaid bills. The file is sorted so that each license purchase record is followed by its annual maintenance payment records in chronological order.
#!/usr/local/bin/kermit .filename = somefile.csv # Input file in CSV format fopen /read \%c \m(filename) # Open it if fail exit # Don't go on if open failed copy \m(filename) ./new # Make a copy of the file .oldserial = 00000000000 # Multiple records for each serial number .zeros = 0 # Unpaid bill counter while true { # Loop fread /line \%c line # Get a record if fail exit # End of file .n := \fsplit(\m(line),&a,\44,CSV) # Split the fields into an array if not equ "\m(oldserial)" "\&a[6]" { # Have new serial number? # Remove all records for previous serial number # if two or more bills were not paid... if > \m(zeros) 1 { grep /nomatch \m(oldserial) /output:./new2 ./new rename ./new2 ./new } .oldserial := \&a[6] # To detect next time serial number changes .zeros = 0 # Reset unpaid bill counter } if equ "\&a[5]" "$0.00" { # Element 5 is amount paid increment zeros # If it's zero, count it. } } fclose \%c
Rewriting the file multiple times is inelegant, but this is a quick and dirty use-once-and-discard script, so elegance doesn't count. The example is interesting in that it purges certain records based on the contents of other records. Maybe there is a way to do this directly with SQL, but why use SQL when you can use Kermit?
Here is the same task but this time no shelling out, and this time we do change and add some fields and then join the result back into a CSV record and write it out to a new file. The object is to create a record for each license that shows not only the date and purchase price of the license but also the date and amount of the last maintenance payment, and to add new fields for sorting by anniversary (month and day):
#!usr/local/bin/kermit + cd ~/somedirectory # CD to appropriate directory if fail exit 1 # Make sure we did .filename := \%1 # Filename from command line if not def filename { # If none give usage message exit 1 "Usage: \%0: infile [ outfile ]" } fopen /read \%c \m(filename) # Open the input CSV file if fail exit # Make sure we did .output := \%2 # Output filename from command line if not def output { # Supply one if not given .output := New_\m(filename) } fopen /write \%o \m(output) # Open output file if fail exit # Check that we did .serial = 00000000000 # Initialize serial number .licenses = 0 # and license counter fread /line \%c line # First line is column labels if fail exit # Check fwrite /line \%o "\m(line),AMM_DD,AYYYY" # Write new labels line # Remaining lines are license purchases (K95B) followed by zero or more # maintenance invoices (K95BM) for each license. .datepaid = 00/00/0000 # Initialize last maint payment date .amtpaid = $0.00 # Initialize last maint payment amount set flag off # For remembering we're at end of file while not flag { # Loop to read all records fread /line \%c line # Read a record if fail set flag on # If EOF set flag for later .n := \fsplit(\m(line),&a,\44,CSV) # Break record into array if ( flag || equ "\&a[3]" "K95B" ) { # License or EOF if fail exit 1 "FAILED: \v(lastcommand)" if licenses { # If this is not the first license .\&x[5] := \m(amtpaid) # Substitute most recent amount paid .\&x[21] := \m(datepaid) # Substitute most recent date paid void \fsplit(\&x[18],&d,/) # Break up original (anniversary) date # and put mm_dd and yyyy in separate fields for sorting... fwrite /line \%o "\fjoin(&x,CSV),\flpad(\&d[1],2,0)_\flpad(\&d[2],2,0),\&d[3]" if fail exit 1 WRITE # Check for error xecho . # Show progress as one dot per record } if flag break # We're at EOF so we're finished increment licenses # New license - count it array copy &a &x # Keep this record while reading next .serial := \&a[6] # Remember serial number .datepaid = 00/00/0000 # Initial maintenance payment date .amtpaid = $0.00 # and amount continue # and go back to read next record } if not eq "\m(serial)" "\&a[6]" { # Catch out-of-sequence record echo echo "SEQUENCE: \m(serial)..\&a[6]: \&a[7] [\&a[1]]" continue } if equ "\&a[5]" "" .\&a[5] = $0.00 # If amount is empty make it $0.00 if not equ "\&a[5]" "$0.00" { # If amount is not $0.00 .datepaid := \&a[21] # remember date paid .amtpaid := \&a[5] # and amount paid } } fclose ALL # Done - close all files and exit exit 0 Done.
The result imports back into Excel, where it can be sorted, formatted, or otherwise manipulated as desired.
Kermit has several built-in data types, but you can invent your own data types as needed using Kermit's macro feature:
For example:define variablename value
This defines a macro named alphabet and gives it the value abcdefghijklmnopqrstuvwxyz. A more convenient notation (added in C-Kermit 7.0, see Table 2) for this is:define alphabet abcdefghijklmnopqrstuvwxyz
The two are exactly equivalent: they make a literal copy the "right hand side" as the value of the macro. Then you can refer to the macro anywhere in a Kermit command as "\m(macroname)":.alphabet = abcdefghijklmnopqrstuvwxyz
There is a second way to define a macro, which is like the first except that the right-hand side is evaluated first; that is, any variable references or function calls in the right-hand side are replaced by their values before the result is assigned to the macro. The command for this is ASSIGN rather than DEFINE:echo "Alphabet = \m(alphabet)"
which prints:define alphabet abcdefghijklmnopqrstuvwxyz assign backwards \freverse(\m(alphabet)) echo "Alphabet backwards = \m(backwards)"
This kind of assignment can also be done like this:Alphabet backwards = zyxwvutsrqponmlkjihgfedcba
Any command starting with a period is an assignment, and the operator (= or :=) tells what to do with the right-hand side before making the assignment..alphabet = abcdefghijklmnopqrstuvwxyz .backwards := \freverse(\m(alphabet))
In both the DEFINE and ASSIGN commands, the variable name itself is taken literally. It is also possible, however, to have Kermit compute the variable name. This is done (as described in Using C-Kermit, 2nd Ed., p.457), using parallel commands that start with underscore: _DEFINE and _ASSIGN (alias _DEF and _ASG). These are just like DEFINE and ASSIGN except they evaluate the variable name before making the assignment. For example:
would create a macro named ONEONEONE with a value of 111, and:define \%a one _define \%a\%a\%a 111
would create the same macro with the same value, but:define \%a one define number 111 _assign \%a\%a\%a \m(number)
would give the macro a value of "\m(number)".define \%a one define number 111 _define \%a\%a\%a \m(number)
You can use the _ASSIGN command to create any kind of data structure you want; you can find some examples in the Object-Oriented Programming section of the Kermit Script Library. In the following program we use this capability to create a two-dimensional array, or matrix, to hold the all the elements of the CSV file, and then to display the matrix:
The matrix is called a and its elements are a[1][1], a[1][2], a[1][3], ... a[2][1], etc, and you can treat this data structure exactly like a two-dimensional array, in which you can refer to any element by its "X and Y coordinates". For example, if the CSV file contained numeric data you could compute row and column sums using simple FOR loops and Kermit's built-in one-dimensional array data type:fopen /read \%c data.csv # Open CSV file if fail exit 1 .\%r = 0 # Row .\%m = 0 # Maximum columns while true { fread /line \%c line # Read a record if fail break # End of file .\%n := \fsplit(\m(line),&a,\44,CSV) # Split record into items incr \%r # Count this row for \%i 1 \%n 1 { # Assign items to this row of matrix _asg a[\%r][\%i] \&a[\%i] } if > \%i \%m { .\%m := \%i } # Remember width of widest row } fclose \%c # Close CSV file decrement \%m # (because of how FOR loop works) echo MATRIX A ROWS: \%r COLUMNS: \%m # Show the matrix for \%i 1 \%r 1 { # Loop through rows for \%j 1 \%m 1 { # Loop through columns of each row xecho "\flpad(\m(a[\%i][\%j]),6)" } echo } exit 0
Note that the sum arrays don't have to be initialized to zero because Kermit's INCREMENT command treats empty definitions as zero.declare \&r[\%r] # Make an array for the row sums declare \&c[\%m] # Make an array for the column sums for \%i 1 \%r 1 { # Loop through rows for \%j 1 \%m 1 { # Loop through columns of each row increment \&r[\%i] \m(a[\%i][\%j]) # Accumulate row sum increment \&c[\%j] \m(a[\%i][\%j]) # Accumulate column sum } }
Home | Kermit 95 | C-Kermit | Scripts | Current | New | FAQ | Support |