The Auxiliary Lua API

The auxiliary Lua APIs provide additional functionality including HTTP(S) client libraries, a mail (SMTP) client, (secure) sockets, and SSL certificate management.

The auxiliary Lua APIs are not part of the standard libraries and must therefore be compiled and integrated with your build. The source code for these APIs can be found in the xrc directory. The wfs and the lspappmgr example servers include all of the auxiliary APIs. Refer to one of these two examples for how to include and initialize the optional auxiliary Lua APIs.

In addition to the C code in the xrc subdirectories, the xrc/lua and subdirectories include Lua code that must be included in your build. The makefiles for the wfs and the lspappmgr assemble these files into the ".lua" directory in the lsp.zip ZIP file. The lsp.zip file is integrated into the example's executable and made available as the default I/O by the C startup code. The Lua function "require" is designed such that it searches for Lua modules in the ".lua" directory. As an example, an LSP page calling 'require("mail")' loads mail.lua and other required Lua modules from this directory. In other words, calling "require" unzips the files from the integrated ZIP file and loads the files as Lua modules.

Crypto Library

ba.crypto.hash([algorithm [, algorithm, key]])

Creates and returns a cryptographic hash function with the given algorithm, which can be used to generate hash digests. The algorithm defaults to sha1 if no arguments are provided. Valid options for the first argument "algorithm" are one of the following strings: md5, sha1, sha256, sha384, sha512, and hmac. The optional second and third argument must be provided when the first argument is hmac. The second and third arguments are ignored for all other hash functions. When the first argument is hmac, the second argument must be one of the following: md5, sha1, sha256, sha384, or sha512. The third argument "key" is the secret HMAC key.

The returned function accepts the following arguments:

The following example shows how to calculate B64(MD5(username password)):

local hfunc = ba.crypto.hash"md5"
hfunc(username)
hfunc(password)
local data = hfunc(true,"b64")

The hash function returns a copy of itself when being fed data, thus making it possible to chain the functions. The following example produces the same result as the above example:

local data = ba.crypto.hash"md5"(username)(password)(true,"b64")

The following example shows how to create an MD5 HMAC for the username and password by using the secret key "qwerty".

local data = ba.crypto.hash("hmac","md5","qwerty")(username)(password)(true,"b64")

Mail (SMTP) Client Library

The SMTP library provides functionality to send e-mail messages. The SMTP library supports authentication and secure SMTP. The implementation conforms to the Simple Mail Transfer Protocol, RFC 2821.

The SMTP library provided with the Barracuda Embedded Web Server is an enhanced version of the LuaSocket SMTP library, which also supports SSL/TLS and STARTTLS email encryption. See email encryption protocols for more information.

The SMTP library is designed for blocking sockets and cannot be used with our asynchronous socket API. If you are not using the SMTP library from an LSP page, make sure you run the SMTP code in the context of the Lua Thread Library. See threading for more information.

Simplified SMTP Client Library

We have created a wrapper library that simplifies the use of the LuaSocket SMTP library. You can choose to directly use the LuaSocket SMTP library or the simplified SMTP library provided by Real Time Logic. The simplified SMTP client library does not require that you have detailed knowledge of MIME encoding or knowledge in how to use LTN012, Filters sources and sinks. The following documentation is for the simplified SMTP library. See the LuaSocket SMTP documentation if you prefer to use the more technical API.

The Simplified SMTP client library, called "mail", makes it easy to send text and html emails. The library also makes it easy to embed images for the html and to add file attachments. The following example shows how to send an email using a standard SMTP server.

require "socket.mail" -- Load mail lib

local mail=socket.mail{server = "smtp.example.com"} -- Create a mail object

-- Send one email
local ok,err=mail:send{
   subject="Hello",
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   body=[[
     Hello Alice,
     This is a test.
   ]]
}

Simplified SMTP Client Library API

socket.mail{
server = string,
[port = number,]
[user = string,]
[password = string,]
[shark=SharkSSL object,]
[starttls=true]
}

Creates a simplified SMTP client object. The socket.mail function takes a table as argument with the following required and optional attributes:

mail:send{
subject=string,
from=string,
to=string|table,
[replyto=string,]
[cc=string|table,]
[bcc=string|table,]
[txtbody=string|fp,]
[htmlbody=string|fp,]
[htmlimg=table,]
[attach=table,]
[encoding="QUOTED" | "B64" | "8BIT" | "NONE"]
[charset=string]
}

In the above, string|fp means either a string with text/binary data or a file pointer. A file pointer can be from opening a file using the standard Lua I/O function or from a Barracuda I/O. Examples: fp=io.open("/path/img.gif"), fp=ba.openio("disk"):open("/path/img.gif").

Email addresses:
Email addresses can be any of the following:

Mail Examples

Creating the mail object

The following example configures and creates a mail object for the secure Google SMTP server:

local mail=socket.mail{
   shark=ba.create.sharkssl(), -- Use TLS
   server="smtp.gmail.com",
   user="YOUR GOOGLE EMAIL ADDRESS",
   password="YOUR GOOGLE PASSWORD",
}

Notice in the code above how we create a SharkSSL object with no Certificate Authority (CA). Though this is valid code, the Google SMTP server will not be validated and a man in the middle attack could compromise the connection. You should therefore install known CAs in the SharkSSL object.

The following example configures and creates a mail object for the STARTTLS version of the hotmail SMTP server:

local mail=socket.mail{
   shark=ba.create.sharkssl(), -- Use TLS
   starttls=true, -- Start as a normal socket and then later upgrade to TLS
   server="smtp.live.com",
   user="YOUR HOTMAIL EMAIL ADDRESS",
   password="YOUR HOTMAIL PASSWORD",
   port="587",
}

A secure SMTP server may require clients to provide a certificate. You can install a valid certificate in the SharkSSL object if your SMTP server requires client certificate authentication.

