Ambivalence

Loren Siebert


Table of Contents

Preface
1. Concepts
What is MVC?
Command Processing Overview
PHP Authentication and Authorization Service (PAAS)
Controllers
Data Access Layer
Layouts and Views
2. Using Ambivalence
Configuring PHP
Configuring ambivalence.xml
Configuring paas.xml
Recommended directory structure
3. Templating Technologies
PHP
4. More Information
5. Credits
A. ambivalence.xml Schema Reference
B. paas.xml Schema Reference
C. Not-So-Frequently Asked Questions

Preface

Ambivalence is a Model-View-Controller framework for PHP web development. Based on the Java-based Maverick project, Ambivalence also offers clean MVC separation and an XML sitemap. Ambivalence provides an optional service to authenticate and enforce access controls upon users, based on the JBoss implementation of the J2EE Java Authorization and Authentication Service (JAAS).

Core features include:

  • Configuration using an XML sitemap.

  • Easily encapsulation of variable content within a common layout, look-and-feel, etc.

  • Clean separation of application security logic from functionality via an XML security map.

  • Object-oriented data access infrastructure based on PEAR.

Chapter 1. Concepts

What is MVC?

An excellent quick introduction of MVC aka "Model 2" concepts can be found here.

Command Processing Overview

This is a short explanation of how Ambivalence (and your PHP web application) processes requests.

Ambivalence processes commands that are mapped to a dispatcher through extension mapping. For example, all URIs which end with *.m are mapped to Ambivalence. The URI minus the extension is the command. For example, if a request comes in for http://your.site.com/runQuery.m, the command is "runQuery". However, if the request comes in for http://your.site.com/foo/runQuery.m, the command is "foo/runQuery".

What Ambivalence does with a command is determined by the Ambivalence configuration file (ambivalence.xml). Here is a complete (albeit simple) example:

<ambivalence version="1.0">
<command name="results">
        <controller file="controller/Results.class" />
        <view name="success" layout="layout/lookAndFeel.inc">
                <param name="content" value="content/results.inc"/>
                <param name="title" value="Triathlon Results- Results"/>
        </view>
        <view name="error" layout="layout/lookAndFeel.inc">
                <param name="content" value="content/errors/resultsError.inc"/>
                <param name="title" value="Triathlon Results- Results"/>
        </view>
</command>
</ambivalence>

This file defines a single command, results, which has two possible outcomes (views): "success" and "error".

When the results command is requested, the following steps occur:

  1. An instance of the controller class Results.class in the directory controller is created.

  2. The perform() method on the controller is called. This results in properties of the controller being populated with the HTTP request parameters prior to executing application logic.

  3. During execution, the perform() method defines the object that will be used as the model.

  4. The return value from perform() specifies the name of the view to render. This example will assume "success".

  5. The model is placed in the global PHP context as the variable $model.

  6. The PHP page lookAndFeel.inc in the layout directory is evaluated along with the appropriate view content (results.inc in the content directory), and these files use the data in $model to generate the view (in HTML, XML, etc).

  7. The result is sent back to the client.

One possibly confusing aspect of Ambivalence is how the actual URL file is used, and what goes into it. For a command named "foo" mapped from the URI /foo.m, there must be an actual file named foo.m in the document root for that virtual host. Where things get slightly confusing is that the file must not have anything in it except for comments, because by the time the PHP parser actually gets down to the foo.m file, all processing has already occurred and the results have already been sent to the client. This is because Ambivalence does all of its processing in the prepend phase, so the actual file itself has nothing left to do. The only reason for the actual file to be there is to act as a placeholder for Apache, because if it is missing, Apache will give a 404 error. Here is an example balances.m file for a page whose MVC logic might display account balances:

<?php
// placeholder for balances.m: This page fetches account balances for a user
?>

Once your application is in place, you will find that you spend more time modifying the model, views, and controllers of existing pages than you do setting up placeholders for brand new ones, so fiddling with the placeholder files is not something that consumes much time.

PHP Authentication and Authorization Service (PAAS)

Ambivalence contains an authentication and authorization service (called PAAS) that enforces access control to resources based on principals and roles. PAAS is loosely based on a Java specification of this service in the J2EE space known as JAAS, and on a concrete implementation of this specification known as JBossSX (part of the JBoss J2EE application server suite).

