Thalassa CMS logo

Thalassa CMS

Signing up and signing in: user accounts


Single-use passwords

Thalassa CMS uses single-use passwords to authentificate its users. The passwords are sent to users' emails on their requests. As of present, this is the only possibility.

Single use passwords are chosen because Thalassa is intended to be used over unencrypted channels, that is, using HTTP (as opposite to HTTPS). This of course doesn't mean you can't use HTTPS — definitely you can. If you want. From the other hand, you can use plain HTTP as well. Practice shows this doesn't make any problems for relatively small sites; despite it is possible to sniff all the traffic between the client and the server in any network between them, it is still hard for a potential attacker to take over the control on any of these networks, and the networks' personnel is in most cases just not interested in such an attack. If, however, the HTTP traffic actually gets sniffed, single-use passwords, being sent by email, will only be seen by the attacker when they get actually used (within the HTTP POST request that logs the user in) and become useless. To intercept the passwords when they are still of value, the attacker will need to sniff the SMTP channels as well, or break into the user's mailbox, or act as a man-in-the-middle rather than just sniffing the traffic, which is notably harder to perform.

WARNING: It is all still perfectly possible! In most of existing real life cases, breaking a user account at a small site is not worth bothering with all these espionage techniques, but your particular case may be different. If, for example, you expect some VIPs like the president or the prime minister of your country (or, well, leaders of political opposition) among your users, perhaps you should take some additional care, such as enabling HTTPS and making sure your outgoing SMTP traffic goes using TLS.

User account

User account name

It is important to note that user (login) names in Thalassa CMS are used as file (well, directory) names within the database so there are strict limits on what can and what can not be used as a user name. Furthermore, there are actually two different limitations: first, what can users specify as their login names, and, second, what the site's administrator can use as login names for accounts created manually.

For both cases, only low-case latin chars, digits and the underscore are acceptable. Any char not within this set, in any position of the name, will make the name non-working even if the account is created by the administrator, because Thalassa CGI rejects it long before any password checks or other actions.

For user names choosen by users on their own, additional limitations are in effect. The name can't be shorter than 2 nor longer than 16 chars (both limits are hardcoded), and it must start with a latin letter, neither a digit nor the underscore are accepted as the first char of such a name.

For example, names such as joe, bond007, mister_x, wolf__ are allowed for users to choose on sign up; names like x, 007, 7seas, _alice are not allowed for users to choose on their own, but the administrator still can make such accounts; and the following names will not work at all: John, JOHN, john.doe, john+doe, john-doe and so on.

User account data and implementation

Besides the login name, a user account has the following properties:

Within the user database directory, a subdirectory named _users contains the user accounts' information. For every account, a subdirectory in the _users directory is created, using the account name (login name) as the name for that directory. For example, if your database is located ad /var/sites/, and a new user registers with the name lizzie, her account's directory will be /var/sites/

In each user account directory there's a text file named _data, which contains NAME = VALUE pairs, just like for session files. The following NAMEs are used to store the properties enumerated above: status, realname, site, created, last_login, last_seen, last_pwdsent, last_mailchange, new_email, confirmation_code, roles.

As you may have noticed, login name and passwords are not on this list. The login name is always the same as the name of the user account's directory; as of passwords, they are stored as hardlinks to the _data file, created within the same directory. The passwords generated by Thalassa never contain the underscore, so the name _data itself can not be used as a password. Once a password is used, Thalassa CGI unlinks it, so it effectively stops to exist.

This is one of the simplest possible implementations that allows not to bother locking the directory, as both link and unlink operations are expected to be atomic. Using hard links is more efficient than, e.g., using empty files, as it doesn't involve making and deleting i-nodes, only the directory and the i-node of the _data file (you know, that link counter field) get changed.

The %[profile: ] macro

The profile macro allows to access the user account properties for an arbitrary (not necessary the current, which is sometimes important) user. The macro accepts at least two arguments, the first for the user login name, and the second is for the function name. It is necessary to note that the first argument gets lowercased, and all leading and trailing spaces get stripped from it before proceeding.

Two special functions are provided. The id function returns the accounts ID, which equals to the first argument after it is stripped and lowercased. The ifexists function accepts two additional arguments: the then value and the else value, and returns the former in case the requested account exists, otherwise returns the latter.

If the second argument of the macro call is neither id nor ifexists, it must be a NAME of the account's property, as user in the account file within the database (see the previous section for the list of these NAMEs). In this case the macro call returns the value of the respective account's property, or an empty string in case the property isn't found.

It might be useful to note that the macro doesn't check if the property name really belongs to any list, so actually you can (manually or with some third party software) add your own properties to your users' accounts simply by adding the desired NAME = VALUE pair to the _data file of the user account in question and access these properties with the profile macro.

Thalassa's notion of a valid email address

The Thalassa's notion of a valid email address is significantly different from what is specified by so-called standards. Thalassa CGI's internal email validator doesn't allow many things that are considered acceptable by the “standards”, but are never actually used.