Sending emails

The following example sends an HTML email to Alice by opening and reading the file "emails/alice.html":

local ok,err=mail:send{
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   subject="Hello",
   htmlbody=io:open"emails/alice.html"
}

It is good practice to embed a plain text version of the HTML email. The following example sends a combined text and HTML email:

local ok,err=mail:send{
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   subject="Hello",
   txtbody="This is the text body",
   htmlbody=[[
        <html>
          <body>
            <h1>This is the html body</h1>
          </body>
        </html>
   ]]
}

The following email shows how to send a combined text and HTMLl image. The HTML includes an embedded image. The email also includes one attachment:

local ok,err=mail:send{
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   subject="Hello",
   txtbody="This is the text body",
   htmlbody=[[
        <html>
          <body>
            <h1>This is the html body</h1>
            <img src="cid:the-unique-id" alt="Text shown by text only clients">
          </body>
        </html>
   ]],
   htmlimg = {
      id="the-unique-id",
      name="logo.gif",
      source=io:open"logo.gif"
   },
   attach={
      description="A document",
      name="my-document.pdf",
      source=io:open"my-document.pdf"
   }
}

Notice how the image source attribute is set to a unique ID. You must create a special HTML file where each embedded image has its own unique ID.

HTTP(S) Client Libraries

The Barracuda HTTP(S) client library, which is implemented in C code, can be accessed from Lua by using the Lua bindings for the HTTP implementation. The Lua bindings are found in xrc/lua/lhttp.c.

In addition to the low level C implementation, two additional HTTP libraries implemented in Lua are provided. The additional libraries wrap around the low level library and simplify the use of the low level implementation. The two Lua scripts are found in the xrc/lua/ directory. The two scripts are also embedded in the wfs and the standalone Lua interpreter.

The libraries are loaded as follows:

The HTTP(S) Client Libraries implementation conforms to the HTTP/1.1 standard, RFC 2616.

Important information:
The HTTP client libraries use blocking socket calls and should therefore run in the context of the Server HTTP Thread Pool or the Lua Thread Library. See threading for more information.

The "httpc" library

The library is loaded as follows:

local http = require"httpc"
require"httpc".create([op])

Returns an instance of the low level HTTP library.

The option table:
  • shark = SharkSSL object -- The SharkSSL object is required when using secure connections -- i.e. URLs starting with https://
  • persistent=bool -- Set/disable persistent HTTP1.1 connections. The Default is to enable persistent connections.
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to any interface.
  • ipv6 = bool -- Use of IPv6 address translation. Note, the server must have been compiled with IPv6 support.
  • proxy = string -- Use a HTTPS or SOCKS5 proxy with IP address or domain name "proxy".
  • proxyport = number -- Proxy port number defaults to 8080 for HTTPS proxy and 1080 for SOCKS5 proxy.
  • socks = bool -- Set to true if you are using a SOCKS5 proxy. The default is to use HTTPS proxy by using HTTP CONNECT.
  • proxyuser = string -- Proxy authenticate using username.
  • proxypass = string -- Proxy authenticate using password.

Methods:

The methods associated with the object returned by require"httpc".create()

http:timeout(milliseconds)

Set the number of milliseconds before connect, read, and write times out. The default is 20000 i.e. 20 seconds. A value of zero waits indefinitely.

http:request(op)

Returns true on success.
On error: returns nil followed by an error code or a number. The error code is provided if some form of socket error occured. The number is only returned if a proxy is installed. The number corresponds to the HTTP status. Example: 407 Proxy Authentication Required.

The option table:
Required attributes:
  • url = string -- URL to the server resource.
  • method = string -- GET, POST, etc.
Optional attributes:
  • header = table -- A table with key value pairs. Include additional HTTP headers.
  • query = table -- A table with key value pairs. Include url encoded data in the query component of the request URL.
  • user = string -- Authenticate using username.
  • password = string -- Authenticate using password.
  • size = number -- Size is an optional length when sending data to a server using POST or PUT. The client sends data using chunked transfer encoding if no size is specified.
  • trusted = bool -- Force method http:request to accept only trusted connections when connecting to a server. This option requires that you have a SharkSSL object with an installed Certificate Authority Store. The connection is closed and the function returns nil,"nottrusted" if the certificate is not trusted, expired, or if the domain name does not match the domain name in the certificate. See also http:trusted.
http:status()

Returns the HTTP server response code.

http:header()

Returns a table with the HTTP header key/value pairs.

http:headerpairs()

Returns an iterator that will traverse all the HTTP header key/value pairs.

http:cookie()

Returns an iterator that will extract and traverse all cookies sent by the server.

The server may send multiple "Set-Cookie" headers:

   Set-Cookie: name=value [; expires=date] [; path=path] [; domain=domain] [; secure]

The iterator returns the cookie name, value, and an optional table. The table is returned if the cookie returned by the server contains more than just the cookie name. The table provides {key, value} pairs, where the keys can be one of the following: expires, path, domain, and secure.

   for name,value,t in x:cookie() do
      print("cookie name:", name, ",value", value)
      if t then
         for k,v in pairs(t) do
            print(k,v)
         end
      end
   end
http:read(size)

Read size bytes.

size is one of:
  • "*a" for all data.
  • Number(n) for reading n bytes.
  • Not provided. Makes the read function silently consume and discard response data.
http:write(n)

Write n bytes.

Returns true on success.
On error: returns nil followed by an error code or a number. The error code is provided if some kind of socket error occured. The number is the HTTP response code. For example, if uploading data fails and the server sends a HTTP response code while the client is uploading, the uploading is aborted immediately, and the HTTP response code is returned to the caller.