The purpose of PAAS is to separate the security constraints surrounding resources from the actual resources themselves. Doing this yields several benefits. First, the application doesn't need to know how to secure itself-- this is done for it by PAAS. So changes to the secutity restrictions to a resource do not require any changes to the actual application code. Second, separating out the security piece makes it easy to have different constraints depending on where the application is deployed. For instance, my application deployed in an intranet setting might allow people with the User role to see a directory listing, but in an extranet setting, I might require a Manager role. With PAAS, this is easily done by making a simple change to the XML configuration file.

Currently, PAAS allows for FORM-based login using some principal (e.g. username) and credential (e.g., password).

Controllers

Controllers are responsible for processing commands and building up the data model for the view to use when rendering results to the client. Ambivalence has a base controller class that populates the derived controller class with any HTTP GET/POST variables before calling the controller's perform() method. Your BaseController subclasses should have setter methods for any parameters you are expecting to get via HTTP GET/POST. For example, if you have an HTML form that contains a text input called "salary" then your controller must have a method called setSalary() that takes the variable as an input and sets a member variable for use later in the perform() method.

Note how the perform() method first calls the perform() method of its parent.

Below is an example of a controller class for a home page that inherits from the ControllerBase class. Note that the ControllerBase.class file doesn't need to be included here because it is already required by Ambivalence.

<?
require_once("model/AdvertDAO.class");
require_once("model/LoggedRequestDAO.class");

class Home extends ControllerBase
{
    var $rsv;
    var $prv;
    var $ad;
    var $salary;
    function getPopularRace() { return $this->rsv; }
    function getAdvert() { return $this->ad; }
    function getPopularResult() { return $this->prv; }
    function getSalary() { return $this->salary; }
    function setSalary($sal) { $this->salary = $sal; }
    function perform()
    {
        parent::perform();

        $advertDAO = new AdvertDAO;
        $loggedRequestDAO = new LoggedRequestDAO;

        $this->ad = $advertDAO->getAdvert("generic");
        $this->rsv = $loggedRequestDAO->getPopularRaceView();
        $this->prv = $loggedRequestDAO->getPopularResultView();
        return "success";
    }
}
?>

Data Access Layer

PAAS makes use of an object oriented data access layer built on top of the PEAR::DB library. The code is based on the Data Access Object design pattern, as outlined in this J2EE Blueprint. By using DAO's in your application and inheriting from the BaseDAO class, you get several benefits:

  1. All the functionality of the PEAR:DB class.
  2. Easy separation of datasource names from code, allowing for datasource specification and binding to happen at deployment time, rather than as a static part of the software development process.
  3. Application-level error handling mechanism for fatal errors that logs the error and displays your customized error page before halting execution.

Here is an example DAO that a controller might use to log requests to a database:

<?php
require_once("BaseDAO.class");

class LoggedRequestDAO extends BaseDAO
{
    function logRaceRequest($raceId, $remoteAddr)
    {
        $insertStatement =
            "insert into request_log (race_id, ip_address) " .
            "values (".$raceId.",'".$remoteAddr."')";

        $result = $this->db->query($insertStatement);
        if (DB::isError($result)) {
            $this->halt($result->getMessage(),$insertStatement);
        }
    }
}
?>

To enable Ambivalence's data access layer, you need to specify a default datasource to use. If you don't specify the name of your datasource name (DSN) file in the DAO constructor, Ambvivalence will search for the DefaultDSN.inc file in your include path. Here is an example of a DSN file:

<?php
$user = 'mysql';
$pass = 'mysql';
$host = 'localhost';
$db_name = 'dev_db';
?>

Note that you can specify multiple DSN's in your application. In fact, each DAO you create can connect to a different database--- all you have to do is specify which datasource file to use. In addition, you can have the same DSN file defined differently in different environments. For example, the DefaultDSN.inc file may point to a development database, a staging database, or a production database depending on where the code is being deployed.

Layouts and Views

Layouts allow an easy way for a common look and feel to be applied to many different views. The difference in Ambivalence is that the view doesn't know what layout it is in--- it doesn't reference it anywhere. The look and feel, or layout, for a view is set up in the ambivalence.xml file.

