JabChapter 7
From WikiContent
Contents |
User Registration and Authorization
With all of Jabber's building blocks at
our fingertips, we can now take a look at two fundamental processes that
revolve around Jabber users:
- The registration, or creation, of Jabber user accounts
- The authentication of a Jabber user account, including the different
authentication methods These processes involve some of the things we learned about in Chapter 5 and Chapter 6, so we'll take a look at them again along the way:
- The XML stream header
- The jabber:iq:register namespace
- The jabber:iq:auth namespace At the end of this chapter, we
will create a small utility program with which we can register users on a server. We can then use this utility program to create users as required for further recipes in this book.
XML Stream Flow
User registration and user authorization are two
processes that take place at the start of an XML stream, immediately
following the XML stream header exchange. The Jabber server is designed
to check for and process any user registration request or
authentication request before anything else.
This makes sense, as outside of a session (a session is created for a user when the authentication completes successfully), any other element—a <message/> element, a <presence/> element, or an <iq/> element that doesn't contain an extension qualified by the jabber:iq:register or jabber:iq:auth namespaces—is invalid. So if any of these other elements are sent before authentication has taken place (that is, before a session has been created for that connection), they are queued and processed after authentication.
Figure 7-1 shows the general flow within an XML stream, with regard to where the registration and/or authentication steps happen.
-</code>
User Registration
User registration with a Jabber server
specifically means the creation (or modification) of a user account for
the JSM component—the component that provides the basic IM services and
has a notion of users and user sessions. It takes place at the start of
a connection to a Jabber server, as does user authentication, as shown
in Figure 7-1.
Let's take a look at the XML fragments involved in a typical user registration process. Example 7-1 shows the XML stream header exchange and the IQ packets in the jabber:iq:register namespace.
A typical user registration process First is the XML stream header
exchange:
SEND: <?xml version='1.0'?> <stream:stream to='yak'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'>
RECV: <?xml version='1.0'?> <stream:stream xmlns:stream='http://etherx.jabber.org/streams' id='3B2DB1A7' xmlns='jabber:client' from='yak'>
Then the client sends a request to discover what information must be
passed to the Jabber server to register a new user:
SEND: <iq type='get'> <query
xmlns='jabber:iq:register'/> </iq>
RECV: <iq type='result'> <query xmlns='jabber:iq:register'> <instructions> Choose a username and password to register with this server. </instructions> <name/> <email/> <username/> <password/> </query> </iq>
The client does as asked and sends the required information, which
results in a successful new user registration:
SEND: <iq type='set'> <query
xmlns='jabber:iq:register'> <username>leslie</username>
<password>secret</password>
<email>lel@plevna.com</email> <name>Leslie
Hawke</name> </query> </iq>
RECV: <iq type='result'/>
Configuration and Module Load Directives
Lets start by reviewing
the relevant configuration in jabber.xml. User registration is a JSM
feature, and we find two places of interest in that component instance
definition. The first is in that instance's configuration, in the
section qualified by the jabber:config:jsm namespace:
<register notify="yes"> <instructions> Choose a
username and password to register with this server.
</instructions> <name/> <email/> </register>
The second is in that instance's connection method, showing the
mod_register module being loaded. The module plays a major part in
handling the registration process, but there are others, too, as we will
see:
<load main="jsm"> <jsm>./jsm/jsm.so</jsm>
<mod_echo>./jsm/jsm.so</mod_echo>
<mod_roster>./jsm/jsm.so</mod_roster>
<mod_time>./jsm/jsm.so</mod_time>
<mod_vcard>./jsm/jsm.so</mod_vcard>
<mod_last>./jsm/jsm.so</mod_last>
<mod_version>./jsm/jsm.so</mod_version>
<mod_announce>./jsm/jsm.so</mod_announce>
<mod_agents>./jsm/jsm.so</mod_agents>
<mod_browse>./jsm/jsm.so</mod_browse>
<mod_admin>./jsm/jsm.so</mod_admin>
<mod_filter>./jsm/jsm.so</mod_filter>
<mod_offline>./jsm/jsm.so</mod_offline>
<mod_presence>./jsm/jsm.so</mod_presence>
<mod_auth_plain>./jsm/jsm.so</mod_auth_plain>
<mod_auth_digest>./jsm/jsm.so</mod_auth_digest>
<mod_auth_0k>./jsm/jsm.so</mod_auth_0k>
<mod_log>./jsm/jsm.so</mod_log>
<mod_register>./jsm/jsm.so</mod_register>
<mod_xml>./jsm/jsm.so</mod_xml> </load>
The <register/> configuration section looks familiar; the
contents are very similar to the IQ-result returned in response to the
IQ-get in the jabber:iq:register namespace in Example 7-1.
Indeed, to respond to an IQ-get, the mod_register module looks for
this <register/> section and formulates the contents into
a reply, making a simple modification as it goes: it appends tags for
the two fields <username/> and <password/> as these two are
always required, regardless of configuration.
Removing the <register/> section from the configuration effectively blocks the registration process and returns an appropriate error to the user:
SEND: <iq type='get'> <query
xmlns='jabber:iq:register'/> </iq>
RECV: <iq type='error'> <query xmlns='jabber:iq:register'/> <error code='501'>Not Implemented</error> </iq>
Removing the reference to the mod_register module from the component
instance's connection method:
<!-- <mod_register>./jsm/jsm.so</mod_register>
-->
has the same effect.
More information on the configuration discussed here can be found in Section 4.4.
Step by Step
Taking the stream contents in Example 7-1 step by
step, what do we see?
The XML stream header exchange
Although the XML declaration
that is sent immediately preceding the opening
<stream:stream/> root tag is optional, it's not a good
idea to leave it out. While the Jabber server currently does not enforce
its presence, future implementations may do so.[1] The Jabber server will always send an XML
declaration in response. In both cases—in both streamed XML
documents—the encoding is assumed to be UTF-8.
For the most part, the rest of the <stream:stream/> root tag is static. The namespace qualifying the stream content is jabber:client (which is the only namespace acceptable when making such an XML stream connection to the c2s—Client-to-Server—component listening on port 5222), and the namespace qualifying the stream itself is fixed at http://etherx.jabber.org/streams.[2]
The only thing that is going to be dynamic is the to attribute, which is used to specify the Jabber server name. Note that this is the logical name, the internal name, of the Jabber server. In our example, we've already resolved the physical hostname, yak, and connected to port 5222; the to attribute is to specify the virtual Jabber host, which in many cases—including an out-of-the-box jabber.xml configuration—has the same name as the physical host.
If our Jabber server has just a single virtual host, we can use an <alias/> configuration tag in the c2s component instance configuration, as described in Section 4.6.3, to remove the requirement of specifying the to attribute.
Using:
<alias to='yak'/>
will set the default alias to yak and indeed override any
value specified in the to attribute.
"Required" fields
The list of fields returned in the response
to the IQ-get in the jabber:iq:register namespace is actually
supposed to be a list of mandatory fields:
- <name/> * <email/> * <username/> * <password/>
However, with the current version of the Jabber server (1.4.1, which is our reference version for this book), this is not the case.
The <username/> and <password/> fields are mandatory; not supplying them or supplying an invalid username or password will result in an error:
SEND: <iq type='set'> <query
xmlns='jabber:iq:register'> <username>leslie</username>
<email>lel@plevna.com</email> <name>Leslie
Hawke</name> </query> </iq>
RECV: <iq type='error'> <query xmlns='jabber:iq:register'> <username>leslie</username> <email>lel@plevna.com</email> <name>Leslie Hawke</name> </query> <error code='406'>Not Acceptable</error> </iq>
However, currently, failing to supply any of the other fields—the fields
that are specified in the <register/> section of the JSM
instance's configuration—will not result in an error. This may be fixed
in a later release. In any case, it's no great loss; the details are
simply stored in the user's spool file on the server, and right now
there's only one situation where this information is subsequently used:
in answering a browse request made to a user's JID, the server looks up
the <name/> tag from the registration data stored and
uses the value there in the browse response:
SEND: <iq type='get' to='dj@yak'> <query
xmlns='jabber:iq:browse'/> </iq>
RECV: <iq type='result' from='dj@yak' to='sabine@yak/Work'> <user name='DJ Adams' xmlns='jabber:iq:browse' jid='dj@yak'/> </iq>
Set without get
In many of the examples of IQ throughout the
book, we've seen a standard pattern: IQ-get -> IQ-result
-> IQ-set -> IQ-result. This pattern isn't any
different here, but it's not essential. Bearing in mind that
registration field requirements aren't going to change that often, and
even if they do, the only ones that are enforced are
<username/> and <password/>, you can get
away with forgoing the IQ-get and cutting straight to the chase with an
IQ-set. This isn't a recommendation to do that, merely an observation,
as it's always good practice to ask first.
Still no connection
After registering a new user, note that
there's still no session. Only after successful authorization (see
later in this chapter) is a session created. Although used in
authentication (and so implicitly in session creation), the value of the
id attribute in the XML stream header returned by the server
(which has the value 3B2DB1A7 in Example 7-1) is a
connection ID, not a session ID.
So, what can we do at this stage? Well, one of two things: register another user (yes!) or proceed to the authentication stage. Basically, reaching the end of the registration process, we're back where we started; a "raw" connection where only one of two sequences is valid: the jabber:iq:register or jabber:iq:auth sequence.
Passwords
You may be wondering about the plaintext nature of
the password sent in the registration process. Although the Jabber
server offers different types of password-based authentication, there's
a "bootstrap" process required to get the password to the server in the
first place. There's currently no way around the fact that the server
must at one time receive the password in all its plaintext glory. After
receiving it, there are authentication processes that don't use the
plaintext password again.
So if you're concerned about the security of this registration phase, consider doing it over a secure (SSL) connection to the server.
We will look at the detail of the different authentication mechanisms later in this chapter; however, it is worth noting here, in the context of the registration process, that the JSM modules that implement the mechanism are responsible for storing the password when it's received. The mod_register module actually registers the user, but it is the mod_auth_plain and mod_auth_0k modules that actually store the password when received.[3]
Passwords are stored on another occasion—when a user wishes to change his password. This procedure is also covered by the jabber:iq:register namespace, albeit in a different context—the context of a session. While a jabber:iq:register-based IQ conversation outside the context of a session is for registering a user, a similar conversation within the context of a session, that is, after a user has authenticated, is used to change the user's password and other registration details. Among other reasons, this is for security: a session context implies the user has identified and authenticated himself, and so has the authority to change the password.
Example 7-2 shows a typical IQ-set to change a password.
Changing a password with jabber:iq:register
SEND: <iq type='set' id='pass_4' to='yak'> <query
xmlns='jabber:iq:register'>
<password>newsecret</password> </query> </iq>
RECV: <iq type='result' id='pass_4' from='yak' to='dj@yak/Work'/>
The to attribute is required here, to make sure the query is
handled by the server itself. We can also see evidence that the context
of this exchange is within a session in the value of the to
attribute on the IQ-result packet—the JID dj@yak/Work includes
a resource suffix, which implies a session (a resource must be specified
in the authentication process—see later in this chapter). And the
specification of a <username/> is not necessary, as the
server will stamp the incoming IQ-set anyway with the JID associated
with the user's session.
If you had made an IQ-get, as recommended earlier, before doing the IQ-set to change the password, the result would have looked like this:
RECV: <iq type='result' to='dj@yak/Work' id='pass_2'
from='yak'> <query xmlns='jabber:iq:register'>
<password/> <instructions> Choose a username and password to
register with this server. </instructions> <name/>
<email/>
<key>9a6957b7f69535274afa5c134fb4d916c5d5c20b</key>
<registered/> </query> </iq>
We see that, as in the registration IQ-get outside the session context,
the contents of the <register/> section of the JSM
instance's configuration have been inserted (the
<instructions/>, <name/>, and
<email/> tags). Additionally, we have a
<key/> tag, as a simple security token (described in
Section 6.2.11) and a <registered/> tag. The
<key/> is not actually checked in the current
implementation and therefore does not have to be supplied in the return
IQ-set packet. And the <register/> tag is merely a flag
telling us that the user is already registered.
Use of the jabber:iq:register namespace in conversation with the JSM in a session context is not limited to changing passwords; you can modify the rest of the registration details supplied when the user was created—in this case, the <name/> and <email/> information. In fact, with the current implementation, because of the lack of checks, you can specify your own fields in the jabber:iq:register IQ-set, in both contexts. But don't do it; it's a habit that will probably be impossible to keep up in later releases of the server.[4]
It almost goes without saying that because IQs in the jabber:iq:register namespace are handled differently in a session context, you can't register a new user once your session has started; you must end it. To end it, the XML stream must be closed with a </stream:stream>, and a new connection and stream must then be created.
Reversing a user registration
The opposite of registering a
user is unregistering a user. This is not the same as removing that
user altogether. When the <remove/> tag, described in
Section 6.2.11, is used in a jabber:iq:register-qualified
IQ-set during a user session, the user is unregistered. That is,
all the information held in the user's spool file is removed. But the
spool file itself is not removed until the Jabber server is shut down.
This means that even if you <remove/> a user, the
username will still exist until the server is cycled, causing an error
if the same username is used in a new registration attempt:
RECV: <iq type='error'> <query
xmlns='jabber:iq:register'> <username>dj</username>
<password>secret</password> </query> <error
code='409'>Username Not Available</error> </iq>
This may well be fixed in a later release of the Jabber server.
A note on error messages
Various errors can occur during user
registration. They are, on the whole, fairly plain and easy to
understand. But because of the way the server has been written, you
might be surprised at what error message you receive in certain
circumstances.
Because the required fields <username/> and <password/> are checked before looking to see whether or not there is a <register/> section in the JSM instance configuration, you will always receive a 406 "Not Acceptable" instead of a 501 "Not Implemented" if you don't supply those fields, even if the registration mechanism has been turned off.
Likewise, if you specify a username that already exists, you will receive a 409 "Username Not Available" instead of a 501 "Not Implemented."
Of course, if you do an IQ-get with the jabber:iq:register namespace beforehand, you will receive the "correct" error—good practice pays!
User Authentication
There are similarities between user
registration and user authentication:
- Authentication must take place outside of a session context (it
doesn't really makes sense inside a session context, anyway). It is perfectly possible to perform a user registration step followed by a user authentication step (for any user) in the same XML stream. * Any packets sent before the authentication step (apart from user registration packets) are queued until after the authentication step has been completed. * The IQ-get in the jabber:iq:auth namespace is not mandatory but is recommended (even more strongly than the recommendation for the IQ-get in the jabber:iq:register namespace). Example 7-3 shows a typical authentication process, including the XML stream header exchange.
A typical user authentication process Here the authentication
process immediately follows the initial XML stream header exchange:
SEND: <?xml version='1.0'?> <stream:stream to='yak'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'>
RECV: <?xml version='1.0'?> <stream:stream xmlns:stream='http://etherx.jabber.org/streams' id='1ED34A55' xmlns='jabber:client' from='yak'>
We ask the server about the authentication methods available for our
specific user:
SEND: <iq type='get'> <query xmlns='jabber:iq:auth'>
<username>dj</username> </query> </iq>
RECV: <iq type='result'> <query xmlns='jabber:iq:auth'> <username>dj</username> <password/> <digest/> <sequence>496</sequence> <token>3B2DEEC0</token> <resource/> </query> </iq>
Because we're connecting here to the server with telnet and don't
have any digest utilities handy, we decide to use the simplest
authentication method and send our password in plaintext. The server
checks the credentials and gives us the thumbs-up.
SEND: <iq type='set'> <query xmlns='jabber:iq:auth'>
<username>dj</username>
<password>secret</password>
<resource>laptop</resource> </query> </iq>
RECV: <iq type='result' id='pthsock_client_auth_ID'/>
At this stage, we have a session.[5]
Configuration and Module Load Directives
The c2s
component contains a configuration directive related to the
authorization process:
<service id="c2s"> <load>
<pthsock_client>./pthsock/pthsock_client.so</pthsock_client>
</load> <pthcsock xmlns='jabber:config:pth-csock'> <alias
to='yak'/> <authtime/> ... </pthcsock>
</service>
This <authtime/> tag is used to set the time limit, in
seconds, within which authentication should be completed, starting to
measure at the time the connection was made. See Section 4.6.3 for more
details.
There is also an undocumented tag, <auth/>, which can be specified in the JSM instance configuration (for example, after the <register/> section) and with which you can specify an external component that is to handle authentication in place of the standard JSM modules (mod_auth_*). The JSM module load directives specify the modules that handle authentication:
<load main="jsm"> <jsm>./jsm/jsm.so</jsm>
<mod_echo>./jsm/jsm.so</mod_echo>
<mod_roster>./jsm/jsm.so</mod_roster>
<mod_time>./jsm/jsm.so</mod_time>
<mod_vcard>./jsm/jsm.so</mod_vcard>
<mod_last>./jsm/jsm.so</mod_last>
<mod_version>./jsm/jsm.so</mod_version>
<mod_announce>./jsm/jsm.so</mod_announce>
<mod_agents>./jsm/jsm.so</mod_agents>
<mod_browse>./jsm/jsm.so</mod_browse>
<mod_admin>./jsm/jsm.so</mod_admin>
<mod_filter>./jsm/jsm.so</mod_filter>
<mod_offline>./jsm/jsm.so</mod_offline>
<mod_presence>./jsm/jsm.so</mod_presence>
<mod_auth_plain>./jsm/jsm.so</mod_auth_plain>
<mod_auth_digest>./jsm/jsm.so</mod_auth_digest>
<mod_auth_0k>./jsm/jsm.so</mod_auth_0k>
<mod_log>./jsm/jsm.so</mod_log>
<mod_register>./jsm/jsm.so</mod_register>
<mod_xml>./jsm/jsm.so</mod_xml> </load>
Each of these modules, mod_auth_plain, mod_auth_digest, and
mod_auth_0k can play a role in the authentication process. As
mentioned in Section 4.4.4, they provide different authentication
methods, these methods being reflected in their names:
- mod_auth_plain: plaintext * mod_auth_digest: digest
- mod_auth_0k: zero-knowledge You have a certain amount of flexibility
as administrator to determine what methods are made available on your Jabber server. If you want to offer all three, do nothing. If you want to offer only the zero-knowledge method, comment out or otherwise remove the other two definitions from the list of module load directives:
<!-- <mod_auth_plain>./jsm/jsm.so</mod_auth_plain>
<mod_auth_digest>./jsm/jsm.so</mod_auth_digest> -->
If you want to offer the digest method, you must include the module
load directives for both mod_auth_plain and mod_auth_digest, as
the latter is merely an extension—a "parasite"—upon the former.
Let's look at each of these authentication methods in turn.
Plaintext authentication method
The plaintext authentication
method works as you would expect and is the default lowest common
denominator method supplied with the Jabber server. It is provided by
the mod_auth_plain module.
- Method
- The password is transmitted in the XML stream, inside the
- <password/> tag in the jabber:iq:auth IQ-set
- packet, from the client to the server in plaintext, where it is
- compared to the password stored, also in plaintext, on the server. :
- When a password is changed, using a jabber:iq:register IQ-set
- as described earlier in this chapter, mod_auth_plain stores
- the password, as received, in the user's spool file.
- Advantages
- This method is by far the simplest to implement on the client side. It
- is also useful for debugging and testing purposes as it can be used in
- a connection "by hand" via telnet, not requiring any extra
- computation such as the digest and zero-knowledge methods do.
- Disadvantages
- It's insecure, on two levels. First, the password is transmitted in
- plaintext across the wire from client to server. The risk can be
- minimized by encrypting the whole connection using SSL. Second, the
- password is stored in plaintext on the server, which may be
- compromised.
Digest authentication method
The module that provides the
digest authentication method, mod_auth_digest, works in conjunction
with the plaintext module, mod_auth_plain. It provides a way to
avoid having to send the plaintext password across the wire.
- Method
- The digest method is similar to the plaintext method, in that the
- password sent by the client is compared to the password stored on the
- server. However, in this case, the password is first encoded using a
- hashing algorithm. It is encoded by the client before being sent
- across the wire, and it is encoded by the server (having retrieved it
- in plaintext) before making the comparison. : The algorithm used is
- the NIST SHA-1 message digest algorithm.[6]: This algorithm
- takes arbitrary input and produces a fingerprint or "message digest"
- of it.[7]: A
- random string, shared between the client and the server, is appended
- to the password before being passed to the hashing algorithm. This
- random string is the connection ID—the value of the id
- attribute in the server's XML stream header response that we saw in
- Example 7-3:
RECV: <?xml version='1.0'?> <stream:stream
xmlns:stream='http://etherx.jabber.org/streams' id='1ED34A55'
xmlns='jabber:client' from='yak'>
- which means, in the case where the password is "secret," the string
- that will be hashed is:
secret1ED34A55
- which is:
03ea09f012493415908d63dcb1f6dbdb9bfc09ba
- The digested password is transmitted to the server inside the
- <digest/> tag. : mod_auth_digest is unlike the
- other two modules in that it doesn't take any responsibility for
- storing passwords; it leaves that to mod_auth_plain, as the
- plaintext password must be rehashed with a new random suffix each
- time.
- Advantages
- No plaintext password is transmitted across the wire from client to
- server, and only a small amount of computation—a single SHA-1 hash
- process—is required.
- Disadvantages
- The password is still stored in plaintext on the server.
Zero-knowledge authentication method
The zero-knowledge
authentication method is so-called as the server requires no knowledge
of the password in order to check the credentials. It makes use of the
same hashing algorithm used in the digest authentication method.
- Method
- Just as mod_auth_plain is responsible for storing the password (in
- plaintext) when a user is created or when the password is changed, so
- mod_auth_0k is responsible for storing its version of the
- password, actually, the information it needs (originally based on
- the password) to check the client's credentials in a zero-knowledge
- authentication process. : As we know from the user registration and
- password change processes, any new password is supplied to the server
- in plaintext. This is where a secure (SSL) connection is critical for
- complete security. While the mod_auth_plain module just stores
- that password as is, the mod_auth_0k module stores a sequenced
- hash of the password instead. : What does this mean? The server
- stores a value that is the password hashed with an arbitrary string
- token multiple (N) times, recursively. It doesn't store the
- password itself. It also remembers how many times it has been hashed
- (N). : Whenever a client wants to authenticate, the server sends
- the client the string token and the value of N. The client, having
- obtained the password from the user, performs the same iterative
- hashing sequence that the server performed when it was originally
- given the password but performs the sequence N-1 times. It passes
- the result of this sequence to the server, which does one more hash
- to go from N-1 to N, and compares what it gets with what it
- has stored. If they match, authentication is successful, and the
- server stores the N-1 hash passed from the client and decrements
- N, ready for next time. : The server sends the client the
- information it needs in two tags:
- <sequence>
- The value of N is sent in the <sequence/> tag.
- <token>
- The arbitrary string token to be hashed with the password is sent in
- the <token/> tag. : The N-1 hash value is passed by
- the client to the server in the <hash/> tag. : By way
- of illustration, Example 7-4 shows a small Perl script that a Telnet
- user can use to make the necessary hashing computations when wishing
- to authenticate with a Jabber server using the zero-knowledge method.
A script implementing the client-side zero-knowledge process
#!/usr/bin/perl -w
use strict; use Digest::SHA1 qw(sha1_hex); use Getopt::Std;
our($opt_p, $opt_t, $opt_s); getopt('pts');
unless ($opt_p and $opt_t and $opt_s) { print "Usage: $0 -p <password> -t <token> -s <sequence>\n"; exit; }
- Initial hash of password
my $hash = sha1_hex($opt_p);
- Sequence 0: hash of hashed-password and token
$hash = sha1_hex($hash.$opt_t);
- Repeat N-1 times
$hash = sha1_hex($hash) while $opt_s--;
print "$hash\n";
- To use the script, pass the data via the parameters, using the values
- obtained from the response to the initial IQ-get. The value that the
- script produces for the value to send in the <hash/>
- tag, like this: :
$ ./0k Usage: 0k -p <password> -t <token> -s
<sequence>
$ ./0k -p secret -t ABCDE -s 400
01945b9b2c3207c4cce5dc99e50605779f570077 $
- Advantages
- No password is stored on the server. No (plaintext) password is
- transmitted from client to server.
- Disadvantages
- This method is slightly more compute-intensive than the other methods.
- There is still a security weak spot in the procedure, when the
- password is set or reset, as it must be passed in plaintext. A reset
- is required when N reaches 0, which is another disadvantage—a
- password can be used only N times before a plaintext reset.
Choosing the Authentication Method
Now that we know a little bit
about the authentication methods, let's jump back to the initial IQ-get
query in Example 7-3:
SEND: <iq type='get'> <query xmlns='jabber:iq:auth'>
<username>dj</username> </query> </iq>
RECV: <iq type='result'> <query xmlns='jabber:iq:auth'> <username>dj</username> <password/> <digest/> <sequence>496</sequence> <token>3B2DEEC0</token> <resource/> </query> </iq>
What we're actually seeing here is the result of the authentication
modules announcing their readiness to authenticate the user
dj. The query is passed to each of the modules.
mod_auth_plain announces its readiness by inserting the
<password/> flag, mod_auth_digest does the same with
the <digest/> flag, and mod_auth_0k inserts the
<sequence/> and <token/> tags and values,
which is what the client will need if it wishes to authenticate using
the zero-knowledge method. The <resource/> tag, which is
required in any authentication, is finally added before the result is
returned.
This way, the IQ-result can convey which authentication methods are available; if the mod_auth_plain and mod_auth_digest modules were to be commented out in the module load directive list, as we saw earlier, then the IQ-result would look like this without the <password/> or <digest/> tags:
RECV: <iq type='result'> <query
xmlns='jabber:iq:auth'> <username>dj</username>
<sequence>496</sequence> <token>3B2DEEC0</token>
<resource/> </query> </iq>
{{Note|At the beginning of this section, we strongly recommended using the IQ-get in the jabber:iq:auth namespace, before proceeding with the IQ-set. This was primarily to be able to check what authentication methods were supported. However, as you can see here, it is in fact essential in the case of the zero-knowledge authentication method, because without the <sequence/> and <token/> information, we cannot begin our hashing sequence.
</code>
Password Errors and Retries
If you don't supply all the required
parameters, you are notified with a 406 "Not Acceptable" error:
SEND: <iq type='set'> <query xmlns='jabber:iq:auth'>
<username>dj</username>
<digest>03ea09f012493415908d63dcb1f6dbdb9bfc09ba</digest>
</query> </iq>
RECV: <iq type='result' id='pthsock_client_auth_ID'> <query xmlns='jabber:iq:auth'> <username>dj</username> <digest>03ea09f012493415908d63dcb1f6dbdb9bfc09ba</digest> </query> <error code='406'>Not Acceptable</error> </iq>
In this case, no <resource/> value was supplied. The
<resource/> value is required, as it is the key part of
the JID that is used to distinguish between multiple connections as the
same user on the same Jabber server.
If you get the password wrong, you receive a 401 "Not Authorized" error. There is currently no limit on the number of times an authorization attempt can be made, but the <karma/> limits for the c2s connections (see Chapter 5) may slow the attempts down.
User Registration Script
Currently, the Jabber technology has no
concept of anonymous users—users who can connect to the server with
no previous registration requirement. Until this is possible, we have
to make do with creating specific users for specific scenarios.
To this end, it would be useful to be able to run a quick script to register a new user, rather than grab an existing client, start it up, and go through the process of registering a new user with the Jabber server, with whatever window navigation and mouse clicking that might entail. All the script must do is interact with the Jabber server in the context of the jabber:iq:register namespace, specifically, pre-session. It must be able to make a registration inquiry by sending an IQ-get and returning the fields listed in the result and to make a registration attempt, when supplied with values for the registration fields.
We should be able to invoke our script, reguser, in one of two ways. The first, specifying merely a hostname (and optional port, which will default to 5222 if not specified), implies a registration inquiry, that we wish to make an inquiry of the Jabber server as to (a) whether registration is possible and (b) if so, what fields are required. The second way, specifying not only the host and optional port but also a list of field name and value pairs, implies a registration attempt. Example 7-5 shows both these ways in action.
Uses of the reguser script
$ ./reguser yak:5222 [Enquiry] Fields: username
password email name
$ ./reguser yak username=joseph password=spinach 'name=Joseph Adams' email=joseph@yak [Attempt] (joseph) Successful registration
$ ./reguser yak username=dj password=secret 'name=DJ Adams' email=dj@yak [Attempt] (dj) Error: 409 (Username Not Available) As it's our first substantial script, let's take it step by step. It's written in Perl and uses the Net::Jabber module:
#!/usr/bin/perl
use strict; use Net::Jabber 1.0022 qw(Client);
use constant NS_REGISTER => 'jabber:iq:register';
unless (@ARGV) { print usage(); exit; }
We start out with some basic start-of-script housekeeping: delaring our
usage of Net::Jabber, setting a constant for the
jabber:iq:register namespace, and handling the case of being
"wrongly" invoked by giving some help text from the usage()
subroutine. The specification of Client in the line:
use Net::Jabber 1.0022 qw(Client);
means that the connection is going to be client-based; in other words,
the namespace that will be used to qualify the XML stream header that
Net::Jabber will produce is jabber:client.
The Net::Jabber module has changed as it has matured over the recent versions (1.0020, 1.0021, and 1.0022), and these changes do sometimes affect how the scripts that use Net::Jabber should be written. So we explicitly specify in the use statement which version of Net::Jabber we require, to avoid confusion.
my ($host, $port) = split(":", shift @ARGV); $port ||= 5222;
my $c = Net::Jabber::Client->new();
defined($c->Connect( hostname => $host, port => $port, )) or die "Cannot reach Jabber server at $host:$port\n";
my ($iq, $query, $result);
We parse the hostname and port, defaulting the latter to 5222 if it
wasn't specified. Then we create a new instance of the
Net::Jabber::Client object. The Net::Jabber family of
modules presents its collective functions in an object-oriented way. The
scalar $c represents the "client" mechanism with which we
connect to the Jabber server.
With the Connect() method, we make a connection to the Jabber server; the namespace of the XML stream for this connection, sent to the Jabber server in the stream header, is jabber:client.
# Registration attempt or inquiry?
if (scalar @ARGV) {
# Attempt: Send <iq type='set'> <query # xmlns='jabber:iq:register'> <username>...</username> # <password>...</password> ... </query> </iq>
print "[Attempt] ";
$iq = Net::Jabber::IQ->new(); $iq->SetType('set'); $query =
$iq->NewQuery(NS_REGISTER);
We work out what we have to do by looking to see if any extra parameters
beyond the hostname and port were specified. If there were, we need to
build an IQ-set in the jabber:iq:register namespace to make a
registration attempt.
The Net::Jabber::IQ module represents the IQ model and provides methods to manage IQ packets. With the new() constructor, we create a new, empty IQ packet in $iq, and set its type attribute to set.
As we know, the <query/> part of an IQ packet is contained within the <iq/> tag. The NewQuery() method, called on an IQ packet, creates a <query/> tag as a child of that IQ packet and delivers us a handle on that <query/> tag—which we store in $query—so that we can manipulate it independently of the IQ packet that wraps around it. The jabber:iq:register namespace value is passed as a parameter to the NewQuery() call to set the correct xmlns namespace attribute.
Figure 7-2 shows what the packet looks like at this stage and how the scalar object references $iq and $query relate to it.
</code> In our foreach
loop, we run through the list of parameters, in the form
fieldname=value, and call a Set method on the
$query object (the <query/> packet) to add
child tags:
foreach my $arg (@ARGV) { my ($field, $value) = split('=",
$arg); print "($value) " if $field eq 'username'; eval
'$query->Set'.ucfirst($field).'($value)';
}
$result = $c->SendAndReceiveWithID($iq);
Net::Jabber::Query provides a number of SetXXXX
methods that are available according to namespace. These "set" methods
available for the jabber:iq:register namespace are plentiful
and include SetName, SetEmail, SetPhone, and
so on. Each method will create a child tag named after the method (e.g.,
SetName will create a <name/> tag, and
SetPhone will create a <phone/> tag) and insert
the value passed to the method into the tag.
For example:
$query->SetName('DJ Adams');
will insert (or amend) a tag in the <query/> thus:
<iq type='set'> <query xmlns='jabber:iq:register'>
<name>DJ Adams</name> </query> </iq>
We use eval to allow us to make our SetXXXX method
calls dynamically, according to each field name specified. The
ucfirst() function is used to change the first character of the
field name to uppercase, to suit the SetXXXX method naming
conventions.
Once we've added all the fields, we send the complete packet ($iq) to the server using the SendAndReceiveWithID() method on the connection ($c) object. This method is extremely powerful and does many things for us. It keeps the process of writing small scripts like this very simple. It adds a unique id attribute to the <iq/> packet, transmits the packet over the XML stream, and waits for a response.
"Hey, what about the event model that we read about?" you might ask. Of course, Net::Jabber supports an event-model programming style, but for now we can get away with keeping our code "procedural" (and short) using this high-level method that does everything we want. After all, in any one execution of the script, we wish to send only one packet to the Jabber server and receive one back. Nothing more complicated than that.
Recipes in later chapters will demonstrate the event model.
The response is stored in $result and is itself an IQ packet, as we expect. So $result is a handle on a Net::Jabber::IQ object that we can now manipulate:
# Success if ($result->GetType() eq 'result') { print
"Successful registration\n";
}
# Failure
else { print "Error: ", $result->GetErrorCode(), " (",
$result->GetError(), ")\n";
}
}
We check the type of the IQ returned from the server. If it's a
result:
RECV: <iq type='result' id='1'> <query
xmlns='jabber:iq:register'/> </iq>
then great—the registration was successful. Otherwise, we can grab the
error code and description from the <error/> element:
RECV: <iq type='error'> <query
xmlns='jabber:iq:register'> <username>dj</username>
<password>secret</password> </query> <error
code='409'>Username Not Available</error> </iq>
using the GetError() and GetErrorCode() methods on the
IQ object.
We go through a similar process if there are no further parameters following the host[:port] specification:
else {
# Inquiry: Send <iq type='get'><query # xmlns='jabber:iq:register'/></iq>
print "[Inquiry] ";
$iq = Net::Jabber::IQ->new(); $iq->SetType('get'); $query =
$iq->NewQuery(NS_REGISTER);
$result = $c->SendAndReceiveWithID($iq);
The only difference here is that we set the IQ type to get, not
set, and we don't insert any tags into the $query
object, before sending the packet off and waiting for a response:
If we receive a result type, like this:
RECV: <iq type='result'> <query
xmlns='jabber:iq:register'> <instructions> Choose a username
and password to register with this server. </instructions>
<name/> <email/> <username/> <password/>
</query> </iq>
then we need to extract the fields listed in the <query/>
tag and return them to the user.
# Success if ($result->GetType() eq 'result') { $query =
$result->GetQuery();
my %contents = $query->GetRegister(); delete
$contents{'instructions'}; print "Fields: ", join(', ', keys
%contents), "\n";
}
While the NewQuery() method creates a new
<query/> tag inside an IQ object, the GetQuery()
method retrieves an existing one, in the form of a
Net::Jabber::Query object whose handle we store in
$query. We can call the GetRegister() method on this
query object, which returns a hash of the contents:
( 'instructions' => 'Choose a username and password ...',
'name' => undef, 'email' => undef, 'username'
=> undef, 'password' => undef )
And, after removing the "instructions," we can display them as the
result.
When an error is returned in response to the IQ-get (perhaps no registrations are allowed), we display the error in the same way as before:
# Failure else { $query = $result->GetQuery(); print "Error:
", $result->GetErrorCode(), " (", $result->GetError(), ")\n";
}
}
When we've finished, we close the connection and exit. Here we also have
the usage() subroutine defined:
$c->Disconnect;
exit;
sub usage {
<<EOF Usage: Enquiry: reguser host[:port] Attempt: reguser host[:port] field1=value1 [fieldN = valueN] ... EOF
}
Using the Script
The script is very basic, but it gets the job
done. It is suitable for calling from another script, for mass user
generation, although you may wish to modify it so that a connection is
not created and destroyed for every username that needs to be
registered.[8]
It also illustrates how simple a Jabber client can be. In this case, the Net::Jabber libraries mask the bulk of the effort (socket connection, XML stream negotiation, XML fragment traffic management, and so on). We'll be making use of this script to create users for our recipes later on in the book.