The caller can set a "Expect: 100-continue" header prior to uploading data, but this is not necessary since the write function asynchronously detects if the server denied the upload. Any 100 continue messages sent by the server are silently consumed by the write function.

Note: The data is uploaded using chunked transfer encoding unless the size attribute was set on the http:request() option table. Not all servers support chunked transfer encoding.

http:certificate()

Returns the peer's certificate chain as a table if the connection is secure and the peer sent a certificate. A second return value is set to true if the certificate is valid i.e. if validated by the certificate store.

http:cipher()

Returns the cipher suite and SSL protocol version being used if the connection is secure. See socket:cipher() for more information on the return values.

http:trusted()

Returns the peer's "trust" status.

http:close()

Close the connection.

The "http" library

The "http" library wraps around the low level C implementation and simplifies the use of the low level implementation.

The library is loaded as follows:

local http = require"http"

Redirect management:

Many server applications may send a 302 temporary redirect request or a 301 permanent redirect request when accessing resources. The server resource may for example redirect to the same resource, but request the client to add or change URL encoded data when accessing the server. The "http" library automatically manages 301 and 302 requests. The client library computes the new URL and sends a new request. This sequence continues until the server returns a non 301/302 response or the redirect counter detects an infinite redirect loop. The extra http:url() method provided by the "http" library returns the computed URL and query data the server may have requested the client to perform.

Default http:request() options:

One of the benefits of the Lua implemented wrapper is that it simplifies the use of the option table. Options that are required on method http:request() can be set when creating the HTTP client instance. The following example illustrates this:

local http=require"http"

-- Set default options that are required by http:request()
local op={
   url="https://localhost/mypage.lsp",
   method="GET",
   shark=ba.create.sharkssl() -- So we can use HTTPS
}
local h=http.create(op)

 -- Use default options: method "GET" and url https://localhost/mypage.lsp"
h:request()
h:read"*a" -- silently consume the response

h:request{method="HEAD"} -- Override default "GET" method
print(h:status())

h:request() -- We are using "GET".
h:close() -- We are done

An instance of the "httpc" library would have thrown an exception when calling h:request(), but the Lua version remembers the extra options added when we created the object.

You can optionally provide an option table and override any of the default settings. The HEAD request above illustrates this.

Methods:

The "http" library provides, in addition to the "httpc" methods, the following method:

http:url()

Returns the url and query data as a table for the current connection. The url and query data may be different on server response than the values set when connecting since the server can send redirect responses. The redirect response is internally managed by the "http" library.

The "httpm" library

The "httpm" (HTTP Managed) library extends the "http" library and simplifies common tasks such as uploading and downloading data, sending HTTP POST requests, and communicating with a server using JSON.

The library is loaded as follows:

local http = require"httpm"

Methods:

The "httpm" library provides, in addition to the "http" methods, the following methods:

http:stat(url [,op])

Returns a table of attributes for the named url -- the resource . If the resource does not exist, 'nil' followed by an error code is returned. The fields of the table are named as follows:

This function is by default using the HTTP method "HEAD". You can change the HTTP method by setting op={method="method-type"}

Examples:

local http = require"httpm"
local h=http.create(my-option-table)
local st,err=h:stat("http://my-server/my-resource")
if st then print(st.size,st.mtime) end
-- Override default options
tab,err=h:stat("http://my-server/my-resource",
                     { method="GET", query={foo="bar"}})
if st then print(st.size,st.mtime) end

http:post(url,tab [,op])

Send a HTTP POST request to a server. This function emulates a HTML form submit in a browser.

The following example is sending url encoded data in the query component of the URL and in the body of the message.

local http = require"httpm"
local h=http.create(my-option-table)
local ok,err=h:post("https://myserver/myresource",
                    {foo="Hello"},
                    {query={bar="world"}})
http:upload(conf [,op])

Upload a file to a server. This function is by default using the HTTP method "PUT". You can change the HTTP method by setting op={method="POST"}

The function can be used in two modes:


The HTTP library is monitoring response data from the server while the upload is in progress. The upload is automatically terminated if the server sends an error response. It is for this reason not necessary to do the typical one byte test upload prior to uploading the actual data to test if the server accepts the upload. HTTP 100-continue messages sent from the server are silently consumed.

http:download(conf [,op])

Download a file from a server and save the file. The function can be used in two modes:

http:json(url, data [,op])

Load JSON-encoded data from the server using a HTTP GET request. This function, which is designed for server side Lua code, is similar in functionality to the jQuery.getJSON() function in the JQuery JavaScript library.

Example 1:

-- Get IP address from http://www.jsontest.com/
local http=require"httpm".create()
local t,err=http:json("http://ip.jsontest.com/",{})
if t then -- If we got a Lua table (decoded JSON)
   print("IP address:",t.ip)
end

Example 2:

-- Connect to a Web-File-Manager in a Barracuda server
-- and request a JSON directory listing.
local t,err = http:json("http://localhost/fs/", {cmd="lj"})
if t then
   for _,r in ipairs(t) do -- Iterate all resources
      response:write("name=",r.n,", size=",r.s < 0 and "DIR" or r.s,
                     ", date=",os.date("%c",r.t),"<br>")
   end
else
   response:write(err)
end

Socket API

The TCP and UDP socket API provide easy to use functions for creating client and server TCP connections and for working with UDP sockets. The TCP socket API is integrated with our own SSL stack SharkSSL™, thus making it very easy to create secure custom protocols. The TCP socket API enables interceptions of HTTP client and server connections at any point, making it possible to morph an HTTP(S) connection into an HTTP(S) tunnel, which can be used for (secure) custom protocols that can bypass proxies and firewalls.

Note that UDP socket is not supported on all embedded/RTOS platforms.

The socket API can be used in three modes:

See the section Designing Socket Protocols in Lua for an introduction to the Lua socket API.