Layouts contain all the content that doesn't change depending on which view gets rendered for a command. Views contain the rest. Here is an example layout file where the title and content are set dynamically (in the ambivalence.xml file) but the rest of the page is static.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title><?=$title?></title>
</head>
<body bgcolor="#eeeeee">
	<? require($content) ?>
</body>
</html>

Chapter 2. Using Ambivalence

Configuring PHP

Ambivalence makes use of PEAR's database functionality for its authentication and authorization service, and also provides access to data via a Data Access Object pattern that you are encouraged to inherit from in your application. You are free to use other database packages out there (PHPlib) or roll your own, but if you leave things as they are, you will just need to make sure you have the PEAR libraries properly installed on your system. Newer versions of PHP come pre-bundled with PEAR, so you may not have to do anything, but if you see errors complaining that "DB.php" cannot be found, you should refer to the PEAR site for help.

Ambivalence also makes use of PHP4 native sessions. The very first thing Ambivalnce does on each invocation is call PHP's session_start() function. If you get any session-related errors, you should check to make sure your version of PHP properly supports sessions. The PHP main site has excellent references to help with this. As with PEAR, if you are using the latest version of PHP, everything should just work.

The first step in building an Ambivalence application is to configure Apache/ PHP so that the Ambivalence dispatcher is invoked for all commands with the ".m" extension.

  1. Unpack the Ambivalence code and put it in a system-wide library location (e.g., /usr/local/etc/ambivalence).
  2. If you haven't got an include directory for your application yet, create one, and make sure it is outside the document root for your application. This is important for security reasons. For the purposes of this example, I'll use /path_to/myapp/php-include as the include directory.
  3. If you haven't got a document root directory for your application yet, create one. For the purposes of this example, I'll use /path_to/myapp/htdocs as the document root directory.
  4. Tell PHP about both your include directory and the directory where Ambivalence lives. If you set this via an Apache VirtualHost directive, the line would look something like this:
    php_value include_path "./:/usr/local/lib/php/:/path_to/myapp/php-include/:/usr/local/etc/ambivalence/"
  5. Ambivalence requires an auto-prepend file to be used. In your include directory, create or modify the prepend.php file so that it contains these lines before anything else happens.
    <?php
    // The only three lines you need.
    require_once("ambivalence_init.inc");      // This one is crucial
    require_once("paas.inc"); // optional, used for authentication/authorization
    require_once("ambivalence.inc");          // This one is crucial
    ?>
    
  6. Tell PHP about your auto-prepend file, if you haven't done so already. If you set this via an Apache VirtualHost directive, the line would look something like this:
    php_value auto_prepend_file "/path_to/myapp/php-include/prepend.php"

Configuring ambivalence.xml

The heart of a Ambivalence application is the ambivalence.xml file. This file contains all the information about what commands are available, what code (controllers) are associated with specific commands, and what views can result from a command.

This file can reside anywhere in your PHP include path, but for simplicity you may want to just put it in your application-specific include directory (e.g., /path_to/myapp/php-include).

By way of convention, the following constructs are usually considered equivalent and can be used interchangably:

  • <element attr="blah"/>

  • <element><attr value="blah"/></element>

See the appendix for an explanation of the options available in the Ambivalence configuration file.

Configuring paas.xml

The paas.xml file is currently used by the authentication and authorization system to specify application-wide login modules along with any security constraints imposed on the application. If your application is not using this (i.e., your auto-prepend file does not include paas.inc), then you do not need this file. Otherwise, Ambivalence will look for this file in your PHP include path.

See the appendix for an explanation of the options available in the paas.xml file.

Recommended directory structure

For small applications, it's easy to stick model, view, and controller code all in one place, but when small applications grow large, it pays to have some structure in place already. Here is a sample directory tree for an Ambivalence application.