Only the email address as such must be entered by the user. Things like John Doe <> or just <> will be rejected. Both '<' and '>' are considered inacceptable chars.

Quoted local-parts are not allowed, so, e.g., "this is crap", "", "foo"."bar", "" and the like are rejected despite some email software might (surprisingly, heh) consider these valid. Did you know about that? Well, this is even not the most ugly thing the “standards” suggest us to allow in email addresses. For example, they allow so called “comments” within the local-part in parentheses. Thalassa CGI doesn't allow that, so (comment) or johnny(comment) will be rejected.

“Standards” allow all of the !#$%&'*+-/=?^_`{|}~ to appear in the local-part, unquoted, with no limitations. Thalassa CMS, however, will reject !#$&'*?/^{|}~ in any position of the local-part, leaving the possibility to use %-+_ and, furthermore, will reject %-+ in the first position.

Just like the “standards” specify, the dot “.” in local-part can appear, but not as the first nor last char, and no more than one dot in a row.

As for domain-part, only FQDNs are accepted. IP addresses in square brackets, like [], are rejected.

The last thing to mention is that FQDN must consist of at least two tokens, so, e.g., john@doe is considered invalid. FQDNs are also checked to be correct, which means tokens must only consist of latin letters, digits and the dash “-”, and must not start nor end with a dash.

Data associated with email addresses

The Thalassa CGI program remembers all email addresses it has ever seen as users' emails, unless you remove the respective file manually. The data is stored in the _email subdirectory within the user database directory; a file is created for each email address.

Email address in question is converted into the file name as follows. First, the domain part of the address is taken, as is, and two underscores are appended to it; then goes the local part of the address. For example, will become example.com__john.doe.

As usual in the user database, each file contains NAME = VALUE pairs. Usually there are exaclty three pairs, with NAMEs status, user and date. The value for the user parameter is a user's login name, and the date is the date/time of the last status change.

The status' value can be one of the following:

Technically there's only one situation when Thalassa CGI actually forgets an email address: it is when a registered user requests to change the address to another, previously unknown one, but later cancels the request. However, in case an address is in the pending (that is, unconfirmed) state and more than 24 hours passed, the program acts as if it never saw that address.

If an email address was entered (so a confirmation code was sent to it), but not confirmed, Thalassa CGI will refuse to send more confirmation requests to this address for a month (strictly speaking, 31x24 hours).

One more “cooldown” limit is that users are not allowed to request email change more often than once in 24 hours. All three limits are presently hardcoded.

Account-related webforms and actions

Thalassa CGI program supports four actions related to user accounts: login, signup, profile and changemail. All the four don't accept any “commandline-like” arguments; what they do is determined solely by parameters sent in the POST request. We'll explain each of them in a dedicated subsection.

Login form

The login form is intended to be shown to users to let them sign in, that is, enter the username, one of the single-use password and let the system check if the password is valid. There's an additional function for the same form (and the same action): in case the user has no more single-use passwords or the remaining passwords are lost, the user should have an option to request new passwords. Hence, the form is expected to have two submit buttons.

For a POST request to a page for which the action parameter's value is “login”, the following input values are expected by Thalassa CGI:

One important thing to note here is that for both types of the request, and not depending on whether the request succeeded or failed, the work session gets bound to the login name and can not be unbound; in case the user makes a mistake in the login name, the only way to fix it is to close the session (with the rmsession request) and create a new one. However, to have the session bound to user name is not the same as to be logged in; being bound to the name does not grant any permissions, access or whatever, it only limits into which particular user account this particular session still may log in.

New passwords can only be sent to a registered user (that is, the account status must be active), and only if one of the following is true: either there are no more unused passwords for this user, or no less that 24 hours passed. In case everything's okay, an email containing new passwords (20 of them, the number is presently hardcoded) is composed and sent to the user's email address. See the service email section for details on service emails (well, emails like this) composition and sending.

For a sign-in request, the CGI program first checks whether the supplied password can (as a string) be a password generated by Thalassa, and then checks whether the given password exists for the particular user. If it is, the password is removed from the database, and the session changes its status to “logged in”. Otherwise, the only change to the session will be to bind it with the user name, without becoming logged in.

Registration (signup) form

The signup action expects the following field values:

First, the userid is checked for acceptability; after that, it is checked whether there's no such user yet. In case the record with such name is found in the database, then it is checked if it has the pending status, and how long it exists. For a record in the pending status older than 24 hours, the work continues as if there was no record at all; otherwise, registration is refused.

Next, the useremail is checked for validity, and in case the address is (lexically) valid, records are checked for the address. If the address is already known to the system, the only possibility to continue is if it is in the pending state and the state is more that 24 hours old; the registration is refused otherwise.

The username is only checked to be non-empty, and the usersite is not checked at all.

If no checks failed, a new user record (in the pending state) is created in the database, and an email containing a confirmation code is sent to the given email. After that, it is possible for the user to log into the system using the confirmation code as a password — and this is exactly what must be done to confirm the email address ownership. Logging in for the second time, the user should request new passwords.