The Barracuda socket API is not identical to the standard LuaSocket, but a compatibility layer is included that makes it possible to use code designed for the Lua socket API such as the mail (SMTP) client.

Functions

ba.socket.bind(port [,op])

Creates and returns a TCP server object bound to a local port, ready to accept client connections.

The option table:
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to all interfaces.
  • ipv6 = bool -- Use IPv6 address translation for 'intf'. Note, the server must have been compiled with IPv6 support. One can typically open two connections on the same port and interface if one is an IPv4 address and the other is an IPv6 address.
  • shark = SharkSSL object -- Accept SSL connections.
ba.socket.connect(address, port [,op])

Creates and returns a TCP client object connected to a remote host/address at a given port.

The option table:
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to any interface.
  • ipv6 = bool -- Use IPv6 address translation for 'address'. Note, the server must have been compiled with IPv6 support.
  • shark = SharkSSL object -- Connect using an SSL connection. The SharkSSL object must be initialized as a TLS client.
  • timeout = number -- Set the maximum time to wait in milliseconds for connection establishment. The timeout value is supported for asynchronous connections and for synchronous (blocking connections) on platforms where the porting layer interfaces to a BSD socket API.
ba.socket.udpcon([address, port] [,op])

Creates and returns a connected or unconnected UDP object. The object is unconnected if address and port are not provided.

The option table:
  • intf = string -- Bind to interface-name/IP-address.
  • port = number -- Bind to port number.
  • ipv6 = bool -- Create an IPv6 socket. Note, the server must have been compiled with IPv6 support.
ba.socket.req2sock(request)

Extracts the active socket connection from the request object. This function, which can only be called from an LSP page or a directory function, makes it possible to morph an HTTP(S) connection into an HTTP(S) tunnel, which can be used for (secure) custom protocols. This function can also upgrade a WebSocket request to a WebSocket connection. The LSP page response object cannot be used after this function is called. Thus if you want to send an HTTP response, the response must be flushed by calling response:flush prior to calling this function. Note: an HTTP response is sent automatically by this function when upgrading a WebSocket request to a WebSocket connection.

This function returns the socket object and any additional data the server received following the HTTP header when upgrading regular HTTP to a socket connection. The second return value is nil if no additional data is available or if upgrading to a WebSocket connection.

ba.socket.http2sock(httpclient)

Extracts the active socket connection from an HTTP client object. The function is typically called after sending the request and calling http:status(). This function can also upgrade a WebSocket request to a WebSocket connection.

The following example connects to a web server running on localhost:80, extracts the socket connection from the HTTP client object, and starts an asynchronous coroutine that reads data until end of stream. End of stream is assumed when the receive function receives no data for 2 seconds.

require"http"

local function recData(s)
   local data,err
   data=true
   while data do
      -- s:read returns nil when no data received for 2 seconds
      data,err = s:read(2000)
      trace(data) -- Print response data to trace.
   end
   if err then trace("Read failed", err) end
   -- Socket is closed on return
end

local c = require"httpc".create()
c:request{url="http://localhost",method="GET"}
local s,err=ba.socket.http2sock(c)
if s then
   s:event(recData, "s")
end
ba.socket.toip(address[,ipv6])

Looks up address by using DNS and translates address to an IP address. Forces the use of IPv6 address translation if the optional parameter is true. Note, the server must have been compiled with IPv6 support.

This function is useful when using asynchronous sockets since DNS lookup may take some time (DNS lookup is blocking). Lua code can be designed such that the address is resolved by code that is allowed to block. The address can be cached such that the non blocking coroutines can use the IP address when connecting to peers.

ba.socket.event(function [,args])
Runs a Lua callback function as a cosocket. The provided function (parameter one) is immediately resumed with the first parameter being the socket, and subsequent parameters being the optional "args". The Lua callback function is resumed in the context of the thread running the Socket Event Dispatcher as soon as the Lua callback function calls the first socket function. All socket operations performed in the function will appear to be blocking, but most functions are asynchronous behind the scene. The function that may block is ba.socket.connect, when using a domain name that must be resolved to an IP address by DNS lookup. See cosockets for more information.

The function returns the socket object.

See also socket:event.
ba.socket.getsock()

Returns the socket object of the current Lua thread state if the current state is a socket coroutine. See also method socket:owner.

ba.socket.h2n(size,number)

Host (number) to network (big endian data stream) conversion.

size - number of bytes to convert from the number -- e.g. 2 converts a 16 bit number and 4 converts a 32 bit number.
number - the number to convert.

The function returns the converted number as a binary string, where the data is stored in network endian format.

ba.socket.n2h(size,string [, start])

Network (big endian data stream) to host (number) conversion.

size - the byte size to read and convert -- e.g. 2 converts a 16 bit number and 4 converts a 32 bit number.
string - the binary data string
start - defaults to 1 and is the offset position in the string.

The function returns the converted data as a number in host endian format.

Socket Object Methods

Socket objects returned by ba.socket.bind(), ba.socket.connect(), ba.socket.req2sock(), and ba.socket.http2sock() have the following methods associated with them:

TCP: socket:read([timeout])
UDP: socket:read([timeout][,true])

Reads data from socket. The function blocks indefinitely if timeout (in milliseconds) is not provided. The function automatically yields if the caller is a socket coroutine and no data is available on the socket.

UDP: use recv when the optional boolean is not set or is false. Use recvfrom when the boolean value is set to true.

Returns:

See WebSocket frames for more information on the return values for WebSocket connections.

unconnected:sendto(datagram, ip, port)

Sends a datagram to the specified IP address and port number.

Datagram is a string with the datagram contents. The maximum datagram size for UDP is 64K minus IP layer overhead. However, datagrams larger than the link layer packet size will be fragmented, which may deteriorate performance and/or reliability. Ip is the IP address of the recipient. Host names are not allowed for performance reasons.