|
+->htdocs (The web server document root containing the placeholder *.m files, along with any images, plain HTML files, or even PHP files that aren't processed by Ambivalence)
|
+->php-include (contains all XML config files and DefaultDSN.inc, and prepend file if not set up elsewhere)
|
+->model (The Data Access Objects and data structure files)
|
+->controller (Command processing classes here)
|
+->layout ("Look-and-feel" wrappers)
|
+->content (Specific views to be wrapped by layouts)
    |
    +->errors (Error-related views)

Chapter 3. Templating Technologies

Table of Contents

PHP

PHP

Using PHP as the templating language with Ambivalence is as straightforward as can be, in that you can just access the properties of the $model object directly in your view code, along with any other variables you may have set up in the ambivalence.xml file.

The biggest risk in using PHP as the templating language is that view developers must be vigilant in using only the aspects of PHP required to display data from the model. That is, it is very easy for someone to break the MVC separation by putting business logic or even a database call in the view code. Following releases of Ambivalence will allow for proper restricted templating (perhaps even via a port of the excellent Velocity language from the Jakarta project) so the onus is not on the view developers to know what not to use when authoring view files.

But if you simply limit yourself to a few directives, PHP can effectively function as a templating language for your Ambivalence project, without any of the extra overhead that yet another layer would introduce. If you use the shorthand for opening PHP code segments, the view code stays much easier to read. Here are the directives you should use along with some examples:

Set
Assign values to local variables.
<? $foo = "bar";  ?>
<? $bar = 7.7;  ?>
<? $wholename = $firstName." ".$lastName;  ?>
<? $salary = $model->getSalary();  ?>
<? $nameArray = $model->getNames();  ?>
If-Else Statements
This allows for text to be included when the web page is generated, on the conditional that the if statement is true. For example:
<? if ($salary > 1000000) : ?>
<strong>You are rich!</strong>
<? endif; ?>
Foreach Loops
This allows for looping. In this example, note how the shorthand for echo/print is used to keep the view code clean looking:
<? foreach ($model->notes as $note) : ?>
One note per line... <?= $note ?>
<? endforeach; ?>
Include
Insert and evaluate a piece of a view stored in some file. Note that the included file must also conform to using only these directives. The included file is free to use the $model object in the same way, as it is scoped at the same level.
<? include ("content/fragments/advertisingBlock.inc"); ?>

Chapter 4. More Information

Ambivalence is hosted by SourceForge. For updated information, visit the website or the project page.

Downloads are available here.

For help using Ambivalence and discussion of the future direction of Ambivalence, subscribe to the amb-user mailing list.

A comprehensive sample application built with Ambivalence is in the works. One real world application built with Ambivalence is the Triathlon Results Application.

Chapter 5. Credits

Ambivalence is an Open Source project distributed with an Apache-style license. It was created by Loren Siebert based on original Java work done by Jeff Schnitzer and Scott Hernandez, along with ideas from the J2EE world. All the good ideas come directly from these projects, and all the errors are my own. The first public release was 21 July 2002.

This product includes software developed by the Apache Software Foundation (http://www.apache.org/).

Appendix A. ambivalence.xml Schema Reference

Table of Contents

ambivalence - The root element of Ambivalence configuration
command - Defines the behavior of one command
controller - Associates a controller with a particular command
view - Defines a view as either part of a command or a globally referencable view

Name

ambivalence — The root element of Ambivalence configuration

Synopsis

Content

ambivalence ::= (command+)

Attributes

NameTypeDefault
versionCDATARequired. Must be "1.0"

Descripton

ambivalence is the root element of Ambivalence configuration. It contains everything that deals with model/view/controller specification.

Attributes

version. The version of the schema. Must be 1.0.

Examples

<ambivalence version="1.0" >
  <command>
    ...
  </command>
  <command>
    ...
  </command>
  <command>
    ...
  </command>
</ambivalence>

Name

command — Defines the behavior of one command

Synopsis

Content

command ::= (controller?, view+)

Attributes

NameTypeDefault
nameCDATARequired

Descripton

A command is the basic unit of work in Ambivalence. When an HTTP request is processed by Ambivalence, the URI is examined to determine which command to execute. If no command matches the URI, Ambivalence returns an error. Note that there are two types of "not found" cases, and they differ. One results from the actual placeholder file associated with the URI being missing. Apache handles this with a 404 error. The other occurs when the placeholder exists but the actual command isn't defined in the ambivalence.xml file, in which case Ambivalence returns an error explaining what happened.

The contents of a command element determine the runtime behavior. You can define a controller class and some number of views, one of which will be rendered for every request.

Note that the controller child element is optional, but if it is left out, there can be only one view element since a controller is needed to select from the available views.

Attributes

name.  The URI to associate with this command, minus the context root, extension, or query parameters. For example, "welcome" and "protected/reportDetail".

Examples

<ambivalence version="1.0" >
    <command name="portfolio">
      <view layout="layout/lookAndFeel.inc">
	  <param name="content" value="content/portfolio.inc"/>
	  <param name="title" value="My Portfolio"/>
      </view>
    </command>
</ambivalence>

Name

controller — Associates a controller with a particular command

Synopsis

Content

controller ::= [Special]

Attributes

NameTypeDefault
fileCDATARequired

Descripton

A controller is a user class which "controls" how the request is handled. Here is where user code is executed, the model is prepared, and which view to render is chosen.

Attributes

file.  The full name of the class file which will act as the controller. This class must inherit from ControllerBase.class.

Examples

<ambivalence version="1.0">
<command name="results">
        <controller file="controller/Results.class" />
        ...
</command>
</ambivalence>

Note: In this example, Ambivalence will instantiate a Results object out of the Results.class class code found in the controller directory, which of course must be in the PHP include path.


Name

view — Defines a view as part of a command

Synopsis

Content

view ::= (param*,[Special])

Attributes

NameTypeDefault
nameCDATA
layoutCDATARequired

Descripton

A view renders the model to the response. Zero or more view elements can appear under a command element. Here they are named (with the name attribute) and designate the options available in the command for rendering the model. The layout attribute allows command-specific presentation to be separated from a common look-and-feel or template applied throughout a site. The layout can be defined in one file, and any changes to this file will automtically be reflected throughout all the views that use that layout.

Note that if a command defines only a single view, it does not need to be named. Regardless of what the controller returns, the one view will be rendered.

param child elements populate the global context variables available to the view code. In the example below, the parameter "title" is set to "Triathlon Results", so the variable $title is populated with this value in the global scope. The meaning of a param is specific to the particular View type.

Attributes

name.  This is the string that the Controller will return to identify which view to render. Can be omitted if there is only one view for a command.

layout.  The "look and feel" or parent view to be wrapped around the current view.

Examples

<ambivalence version="1.0">
<command name="results">
        <controller file="controller/Results.class" />
        <view name="success" layout="layout/lookAndFeel.inc">
                <param name="content" value="content/results.inc"/>
                <param name="title" value="Triathlon Results"/>
        </view>
        <view name="error" layout="layout/lookAndFeel.inc">
                <param name="content" value="content/errors/resultsError.inc"/>
                <param name="title" value="Triathlon Results- Error"/>
        </view>
</command>
</ambivalence>

Appendix B. paas.xml Schema Reference

Table of Contents

paas - The root element of the PAAS configuration
datalogin - Defines the datasource name and the queries to use for both authentication and authorization.
security-constraint - The security-constraint element is used to associate security constraints with one or more web resource collections.
login-config - The login-config element is used to configure the authentication method that should be used (currently only FORM is supported), and the attributes that are needed by the form login mechanism.
web-resource-collection - The web-resource-collection element is used to identify a subset of the resources within a web application to which a security constraint applies.
auth-constraint - The auth-constraint element indicates the user roles that should be permitted access to this resource collection.
form-login-config - The form-login-config element specifies the login and error pages that should be used in form based login.

Name

paas — The root element of the PAAS configuration

Synopsis

Content

paas ::= (datalogin, security-constraint+, login-config)

Attributes

NameTypeDefault
versionCDATARequired. Must be "1.0"

Descripton

paas is the root element of the configuration for the PHP Authentication and Authorization Service (PAAS). It contains everything that deals with authentication and authorizization or users for web application resources.

Attributes

version. The version of the schema. Must be 1.0.

Examples

<paas version="1.0" >
  <datalogin>
    ...
  </datalogin>
  <security-constraint>
    ...
  </security-constraint>
  <security-constraint>
    ...
  </security-constraint>
  <login-config>
    ...
  </login-config>
</paas>

Name

datalogin — Defines the datasource name and the queries to use for both authentication and authorization.

Synopsis

Content

datalogin ::= (dsnFileName, principalsQuery, rolesQuery)

Attributes

NameTypeDefault
dsnFileNameCDATARequired
principalsQueryCDATARequired
rolesQueryCDATARequired

Descripton

The datalogin element tells PAAS which datasource to use when validating users and their credentials, as well as the queries to use. The principalsQuery and rolesQuery attributes specify queries to first authenticate and then authorize the user. The "?" in the queries is replaced at runtime by PAAS with the principal passed in to it (usually a username). It is up to you to write these queries in a way that is legal for your database.

Attributes

dsnFileName.  The dsnFileName attribute points to the file containing the connection information for the datasource associated with the security information. It can be the same one used by the application by default (DefaultDSN.inc), or it can be a separate one as shown in the example below.

principalsQuery.  The principalsQuery should return a single value.

rolesQuery.  The rolesQuery should return a column of possible roles for that principal. Of course, the column may be emtpy or have only one element in it.

Examples

<paas version="1.0">
    <datalogin>
        <dsnFileName>SecurityDSN.inc</dsnFileName>
        <principalsQuery>select password from Users where username = '?'</principalsQuery>
        <rolesQuery>select role from Roles where username = '?'</rolesQuery>
    </datalogin>
</paas>

Name

security-constraint — The security-constraint element is used to associate security constraints with one or more web resource collections.

Synopsis

Content

security-constraint ::= (web-resource-collection+, auth-constraint)

Attributes

none

Descripton

A security-constraint specifies which roles are sanctioned to access certain resources deemed to be protected.

Attributes

none

Examples

<paas version="1.0">
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Restricted</web-resource-name>
            <description>Authorized users only</description>
            <url-pattern>/au/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>AuthenticatedUser</role-name>
        </auth-constraint>
    </security-constraint>
</paas>

Name

login-config — The login-config element is used to configure the authentication method that should be used (currently only FORM is supported), and the attributes that are needed by the form login mechanism.

Synopsis

Content

login-config ::= (auth-method, form-login-config)

Attributes

NameTypeDefault
auth-methodCDATAFORM

Descripton

The login-config element tells PAAS which pages to show for logging in a user and for displaying an error during the login process.

Attributes

auth-method.  Currently only "FORM" based authentication is available.

Examples

<paas version="1.0">
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login.m</form-login-page>
            <form-error-page>/loginError.m</form-error-page>
        </form-login-config>
    </login-config>
</paas>

Name

web-resource-collection — The web-resource-collection element is used to identify a subset of the resources within a web application to which a security constraint applies.

Synopsis

Content

web-resource-collection ::= (web-resource-name, description, url-pattern)

Attributes

NameTypeDefault
web-resource-nameCDATARequired
descriptionCDATARequired
url-patternCDATARequired

Descripton

The web-resource-collection element tells PAAS which url-pattern to restrict, and gives this restriction a name and a description.

Attributes

web-resource-name.  The web-resource-name attribute is just a human-readable name for this restriction.

description.  The description is just a human-readable description for this restriction.

url-pattern.  The url-pattern is a regular expression denoting which URLs should be restricted.

Examples

See security-constraint.

Name

auth-constraint — The auth-constraint element indicates the user roles that should be permitted access to this resource collection.

Synopsis

Content

auth-constraint ::= (role-name)

Attributes

NameTypeDefault
role-nameCDATARequired

Descripton

The auth-constraint element tells PAAS which role is allowed to access this resource.

Attributes

role-name.  The role-name attribute is a text string that corresponds to a role in the table that matches users to roles, specified in the rolesQuery attribute of the datalogin element.

Examples

See security-constraint.

Name

form-login-config — The form-login-config element specifies the login and error pages that should be used in form based login.

Synopsis

Content

form-login-config ::= (form-login-page, form-error-page)

Attributes

NameTypeDefault
form-login-pageCDATARequired
form-error-pageCDATARequired

Descripton

The form-login-config element tells PAAS which page to load when the security interceptor detects an invalid access attempt to a restricted resource, and which page to show if the login attempt fails.

Attributes

form-login-page.  The form-login-page attribute becomes the target of a redirect when a login is required by the security interceptor.

form-error-page.  The form-error-page attribute becomes the target of a redirect when a login attempt fails.

Examples

See login-config.

Appendix C. Not-So-Frequently Asked Questions

B.1. Why did you choose the name Ambivalence?
B.2. Huh?
B.3. What public websites are using Ambivalence?
B.1.

Why did you choose the name Ambivalence?

Because Maverick was already taken.

B.2.

Huh?

grep -i m.*v.*c /usr/share/dict/words

B.3.

What public websites are using Ambivalence?