It is strongly recommended to check from within the registration page template whether it is being displayed as a result of a successful signup request, and display a short version of the login form, with only the passcode input field an the submit button, having the login field as a hidden type input, so that the user can enter the confirmation code right there, not jumping around from page to page.

Profile data modification form

The profile action lets the user to change values for the visible (“real”) name and the site URL. This action expects two input field values, username and usersite. The only performed checks are whether the session is in the logged in state, and whether the username is not empty; if any of them fails, the action fails as well. If both checks are successful, new values are saved in the current user's record within the database.

Email changing form

The changemail action allows users to replace their email addresses. The user must first sign in.

What data is expected depends on whether the user is, according to the record in the database, already in progress of changing the email. If not, the CGI expects two input field values, named “newemail” (for the new address) and “passtoken” (for a single-use password).

The password is required for this operation since the version 0.3.00. Earlier versions would only expect the newemail value.

First of all, the CGI program checks the password and removes it from the list of passwords available for the user. This happens even if the user specifies an invalid or non-available email and receives the respective error message; that is, if a valid password was specified, it gets used up even if no action is taken. This is because Thalassa CMS is supposed to work on unencrypted sites; hence, a valid password, once transferred through the network, must become invalid to prevent its usage by those who manage to sniff the connection.

After that, the email address is checked for validity; for a valid address, the program checks if it already knows the address. The user is allowed to try climing the address if any of the following is true:

If there are no obstacles to go on, both the user account state and the email record state are changed accordingly, and a message containing a confirmation code is composed and sent to the email address being claimed.

In case the user is already in progress of email changing, the POST request to a page with the changemail action is handled differently. It is assumed there's a web form that lets the user either enter the confirmation code, or cancel the email change request; hence, the form should have two submit buttons, or there may be just two forms. Anyway, the program first checks if there's a value named cancel_change set to yes; if so, it also checks for the really field's value to be the string “really” (yes, both the name and the expected value are “really”), and if the check passes, both the user account and the record for the claimed email are changed accordingly.

Otherwise (that is, if there's no cancel_change value or if it is not yes), the program gets the value of the confirmcode parameter and checks whether it is the same as the confirmation code on the user's account file. If the check is successful, the program updates the user's account file and records for both the old and the new email addresses.

Presently, there's no limit for failed attempts. This might be one of the subjects for future work.

Related functions of the %[sess: ] macro

The %[sess: ] macro was introduced along with sessions, but only two of its functions, cookie and ifvalid, were described.

The ifhasuser, ifloggedin, ifcanchangemail and ifchangemail functions are conditional checkers; they all take two additional arguments: the then value and the else value, and return the former in case the condition is true and the latter if it is false. The conditions actually checked are as follows:

The following functions are used to access the data related to the user account; they don't need parameters:

The rest of the sess macro functions, related to the moderation queue (namely premodq, pqprev and pqnext), are described along with comments.

Service email configuration

In the text above (on this page) we've seen two situations when Thalassa has to send an email: a message with a confirmation code to let the user prove his/her ownership over the email address (both as a part of signup and email change — these cases use the same email composition procedure) and a letter containing new single-use passwords. These two cases are identified as confirm and passwords. As of present, there are no more cases when Thalassa sends “service” (that is, composed by Thalassa itself) email messages.

Thalassa supports the contact form which is also able to send emails, but in that case email text and subject are supplied by the user and there's a fixed pre-configured list of addresses such a message can be sent to.

Thalassa CGI program sends emails by launching an external command (presumably /usr/sbin/sendmail, but both the name of the command and its arguments are fully configurable) and supplying the message (header and body) to its standard input stream.

The [servicemail] ini file section is used to configure what exactly is to be sent and how. There are four parameters in the section:

For all four parameters, either confirm or passwords is always applied as the specifier, so you can configure different values for different cases.

The following context-specific macros are expanded within the values of the send_command, header and body parameters:

The following can serve as an example of the [servicemail] section:


  send_command = /usr/sbin/sendmail -bm -i
  +   -f '%[receiver]'

  header = From:
  +To: %[receiver]
  +Subject: %[subject]
  +MIME-Version: 1.0
  +Content-Type: text/plain; charset=us-ascii
  +Content-Transfer-Encoding: 8Bit
  +X-Sender-Software: Thalassa CGI script
  +X-Client: %[getenv:REMOTE_ADDR]:%[getenv:REMOTE_PORT]

  subject:passwords = access to

  body:passwords = Your new single-use passwords:
  +See you on the site!

  subject:confirm = email address confirmation

  body:confirm =
  +Someone (most probably you) entered %[receiver] at the site as his/her email address.  If it weren't you,
  +please simply ignore this message.  No more messages will be
  +sent to your address.  To confirm you really own this address,
  +please enter the following code on the site:

© Andrey V. Stolyarov, 2023, 2024