If successful, the method returns true. In case of error, the method returns nil followed by an error message.

Note: In UDP, the send method never blocks and the only way it can fail is if the underlying transport layer refuses to send a message to the specified address (i.e. no interface accepts the address).

socket:write(data [, i [, j]] [,utf8])
socket:write(data, utf8)

Send data over a TCP connection or connected UDP connection. A UDP connection has the same features and limitations as sendto. The optional arguments i and j work exactly as the standard string.sub Lua function to allow the selection of a substring to be sent.

Argument utf8 is a boolean value that can be set to true or false for WebSocket connections. WebSocket frames default to binary. Setting the utf8 argument to true sends a WebSocket text frame. See WebSocket frames for more information.

A blocking socket blocks if the TCP queue is full. A cosocket automatically yields if the TCP send queue is full. See cosockets flow control for more information.

Returns: Limitations: you cannot send empty (websocket) messages. The method returns false if he data length is zero.
socket:accept()

Waits for remote connections on the server socket object and returns a socket object representing that connection. This method can only be called by a coroutine in accept mode, a mode enabled by ba.socket.bind.

socket:disable()
Disable an asynchronous socket coroutine or a non blocking asynchronous coroutine. The function is typically called by the coroutine itself when your design requires that the coroutine stops receiving socket events. The TCP IP stack will then buffer the incoming messages until it's sliding window queue is full. The peer will then stop sending messages.

The function can also be called by code outside of the coroutine, but this construction requires that the coroutine has yielded in socket:read and not in socket:write. The call fails if the coroutine his currently suspended in socket:write.

The coroutine is immediately suspended when called from within the coroutine. The function will not resume before another coroutine or other code running in the context of a native thread calls method socket:enable. Any parameters provided to socket:enable is then returned as arguments when socket:disable returns.

The Lua socket proxy example shows how to use methods socket:disable and socket:enable.
socket:enable([args])
Enable a socket coroutine in state 'disabled'. The optional arguments are provided as return values to socket:disable, however this requires that the coroutine was suspended in socket:disable. The optional arguments are ignored if the coroutine was disabled by another coroutine i.e. if the coroutine is currently suspended in socket:read. The Lua socket proxy example shows how to use methods socket:disable and socket:enable.
socket:event(function [, "r|s" [, args]])

Run a Lua callback function as an asynchronous socket coroutine or a cosocket. This method is similar to function ba.socket.event, but allows the caller to bind or connect a socket before enabling socket events.

The provided function (parameter one) is immediately resumed, with the first parameter being the socket, and subsequent parameters being the optional "args". The Lua callback function is resumed in the context of the thread running the Socket Event Dispatcher as soon as the Lua callback function calls the first socket function.

The provided Lua function can run as coroutine in two modes:

Parameter two must be "r" for listen sockets i.e. sockets returned by ba.socket.bind. A listen socket can only receive "accept" events.

socket:queuelen([len])
Set or get the socket queue length. The queue length specifies how many bytes that can be buffered internally when a non asynchronous caller calls socket:write on a cosocket. The default length is 16K. See cosockets flow control for more information.
socket:owner()

Returns true if the current Lua thread state is the socket coroutine that owns the socket connection. See also function ba.socket.getsock.

socket:peername()

Returns a string of the client name/IP address, the connected port number, and a boolean value set to true for IPv6 and false for IPv4.

socket:sockname()

Returns information about the local side of the TCP connection. Returns a string with the local IP address, the connected port number, and a boolean value set to true for IPv6 and false for IPv4.

socket:websocket()

Returns true if the socket is a WebSocket connection, otherwise false is returned.

socket:setoption(option [, value])

Sets an option for the TCP or UDP object. The first argument 'option' is a string with the option name, and value depends on the option being set:

TCP:

UDP:

The function returns true on success and nil,error if the operation failed.

Error codes for 'keepalive':

socket:state()

Returns the socket state:

notconCreated but not connected
execExecuting
acceptAsynchronous accept
connectAsynchronous connect
upgradeAsynchronous upgrade to TLS
disableddisabled
readWaiting in asynchronous read
writeWaiting in asynchronous write
terminatedTerminated/closed
socket:close()

Close the socket connection.

socket:upgrade(shark)

Upgrade a standard socket to a secure socket. The method is typically used by protocols that connect in non-secure mode and then later upgrade to secure communication. STARTTLS is an example of a protocol that upgrades to SSL/TLS.

  local s=ba.socket.connect(myaddress, myport)
  -- Send handshake data
  s:upgrade(ba.create.sharkssl())
socket:certificate()

Returns the peer's certificate chain as a table if the connection is secure and the peer sent a certificate. A second return value is set to true if the certificate is valid i.e. if validated by the certificate store.

socket:cipher()

Returns the cipher suite and SSL protocol version being used if the connection is secure.

Cipher suites: Protocol versions:
socket:trusted([host-name])

Returns the peer's "trust" status.

If the argument host-name is not provided, the certificate's common name is compared with the address provided when the connect function was called. Server connections must provide the host-name argument.

Lua Thread Library (Thread Pool)

Let's revisit the diagram from the introduction.

The Lua Thread Library (the thread pool) is seen in the bottom right corner in the above diagram. The thread pool is typically used when running Lua code that is not part of a typical web-server request/response. One can for example design code to run at regular intervals by using a timer and trigger a function to run for a prolonged time in the thread pool.

ba.thread.run(function)

Delegate the execution of function to one of the threads in the Lua thread pool. The execution is queued if no thread(s) are available -- i.e. if the thread(s) are busy executing other functions. The function remains in a FIFO queue until a thread becomes ready.

There are no time limits on the execution time for the provided function. The thread executing the function becomes ready when the function returns (terminates).

See the Lua socket web server example for more information.

ba.thread.configure([maxthreads])

Set or get the maximum number of allowed threads in the thread pool. The default value is one. Threads never terminate, thus setting a higher value followed by a lower value does not reduce the thread pool size. Threads are not created unless there are no available threads when function ba.thread.run() is called.

ba.thread.create()

Dynamically create a new thread. The function returns a thread object. The thread is activated by calling the run method on the returned object.

Example:
local function myThreadFunc()
   for i=1,10 do
      print("Hello")
      ba.sleep(1000)
   end
end

local thread=ba.thread.create()
thread:run(myThreadFunc)

SharkSSL

(Secure SSL/TLS communication management)

SharkSSL is an extremely compact SSL/TLS stack, supporting TLSv1.0 to TLSv1.2. SharkSSL provides security for network communications, such as for securing HTTP and SMTP communication. The SharkSSL stack supports both client-side and server-side connections, enabling the client to confirm the server's identity and vice-versa.

When using secure communication such as secure sockets or the HTTP library in secure mode, a SharkSSL object is required. The various API's such as the HTTP client library and the socket library take an option table as argument. This table must have the attribute "shark" set to an instance of SharkSSL when using secure communication.

ba.create.sharkssl(certstore [,op])

Create a SharkSSL object. Parameter certstore, the CA list, is an object created with ba.create.certstore(), parameter op is an optional configuration table.

The returned object has the following method:

sharkssl:addcert(sharkcert)
sharkcert, the certificate and key, is an object created with ba.create.sharkcert(). You can add multiple certificates such as RSA certificates and ECC certificates.
Typical use cases:
  1. Create a client SharkSSL object that verifies the server using the supplied CA
    clientObj=ba.create.sharkssl(certstore)
  2. Create a client SharkSSL object that accepts any server, including self signed certificates.
    clientObj=ba.create.sharkssl()
  3. Create a standard server object that does not require client certificates.
    serverObj=ba.create.sharkssl(nil, {server=true}); serverObj:addcert(sharkcert)

The above 3 use cases show the typical web client and server configurations.


The option (op) table:
  • server=bool -- The SSL/TLS protocol is a client server protocol. You must select the correct protocol for client and server connections. The default is to create a client SSL object. Set server=true if the ssl object is used as an option when calling ba.create.servcon.
  • insize=number -- The internal SharkSSL start "read" buffer size. The size automatically grows if needed. The default value is 8192 bytes.
  • outsize=number -- The internal SharkSSL "write" buffer size. The default value is 8192 bytes.
  • cachesize=number -- The SSL session resumption cache size. The default 'cachesize' value for a SSL client object is 8. The default 'cachesize' value for a SSL server object is set to the web server's maximum number of connections. The server's maximum number of connections is set by the C startup code by calling HttpServerConfig::setNoOfHttpConnections().
ba.create.certstore()

Create a Certificate Authority Store. A Certificate Authority or CA for short is an entity that issues digital certificates for use by other parties. In layman's terminology, a CA is like a certified (government) passport issuer. A CA is typically used by a client SharkSSL object, but can also be used by a server SharkSSL object if the server requires client certificate validation.

The object returned by ba.create.certstore() have the following method associated with them:

addcert(io,name)
addcert(data)

This function adds a certificate in PEM or p7b format to the CA store. A convenient way to get CA's is to export the certificates from a browser in PEM or p7b format. The p7b format is a container format that can contain many CA's.

The method can add certificates in two modes:

  • Mode 1: Provide a Barracuda I/O interface (io) and the path+name (name) to the location of the CA.
  • Mode 2: Load the file data manually and insert the data.

The following example downloads the "CA Root Certificates bundle" maintained by CURL, creates a CA store, and installs the downloaded bundle in the CA store. Note that the download must be over a secure connection, but we are unable to verify the download since we do not have a CA store with CA certificates when downloading. We set the "shark" attribute to a SharkSSL object with an empty CA store when downloading.

local store=ba.create.certstore()
local http=require"httpc".create()
http:request{
   -- Create a temporary shark object and use the empty store.
   shark=ba.create.sharkssl(store),
   method="GET",
   url="https://curl.haxx.se/ca/cacert.pem"
}
local data,err=http:read"*a"
if data then
   local certs = store:addcert(data)
   print("Number of CA certs installed:",certs)
   -- Use 'store'
end
ba.create.sharkcert(io, certfile, keyfile[, password])
ba.create.sharkcert(certdata, keydata[, password])

A SharkSSL certificate is a combination of the certificate (the public part) and the certificate key (the private part).

The function can create a SharkSSL certificate in two modes:

The password is an optional argument that must be provided if the key is encrypted.

Barracuda Web Server Listen Object

ba.create.servcon(port [,op])

Create a Barracuda Web Server connection listen object and install the object in the Socket Event Dispatcher. The web-server listen object(s) are typically installed by the C startup code, but can optionally be installed by the server startup script (.config). The benefit of having Lua code manage the objects is that it is easier to configure the server listen objects.

See the Lua web-server startup script trunk/examples/wfs/lsp/.openports in the Barracuda Embedded Web Server SDK for an example on how to dynamically detect free server port numbers and configure multiple secure and non-secure server connection objects.

For security reasons, objects created with this function are self referencing, which means they will not be collected by the garbage collector if you do not keep a reference to these objects. The Barracuda Socket Event Dispatcher cannot operate without at least one connection. If you close all listen objects, the Socket Event Dispatcher will eventually run out of active connections and malfunction. It is for this reason vital that you keep at least one listen object active at all times.

The option table:
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to all interfaces.
  • ipv6 = bool -- Create an IPv6 server object. Note, the server must have been compiled with IPv6 support.
  • shark = SharkSSL object -- Accept SSL connections.

You can open any number of server connection objects as long as the combination of all of the following is unique: port number, interface, and protocol version (IPv4/IPv6). This means that you can have many server connection objects listening on port 80 if you have more than one interface and/or protocol version.

Objects returned by ba.create.servcon() have the following methods associated with them:

servcon:setport(port [,op])

Change the port number and configuration options in an active server connection object. The parameters are identical to the parameters for function ba.create.servcon().

The function is internally creating a new connection object. The original connection is closed if the new object is created successfully. This function differs from closing an active connection and then creating a new object by leaving any active "accepted" socket connections from the original server connection running.

servcon:close()

Close the active server listening object, remove the listening object from the Socket Event Dispatcher, and remove the self reference on the object.

The following example shows how to create a secure (SSL) listen object. You may download the complete example and run the example on your server.

-- Servers do not use a 'cert store' unless mutual authentication is required.
local shark=ba.create.sharkssl(nil, {server=true})
-- x509certData and x509KeyData are Lua strings containing cert and key.
local x509cert = ba.create.sharkcert(x509certData,x509KeyData)
shark:addcert(x509cert) -- Certificate required for servers.
ba.create.servcon(port, {shark=shark})

forkpty

The forkpty library provides a combined fork, exec, and child process pseudo-terminal. The code is currently available for Linux only such as embedded Linux.

The code can be used for executing and managing Linux executables such as "ls", "kill", etc, or be used as a base for designing a web shell. The forkpty library provides a much more advanced child process management than the more basic function ba.exec

pty,err=ba.forkpty([function,] prog [, args...])

Starts program prog in a separate process and returns a process object that is used for child process management. The pty process object support full duplex read and write operations.

On success, the function returns a pty child process communication object with 4 methods:

  1. pty:read: Read child's stdout.
  2. pty:write: Write to child's stdin.
  3. pty:pause: Pause or resume child process.
  4. pty:winsize: Set the child's terminal screen size.
  5. pty:close: Gracefull kill.
  6. pty:terminate: Immediate kill.

Parameter prog is the process to start such as "/bin/bash", "/bin/login", etc. The optional args are passed to the child process.

The external process can be managed in two modes, polling, or asynchronous read. Polling the pty is typically used when designing web-clients that use AJAX for polling the child process for new data. Asynchronous read is typically used together with SMQ, where a web-client is using a persistent asynchronous communication channel with the server. The optional "function" must be supplied for asynchronous read operation. The default mode is designed for polling mode. The examples below assume that you do not provide a function. Asynchronous mode is explained later.

PTY Methods

data, err = pty:read(true|milliseconds)

Read stdout,stderr from the child process.

The function returns nil when the child process exits or if any error occurred.

Note:
pty:read returns nil followed by an error code when the child process exits. You must use method pty:close or pty:terminate to cleanup and to get the child exit code. The error code is "terminated" if the process exited, otherwise the error indicates an error with the pty.

Example:
local data,err = pty:read()
if data then
 -- manage data
elseif err then
  if err = "terminated" then
    -- Terminated
  else
    -- pty error
  end
end
ok,err = pty:write(string)

Send data to the child process's stdin.

ok is nil on error. See pty:read for how to manage the error code.

pty:pause(true|false)

Pause or resume a paused child process.

ok,err=pty:winsize(lines, cols)

Set the child's terminal screen size.

local status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:close([true])

Return the exit code for the <defunc> child process or gracefully kill the active child process.

The function returns immediately unless the optional parameter is set to true. The function waits for the child to terminate if the optional parameter is set to true.

status is the child process exit code.

Status codes:

if status is nil, the second parameter is the error message. The child process is immediately kill if any errors are detected.

Example:
local status, WIFEXITED, WIFSIGNALED, WTERMSIG = pty:close()
if status == nil then
   print("Failed, msg:", WIFEXITED)
elseif status < 0 then
   print("Running")
elseif status == 0 then
   print("Exited");
else
   print("Exited with error code:", status , WIFEXITED, WIFSIGNALED, WTERMSIG);
end

Note, pty:close() is designed to be called repeatedly until the child terminates i.e. until it returns a value that is not less than zero.

local ok, status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:terminate()

Close the connection to the <defunc> child process or immediately kill the active child process.

Asynchronous PTY Mode

The PTY object is self-referencing in asynchonous mode. Normally, a reference must be kept to the pty objected returned by ba.forkpty to prevent garbage collection (GC). When in asynchronous mode, a reference is automatically created by ba.forkpty to prevent GC. This reference is valid as long as the coroutine executes.

The following example shows how to use asynchronous mode:

-- The asynchronous receive function keeps reading until the process terminates
function recData(pty)
   while true do
      local d,e = pty:read() -- Read data. Arguments to pty:read are ignored.
      if not d then -- Child process terminated
         break
      end
      trace(d,e)
   end
   trace("\nexit coroutine", e and e or "OK")
   -- coroutine automatically cleans up on exit, but we are allowed to call:
   -- local ok, status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:terminate()
end

local pty,err -- Local variables i.e. we do not need a reference to prevent GC
pty,err = ba.forkpty(recData, "/bin/ls", "-l") -- Execute ls -l
trace(pty,err)

In asynchronous mode, a function must be provided as the first argument to ba.forkpty. This function is executed as a Lua coroutine and is automatically yielded by read when no data is available. The function is resumed when there is data available or if the child process terminates.

All pty:xxx functions operate as normal, except for read when called from within the coroutine.

LuaIo

The LuaIo module implements Lua mapping between the Barracuda I/O interface and Lua. The LuaIo API makes it possible to implement a Barracuda I/O interface using Lua.

The purpose with the LuaIo is to provide filtering on existing I/O's or implement new I/O types in Lua. As an example, a SQL mapped LuaIo could provide WebDAV access to a SQL database.

Error handling:

The I/O functions return nil or false on error. Functions returning a table return nil on error and functions returning status information return false on error. In addition to returning nil/false, the functions can optionally return one of the following error codes (string):

-- Open resource in read or write mode. mode ="r" or "w"
local function open(name, mode)
   -- open resource, return nil on error.
   -- Resource management functions
   local function read(maxsize)
      -- Return data
   end
   local function write(data)
      -- Return true or false
   end
   local function seek(offset)
      -- Return true or false
   end
   local function flush()
      -- Return true or false
   end
   local function close()
      -- Return true or false
   end
   local res={
      read=read,
      write=write,
      seek=seek,
      flush=flush,
      close=close
   }
   return res -- Table with functions: read,write,seek,flush,close
end

-- Directory iterator
local function files(name)
   -- Find resource, return nil if not found.
   local function read()
      -- Iterate to first/next resource, return true if more files or false
      -- if no more files.
   end
   local function name()
      -- return resource name
   end
   local function stat()
      -- return table with mtime,size, and isdir (true/false)
   end
   return {read=read,name=name,stat=stat} -- Return table with functions
end

-- Return resource information
local function stat(name)
   -- return table with mtime,size, and isdir (true/false) if found or
   -- nil if not found
end

-- Create directory
local function mkdir(name)
   -- Return true on success and false on error
end

-- Remove directory
local function rmdir(name)
   -- Return true on success and false on error
end

-- Remove file
local function remove(name)
   -- Return true on success and false on error
end

-- Table with the Lua I/O callback functions
local iofuncs {
   open=open,
   files=files,
   stat=stat,
   mkdir=mkdir,
   rmdir=rmdir,
   remove=remove
}

-- Create a Lua I/O instance
local io=ba.create.luaio(iofuncs)

-- Install the Lua I/O in any Barracuda resource that takes an I/O
-- interface as argument.

TraceLogger

TraceLogger

The TraceLogger lets you intercept the output from the server's trace buffer, control what data the server's trace buffer should log, and display the traced data in real-time in a browser window. The TraceLogger's web interface URI is by default set to /rtl/tracelogger/ and the JavaScript code in index.html file opens a persistent real-time communication link with "../tracelogger.service/". Note: the output from function trace and _G.print is also sent to the trace buffer. The TraceLogger is an optional component that must be installed by the C startup code. As an example, the Mako Server includes this optional component; set tracelogger=true in the Mako Server's mako.conf file.

ba.create.tracelogger()

Create a TraceLogger directory object. The function returns a TraceLogger directory object specifically designed to act a web-service for the JavaScript code in the TraceLogger client's index.html file.

Example code: study the Mako Server's .config script.

Note: the TraceLogger directory object is automatically referenced and will not garbage collect unless method dir:close() is called. The TraceLogger interfaces to the global trace buffer and you can for this reason only create one TraceLogger instance.

The created directory object has the following additional directory method:

dir:configure([op])

Set and/or get the server's trace buffer settings.

The option (op) table:
  • request=bool (default false) -- Trace the HTTP method type and URL for each request.
  • requestheaders=bool (default false) -- Trace the HTTP headers for each request.
  • responseheaders=bool (default false) -- Trace all HTTP response headers.
  • responsebody=bool (default false) -- Trace all response data created by dynamic pages such as LSP and CSP.
  • http11state=bool (default false) -- Trace the server's internal HTTP 1.1 state machine.
  • priority=number (default 9) -- Set the trace priority.
dir:close()

Shuts down all active connections, removes the directory from the virtual file system (if installed), and releases the reference so the garbage collector can remove the directory object.

Reverse Proxy

Ref Wikipidia:

A reverse proxy is a type of proxy server that retrieves resources on behalf of a client from one or more servers. These resources are then returned to the client as though they originated from the server itself.

The purpose with the reverse proxy is to make it possible to integrate existing web solutions with the Barracuda Application Server and/or provide a means to implement cross site scripting.

The reverse proxy behaves as a Barracuda Server directory object. One or several instances of the reverse proxy can be inserted into the Barracuda virtual file system. The reverse proxy can also implement authentication and authorization just like any other Barracuda directory object. One can for this reason provide security to less secure backend servers. The reverse proxy also offers SSL termination.

ba.create.redirector(name,domain,port[,baseuri][,priority])
Create a standard reverse proxy.
Parameters:
ba.create.redirector(name[,priority])
Create a cross site scripting redirector.
Parameters:

Creating a standard reverse proxy:

The most common operation is to create a standard reverse proxy. The following example creates a reverse proxy for a backend server with IP address 127.0.0.1

  rproxy=ba.create.redirector("backend","127.0.0.1",80)
  myRootDir:insert(rproxy)

The backend server 127.0.0.1 can now be access as: http://localserver/backend/ Use the baseuri if you only want to access a subset of the backend server:

  rproxy=ba.create.redirector("backend","127.0.0.1",80,"subdir")

The backend server URL http://127.0.0.1/subdir/ can now be accessed as: http://localserver/backend/

The basic proxy does not provide a rewrite module, thus absolute encoded URLs in web pages retrieved from a backend server such as images will not show up in the browser unless the reverse proxy is installed as a root directory.

  rproxy=ba.create.redirector(nil,"127.0.0.1",80,-1) -- Priority=-1
  rproxy:insert() -- Insert as root directory

The above is the most common case for inserting an existing web application into a barracuda server. The reverse proxy is inserted as a root directory with a priority that is less than the root directory for the existing application. This will make sure the Barracuda server first searches for the requested resource locally and then delegates the request to the reverse proxy only if the requested resource was not found in the Barracuda Server.

Cross site scripting redirector:

  rproxy=ba.create.redirector("gateway")
  myRootDir:insert(rproxy)

Any server and web page can now be access as: http://localserver/gateway/domain-name/path

e.g. http://localserver/gateway/www.cnn.com